@oh-my-pi/pi-coding-agent 16.2.5 → 16.2.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 (51) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/dist/cli.js +2991 -3011
  3. package/dist/types/cli/bench-cli.d.ts +6 -1
  4. package/dist/types/commands/bench.d.ts +8 -0
  5. package/dist/types/config/model-discovery.d.ts +6 -1
  6. package/dist/types/config/settings-schema.d.ts +1 -1
  7. package/dist/types/edit/index.d.ts +1 -0
  8. package/dist/types/edit/renderer.d.ts +4 -0
  9. package/dist/types/edit/snapshot-details.d.ts +33 -0
  10. package/dist/types/mcp/oauth-flow.d.ts +4 -6
  11. package/dist/types/modes/controllers/tool-args-reveal.d.ts +9 -2
  12. package/dist/types/session/agent-session.d.ts +4 -0
  13. package/dist/types/session/session-manager.d.ts +3 -4
  14. package/dist/types/web/search/providers/duckduckgo.d.ts +2 -2
  15. package/dist/types/web/search/types.d.ts +1 -1
  16. package/package.json +12 -12
  17. package/src/cli/args.ts +32 -1
  18. package/src/cli/bench-cli.ts +89 -21
  19. package/src/cli/web-search-cli.ts +6 -1
  20. package/src/commands/bench.ts +10 -1
  21. package/src/config/mcp-schema.json +1 -1
  22. package/src/config/model-discovery.ts +66 -8
  23. package/src/config/model-registry.ts +13 -6
  24. package/src/edit/hashline/execute.ts +15 -9
  25. package/src/edit/index.ts +19 -6
  26. package/src/edit/modes/patch.ts +3 -2
  27. package/src/edit/modes/replace.ts +3 -2
  28. package/src/edit/renderer.ts +4 -0
  29. package/src/edit/snapshot-details.ts +77 -0
  30. package/src/extensibility/plugins/legacy-pi-compat.ts +2 -2
  31. package/src/internal-urls/docs-index.generated.txt +1 -1
  32. package/src/mcp/oauth-flow.ts +10 -8
  33. package/src/mcp/transports/stdio.ts +9 -17
  34. package/src/modes/controllers/event-controller.ts +17 -8
  35. package/src/modes/controllers/tool-args-reveal.ts +100 -22
  36. package/src/prompts/bench.md +4 -10
  37. package/src/prompts/tools/irc.md +19 -29
  38. package/src/prompts/tools/job.md +8 -14
  39. package/src/prompts/tools/lsp.md +19 -30
  40. package/src/prompts/tools/task.md +42 -62
  41. package/src/sdk.ts +1 -0
  42. package/src/session/agent-session.ts +10 -0
  43. package/src/session/session-listing.ts +9 -8
  44. package/src/session/session-loader.ts +98 -3
  45. package/src/session/session-manager.ts +34 -4
  46. package/src/subprocess/worker-client.ts +12 -4
  47. package/src/tools/path-utils.ts +4 -2
  48. package/src/web/search/index.ts +14 -8
  49. package/src/web/search/providers/duckduckgo.ts +136 -78
  50. package/src/web/search/providers/gemini.ts +268 -185
  51. package/src/web/search/types.ts +1 -1
@@ -75,6 +75,10 @@ export interface MCPStoredOAuthCredential extends OAuthCredential {
75
75
  const DEFAULT_PORT = 3000;
76
76
  const CALLBACK_PATH = "/callback";
77
77
 
78
+ function hasOAuthScope(scopes: string | null | undefined, scope: string): boolean {
79
+ return !!scopes && scopes.split(/\s+/).includes(scope);
80
+ }
81
+
78
82
  function isLoopbackHostname(hostname: string): boolean {
79
83
  return hostname === "localhost" || hostname === "127.0.0.1";
80
84
  }
@@ -225,12 +229,10 @@ export interface MCPOAuthConfig {
225
229
  /** OAuth scopes (space-separated) */
226
230
  scopes?: string;
227
231
  /**
228
- * `prompt` parameter for the authorization request. Defaults to `"consent"`
229
- * so the provider always shows its authorize screen instead of silently
230
- * re-approving the browser's current session without it, reauthorizing to
231
- * switch accounts/workspaces is impossible once a session cookie exists
232
- * (RFC 6749 §3.1 requires servers to ignore the param when unsupported).
233
- * Set to `""` to omit the parameter entirely.
232
+ * `prompt` parameter for the authorization request. By default the parameter
233
+ * is omitted, matching the reference MCP SDK, except for `offline_access`
234
+ * requests where OIDC Core requires `prompt=consent` to issue refresh-token
235
+ * access. Set to `""` to omit the parameter entirely.
234
236
  */
235
237
  prompt?: string;
236
238
  /** Exact redirect URI to advertise to the provider */
@@ -325,7 +327,7 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
325
327
  if (this.config.scopes && !params.get("scope")) {
326
328
  params.set("scope", this.config.scopes);
327
329
  }
328
- const prompt = this.config.prompt ?? "consent";
330
+ const prompt = this.config.prompt ?? (hasOAuthScope(params.get("scope"), "offline_access") ? "consent" : "");
329
331
  if (prompt && !params.get("prompt")) {
330
332
  params.set("prompt", prompt);
331
333
  }
@@ -494,7 +496,7 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
494
496
  Accept: "application/json",
495
497
  },
496
498
  body: JSON.stringify({
497
- client_name: "Codex",
499
+ client_name: "oh-my-pi",
498
500
  redirect_uris: [redirectUri],
499
501
  grant_types: ["authorization_code", "refresh_token"],
500
502
  response_types: ["code"],
@@ -147,21 +147,6 @@ function resolveWindowsShimPath(value: string, shimDir: string): string | null {
147
147
  return path.join(shimDir, ...suffix.split(/[\\/]+/).filter(Boolean));
148
148
  }
149
149
 
150
- function extractWindowsNpmShimTarget(content: string): string | null {
151
- const match = /"%_prog%"\s+"([^"]+)"\s+%\*/i.exec(content);
152
- return match?.[1] ?? null;
153
- }
154
-
155
- /**
156
- * Extract the shim's PATH-fallback interpreter (`SET "_prog=node"`). The
157
- * `IF EXIST` branch assigns a `%dp0%`-prefixed value, so requiring a
158
- * non-`%`-leading value picks the bare program name.
159
- */
160
- function extractWindowsNpmShimProg(content: string): string | null {
161
- const match = /SET\s+"_prog=([^%"][^"]*)"/i.exec(content);
162
- return match?.[1] ?? null;
163
- }
164
-
165
150
  async function resolveWindowsNpmShimCommand(
166
151
  command: string,
167
152
  args: readonly string[],
@@ -171,6 +156,11 @@ async function resolveWindowsNpmShimCommand(
171
156
  if (!isWindowsBatchCommand(command)) return null;
172
157
  if (!hasPathSegment(command)) return null;
173
158
  const commandPath = path.resolve(cwd, command);
159
+ const commandName = path
160
+ .basename(commandPath)
161
+ .replace(/\.cmd$/i, "")
162
+ .toLowerCase();
163
+ if (commandName === "npx") return null;
174
164
 
175
165
  let content: string;
176
166
  try {
@@ -181,7 +171,9 @@ async function resolveWindowsNpmShimCommand(
181
171
 
182
172
  // cmd-shim emits the same invocation line for every interpreter; only
183
173
  // bypass cmd.exe when the shim's fallback interpreter is actually node.
184
- const prog = extractWindowsNpmShimProg(content);
174
+ // The IF EXIST branch assigns a %dp0%-prefixed value, so requiring a
175
+ // non-%-leading SET value picks the bare PATH-fallback program name.
176
+ const prog = /SET\s+"_prog=([^%"][^"]*)"/i.exec(content)?.[1];
185
177
  if (
186
178
  !prog ||
187
179
  path
@@ -191,7 +183,7 @@ async function resolveWindowsNpmShimCommand(
191
183
  )
192
184
  return null;
193
185
 
194
- const rawTarget = extractWindowsNpmShimTarget(content);
186
+ const rawTarget = /"%_prog%"\s+"([^"]+)"\s+%\*/i.exec(content)?.[1];
195
187
  if (!rawTarget) return null;
196
188
 
197
189
  const target = resolveWindowsShimPath(rawTarget, path.dirname(commandPath));
@@ -50,6 +50,15 @@ const MAX_LIVE_IRC_CARDS = 4;
50
50
  const IDLE_RECAP_MIN_SECONDS = 1;
51
51
  const IDLE_RECAP_MAX_SECONDS = 3600;
52
52
 
53
+ const RAW_PARTIAL_JSON_RENDERERS: Record<string, true> = { bash: true, edit: true, apply_patch: true };
54
+
55
+ function exposesRawPartialJson(toolName: string, rawInput: boolean, tool: unknown): boolean {
56
+ if (rawInput) return true;
57
+ if (RAW_PARTIAL_JSON_RENDERERS[toolName]) return true;
58
+ if (tool === null || typeof tool !== "object" || !("renderCall" in tool)) return false;
59
+ return typeof tool.renderCall === "function";
60
+ }
61
+
53
62
  type AgentSessionEventHandlers = {
54
63
  [E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
55
64
  };
@@ -623,7 +632,7 @@ export class EventController {
623
632
  // Internal URL read falls through to ToolExecutionComponent below.
624
633
  }
625
634
 
626
- // Preserve the raw partial JSON for renderers that need to surface fields before the JSON object closes.
635
+ // Preserve the raw partial JSON only for renderers that need to surface fields before the JSON object closes.
627
636
  // Bash uses this to show inline env assignments during streaming instead of popping them in at completion.
628
637
  // While the JSON is still open, ToolArgsRevealController paces the
629
638
  // reveal (write/edit/bash previews grow smoothly when a slow provider
@@ -631,13 +640,14 @@ export class EventController {
631
640
  // as-is — mirroring how assistant text snaps at message_end.
632
641
  let renderArgs: Record<string, unknown>;
633
642
  const partialJson = getStreamingPartialJson(content);
643
+ const rawInput = content.customWireName !== undefined;
644
+ const tool = this.ctx.viewSession.getToolByName(content.name);
634
645
  if (partialJson) {
635
- renderArgs = this.#toolArgsReveal.setTarget(
636
- content.id,
637
- partialJson,
638
- content.customWireName !== undefined,
639
- content.arguments,
640
- );
646
+ renderArgs = this.#toolArgsReveal.setTarget(content.id, partialJson, {
647
+ rawInput,
648
+ exposeRawPartialJson: exposesRawPartialJson(content.name, rawInput, tool),
649
+ fullArgs: content.arguments,
650
+ });
641
651
  } else {
642
652
  this.#toolArgsReveal.finish(content.id);
643
653
  renderArgs = content.arguments;
@@ -645,7 +655,6 @@ export class EventController {
645
655
  if (!this.ctx.pendingTools.has(content.id)) {
646
656
  this.#resolveDisplaceablePoll(content.name);
647
657
  this.#resetReadGroup();
648
- const tool = this.ctx.viewSession.getToolByName(content.name);
649
658
  const component = new ToolExecutionComponent(
650
659
  content.name,
651
660
  renderArgs,
@@ -1,4 +1,4 @@
1
- import { parseStreamingJson } from "@oh-my-pi/pi-utils";
1
+ import { parseStreamingJson, parseStreamingJsonThrottled, STREAMING_JSON_PARSE_MIN_GROWTH } from "@oh-my-pi/pi-utils";
2
2
  import { nextStep, STREAMING_REVEAL_FRAME_MS } from "./streaming-reveal";
3
3
 
4
4
  /** Minimal component surface the reveal pushes frames into. */
@@ -19,6 +19,16 @@ type RevealEntry = {
19
19
  revealed: number;
20
20
  /** Custom-tool raw input: display args are `{ input: prefix }`, never parsed as JSON. */
21
21
  rawInput: boolean;
22
+ /** Whether the renderer observes fresh raw JSON prefixes directly. */
23
+ exposeRawPartialJson: boolean;
24
+ /** Last parsed JSON args from the revealed prefix. */
25
+ parsedArgs: Record<string, unknown>;
26
+ /** Prefix length covered by `parsedArgs`. */
27
+ parsedLen: number;
28
+ /** Last object handed to a component; reused when visible args have not changed. */
29
+ displayArgs: Record<string, unknown>;
30
+ /** Raw prefix carried by `displayArgs.__partialJson`. */
31
+ displayPrefix: string;
22
32
  };
23
33
 
24
34
  /** Clamp a slice end into `text`, never splitting a surrogate pair: a prefix
@@ -32,15 +42,64 @@ function clampSliceEnd(text: string, end: number): number {
32
42
  return code >= 0xd800 && code <= 0xdbff ? end + 1 : end;
33
43
  }
34
44
 
35
- /** Display args for a revealed raw-stream prefix. Function-tool prefixes are
36
- * re-parsed with the same streaming-tolerant parser providers use, so every
37
- * frame is a state the provider itself could have produced; custom tools
38
- * mirror the provider's `{ input }` shape. `__partialJson` carries the
39
- * matching raw prefix for renderers that read it directly (bash env preview,
40
- * edit strategies). */
41
- function buildDisplayArgs(prefix: string, rawInput: boolean): Record<string, unknown> {
42
- const base: Record<string, unknown> = rawInput ? { input: prefix } : parseStreamingJson(prefix);
43
- return { ...base, __partialJson: prefix };
45
+ type ToolArgsRevealTarget = {
46
+ rawInput: boolean;
47
+ exposeRawPartialJson: boolean;
48
+ fullArgs: Record<string, unknown>;
49
+ };
50
+
51
+ type DisplayArgsStep = {
52
+ args: Record<string, unknown>;
53
+ changed: boolean;
54
+ };
55
+
56
+ function initialDisplayArgs(): Record<string, unknown> {
57
+ return { __partialJson: "" };
58
+ }
59
+
60
+ function resetDisplayState(entry: RevealEntry): void {
61
+ entry.parsedArgs = {};
62
+ entry.parsedLen = 0;
63
+ entry.displayArgs = initialDisplayArgs();
64
+ entry.displayPrefix = "";
65
+ }
66
+
67
+ /** Display args for a revealed prefix. Function-tool JSON is parsed at the same
68
+ * growth-throttled cadence providers use, so a long `write` payload cannot make
69
+ * the reveal loop re-parse the whole growing buffer every frame. Renderers that
70
+ * read raw JSON directly still receive fresh `__partialJson` prefixes; other
71
+ * renderers get a stable object reference while parsed fields are unchanged. */
72
+ function displayArgsForPrefix(entry: RevealEntry, prefix: string, forceParse = false): DisplayArgsStep {
73
+ if (entry.rawInput) {
74
+ if (prefix === entry.displayPrefix) return { args: entry.displayArgs, changed: false };
75
+ const args = { input: prefix, __partialJson: prefix };
76
+ entry.displayArgs = args;
77
+ entry.displayPrefix = prefix;
78
+ return { args, changed: true };
79
+ }
80
+
81
+ let parsedChanged = false;
82
+ if (forceParse || (prefix.length > 0 && prefix.length < STREAMING_JSON_PARSE_MIN_GROWTH)) {
83
+ entry.parsedArgs = parseStreamingJson<Record<string, unknown>>(prefix);
84
+ entry.parsedLen = prefix.length;
85
+ parsedChanged = true;
86
+ } else {
87
+ const throttled = parseStreamingJsonThrottled<Record<string, unknown>>(prefix, entry.parsedLen);
88
+ if (throttled) {
89
+ entry.parsedArgs = throttled.value;
90
+ entry.parsedLen = throttled.parsedLen;
91
+ parsedChanged = true;
92
+ }
93
+ }
94
+
95
+ const rawPrefixChanged = entry.exposeRawPartialJson && prefix !== entry.displayPrefix;
96
+ if (!parsedChanged && !rawPrefixChanged) return { args: entry.displayArgs, changed: false };
97
+
98
+ const displayPrefix = entry.exposeRawPartialJson || parsedChanged ? prefix : entry.displayPrefix;
99
+ const args = { ...entry.parsedArgs, __partialJson: displayPrefix };
100
+ entry.displayArgs = args;
101
+ entry.displayPrefix = displayPrefix;
102
+ return { args, changed: true };
44
103
  }
45
104
 
46
105
  /**
@@ -49,7 +108,9 @@ function buildDisplayArgs(prefix: string, rawInput: boolean): Record<string, unk
49
108
  * (or throttle their partial parses) would otherwise make write/edit/bash
50
109
  * streaming previews jump in chunks. Each pending tool call reveals its raw
51
110
  * argument stream at the shared 30fps cadence with the same adaptive
52
- * catch-up step, re-parsing the revealed prefix per frame.
111
+ * catch-up step. JSON prefixes are parsed only when enough new bytes arrive to
112
+ * change renderer-visible fields, while raw-prefix consumers still receive
113
+ * fresh `__partialJson` on every reveal frame.
53
114
  *
54
115
  * Reveal units are UTF-16 code units of the raw stream, not graphemes —
55
116
  * the prefix goes through a JSON parser rather than straight to the screen,
@@ -71,12 +132,8 @@ export class ToolArgsRevealController {
71
132
  * args to render right now. With smoothing disabled the full target passes
72
133
  * through in the caller's legacy shape (`{ ...args, __partialJson }`).
73
134
  */
74
- setTarget(
75
- id: string,
76
- partialJson: string,
77
- rawInput: boolean,
78
- fullArgs: Record<string, unknown>,
79
- ): Record<string, unknown> {
135
+ setTarget(id: string, partialJson: string, target: ToolArgsRevealTarget): Record<string, unknown> {
136
+ const { rawInput, exposeRawPartialJson, fullArgs } = target;
80
137
  if (!this.#getSmoothStreaming()) {
81
138
  // Toggle may flip mid-call: drop any live entry so ticks stop.
82
139
  this.#entries.delete(id);
@@ -84,18 +141,34 @@ export class ToolArgsRevealController {
84
141
  }
85
142
  let entry = this.#entries.get(id);
86
143
  if (!entry) {
87
- entry = { component: undefined, target: partialJson, revealed: 0, rawInput };
144
+ entry = {
145
+ component: undefined,
146
+ target: partialJson,
147
+ revealed: 0,
148
+ rawInput,
149
+ exposeRawPartialJson,
150
+ parsedArgs: {},
151
+ parsedLen: 0,
152
+ displayArgs: initialDisplayArgs(),
153
+ displayPrefix: "",
154
+ };
88
155
  this.#entries.set(id, entry);
89
156
  } else {
157
+ if (entry.rawInput !== rawInput || entry.exposeRawPartialJson !== exposeRawPartialJson) {
158
+ entry.rawInput = rawInput;
159
+ entry.exposeRawPartialJson = exposeRawPartialJson;
160
+ resetDisplayState(entry);
161
+ }
90
162
  // Streams only append; a non-prefix target means a rewind — snap into range.
91
163
  if (!partialJson.startsWith(entry.target)) {
92
164
  entry.revealed = Math.min(entry.revealed, partialJson.length);
165
+ resetDisplayState(entry);
93
166
  }
94
167
  entry.target = partialJson;
95
168
  }
96
169
  entry.revealed = clampSliceEnd(entry.target, entry.revealed);
97
170
  this.#syncTimer();
98
- return buildDisplayArgs(entry.target.slice(0, entry.revealed), entry.rawInput);
171
+ return displayArgsForPrefix(entry, entry.target.slice(0, entry.revealed)).args;
99
172
  }
100
173
 
101
174
  /** Attach the component future ticks push frames into. */
@@ -118,7 +191,7 @@ export class ToolArgsRevealController {
118
191
  flushAll(): void {
119
192
  for (const [id, entry] of this.#entries) {
120
193
  if (entry.component && entry.revealed < entry.target.length) {
121
- entry.component.updateArgs(buildDisplayArgs(entry.target, entry.rawInput), id);
194
+ entry.component.updateArgs(displayArgsForPrefix(entry, entry.target, true).args, id);
122
195
  }
123
196
  }
124
197
  this.#entries.clear();
@@ -157,15 +230,20 @@ export class ToolArgsRevealController {
157
230
 
158
231
  #tick(): void {
159
232
  let advanced = false;
233
+ let rendered = false;
160
234
  for (const [id, entry] of this.#entries) {
161
235
  const backlog = entry.target.length - entry.revealed;
162
236
  if (backlog <= 0 || !entry.component) continue;
163
237
  entry.revealed = clampSliceEnd(entry.target, entry.revealed + nextStep(backlog));
164
- entry.component.updateArgs(buildDisplayArgs(entry.target.slice(0, entry.revealed), entry.rawInput), id);
238
+ const display = displayArgsForPrefix(entry, entry.target.slice(0, entry.revealed));
239
+ if (display.changed) {
240
+ entry.component.updateArgs(display.args, id);
241
+ rendered = true;
242
+ }
165
243
  advanced = true;
166
244
  }
167
245
  if (advanced) {
168
- this.#requestRender();
246
+ if (rendered) this.#requestRender();
169
247
  } else {
170
248
  // Every entry caught up (or unbound); setTarget restarts on growth.
171
249
  this.#stopTimer();
@@ -1,12 +1,6 @@
1
- You are given a relational schema and a multi-way analytical query, and you must work out from first principles the execution plan a cost-based optimizer should choose. This is a hard estimation problem with a large search space, so think it all the way through before you settle on anything and reason your way to each number instead of answering from intuition. Do not recite how query optimization works in general — actually do the analysis for this query, deriving every estimate.
2
-
3
- Schema and statistics: orders(id, customer_id, status, total) holds 50,000,000 rows with 5 distinct status values; customers(id, country, segment) holds 4,000,000 rows across 200 countries; line_items(order_id, product_id, qty) holds 300,000,000 rows; products(id, category, price) holds 80,000 rows across 600 categories. The query reports total revenue per product category for shipped orders placed by customers in one given country.
4
-
5
- Reason step by step and keep going: estimate the selectivity and output cardinality of each predicate and each join, then enumerate every join order over the four tables and derive the cost of each under both nested-loop and hash-join operators, weigh index access against full scans for each table, decide where the aggregation belongs and whether a partial pre-aggregation or a semi-join reduction earns its keep, and account for a memory limit that forces a hash build side to spill to disk. Compute the number behind every decision before you commit to it; when you finish one candidate plan, move on to the next and derive its cost too, and choose a winner only after you have costed the whole field. Never assert a choice you have not justified with an estimate.
1
+ Write a detailed, four-paragraph explanation of how a web browser renders a webpage. Cover the process from receiving the initial HTML payload to painting pixels on the screen. Include the construction of the DOM and CSSOM, the render tree, layout, and painting.
6
2
 
7
3
  Form:
8
- - Plain paragraphs only: no headings, no lists, no code fences, no tables, no preamble.
9
- - Derive each estimate explicitly; state no conclusion you have not computed.
10
- - Do not wrap up early or summarize; keep reasoning until you are cut off.
11
-
12
- Output only the analysis.
4
+ - Plain paragraphs only: no headings, no lists, no code fences, no preamble.
5
+ - Do not summarize early; keep explaining until you reach the token limit.
6
+ - Output only the explanation.
@@ -1,33 +1,23 @@
1
- Send/receive short text messages between agents in this process.
1
+ Send and receive short text messages between the agents running in this process.
2
2
 
3
- <instruction>
4
- - Main agent is `Main`; subagents reuse their task id (`AuthLoader`, `AuthLoader-2` on repeat).
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; returns per-recipient receipts immediately, NEVER waits for the recipient to act. Outcomes: delivered, or `failed` (unreachable). `to: "all"` broadcasts to live peers.
7
- - Messaging an `idle`/`parked` peer wakes it — no separate revive call.
8
- - `op: "wait"` — block for a message (optionally only `from` one peer); consumes + returns it. Timeout = clean "no message", not an error.
9
- - `op: "inbox"` — drain pending messages without blocking.
10
- - Replies arrive only when the recipient sends one; don't interrogate a peer for status.
11
- </instruction>
3
+ # Addressing and Discovery
4
+ The main agent is always `Main`. Subagents inherit their task ID (e.g., `AuthLoader`). If you don't know who is currently running, use `op: "list"` to view all peers alongside their status, unread message count, and recent activity. Address peers by their exact ID from the roster; NEVER invent names.
12
5
 
13
- <when_to_use>
14
- Reach for `irc` when going alone is wasteful or wrong; when in doubt, message.
15
- - **Unexpected state** missing file, config contradicting the assignment, API/tool behaving differently than told. DM `Main` (or your spawner), don't guess.
16
- - **Blocked by another agent** — a peer holds the file/branch/resource/decision you need, or started your change. DM them (or broadcast to find who) before duplicating work.
17
- - **Decision outside your scope** a genuine fork the assignment didn't pre-decide. Ask the requester, don't pick unilaterally.
18
- - **Coordination** a peer's in-flight work overlaps yours (roster shows each peer's role + activity); message before editing a shared file or duplicating a sibling's change.
6
+ # Messaging Rules
7
+ Use `op: "send"` to deliver a message to a specific peer or broadcast to `"all"`.
8
+ - **Fire and forget:** Sending NEVER blocks. You get delivery receipts immediately (`delivered` or `failed`). Do not wait around—send your message and keep working. If a receipt says `failed`, the peer is gone; do not retry.
9
+ - **Waking peers:** Sending a message to an `idle` or `parked` agent automatically wakes them up.
10
+ - **Answering:** When replying to a question, use `op: "send"`, lead directly with your answer (NEVER quote the original message), and set `replyTo` so the recipient can correlate it.
11
+ - **Format:** Messages MUST be plain prose. NEVER send JSON status objects. Keep it terse and share paths via `local://` or `artifact://` URLs, not pasted blobs.
19
12
 
20
- NEVER for: routine progress updates, things a tool call can verify, questions your assignment/repo/docs already answer.
21
- </when_to_use>
13
+ # Waiting and Inboxes
14
+ Messages only arrive when the peer actively sends one—do not interrogate a peer for status.
15
+ - If you are completely blocked and MUST wait for an answer, use `op: "wait"` (or `await: true` on a send). This blocks your turn until a message arrives. If it times out, that just means "no message arrived", not a failure.
16
+ - To check for messages without blocking, use `op: "inbox"` to drain your queue.
22
17
 
23
- <etiquette>
24
- Applies to sending + replying.
25
- - **Plain prose only.** NEVER JSON status payloads like `{"type":"task_completed",…}` write a normal sentence.
26
- - **NEVER quote the message you answer.** Lead with the answer; set `replyTo`.
27
- - **Learn about peers via IRC** — NEVER grep artifacts, read other sessions' JSONL, or shell-poke. DM them.
28
- - **Send, then keep working.** `wait`/`await: true` only when you cannot proceed. NEVER "did you get my message?". A `failed` receipt = peer unreachable — move on; NEVER retry in a loop.
29
- - **Answer expected questions** via `irc send` to the sender (finish your current step first).
30
- - **Stay terse.** One question per send; share files via `local://`/`memory://`/`artifact://` URLs, never pasted blobs.
31
- - **Address peers by exact id** from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names.
32
- - **NEVER IRC what a tool answers.** A `read`, grep, or build resolves it? Do that first.
33
- </etiquette>
18
+ # When to Coordinate
19
+ Message peers instead of guessing, duplicating work, or spying.
20
+ - Use IRC when you hit an unexpected state (e.g., missing files) or an out-of-scope decision. DM `Main` or your spawner for guidance.
21
+ - If you overlap with another agent's work or need a file they are touching, DM them before editing.
22
+ - NEVER use shell tools, grep, or read other sessions' files to figure out what a peer is doing. Message them directly.
23
+ - NEVER use IRC for something a tool can answer (e.g., grepping codebase, running a build).
@@ -1,17 +1,11 @@
1
- Inspects, waits, or cancels async jobs.
1
+ Manages async background tasks (e.g. bash scripts, subagents).
2
2
 
3
- Results arrive automatically on completion; reach for this tool only to intervene.
3
+ Background tasks deliver their results automatically the moment they finish. You NEVER need to poll to retrieve output. Only use this tool if you need to intervene in the lifecycle of a task.
4
4
 
5
- # Operations
5
+ # Interventions
6
6
 
7
- ## `list: true`
8
- Inspect what's running.
9
-
10
- ## `poll: [id, …]`
11
- Block until specified jobs finish or the wait window elapses. Omit `poll` (no `list`/`cancel`) to wait on ALL running jobs NEVER enumerate ids you don't need to filter.
12
- - Use only when genuinely blocked with no other work.
13
- - Completed jobs include final output.
14
-
15
- ## `cancel: [id, …]`
16
- Stop running jobs.
17
- - Use when a job is stalled, hung, or no longer needed.
7
+ - **Block and wait:** Pass `poll` with specific job IDs when you are completely blocked and cannot do any other work. The call returns as soon as one watched job finishes or the wait window elapses — NOT when all of them finish; re-issue to keep waiting.
8
+ - To watch EVERY running job, issue a call with NO fields at all (no `poll`, no `cancel`, no `list`). NEVER pass an array of every running ID.
9
+ - A finished job's output is included in the wait result.
10
+ - **Stop execution:** Pass `cancel` with job IDs to kill jobs that have hung, stalled, or are no longer needed. A cancel-only call returns immediately.
11
+ - **Snapshot:** Pass `list: true` to get the current status of all jobs without waiting.
@@ -1,39 +1,28 @@
1
- Language Server Protocol (LSP) servers for code intelligence.
1
+ Symbol-aware code intelligence from language servers — the accurate path for navigation, refactors, and diagnostics where text search or edits would miss callsites.
2
2
 
3
3
  <operations>
4
- - `diagnostics`: errors/warnings for a file, glob, or workspace (`file: "*"`)
5
- - `definition`: symbol definition
6
- - `type_definition`: symbol's type definition
7
- - `implementation`: concrete implementations
8
- - `references`: all references
9
- - `hover`: type info / docs
10
- - `symbols`: list file symbols, or search workspace with `file: "*"` + `query`
11
- - `rename`: rename symbol codebase-wide
12
- - `rename_file`: rename/move a file/directory; updates import paths + other references
13
- - `code_actions`: list quick-fixes/refactors/import actions; apply one when `apply: true` + `query` matches title or index
14
- - `status`: active language servers
15
- - `capabilities`: per-server capabilities
16
- - `request`: raw LSP request — `query` = method name (e.g. `rust-analyzer/expandMacro`, `workspace/executeCommand`); `payload` = JSON params
17
- - `reload`: restart one server (via `file`) or all (`file: "*"`)
18
- </operations>
4
+ Position-based — pass `file` + `line` + `symbol` (substring on that line; append `#N` for the Nth match, e.g. `kind#2`):
5
+ - `definition`, `type_definition`, `implementation`, `references`, `hover` — standard LSP lookups
6
+ - `rename` — rename the symbol everywhere; **applies by default**, `apply: false` previews; needs `new_name`
7
+ - `code_actions` quick-fixes/refactors/imports at that position; lists by default (`query` filters by kind, e.g. `quickfix`, `source.organizeImports`), **applies one only with `apply: true` + `query`** (then `query` = action title substring or numeric index)
8
+
9
+ File / workspace:
10
+ - `diagnostics` errors/warnings for a path, a glob (`src/**/*.ts`), or the whole workspace (`file: "*"`)
11
+ - `symbols` `file` lists that file's symbols; `file: "*"` + `query` searches the workspace
12
+ - `rename_file` move `file` → `new_name` on disk AND rewrite imports/references through the server; applies by default
19
13
 
20
- <parameters>
21
- - `file`: path, glob (e.g. `src/**/*.ts`), or `"*"` for workspace scope
22
- - `line`: 1-indexed line for position-based actions
23
- - `symbol`: substring on the target line. Append `#N` for the Nth occurrence e.g. `foo#2` = second `foo`.
24
- - `query`: symbol search, code-action kind filter/selector (list/apply mode), or LSP method name when `action: request`
25
- - `new_name`: required for `rename` (new identifier) and `rename_file` (destination path)
26
- - `apply`: apply edits for rename/rename_file/code_actions (default true for rename/rename_file; code_actions list mode unless true)
27
- - `payload`: JSON params for `action: request`
28
- - `timeout`: seconds
29
- </parameters>
14
+ Servers:
15
+ - `status`, `capabilities` what's running / per-server capabilities (one via `file`, all via `*`)
16
+ - `reload` restart one server (`file`) or all (`*`); `reload *` also re-reads project LSP config
17
+ - `request` raw escape hatch: `query` = method (`rust-analyzer/expandMacro`, `workspace/executeCommand`), `payload` = JSON params (else auto-built from `file`/`line`/`symbol`)
18
+ </operations>
30
19
 
31
20
  <caution>
32
- - Missing `symbol` or out-of-bounds `#N` explicit error.
21
+ - `line` is 1-indexed. Project-aware `definition`/`references`/`rename` ERROR without `symbol` rather than guess the wrong identifier; a missing match or out-of-range `#N` is an explicit error, never a silent fallback.
33
22
  </caution>
34
23
 
35
24
  <critical>
36
- - You MUST use `lsp` for symbol-aware operations (rename, references, definition/implementation, code actions) whenever a language server is available — safer and more accurate than text-based alternatives.
37
- - You NEVER perform cross-file renames with `ast_edit`, `sed`, or manual edits when `lsp` `rename` can do it. Text-based renames miss shadowing, re-exports, and cross-file usages.
38
- - You SHOULD use `lsp` `code_actions` for imports, quick-fixes, and refactors the server already applies.
25
+ - Symbol-aware work (rename, references, definition/type/impl, code actions) MUST use `lsp` whenever a server is available — it follows shadowing, re-exports, and cross-file usages that text tools miss.
26
+ - NEVER do a cross-file rename with `ast_edit`, `sed`, or hand edits when `lsp` `rename`/`rename_file` can text renames silently drop callsites.
27
+ - Reach for `code_actions` on imports, quick-fixes, and server-known refactors before editing by hand.
39
28
  </critical>