pi-subagents 0.20.1 → 0.21.1

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 (61) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +35 -6
  3. package/agent-management.ts +16 -10
  4. package/agent-manager-chain-detail.ts +1 -1
  5. package/agent-manager-detail.ts +2 -0
  6. package/agent-manager-edit.ts +31 -6
  7. package/agent-manager-list.ts +10 -3
  8. package/agent-manager-parallel.ts +2 -2
  9. package/agent-manager.ts +14 -9
  10. package/agent-serializer.ts +2 -0
  11. package/agents/context-builder.md +13 -6
  12. package/agents/delegate.md +2 -0
  13. package/agents/oracle.md +4 -1
  14. package/agents/planner.md +5 -1
  15. package/agents/researcher.md +4 -1
  16. package/agents/reviewer.md +78 -20
  17. package/agents/scout.md +5 -2
  18. package/agents/worker.md +7 -4
  19. package/agents.ts +29 -7
  20. package/async-execution.ts +64 -35
  21. package/async-job-tracker.ts +52 -15
  22. package/async-status.ts +107 -14
  23. package/chain-clarify.ts +1 -1
  24. package/chain-execution.ts +10 -2
  25. package/completion-dedupe.ts +1 -1
  26. package/completion-guard.ts +125 -0
  27. package/doctor.ts +1 -1
  28. package/execution.ts +170 -30
  29. package/fork-context.ts +3 -3
  30. package/index.ts +20 -9
  31. package/intercom-bridge.ts +19 -9
  32. package/jsonl-writer.ts +2 -2
  33. package/long-running-guard.ts +175 -0
  34. package/model-fallback.ts +1 -1
  35. package/package.json +1 -1
  36. package/pi-args.ts +4 -2
  37. package/pi-spawn.ts +1 -1
  38. package/prompt-template-bridge.ts +9 -9
  39. package/prompts/parallel-cleanup.md +10 -3
  40. package/render.ts +239 -49
  41. package/result-intercom.ts +5 -5
  42. package/result-watcher.ts +111 -32
  43. package/run-status.ts +1 -1
  44. package/schemas.ts +24 -13
  45. package/session-tokens.ts +2 -6
  46. package/settings.ts +2 -2
  47. package/single-output.ts +1 -1
  48. package/skills/pi-subagents/SKILL.md +97 -22
  49. package/skills.ts +8 -2
  50. package/slash-bridge.ts +3 -3
  51. package/slash-commands.ts +6 -3
  52. package/subagent-control.ts +103 -21
  53. package/subagent-executor.ts +82 -25
  54. package/subagent-prompt-runtime.ts +80 -3
  55. package/subagent-runner.ts +352 -98
  56. package/subagents-status.ts +9 -7
  57. package/text-editor.ts +22 -6
  58. package/top-level-async.ts +1 -1
  59. package/types.ts +68 -8
  60. package/utils.ts +3 -3
  61. package/worktree.ts +5 -5
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.21.1] - 2026-04-30
6
+
7
+ ### Changed
8
+ - Changed the `/agents` new-agent shortcut from `Alt+N` to `Shift+Ctrl+N`, and added `agentManager.newShortcut` config for overriding it.
9
+
10
+ ### Fixed
11
+ - Fall back to polling async result files when native result watching is unavailable due to `EMFILE` or `ENOSPC`.
12
+ - Treat forced final-drain termination after a valid final assistant output as cleanup success instead of failing the subagent run.
13
+ - Hide disabled builtin agents from `subagent({ action: "list" })` output so agent-facing choices match executable runtime discovery.
14
+ - Resolve intercom bridge default paths at runtime so tests and isolated environments that change `HOME` use the correct `pi-intercom` location.
15
+ - Made the tool-description source check tolerant of Windows line endings.
16
+
17
+ ## [0.21.0] - 2026-04-29
18
+
19
+ ### Changed
20
+ - Document the recommended parent-agent workflow as `clarify → planner → worker → fresh reviewers → worker` in the docs and bundled skill.
21
+ - Packaged `planner`, `worker`, and `oracle` now default to forked session context when the launch omits `context`; explicit `context: "fresh"` still overrides the agent default.
22
+ - Expanded builtin subagent guidance so agents with a safe pi-intercom target can hand results back with blocking `intercom ask`, documented the self-orchestrated clarify → plan → implement → review workflow, and added GPT-5.5-oriented subagent prompt guidance to the bundled skill and `context-builder`.
23
+
24
+ ### Fixed
25
+ - Prevent child subagents from receiving parent orchestration tooling/history, and inject boundary instructions that forbid sub-delegation and pseudo tool calls.
26
+ - Added active-long-running and repeated mutating-tool failure notices so supervised/forked workers cannot burn turns silently while still appearing healthy.
27
+ - Fixed task editor wrapping so wide characters cannot push text past the right border.
28
+ - Mark implementation subagents as failed when they complete without any file mutation attempt.
29
+ - Applied the same no-mutation completion guard to async/background runner paths.
30
+ - Split terminal no-mutation guard notices from live idle notices so completed failures do not suggest status or interrupt commands.
31
+ - Clarified worker/intercom bridge instructions so blocked decisions use `intercom ask` and stay alive for the reply instead of completing with a question.
32
+ - Labeled the Agents widget as async/background work so running detached agents are easier to identify.
33
+ - Reworked parallel progress wording so parallel runs show running/done agent counts (and chain parallel groups show `step X/Y · parallel group` with agent fractions) instead of serial `step X/Y` counters.
34
+ - Expanded `/parallel-cleanup` guidance to flag redundant wrapper tests when one focused regression is enough.
35
+ - Fixed flexible schema validation for `reads` and `skill` overrides so `reads: false`, `skill: "review"`, and `skill: false` no longer trigger `element.reads.every is not a function` (issue #124).
36
+ - Hardened slash-result and async-widget animation timers so stale extension contexts after `/new` or reload stop their timers instead of crashing on `ctx.ui` access (issue #122).
37
+
5
38
  ## [0.20.1] - 2026-04-27
6
39
 
7
40
  ### Fixed
package/README.md CHANGED
@@ -174,6 +174,20 @@ or ask:
174
174
  Check whether subagents and intercom are set up correctly.
175
175
  ```
176
176
 
177
+ ## Recommended orchestration pattern (scaffolding)
178
+
179
+ Use orchestration as parent-agent guidance, not as a runtime workflow mode. For implementation work, the recommended loop is:
180
+
181
+ ```text
182
+ clarify → planner → worker → fresh reviewers → worker
183
+ ```
184
+
185
+ Use the optional prompt shortcuts below when you want the pattern to be repeatable.
186
+
187
+ Packaged `planner`, `worker`, and `oracle` default to forked context when a launch omits `context`; pass `context: "fresh"` when you intentionally want a fresh child run.
188
+
189
+ Child-safety boundaries are enforced at runtime. Spawned child sessions do not register the `subagent` tool, do not receive the bundled `pi-subagents` skill, and receive explicit boundary instructions that they are not the parent orchestrator and must not propose or run subagents. Forked child context filtering also removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
190
+
177
191
  ## Optional shortcuts
178
192
 
179
193
  The package includes reusable prompt templates for common workflows. You do not need them, but they are handy when you want the same shape every time:
@@ -183,6 +197,7 @@ The package includes reusable prompt templates for common workflows. You do not
183
197
  | `/parallel-review` | Launch fresh-context reviewers with distinct angles, then synthesize what to fix. |
184
198
  | `/parallel-research` | Combine `researcher` and `scout` for external evidence, local code context, and practical tradeoffs. |
185
199
  | `/gather-context-and-clarify` | Scout/research first, then ask the user the clarification questions that matter. |
200
+ | `/parallel-cleanup` | Run review-only cleanup passes after implementation. |
186
201
 
187
202
  ## Optional pi-intercom companion
188
203
 
@@ -237,7 +252,7 @@ Skip this section until you want exact syntax.
237
252
  | `/subagents-status` | Open the active/recent run overlay |
238
253
  | `/subagents-doctor` | Show read-only setup diagnostics |
239
254
 
240
- Commands validate agent names locally, support tab completion, and still send results back into the conversation.
255
+ Commands validate agent names locally, support tab completion, and send results back into the conversation.
241
256
 
242
257
  ### Per-step tasks
243
258
 
@@ -359,7 +374,7 @@ Useful keys:
359
374
 
360
375
  - type to search the list
361
376
  - `Enter` opens detail screens
362
- - `Alt+N` creates an agent or chain from a template
377
+ - `Shift+Ctrl+N` creates an agent or chain from a template
363
378
  - `Ctrl+R` launches selected agents as a run or chain
364
379
  - `Ctrl+P` opens the parallel builder
365
380
  - `Tab` selects agents in the list or toggles skip-clarify in task input
@@ -413,11 +428,11 @@ Example:
413
428
  }
414
429
  ```
415
430
 
416
- Supported override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `disabled`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides.
431
+ Supported override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `defaultContext`, `disabled`, `skills`, `tools`, and `systemPrompt`. Use `defaultContext: false` in builtin overrides to clear an inherited context default. Project overrides beat user overrides.
417
432
 
418
433
  You can also manage builtin overrides from `/agents`. On a builtin detail screen, press `e`, choose user or project scope if needed, and save the fields you want to override.
419
434
 
420
- Set `disabled: true` to hide a builtin from runtime discovery while keeping it visible in `/agents`. For bulk control, set `subagents.disableBuiltins: true` in settings. Overridden builtins show badges like `[builtin+user]` or `[builtin+project]`; disabled builtins show `off` badges in the manager.
435
+ Set `disabled: true` to hide a builtin from runtime discovery and agent-facing `subagent({ action: "list" })` output while keeping it visible in `/agents`. For bulk control, set `subagents.disableBuiltins: true` in settings. Overridden builtins show badges like `[builtin+user]` or `[builtin+project]`; disabled builtins show `off` badges in the manager.
421
436
 
422
437
  ### Prompt assembly
423
438
 
@@ -430,6 +445,7 @@ Use these fields when an agent should see more:
430
445
  | `systemPromptMode: append` | Append the agent prompt to Pi’s normal base prompt. |
431
446
  | `inheritProjectContext: true` | Keep inherited project instructions from files like `AGENTS.md` and `CLAUDE.md`. |
432
447
  | `inheritSkills: true` | Let the child see Pi’s discovered skills catalog. |
448
+ | `defaultContext: fork` | Use forked session context when a launch omits `context`; explicit `context: "fresh"` still wins. |
433
449
 
434
450
  Builtin agents opt into project instruction inheritance by default so they follow repo-specific rules out of the box. `delegate` also uses append mode because its job is orchestration inside the parent workflow.
435
451
 
@@ -472,6 +488,7 @@ Important fields:
472
488
  | `systemPromptMode` | `replace` by default; `append` keeps Pi’s base prompt. |
473
489
  | `inheritProjectContext` | Keeps or strips inherited project instruction blocks. |
474
490
  | `inheritSkills` | Keeps or strips Pi’s discovered skills catalog. |
491
+ | `defaultContext` | Optional `fresh` or `fork` launch context default for this agent. |
475
492
  | `skills` | Injects specific skills directly, regardless of `inheritSkills`. |
476
493
  | `output` | Default single-agent output file. |
477
494
  | `defaultReads` | Files to read before running in chain/parallel behavior. |
@@ -712,7 +729,7 @@ Agent definitions are not loaded into context by default. Management actions let
712
729
  | `concurrency` | number | config or `4` | Top-level parallel concurrency. |
713
730
  | `worktree` | boolean | false | Create isolated git worktrees for parallel tasks. |
714
731
  | `chain` | array | - | Sequential and parallel chain steps. |
715
- | `context` | `fresh \| fork` | `fresh` | `fork` creates real branched sessions from the parent leaf. |
732
+ | `context` | `fresh \| fork` | agent default or `fresh` | `fork` creates real branched sessions from the parent leaf. Packaged `planner`, `worker`, and `oracle` default to `fork`. |
716
733
  | `chainDir` | string | temp chain dir | Persistent directory for chain artifacts. |
717
734
  | `clarify` | boolean | true for chains | Show TUI preview/edit flow. |
718
735
  | `agentScope` | `user \| project \| both` | `both` | Agent discovery scope. Project wins on collisions. |
@@ -724,7 +741,7 @@ Agent definitions are not loaded into context by default. Management actions let
724
741
  | `share` | boolean | false | Upload session export to GitHub Gist. |
725
742
  | `sessionDir` | string | derived | Override session log directory. |
726
743
 
727
- `context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`.
744
+ `context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`. In multi-agent runs, if any requested agent has `defaultContext: fork` and the launch omits `context`, the whole invocation uses forked context; pass `context: "fresh"` when you intentionally want a fresh run.
728
745
 
729
746
  Sequential and parallel chain tasks accept `agent`, `task`, `cwd`, `output`, `reads`, `progress`, `skill`, and `model`. Parallel tasks also accept `count`. Parallel step groups accept `parallel`, `concurrency`, `failFast`, and `worktree`.
730
747
 
@@ -816,6 +833,18 @@ Session directory precedence is: `params.sessionDir`, then `config.defaultSessio
816
833
 
817
834
  Controls nested delegation when no inherited `PI_SUBAGENT_MAX_DEPTH` is already in effect. Per-agent `maxSubagentDepth` can tighten the limit for that agent’s child runs, but cannot relax an inherited stricter limit.
818
835
 
836
+ ### `agentManager.newShortcut`
837
+
838
+ ```json
839
+ {
840
+ "agentManager": {
841
+ "newShortcut": "shift+ctrl+n"
842
+ }
843
+ }
844
+ ```
845
+
846
+ Sets the `/agents` list shortcut for opening the new agent/chain template picker. The default is `shift+ctrl+n`; use Pi key names such as `ctrl+n` if your terminal cannot distinguish Shift for control chords.
847
+
819
848
  ### `intercomBridge`
820
849
 
821
850
  ```json
@@ -67,7 +67,7 @@ function normalizeListScope(scope: unknown): AgentScope | undefined {
67
67
  return undefined;
68
68
  }
69
69
 
70
- export function sanitizeName(name: string): string {
70
+ function sanitizeName(name: string): string {
71
71
  return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
72
72
  }
73
73
 
@@ -81,7 +81,7 @@ function availableNames(cwd: string, kind: "agent" | "chain"): string[] {
81
81
  return [...new Set(items.map((x) => x.name))].sort((a, b) => a.localeCompare(b));
82
82
  }
83
83
 
84
- export function findAgents(name: string, cwd: string, scope: AgentScope = "both"): AgentConfig[] {
84
+ function findAgents(name: string, cwd: string, scope: AgentScope = "both"): AgentConfig[] {
85
85
  const d = discoverAgentsAll(cwd);
86
86
  const raw = name.trim();
87
87
  const sanitized = sanitizeName(raw);
@@ -90,7 +90,7 @@ export function findAgents(name: string, cwd: string, scope: AgentScope = "both"
90
90
  .sort((a, b) => a.source.localeCompare(b.source));
91
91
  }
92
92
 
93
- export function findChains(name: string, cwd: string, scope: AgentScope = "both"): ChainConfig[] {
93
+ function findChains(name: string, cwd: string, scope: AgentScope = "both"): ChainConfig[] {
94
94
  const raw = name.trim();
95
95
  const sanitized = sanitizeName(raw);
96
96
  return discoverAgentsAll(cwd).chains
@@ -259,6 +259,11 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
259
259
  if (typeof cfg.inheritSkills !== "boolean") return "config.inheritSkills must be a boolean when provided.";
260
260
  target.inheritSkills = cfg.inheritSkills;
261
261
  }
262
+ if (hasKey(cfg, "defaultContext")) {
263
+ if (cfg.defaultContext === false || cfg.defaultContext === "") target.defaultContext = undefined;
264
+ else if (cfg.defaultContext === "fresh" || cfg.defaultContext === "fork") target.defaultContext = cfg.defaultContext;
265
+ else return "config.defaultContext must be 'fresh', 'fork', or false when provided.";
266
+ }
262
267
  if (hasKey(cfg, "output")) {
263
268
  if (cfg.output === false || cfg.output === "") target.output = undefined;
264
269
  else if (typeof cfg.output === "string") target.output = cfg.output;
@@ -328,7 +333,7 @@ function renamePath(
328
333
  return { filePath };
329
334
  }
330
335
 
331
- export function formatAgentDetail(agent: AgentConfig): string {
336
+ function formatAgentDetail(agent: AgentConfig): string {
332
337
  const tools = [...(agent.tools ?? []), ...(agent.mcpDirectTools ?? []).map((t) => `mcp:${t}`)];
333
338
  const lines: string[] = [`Agent: ${agent.name} (${agent.source})`, `Path: ${agent.filePath}`, `Description: ${agent.description}`];
334
339
  if (agent.model) lines.push(`Model: ${agent.model}`);
@@ -338,6 +343,7 @@ export function formatAgentDetail(agent: AgentConfig): string {
338
343
  lines.push(`System prompt mode: ${agent.systemPromptMode}`);
339
344
  lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
340
345
  lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
346
+ if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
341
347
  if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
342
348
  if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
343
349
  if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
@@ -349,7 +355,7 @@ export function formatAgentDetail(agent: AgentConfig): string {
349
355
  return lines.join("\n");
350
356
  }
351
357
 
352
- export function formatChainDetail(chain: ChainConfig): string {
358
+ function formatChainDetail(chain: ChainConfig): string {
353
359
  const lines: string[] = [`Chain: ${chain.name} (${chain.source})`, `Path: ${chain.filePath}`, `Description: ${chain.description}`, "", "Steps:"];
354
360
  for (let i = 0; i < chain.steps.length; i++) {
355
361
  const s = chain.steps[i]!;
@@ -372,12 +378,12 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
372
378
  const d = discoverAgentsAll(ctx.cwd);
373
379
  const scopedAgents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name));
374
380
  const agents = scopedAgents.filter((a) => !a.disabled);
375
- const disabledBuiltins = scopedAgents.filter((a) => a.source === "builtin" && a.disabled);
376
381
  const chains = d.chains.filter((c) => scope === "both" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
377
382
  const lines = [
378
383
  "Executable agents:",
379
- ...(agents.length ? agents.map((a) => `- ${a.name} (${a.source}): ${a.description}`) : ["- (none)"]),
380
- ...(disabledBuiltins.length ? ["", "Disabled builtins:", ...disabledBuiltins.map((a) => `- ${a.name} (${a.source}, disabled): ${a.description}`)] : []),
384
+ ...(agents.length
385
+ ? agents.map((a) => `- ${a.name} (${a.source}${a.defaultContext ? `, context: ${a.defaultContext}` : ""}): ${a.description}`)
386
+ : ["- (none)"]),
381
387
  "",
382
388
  "Chains:",
383
389
  ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"]),
@@ -385,7 +391,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
385
391
  return result(lines.join("\n"));
386
392
  }
387
393
 
388
- export function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
394
+ function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
389
395
  if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for get.", true);
390
396
  const hasBoth = Boolean(params.agent && params.chainName);
391
397
  const blocks: string[] = [];
@@ -560,7 +566,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
560
566
  return result([headline, ...warnings].join("\n"));
561
567
  }
562
568
 
563
- export function handleDelete(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
569
+ function handleDelete(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
564
570
  if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for delete.", true);
565
571
  if (params.agent && params.chainName) return result("Specify either 'agent' or 'chainName', not both.", true);
566
572
  const scopeHint = asDisambiguationScope(params.agentScope);
@@ -17,7 +17,7 @@ const CHAIN_DETAIL_VIEWPORT_HEIGHT = 12;
17
17
 
18
18
  type DetailChainStep = ChainStepConfig | ChainStep;
19
19
 
20
- export function buildDependencyMap(steps: DetailChainStep[]): Map<number, number[]> {
20
+ function buildDependencyMap(steps: DetailChainStep[]): Map<number, number[]> {
21
21
  const outputMap = new Map<string, number>();
22
22
  const deps = new Map<number, number[]>();
23
23
  for (let i = 0; i < steps.length; i++) {
@@ -58,12 +58,14 @@ function buildDetailLines(
58
58
  const output = agent.output ?? "(none)";
59
59
  const reads = agent.defaultReads && agent.defaultReads.length > 0 ? agent.defaultReads.join(", ") : "(none)";
60
60
  const progress = agent.defaultProgress ? "on" : "off";
61
+ const defaultContext = agent.defaultContext ?? "auto";
61
62
  const maxSubagentDepth = agent.maxSubagentDepth !== undefined ? String(agent.maxSubagentDepth) : "(default)";
62
63
 
63
64
  lines.push(renderFieldLine("Model:", agent.model ?? "default", contentWidth, theme));
64
65
  lines.push(renderFieldLine("Prompt mode:", agent.systemPromptMode, contentWidth, theme));
65
66
  lines.push(renderFieldLine("Project ctx:", agent.inheritProjectContext ? "on" : "off", contentWidth, theme));
66
67
  lines.push(renderFieldLine("Skills ctx:", agent.inheritSkills ? "on" : "off", contentWidth, theme));
68
+ lines.push(renderFieldLine("Run context:", defaultContext, contentWidth, theme));
67
69
  if (agent.source === "builtin") {
68
70
  lines.push(renderFieldLine("Disabled:", agent.disabled ? "on" : "off", contentWidth, theme));
69
71
  }
@@ -1,6 +1,6 @@
1
1
  import type { Theme } from "@mariozechner/pi-coding-agent";
2
2
  import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
3
- import { defaultSystemPromptMode, type AgentConfig, type BuiltinAgentOverrideBase } from "./agents.ts";
3
+ import { defaultSystemPromptMode, type AgentConfig, type AgentDefaultContext, type BuiltinAgentOverrideBase } from "./agents.ts";
4
4
  import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
5
5
  import type { TextEditorState } from "./text-editor.ts";
6
6
  import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.ts";
@@ -18,15 +18,15 @@ export interface EditState {
18
18
  title?: string;
19
19
  overrideBase?: BuiltinAgentOverrideBase;
20
20
  }
21
- export interface EditInputResult { action?: "save" | "discard" | "delete"; nextScreen?: EditScreen; }
22
- export interface CreateEditStateOptions {
21
+ interface EditInputResult { action?: "save" | "discard" | "delete"; nextScreen?: EditScreen; }
22
+ interface CreateEditStateOptions {
23
23
  fields?: EditField[];
24
24
  title?: string;
25
25
  overrideBase?: BuiltinAgentOverrideBase;
26
26
  }
27
27
 
28
28
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
29
- const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
29
+ const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "defaultContext", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
30
30
  type ThinkingLevel = typeof THINKING_LEVELS[number];
31
31
  const PROMPT_VIEWPORT_HEIGHT = 16;
32
32
  const MODEL_SELECTOR_HEIGHT = 10;
@@ -38,6 +38,12 @@ function parseTools(value: string): { tools?: string[]; mcp?: string[] } { const
38
38
  function parseCommaList(value: string): string[] | undefined { const items = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0); return items.length > 0 ? items : undefined; }
39
39
  function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean { if (!a && !b) return true; if (!a || !b || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; }
40
40
 
41
+ function nextDefaultContext(value: AgentDefaultContext | undefined): AgentDefaultContext | undefined {
42
+ if (value === undefined) return "fork";
43
+ if (value === "fork") return "fresh";
44
+ return undefined;
45
+ }
46
+
41
47
  function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
42
48
  const base = state.overrideBase;
43
49
  if (!base) return false;
@@ -48,6 +54,7 @@ function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
48
54
  case "systemPromptMode": return state.draft.systemPromptMode === base.systemPromptMode;
49
55
  case "inheritProjectContext": return state.draft.inheritProjectContext === base.inheritProjectContext;
50
56
  case "inheritSkills": return state.draft.inheritSkills === base.inheritSkills;
57
+ case "defaultContext": return state.draft.defaultContext === base.defaultContext;
51
58
  case "disabled": return state.draft.disabled === base.disabled;
52
59
  case "tools": return arraysEqual(toolList(state.draft), toolList(base));
53
60
  case "skills": return arraysEqual(state.draft.skills, base.skills);
@@ -66,6 +73,7 @@ function resetFieldToBase(field: EditField, state: EditState): void {
66
73
  case "systemPromptMode": state.draft.systemPromptMode = base.systemPromptMode; break;
67
74
  case "inheritProjectContext": state.draft.inheritProjectContext = base.inheritProjectContext; break;
68
75
  case "inheritSkills": state.draft.inheritSkills = base.inheritSkills; break;
76
+ case "defaultContext": state.draft.defaultContext = base.defaultContext; break;
69
77
  case "disabled": state.draft.disabled = base.disabled; break;
70
78
  case "tools": state.draft.tools = base.tools ? [...base.tools] : undefined; state.draft.mcpDirectTools = base.mcpDirectTools ? [...base.mcpDirectTools] : undefined; break;
71
79
  case "skills": state.draft.skills = base.skills ? [...base.skills] : undefined; break;
@@ -94,6 +102,7 @@ function renderFieldValue(field: EditField, state: EditState): string {
94
102
  case "systemPromptMode": return draft.systemPromptMode ?? defaultSystemPromptMode(draft.name);
95
103
  case "inheritProjectContext": return draft.inheritProjectContext ? "on" : "off";
96
104
  case "inheritSkills": return draft.inheritSkills ? "on" : "off";
105
+ case "defaultContext": return draft.defaultContext ?? "auto";
97
106
  case "disabled": return draft.disabled ? "on" : "off";
98
107
  case "tools": return formatTools(draft);
99
108
  case "extensions": return draft.extensions !== undefined ? (draft.extensions.length > 0 ? draft.extensions.join(", ") : "") : "(all)";
@@ -129,6 +138,15 @@ function applyFieldValue(field: EditField, state: EditState, value: string): voi
129
138
  case "skills": draft.skills = parseCommaList(value); break;
130
139
  case "output": { const trimmed = value.trim(); draft.output = trimmed.length > 0 ? trimmed : undefined; break; }
131
140
  case "reads": draft.defaultReads = parseCommaList(value); break;
141
+ case "defaultContext": {
142
+ const trimmed = value.trim();
143
+ if (trimmed === "" || trimmed === "auto") {
144
+ draft.defaultContext = undefined;
145
+ break;
146
+ }
147
+ if (trimmed === "fresh" || trimmed === "fork") draft.defaultContext = trimmed;
148
+ break;
149
+ }
132
150
  case "inheritProjectContext":
133
151
  case "inheritSkills":
134
152
  case "disabled":
@@ -260,9 +278,10 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
260
278
  if (data === "m") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
261
279
  if (data === "t") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
262
280
  if (data === "s") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
263
- if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "disabled" || field === "progress" || field === "interactive")) {
281
+ if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "defaultContext" || field === "disabled" || field === "progress" || field === "interactive")) {
264
282
  if (field === "inheritProjectContext") state.draft.inheritProjectContext = !state.draft.inheritProjectContext;
265
283
  if (field === "inheritSkills") state.draft.inheritSkills = !state.draft.inheritSkills;
284
+ if (field === "defaultContext") state.draft.defaultContext = nextDefaultContext(state.draft.defaultContext);
266
285
  if (field === "disabled") state.draft.disabled = !state.draft.disabled;
267
286
  if (field === "progress") state.draft.defaultProgress = !state.draft.defaultProgress;
268
287
  if (field === "interactive") state.draft.interactive = !state.draft.interactive;
@@ -273,6 +292,10 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
273
292
  if (field === "thinking") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
274
293
  if (field === "skills") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
275
294
  if (field === "prompt") { state.promptEditor = createEditorState(state.draft.systemPrompt ?? ""); return { nextScreen: "edit-prompt" }; }
295
+ if (field === "defaultContext") {
296
+ state.draft.defaultContext = nextDefaultContext(state.draft.defaultContext);
297
+ return;
298
+ }
276
299
  if (field === "inheritProjectContext" || field === "inheritSkills" || field === "disabled" || field === "progress" || field === "interactive") return;
277
300
  state.fieldMode = "text"; state.fieldEditor = createEditorState(renderFieldValue(field, state)); return { nextScreen: "edit-field" };
278
301
  }
@@ -346,7 +369,9 @@ export function renderEdit(screen: EditScreen, state: EditState, width: number,
346
369
  ? "Project Ctx"
347
370
  : field === "inheritSkills"
348
371
  ? "Skills Ctx"
349
- : field === "disabled"
372
+ : field === "defaultContext"
373
+ ? "Default Ctx"
374
+ : field === "disabled"
350
375
  ? "Disabled"
351
376
  : `${field[0]!.toUpperCase()}${field.slice(1)}`;
352
377
  const rawLabel = pad(`${fieldLabel}:`, labelWidth);
@@ -22,6 +22,12 @@ export interface ListState {
22
22
  selected: string[];
23
23
  }
24
24
 
25
+ export interface ListShortcuts {
26
+ newShortcut: string;
27
+ }
28
+
29
+ export const DEFAULT_AGENT_MANAGER_NEW_SHORTCUT = "shift+ctrl+n";
30
+
25
31
  export type ListAction =
26
32
  | { type: "open-detail"; id: string }
27
33
  | { type: "clone"; id: string }
@@ -57,7 +63,7 @@ function clampCursor(state: ListState, filtered: ListAgent[]): void {
57
63
  }
58
64
  }
59
65
 
60
- export function handleListInput(state: ListState, agents: ListAgent[], data: string): ListAction | undefined {
66
+ export function handleListInput(state: ListState, agents: ListAgent[], data: string, shortcuts: ListShortcuts = { newShortcut: DEFAULT_AGENT_MANAGER_NEW_SHORTCUT }): ListAction | undefined {
61
67
  const filtered = fuzzyFilter(agents, state.filterQuery);
62
68
 
63
69
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
@@ -98,7 +104,7 @@ export function handleListInput(state: ListState, agents: ListAgent[], data: str
98
104
  return;
99
105
  }
100
106
 
101
- if (matchesKey(data, "alt+n")) {
107
+ if (matchesKey(data, shortcuts.newShortcut)) {
102
108
  return { type: "new" };
103
109
  }
104
110
 
@@ -160,6 +166,7 @@ export function renderList(
160
166
  width: number,
161
167
  theme: Theme,
162
168
  statusMessage?: { text: string; type: "error" | "info" },
169
+ shortcuts: ListShortcuts = { newShortcut: DEFAULT_AGENT_MANAGER_NEW_SHORTCUT },
163
170
  ): string[] {
164
171
  const lines: string[] = [];
165
172
  const filtered = fuzzyFilter(agents, state.filterQuery);
@@ -269,7 +276,7 @@ export function renderList(
269
276
  ? ` [ctrl+r] chain [ctrl+p] parallel [tab] add [shift+tab] remove [esc] clear (${selCount}) `
270
277
  : selCount === 1
271
278
  ? " [ctrl+r] run [ctrl+p] parallel [tab] add more [shift+tab] remove [esc] clear "
272
- : " [enter] view [ctrl+r] run [tab] select [alt+n] new [esc] close ";
279
+ : ` [enter] view [ctrl+r] run [tab] select [${shortcuts.newShortcut}] new [esc] close `;
273
280
  lines.push(renderFooter(footerText, width, theme));
274
281
 
275
282
  return lines;
@@ -4,7 +4,7 @@ import type { TextEditorState } from "./text-editor.ts";
4
4
  import { createEditorState, handleEditorInput, renderEditor, wrapText, getCursorDisplayPos, ensureCursorVisible } from "./text-editor.ts";
5
5
  import { pad, row, renderHeader, renderFooter, fuzzyFilter } from "./render-helpers.ts";
6
6
 
7
- export interface ParallelSlot {
7
+ interface ParallelSlot {
8
8
  agentName: string;
9
9
  customTask: string;
10
10
  }
@@ -20,7 +20,7 @@ export interface ParallelState {
20
20
  editEditor: TextEditorState | null;
21
21
  }
22
22
 
23
- export type ParallelAction =
23
+ type ParallelAction =
24
24
  | { type: "proceed" }
25
25
  | { type: "back" };
26
26
 
package/agent-manager.ts CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  import { serializeAgent } from "./agent-serializer.ts";
19
19
  import { TEMPLATE_ITEMS, type AgentTemplate, type TemplateItem } from "./agent-templates.ts";
20
20
  import { parseChain, serializeChain } from "./chain-serializer.ts";
21
- import { renderList, handleListInput, type ListAgent, type ListState, type ListAction } from "./agent-manager-list.ts";
21
+ import { DEFAULT_AGENT_MANAGER_NEW_SHORTCUT, renderList, handleListInput, type ListAgent, type ListShortcuts, type ListState, type ListAction } from "./agent-manager-list.ts";
22
22
  import { createParallelState, handleParallelInput, renderParallel, formatParallelTitle, type ParallelState, type AgentOption } from "./agent-manager-parallel.ts";
23
23
  import { renderDetail, handleDetailInput, renderTaskInput, type DetailState, type DetailAction, type LaunchToggleState } from "./agent-manager-detail.ts";
24
24
  import { renderChainDetail, handleChainDetailInput, type ChainDetailAction, type ChainDetailState } from "./agent-manager-chain-detail.ts";
@@ -43,8 +43,9 @@ interface ChainEntry { id: string; kind: "chain"; config: ChainConfig; }
43
43
  interface NameInputState { mode: "new-agent" | "clone-agent" | "clone-chain" | "new-chain"; editor: TextEditorState; scope: "user" | "project"; allowProject: boolean; sourceId?: string; template?: AgentTemplate; error?: string; }
44
44
  interface StatusMessage { text: string; type: "error" | "info"; }
45
45
  interface OverrideScopeState { selectedScope: "user" | "project"; allowProject: boolean; }
46
+ export interface AgentManagerOptions { newShortcut?: string; }
46
47
 
47
- const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "disabled", "tools", "skills", "prompt"];
48
+ const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "defaultContext", "disabled", "tools", "skills", "prompt"];
48
49
 
49
50
  function cloneConfig(config: AgentConfig): AgentConfig {
50
51
  return {
@@ -61,6 +62,7 @@ function cloneConfig(config: AgentConfig): AgentConfig {
61
62
  base: {
62
63
  ...config.override.base,
63
64
  disabled: config.override.base.disabled,
65
+ defaultContext: config.override.base.defaultContext,
64
66
  fallbackModels: config.override.base.fallbackModels ? [...config.override.base.fallbackModels] : undefined,
65
67
  skills: config.override.base.skills ? [...config.override.base.skills] : undefined,
66
68
  tools: config.override.base.tools ? [...config.override.base.tools] : undefined,
@@ -130,14 +132,16 @@ export class AgentManagerComponent implements Component {
130
132
  private models: ModelInfo[];
131
133
  private skills: SkillInfo[];
132
134
  private done: (result: ManagerResult) => void;
135
+ private shortcuts: ListShortcuts;
133
136
 
134
- constructor(tui: TUI, theme: Theme, agentData: AgentData, models: ModelInfo[], skills: SkillInfo[], done: (result: ManagerResult) => void) {
137
+ constructor(tui: TUI, theme: Theme, agentData: AgentData, models: ModelInfo[], skills: SkillInfo[], done: (result: ManagerResult) => void, options: AgentManagerOptions = {}) {
135
138
  this.tui = tui;
136
139
  this.theme = theme;
137
140
  this.agentData = agentData;
138
141
  this.models = models;
139
142
  this.skills = skills;
140
143
  this.done = done;
144
+ this.shortcuts = { newShortcut: options.newShortcut?.trim() || DEFAULT_AGENT_MANAGER_NEW_SHORTCUT };
141
145
  this.loadEntries();
142
146
  }
143
147
 
@@ -162,6 +166,7 @@ export class AgentManagerComponent implements Component {
162
166
  systemPromptMode: entry.config.systemPromptMode,
163
167
  inheritProjectContext: entry.config.inheritProjectContext,
164
168
  inheritSkills: entry.config.inheritSkills,
169
+ defaultContext: entry.config.defaultContext,
165
170
  disabled: entry.config.disabled,
166
171
  systemPrompt: entry.config.systemPrompt,
167
172
  skills: entry.config.skills ? [...entry.config.skills] : undefined,
@@ -516,7 +521,7 @@ export class AgentManagerComponent implements Component {
516
521
  if (this.screen === "list" && this.statusMessage) this.clearStatus();
517
522
  if (this.screen.startsWith("edit") && this.editState?.error) this.editState.error = undefined;
518
523
  switch (this.screen) {
519
- case "list": { const action = handleListInput(this.listState, this.listAgents(), data); if (action) this.handleListAction(action); this.tui.requestRender(); return; }
524
+ case "list": { const action = handleListInput(this.listState, this.listAgents(), data, this.shortcuts); if (action) this.handleListAction(action); this.tui.requestRender(); return; }
520
525
  case "template-select": this.handleTemplateSelectInput(data); return;
521
526
  case "override-scope": this.handleOverrideScopeInput(data); return;
522
527
  case "detail": {
@@ -667,16 +672,16 @@ export class AgentManagerComponent implements Component {
667
672
  }
668
673
 
669
674
  render(width: number): string[] {
670
- this.overlayWidth = Math.min(width, 84); const w = this.overlayWidth;
675
+ this.overlayWidth = width; const w = this.overlayWidth;
671
676
  switch (this.screen) {
672
- case "list": return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage);
677
+ case "list": return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts);
673
678
  case "template-select": return this.renderTemplateSelect(w);
674
679
  case "override-scope": return this.renderOverrideScope(w);
675
- case "detail": { const entry = this.getAgentEntry(this.currentAgentId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage); return renderDetail(this.detailState, entry.config, this.agentData.cwd, w, this.theme); }
676
- case "chain-detail": { const entry = this.getChainEntry(this.currentChainId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage); return renderChainDetail(this.chainDetailState, entry.config, w, this.theme); }
680
+ case "detail": { const entry = this.getAgentEntry(this.currentAgentId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts); return renderDetail(this.detailState, entry.config, this.agentData.cwd, w, this.theme); }
681
+ case "chain-detail": { const entry = this.getChainEntry(this.currentChainId); if (!entry) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts); return renderChainDetail(this.chainDetailState, entry.config, w, this.theme); }
677
682
  case "edit": case "edit-field": case "edit-prompt": return this.editState ? renderEdit(this.screen as EditScreen, this.editState, w, this.theme) : [];
678
683
  case "parallel-builder": {
679
- if (!this.parallelState) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage);
684
+ if (!this.parallelState) return renderList(this.listState, this.listAgents(), w, this.theme, this.statusMessage, this.shortcuts);
680
685
  const agentOptions: AgentOption[] = this.agents.map((e) => ({ name: e.config.name, description: e.config.description, model: e.config.model }));
681
686
  return renderParallel(this.parallelState, agentOptions, w, this.theme);
682
687
  }
@@ -11,6 +11,7 @@ export const KNOWN_FIELDS = new Set([
11
11
  "systemPromptMode",
12
12
  "inheritProjectContext",
13
13
  "inheritSkills",
14
+ "defaultContext",
14
15
  "skill",
15
16
  "skills",
16
17
  "extensions",
@@ -46,6 +47,7 @@ export function serializeAgent(config: AgentConfig): string {
46
47
  lines.push(`systemPromptMode: ${config.systemPromptMode}`);
47
48
  lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
48
49
  lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
50
+ if (config.defaultContext) lines.push(`defaultContext: ${config.defaultContext}`);
49
51
 
50
52
  const skillsValue = joinComma(config.skills);
51
53
  if (skillsValue) lines.push(`skills: ${skillsValue}`);
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: context-builder
3
3
  description: Analyzes requirements and codebase, generates context and meta-prompt
4
- tools: read, grep, find, ls, bash, write, web_search
4
+ tools: read, grep, find, ls, bash, write, web_search, intercom
5
5
  model: openai-codex/gpt-5.5
6
6
  thinking: medium
7
7
  systemPromptMode: replace
@@ -12,7 +12,7 @@ output: context.md
12
12
 
13
13
  You are a requirements-to-context subagent.
14
14
 
15
- Analyze the user request against the codebase, gather the minimum high-value context, and produce structured handoff material for planning.
15
+ Analyze the user request against the codebase, gather the minimum high-value context, and produce structured handoff material for planning and GPT-5.5 subagent prompts.
16
16
 
17
17
  Working rules:
18
18
  - Read the request carefully before touching the codebase.
@@ -29,9 +29,16 @@ When running in a chain, expect to generate two files in the chain directory:
29
29
  - dependencies, constraints, and implementation risks
30
30
 
31
31
  `meta-prompt.md`
32
- - distilled requirements summary
33
- - technical constraints
34
- - suggested implementation approach
32
+ - goal: the concrete outcome the next agent should produce
33
+ - context/evidence: relevant files, diffs, decisions, constraints, and source-backed facts
34
+ - success criteria: what must be true before the next agent can finish
35
+ - hard constraints: true invariants only, such as no edits for review-only work or escalation for unapproved decisions
36
+ - suggested approach: concise direction without over-specifying every step
37
+ - validation: targeted checks to run, or the next-best check if validation is unavailable
38
+ - stop/escalation rules: when to ask via `intercom`, when enough evidence is enough, and when to stop
35
39
  - resolved questions and assumptions
36
40
 
37
- The goal is to hand the planner exactly enough code and requirement context to produce a strong implementation plan without having to rediscover the same ground.
41
+ The goal is to hand the planner or another GPT-5.5 subagent exactly enough code and requirement context to act without rediscovering the same ground. Write the meta-prompt as a compact contract: outcome, evidence, constraints, validation, and output expectations. Avoid long procedural scripts unless each step is a real requirement.
42
+
43
+ ## Pi-intercom handoff
44
+ If `intercom` is available and runtime bridge instructions or the task name a safe orchestrator target, send your completed context summary back with a blocking `intercom({ action: "ask", ... })` before finishing. Keep the message concise, include the output path, and ask whether the orchestrator wants clarification or deeper context. If no safe target is available, do not guess; return normally.
@@ -7,3 +7,5 @@ inheritSkills: false
7
7
  ---
8
8
 
9
9
  You are a delegated agent. Execute the assigned task using the provided tools. Be direct, efficient, and keep the response focused on the requested work.
10
+
11
+ If `intercom` is available and runtime bridge instructions or the task name a safe orchestrator target, send your completed result back with a blocking `intercom({ action: "ask", ... })` before finishing. Stay alive for the reply so you can clarify or do a small follow-up if asked. If no safe target is available, do not guess; return normally.
package/agents/oracle.md CHANGED
@@ -7,6 +7,7 @@ thinking: high
7
7
  systemPromptMode: replace
8
8
  inheritProjectContext: true
9
9
  inheritSkills: false
10
+ defaultContext: fork
10
11
  ---
11
12
 
12
13
  You are the oracle: a high-context decision-consistency subagent.
@@ -17,7 +18,9 @@ Before you do anything else, reconstruct the key inherited decisions, constraint
17
18
 
18
19
  If you need clarification from the main agent, use `intercom`. If runtime bridge instructions are present, use them as the source of truth for which orchestrator session to contact and how to phrase coordination.
19
20
 
20
- Use `intercom({ action: "ask", ... })` when you need a real decision or clarification. Use `intercom({ action: "send", ... })` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep intercom traffic tight and purposeful. Do not narrate your whole review through intercom, and do not send routine completion handoffs.
21
+ Use `intercom({ action: "ask", ... })` when you need a real decision or clarification. Use `intercom({ action: "send", ... })` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep intercom traffic tight and purposeful. Do not narrate your whole review through intercom.
22
+
23
+ If runtime bridge instructions or the task name a safe orchestrator target, send your final oracle recommendation back with a blocking `intercom({ action: "ask", ... })` before finishing. Stay alive for the reply so you can clarify, revise the recommendation, or produce a worker prompt if asked. If no safe target is available, do not guess; return normally.
21
24
 
22
25
  Core responsibilities:
23
26
  - reconstruct inherited decisions, constraints, and open questions from the context