@oh-my-pi/pi-coding-agent 15.11.4 → 15.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -1
- package/dist/cli.js +520 -451
- package/dist/types/cli/bench-cli.d.ts +78 -0
- package/dist/types/cli/usage-cli.d.ts +10 -1
- package/dist/types/commands/bench.d.ts +29 -0
- package/dist/types/commands/usage.d.ts +9 -0
- package/dist/types/config/model-resolver.d.ts +3 -2
- package/dist/types/config/settings-schema.d.ts +125 -3
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -1
- package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-selector.d.ts +8 -1
- package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -0
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +3 -0
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +10 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +3 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -1
- package/dist/types/session/agent-session.d.ts +14 -1
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/codex-auto-reset.d.ts +107 -0
- package/dist/types/session/snapcompact-inline.d.ts +107 -4
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/task/render.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/renderers.d.ts +13 -0
- package/dist/types/tools/ssh.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +0 -11
- package/package.json +11 -11
- package/src/cli/bench-cli.ts +437 -0
- package/src/cli/usage-cli.ts +187 -16
- package/src/cli-commands.ts +1 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/usage.ts +8 -0
- package/src/config/model-registry.ts +52 -5
- package/src/config/model-resolver.ts +36 -5
- package/src/config/settings-schema.ts +148 -3
- package/src/config/settings.ts +9 -0
- package/src/edit/renderer.ts +5 -0
- package/src/hindsight/client.ts +26 -1
- package/src/hindsight/state.ts +6 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/mcp/transports/stdio.ts +81 -7
- package/src/modes/components/oauth-selector.ts +67 -7
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/session-selector.ts +8 -2
- package/src/modes/components/settings-selector.ts +89 -47
- package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/tool-execution.ts +26 -0
- package/src/modes/components/transcript-container.ts +23 -1
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/input-controller.ts +8 -6
- package/src/modes/controllers/selector-controller.ts +72 -2
- package/src/modes/interactive-mode.ts +83 -0
- package/src/modes/session-observer-registry.ts +61 -3
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +24 -6
- package/src/modes/setup-wizard/scenes/providers.ts +36 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +10 -1
- package/src/modes/setup-wizard/scenes/theme.ts +28 -1
- package/src/modes/setup-wizard/scenes/types.ts +10 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +22 -6
- package/src/modes/setup-wizard/wizard-overlay.ts +38 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/context-usage.ts +75 -1
- package/src/prompts/bench.md +7 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
- package/src/prompts/tools/browser.md +33 -43
- package/src/prompts/tools/eval.md +27 -50
- package/src/prompts/tools/irc.md +29 -31
- package/src/prompts/tools/read.md +31 -37
- package/src/prompts/tools/todo.md +1 -2
- package/src/sdk.ts +4 -2
- package/src/session/agent-session.ts +136 -6
- package/src/session/auth-storage.ts +3 -0
- package/src/session/codex-auto-reset.ts +190 -0
- package/src/session/snapcompact-inline.ts +404 -75
- package/src/slash-commands/builtin-registry.ts +145 -8
- package/src/slash-commands/helpers/context-report.ts +28 -1
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/usage-report.ts +12 -0
- package/src/task/index.ts +30 -7
- package/src/task/render.ts +34 -19
- package/src/tools/bash.ts +3 -0
- package/src/tools/eval-render.ts +4 -0
- package/src/tools/renderers.ts +13 -0
- package/src/tools/ssh.ts +3 -0
- package/src/tools/todo.ts +8 -128
package/src/prompts/tools/irc.md
CHANGED
|
@@ -1,55 +1,53 @@
|
|
|
1
1
|
Sends short text messages to other agents in this process and receives theirs.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
-
|
|
5
|
-
- `op: "list"` —
|
|
6
|
-
- `op: "send"` — fire-and-forget
|
|
7
|
-
- Messaging an `idle
|
|
8
|
-
- `send`
|
|
9
|
-
- `op: "wait"` — block until a message arrives (optionally only `from`
|
|
10
|
-
- `op: "inbox"` — drain pending messages without blocking (`peek: true`
|
|
11
|
-
- `replyTo` —
|
|
12
|
-
-
|
|
4
|
+
- Main agent is `Main`; subagents reuse their task id (`AuthLoader`, or `AuthLoader-2` when the name repeats).
|
|
5
|
+
- `op: "list"` — peers with status (`running` | `idle` | `parked`), unread count, parent, last activity. Use when unsure who exists.
|
|
6
|
+
- `op: "send"` — fire-and-forget `message` to `to` (peer id, or `"all"` to broadcast to live peers). Returns per-recipient receipts immediately; NEVER waits for the recipient to act. Outcomes: `injected` (mid-turn; folded in at next step boundary), `woken` (idle peer started a turn), `revived` (parked peer brought back and woken), `failed`.
|
|
7
|
+
- Messaging an `idle`/`parked` peer is how you wake it — there is no separate revive call.
|
|
8
|
+
- `send` + `await: true` — round-trip: send, then block until that peer's next message (or timeout). Invalid with `to: "all"`.
|
|
9
|
+
- `op: "wait"` — block until a message arrives (optionally only `from` one peer); consumes and returns it. Timeout = clean "no message", not an error.
|
|
10
|
+
- `op: "inbox"` — drain pending messages without blocking (`peek: true` leaves them unread).
|
|
11
|
+
- `replyTo` — id of the message you are answering, so the sender can correlate.
|
|
12
|
+
- Replies arrive only when the recipient sends one. Exception: `await: true` to a peer stuck mid-turn (async execution disabled, e.g. blocked in a synchronous task spawn) gets a side-channel auto-reply from its context. For background on a peer, `read` `history://<id>` instead of interrogating it.
|
|
13
13
|
</instruction>
|
|
14
14
|
|
|
15
15
|
<when_to_use>
|
|
16
|
-
|
|
17
|
-
- **Unexpected state
|
|
18
|
-
- **Blocked by another agent
|
|
19
|
-
- **Decision
|
|
20
|
-
- **Coordination
|
|
16
|
+
Reach for `irc` proactively when continuing alone is wasteful or wrong; when in doubt, message.
|
|
17
|
+
- **Unexpected state** — missing file, config contradicting the assignment, API/tool behaving differently than told. DM `Main` (or your spawner) instead of guessing.
|
|
18
|
+
- **Blocked by another agent** — a peer holds the file/branch/resource or decision you need, or started the change you're about to make. DM them (or broadcast to discover who) before duplicating work.
|
|
19
|
+
- **Decision outside your scope** — a genuine fork the assignment didn't pre-decide. Ask the requester rather than picking unilaterally.
|
|
20
|
+
- **Coordination** — a peer's in-flight work would benefit from yours, or vice-versa.
|
|
21
21
|
|
|
22
|
-
NEVER
|
|
22
|
+
NEVER for: routine progress updates, things a tool call can verify, questions your assignment/repo/docs already answer.
|
|
23
23
|
</when_to_use>
|
|
24
24
|
|
|
25
25
|
<etiquette>
|
|
26
|
-
|
|
27
|
-
- **Plain prose only.** NEVER
|
|
28
|
-
- **NEVER quote the message you
|
|
29
|
-
- **
|
|
30
|
-
- **Send, then keep working.** `
|
|
31
|
-
- **Answer
|
|
32
|
-
- **Stay terse.**
|
|
33
|
-
- **Address peers by
|
|
34
|
-
- **NEVER IRC
|
|
26
|
+
Applies to sending and replying.
|
|
27
|
+
- **Plain prose only.** NEVER JSON status payloads like `{"type":"task_completed",…}` — write a normal sentence.
|
|
28
|
+
- **NEVER quote the message you answer.** Lead with the answer; set `replyTo`.
|
|
29
|
+
- **Learn about peers via IRC** — NEVER grep artifacts, read other sessions' JSONL, or shell-poke. DM them, or `read` `history://<id>`.
|
|
30
|
+
- **Send, then keep working.** `wait`/`await: true` only when you genuinely cannot proceed. NEVER "did you get my message?". A `failed` receipt = peer unreachable — move on; NEVER retry in a loop.
|
|
31
|
+
- **Answer expected questions** via `irc send` to the sender (finishing your current step first is fine).
|
|
32
|
+
- **Stay terse.** One question per send; share files via `local://`/`memory://`/`artifact://` URLs, never pasted blobs.
|
|
33
|
+
- **Address peers by exact id** from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names.
|
|
34
|
+
- **NEVER IRC what a tool answers.** A `read`, grep, or build resolves it? Do that first.
|
|
35
35
|
</etiquette>
|
|
36
36
|
|
|
37
37
|
<output>
|
|
38
|
-
- `send`: per-recipient
|
|
38
|
+
- `send`: per-recipient receipts; with `await: true`, also the reply (or timeout notice).
|
|
39
39
|
- `wait`: the consumed message, or a clean timeout notice.
|
|
40
40
|
- `inbox`: pending messages, oldest first.
|
|
41
|
-
- `list`: peers with status, unread count, parent,
|
|
41
|
+
- `list`: peers with status, unread count, parent, last activity.
|
|
42
42
|
</output>
|
|
43
43
|
|
|
44
44
|
<examples>
|
|
45
45
|
# List peers
|
|
46
46
|
`{"op": "list"}`
|
|
47
|
-
# Fire-and-forget DM —
|
|
48
|
-
`{"op": "send", "to": "AuthLoader", "message": "
|
|
47
|
+
# Fire-and-forget DM — same send wakes idle/parked peers
|
|
48
|
+
`{"op": "send", "to": "AuthLoader", "message": "Still touching src/server/auth.ts? I need to add a 401 path."}`
|
|
49
49
|
# Round-trip when you cannot proceed without the answer
|
|
50
|
-
`{"op": "send", "to": "Main", "message": "
|
|
51
|
-
# Wake a parked agent (same send — the bus revives it)
|
|
52
|
-
`{"op": "send", "to": "SchemaMigrator", "message": "The users table changed again; please re-check your migration."}`
|
|
50
|
+
`{"op": "send", "to": "Main", "message": "JWT or session cookies for the auth flow?", "await": true}`
|
|
53
51
|
# Block until a specific peer answers
|
|
54
52
|
`{"op": "wait", "from": "AuthLoader", "timeoutMs": 60000}`
|
|
55
53
|
# Drain pending messages
|
|
@@ -1,84 +1,78 @@
|
|
|
1
1
|
Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).
|
|
5
4
|
- You SHOULD parallelize independent reads when exploring related files.
|
|
6
|
-
- You SHOULD reach for `read` — not a browser/puppeteer tool — for
|
|
5
|
+
- You SHOULD reach for `read` — not a browser/puppeteer tool — for web content; browser only when `read` cannot deliver it.
|
|
7
6
|
</instruction>
|
|
8
7
|
|
|
9
8
|
## Parameters
|
|
10
9
|
|
|
11
|
-
- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:<sel>` for line ranges
|
|
10
|
+
- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:<sel>` for line ranges or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
|
|
12
11
|
|
|
13
12
|
## Selectors
|
|
14
13
|
|
|
15
|
-
Append `:<sel>` to `path
|
|
14
|
+
Append `:<sel>` to `path`; bare path = default mode.
|
|
16
15
|
|
|
17
|
-
- _(none)_ — parseable code → structural summary
|
|
18
|
-
- `:50` / `:50-` —
|
|
16
|
+
- _(none)_ — parseable code → structural summary; other files → from start (up to {{DEFAULT_LIMIT}} lines).
|
|
17
|
+
- `:50` / `:50-` — from line 50 onward.
|
|
19
18
|
- `:50-200` — lines 50–200 inclusive.
|
|
20
|
-
- `:50+150` — 150 lines starting at
|
|
21
|
-
- `:20+1` — anchor
|
|
22
|
-
- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged)
|
|
23
|
-
- `:raw` — verbatim
|
|
24
|
-
- `:2-4:raw`
|
|
25
|
-
- `:conflicts` — one
|
|
19
|
+
- `:50+150` — 150 lines starting at 50.
|
|
20
|
+
- `:20+1` — anchor line 20 (single-range reads pad ≤1 leading / ≤3 trailing context lines).
|
|
21
|
+
- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged); exact bounds, no padding.
|
|
22
|
+
- `:raw` — verbatim; no anchors, no summary, no line prefixes.
|
|
23
|
+
- `:2-4:raw` / `:raw:2-4` — range AND verbatim; compose in either order.
|
|
24
|
+
- `:conflicts` — one line per unresolved git merge conflict block.
|
|
26
25
|
|
|
27
26
|
# Files
|
|
28
27
|
|
|
29
|
-
-
|
|
28
|
+
- Directory path → depth-limited dirent listing.
|
|
30
29
|
{{#if IS_HL_MODE}}
|
|
31
|
-
-
|
|
30
|
+
- File with explicit selector → snapshot tag header + numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy the `[PATH#TAG]` header for anchored edits; ops use bare line numbers. NEVER fabricate the tag.
|
|
32
31
|
{{else}}
|
|
33
32
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
34
|
-
-
|
|
33
|
+
- File with explicit selector → lines prefixed with numbers: `41|def alpha():`.
|
|
35
34
|
{{/if}}
|
|
36
35
|
{{/if}}
|
|
37
|
-
- Parseable code without
|
|
38
|
-
|
|
39
|
-
`[NN lines elided; re-read needed ranges, e.g. <path>:5-16,40-80]`
|
|
40
|
-
|
|
41
|
-
Re-issue **only the relevant range(s)** using the multi-range selector (e.g. `<path>:5-16,120-200`). NEVER guess what's inside `..` / `…` — those markers carry no content. NEVER re-read the whole file or use `:raw` when targeted ranges suffice.
|
|
36
|
+
- Parseable code without selector → **structural summary**: declarations kept, bodies collapsed to `..` (merged brace pair) or `…` (standalone). The footer shows the recovery selector: `[NN lines elided; re-read needed ranges, e.g. <path>:5-16,40-80]`. Re-issue ONLY the ranges you need via the multi-range selector. `..`/`…` carry no content — NEVER guess what's inside; NEVER re-read the whole file or `:raw` when ranges suffice.
|
|
42
37
|
|
|
43
38
|
# Documents & Notebooks
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
PDF, Word, PowerPoint, Excel, RTF, EPUB → extracted text. Notebooks (`.ipynb`) → editable `# %% [type] cell:N` text; edits round-trip to the underlying JSON preserving metadata. `:raw` bypasses the converter.
|
|
46
41
|
|
|
47
42
|
# Images
|
|
48
43
|
|
|
49
44
|
{{#if INSPECT_IMAGE_ENABLED}}
|
|
50
|
-
|
|
45
|
+
Image path → metadata (mime, bytes, dimensions, channels, alpha). For visual analysis, call `inspect_image` with the path and a question.
|
|
51
46
|
{{else}}
|
|
52
|
-
|
|
47
|
+
Image path → decoded image inline (PNG, JPEG, GIF, WEBP) for direct visual analysis.
|
|
53
48
|
{{/if}}
|
|
54
49
|
|
|
55
50
|
# Archives
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
`.tar`, `.tar.gz`, `.tgz`, `.zip`. `archive.ext:path/inside/archive` reads a member; inner paths take normal selectors: `archive.zip:dir/file.ts:50-60`.
|
|
58
53
|
|
|
59
54
|
# SQLite
|
|
60
55
|
|
|
61
56
|
For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
|
|
62
|
-
- `file.db` —
|
|
57
|
+
- `file.db` — tables with row counts
|
|
63
58
|
- `file.db:table` — schema + sample rows
|
|
64
|
-
- `file.db:table:key` —
|
|
65
|
-
- `file.db:table?limit=50&offset=100` —
|
|
66
|
-
- `file.db:table?where=status='active'&order=created:desc` —
|
|
67
|
-
- `file.db?q=SELECT …` — read-only SELECT
|
|
59
|
+
- `file.db:table:key` — row by primary key
|
|
60
|
+
- `file.db:table?limit=50&offset=100` — pagination
|
|
61
|
+
- `file.db:table?where=status='active'&order=created:desc` — filter/order
|
|
62
|
+
- `file.db?q=SELECT …` — read-only SELECT
|
|
68
63
|
|
|
69
64
|
# URLs
|
|
70
65
|
|
|
71
|
-
-
|
|
72
|
-
- `:raw`
|
|
73
|
-
- Bare `host:port`
|
|
66
|
+
- Reader-mode by default: HTML, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.
|
|
67
|
+
- `:raw` → untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetch.
|
|
68
|
+
- Bare `host:port` collides with the selector grammar — add a trailing slash: `https://example.com/:80`.
|
|
74
69
|
|
|
75
70
|
# Internal URIs
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
All `path` URI schemes resolve transparently and take the same line selectors. `artifact://<id>` recovers full output a previous bash/eval/tool result spilled or truncated. `history://<agentId>` is an agent's transcript as concise markdown; bare `history://` lists agents.
|
|
78
73
|
|
|
79
74
|
<critical>
|
|
80
|
-
- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
- Summary footer names ranges to re-read? Re-issue ONLY the ranges you need via the multi-range selector. NEVER guess what's inside `..` / `…` markers — they carry no content.
|
|
75
|
+
- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN bash calls, however short or convenient.
|
|
76
|
+
- Line ranges go in the selector (`path="src/foo.ts:50-200"`) — NEVER `sed -n`, `awk NR`, or `head`/`tail` pipelines.
|
|
77
|
+
- Summary footer names elided ranges? Re-issue ONLY those ranges. NEVER guess `..`/`…` content.
|
|
84
78
|
</critical>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Manages a phased task list. Pass `ops`: a flat array of operations.
|
|
4
4
|
The next pending task is auto-promoted to `in_progress` after each completion.
|
|
5
|
-
Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`,
|
|
5
|
+
Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, and `view`. `pending` is a task status, not an `op`; leave not-yet-started tasks implicit in `init`/`append` lists.
|
|
6
6
|
|
|
7
7
|
## Operations
|
|
8
8
|
|
|
@@ -14,7 +14,6 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, `n
|
|
|
14
14
|
|`drop`|`task` or `phase`|Mark abandoned|
|
|
15
15
|
|`rm`|`task` or `phase` (optional)|Remove task or phase's tasks; omit both to clear the entire list|
|
|
16
16
|
|`append`|`phase`, `items: string[]`|Append tasks to `phase`; lazily creates phase|
|
|
17
|
-
|`note`|`task`, `text`|Append a note to a task. Reminders for future-you only.|
|
|
18
17
|
|`view`|—|Read-only: echo the current list without modifying it|
|
|
19
18
|
|
|
20
19
|
## Anatomy
|
package/src/sdk.ts
CHANGED
|
@@ -2162,11 +2162,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2162
2162
|
// Per-request provider-context transforms. Obfuscate FIRST so secrets are
|
|
2163
2163
|
// redacted from text before snapcompact rasterizes it into PNG frames.
|
|
2164
2164
|
// Both operate on the transient outgoing Context only — never persisted.
|
|
2165
|
+
const snapcompactSystemPromptMode = settings.get("snapcompact.systemPrompt");
|
|
2165
2166
|
const snapcompactInline =
|
|
2166
|
-
|
|
2167
|
+
snapcompactSystemPromptMode !== "none" || settings.get("snapcompact.toolResults")
|
|
2167
2168
|
? new SnapcompactInlineTransformer({
|
|
2168
|
-
renderSystemPrompt:
|
|
2169
|
+
renderSystemPrompt: snapcompactSystemPromptMode,
|
|
2169
2170
|
renderToolResults: settings.get("snapcompact.toolResults"),
|
|
2171
|
+
shape: settings.get("snapcompact.shape"),
|
|
2170
2172
|
})
|
|
2171
2173
|
: undefined;
|
|
2172
2174
|
const transformProviderContext =
|
|
@@ -73,6 +73,9 @@ import type {
|
|
|
73
73
|
Model,
|
|
74
74
|
ProviderResponseMetadata,
|
|
75
75
|
ProviderSessionState,
|
|
76
|
+
ResetCreditAccountStatus,
|
|
77
|
+
ResetCreditRedeemOutcome,
|
|
78
|
+
ResetCreditTarget,
|
|
76
79
|
ServiceTier,
|
|
77
80
|
SimpleStreamOptions,
|
|
78
81
|
TextContent,
|
|
@@ -237,6 +240,7 @@ import { normalizeModelContextImages } from "../utils/image-loading";
|
|
|
237
240
|
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
238
241
|
import type { AuthStorage } from "./auth-storage";
|
|
239
242
|
import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome } from "./client-bridge";
|
|
243
|
+
import { defaultCodexAutoRedeemCoordinator, evaluateCodexAutoRedeem } from "./codex-auto-reset";
|
|
240
244
|
import {
|
|
241
245
|
type BashExecutionMessage,
|
|
242
246
|
type CustomMessage,
|
|
@@ -5399,11 +5403,7 @@ export class AgentSession {
|
|
|
5399
5403
|
#cloneTodoPhases(phases: TodoPhase[]): TodoPhase[] {
|
|
5400
5404
|
return phases.map(phase => ({
|
|
5401
5405
|
name: phase.name,
|
|
5402
|
-
tasks: phase.tasks.map(task => {
|
|
5403
|
-
const out: TodoItem = { content: task.content, status: task.status };
|
|
5404
|
-
if (task.notes && task.notes.length > 0) out.notes = [...task.notes];
|
|
5405
|
-
return out;
|
|
5406
|
-
}),
|
|
5406
|
+
tasks: phase.tasks.map(task => ({ content: task.content, status: task.status })),
|
|
5407
5407
|
}));
|
|
5408
5408
|
}
|
|
5409
5409
|
|
|
@@ -6388,6 +6388,10 @@ export class AgentSession {
|
|
|
6388
6388
|
const snapcompactResult = await snapcompact.compact(preparation, {
|
|
6389
6389
|
convertToLlm,
|
|
6390
6390
|
model: this.model,
|
|
6391
|
+
shape: snapcompact.resolveShape(this.model, this.settings.get("snapcompact.shape")),
|
|
6392
|
+
// Providers with hard image caps (OpenRouter: 8) silently drop
|
|
6393
|
+
// frames past the cap — keep the archive within budget.
|
|
6394
|
+
maxFrames: snapcompact.providerFrameBudget(this.model.provider),
|
|
6391
6395
|
});
|
|
6392
6396
|
summary = snapcompactResult.summary;
|
|
6393
6397
|
shortSummary = snapcompactResult.shortSummary;
|
|
@@ -7921,6 +7925,7 @@ export class AgentSession {
|
|
|
7921
7925
|
const snapcompactResult = await snapcompact.compact(preparation, {
|
|
7922
7926
|
convertToLlm,
|
|
7923
7927
|
model: this.model,
|
|
7928
|
+
maxFrames: snapcompact.providerFrameBudget(this.model?.provider),
|
|
7924
7929
|
});
|
|
7925
7930
|
summary = snapcompactResult.summary;
|
|
7926
7931
|
shortSummary = snapcompactResult.shortSummary;
|
|
@@ -8354,7 +8359,8 @@ export class AgentSession {
|
|
|
8354
8359
|
|
|
8355
8360
|
return (
|
|
8356
8361
|
/\bItem with id ['"][^'"]+['"] not found\.?/i.test(errorMessage) ||
|
|
8357
|
-
(/previous[ _]?response/i.test(errorMessage) &&
|
|
8362
|
+
(/previous[ _]?response/i.test(errorMessage) &&
|
|
8363
|
+
/not[ _]?found|invalid|expired|stale|zero[ _-]?data[ _-]?retention/i.test(errorMessage))
|
|
8358
8364
|
);
|
|
8359
8365
|
}
|
|
8360
8366
|
|
|
@@ -8720,6 +8726,13 @@ export class AgentSession {
|
|
|
8720
8726
|
if (outcome.switched) {
|
|
8721
8727
|
switchedCredential = true;
|
|
8722
8728
|
delayMs = 0;
|
|
8729
|
+
} else if (await this.#maybeAutoRedeemCodexReset()) {
|
|
8730
|
+
// A live usage-limit 429 on the active Codex account, with a banked
|
|
8731
|
+
// reset and the opt-in setting on: spend the reset and retry
|
|
8732
|
+
// immediately instead of waiting out the window. Runs after the
|
|
8733
|
+
// free sibling-switch above and before model fallback below.
|
|
8734
|
+
switchedCredential = true;
|
|
8735
|
+
delayMs = 0;
|
|
8723
8736
|
} else {
|
|
8724
8737
|
// No sibling credential is usable right now. Wait for whichever
|
|
8725
8738
|
// comes first: the provider's retry-after window for the current
|
|
@@ -10137,6 +10150,123 @@ export class AgentSession {
|
|
|
10137
10150
|
});
|
|
10138
10151
|
}
|
|
10139
10152
|
|
|
10153
|
+
/**
|
|
10154
|
+
* Redeem one saved Codex rate-limit reset for a specific account, injecting
|
|
10155
|
+
* the provider base URL like {@link AgentSession.fetchUsageReports}. Powers
|
|
10156
|
+
* the `/usage reset` command and auto-redeem. Never throws for business
|
|
10157
|
+
* outcomes — inspect the returned `code`.
|
|
10158
|
+
*/
|
|
10159
|
+
async redeemResetCredit(target: ResetCreditTarget, signal?: AbortSignal): Promise<ResetCreditRedeemOutcome> {
|
|
10160
|
+
return this.#modelRegistry.authStorage.redeemResetCredit({
|
|
10161
|
+
target,
|
|
10162
|
+
baseUrlResolver: provider => this.#modelRegistry.getProviderBaseUrl?.(provider),
|
|
10163
|
+
signal,
|
|
10164
|
+
});
|
|
10165
|
+
}
|
|
10166
|
+
|
|
10167
|
+
/**
|
|
10168
|
+
* List saved Codex rate-limit resets per stored account, fetched live from
|
|
10169
|
+
* the dedicated credits endpoint (bypasses the usage cache). Powers the
|
|
10170
|
+
* `/usage reset` account selector.
|
|
10171
|
+
*/
|
|
10172
|
+
async listResetCredits(signal?: AbortSignal): Promise<ResetCreditAccountStatus[]> {
|
|
10173
|
+
return this.#modelRegistry.authStorage.listResetCredits({
|
|
10174
|
+
sessionId: this.sessionId,
|
|
10175
|
+
baseUrlResolver: provider => this.#modelRegistry.getProviderBaseUrl?.(provider),
|
|
10176
|
+
signal,
|
|
10177
|
+
});
|
|
10178
|
+
}
|
|
10179
|
+
|
|
10180
|
+
/**
|
|
10181
|
+
* Auto-redeem hook for {@link AgentSession.#handleRetryableError}'s
|
|
10182
|
+
* usage-limit branch. Returns `true` only when a saved Codex reset was
|
|
10183
|
+
* actually spent (so the caller retries immediately). Opt-in, reactive, and
|
|
10184
|
+
* heavily gated — see `./codex-auto-reset` and the design in
|
|
10185
|
+
* `local://autoreset-spec.md`. Per-account in-flight dedup lets concurrent
|
|
10186
|
+
* sessions adopt one redeem instead of double-spending.
|
|
10187
|
+
*/
|
|
10188
|
+
async #maybeAutoRedeemCodexReset(coordinator = defaultCodexAutoRedeemCoordinator): Promise<boolean> {
|
|
10189
|
+
const cfg = this.settings.getGroup("codexResets");
|
|
10190
|
+
const model = this.model;
|
|
10191
|
+
// Cheap exits before any IO.
|
|
10192
|
+
if (!cfg.autoRedeem || !model || model.provider !== "openai-codex") return false;
|
|
10193
|
+
const authStorage = this.#modelRegistry.authStorage;
|
|
10194
|
+
// Capture identity BEFORE awaits: markUsageLimitReached leaves the
|
|
10195
|
+
// usage-limit session credential sticky, so this names the blocked account.
|
|
10196
|
+
const identity = authStorage.getOAuthAccountIdentity("openai-codex", this.sessionId);
|
|
10197
|
+
const accountKey = (identity?.accountId ?? identity?.email)?.trim().toLowerCase();
|
|
10198
|
+
if (!accountKey) return false;
|
|
10199
|
+
const existing = coordinator.inFlightByAccount.get(accountKey);
|
|
10200
|
+
if (existing) return existing;
|
|
10201
|
+
|
|
10202
|
+
const run = (async (): Promise<boolean> => {
|
|
10203
|
+
const reports = await this.fetchUsageReports();
|
|
10204
|
+
const decision = evaluateCodexAutoRedeem({
|
|
10205
|
+
nowMs: Date.now(),
|
|
10206
|
+
provider: model.provider,
|
|
10207
|
+
modelId: model.id,
|
|
10208
|
+
settings: {
|
|
10209
|
+
autoRedeem: cfg.autoRedeem,
|
|
10210
|
+
minBlockedMinutes: Math.max(0, cfg.minBlockedMinutes),
|
|
10211
|
+
keepCredits: Math.max(0, Math.trunc(cfg.keepCredits)),
|
|
10212
|
+
},
|
|
10213
|
+
identity,
|
|
10214
|
+
reports,
|
|
10215
|
+
attemptedBlockKeys: coordinator.attemptedBlockKeys,
|
|
10216
|
+
lastAttemptAtByAccount: coordinator.lastAttemptAtByAccount,
|
|
10217
|
+
});
|
|
10218
|
+
if (!decision.redeem) {
|
|
10219
|
+
logger.debug("codex-auto-reset: skipped", { reason: decision.reason });
|
|
10220
|
+
return false;
|
|
10221
|
+
}
|
|
10222
|
+
// Commit the attempt BEFORE acting so this block can never re-enter.
|
|
10223
|
+
coordinator.attemptedBlockKeys.add(decision.blockKey);
|
|
10224
|
+
coordinator.lastAttemptAtByAccount.set(decision.accountKey, Date.now());
|
|
10225
|
+
const who = decision.target.email ?? decision.target.accountId ?? "the active account";
|
|
10226
|
+
const outcome = await authStorage.redeemResetCredit({
|
|
10227
|
+
target: decision.target,
|
|
10228
|
+
baseUrlResolver: provider => this.#modelRegistry.getProviderBaseUrl?.(provider),
|
|
10229
|
+
// Not tied to the retry abort controller: aborting a consume
|
|
10230
|
+
// mid-flight leaves credit state unknown.
|
|
10231
|
+
signal: AbortSignal.timeout(15_000),
|
|
10232
|
+
});
|
|
10233
|
+
switch (outcome.code) {
|
|
10234
|
+
case "reset": {
|
|
10235
|
+
const left = Math.max(0, decision.availableCount - 1);
|
|
10236
|
+
this.emitNotice(
|
|
10237
|
+
"info",
|
|
10238
|
+
`Auto-redeemed a saved Codex rate-limit reset for ${who} (${left} left); retrying now.`,
|
|
10239
|
+
"codex-auto-reset",
|
|
10240
|
+
);
|
|
10241
|
+
void this.fetchUsageReports();
|
|
10242
|
+
return true;
|
|
10243
|
+
}
|
|
10244
|
+
case "already_redeemed":
|
|
10245
|
+
this.emitNotice(
|
|
10246
|
+
"warning",
|
|
10247
|
+
"A saved Codex reset was already redeemed elsewhere; waiting for the window.",
|
|
10248
|
+
"codex-auto-reset",
|
|
10249
|
+
);
|
|
10250
|
+
return false;
|
|
10251
|
+
case "no_credit":
|
|
10252
|
+
logger.debug("codex-auto-reset: no_credit (snapshot/live mismatch)", { account: accountKey });
|
|
10253
|
+
return false;
|
|
10254
|
+
case "nothing_to_reset":
|
|
10255
|
+
this.emitNotice(
|
|
10256
|
+
"warning",
|
|
10257
|
+
"Codex reset reported nothing to reset; auto-redeem suppressed for this window.",
|
|
10258
|
+
"codex-auto-reset",
|
|
10259
|
+
);
|
|
10260
|
+
return false;
|
|
10261
|
+
default:
|
|
10262
|
+
this.emitNotice("warning", `Codex auto-redeem failed (${outcome.code}).`, "codex-auto-reset");
|
|
10263
|
+
return false;
|
|
10264
|
+
}
|
|
10265
|
+
})().finally(() => coordinator.inFlightByAccount.delete(accountKey));
|
|
10266
|
+
coordinator.inFlightByAccount.set(accountKey, run);
|
|
10267
|
+
return run;
|
|
10268
|
+
}
|
|
10269
|
+
|
|
10140
10270
|
/**
|
|
10141
10271
|
* Estimate context tokens from messages, using the last assistant usage when available.
|
|
10142
10272
|
*/
|