commit 31c9602d0c4cf05e322b29e33577848b8b1200a7 Author: Prad Nukala Date: Fri Oct 16 10:51:26 2026 -0400 feat: back up omp config (agent settings, commands, extensions, themes, loops, plugins) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7706732 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# This repo is checked out directly at ~/.omp (chezmoi external), which is +# mostly runtime state: dbs, sessions, blobs, caches, logs, oauth, natives, +# browser profiles, device ids. Ignore everything; whitelist config only. +/* +!/.gitignore +!/marketplaces.json + +!/agent/ +/agent/* +!/agent/config.yml +!/agent/mcp.json +!/agent/keybindings.yml +!/agent/commands/ +!/agent/extensions/ +!/agent/themes/ + +!/loops/ +/loops/*/state.md +/loops/*/runs.log +/loops/escalations.log + +!/plugins/ +/plugins/* +!/plugins/package.json +!/plugins/bun.lock +!/plugins/omp-plugins.lock.json +!/plugins/installed_plugins.json diff --git a/agent/commands/leetcode-docs.md b/agent/commands/leetcode-docs.md new file mode 100644 index 0000000..3e397d1 --- /dev/null +++ b/agent/commands/leetcode-docs.md @@ -0,0 +1,84 @@ +--- +description: Convert a LeetCode solution file into a Fumadocs MDX docs page +--- + +Convert the LeetCode solution file at `$1` into a Fumadocs-formatted MDX page. + +Extra instructions (optional, may include output dir override): $@ + +## Input format + +The source file follows the standard on-disk format: +- A `/* ... */` header block: problem number, title, difficulty, LeetCode URL, problem description, examples, constraints +- A JSDoc `@param`/`@return` block +- One or more solution functions (possibly with line-by-line beginner comments and `// ====` banners) + +## Output location + +Default: write next to the content root at `content/docs/leetcode///-.mdx`, mirroring the source tree (e.g. `Easy/Array/1365.how-many-numbers.js` → `content/docs/leetcode/easy/array/1365-how-many-numbers.mdx`). Lowercase all path segments. If the extra instructions specify a different output path or content root, use that instead. Create directories as needed. + +## Page structure + +Produce exactly this shape: + +```mdx +--- +title: 1365. How Many Numbers Are Smaller Than the Current Number +description: +--- + + + [View on LeetCode](https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/) + + +## Problem + + + +## Examples + + + +```txt title="Example 1" +Input: nums = [8,1,2,2,3] +Output: [4,0,1,1,3] +``` + + + +## Constraints + + + +## Solution + + + +```js title="solution.js" lineNumbers + +``` + + + **Time:** O(n log n) — **Space:** O(n) + +``` + +## Rules + +1. **Frontmatter `title` carries the h1** — Fumadocs renders it as the page title, so never emit a `# heading` in the body. +2. **Escape MDX hazards.** The header text may contain `<`, `>`, `{`, `}` (e.g. `j != i and nums[j] < nums[i]`, `2 <= nums.length <= 500`). Anything with these characters must be wrapped in inline code or fenced blocks, or the MDX compile will fail. When in doubt, inline-code it. +3. **Code is verbatim.** Copy the solution function(s) exactly — do not rewrite, reformat, or strip existing comments. The docs page documents the code as it exists. +4. **Multiple approaches → tab group:** + ````mdx + ```js tab="Frequency Map" tab-group="approach" + ... + ``` + + ```js tab="Sort + Map" + ... + ``` + ```` +5. **Complexity callout is derived, not guessed blindly.** Read the code: nested loop → O(n²), sort → O(n log n), single pass + map → O(n). If genuinely ambiguous, state the dominant term and why in one clause. +6. **Callout header line** = ` · `. Difficulty comes from the header block; infer 1–2 topic tags from the directory path and the technique used. +7. **No invented content.** Problem statement, examples, and constraints come only from the header block. The intuition paragraph and complexity are the only sections you author. +8. **After writing**, print one line: the output path. Nothing else. diff --git a/agent/commands/leetcode-eli5.md b/agent/commands/leetcode-eli5.md new file mode 100644 index 0000000..7224f90 --- /dev/null +++ b/agent/commands/leetcode-eli5.md @@ -0,0 +1,74 @@ +--- +description: Add super-simple line-by-line comments to a LeetCode solution file +--- + +Add beginner-level line-by-line comments to the LeetCode solution file at: $1 + +## Rules + +1. **Read the file first.** It follows the standard on-disk format: + - A `/* ... */` header block containing problem number, title, difficulty, URL, description, examples, and constraints + - A JSDoc `@param`/`@return` block + - The solution function(s) + +2. **Never touch the header block or JSDoc block.** Preserve them byte-for-byte. + +3. **Never change the code.** Do not rename variables, reformat logic, alter whitespace inside expressions, or "improve" anything. Comments only. The file must produce an identical AST after your edit. + +4. **Comment style — explain as if to a complete beginner who has never programmed:** + - One comment line (or short block) ABOVE each meaningful line/statement + - Use plain everyday language and physical metaphors: + - object/map → "box" ("Create an empty box to count how many times each number appears") + - increment → "add 1 to its count" + - `(freq[x] || 0) + 1` → "If we already saw this number, add 1 to its count. If we never saw it before, start counting from 1" + - sort comparator → spell out the rule in words ("put the letter that appears MORE times first; if tied, alphabetical order") + - return → say WHAT is being returned and WHY in plain words + - Explain the WHY, not just the WHAT ("so the next bigger numbers know there are more smaller ones") + - Inline comments (`// ...` at end of line) are fine for short trailing clarifications like `.join('')` → "glue the sorted letters back into a string" + - No jargon without immediate translation: if you must say "index", follow with "(its position)" + +5. **Add a section banner** above the solution function in this exact style: + ```js + // ============================================ + // . + // ============================================ + ``` + Pull the number and title from the header block. + +6. **Multiple solutions in one file:** comment every one of them, each with its own banner (append "– Approach 2" etc. to the banner title). + +7. **Extra instructions** (if provided after the file path): $@ + +8. **After editing**, print a one-line confirmation: file path + number of comment lines added. Nothing else. + +## Example transformation + +Input body: +```js +var smallerNumbersThanCurrent = function (nums) { + const freq = {}; + for (let x of nums) freq[x] = (freq[x] || 0) + 1; + return nums.map((x) => smaller[x]); +}; +``` + +Output body: +```js +// ============================================ +// 1365. How Many Numbers Are Smaller Than the Current Number +// ============================================ + +var smallerNumbersThanCurrent = function (nums) { + // Create an empty box to store how many times each number appears + const freq = {}; + + // Go through every number in the list one by one + // If we already saw this number, add 1 to its count + // If we never saw it before, start counting from 1 + for (let x of nums) freq[x] = (freq[x] || 0) + 1; + + // Go through the original list and replace each number + // with "how many numbers are smaller than it" + return nums.map((x) => smaller[x]); +}; +``` diff --git a/agent/config.yml b/agent/config.yml new file mode 100644 index 0000000..4ec289d --- /dev/null +++ b/agent/config.yml @@ -0,0 +1,31 @@ +theme: + dark: titanium + light: light +symbolPreset: nerd +setupVersion: 2 +hideThinkingBlock: false +modelRoles: + plan: anthropic/claude-fable-5-1:high + default: anthropic/claude-opus-5-5:high + smol: anthropic/claude-haiku-5-5 + task: anthropic/claude-sonnet-5-5 + web: web/exa + dictation: local/parakeet-tdt-0.6b-v3 + speech: local/kokoro + image: openai/gpt-image-2 + advisor: anthropic/claude-mythos-5-1:xhigh + slow: anthropic/claude-fable-5-1:high + vision: openai/gpt-6-astra:high + commit: cerebras/qwen-3-coder-480b + tiny: opencode-go/step-5-preview:auto + memory: opencode-go/gemini-4-argon-contributor:medium + judge: anthropic/claude-fable-5-1:max +composer: + shape: rule +modelPresets: + Primary: + modelRoles: + plan: anthropic/claude-fable-5-1:high + default: anthropic/claude-opus-5-5:high + smol: anthropic/claude-haiku-5-5 + defaultThinkingLevel: high \ No newline at end of file diff --git a/agent/extensions/herdr-omp-agent-state.ts b/agent/extensions/herdr-omp-agent-state.ts new file mode 100644 index 0000000..fce6210 --- /dev/null +++ b/agent/extensions/herdr-omp-agent-state.ts @@ -0,0 +1,471 @@ +// installed by herdr +// managed by herdr; reinstalling or updating the integration overwrites this file. +// add custom hooks/plugins beside this file instead of editing it. +// HERDR_INTEGRATION_ID=omp +// HERDR_INTEGRATION_VERSION=10 +// @ts-nocheck + +import net from "node:net"; +import path from "node:path"; + +const HERDR_ENV = process.env.HERDR_ENV; +const socketPath = process.env.HERDR_SOCKET_PATH; +const socketEndpoint = + process.platform === "win32" && socketPath ? `\\\\.\\pipe\\${socketPath}` : socketPath; +const paneId = process.env.HERDR_PANE_ID; +const source = "herdr:omp"; +// OMP marks every shell it spawns with OMPCODE=1. A nested `omp` launched from +// a parent session's shell inherits it, so that process is not the pane's root +// agent and must not report its short-lived session over the parent's. +const nestedOmpSession = process.env.OMPCODE === "1"; + +function enabled() { + return HERDR_ENV === "1" && !!socketPath && !!paneId && !nestedOmpSession; +} + +let requestQueue = Promise.resolve(); + +function sendRequestAttempt(request: unknown, timeoutMs: number): Promise { + if (!enabled()) { + return Promise.resolve(true); + } + + return new Promise((resolve) => { + let done = false; + let timeout: ReturnType | undefined; + const finish = (delivered: boolean) => { + if (done) return; + done = true; + if (timeout) { + clearTimeout(timeout); + } + socket.destroy(); + resolve(delivered); + }; + + const socket = net.createConnection(socketEndpoint!); + socket.on("error", () => finish(false)); + socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`)); + socket.on("data", () => finish(true)); + socket.on("end", () => finish(false)); + timeout = setTimeout(() => finish(false), timeoutMs); + timeout.unref?.(); + }); +} + +async function sendRequestNow(request: unknown): Promise { + if (await sendRequestAttempt(request, 500)) { + return; + } + await sendRequestAttempt(request, 1500); +} + +function sendRequest(request: unknown): Promise { + requestQueue = requestQueue.then( + () => sendRequestNow(request), + () => sendRequestNow(request), + ); + return requestQueue; +} + +type AgentState = "working" | "blocked" | "idle"; + +type QueuedState = { + state: AgentState; + message?: string; + seq: number; +}; + +const idleDebounceMs = parseDurationEnv("HERDR_OMP_IDLE_DEBOUNCE_MS", 250); +const retryGraceMs = parseDurationEnv("HERDR_OMP_RETRY_GRACE_MS", 2500); +const retryableErrorPattern = + /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i; +let reportSeq = Date.now() * 1000; +let currentAgentSessionId: string | undefined; +let currentAgentSessionPath: string | undefined; + +function nextReportSeq(): number { + reportSeq += 1; + return reportSeq; +} + +export function isAbsoluteSessionPath(file: unknown): file is string { + return ( + typeof file === "string" && + (path.posix.isAbsolute(file) || path.win32.isAbsolute(file)) + ); +} + +function updateSessionRef(ctx: any): void { + try { + const file = ctx?.sessionManager?.getSessionFile?.(); + currentAgentSessionPath = isAbsoluteSessionPath(file) ? file : undefined; + } catch { + currentAgentSessionPath = undefined; + } + + try { + const id = ctx?.sessionManager?.getSessionId?.(); + currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : undefined; + } catch { + currentAgentSessionId = undefined; + } +} + +function withSessionRef(params: Record): Record { + if (currentAgentSessionPath) { + return { ...params, agent_session_path: currentAgentSessionPath }; + } + if (currentAgentSessionId) { + return { ...params, agent_session_id: currentAgentSessionId }; + } + return params; +} + +function parseDurationEnv(name: string, fallback: number): number { + const raw = process.env[name]; + if (!raw) { + return fallback; + } + const parsed = Number.parseInt(raw, 10); + if (!Number.isFinite(parsed) || parsed < 0) { + return fallback; + } + return parsed; +} + +function currentSessionRef(): Record | undefined { + if (currentAgentSessionPath) { + return { agent_session_path: currentAgentSessionPath }; + } + if (currentAgentSessionId) { + return { agent_session_id: currentAgentSessionId }; + } + return undefined; +} + +function reportSession(sessionStartSource = "startup"): Promise { + const sessionRef = currentSessionRef(); + if (!sessionRef) { + return Promise.resolve(); + } + + return sendRequest({ + id: `${source}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`, + method: "pane.report_agent_session", + params: { + pane_id: paneId, + source, + agent: "omp", + seq: nextReportSeq(), + session_start_source: sessionStartSource, + ...sessionRef, + }, + }); +} + +function sendState(state: AgentState, message?: string, seq = nextReportSeq()): Promise { + return sendRequest({ + id: `${source}:${Date.now()}:${Math.random().toString(36).slice(2)}`, + method: "pane.report_agent", + params: withSessionRef({ + pane_id: paneId, + source, + agent: "omp", + state, + message, + seq, + }), + }); +} + +let sendInFlight = false; +let queuedState: QueuedState | undefined; + +function queueState(state: AgentState, message?: string): void { + queuedState = { state, message, seq: nextReportSeq() }; + if (!sendInFlight) { + void drainStateQueue(); + } +} + +async function drainStateQueue(): Promise { + if (sendInFlight) { + return; + } + + sendInFlight = true; + try { + while (queuedState) { + const next = queuedState; + queuedState = undefined; + await sendState(next.state, next.message, next.seq); + } + } finally { + sendInFlight = false; + if (queuedState) { + void drainStateQueue(); + } + } +} + +function lastAssistantMessage(messages: unknown[]): any | undefined { + for (let i = messages.length - 1; i >= 0; i -= 1) { + const message = messages[i] as any; + if (message?.role === "assistant") { + return message; + } + } + return undefined; +} + +function retryableErrorMessage(event: any): string | undefined { + const messages = Array.isArray(event?.messages) ? event.messages : []; + const assistant = lastAssistantMessage(messages); + if (assistant?.stopReason !== "error") { + return undefined; + } + + const errorMessage = String(assistant.errorMessage ?? ""); + if (!retryableErrorPattern.test(errorMessage)) { + return undefined; + } + return errorMessage || "retryable provider error"; +} + +function askBlockedMessage(args: any): string { + const questions = Array.isArray(args?.questions) ? args.questions : []; + const firstQuestion = questions.find((question: any) => typeof question?.question === "string"); + if (firstQuestion?.question) { + return firstQuestion.question; + } + return "waiting for user input"; +} + +export default function (pi) { + if (!enabled()) { + return; + } + + let agentActive = false; + let retryHoldActive = false; + let failureBlocked = false; + let failureMessage: string | undefined; + let blockedCount = 0; + let blockedMessage: string | undefined; + let lastState: AgentState | undefined; + let lastMessage: string | undefined; + let idleTimer: ReturnType | undefined; + let retryTimer: ReturnType | undefined; + let rootSession = false; + + function clearTimer(timer: ReturnType | undefined) { + if (timer) { + clearTimeout(timer); + } + } + + function clearPendingTimers() { + clearTimer(idleTimer); + clearTimer(retryTimer); + idleTimer = undefined; + retryTimer = undefined; + } + + function clearFailureState() { + retryHoldActive = false; + failureBlocked = false; + failureMessage = undefined; + } + + function desiredState() { + if (blockedCount > 0) { + return { state: "blocked" as const, message: blockedMessage }; + } + if (failureBlocked) { + return { state: "blocked" as const, message: failureMessage }; + } + if (agentActive || retryHoldActive) { + return { state: "working" as const, message: undefined }; + } + return { state: "idle" as const, message: undefined }; + } + + function publishState(force = false) { + const next = desiredState(); + if (!force && next.state === lastState && next.message === lastMessage) { + return; + } + lastState = next.state; + lastMessage = next.message; + queueState(next.state, next.message); + } + + function scheduleIdle() { + clearPendingTimers(); + clearFailureState(); + idleTimer = setTimeout(() => { + idleTimer = undefined; + publishState(); + }, idleDebounceMs); + idleTimer.unref?.(); + } + + function holdForRetry(message: string) { + clearPendingTimers(); + retryHoldActive = true; + failureBlocked = false; + failureMessage = message; + publishState(); + + retryTimer = setTimeout(() => { + retryTimer = undefined; + retryHoldActive = false; + failureBlocked = true; + publishState(); + }, retryGraceMs); + retryTimer.unref?.(); + } + + function activateRootSession(ctx: any, sessionStartSource = "startup"): boolean { + if (ctx?.hasUI !== true) { + return false; + } + rootSession = true; + updateSessionRef(ctx); + void reportSession(sessionStartSource); + return true; + } + + function resetSessionState() { + clearPendingTimers(); + clearFailureState(); + agentActive = false; + blockedCount = 0; + blockedMessage = undefined; + } + + function activateBlocked(message: string | undefined) { + clearPendingTimers(); + blockedCount += 1; + blockedMessage = message; + publishState(); + } + + function deactivateBlocked() { + blockedCount = Math.max(0, blockedCount - 1); + if (blockedCount === 0) { + blockedMessage = undefined; + } + publishState(); + } + + pi.events.on("herdr:blocked", (data) => { + if (!rootSession) { + return; + } + if (!data?.active) { + deactivateBlocked(); + return; + } + + activateBlocked(data.label); + }); + + pi.on("session_start", (_event, ctx) => { + if (!activateRootSession(ctx)) { + return; + } + // A reload can replace this extension mid-run without emitting another agent_start. + agentActive = ctx?.isIdle?.() === false; + publishState(true); + }); + + pi.on("session_switch", (event, ctx) => { + if (!activateRootSession(ctx, event?.reason || "resume")) { + return; + } + resetSessionState(); + publishState(true); + }); + + pi.on("agent_start", (_event, ctx) => { + if (!rootSession && !activateRootSession(ctx)) { + return; + } + updateSessionRef(ctx); + void reportSession(); + clearPendingTimers(); + clearFailureState(); + agentActive = true; + publishState(); + }); + + pi.on("tool_approval_requested", (event, ctx) => { + if (!rootSession && !activateRootSession(ctx)) { + return; + } + const label = event?.reason || `${event?.toolName || "Tool"} approval`; + activateBlocked(label); + }); + + pi.on("tool_approval_resolved", (_event, ctx) => { + if (!rootSession && !activateRootSession(ctx)) { + return; + } + deactivateBlocked(); + }); + + pi.on("tool_execution_start", (event, ctx) => { + if (event?.toolName !== "ask") { + return; + } + if (!rootSession && !activateRootSession(ctx)) { + return; + } + activateBlocked(askBlockedMessage(event.args)); + }); + + pi.on("tool_execution_end", (event, ctx) => { + if (event?.toolName !== "ask") { + return; + } + if (!rootSession && !activateRootSession(ctx)) { + return; + } + deactivateBlocked(); + }); + + pi.on("agent_end", (event) => { + if (!rootSession) { + return; + } + if (!agentActive) { + // OMP can emit duplicate/late end events while auto-retry is already + // holding the pane in Working. Do not let an unqualified duplicate end + // cancel the retry hold and publish a false Idle. + return; + } + if (event?.willContinue === true) { + // A continuation is already scheduled, so this end is not a settle. + // Older builds omit the field and fall through as before. + return; + } + + agentActive = false; + + const retryableMessage = retryableErrorMessage(event); + if (retryableMessage) { + holdForRetry(retryableMessage); + return; + } + + scheduleIdle(); + }); + + pi.on("session_shutdown", () => { + if (rootSession) { + clearPendingTimers(); + } + }); +} diff --git a/agent/keybindings.yml b/agent/keybindings.yml new file mode 100644 index 0000000..a3f50ec --- /dev/null +++ b/agent/keybindings.yml @@ -0,0 +1,2 @@ +app.editor.external: Ctrl+E +app.exit: Ctrl+Q diff --git a/agent/mcp.json b/agent/mcp.json new file mode 100644 index 0000000..676c8fe --- /dev/null +++ b/agent/mcp.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json", + "mcpServers": { + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "!printf 'Bearer %s' \"$(gh auth token)\"" + } + }, + "obscura": { + "type": "stdio", + "command": "obscura", + "args": [ + "mcp" + ] + } + } +} \ No newline at end of file diff --git a/agent/themes/cyberdream.json b/agent/themes/cyberdream.json new file mode 100644 index 0000000..06d9add --- /dev/null +++ b/agent/themes/cyberdream.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", + "name": "cyberdream", + "vars": { + "bg": "#16181a", + "bgAlt": "#1e2124", + "bgHighlight": "#3c4048", + "fg": "#ffffff", + "grey": "#7b8496", + "blue": "#5ea1ff", + "green": "#5eff6c", + "cyan": "#5ef1ff", + "red": "#ff6e5e", + "yellow": "#f1ff5e", + "magenta": "#ff5ef1", + "pink": "#ff5ea0", + "orange": "#ffbd5e", + "purple": "#bd5eff" + }, + "colors": { + "accent": "blue", + "border": "blue", + "borderAccent": "cyan", + "borderMuted": "grey", + "success": "green", + "error": "red", + "warning": "yellow", + "muted": "grey", + "dim": "grey", + "text": "", + "thinkingText": "grey", + + "selectedBg": "bgHighlight", + "userMessageBg": "bgAlt", + "userMessageText": "fg", + "customMessageBg": "bgAlt", + "customMessageText": "fg", + "customMessageLabel": "cyan", + "toolPendingBg": "bgAlt", + "toolSuccessBg": "bgAlt", + "toolErrorBg": "bgAlt", + "toolTitle": "cyan", + "toolOutput": "fg", + + "mdHeading": "orange", + "mdLink": "cyan", + "mdLinkUrl": "blue", + "mdCode": "cyan", + "mdCodeBlock": "fg", + "mdCodeBlockBorder": "grey", + "mdQuote": "grey", + "mdQuoteBorder": "grey", + "mdHr": "grey", + "mdListBullet": "cyan", + + "toolDiffAdded": "green", + "toolDiffRemoved": "red", + "toolDiffContext": "grey", + + "syntaxComment": "grey", + "syntaxKeyword": "magenta", + "syntaxFunction": "blue", + "syntaxVariable": "yellow", + "syntaxString": "green", + "syntaxNumber": "purple", + "syntaxType": "cyan", + "syntaxOperator": "pink", + "syntaxPunctuation": "grey", + + "thinkingOff": "grey", + "thinkingMinimal": "blue", + "thinkingLow": "cyan", + "thinkingMedium": "green", + "thinkingHigh": "yellow", + "thinkingXhigh": "orange", + "thinkingMax": "red", + + "bashMode": "yellow", + "pythonMode": "purple", + + "statusLineBg": "bgAlt", + "statusLineSep": "grey", + "statusLineModel": "purple", + "statusLinePath": "blue", + "statusLineGitClean": "green", + "statusLineGitDirty": "yellow", + "statusLineContext": "cyan", + "statusLineSpend": "pink", + "statusLineStaged": "green", + "statusLineDirty": "yellow", + "statusLineUntracked": "red", + "statusLineOutput": "fg", + "statusLineCost": "orange", + "statusLineSubagents": "magenta" + } +} diff --git a/loops/answer-gap/loop.md b/loops/answer-gap/loop.md new file mode 100644 index 0000000..bf2150b --- /dev/null +++ b/loops/answer-gap/loop.md @@ -0,0 +1,28 @@ +# Loop 1 — answer-engine gap (green) +# Cadence: Thu 07:00 weekly · Tool: Exa + +You are one round of a weekly answer-gap loop: questions developers ask that +WalletBase should be the answer to, but isn't. + +## Question universe (rotate through, same check per question) +Seed: "how do I give my AI agent a wallet", "how to sponsor gas for users", +"passkey signing for smart accounts", "how to limit what an AI agent can spend", +"ERC-4337 vs EIP-7702 for agents", "revoke an AI agent's permissions onchain". +Plus: new questions harvested from previous rounds (see state file ## queue). + +## Round procedure +1. READ the state file. Find the first question in ## queue not yet processed + (or the next seed question on first runs). +2. For that ONE question: search what currently answers it (docs, blogs, SO, + competitor pages). Log who owns the answer today. +3. Write ONE full content brief under ## queue: exact target question, current + best answer's weakness, outline, code-example requirements, internal + docs/GUIDE.md sections to link. Brief only — a human writes and ships it. +4. Harvest up to 3 NEW adjacent questions discovered during research; append + to ## queue unprocessed. + +## Escalation contract +Never escalate. Gap analysis is routine work. + +## Stop +ONE question per round, ONE brief. Max 15 fetches. Two failures → ## blocked. diff --git a/loops/bin/run-loop.sh b/loops/bin/run-loop.sh new file mode 100755 index 0000000..5ff1676 --- /dev/null +++ b/loops/bin/run-loop.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# run-loop.sh — one round of a loop, cheap-model-first with Fable escalation. +# Green loops only: prompts are read-only + write-to-own-state-file by design. +set -euo pipefail + +LOOP="${1:?usage: run-loop.sh }" +DIR="$HOME/.omp/loops/$LOOP" +PROMPT="$DIR/loop.md" +STATE="$DIR/state.md" +LOG="$DIR/runs.log" +[[ -f "$PROMPT" ]] || { echo "no such loop: $LOOP" >&2; exit 1; } +[[ -f "$STATE" ]] || printf '# %s — state\n\n## queue\n\n## done\n\n## blocked\n' "$LOOP" > "$STATE" + +# Hard caps (loop #21 escalation policy + the article's stop rules): +# - smol model runs the routine round, 10 min wall clock +# - Fable (default model) only re-runs on an explicit ESCALATE marker, 20 min cap +# - one escalation max per round; never more than one round per invocation +SMOL_MAX=600 +FABLE_MAX=1200 + +ts() { date -Is; } +run() { # run — returns the model's stdout + omp -p --no-session --max-time "$2" ${1:+--model "$1"} \ + "$(cat "$PROMPT") + +Loop directory: $DIR +State file (read it first, append to it, never rewrite history): $STATE +Round timestamp: $(ts)" +} + +echo "[$(ts)] round start (smol)" >> "$LOG" +OUT="$(run "kimi-for-coding" "$SMOL_MAX" 2>>"$LOG" || true)" +printf '%s\n' "$OUT" >> "$LOG" + +if grep -q '^ESCALATE' <<<"$OUT"; then + echo "[$(ts)] escalating to default model" >> "$LOG" + OUT="$(run "" "$FABLE_MAX" 2>>"$LOG" || true)" + printf '%s\n' "$OUT" >> "$LOG" + # Escalation ledger (loop #21): every Fable call must be visible and countable + echo "[$(ts)] $LOOP escalated" >> "$HOME/.omp/loops/escalations.log" +fi + +echo "[$(ts)] round end" >> "$LOG" diff --git a/loops/brand-radar/loop.md b/loops/brand-radar/loop.md new file mode 100644 index 0000000..70ff8f4 --- /dev/null +++ b/loops/brand-radar/loop.md @@ -0,0 +1,31 @@ +# Loop 10 — brand-mention feature radar (green) +# Cadence: Wed 07:00 weekly · Tools: HN Algolia API (no key), Exa + +You are one round of a weekly mention-sweep for WalletBase and its problem +space. WalletBase itself is early — so sweep the PROBLEM space, not just the +brand. + +## Queries (same check every round) +- HN Algolia (https://hn.algolia.com/api/v1/search_by_date?query=...): + "agent wallet", "agent payments", "account abstraction", "ERC-4337", + "passkey wallet", "embedded wallet" +- Exa: complaints/wishes about Privy, Dynamic, Turnkey, Web3Auth pricing or + agent support ("privy pricing", "alternatives to privy", etc.) +- Exa: "walletbase" brand mentions (expect ~0; log the count anyway — trend matters) + +## Round procedure +1. READ the state file. Skip item IDs/URLs already logged. +2. Sweep last 7 days. Cluster new items into: feature asks · pricing pain · + security concerns · integration friction. +3. Log clusters with counts and 1–2 representative links each. +4. ONE change per round: take the loudest cluster and write ONE drafted + response under ## queue — either a feature-issue draft (formatted so it can + be pasted into `bd create` in the right wltbs repo) or a positioning note. + Draft only. Never create the issue yourself. + +## Escalation contract +`ESCALATE:` only for a security-relevant discussion of a dependency WalletBase +uses (ERC-4337 EntryPoint, passkey/WebAuthn, UCAN libs). + +## Stop +One sweep, one draft, stop. Max 30 API calls. Two failures → ## blocked, stop. diff --git a/loops/competitor-watch/loop.md b/loops/competitor-watch/loop.md new file mode 100644 index 0000000..8c9a06e --- /dev/null +++ b/loops/competitor-watch/loop.md @@ -0,0 +1,26 @@ +# Loop 4 — competitor-content watch (green) +# Cadence: Fri 07:00 weekly · Tool: Exa web search + +You are one round of a weekly competitor-content loop for WalletBase. + +## Watched competitors (same check every round) +- Privy (privy.io/blog), Dynamic (dynamic.xyz/blog), Turnkey (turnkey.com/blog) +- Crossmint (blog.crossmint.com), Para (getpara.com/blog), Pimlico (pimlico.io/blog) + +## Round procedure +1. READ the state file. Skip URLs already logged. +2. Find posts published in the last 7 days from each competitor. +3. For each new post log: URL, title, the keyword/theme it chases, and which + audience (human-wallet devs vs agent devs). +4. ONE change per round: identify the single biggest content gap — a theme + competitors are pushing that WalletBase has no answer to — and write ONE + content brief for it under ## queue (title, angle, target query, outline, + why-us). One brief, not a list. + +## Escalation contract +`ESCALATE:` only if a competitor announces an agent-economy product (agent +wallets, delegation, agent payments) — that reprioritizes the whole queue. + +## Stop +Six competitors, one pass, one brief, stop. Max 20 page fetches. Two +consecutive tool failures → log under ## blocked, stop. diff --git a/loops/goals/templates.md b/loops/goals/templates.md new file mode 100644 index 0000000..0e1b416 --- /dev/null +++ b/loops/goals/templates.md @@ -0,0 +1,36 @@ +# /goal templates — kill-criteria (#22) and pre-mortem (#23) + +Not scheduled. Paste into an interactive omp session when facing a decision. +Both end with pasted proof per the goal contract: the judge only sees the +conversation, so "done" = the artifact is IN the chat. + +--- + +## kill-criteria (#22) + +/goal For the decision "[DECISION]", produce a kill-criteria table for every +live option. For each option: (1) state the option in one line, (2) write the +SINGLE measurable condition that would disqualify it — a number, a date, or a +named event, not vibes, (3) hunt for evidence for/against that condition using +web search and the repos under ~/Developer/github.com/wltbs, citing every +claim, (4) verdict: ALIVE / KILLED / NEEDS-DATA with what data and where to +get it. Done when the full table is pasted in this chat with a citation per +verdict, and every KILLED verdict quotes the evidence that tripped its +condition. If evidence can't be found for an option, paste what you searched +and mark it NEEDS-DATA — do not guess. Hard cap: 40 tool calls. + +--- + +## pre-mortem (#23) + +/goal It is 12 months from now and the decision "[DECISION]" failed badly. +Write the post-mortem as if it already happened: (1) the headline of the +failure, (2) the 3–5 causal chains that led there, each traced back to a +specific assumption we hold TODAY (quote the assumption from our notes/repos +where possible), (3) for each chain, the earliest observable warning sign and +where it would show up first (a metric, a log, a competitor move, a regulator +page), (4) the one cheapest mitigation per chain that we could start this +week. Done when the full pre-mortem is pasted in this chat and every causal +chain names its present-day assumption explicitly. No generic risks ("market +turns") — every chain must be specific to WalletBase's architecture, GTM, or +regulatory surface. Hard cap: 30 tool calls. diff --git a/loops/intel-refresh/loop.md b/loops/intel-refresh/loop.md new file mode 100644 index 0000000..cb14256 --- /dev/null +++ b/loops/intel-refresh/loop.md @@ -0,0 +1,31 @@ +# Loop 19 — overnight intel refresh (green) +# Cadence: Mon 06:00 weekly · Tool: Exa web search + +You are one round of a weekly intel loop for WalletBase — embedded wallet +infrastructure for the agent economy (ERC-4337 smart accounts, passkeys, +UCAN delegation, in-house bundler/paymaster on Base). + +## Tracked beats (same check every round) +1. ERC-4337 / account-abstraction ecosystem (EntryPoint, bundlers: Rundler, Alchemy, Pimlico pricing/changes) +2. EIP-7702 adoption and wallet implementations +3. Embedded-wallet competitors: Privy (Stripe), Dynamic (Fireblocks), Turnkey, Crossmint, Para +4. Agent-payments protocols: x402, UCAN ecosystem, agent wallet SDKs +5. Base chain: fees, precompiles (RIP-7212), infra changes + +## Round procedure +1. READ the state file first. Skip anything already logged in ## done. +2. Search each beat for items from the last 7 days ONLY. +3. Write at most 5 bullets per beat to the state file under a `## week of ` + heading: link, one-line summary, one-line "so what for WalletBase". +4. ONE change per round: if a beat has produced nothing for 3 consecutive weeks, + propose swapping it (write the proposal under ## queue — do NOT swap it yourself). + +## Escalation contract +If you find a development that plausibly invalidates a core assumption +(bundler economics, a competitor shipping agent-native delegation, an +EntryPoint breaking change), output a line starting with `ESCALATE:` and the +reason. Otherwise never escalate. + +## Stop +One pass over the 5 beats, then stop. If a search tool fails twice, log it +under ## blocked and stop. Never fetch more than 25 pages total. diff --git a/loops/regulatory-digest/loop.md b/loops/regulatory-digest/loop.md new file mode 100644 index 0000000..f20b4a4 --- /dev/null +++ b/loops/regulatory-digest/loop.md @@ -0,0 +1,31 @@ +# Loop 20 — regulatory / source digest (green) +# Cadence: Tue 07:00 weekly · Tool: Exa +# Maps to the WalletBase risk register: MSB/FinCEN, OFAC, MiCA/CASP, MEV policy. + +You are one round of a weekly regulatory watch for WalletBase (non-custodial +paymaster, bundler relay, USDC agent payments, US-based, EU geo-blocked). + +## Watched sources (same check every round) +1. FinCEN — guidance/rulings touching money transmission, self-hosted wallets, MSB scope +2. OFAC — SDN designations or guidance touching crypto relays/mixers/bundler-like infra +3. SEC/CFTC — enforcement touching wallet infra or AA providers +4. EU — MiCA/CASP implementation news relevant to non-custodial infra +5. Ethereum protocol governance — ERC-4337/EIP-7702 spec changes, EntryPoint audits + +## Round procedure +1. READ the state file. Skip items already logged. +2. Search last 7 days per source. ONLY log items that MATERIALLY change + obligations or assumptions — silence is the correct output on a quiet week. +3. Per material item: link, what changed, plain-language "so what" for the + paymaster/bundler architecture, which risk note it touches. +4. ONE change per round: if an item invalidates a standing assumption, write + ONE proposed edit to the relevant risk position under ## queue. + +## Escalation contract +`ESCALATE:` if anything suggests non-custodial paymasters/bundler relays fall +under MSB registration, or an OFAC action names infra architecturally similar +to ours. These are existential — worth Fable's read. + +## Stop +Five sources, one pass, stop. A quiet week produces a one-line "no material +changes " entry — that is a SUCCESS, not a failure. Max 20 fetches. diff --git a/loops/repeat-offender/loop.md b/loops/repeat-offender/loop.md new file mode 100644 index 0000000..f2b5a64 --- /dev/null +++ b/loops/repeat-offender/loop.md @@ -0,0 +1,32 @@ +# Loop 24 — repeat-offender digest (green) +# Cadence: Sun 18:00 weekly · Tools: beads (bd) in ~/Developer/github.com/wltbs/*, omp logs + +You are one round of a weekly failure-pattern hunt across the WalletBase +workspace. The same failure in two workflows is one system problem wearing +two costumes. + +## Sources (same check every round) +1. `bd list --all --json` in each of: ~/Developer/github.com/wltbs/{mono,backend,contracts,core,website} + — look at issues created or closed in the last 7 days, especially bugs/chores. +2. ~/.omp/loops/*/runs.log and ~/.omp/loops/*/state.md ## blocked sections + — the loop harness's own failures. +3. ~/.omp/loops/escalations.log — is any loop escalating too often? + +## Round procedure +1. READ the state file. Note previously identified patterns. +2. Sweep the sources. Cluster failures by ROOT shape, not surface symptom + (e.g. "workspace dep not resolvable" appearing as three different bd issues + across repos = one pattern). +3. Log new/recurring patterns: name, occurrences (issue IDs / log lines), trend. +4. ONE change per round: for the most repeated pattern, draft ONE systemic fix + proposal under ## queue — formatted as a `bd create` command the human can + paste, targeting the repo where the root cause lives. + NEVER run bd create yourself — draft only; a human files it. + +## Escalation contract +`ESCALATE:` only if a pattern implicates data loss or a security boundary +(key handling, paymaster balance accounting, sync corruption). + +## Stop +One sweep, one proposal, stop. Read-only on beads (list/show only). Two +failures → ## blocked, stop. diff --git a/loops/share-of-model/loop.md b/loops/share-of-model/loop.md new file mode 100644 index 0000000..561c02a --- /dev/null +++ b/loops/share-of-model/loop.md @@ -0,0 +1,28 @@ +# Loop 3 — share-of-model watch (green) +# Cadence: Mon 07:00 weekly · Tool: Exa (answer mode) + +You are one round of a weekly share-of-model tracker: when buyers ask AI +engines about our category, who is in the answer? + +## Fixed question set (NEVER reword — comparability is the point) +1. "best embedded wallet SDK for developers" +2. "best wallet infrastructure for AI agents" +3. "how to give an AI agent a crypto wallet" +4. "Privy alternatives" +5. "ERC-4337 bundler and paymaster providers" +6. "passkey wallet SDK" + +## Round procedure +1. READ the state file for last week's table. +2. Ask each question via Exa answer/search. Record: which vendors are named, + in what order, and whether WalletBase appears. +3. Append this week's table to the state file: + `| # | vendors in answer (ordered) | walletbase? |` +4. ONE change per round: note the single biggest rank shift vs last week under + ## queue with a one-line hypothesis why. + +## Escalation contract +Never escalate. This loop only measures. + +## Stop +Six questions, one table, stop. If Exa fails twice → ## blocked, stop. diff --git a/marketplaces.json b/marketplaces.json new file mode 100644 index 0000000..2918bbe --- /dev/null +++ b/marketplaces.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "marketplaces": [ + { + "name": "ponytail", + "sourceType": "github", + "sourceUri": "DietrichGebert/ponytail", + "catalogPath": "/home/prad/.omp/plugins/cache/marketplaces/ponytail/marketplace.json", + "addedAt": "2026-10-06T16:12:52.335Z", + "updatedAt": "2026-10-16T14:51:17.380Z" + } + ] +} diff --git a/plugins/bun.lock b/plugins/bun.lock new file mode 100644 index 0000000..285f893 --- /dev/null +++ b/plugins/bun.lock @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "omp-plugins", + "dependencies": { + "omp.nvim": "^2.0.0", + }, + }, + }, + "packages": { + "omp.nvim": ["omp.nvim@2.0.0", "", {}, "sha512-7ARJF2Md0VU3uyk9SNwHcO6n/J8Qsl1Gi8yfH9eLtPTKMZQA5KdZbUMP2/RN8Ab6FZyxucWQrxOdh+G8Y2JLQA=="], + } +} diff --git a/plugins/installed_plugins.json b/plugins/installed_plugins.json new file mode 100644 index 0000000..cb85d5c --- /dev/null +++ b/plugins/installed_plugins.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "plugins": { + "ponytail@ponytail": [ + { + "scope": "user", + "installPath": "/home/prad/.omp/plugins/cache/plugins/ponytail___ponytail___4.13.0", + "version": "4.13.0", + "installedAt": "2026-10-06T16:12:52.700Z", + "lastUpdated": "2026-10-06T16:12:52.700Z" + } + ] + } +} diff --git a/plugins/omp-plugins.lock.json b/plugins/omp-plugins.lock.json new file mode 100644 index 0000000..7b690c1 --- /dev/null +++ b/plugins/omp-plugins.lock.json @@ -0,0 +1,15 @@ +{ + "plugins": { + "omp.nvim": { + "version": "2.0.0", + "enabledFeatures": null, + "enabled": true + }, + "@dietrichgebert/ponytail": { + "version": "4.13.0", + "enabledFeatures": null, + "enabled": true + } + }, + "settings": {} +} \ No newline at end of file diff --git a/plugins/package.json b/plugins/package.json new file mode 100644 index 0000000..05c7ff8 --- /dev/null +++ b/plugins/package.json @@ -0,0 +1,7 @@ +{ + "name": "omp-plugins", + "private": true, + "dependencies": { + "omp.nvim": "^2.0.0" + } +} \ No newline at end of file