@oh-my-pi/pi-coding-agent 17.0.4 → 17.0.6

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.
Files changed (143) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/dist/cli.js +3616 -3676
  3. package/dist/types/advisor/advise-tool.d.ts +3 -0
  4. package/dist/types/advisor/runtime.d.ts +7 -1
  5. package/dist/types/async/job-manager.d.ts +2 -0
  6. package/dist/types/config/model-registry.d.ts +7 -0
  7. package/dist/types/config/model-resolver.d.ts +8 -0
  8. package/dist/types/config/models-config-schema.d.ts +10 -0
  9. package/dist/types/config/models-config.d.ts +6 -0
  10. package/dist/types/dap/client.d.ts +10 -0
  11. package/dist/types/dap/types.d.ts +6 -5
  12. package/dist/types/exec/bash-executor.d.ts +1 -0
  13. package/dist/types/extensibility/extensions/load-errors.d.ts +3 -0
  14. package/dist/types/extensibility/extensions/runner.d.ts +4 -2
  15. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +16 -0
  16. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
  17. package/dist/types/launch/spawn-options.d.ts +10 -0
  18. package/dist/types/launch/spawn-options.test.d.ts +1 -0
  19. package/dist/types/modes/components/status-line/component.d.ts +10 -3
  20. package/dist/types/modes/components/status-line/types.d.ts +3 -1
  21. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  22. package/dist/types/modes/components/transcript-container.d.ts +4 -3
  23. package/dist/types/modes/components/tree-selector.d.ts +6 -2
  24. package/dist/types/modes/components/usage-row.d.ts +1 -1
  25. package/dist/types/modes/interactive-mode.d.ts +2 -0
  26. package/dist/types/modes/print-mode.d.ts +4 -0
  27. package/dist/types/modes/rpc/rpc-client.d.ts +1 -1
  28. package/dist/types/modes/rpc/rpc-frame.d.ts +9 -0
  29. package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
  30. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
  31. package/dist/types/modes/setup-wizard/index.d.ts +1 -1
  32. package/dist/types/modes/setup-wizard/scenes/model.d.ts +3 -0
  33. package/dist/types/modes/types.d.ts +2 -0
  34. package/dist/types/sdk.d.ts +2 -0
  35. package/dist/types/session/agent-session.d.ts +42 -6
  36. package/dist/types/session/messages.d.ts +6 -0
  37. package/dist/types/session/session-paths.d.ts +21 -4
  38. package/dist/types/tools/browser/aria/aria-snapshot.d.ts +17 -7
  39. package/dist/types/tools/browser/cmux/socket-client.d.ts +2 -0
  40. package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
  41. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  42. package/dist/types/tools/gh.d.ts +5 -3
  43. package/dist/types/tools/hub/index.d.ts +1 -1
  44. package/dist/types/tools/hub/jobs.d.ts +1 -0
  45. package/dist/types/tools/hub/types.d.ts +2 -0
  46. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  47. package/dist/types/tools/xdev.d.ts +5 -3
  48. package/dist/types/utils/git.d.ts +33 -0
  49. package/dist/types/vibe/__tests__/token-rate.test.d.ts +1 -0
  50. package/dist/types/vibe/runtime.d.ts +23 -0
  51. package/dist/types/web/search/providers/codex.d.ts +1 -1
  52. package/package.json +12 -12
  53. package/src/advisor/__tests__/advisor.test.ts +150 -0
  54. package/src/advisor/advise-tool.ts +4 -0
  55. package/src/advisor/runtime.ts +38 -14
  56. package/src/async/job-manager.ts +3 -0
  57. package/src/cli/bench-cli.ts +8 -2
  58. package/src/cli/dry-balance-cli.ts +1 -0
  59. package/src/config/model-registry.ts +89 -8
  60. package/src/config/model-resolver.ts +78 -14
  61. package/src/config/models-config-schema.ts +1 -0
  62. package/src/config/settings.ts +3 -1
  63. package/src/dap/client.ts +168 -1
  64. package/src/dap/config.ts +51 -1
  65. package/src/dap/session.ts +575 -234
  66. package/src/dap/types.ts +6 -5
  67. package/src/discovery/agents.ts +2 -2
  68. package/src/exec/bash-executor.ts +68 -8
  69. package/src/extensibility/extensions/load-errors.ts +13 -0
  70. package/src/extensibility/extensions/runner.ts +6 -4
  71. package/src/extensibility/legacy-pi-coding-agent-shim.ts +26 -0
  72. package/src/extensibility/plugins/legacy-pi-compat.ts +41 -6
  73. package/src/launch/broker.ts +34 -31
  74. package/src/launch/client.ts +7 -1
  75. package/src/launch/spawn-options.test.ts +31 -0
  76. package/src/launch/spawn-options.ts +17 -0
  77. package/src/lsp/types.ts +5 -1
  78. package/src/main.ts +25 -4
  79. package/src/mcp/transports/stdio.test.ts +9 -1
  80. package/src/modes/components/ask-dialog.ts +137 -73
  81. package/src/modes/components/chat-transcript-builder.ts +10 -1
  82. package/src/modes/components/status-line/component.ts +57 -3
  83. package/src/modes/components/status-line/segments.ts +20 -3
  84. package/src/modes/components/status-line/types.ts +3 -1
  85. package/src/modes/components/tool-execution.ts +5 -0
  86. package/src/modes/components/transcript-container.ts +23 -122
  87. package/src/modes/components/tree-selector.ts +10 -4
  88. package/src/modes/components/usage-row.ts +17 -1
  89. package/src/modes/controllers/command-controller.ts +41 -1
  90. package/src/modes/controllers/event-controller.ts +7 -3
  91. package/src/modes/controllers/input-controller.ts +1 -1
  92. package/src/modes/controllers/selector-controller.ts +29 -8
  93. package/src/modes/interactive-mode.ts +102 -60
  94. package/src/modes/noninteractive-dispose.test.ts +14 -1
  95. package/src/modes/print-mode.ts +81 -9
  96. package/src/modes/rpc/rpc-client.ts +76 -35
  97. package/src/modes/rpc/rpc-frame.ts +156 -0
  98. package/src/modes/rpc/rpc-input.ts +38 -0
  99. package/src/modes/rpc/rpc-mode.ts +11 -4
  100. package/src/modes/setup-wizard/index.ts +2 -0
  101. package/src/modes/setup-wizard/scenes/model.ts +134 -0
  102. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -1
  103. package/src/modes/types.ts +2 -0
  104. package/src/modes/utils/ui-helpers.ts +9 -3
  105. package/src/prompts/system/workflow-notice.md +89 -74
  106. package/src/prompts/tools/browser.md +3 -2
  107. package/src/prompts/tools/debug.md +2 -7
  108. package/src/prompts/tools/github.md +6 -1
  109. package/src/prompts/tools/hub.md +4 -2
  110. package/src/prompts/tools/task-async-contract.md +1 -0
  111. package/src/prompts/tools/task.md +9 -2
  112. package/src/sdk.ts +38 -6
  113. package/src/session/agent-session.ts +484 -188
  114. package/src/session/messages.test.ts +91 -0
  115. package/src/session/messages.ts +248 -110
  116. package/src/session/session-manager.ts +34 -3
  117. package/src/session/session-paths.ts +38 -9
  118. package/src/slash-commands/builtin-registry.ts +1 -0
  119. package/src/task/executor.ts +104 -33
  120. package/src/task/index.ts +46 -9
  121. package/src/task/worktree.ts +10 -0
  122. package/src/tools/browser/aria/aria-snapshot.ts +36 -8
  123. package/src/tools/browser/cmux/cmux-tab.ts +2 -1
  124. package/src/tools/browser/cmux/socket-client.ts +139 -3
  125. package/src/tools/browser/run-cancellation.ts +4 -0
  126. package/src/tools/browser/tab-protocol.ts +2 -0
  127. package/src/tools/browser/tab-supervisor.ts +21 -11
  128. package/src/tools/browser/tab-worker.ts +199 -33
  129. package/src/tools/debug.ts +3 -0
  130. package/src/tools/gh.ts +40 -2
  131. package/src/tools/hub/index.ts +4 -1
  132. package/src/tools/hub/jobs.ts +42 -1
  133. package/src/tools/hub/types.ts +2 -0
  134. package/src/tools/tool-timeouts.ts +1 -1
  135. package/src/tools/xdev.ts +11 -4
  136. package/src/tools/yield.ts +4 -1
  137. package/src/utils/git.ts +237 -0
  138. package/src/{modes/components/status-line → utils}/token-rate.ts +6 -0
  139. package/src/vibe/__tests__/token-rate.test.ts +96 -0
  140. package/src/vibe/runtime.ts +50 -0
  141. package/src/web/search/index.ts +9 -5
  142. package/src/web/search/providers/codex.ts +195 -99
  143. /package/dist/types/{modes/components/status-line → utils}/token-rate.d.ts +0 -0
@@ -1,89 +1,104 @@
1
1
  <system-notice>
2
- The user's message above contains the **workflowz** keyword: drive this task as a deterministic multi-subagent workflow. Use the `task` tool {{#if taskBatch}}for batched fan-out{{else}}once per independent subagent{{/if}} — to be comprehensive (decompose and cover in parallel), to be confident (independent perspectives and adversarial checks before you commit), or to take on scale one context can't hold (audits, migrations, broad sweeps). This overrides any default tendency to do the whole task inline when fanning out would be more thorough.
2
+ The user's message above contains the **workflowz** keyword: drive this task as a deterministic multi-subagent workflow. Author the orchestration in the `eval` tool and fan out subagents — to be comprehensive (decompose and cover in parallel), to be confident (independent perspectives and adversarial checks before you commit), or to take on scale one context can't hold (audits, migrations, broad sweeps). This overrides any default tendency to do the whole task inline when fanning out would be more thorough.
3
3
 
4
4
  <when>
5
- Worth it when the task benefits from decomposition + parallel coverage, or from independent/adversarial cross-checking before you commit. For a quick lookup or single edit, just do it directly — don't spin up agents. Scout inline first (list the files, scope the diff, find the call sites) to discover the work list, then fan out over it. Common shapes:
6
- - **Understand** — parallel readers over subsystems → structured map.
7
- - **Design** — independent approaches → scored synthesis.
8
- - **Review** — split dimensions → find per dimension → adversarially verify each finding.
9
- - **Research** — multi-modal sweep → deep-read the hits → synthesize.
10
- - **Migrate** — discover sites → transform each → verify.
5
+ Worth it when the task benefits from decomposition + parallel coverage, or from independent/adversarial cross-checking before you commit. For a quick lookup or single edit, just do it directly — don't spin up agents. Scout inline FIRST (list the files, scope the diff, find the call sites) to discover the work-list, then fan out over it — you don't need to know the shape before the *task*, only before the *fan-out*. Common shapes, each a well-scoped `eval` call you can chain across turns:
6
+ - **Understand** — parallel readers over subsystems → structured map
7
+ - **Design** — judge panel of N independent approaches → scored synthesis
8
+ - **Review** — split into dimensions → find per dimension → adversarially verify each finding
9
+ - **Research** — multi-modal sweep → deep-read the hits → synthesize
10
+ - **Migrate** — discover sites → transform each → verify
11
11
  </when>
12
12
 
13
- <task-contract>
14
- {{#if taskBatch}}
15
- Call `task` once per independent fan-out batch. Put shared background in `context`, and put each independent work item in `tasks[]`. Do not emulate batching with shell loops or eval helper APIs.
16
-
17
- `context` must carry the shared contract:
18
-
19
- # Goal
20
- What the batch accomplishes.
21
- # Constraints
22
- Rules, non-goals, permissions, and verification limits.
23
- # Contract
24
- Shared interfaces, output shape, branch/base assumptions, and coordination rules.
25
-
26
- Each task assignment must be self-contained:
27
-
28
- # Target
29
- Exact files, symbols, subsystem, or evidence surface; explicit non-goals.
30
- # Change
31
- What to inspect or modify, step by step, including APIs and patterns to reuse.
32
- # Acceptance
33
- Observable result, return packet, and local verification. Subagents skip formatters,
34
- linters, and project-wide tests; the parent runs shared proof once.
35
- {{else}}
36
- Call `task` once per independent subagent. Put the full shared background and the leaf work in that call's `assignment`. Do not pass `context` or `tasks[]`: the flat task schema rejects them when batch calls are disabled.
37
-
38
- Each assignment must be self-contained:
39
-
40
- # Target
41
- Exact files, symbols, subsystem, or evidence surface; explicit non-goals.
42
- # Change
43
- Shared background plus what to inspect or modify, step by step, including APIs and patterns to reuse.
44
- # Acceptance
45
- Observable result, return packet, and local verification. Subagents skip formatters,
46
- linters, and project-wide tests; the parent runs shared proof once.
47
- {{/if}}
13
+ <helpers>
14
+ State persists across eval calls, so scout in one call and fan out in the next. Every eval call has:
15
+
16
+ - `agent(prompt, *, agent="task", model=None, label=None, schema=None, isolated=None, apply=None, merge=None, handle=False)` — run ONE subagent; returns its final text, or the validated object when `schema` (a JSON Schema dict) is given. With `schema` the subagent is forced to emit structured output that is validated for you — branch on the object, not on parsed prose. `agent` picks a discovered agent ("explore", "reviewer", …); `label` names the artifact. Shared background goes in a `local://` file referenced from each prompt, not a parameter. Subagents are told their final text IS the return value, so they hand back raw data. `agent()` blocks until the subagent finishes. Recursion follows `task.maxRecursionDepth` (default 2; a negative value disables the cap): main agent depth = 0, each `agent()` child increments depth by 1, and, when the cap is non-negative, a spawner may call `agent()` only while its current `taskDepth < cap`. Pass `isolated=True` to run the spawn in a copy-on-write worktree so parallel `agent()` calls can edit overlapping files safely — strict opt-in, mirrors the `task` tool, defaults off regardless of `task.isolation.mode`; `isolated=True` while the setting is `"none"` errors out instead of silently downgrading. With isolation, `apply=False` keeps changes in the worktree, and `merge=False` forces patch mode even when the setting is `"branch"`. Captured root patch path, branch name, nested repo patches, and apply summary reach the workflow through `handle=True` — combine it with `apply=False` (or `apply=False, schema=…`) and read `node["patch_path"]`, `node["branch_name"]`, `node["nested_patches"]`, `node["changes_applied"]`, `node["isolation_summary"]` (JS: same keys camelCased) to recover artifacts.
17
+ - `parallel(thunks)` run zero-arg callables concurrently through a bounded pool, preserving input order; returns once all finish. The pool is bounded by the session's `task` concurrency — don't hand-tune it; fan out as wide as the work divides. A thunk that raises propagates — wrap risky work in `try/except` inside the thunk to keep partial results. In a loop, bind each closure's value with a default arg (`lambda d=d: …`) or every thunk captures the last one.
18
+ - `pipeline(items, *stages)` — map items through `stages` left-to-right. There is a BARRIER between stages: ALL items clear stage N before stage N+1 begins. Each stage is a one-arg callable; stage 1 gets the original item, later stages get the previous result. Same pool width as `parallel()`.
19
+ - `completion(prompt, *, model="default", system=None, schema=None)` — oneshot, stateless model call (no tools, no history). Tiers: "smol", "default", "slow". Cheap classification/scoring inside a fan-out.
20
+ - `log(message)` — emit a progress line above the status tree. `phase(title)` — start a phase; the status lines that follow group under it.
21
+ - `budget` — `budget.total` (output-token ceiling, or `None` when none is set), `budget.spent()` (tokens spent this turn — main loop + eval subagents), `budget.remaining()` (`math.inf` when total is `None`), `budget.hard` (whether it's enforced). A ceiling is set by the user: `+Nk` in their message is advisory (you self-limit via `budget.remaining()`), `+Nk!` (or Goal Mode) is hard — `agent()` refuses to spawn once spent reaches it. Gate loops on `budget.total` first, since it's `None` when the user set no budget.
22
+
23
+ Everything runs INLINE and synchronously inside the eval call — no background mode, no resume, no separate progress app. Each eval call is one well-scoped fan-out; chain several across calls and turns for multi-phase work, reading each result before you decide the next phase.
24
+ </helpers>
48
25
 
49
26
  <structure>
50
- Decompose first, then {{#if taskBatch}}batch the independent leaves{{else}}issue one independent task call per leaf in the same turn{{/if}}:
51
-
52
- {{#if taskBatch}}
53
- task(
54
- context: "# Goal\nReview the auth diff…\n# Constraints\nRead-only…\n# Contract\nReturn findings as severity/file/line/fix…",
55
- tasks: [
56
- { id: "AuthOwner", role: "Auth Storage Reviewer", assignment: "# Target\npackages/ai/src/auth-storage.ts\n# Change\nTrace credential selection…\n# Acceptance\nReturn confirmed findings only…" },
57
- { id: "PromptOwner", role: "Prompt Contract Reviewer", assignment: "# Target\npackages/coding-agent/src/prompts/**\n# Change\nCheck active-tool guidance…\n# Acceptance\nReturn mismatches and exact prompt lines…" },
58
- ]
59
- )
60
- {{else}}
61
- task(
62
- role: "Auth Storage Reviewer",
63
- assignment: "# Target\npackages/ai/src/auth-storage.ts\n# Change\nReview the auth diff. Shared contract: read-only; return findings as severity/file/line/fix.\n# Acceptance\nReturn confirmed findings only…"
64
- )
65
- task(
66
- role: "Prompt Contract Reviewer",
67
- assignment: "# Target\npackages/coding-agent/src/prompts/**\n# Change\nCheck active-tool guidance. Shared contract: read-only; return mismatches and exact prompt lines.\n# Acceptance\nReturn confirmed findings only…"
68
- )
69
- {{/if}}
70
-
71
- {{#if taskBatch}}Prefer one wide batch over serial subagent calls when work items do not share files. If tasks overlap, name the overlap and have agents coordinate through IRC before editing.{{else}}Prefer issuing all independent task calls in one assistant turn over serial dispatch when work items do not share files. If tasks overlap, name the overlap and have agents coordinate through IRC before editing.{{/if}}
27
+ For independent per-item chains (review → verify, fetch extract → score), wrap the WHOLE chain in one function and run it with `parallel()` — then each item flows through its own steps without waiting on the others:
28
+
29
+ **Python (`eval`, Python backend):**
30
+
31
+ DIMENSIONS = [{"key": "bugs", "prompt": "…"}, {"key": "perf", "prompt": "…"}]
32
+ def review_and_verify(d):
33
+ found = agent(d["prompt"], label=f"review:{d['key']}", schema=FINDINGS_SCHEMA)
34
+ return parallel([lambda f=f: {**f, "verdict": agent(
35
+ f"Refute if you can (default refuted when unsure): {f['title']}",
36
+ label=f"verify:{f['file']}", schema=VERDICT_SCHEMA)} for f in found["findings"]])
37
+ phase("Review")
38
+ results = parallel([lambda d=d: review_and_verify(d) for d in DIMENSIONS])
39
+ confirmed = [f for group in results for f in group if f["verdict"]["is_real"]]
40
+
41
+ **JavaScript (`eval`, JavaScript backend):**
42
+
43
+ const DIMENSIONS = [{ key: "bugs", prompt: "…" }, { key: "perf", prompt: "…" }];
44
+ async function reviewAndVerify(d) {
45
+ const found = await agent(d.prompt, {
46
+ label: `review:${d.key}`,
47
+ schema: FINDINGS_SCHEMA,
48
+ });
49
+ return await parallel(found.findings.map((f) => async () => ({
50
+ …f,
51
+ verdict: await agent(
52
+ `Refute if you can (default refuted when unsure): ${f.title}`,
53
+ { label: `verify:${f.file}`, schema: VERDICT_SCHEMA },
54
+ ),
55
+ })));
56
+ }
57
+ phase("Review");
58
+ const results = await parallel(DIMENSIONS.map((d) => async () => reviewAndVerify(d)));
59
+ const confirmed = results.flat().filter((f) => f.verdict.is_real);
60
+ Reach for `pipeline()` only when a stage genuinely needs ALL of the previous stage first — dedup/merge across the whole set, early-exit on zero, or "compare against the other findings" — because its inter-stage barrier makes every item wait for the slowest peer:
61
+
62
+ **Python (`eval`, Python backend):**
63
+
64
+ phase("Find")
65
+ found = parallel([lambda d=d: agent(d["prompt"], schema=FINDINGS_SCHEMA) for d in DIMENSIONS])
66
+ findings = dedupe([f for r in found for f in r["findings"]]) # needs everything at once
67
+ phase("Verify")
68
+ verdicts = parallel([lambda f=f: agent(verify_prompt(f), schema=VERDICT_SCHEMA) for f in findings])
69
+
70
+ **JavaScript (`eval`, JavaScript backend):**
71
+
72
+ phase("Find");
73
+ const found = await parallel(DIMENSIONS.map((d) => async () =>
74
+ await agent(d.prompt, { schema: FINDINGS_SCHEMA }),
75
+ ));
76
+ const findings = dedupe(found.flatMap((r) => r.findings)); // needs everything at once
77
+ phase("Verify");
78
+ const verdicts = await parallel(findings.map((f) => async () =>
79
+ await agent(verifyPrompt(f), { schema: VERDICT_SCHEMA }),
80
+ ));
81
+ Use ordinary code between calls to flatten/map/filter; don't add a barrier just for that. Nested `parallel()` pools each cap independently, so keep total fan-out sane.
72
82
  </structure>
73
83
 
74
84
  <patterns>
75
- - **Adversarial verify** — dispatch skeptical reviewers with distinct targets, then keep only findings the parent can verify against source.
76
- - **Perspective-diverse review** — use separate correctness, security, performance, and maintainability roles instead of identical reviewers.
77
- - **Completeness critic** — after the first batch, dispatch one read-only critic that asks what modality, file, claim, or proof was missed.
78
- - **No silent caps** — if you bound coverage (top-N, no retry, sampling), state what was dropped and why before acting.
79
- - **Parent owns closure** — subagents return evidence; the parent reads it, resolves contradictions, runs proof, and makes the final decision.
85
+ Compose the harness the task calls for:
86
+ - **Adversarial verify** — N independent skeptics per finding, each prompted to REFUTE; keep it only if a majority survive. `votes = parallel([lambda i=i: agent(f"Refute: {claim}. refuted=true if unsure.", schema=VERDICT) for i in range(3)])`, then keep when `sum(not v["refuted"] for v in votes) ≥ 2`.
87
+ - **Perspective-diverse verify** — give each verifier a distinct lens (correctness, security, perf, does-it-reproduce) instead of N identical refuters.
88
+ - **Judge panel** — N attempts from different angles, scored by parallel judges; synthesize from the winner, graft the best of the rest.
89
+ - **Loop-until-dry** — for unknown-size discovery, keep spawning finders until K consecutive rounds surface nothing new; dedup against everything SEEN, not just what was confirmed, or it never converges.
90
+ - **Multi-modal sweep** — parallel finders each searching a different way (by-container, by-content, by-entity, by-time), each blind to the others.
91
+ - **Completeness critic** — a final agent that asks "what's missing — modality not run, claim unverified, file unread?"; its answer is the next round.
92
+ - **Budget/count loops** — Python: `while len(bugs) < 10:`; JavaScript: `while (bugs.length < 10) { … }`. In Python, gate an explicit budget with `budget.total` and `budget.remaining()`; in JavaScript, use `await budget.total()` and `await budget.remaining()`. `log()` each round.
93
+ - **No silent caps** — if you bound coverage (top-N, no-retry, sampling), `log()` what you dropped; silent truncation reads as "covered everything" when it didn't.
94
+
95
+ Scale to the ask: "find any bugs" → a few finders, single-vote verify. "thoroughly audit / be comprehensive" → larger finder pool, 3–5-vote adversarial pass, a synthesis stage.
80
96
  </patterns>
81
97
 
82
98
  <execution>
83
- - Capture multi-phase workflow state in the visible todo system when available.
84
- {{#if taskBatch}}- Batch independent subagents in one `task` call.{{else}}- Dispatch independent subagents as separate `task` calls in the same turn.{{/if}}
85
- - Give every subagent a narrow target, explicit non-goals, and a concrete return packet.
86
- - After fan-out returns, read the artifacts, patch or decide, and run the shared gate.
87
- - Keep going until the task is closed — returned fan-out is a step, not a stopping point.
99
+ - Decompose the surface first; capture it in `todo` when it spans phases.
100
+ - Prefer `schema=` for any agent whose output you branch on.
101
+ - After a fan-out returns, YOU own correctness: read the artifacts, run the gate, verify before acting. Subagents do the legwork; they don't get the last word.
102
+ - Keep going until the task is closed — a returned fan-out is a step, not a stopping point.
88
103
  </execution>
89
104
  </system-notice>
@@ -6,7 +6,7 @@ Drives real Chromium tab; full puppeteer access via JS.
6
6
  - `run` scope: `page`, `browser`, `tab`, `display`, `assert`, `wait` available. `wait(fn)` polls until truthy — use instead of polling inside `tab.evaluate`.
7
7
 
8
8
  - `tab` helpers (drop to raw puppeteer `page` for anything uncovered):
9
- Element handles: `tab.ref("e5")` / `tab.id(n)`. Also `aria-ref=e5` inline.
9
+ Element handles: `tab.ref("e5")` / `tab.id(n)` return a handle you call methods on directly — `(await tab.id(n)).click()`. Handles are NOT selectors: `tab.click`/`type`/`fill`/`waitFor*` take STRING selectors only. Snapshot refs work in any selector slot: `tab.click("e5")` ≡ `tab.click("aria-ref=e5")`.
10
10
  Simple: `tab.goto`, `tab.click`, `tab.type`, `tab.fill`, `tab.press`, `tab.scroll`, `tab.scrollIntoView`, `tab.drag`, `tab.uploadFile`, `tab.select`, `tab.screenshot`, `tab.extract`, `tab.evaluate`.
11
11
  Waits: `tab.waitFor`, `tab.waitForSelector`, `tab.waitForUrl`, `tab.waitForResponse`, `tab.waitForNavigation`.
12
12
  Snapshots: `tab.observe()` → accessibility tree; `tab.ariaSnapshot()` → ARIA YAML with `[ref=eN]`.
@@ -14,8 +14,9 @@ Drives real Chromium tab; full puppeteer access via JS.
14
14
  Gotchas:
15
15
  - `tab.fill` NEVER works for `<select>` — use `tab.select`.
16
16
  - `tab.waitForNavigation` must start BEFORE the trigger click.
17
- - Navigation invalidates element ids — re-observe.
17
+ - Navigation and re-renders (virtualized lists, SPA updates) invalidate ids/refs — re-observe or re-snapshot, then act in the same cell.
18
18
  - Stalled actions fail fast with named error, never whole-cell timeout.
19
+ - Raw request interception is run-scoped: run end removes `request` handlers, disables interception, releases held requests.
19
20
 
20
21
  - `app.path` → NEVER tamper with a real desktop app (no stealth patches).
21
22
  - Selectors: CSS + puppeteer `aria/…`, `text/…`, `xpath/…`, `pierce/…`. Playwright-only pseudos (`:has-text()`, `:visible`) are REJECTED.
@@ -1,8 +1,3 @@
1
1
  Debugger access. Prefer over bash for program state, breakpoints, stepping, or thread inspection.
2
-
3
- Only one active session at a time. `program` is a target path, not a shell command. Directories need a directory-capable adapter (`dlv`).
4
-
5
- Adapters:
6
- - Python: `debugpy` (`pip install debugpy`)
7
- - Go: Delve (`go install github.com/go-delve/delve/cmd/dlv@latest`)
8
- - Ruby: `rdbg` (`gem install debug`)
2
+ Only one active session at a time. `program` is a target path, not a shell command.
3
+ Directories need a directory-capable adapter (e.g. `dlv`).
@@ -1,8 +1,9 @@
1
- Op-based `gh` wrapper: repos, PRs, search, checkout, push, Actions watch. Read an issue/PR via `issue://<N>`/`pr://<N>`. PR diffs: `pr://<N>/diff` (file listing), `pr://<N>/diff/<i>` (file slice, 1-indexed), `pr://<N>/diff/all` (full diff).
1
+ Op-based `gh` wrapper: repos, repository files, PRs, search, checkout, push, Actions watch. Read an issue/PR via `issue://<N>`/`pr://<N>`. PR diffs: `pr://<N>/diff` (file listing), `pr://<N>/diff/<i>` (file slice, 1-indexed), `pr://<N>/diff/all` (full diff).
2
2
 
3
3
  <instruction>
4
4
  Pick op via `op`. Beyond the field descriptions, per op:
5
5
  - `repo_view` — omit `repo` to view the current checkout.
6
+ - `file_read` — reads `path` from `repo`; omit `repo` for the current checkout and `branch` for its default branch.
6
7
  - `pr_create` — `head` defaults to the current branch.
7
8
  - `pr_checkout` — checks PR(s) out into dedicated git worktrees, not your working tree; pass an array of `pr` to batch multiple in one call.
8
9
  - `pr_push` — requires the branch to have been checked out first via `op: pr_checkout`.
@@ -15,3 +16,7 @@ Pick op via `op`. Beyond the field descriptions, per op:
15
16
  <output>
16
17
  Concise summary per op. `run_watch` failures save full logs to a session artifact.
17
18
  </output>
19
+
20
+ <critical>
21
+ GitHub-hosted repository file? MUST use `file_read`; NEVER `curl`/`wget`.
22
+ </critical>
@@ -3,7 +3,7 @@ Use `op: "list"` to discover peers. Address peers by exact roster ID — NEVER i
3
3
 
4
4
  # Messaging & Jobs
5
5
 
6
- Background jobs deliver their results automatically the moment they finish. You NEVER need to poll for output intervene only to block, kill, or inspect.
6
+ Background jobs auto-deliver when they finish. You NEVER need to poll; if `jobs`/`wait` observes a settled job first, that snapshot is the delivery and suppresses duplicate `async-result`.
7
7
 
8
8
  - **`send`** (with `to`): fire-and-forget, NEVER blocks. Delivery receipts (`delivered`/`failed`) immediate; `failed` → peer gone, don't retry.
9
9
  Sending wakes `idle`/`parked` peers. Answering: lead with answer, NEVER quote, set `replyTo`.
@@ -12,7 +12,9 @@ Background jobs deliver their results automatically the moment they finish. You
12
12
  - Bare `wait` watches every running job AND incoming messages. NEVER pass an array of every running ID; `ids` narrows to specific jobs, `from` to one peer (or use `await: true` on send).
13
13
  - **`inbox`**: drain queued messages without blocking.
14
14
  - **`cancel`**: kill background jobs by `ids` when they have hung, stalled, or are no longer needed. Returns immediately.
15
- - **`jobs`**: status snapshot of every job without waiting. Also names running subagents with no job entry — coordinate with those via `send`.
15
+ - **`jobs`**: status snapshot of every job without waiting. A settled row consumes auto-delivery. Also names running subagents with no job entry — coordinate with those via `send`.
16
+ - Job rows are process-local and expire roughly five minutes after settlement. Afterward, use the agent ID with `send`, `agent://<id>`, or `history://<id>`.
17
+ - `completed` means successful yield/job exit, not artifact acceptance. Verify claimed changes.
16
18
  - NEVER use shell tools, grep, or read other sessions' files to figure out what a peer is doing. Message them directly.
17
19
  - NEVER use hub messaging for something a tool can answer (e.g., grepping codebase, running a build).
18
20
 
@@ -0,0 +1 @@
1
+ No polling is needed. Inspecting a settled job with `hub jobs` or `hub wait` makes that snapshot its delivery, so no duplicate `async-result` follows. Job IDs live in process memory for roughly five minutes after settlement; afterward, use the agent ID with `hub send`, `agent://<id>`, or `history://<id>`. `completed` means the subagent yielded successfully, not that claimed artifacts were verified.
@@ -1,7 +1,14 @@
1
1
  {{#if asyncEnabled}}{{#if batchEnabled}}Delegate work to background subagents by passing multiple items in a single `tasks[]` batch.
2
- Execution does not block — you receive IDs immediately; results deliver when subagents finish.{{else}}Delegate work to ONE background subagent per call.
3
- Execution does not block — you receive an ID immediately; the result delivers when the subagent finishes.{{/if}}{{#if hasBlockingAgents}}
2
+ Execution does not block — you receive IDs immediately.{{else}}Delegate work to ONE background subagent per call.
3
+ Execution does not block — you receive an ID immediately.{{/if}}{{#if hasBlockingAgents}}
4
4
  Agents marked BLOCKING run inline — results return in this call; non-blocking items in the same batch still spawn as background jobs.{{/if}}{{else}}{{#if batchEnabled}}Run subagents synchronously by passing items in a `tasks[]` batch. Execution blocks until all work finishes.{{else}}Run ONE subagent synchronously. Execution blocks until work finishes.{{/if}}{{/if}}
5
+ {{#if asyncEnabled}}
6
+
7
+ # Async Job Contract
8
+ - Results auto-deliver. A settled `hub jobs`/`hub wait` snapshot is the delivery; no duplicate `async-result` follows.
9
+ - Job IDs are process-local and expire roughly five minutes after settlement. Afterward, use the agent ID with `hub send`, `agent://<id>`, or `history://<id>`.
10
+ - `completed` means successful yield/job exit, not artifact acceptance. Verify claimed changes.
11
+ {{/if}}
5
12
 
6
13
  # Task Design
7
14
  - **Agent typing:** Pick each item's `agent` type. Read-only research MUST use `agent: "scout"` (faster model). Use default worker only when no specialist fits.
package/src/sdk.ts CHANGED
@@ -50,6 +50,7 @@ import {
50
50
  parseModelString,
51
51
  pickDefaultAvailableModel,
52
52
  resolveAllowedModels,
53
+ resolveCliModel,
53
54
  resolveConfiguredModelPatterns,
54
55
  resolveModelRoleValue,
55
56
  } from "./config/model-resolver";
@@ -389,6 +390,8 @@ export interface CreateAgentSessionOptions {
389
390
  modelPatternAuthFallback?: string;
390
391
  /** Role name used to install retry fallbacks after deferred subagent patterns resolve. */
391
392
  modelPatternFallbackRole?: string;
393
+ /** Validated default retry chain to install when a deferred singleton pattern resolves. */
394
+ modelPatternDefaultFallbackChain?: string[];
392
395
  /** Thinking selector. Default: from settings, else unset */
393
396
  thinkingLevel?: ConfiguredThinkingLevel;
394
397
  /** Models available for cycling (Ctrl+P in interactive mode) */
@@ -2066,13 +2069,35 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2066
2069
  }
2067
2070
  }
2068
2071
  // Resolve deferred --model/subagent patterns now that extension models are
2069
- // registered. Expand role aliases (`@smol`) and comma chains to concrete
2070
- // selectors first so deferred resolution accepts everything the immediate
2071
- // path (resolveModelOverride → resolveModelRoleValue) accepts.
2072
+ // registered. Use the same CLI resolver as the immediate path so bare role
2073
+ // names, exact model names, and provider selectors keep one precedence rule.
2072
2074
  if (!model && deferredModelPatterns.length > 0) {
2073
- const expandedModelPatterns = resolveConfiguredModelPatterns(deferredModelPatterns, settings);
2074
2075
  const availableModels = modelRegistry.getAll();
2075
2076
  const matchPreferences = getModelMatchPreferences(settings);
2077
+ const expandedModelPatterns = deferredModelPatterns.flatMap(pattern =>
2078
+ pattern.split(",").flatMap(selector => {
2079
+ const trimmedSelector = selector.trim();
2080
+ if (!trimmedSelector) return [];
2081
+ const resolved = resolveCliModel({
2082
+ cliModel: trimmedSelector,
2083
+ modelRegistry,
2084
+ settings,
2085
+ preferences: matchPreferences,
2086
+ });
2087
+ if (resolved.configuredPatterns && resolved.configuredPatterns.length > 0) {
2088
+ return resolved.configuredPatterns;
2089
+ }
2090
+ if (resolved.model) {
2091
+ return [
2092
+ formatModelSelectorValue(
2093
+ resolved.selector ?? formatModelStringWithRouting(resolved.model),
2094
+ resolved.thinkingLevel,
2095
+ ),
2096
+ ];
2097
+ }
2098
+ return resolveConfiguredModelPatterns([trimmedSelector], settings);
2099
+ }),
2100
+ );
2076
2101
  for (let patternIndex = 0; patternIndex < expandedModelPatterns.length; patternIndex += 1) {
2077
2102
  const pattern = expandedModelPatterns[patternIndex];
2078
2103
  const primary = parseModelPattern(pattern, availableModels, matchPreferences);
@@ -2118,6 +2143,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2118
2143
  seenSelectors.add(fallbackSelector);
2119
2144
  fallbackSelectors.push(fallbackSelector);
2120
2145
  }
2146
+ if (fallbackSelectors.length === 0) {
2147
+ for (const selector of options.modelPatternDefaultFallbackChain ?? []) {
2148
+ if (typeof selector !== "string" || seenSelectors.has(selector)) continue;
2149
+ seenSelectors.add(selector);
2150
+ fallbackSelectors.push(selector);
2151
+ }
2152
+ }
2121
2153
  if (fallbackSelectors.length > 0) {
2122
2154
  const modelRoles: Record<string, string> = {};
2123
2155
  const existingRoles = settings.getModelRoles();
@@ -2698,8 +2730,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2698
2730
  if (snapcompactInline) transformed = await snapcompactInline.transform(transformed, transformModel);
2699
2731
  return clampProviderContextImages(transformed, transformModel);
2700
2732
  };
2701
- const onPayload = async (payload: unknown, _model?: Model) => {
2702
- return await extensionRunner.emitBeforeProviderRequest(payload);
2733
+ const onPayload = async (payload: unknown, model?: Model) => {
2734
+ return await extensionRunner.emitBeforeProviderRequest(payload, model);
2703
2735
  };
2704
2736
  const onResponse: SimpleStreamOptions["onResponse"] = async (response, model) => {
2705
2737
  await extensionRunner.emitAfterProviderResponse(response, model);