@namzu/cli 18.1.0 → 19.0.0

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 (219) hide show
  1. package/CHANGELOG.md +5898 -0
  2. package/README.md +23 -24
  3. package/dist/bin.js +12 -1
  4. package/dist/bin.js.map +1 -1
  5. package/dist/checkpoints/store.d.ts +75 -0
  6. package/dist/checkpoints/store.d.ts.map +1 -0
  7. package/dist/checkpoints/store.js +177 -0
  8. package/dist/checkpoints/store.js.map +1 -0
  9. package/dist/checkpoints/wrap.d.ts +15 -0
  10. package/dist/checkpoints/wrap.d.ts.map +1 -0
  11. package/dist/checkpoints/wrap.js +37 -0
  12. package/dist/checkpoints/wrap.js.map +1 -0
  13. package/dist/cli.d.ts +6 -0
  14. package/dist/cli.d.ts.map +1 -1
  15. package/dist/cli.js +12 -1
  16. package/dist/cli.js.map +1 -1
  17. package/dist/commands/acp.d.ts.map +1 -1
  18. package/dist/commands/acp.js +2 -0
  19. package/dist/commands/acp.js.map +1 -1
  20. package/dist/commands/drain.d.ts.map +1 -1
  21. package/dist/commands/drain.js +15 -2
  22. package/dist/commands/drain.js.map +1 -1
  23. package/dist/commands/provider-wait.d.ts +44 -0
  24. package/dist/commands/provider-wait.d.ts.map +1 -0
  25. package/dist/commands/provider-wait.js +65 -0
  26. package/dist/commands/provider-wait.js.map +1 -0
  27. package/dist/commands/run-flags.d.ts +13 -1
  28. package/dist/commands/run-flags.d.ts.map +1 -1
  29. package/dist/commands/run-flags.js +41 -0
  30. package/dist/commands/run-flags.js.map +1 -1
  31. package/dist/commands/run-stream.d.ts.map +1 -1
  32. package/dist/commands/run-stream.js +33 -13
  33. package/dist/commands/run-stream.js.map +1 -1
  34. package/dist/commands/run.d.ts.map +1 -1
  35. package/dist/commands/run.js +132 -78
  36. package/dist/commands/run.js.map +1 -1
  37. package/dist/commands/stdin.d.ts +17 -0
  38. package/dist/commands/stdin.d.ts.map +1 -0
  39. package/dist/commands/stdin.js +69 -0
  40. package/dist/commands/stdin.js.map +1 -0
  41. package/dist/config/load.d.ts.map +1 -1
  42. package/dist/config/load.js +107 -2
  43. package/dist/config/load.js.map +1 -1
  44. package/dist/config/schema.d.ts +80 -5
  45. package/dist/config/schema.d.ts.map +1 -1
  46. package/dist/config/schema.js.map +1 -1
  47. package/dist/context/doctrine.d.ts +16 -38
  48. package/dist/context/doctrine.d.ts.map +1 -1
  49. package/dist/context/doctrine.js +16 -75
  50. package/dist/context/doctrine.js.map +1 -1
  51. package/dist/context/environment.d.ts +2 -0
  52. package/dist/context/environment.d.ts.map +1 -1
  53. package/dist/context/environment.js +3 -0
  54. package/dist/context/environment.js.map +1 -1
  55. package/dist/integrations/mcp/servers.d.ts +19 -0
  56. package/dist/integrations/mcp/servers.d.ts.map +1 -1
  57. package/dist/integrations/mcp/servers.js +23 -2
  58. package/dist/integrations/mcp/servers.js.map +1 -1
  59. package/dist/integrations/plugins/runtime.d.ts +8 -2
  60. package/dist/integrations/plugins/runtime.d.ts.map +1 -1
  61. package/dist/integrations/plugins/runtime.js +24 -12
  62. package/dist/integrations/plugins/runtime.js.map +1 -1
  63. package/dist/integrations/providers/chain-capabilities.js +1 -1
  64. package/dist/integrations/providers/chain-capabilities.js.map +1 -1
  65. package/dist/integrations/providers/credential-store.d.ts +1 -1
  66. package/dist/integrations/providers/credential-store.js +1 -1
  67. package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
  68. package/dist/integrations/providers/harness-credentials.js +7 -0
  69. package/dist/integrations/providers/harness-credentials.js.map +1 -1
  70. package/dist/integrations/providers/registry.d.ts +2 -2
  71. package/dist/integrations/providers/registry.js +2 -2
  72. package/dist/integrations/sessions/store.d.ts +14 -22
  73. package/dist/integrations/sessions/store.d.ts.map +1 -1
  74. package/dist/integrations/sessions/store.js +78 -175
  75. package/dist/integrations/sessions/store.js.map +1 -1
  76. package/dist/integrations/sessions/transcript-export.d.ts.map +1 -1
  77. package/dist/integrations/sessions/transcript-export.js +6 -2
  78. package/dist/integrations/sessions/transcript-export.js.map +1 -1
  79. package/dist/integrations/state/identity.d.ts +21 -0
  80. package/dist/integrations/state/identity.d.ts.map +1 -0
  81. package/dist/integrations/state/identity.js +49 -0
  82. package/dist/integrations/state/identity.js.map +1 -0
  83. package/dist/integrations/state/immutable-json.d.ts +10 -0
  84. package/dist/integrations/state/immutable-json.d.ts.map +1 -0
  85. package/dist/integrations/state/immutable-json.js +38 -0
  86. package/dist/integrations/state/immutable-json.js.map +1 -0
  87. package/dist/integrations/state/project.d.ts +10 -0
  88. package/dist/integrations/state/project.d.ts.map +1 -0
  89. package/dist/integrations/state/project.js +15 -0
  90. package/dist/integrations/state/project.js.map +1 -0
  91. package/dist/integrations/state/report.d.ts.map +1 -1
  92. package/dist/integrations/state/report.js +42 -12
  93. package/dist/integrations/state/report.js.map +1 -1
  94. package/dist/integrations/subagents/definitions.d.ts +19 -0
  95. package/dist/integrations/subagents/definitions.d.ts.map +1 -0
  96. package/dist/integrations/subagents/definitions.js +26 -0
  97. package/dist/integrations/subagents/definitions.js.map +1 -0
  98. package/dist/integrations/subagents/parent.d.ts +13 -0
  99. package/dist/integrations/subagents/parent.d.ts.map +1 -0
  100. package/dist/integrations/subagents/parent.js +57 -0
  101. package/dist/integrations/subagents/parent.js.map +1 -0
  102. package/dist/integrations/subagents/runtime.d.ts +24 -13
  103. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  104. package/dist/integrations/subagents/runtime.js +282 -122
  105. package/dist/integrations/subagents/runtime.js.map +1 -1
  106. package/dist/memory/store.d.ts +35 -15
  107. package/dist/memory/store.d.ts.map +1 -1
  108. package/dist/memory/store.js +79 -25
  109. package/dist/memory/store.js.map +1 -1
  110. package/dist/permissions/mode.d.ts +12 -45
  111. package/dist/permissions/mode.d.ts.map +1 -1
  112. package/dist/permissions/mode.js +18 -13
  113. package/dist/permissions/mode.js.map +1 -1
  114. package/dist/release-notes.d.ts +20 -0
  115. package/dist/release-notes.d.ts.map +1 -0
  116. package/dist/release-notes.js +64 -0
  117. package/dist/release-notes.js.map +1 -0
  118. package/dist/tui/AgentExplorer.d.ts.map +1 -1
  119. package/dist/tui/AgentExplorer.js +14 -18
  120. package/dist/tui/AgentExplorer.js.map +1 -1
  121. package/dist/tui/App.d.ts.map +1 -1
  122. package/dist/tui/App.js +606 -207
  123. package/dist/tui/App.js.map +1 -1
  124. package/dist/tui/BrandHeader.d.ts +12 -0
  125. package/dist/tui/BrandHeader.d.ts.map +1 -0
  126. package/dist/tui/BrandHeader.js +17 -0
  127. package/dist/tui/BrandHeader.js.map +1 -0
  128. package/dist/tui/ChoicePicker.d.ts +11 -1
  129. package/dist/tui/ChoicePicker.d.ts.map +1 -1
  130. package/dist/tui/ChoicePicker.js +46 -13
  131. package/dist/tui/ChoicePicker.js.map +1 -1
  132. package/dist/tui/Composer.d.ts +4 -1
  133. package/dist/tui/Composer.d.ts.map +1 -1
  134. package/dist/tui/Composer.js +4 -4
  135. package/dist/tui/Composer.js.map +1 -1
  136. package/dist/tui/ComposerFrame.d.ts +10 -0
  137. package/dist/tui/ComposerFrame.d.ts.map +1 -0
  138. package/dist/tui/ComposerFrame.js +93 -0
  139. package/dist/tui/ComposerFrame.js.map +1 -0
  140. package/dist/tui/EditPromptPicker.d.ts.map +1 -1
  141. package/dist/tui/EditPromptPicker.js +1 -1
  142. package/dist/tui/EditPromptPicker.js.map +1 -1
  143. package/dist/tui/LiveActivity.d.ts +12 -5
  144. package/dist/tui/LiveActivity.d.ts.map +1 -1
  145. package/dist/tui/LiveActivity.js +17 -32
  146. package/dist/tui/LiveActivity.js.map +1 -1
  147. package/dist/tui/PermissionOverlay.d.ts +26 -2
  148. package/dist/tui/PermissionOverlay.d.ts.map +1 -1
  149. package/dist/tui/PermissionOverlay.js +108 -16
  150. package/dist/tui/PermissionOverlay.js.map +1 -1
  151. package/dist/tui/Picker.d.ts +2 -2
  152. package/dist/tui/Picker.d.ts.map +1 -1
  153. package/dist/tui/Picker.js +79 -52
  154. package/dist/tui/Picker.js.map +1 -1
  155. package/dist/tui/StatusBar.d.ts.map +1 -1
  156. package/dist/tui/StatusBar.js +5 -2
  157. package/dist/tui/StatusBar.js.map +1 -1
  158. package/dist/tui/TaskList.d.ts +3 -1
  159. package/dist/tui/TaskList.d.ts.map +1 -1
  160. package/dist/tui/TaskList.js +13 -4
  161. package/dist/tui/TaskList.js.map +1 -1
  162. package/dist/tui/Transcript.d.ts +3 -3
  163. package/dist/tui/Transcript.d.ts.map +1 -1
  164. package/dist/tui/Transcript.js +16 -34
  165. package/dist/tui/Transcript.js.map +1 -1
  166. package/dist/tui/agent.d.ts +113 -50
  167. package/dist/tui/agent.d.ts.map +1 -1
  168. package/dist/tui/agent.js +512 -293
  169. package/dist/tui/agent.js.map +1 -1
  170. package/dist/tui/choice-selection.d.ts +17 -0
  171. package/dist/tui/choice-selection.d.ts.map +1 -0
  172. package/dist/tui/choice-selection.js +38 -0
  173. package/dist/tui/choice-selection.js.map +1 -0
  174. package/dist/tui/exit-summary.d.ts +7 -1
  175. package/dist/tui/exit-summary.d.ts.map +1 -1
  176. package/dist/tui/exit-summary.js +12 -3
  177. package/dist/tui/exit-summary.js.map +1 -1
  178. package/dist/tui/index.d.ts +3 -1
  179. package/dist/tui/index.d.ts.map +1 -1
  180. package/dist/tui/index.js +5 -3
  181. package/dist/tui/index.js.map +1 -1
  182. package/dist/tui/logo.d.ts +7 -22
  183. package/dist/tui/logo.d.ts.map +1 -1
  184. package/dist/tui/logo.js +8 -34
  185. package/dist/tui/logo.js.map +1 -1
  186. package/dist/tui/model-choices.d.ts.map +1 -1
  187. package/dist/tui/model-choices.js +21 -6
  188. package/dist/tui/model-choices.js.map +1 -1
  189. package/dist/tui/permission-review.d.ts +15 -4
  190. package/dist/tui/permission-review.d.ts.map +1 -1
  191. package/dist/tui/permission-review.js +48 -9
  192. package/dist/tui/permission-review.js.map +1 -1
  193. package/dist/tui/provider-selection.d.ts +4 -0
  194. package/dist/tui/provider-selection.d.ts.map +1 -0
  195. package/dist/tui/provider-selection.js +16 -0
  196. package/dist/tui/provider-selection.js.map +1 -0
  197. package/dist/tui/run-interruption.d.ts +3 -2
  198. package/dist/tui/run-interruption.d.ts.map +1 -1
  199. package/dist/tui/run-interruption.js +2 -1
  200. package/dist/tui/run-interruption.js.map +1 -1
  201. package/dist/tui/shell-escape.d.ts +42 -0
  202. package/dist/tui/shell-escape.d.ts.map +1 -0
  203. package/dist/tui/shell-escape.js +110 -0
  204. package/dist/tui/shell-escape.js.map +1 -0
  205. package/dist/tui/slashCommands.d.ts +77 -31
  206. package/dist/tui/slashCommands.d.ts.map +1 -1
  207. package/dist/tui/slashCommands.js +343 -303
  208. package/dist/tui/slashCommands.js.map +1 -1
  209. package/dist/tui/terminal-choice-text.d.ts +8 -0
  210. package/dist/tui/terminal-choice-text.d.ts.map +1 -0
  211. package/dist/tui/terminal-choice-text.js +37 -0
  212. package/dist/tui/terminal-choice-text.js.map +1 -0
  213. package/dist/tui/theme.d.ts +3 -4
  214. package/dist/tui/theme.d.ts.map +1 -1
  215. package/dist/tui/theme.js +16 -17
  216. package/dist/tui/theme.js.map +1 -1
  217. package/dist/tui/types.d.ts +6 -1
  218. package/dist/tui/types.d.ts.map +1 -1
  219. package/package.json +6 -4
@@ -11,25 +11,27 @@
11
11
  * store backs the AgentManager, so sub-agent bookkeeping never touches the
12
12
  * CLI's on-disk `/resume` conversation store.
13
13
  */
14
- import { type AuthorizationGateConfig, type LLMProvider, type PathBuilder, type ProjectInstructionContext, type ResumeHandler, type RunEvent, type SandboxProvider, type TaskScheduler, type ToolContext, type ToolDefinition, type ToolRegistryContract } from '@namzu/sdk';
14
+ import { type AgentFileDefinition, type AuthorizationGateConfig, type LLMProvider, type PathBuilder, type Project, type ProjectInstructionContext, type ResumeHandler, type RunEvent, type RunId, type SandboxProvider, type SessionId, type TaskScheduler, type ToolContext, type ToolDefinition, type ToolRegistryContract, type Topic } from '@namzu/sdk';
15
15
  import { type SubagentActivitySource } from './activity.js';
16
16
  export declare const GENERAL_PURPOSE_SUBAGENT = "general-purpose";
17
17
  /**
18
- * A sub-agent that can only look.
19
- *
20
- * "Find where X is defined", "which files reference Y", "how does Z work" are
21
- * the delegations a parent makes most, and every one of them ran with
22
- * `write`, `edit` and `bash` in the child's roster — so a lookup needed the
23
- * same approvals as a change, and an operator in `accept-edits` mode was
24
- * still asked about a child's shell command that only wanted to `cat`. This
25
- * definition's registry is the parent's working set filtered to tools that
26
- * declare themselves read-only, so a child that cannot change anything is
27
- * never asked whether it may.
18
+ * The kernel's read-only delegate, with this application's doctrine under
19
+ * its prompt. Roster and prompt come from `@namzu/sdk`; what is added here is
20
+ * the same working rules every namzu child gets.
28
21
  */
29
22
  export declare const EXPLORE_SUBAGENT = "explore";
23
+ export interface SubagentParent {
24
+ readonly project: Project;
25
+ readonly topic: Topic;
26
+ readonly sessionId: SessionId;
27
+ }
30
28
  export interface SubagentRuntimeOptions {
29
+ /** Resolve the actual invoking run; reject calls whose parent no longer exists. */
30
+ readonly resolveParent: (runId: RunId) => Promise<SubagentParent>;
31
31
  readonly cwd: string;
32
32
  readonly model: string;
33
+ /** Aggregate parent-and-descendant limit; defaults to the CLI's one million tokens. */
34
+ readonly tokenBudget?: number;
33
35
  /** Durable layout for child runs; omitted preserves the SDK default. */
34
36
  readonly pathBuilder?: PathBuilder;
35
37
  /** Root every child allocation at the session workspace or a fresh temp tree. */
@@ -62,9 +64,18 @@ export interface SubagentRuntimeOptions {
62
64
  readonly readEnvironment?: () => Promise<string>;
63
65
  /** Receives the child's RunEvents (lineage-stamped) — for the tree view. */
64
66
  readonly onEvent?: (event: RunEvent) => void;
67
+ /**
68
+ * Agents a project or user defined in `.namzu/agents/<name>.md`. Each
69
+ * becomes a type the `Agent` tool offers beside the built-in two, with
70
+ * its own prompt, roster and model. See `definitions.ts`.
71
+ */
72
+ readonly definitions?: readonly AgentFileDefinition[];
65
73
  }
66
74
  export interface SubagentRuntime {
67
- readonly gateway: TaskScheduler;
75
+ /** One immutable scheduler context per actual parent run. */
76
+ gatewayForRun(runId: RunId): Promise<TaskScheduler>;
77
+ /** Release a settled parent's bookkeeping and cancel children it still owns. */
78
+ releaseRun(runId: RunId): Promise<void>;
68
79
  readonly agentTool: ToolDefinition;
69
80
  readonly allowedAgentIds: readonly string[];
70
81
  /** Live, bounded observation of children created by this CLI session. */
@@ -74,7 +85,7 @@ export interface SubagentRuntime {
74
85
  }
75
86
  /**
76
87
  * Stand up the AgentManager + gateway + `Agent` tool. Returns the tool to
77
- * register on the parent and the gateway to pass to `query({ taskGateway })`.
88
+ * register on the parent and the gateway to pass to `query({ taskScheduler })`.
78
89
  */
79
90
  export declare function createSubagentRuntime(opts: SubagentRuntimeOptions): Promise<SubagentRuntime>;
80
91
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/integrations/subagents/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAMN,KAAK,uBAAuB,EAQ5B,KAAK,WAAW,EAEhB,KAAK,WAAW,EAChB,KAAK,yBAAyB,EAG9B,KAAK,aAAa,EAElB,KAAK,QAAQ,EACb,KAAK,eAAe,EAGpB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EASzB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAIN,KAAK,sBAAsB,EAC3B,MAAM,eAAe,CAAA;AAGtB,eAAO,MAAM,wBAAwB,oBAAoB,CAAA;AACzD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAA;AA6CzC,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAA;IAClC,iFAAiF;IACjF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,mBAAmB,GAAG,WAAW,CAAA;IAC7D,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAA;IACzC,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,oBAAoB,CAAA;IAC/C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,uBAAuB,CAAA;IACpD;;;OAGG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,aAAa,GAAG,SAAS,CAAA;IAC1F,kEAAkE;IAClE,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAA;IAC1C,sEAAsE;IACtE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAA;IAC1C,2EAA2E;IAC3E,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,yBAAyB,CAAA;IACpE;;;;;;;;OAQG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;CAC5C;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAA;IAClC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAA;IACzC,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAQD;;;GAGG;AACH,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,sBAAsB,GAC1B,OAAO,CAAC,eAAe,CAAC,CA2R1B"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/integrations/subagents/runtime.ts"],"names":[],"mappings":"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"}
@@ -11,48 +11,28 @@
11
11
  * store backs the AgentManager, so sub-agent bookkeeping never touches the
12
12
  * CLI's on-disk `/resume` conversation store.
13
13
  */
14
- import { AgentManager, AgentRegistry, DefaultCapacityValidator, InMemorySessionStore, InMemoryTopicStore, LocalTaskScheduler, ReactiveAgent, RunCancelled, SessionSummaryMaterializer, ToolRegistry, TopicManager, WorkspaceBackendRegistry, asRunId, asSummaryId, asTenantId, asUserId, defineTool, mcpJsonSchemaToZod, } from '@namzu/sdk';
14
+ import { AgentManager, AgentRegistry, DefaultCapacityValidator, EXPLORE_AGENT_DESCRIPTION, EXPLORE_AGENT_ID, EXPLORE_AGENT_PROMPT, InMemorySessionStore, InMemoryTopicStore, LocalTaskScheduler, ReactiveAgent, RunCancelled, SessionSummaryMaterializer, TopicArchivedError, TopicManager, WorkspaceBackendRegistry, defineTool, filterReadOnlyTools, filterToolsNamed, generateSummaryId, mcpJsonSchemaToZod, openTokenBudget, requireOpenProject, } from '@namzu/sdk';
15
15
  import { NAMZU_WORKING_DOCTRINE } from '../../context/doctrine.js';
16
16
  import { MAX_AGENT_ACTIVITY_LABEL_CODE_UNITS, MAX_AGENT_PHASE_ORDER, SubagentActivityMonitor, } from './activity.js';
17
17
  import { CLI_INTERACTIVE_RUN_TIMEOUT_MS } from './policy.js';
18
18
  export const GENERAL_PURPOSE_SUBAGENT = 'general-purpose';
19
19
  /**
20
- * A sub-agent that can only look.
21
- *
22
- * "Find where X is defined", "which files reference Y", "how does Z work" are
23
- * the delegations a parent makes most, and every one of them ran with
24
- * `write`, `edit` and `bash` in the child's roster — so a lookup needed the
25
- * same approvals as a change, and an operator in `accept-edits` mode was
26
- * still asked about a child's shell command that only wanted to `cat`. This
27
- * definition's registry is the parent's working set filtered to tools that
28
- * declare themselves read-only, so a child that cannot change anything is
29
- * never asked whether it may.
20
+ * The kernel's read-only delegate, with this application's doctrine under
21
+ * its prompt. Roster and prompt come from `@namzu/sdk`; what is added here is
22
+ * the same working rules every namzu child gets.
30
23
  */
31
- export const EXPLORE_SUBAGENT = 'explore';
32
- const EXPLORE_PROMPT = [
33
- 'You are a read-only sub-agent dispatched by namzu to find things out: where something is defined, which files reference it, how a piece of code works, what a directory contains.',
34
- 'You cannot see the parent conversation — work only from the prompt you were given.',
35
- 'You have reading and searching tools only. You cannot edit, write or run commands, and you must not describe a change as made.',
36
- 'Search broadly first, then read what matters. Report file paths with line numbers, quote the relevant lines, and say plainly what you did not find.',
37
- 'End with a concise answer the parent can act on; do not ask the parent questions.',
38
- '',
39
- NAMZU_WORKING_DOCTRINE,
40
- ].join('\n');
24
+ export const EXPLORE_SUBAGENT = EXPLORE_AGENT_ID;
25
+ const EXPLORE_PROMPT = [EXPLORE_AGENT_PROMPT, '', NAMZU_WORKING_DOCTRINE].join('\n');
41
26
  /**
42
- * The parent's roster, minus everything that can change state.
43
- *
44
- * Decided by each tool's own `isReadOnly` with no input — the declaration
45
- * `defineTool` records — rather than by a name list here, so a new read-only
46
- * builtin joins the explore roster without this file learning its name and
47
- * a tool that stops being read-only leaves it the same way.
27
+ * A file-defined agent's roster: the parent's working set, kept to the
28
+ * file's `tools` allowlist when there is one, and to read-only tools when
29
+ * the file says `readOnly: true`. Intersection, never union.
48
30
  */
49
- function readOnlyToolsFrom(source) {
50
- const filtered = new ToolRegistry();
51
- for (const tool of source.getAll()) {
52
- if (tool.isReadOnly?.(undefined) === true)
53
- filtered.register(tool);
54
- }
55
- return filtered;
31
+ function fileAgentTools(definition, opts) {
32
+ return () => {
33
+ const source = definition.readOnly ? filterReadOnlyTools(opts.buildTools()) : opts.buildTools();
34
+ return definition.tools ? filterToolsNamed(source, definition.tools) : source;
35
+ };
56
36
  }
57
37
  const SUBAGENT_PROMPT = [
58
38
  'You are a focused sub-agent dispatched by namzu to complete one self-contained task and report back.',
@@ -70,6 +50,39 @@ const SUBAGENT_PROMPT = [
70
50
  // the parent does not is the same defect in the other direction.
71
51
  NAMZU_WORKING_DOCTRINE,
72
52
  ].join('\n');
53
+ /** Recheck the invoking run before admitting work through a retained gateway. */
54
+ class ParentTaskScheduler extends LocalTaskScheduler {
55
+ validateParent;
56
+ constructor(manager, context, onEvent, validateParent) {
57
+ super(manager, context, onEvent);
58
+ this.validateParent = validateParent;
59
+ }
60
+ async createTask(options) {
61
+ await this.validateParent();
62
+ return super.createTask(options);
63
+ }
64
+ owns(taskId) {
65
+ return super.listTasks().some((task) => task.taskId === taskId);
66
+ }
67
+ getTask(taskId) {
68
+ return this.owns(taskId) ? super.getTask(taskId) : undefined;
69
+ }
70
+ cancelTask(taskId, cause) {
71
+ if (this.owns(taskId))
72
+ super.cancelTask(taskId, cause);
73
+ }
74
+ async waitForTask(taskId) {
75
+ if (!this.owns(taskId))
76
+ throw new Error(`Task ${taskId} does not belong to this parent run`);
77
+ return super.waitForTask(taskId);
78
+ }
79
+ async continueTask(taskId, message) {
80
+ if (!this.owns(taskId))
81
+ throw new Error(`Task ${taskId} does not belong to this parent run`);
82
+ await this.validateParent();
83
+ await super.continueTask(taskId, message);
84
+ }
85
+ }
73
86
  /** A delegated run may never inherit the SDK's headless auto-approval fallback. */
74
87
  const refuseUnownedChildReview = async () => ({
75
88
  action: 'abort',
@@ -77,63 +90,204 @@ const refuseUnownedChildReview = async () => ({
77
90
  });
78
91
  /**
79
92
  * Stand up the AgentManager + gateway + `Agent` tool. Returns the tool to
80
- * register on the parent and the gateway to pass to `query({ taskGateway })`.
93
+ * register on the parent and the gateway to pass to `query({ taskScheduler })`.
81
94
  */
82
95
  export async function createSubagentRuntime(opts) {
83
- const tenantId = asTenantId('tnt_namzu-cli');
84
- const store = new InMemorySessionStore();
85
- const topicStore = new InMemoryTopicStore();
86
- const userActor = {
87
- kind: 'user',
88
- userId: asUserId('usr_namzu'),
89
- tenantId,
90
- };
91
- const project = await store.createProject({ tenantId, name: 'namzu-cli' }, tenantId);
92
- const thread = await topicStore.createTopic({ projectId: project.id, title: 'namzu-cli' }, tenantId);
93
- // No workspace gate here, and that is a finding rather than an omission.
94
- //
95
- // A direct `createSession` bypasses `requireOpenProject`, which is why the
96
- // CLI's persistent conversation store now calls it explicitly. This site is
97
- // the other shape: the store is a fresh `InMemorySessionStore` built four
98
- // lines up, the project was created two lines up, and neither outlives this
99
- // runtime — so the id can never be one an owner has closed. A gate on a path
100
- // that always creates its own project checks a condition that cannot be
101
- // false, and a check that cannot fail teaches the next reader nothing except
102
- // that gates here are decoration.
103
- //
104
- // The trigger to add one is the day this store is replaced by a persistent
105
- // one, or the project id starts arriving from a caller.
106
- const parentSession = await store.createSession({ topicId: thread.id, projectId: project.id, currentActor: userActor }, tenantId);
107
- await store.updateSession({ ...parentSession, status: 'active' }, tenantId);
108
- let summaryCounter = 0;
109
- const materializer = new SessionSummaryMaterializer({
110
- store,
111
- generateSummaryId: () => asSummaryId(`sum_namzu_${++summaryCounter}`),
112
- });
113
96
  const registry = new AgentRegistry();
114
97
  registry.register(buildDefinition(GENERAL_PURPOSE_SUBAGENT, 'A general-purpose sub-agent.', SUBAGENT_PROMPT, opts));
115
- registry.register(buildDefinition(EXPLORE_SUBAGENT, 'A read-only sub-agent for finding files, symbols and answers.', EXPLORE_PROMPT, opts, () => readOnlyToolsFrom(opts.buildTools())));
116
- const topicManager = new TopicManager({ topicStore, sessionStore: store });
117
- const manager = new AgentManager(registry, { childTimeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS }, {
118
- sessionStore: store,
119
- summaryMaterializer: materializer,
120
- workspaceRegistry: new WorkspaceBackendRegistry(),
121
- capacity: new DefaultCapacityValidator(store),
122
- topicManager,
123
- });
124
- const taskContext = {
125
- parentRunId: asRunId('run_namzu-cli'),
126
- parentAgentId: 'namzu',
127
- parentAbortController: new AbortController(),
128
- depth: 0,
129
- budgetTracker: { total: 1_000_000, remaining: 1_000_000 },
130
- tenantId,
131
- topicId: thread.id,
132
- sessionId: parentSession.id,
133
- projectId: project.id,
134
- parentActor: userActor,
98
+ registry.register(buildDefinition(EXPLORE_SUBAGENT, EXPLORE_AGENT_DESCRIPTION, EXPLORE_PROMPT, opts, () => filterReadOnlyTools(opts.buildTools())));
99
+ // Agents a project or user defined in a file, each a type of its own.
100
+ // The roster is the file's allowlist intersected with the parent's set —
101
+ // a file cannot grant a tool the parent does not have — narrowed further
102
+ // to read-only when the file says so. The prompt is the file's body over
103
+ // the same sub-agent base every child gets.
104
+ const fileAgents = new Map();
105
+ for (const definition of opts.definitions ?? []) {
106
+ fileAgents.set(definition.name, definition);
107
+ registry.register(buildDefinition(definition.name, definition.description, definition.prompt, opts, fileAgentTools(definition, opts), definition.model ?? opts.model));
108
+ }
109
+ const agentTypeIds = [GENERAL_PURPOSE_SUBAGENT, EXPLORE_SUBAGENT, ...fileAgents.keys()];
110
+ const fileAgentSummary = [...fileAgents.values()]
111
+ .map((definition) => `"${definition.name}" — ${definition.description}`)
112
+ .join('; ');
113
+ const parents = new Map();
114
+ const sessions = new Map();
115
+ let closed = false;
116
+ const sessionKey = ({ project, sessionId }) => JSON.stringify([project.tenantId, project.id, sessionId]);
117
+ const resolveParent = async (runId) => {
118
+ const parent = structuredClone(await opts.resolveParent(runId));
119
+ const { project, topic } = parent;
120
+ if (topic.projectId !== project.id || topic.tenantId !== project.tenantId) {
121
+ throw new Error('Delegation parent topic does not belong to its project and tenant');
122
+ }
123
+ await requireOpenProject({ getProject: async () => project }, project.id, project.tenantId, 'spawn');
124
+ if (topic.status === 'archived')
125
+ throw new TopicArchivedError({ topicId: topic.id, op: 'spawn' });
126
+ return parent;
127
+ };
128
+ const createSessionRuntime = async (parent) => {
129
+ const { project, topic, sessionId } = parent;
130
+ const tenantId = project.tenantId;
131
+ const store = new InMemorySessionStore([project]);
132
+ const topicStore = new InMemoryTopicStore([topic]);
133
+ const parentActor = { kind: 'agent', agentId: 'namzu', tenantId };
134
+ const session = await store.createSession({
135
+ id: sessionId,
136
+ topicId: topic.id,
137
+ projectId: project.id,
138
+ currentActor: parentActor,
139
+ }, tenantId);
140
+ await store.updateSession({ ...session, status: 'active' }, tenantId);
141
+ const manager = new AgentManager(registry, { childTimeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS }, {
142
+ sessionStore: store,
143
+ summaryMaterializer: new SessionSummaryMaterializer({
144
+ store,
145
+ generateSummaryId,
146
+ }),
147
+ workspaceRegistry: new WorkspaceBackendRegistry(),
148
+ capacity: new DefaultCapacityValidator(store),
149
+ topicManager: new TopicManager({ topicStore, sessionStore: store }),
150
+ });
151
+ return {
152
+ manager,
153
+ store,
154
+ topicId: topic.id,
155
+ projectUpdatedAt: project.updatedAt.getTime(),
156
+ };
157
+ };
158
+ const refreshLimits = async (shared, parent) => {
159
+ if (shared.topicId !== parent.topic.id)
160
+ throw new Error('Delegation parent changed its topic while runs are active');
161
+ // A slow metadata read must not overwrite a newer project snapshot.
162
+ const updatedAt = parent.project.updatedAt.getTime();
163
+ if (updatedAt < shared.projectUpdatedAt)
164
+ return;
165
+ shared.projectUpdatedAt = updatedAt;
166
+ await shared.store.updateProject(parent.project.id, parent.project.config, parent.project.tenantId);
167
+ };
168
+ const acquireSession = async (parent) => {
169
+ const key = sessionKey(parent);
170
+ let entry = sessions.get(key);
171
+ if (!entry) {
172
+ entry = { ready: createSessionRuntime(parent), owners: 0 };
173
+ sessions.set(key, entry);
174
+ }
175
+ entry.owners++;
176
+ const ownedEntry = entry;
177
+ let released = false;
178
+ const release = () => {
179
+ if (released)
180
+ return;
181
+ released = true;
182
+ ownedEntry.owners--;
183
+ if (ownedEntry.owners !== 0)
184
+ return;
185
+ if (sessions.get(key) === ownedEntry)
186
+ sessions.delete(key);
187
+ void ownedEntry.ready.then((shared) => shared.manager.dispose(), () => undefined);
188
+ };
189
+ try {
190
+ const shared = await entry.ready;
191
+ await refreshLimits(shared, parent);
192
+ return { shared, release };
193
+ }
194
+ catch (error) {
195
+ release();
196
+ throw error;
197
+ }
198
+ };
199
+ const gatewayForRun = async (runId) => {
200
+ if (closed)
201
+ throw new Error('Sub-agent runtime is closed');
202
+ let pending = parents.get(runId);
203
+ if (!pending) {
204
+ pending = (async () => {
205
+ const parent = await resolveParent(runId);
206
+ const budget = await openTokenBudget({
207
+ scope: {
208
+ tenantId: parent.project.tenantId,
209
+ projectId: parent.project.id,
210
+ sessionId: parent.sessionId,
211
+ runId,
212
+ },
213
+ limit: opts.tokenBudget ?? 1_000_000,
214
+ pathBuilder: opts.pathBuilder,
215
+ workingDirectory: opts.cwd,
216
+ });
217
+ const lease = await acquireSession(parent);
218
+ const { manager } = lease.shared;
219
+ const parentAbortController = new AbortController();
220
+ let released = false;
221
+ const assertOwned = () => {
222
+ if (closed ||
223
+ released ||
224
+ parentAbortController.signal.aborted ||
225
+ parents.get(runId) !== pending) {
226
+ throw new Error(`Parent run ${runId} was released`);
227
+ }
228
+ };
229
+ const taskContext = {
230
+ parentRunId: runId,
231
+ parentAgentId: 'namzu',
232
+ parentAbortController,
233
+ depth: 0,
234
+ budget,
235
+ tenantId: parent.project.tenantId,
236
+ topicId: parent.topic.id,
237
+ sessionId: parent.sessionId,
238
+ projectId: parent.project.id,
239
+ parentActor: {
240
+ kind: 'agent',
241
+ agentId: 'namzu',
242
+ tenantId: parent.project.tenantId,
243
+ },
244
+ };
245
+ const runtime = {
246
+ gateway: new ParentTaskScheduler(manager, taskContext, opts.onEvent, async () => {
247
+ assertOwned();
248
+ const current = await resolveParent(runId);
249
+ assertOwned();
250
+ if (sessionKey(current) !== sessionKey(parent))
251
+ throw new Error('Delegation run changed its parent scope');
252
+ await refreshLimits(lease.shared, current);
253
+ assertOwned();
254
+ }),
255
+ close() {
256
+ if (released)
257
+ return;
258
+ released = true;
259
+ parentAbortController.abort(new RunCancelled('parent'));
260
+ manager.cancelAll(runId, 'parent');
261
+ lease.release();
262
+ },
263
+ };
264
+ if (closed) {
265
+ runtime.close();
266
+ throw new Error('Sub-agent runtime is closed');
267
+ }
268
+ return runtime;
269
+ })();
270
+ parents.set(runId, pending);
271
+ }
272
+ try {
273
+ const runtime = await pending;
274
+ if (closed || parents.get(runId) !== pending)
275
+ throw new Error(`Parent run ${runId} was released`);
276
+ return runtime.gateway;
277
+ }
278
+ catch (error) {
279
+ if (parents.get(runId) === pending)
280
+ parents.delete(runId);
281
+ throw error;
282
+ }
283
+ };
284
+ const releaseRun = async (runId) => {
285
+ const pending = parents.get(runId);
286
+ if (!pending)
287
+ return;
288
+ parents.delete(runId);
289
+ await pending.then((runtime) => runtime.close(), () => undefined);
135
290
  };
136
- const gateway = new LocalTaskScheduler(manager, taskContext, opts.onEvent);
137
291
  const activity = new SubagentActivityMonitor();
138
292
  // Dynamic `Agent` tool: the model passes an optional `role` (the persona /
139
293
  // system prompt) and we register + spawn a fresh specialist for it at call
@@ -141,19 +295,21 @@ export async function createSubagentRuntime(opts) {
141
295
  let dynCounter = 0;
142
296
  const agentTool = defineTool({
143
297
  name: 'Agent',
144
- description: 'Delegate a self-contained task to a sub-agent and get its result back (BLOCKING). ' +
145
- 'Pick `subagent_type: "explore"` for anything that only needs to look — where is X defined, which ' +
146
- 'files reference Y, how does Z work — it has reading and searching tools only and never asks for ' +
147
- 'permission. Use the default "general-purpose" when the task must change files or run commands. ' +
148
- 'Define a specialist inline with `role` — a system prompt describing who the sub-agent ' +
149
- 'is and how to behave (e.g. "You are a security auditor; flag vulnerabilities and rate severity"); ' +
150
- 'with `subagent_type: "explore"` the role keeps the read-only roster. ' +
151
- 'Omit `role` for the plain sub-agent. The sub-agent runs in its own context with its own ' +
152
- 'tools and cannot see this conversation — put everything it needs in `prompt`. Call this multiple ' +
153
- 'times in one response to run specialists in parallel. When coordinating several specialists, give ' +
154
- 'them the same `workflow` label and an explicit `phase` plus `phase_order` so the operator can follow ' +
155
- 'the work in the agent cockpit. These fields are display annotations only; they do not create ' +
156
- 'dependencies, barriers, or serial execution.',
298
+ description: [
299
+ 'Delegate a self-contained task to a sub-agent and get its result back (BLOCKING).',
300
+ 'Pick `subagent_type: "explore"` for anything that only needs to look — where is X defined, which files reference Y, how does Z work — it has reading and searching tools only and never asks for permission.',
301
+ 'Use the default "general-purpose" when the task must change files or run commands.',
302
+ 'Define a specialist inline with `role` — a system prompt describing who the sub-agent is and how to behave (e.g.',
303
+ '"You are a security auditor; flag vulnerabilities and rate severity"); with `subagent_type: "explore"` the role keeps the read-only roster.',
304
+ 'Omit `role` for the plain sub-agent.',
305
+ 'The sub-agent runs in its own context with its own tools and cannot see this conversation — put everything it needs in `prompt`.',
306
+ 'Call this multiple times in one response to run specialists in parallel.',
307
+ 'When coordinating several specialists, give them the same `workflow` label and an explicit `phase` plus `phase_order` so the operator can follow the work in the agent cockpit.',
308
+ 'These fields are display annotations only; they do not create dependencies, barriers, or serial execution.',
309
+ fileAgentSummary.length > 0 ? `Project-defined types: ${fileAgentSummary}.` : '',
310
+ ]
311
+ .filter((part) => part.length > 0)
312
+ .join(' '),
157
313
  inputSchema: mcpJsonSchemaToZod({
158
314
  type: 'object',
159
315
  properties: {
@@ -167,8 +323,10 @@ export async function createSubagentRuntime(opts) {
167
323
  },
168
324
  subagent_type: {
169
325
  type: 'string',
170
- enum: [GENERAL_PURPOSE_SUBAGENT, EXPLORE_SUBAGENT],
171
- description: '"explore" for read-only lookups (find, search, explain); "general-purpose" (default) when the task changes files or runs commands.',
326
+ enum: agentTypeIds,
327
+ description: `"explore" for read-only lookups (find, search, explain); "general-purpose" (default) when the task changes files or runs commands.${fileAgentSummary.length > 0
328
+ ? ` This project also defines: ${fileAgentSummary}. Prefer a project-defined type when its description fits the task.`
329
+ : ''}`,
172
330
  },
173
331
  role: {
174
332
  type: 'string',
@@ -202,15 +360,22 @@ export async function createSubagentRuntime(opts) {
202
360
  async execute(input, context) {
203
361
  const { description, prompt, subagent_type, role, workflow, phase, phase_order } = input;
204
362
  const explore = subagent_type === EXPLORE_SUBAGENT;
205
- let agentId = explore ? EXPLORE_SUBAGENT : GENERAL_PURPOSE_SUBAGENT;
363
+ const fileAgent = subagent_type !== undefined ? fileAgents.get(subagent_type) : undefined;
364
+ let agentId = fileAgent?.name ?? (explore ? EXPLORE_SUBAGENT : GENERAL_PURPOSE_SUBAGENT);
206
365
  const persona = typeof role === 'string' ? role.trim() : '';
207
366
  const dynamic = persona.length > 0;
208
367
  if (dynamic) {
209
- // A role on top of explore keeps explore's roster: the persona says
210
- // who the child is, the type says what it may touch, and a role
211
- // must not be a way to hand a read-only child a `write` tool.
368
+ // A role on top of a type keeps that type's roster and model: the
369
+ // persona says who the child is, the type says what it may touch,
370
+ // and a role must not be a way to hand a read-only child a `write`
371
+ // tool. Over a file-defined agent the role is appended to the
372
+ // file's prompt rather than replacing it.
212
373
  agentId = `dyn-${++dynCounter}`;
213
- registry.register(buildDefinition(agentId, `Dynamic specialist: ${agentId}`, persona, opts, explore ? () => readOnlyToolsFrom(opts.buildTools()) : opts.buildTools));
374
+ registry.register(buildDefinition(agentId, `Dynamic specialist: ${agentId}`, fileAgent ? `${fileAgent.prompt}\n\n${persona}` : persona, opts, fileAgent
375
+ ? fileAgentTools(fileAgent, opts)
376
+ : explore
377
+ ? () => filterReadOnlyTools(opts.buildTools())
378
+ : opts.buildTools, fileAgent?.model ?? opts.model));
214
379
  }
215
380
  const tracker = activity.begin({
216
381
  agentId,
@@ -236,7 +401,7 @@ export async function createSubagentRuntime(opts) {
236
401
  };
237
402
  try {
238
403
  const completed = await runBlockingAgentTask({
239
- gateway,
404
+ gateway: await gatewayForRun(context.runId),
240
405
  signal: context.abortSignal,
241
406
  create: {
242
407
  agentId,
@@ -289,22 +454,15 @@ export async function createSubagentRuntime(opts) {
289
454
  const close = () => {
290
455
  if (closePromise)
291
456
  return closePromise;
292
- closePromise = Promise.resolve().then(() => {
293
- // This controller is the parent of every child the manager creates.
294
- // Abort it before disposing bookkeeping so a live child keeps the
295
- // structured reason even if its blocking tool wait already detached.
296
- if (!taskContext.parentAbortController.signal.aborted) {
297
- taskContext.parentAbortController.abort(new RunCancelled('parent'));
298
- }
299
- manager.dispose();
300
- activity.close();
301
- });
457
+ closed = true;
458
+ closePromise = Promise.all([...parents.keys()].map(releaseRun)).then(() => activity.close());
302
459
  return closePromise;
303
460
  };
304
461
  return {
305
- gateway,
462
+ gatewayForRun,
463
+ releaseRun,
306
464
  agentTool,
307
- allowedAgentIds: [GENERAL_PURPOSE_SUBAGENT, EXPLORE_SUBAGENT],
465
+ allowedAgentIds: agentTypeIds,
308
466
  activity,
309
467
  close,
310
468
  };
@@ -371,7 +529,9 @@ async function runBlockingAgentTask(input) {
371
529
  */
372
530
  function buildDefinition(id, description, systemPrompt, opts,
373
531
  /** This definition's own roster; absent means the parent's working set. */
374
- tools = opts.buildTools) {
532
+ tools = opts.buildTools,
533
+ /** This definition's model; absent means the session's. */
534
+ model = opts.model) {
375
535
  const agent = new ReactiveAgent({
376
536
  id,
377
537
  name: id,
@@ -391,7 +551,7 @@ tools = opts.buildTools) {
391
551
  category: 'general',
392
552
  description,
393
553
  tools: [],
394
- defaults: { model: opts.model, tokenBudget: 200_000 },
554
+ defaults: { model, tokenBudget: 200_000 },
395
555
  },
396
556
  // ReactiveAgent is Agent<ReactiveAgentConfig,…>; the registry stores the
397
557
  // erased Agent<BaseAgentConfig,…>. configBuilder supplies the richer config.
@@ -403,7 +563,7 @@ tools = opts.buildTools) {
403
563
  // is worse than one that was never told.
404
564
  const environment = opts.readEnvironment ? await opts.readEnvironment() : null;
405
565
  return {
406
- model: options.model ?? opts.model,
566
+ model: options.model ?? model,
407
567
  tokenBudget: options.tokenBudget ?? 200_000,
408
568
  timeoutMs: options.timeoutMs ?? CLI_INTERACTIVE_RUN_TIMEOUT_MS,
409
569
  maxIterations: 40,