@namzu/cli 20.0.0 → 22.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 (224) hide show
  1. package/CHANGELOG.md +371 -0
  2. package/README.md +71 -10
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +24 -2
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/acp.d.ts.map +1 -1
  7. package/dist/commands/acp.js +6 -1
  8. package/dist/commands/acp.js.map +1 -1
  9. package/dist/commands/run-stream.d.ts.map +1 -1
  10. package/dist/commands/run-stream.js +9 -1
  11. package/dist/commands/run-stream.js.map +1 -1
  12. package/dist/commands/run.d.ts.map +1 -1
  13. package/dist/commands/run.js +15 -12
  14. package/dist/commands/run.js.map +1 -1
  15. package/dist/config/load.d.ts.map +1 -1
  16. package/dist/config/load.js +44 -2
  17. package/dist/config/load.js.map +1 -1
  18. package/dist/config/schema.d.ts +24 -13
  19. package/dist/config/schema.d.ts.map +1 -1
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/doctor/checks/chain.d.ts.map +1 -1
  22. package/dist/doctor/checks/chain.js +11 -5
  23. package/dist/doctor/checks/chain.js.map +1 -1
  24. package/dist/doctor/checks/credentials.d.ts.map +1 -1
  25. package/dist/doctor/checks/credentials.js +7 -1
  26. package/dist/doctor/checks/credentials.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/installation.d.ts +5 -0
  31. package/dist/installation.d.ts.map +1 -0
  32. package/dist/installation.js +48 -0
  33. package/dist/installation.js.map +1 -0
  34. package/dist/integrations/providers/access.d.ts +7 -0
  35. package/dist/integrations/providers/access.d.ts.map +1 -0
  36. package/dist/integrations/providers/access.js +17 -0
  37. package/dist/integrations/providers/access.js.map +1 -0
  38. package/dist/integrations/providers/current-credential.d.ts +4 -0
  39. package/dist/integrations/providers/current-credential.d.ts.map +1 -0
  40. package/dist/integrations/providers/current-credential.js +45 -0
  41. package/dist/integrations/providers/current-credential.js.map +1 -0
  42. package/dist/integrations/providers/discover.d.ts +28 -54
  43. package/dist/integrations/providers/discover.d.ts.map +1 -1
  44. package/dist/integrations/providers/discover.js +62 -58
  45. package/dist/integrations/providers/discover.js.map +1 -1
  46. package/dist/integrations/providers/gemini-credentials.d.ts +13 -0
  47. package/dist/integrations/providers/gemini-credentials.d.ts.map +1 -0
  48. package/dist/integrations/providers/gemini-credentials.js +148 -0
  49. package/dist/integrations/providers/gemini-credentials.js.map +1 -0
  50. package/dist/integrations/providers/harness-credentials.d.ts +14 -0
  51. package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
  52. package/dist/integrations/providers/harness-credentials.js +46 -0
  53. package/dist/integrations/providers/harness-credentials.js.map +1 -1
  54. package/dist/integrations/providers/model-reasoning.d.ts +4 -0
  55. package/dist/integrations/providers/model-reasoning.d.ts.map +1 -0
  56. package/dist/integrations/providers/model-reasoning.js +46 -0
  57. package/dist/integrations/providers/model-reasoning.js.map +1 -0
  58. package/dist/integrations/providers/register.d.ts +1 -1
  59. package/dist/integrations/providers/register.d.ts.map +1 -1
  60. package/dist/integrations/providers/register.js +16 -1
  61. package/dist/integrations/providers/register.js.map +1 -1
  62. package/dist/integrations/providers/registry.d.ts +4 -4
  63. package/dist/integrations/providers/registry.d.ts.map +1 -1
  64. package/dist/integrations/providers/registry.js +33 -0
  65. package/dist/integrations/providers/registry.js.map +1 -1
  66. package/dist/integrations/providers/setup.d.ts +49 -0
  67. package/dist/integrations/providers/setup.d.ts.map +1 -0
  68. package/dist/integrations/providers/setup.js +105 -0
  69. package/dist/integrations/providers/setup.js.map +1 -0
  70. package/dist/integrations/sessions/context-inventory.d.ts +4 -0
  71. package/dist/integrations/sessions/context-inventory.d.ts.map +1 -0
  72. package/dist/integrations/sessions/context-inventory.js +44 -0
  73. package/dist/integrations/sessions/context-inventory.js.map +1 -0
  74. package/dist/integrations/sessions/conversation-search.d.ts +60 -0
  75. package/dist/integrations/sessions/conversation-search.d.ts.map +1 -0
  76. package/dist/integrations/sessions/conversation-search.js +461 -0
  77. package/dist/integrations/sessions/conversation-search.js.map +1 -0
  78. package/dist/integrations/sessions/store.d.ts +1 -0
  79. package/dist/integrations/sessions/store.d.ts.map +1 -1
  80. package/dist/integrations/sessions/store.js +17 -4
  81. package/dist/integrations/sessions/store.js.map +1 -1
  82. package/dist/integrations/sessions/task-context.d.ts +4 -0
  83. package/dist/integrations/sessions/task-context.d.ts.map +1 -0
  84. package/dist/integrations/sessions/task-context.js +73 -0
  85. package/dist/integrations/sessions/task-context.js.map +1 -0
  86. package/dist/integrations/subagents/activity.d.ts +3 -1
  87. package/dist/integrations/subagents/activity.d.ts.map +1 -1
  88. package/dist/integrations/subagents/activity.js +32 -5
  89. package/dist/integrations/subagents/activity.js.map +1 -1
  90. package/dist/integrations/subagents/history.d.ts +26 -0
  91. package/dist/integrations/subagents/history.d.ts.map +1 -0
  92. package/dist/integrations/subagents/history.js +76 -0
  93. package/dist/integrations/subagents/history.js.map +1 -0
  94. package/dist/integrations/subagents/model-effort.d.ts +4 -0
  95. package/dist/integrations/subagents/model-effort.d.ts.map +1 -0
  96. package/dist/integrations/subagents/model-effort.js +12 -0
  97. package/dist/integrations/subagents/model-effort.js.map +1 -0
  98. package/dist/integrations/subagents/runtime.d.ts +27 -7
  99. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  100. package/dist/integrations/subagents/runtime.js +305 -35
  101. package/dist/integrations/subagents/runtime.js.map +1 -1
  102. package/dist/integrations/web/search.d.ts +11 -0
  103. package/dist/integrations/web/search.d.ts.map +1 -0
  104. package/dist/integrations/web/search.js +73 -0
  105. package/dist/integrations/web/search.js.map +1 -0
  106. package/dist/memory/io.d.ts +12 -0
  107. package/dist/memory/io.d.ts.map +1 -0
  108. package/dist/memory/io.js +177 -0
  109. package/dist/memory/io.js.map +1 -0
  110. package/dist/memory/presentation.d.ts +4 -1
  111. package/dist/memory/presentation.d.ts.map +1 -1
  112. package/dist/memory/presentation.js +16 -2
  113. package/dist/memory/presentation.js.map +1 -1
  114. package/dist/memory/store.d.ts +17 -3
  115. package/dist/memory/store.d.ts.map +1 -1
  116. package/dist/memory/store.js +61 -35
  117. package/dist/memory/store.js.map +1 -1
  118. package/dist/output-schema.d.ts +4 -0
  119. package/dist/output-schema.d.ts.map +1 -0
  120. package/dist/output-schema.js +18 -0
  121. package/dist/output-schema.js.map +1 -0
  122. package/dist/tui/AgentExplorer.d.ts +13 -2
  123. package/dist/tui/AgentExplorer.d.ts.map +1 -1
  124. package/dist/tui/AgentExplorer.js +85 -23
  125. package/dist/tui/AgentExplorer.js.map +1 -1
  126. package/dist/tui/App.d.ts.map +1 -1
  127. package/dist/tui/App.js +694 -150
  128. package/dist/tui/App.js.map +1 -1
  129. package/dist/tui/BrandHeader.d.ts +1 -4
  130. package/dist/tui/BrandHeader.d.ts.map +1 -1
  131. package/dist/tui/BrandHeader.js +4 -8
  132. package/dist/tui/BrandHeader.js.map +1 -1
  133. package/dist/tui/Composer.d.ts +2 -1
  134. package/dist/tui/Composer.d.ts.map +1 -1
  135. package/dist/tui/Composer.js +26 -3
  136. package/dist/tui/Composer.js.map +1 -1
  137. package/dist/tui/PermissionOverlay.d.ts +5 -2
  138. package/dist/tui/PermissionOverlay.d.ts.map +1 -1
  139. package/dist/tui/PermissionOverlay.js +14 -6
  140. package/dist/tui/PermissionOverlay.js.map +1 -1
  141. package/dist/tui/Picker.d.ts +2 -1
  142. package/dist/tui/Picker.d.ts.map +1 -1
  143. package/dist/tui/Picker.js +104 -24
  144. package/dist/tui/Picker.js.map +1 -1
  145. package/dist/tui/ProviderSetup.d.ts +6 -0
  146. package/dist/tui/ProviderSetup.d.ts.map +1 -0
  147. package/dist/tui/ProviderSetup.js +125 -0
  148. package/dist/tui/ProviderSetup.js.map +1 -0
  149. package/dist/tui/ResumePicker.d.ts.map +1 -1
  150. package/dist/tui/ResumePicker.js +5 -3
  151. package/dist/tui/ResumePicker.js.map +1 -1
  152. package/dist/tui/StatusPanel.d.ts +5 -0
  153. package/dist/tui/StatusPanel.d.ts.map +1 -0
  154. package/dist/tui/StatusPanel.js +11 -0
  155. package/dist/tui/StatusPanel.js.map +1 -0
  156. package/dist/tui/TextPrompt.d.ts +3 -1
  157. package/dist/tui/TextPrompt.d.ts.map +1 -1
  158. package/dist/tui/TextPrompt.js +55 -9
  159. package/dist/tui/TextPrompt.js.map +1 -1
  160. package/dist/tui/ToolOutputViewer.d.ts +11 -0
  161. package/dist/tui/ToolOutputViewer.d.ts.map +1 -0
  162. package/dist/tui/ToolOutputViewer.js +39 -0
  163. package/dist/tui/ToolOutputViewer.js.map +1 -0
  164. package/dist/tui/Transcript.d.ts.map +1 -1
  165. package/dist/tui/Transcript.js +10 -4
  166. package/dist/tui/Transcript.js.map +1 -1
  167. package/dist/tui/agent.d.ts +24 -3
  168. package/dist/tui/agent.d.ts.map +1 -1
  169. package/dist/tui/agent.js +362 -97
  170. package/dist/tui/agent.js.map +1 -1
  171. package/dist/tui/live-window.d.ts.map +1 -1
  172. package/dist/tui/live-window.js +5 -1
  173. package/dist/tui/live-window.js.map +1 -1
  174. package/dist/tui/logo.d.ts +4 -4
  175. package/dist/tui/logo.d.ts.map +1 -1
  176. package/dist/tui/logo.js +4 -4
  177. package/dist/tui/logo.js.map +1 -1
  178. package/dist/tui/model-catalogue-view.d.ts +3 -0
  179. package/dist/tui/model-catalogue-view.d.ts.map +1 -0
  180. package/dist/tui/model-catalogue-view.js +50 -0
  181. package/dist/tui/model-catalogue-view.js.map +1 -0
  182. package/dist/tui/model-choices.d.ts +3 -1
  183. package/dist/tui/model-choices.d.ts.map +1 -1
  184. package/dist/tui/model-choices.js +12 -1
  185. package/dist/tui/model-choices.js.map +1 -1
  186. package/dist/tui/model-search.d.ts +4 -0
  187. package/dist/tui/model-search.d.ts.map +1 -0
  188. package/dist/tui/model-search.js +11 -0
  189. package/dist/tui/model-search.js.map +1 -0
  190. package/dist/tui/model-selection-intent.d.ts +8 -0
  191. package/dist/tui/model-selection-intent.d.ts.map +1 -0
  192. package/dist/tui/model-selection-intent.js +96 -0
  193. package/dist/tui/model-selection-intent.js.map +1 -0
  194. package/dist/tui/model-switch-tool.d.ts +7 -0
  195. package/dist/tui/model-switch-tool.d.ts.map +1 -0
  196. package/dist/tui/model-switch-tool.js +74 -0
  197. package/dist/tui/model-switch-tool.js.map +1 -0
  198. package/dist/tui/model-switch.d.ts +35 -0
  199. package/dist/tui/model-switch.d.ts.map +1 -0
  200. package/dist/tui/model-switch.js +106 -0
  201. package/dist/tui/model-switch.js.map +1 -0
  202. package/dist/tui/permission-review.d.ts +2 -0
  203. package/dist/tui/permission-review.d.ts.map +1 -1
  204. package/dist/tui/permission-review.js +82 -15
  205. package/dist/tui/permission-review.js.map +1 -1
  206. package/dist/tui/run-interruption.d.ts +7 -0
  207. package/dist/tui/run-interruption.d.ts.map +1 -1
  208. package/dist/tui/run-interruption.js +41 -0
  209. package/dist/tui/run-interruption.js.map +1 -1
  210. package/dist/tui/slashCommands.d.ts +7 -0
  211. package/dist/tui/slashCommands.d.ts.map +1 -1
  212. package/dist/tui/slashCommands.js +77 -2
  213. package/dist/tui/slashCommands.js.map +1 -1
  214. package/dist/tui/status-card.d.ts +3 -0
  215. package/dist/tui/status-card.d.ts.map +1 -0
  216. package/dist/tui/status-card.js +30 -0
  217. package/dist/tui/status-card.js.map +1 -0
  218. package/dist/tui/status-panel-layout.d.ts +11 -0
  219. package/dist/tui/status-panel-layout.d.ts.map +1 -0
  220. package/dist/tui/status-panel-layout.js +42 -0
  221. package/dist/tui/status-panel-layout.js.map +1 -0
  222. package/dist/tui/types.d.ts +7 -1
  223. package/dist/tui/types.d.ts.map +1 -1
  224. package/package.json +9 -7
@@ -3,17 +3,20 @@
3
3
  * DEFINE a specialist on the fly — pass a `role` (the persona / system prompt)
4
4
  * and namzu spins up a fresh sub-agent with that role at runtime, no
5
5
  * pre-registered definition needed. Omit `role` for a general-purpose one.
6
- * The call waits for a final result unless operator input releases the wait.
6
+ * The call waits for a final result unless background execution is requested
7
+ * or operator input releases the wait.
7
8
  * The child then stays owned by the parent run, and its completion reaches the
8
- * same query's inbox. Headless callers without an input waiter remain blocking.
9
+ * same query's inbox. Headless callers also support explicit background work.
9
10
  *
10
11
  * The runtime is fully self-contained: a dedicated in-memory session/thread
11
12
  * store backs the AgentManager, so sub-agent bookkeeping never touches the
12
- * CLI's on-disk `/resume` conversation store.
13
+ * CLI's on-disk `/resume` conversation store. A separate session-scoped
14
+ * receipt archive preserves observed outcomes without restoring execution authority.
13
15
  */
14
- import { AgentManager, AgentRegistry, CompletionInbox, DefaultCapacityValidator, EXPLORE_AGENT_DESCRIPTION, EXPLORE_AGENT_ID, EXPLORE_AGENT_PROMPT, InMemorySessionStore, InMemoryTopicStore, LocalTaskScheduler, ReactiveAgent, RunCancelled, SessionSummaryMaterializer, TopicArchivedError, TopicManager, WorkspaceBackendRegistry, asTaskId, defineTool, filterReadOnlyTools, filterToolsNamed, generateSummaryId, isTerminalAgentTaskState, mcpJsonSchemaToZod, openTokenBudget, requireOpenProject, } from '@namzu/sdk';
16
+ import { AgentManager, AgentRegistry, CompletionInbox, DefaultCapacityValidator, EXPLORE_AGENT_DESCRIPTION, EXPLORE_AGENT_ID, EXPLORE_AGENT_PROMPT, InMemorySessionStore, InMemoryTopicStore, LocalTaskScheduler, ReactiveAgent, RunCancelled, SessionSummaryMaterializer, TopicArchivedError, TopicManager, WorkspaceBackendRegistry, asRunId, asTaskId, defineTool, filterReadOnlyTools, filterToolsNamed, generateSummaryId, isTerminalAgentTaskState, mcpJsonSchemaToZod, openTokenBudget, requireOpenProject, } from '@namzu/sdk';
15
17
  import { NAMZU_WORKING_DOCTRINE } from '../../context/doctrine.js';
16
18
  import { MAX_AGENT_ACTIVITY_LABEL_CODE_UNITS, MAX_AGENT_PHASE_ORDER, SubagentActivityMonitor, } from './activity.js';
19
+ import { DelegationHistory, HISTORY_GUIDANCE } from './history.js';
17
20
  import { CLI_INTERACTIVE_RUN_TIMEOUT_MS } from './policy.js';
18
21
  export const GENERAL_PURPOSE_SUBAGENT = 'general-purpose';
19
22
  /**
@@ -59,21 +62,32 @@ class ParentTaskScheduler extends LocalTaskScheduler {
59
62
  }
60
63
  async createTask(options) {
61
64
  await this.validateParent();
62
- return super.createTask(options);
65
+ return super.createTask({
66
+ ...options,
67
+ beforeStart: async () => {
68
+ await options.beforeStart?.();
69
+ await this.validateParent();
70
+ },
71
+ });
63
72
  }
64
73
  owns(taskId) {
65
74
  return super.listTasks().some((task) => task.taskId === taskId);
66
75
  }
67
76
  getTask(taskId) {
68
- return this.owns(taskId) ? super.getTask(taskId) : undefined;
77
+ // The parent ledger retains terminal results after the manager evicts
78
+ // its live record. Lookup and waiting must read the same owned history.
79
+ return super.listTasks().find((task) => task.taskId === taskId);
69
80
  }
70
81
  cancelTask(taskId, cause) {
71
82
  if (this.owns(taskId))
72
83
  super.cancelTask(taskId, cause);
73
84
  }
74
85
  async waitForTask(taskId) {
75
- if (!this.owns(taskId))
86
+ const task = this.getTask(taskId);
87
+ if (!task)
76
88
  throw new Error(`Task ${taskId} does not belong to this parent run`);
89
+ if (isTerminalAgentTaskState(task.state))
90
+ return task;
77
91
  return super.waitForTask(taskId);
78
92
  }
79
93
  async continueTask(taskId, message) {
@@ -138,7 +152,7 @@ export async function createSubagentRuntime(opts) {
138
152
  currentActor: parentActor,
139
153
  }, tenantId);
140
154
  await store.updateSession({ ...session, status: 'active' }, tenantId);
141
- const manager = new AgentManager(registry, { childTimeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS }, {
155
+ const manager = new AgentManager(registry, { childTimeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS, capacityBehavior: 'queue' }, {
142
156
  sessionStore: store,
143
157
  summaryMaterializer: new SessionSummaryMaterializer({
144
158
  store,
@@ -210,7 +224,7 @@ export async function createSubagentRuntime(opts) {
210
224
  sessionId: parent.sessionId,
211
225
  runId,
212
226
  },
213
- limit: opts.tokenBudget ?? 1_000_000,
227
+ limit: opts.tokenBudget ?? 0,
214
228
  pathBuilder: opts.pathBuilder,
215
229
  workingDirectory: opts.cwd,
216
230
  });
@@ -308,14 +322,15 @@ export async function createSubagentRuntime(opts) {
308
322
  const agentTool = defineTool({
309
323
  name: 'Agent',
310
324
  description: [
311
- 'Delegate a self-contained task to a sub-agent and wait for its result. If the operator sends a message while you wait, this call returns the running task ID so you can respond; the child keeps working and its result arrives later as a task notification.',
325
+ 'Delegate a self-contained task to a sub-agent. Set run_in_background: true to receive its task ID immediately and continue independent work; completion arrives as a task notification. Otherwise wait for its result. Operator input can also release a blocking wait while the child keeps working. Use agent_task_list for current agent status (task_list is only the planning list), send_message to correct a running task, cancel_agent to stop only one owned task, and wait_for_task to retrieve its result; never launch duplicate work.',
326
+ 'For a request to run multiple independent tasks in parallel, issue all Agent calls together in one response, or set run_in_background: true on each launch before waiting. A single blocking Agent call followed by another call runs them sequentially.',
312
327
  '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.',
313
328
  'Use the default "general-purpose" when the task must change files or run commands.',
314
329
  'Define a specialist inline with `role` — a system prompt describing who the sub-agent is and how to behave (e.g.',
315
330
  '"You are a security auditor; flag vulnerabilities and rate severity"); with `subagent_type: "explore"` the role keeps the read-only roster.',
316
331
  'Omit `role` for the plain sub-agent.',
317
332
  'The sub-agent runs in its own context with its own tools and cannot see this conversation — put everything it needs in `prompt`.',
318
- 'Call this multiple times in one response to run specialists in parallel.',
333
+ "Call this multiple times in one response to run specialists in parallel. Tasks beyond the project's live-agent capacity wait in a queue and start as slots become available. Queued tasks have not started execution; do not relaunch them.",
319
334
  '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.',
320
335
  'These fields are display annotations only; they do not create dependencies, barriers, or serial execution.',
321
336
  fileAgentSummary.length > 0 ? `Project-defined types: ${fileAgentSummary}.` : '',
@@ -333,6 +348,10 @@ export async function createSubagentRuntime(opts) {
333
348
  type: 'string',
334
349
  description: 'Self-contained task with all the context the sub-agent needs.',
335
350
  },
351
+ run_in_background: {
352
+ type: 'boolean',
353
+ description: 'Return after launch so the parent can continue independent work. Defaults to false.',
354
+ },
336
355
  subagent_type: {
337
356
  type: 'string',
338
357
  enum: agentTypeIds,
@@ -340,6 +359,18 @@ export async function createSubagentRuntime(opts) {
340
359
  ? ` This project also defines: ${fileAgentSummary}. Prefer a project-defined type when its description fits the task.`
341
360
  : ''}`,
342
361
  },
362
+ model: {
363
+ type: 'string',
364
+ description: 'Exact child model ID. Omit to inherit the session model. Use agent_models to discover available IDs; do not search repository files.',
365
+ },
366
+ provider: {
367
+ type: 'string',
368
+ description: 'Provider for this child only. Requires model; does not switch the parent conversation.',
369
+ },
370
+ effort: {
371
+ type: 'string',
372
+ description: 'Exact reasoning effort supported by the child model. Requires model. Omit for its provider default.',
373
+ },
343
374
  role: {
344
375
  type: 'string',
345
376
  description: 'Optional persona / system prompt that defines this specialist sub-agent. Omit for general-purpose.',
@@ -370,12 +401,19 @@ export async function createSubagentRuntime(opts) {
370
401
  concurrencySafe: true,
371
402
  timeoutMs: CLI_INTERACTIVE_RUN_TIMEOUT_MS,
372
403
  async execute(input, context) {
373
- const { description, prompt, subagent_type, role, workflow, phase, phase_order } = input;
404
+ const { description, prompt, subagent_type, role, workflow, phase, phase_order, run_in_background, model: requestedModel, provider: requestedProvider, effort: requestedEffort, } = input;
405
+ if (!requestedModel && (requestedProvider || requestedEffort))
406
+ throw new Error('Supply model when selecting a child provider or effort.');
407
+ if (requestedModel && !opts.resolveModel)
408
+ throw new Error('Child model selection is unavailable in this host.');
409
+ const selection = requestedModel
410
+ ? await opts.resolveModel?.({ model: requestedModel, provider: requestedProvider, effort: requestedEffort }, context.abortSignal)
411
+ : undefined;
374
412
  const explore = subagent_type === EXPLORE_SUBAGENT;
375
413
  const fileAgent = subagent_type !== undefined ? fileAgents.get(subagent_type) : undefined;
376
414
  let agentId = fileAgent?.name ?? (explore ? EXPLORE_SUBAGENT : GENERAL_PURPOSE_SUBAGENT);
377
415
  const persona = typeof role === 'string' ? role.trim() : '';
378
- const dynamic = persona.length > 0;
416
+ const dynamic = persona.length > 0 || selection !== undefined;
379
417
  if (dynamic) {
380
418
  // A role on top of a type keeps that type's roster and model: the
381
419
  // persona says who the child is, the type says what it may touch,
@@ -383,11 +421,13 @@ export async function createSubagentRuntime(opts) {
383
421
  // tool. Over a file-defined agent the role is appended to the
384
422
  // file's prompt rather than replacing it.
385
423
  agentId = `dyn-${++dynCounter}`;
386
- registry.register(buildDefinition(agentId, `Dynamic specialist: ${agentId}`, fileAgent ? `${fileAgent.prompt}\n\n${persona}` : persona, opts, fileAgent
424
+ registry.register(buildDefinition(agentId, `Dynamic specialist: ${agentId}`, fileAgent
425
+ ? `${fileAgent.prompt}\n\n${persona}`
426
+ : persona || (explore ? EXPLORE_PROMPT : SUBAGENT_PROMPT), opts, fileAgent
387
427
  ? fileAgentTools(fileAgent, opts)
388
428
  : explore
389
429
  ? () => filterReadOnlyTools(opts.buildTools())
390
- : opts.buildTools, fileAgent?.model ?? opts.model));
430
+ : opts.buildTools, selection?.model ?? fileAgent?.model ?? opts.model, selection));
391
431
  }
392
432
  const tracker = activity.begin({
393
433
  agentId,
@@ -408,6 +448,8 @@ export async function createSubagentRuntime(opts) {
408
448
  // longer be proved.
409
449
  const resumeHandler = opts.resolveResumeHandler?.(context.runId) ?? refuseUnownedChildReview;
410
450
  const configOverrides = {
451
+ tokenBudget: opts.tokenBudget ?? 0,
452
+ ...(selection ? { model: selection.model, effort: selection.effort } : {}),
411
453
  ...(Object.keys(context.env ?? {}).length > 0 ? { env: context.env } : {}),
412
454
  resumeHandler,
413
455
  };
@@ -418,15 +460,33 @@ export async function createSubagentRuntime(opts) {
418
460
  };
419
461
  try {
420
462
  const completionInbox = await completionInboxForRun(context.runId);
463
+ const parent = await resolveParent(context.runId);
464
+ const history = opts.historyRoot
465
+ ? new DelegationHistory(opts.historyRoot, parent.sessionId)
466
+ : undefined;
467
+ const save = (handle, terminal) => {
468
+ history?.write({
469
+ taskId: handle.taskId,
470
+ parentRunId: context.runId,
471
+ description,
472
+ status: terminal ? agentTaskOutcome(handle) : 'unresolved',
473
+ ...(terminal ? { output: String(completedAgentResult(handle).output ?? '') } : {}),
474
+ });
475
+ };
421
476
  const outcome = await runBlockingAgentTask({
422
477
  gateway: await gatewayForRun(context.runId),
423
478
  signal: context.abortSignal,
424
479
  waitForInbound: opts.resolveWaitForInbound?.(context.runId),
480
+ background: run_in_background === true,
425
481
  completionInbox,
426
- onCreated: () => {
482
+ onCreated: (handle) => {
483
+ save(handle, false);
427
484
  taskOwnsCleanup = true;
428
485
  },
429
- onSettled: (completed) => tracker.settle(completed),
486
+ onSettled: (completed) => {
487
+ save(completed, true);
488
+ tracker.settle(completed);
489
+ },
430
490
  onFailed: (error) => tracker.fail(error),
431
491
  onFinished: cleanupDefinition,
432
492
  create: {
@@ -444,13 +504,14 @@ export async function createSubagentRuntime(opts) {
444
504
  },
445
505
  });
446
506
  if (outcome.kind === 'yielded') {
507
+ const progress = outcome.handle.state === 'pending' ? 'queued for an available slot' : 'still running';
447
508
  return {
448
509
  success: true,
449
- output: `Sub-agent ${agentId} is still running as task ${outcome.handle.taskId}; it has not completed. Waiting was released because the operator sent a message. Respond to that message while this task continues. Its actual result will arrive as a task notification; do not launch the same work again.`,
510
+ output: `Sub-agent ${agentId} for task ${JSON.stringify(description)} is ${progress} as task ${outcome.handle.taskId}; it has not completed. ${run_in_background ? 'Continue independent work while this task runs in the background.' : 'Waiting was released because the operator sent a message. Answer their question or status request before making further tool calls; preserve existing work unless they ask to cancel or change it.'} Its actual result will arrive as a task notification; do not launch the same work again.`,
450
511
  data: {
451
512
  task_id: outcome.handle.taskId,
452
513
  state: outcome.handle.state,
453
- wait_released: 'operator_input',
514
+ wait_released: run_in_background ? 'background' : 'operator_input',
454
515
  },
455
516
  };
456
517
  }
@@ -468,6 +529,57 @@ export async function createSubagentRuntime(opts) {
468
529
  }
469
530
  },
470
531
  });
532
+ const agentTaskListTool = defineTool({
533
+ name: 'agent_task_list',
534
+ description: 'List the agent invocations launched by this run and their current status, without waiting or starting work. Use this for agent progress questions. task_list contains planning items, not agent invocations. Use wait_for_task with a live ID for its result. Set history: true to inspect saved receipts from this conversation, optionally task_id for one saved result; archives do not prove liveness.',
535
+ inputSchema: mcpJsonSchemaToZod({
536
+ type: 'object',
537
+ properties: { history: { type: 'boolean' }, task_id: { type: 'string' } },
538
+ additionalProperties: false,
539
+ }),
540
+ category: 'custom',
541
+ permissions: [],
542
+ readOnly: true,
543
+ destructive: false,
544
+ concurrencySafe: true,
545
+ async execute(input, context) {
546
+ const request = input;
547
+ if (request.history || request.task_id) {
548
+ if (!opts.historyRoot)
549
+ return { success: false, output: 'Saved delegation history is unavailable in this host.' };
550
+ const parent = await resolveParent(context.runId);
551
+ const history = new DelegationHistory(opts.historyRoot, parent.sessionId);
552
+ const saved = request.task_id
553
+ ? { tasks: [history.read(request.task_id)], omitted: 0 }
554
+ : history.list();
555
+ const tasks = saved.tasks.map(({ output, ...row }) => request.task_id ? { ...row, output } : row);
556
+ return {
557
+ success: true,
558
+ output: JSON.stringify({ ...saved, tasks, guidance: HISTORY_GUIDANCE }),
559
+ };
560
+ }
561
+ const gateway = await gatewayForRun(context.runId);
562
+ const tasks = gateway.listTasks();
563
+ const labels = new Map(activity.getSnapshot().map((entry) => [entry.taskId, entry.description]));
564
+ const shown = tasks.slice(-40).map((task) => ({
565
+ description: labels.get(task.taskId) ?? task.agentId,
566
+ task_id: task.taskId,
567
+ agent: task.agentId,
568
+ state: task.state,
569
+ status: agentTaskOutcome(task),
570
+ ...(task.result?.stopReason ? { stop_reason: task.result.stopReason } : {}),
571
+ }));
572
+ return {
573
+ success: true,
574
+ output: JSON.stringify({
575
+ tasks: shown,
576
+ total: tasks.length,
577
+ omitted: tasks.length - shown.length,
578
+ }),
579
+ data: { tasks: shown, total: tasks.length, omitted: tasks.length - shown.length },
580
+ };
581
+ },
582
+ });
471
583
  const waitForTaskTool = defineTool({
472
584
  name: 'wait_for_task',
473
585
  description: 'Wait for a task this run already launched, or retrieve its complete result after a task notification. This does not start new work. Operator input releases the wait while the task continues.',
@@ -476,7 +588,7 @@ export async function createSubagentRuntime(opts) {
476
588
  properties: {
477
589
  task_id: {
478
590
  type: 'string',
479
- description: 'The task ID returned by Agent or a task notification.',
591
+ description: 'The task_id from the Agent launch receipt or task notification metadata.',
480
592
  },
481
593
  },
482
594
  required: ['task_id'],
@@ -520,13 +632,109 @@ export async function createSubagentRuntime(opts) {
520
632
  });
521
633
  if (outcome.kind === 'completed')
522
634
  return completedAgentResult(outcome.handle);
635
+ const progress = outcome.handle.state === 'pending' ? 'queued for an available slot' : 'still running';
523
636
  return {
524
637
  success: true,
525
- output: `Task ${taskId} is still running; it has not completed. Waiting was released for an operator message. Its result will arrive as a task notification.`,
638
+ output: `Task ${taskId} is ${progress}; it has not completed. Waiting was released for an operator message. Its result will arrive as a task notification.`,
526
639
  data: { task_id: taskId, state: outcome.handle.state, wait_released: 'operator_input' },
527
640
  };
528
641
  },
529
642
  });
643
+ const sendMessageTool = defineTool({
644
+ name: 'send_message',
645
+ description: 'Queue a correction or additional context for a running or queued sub-agent task owned by this run. The child receives it at its next request boundary; acceptance is not delivery. This does not start new work or restart finished tasks.',
646
+ inputSchema: mcpJsonSchemaToZod({
647
+ type: 'object',
648
+ properties: {
649
+ task_id: { type: 'string', description: 'Task UUID returned by Agent.' },
650
+ message: {
651
+ type: 'string',
652
+ minLength: 1,
653
+ maxLength: 16000,
654
+ description: 'Correction or additional context for this task.',
655
+ },
656
+ },
657
+ required: ['task_id', 'message'],
658
+ additionalProperties: false,
659
+ }),
660
+ category: 'custom',
661
+ permissions: [],
662
+ readOnly: false,
663
+ destructive: false,
664
+ concurrencySafe: false,
665
+ async execute(input, context) {
666
+ context.abortSignal.throwIfAborted();
667
+ const { task_id, message } = input;
668
+ if (!message.trim())
669
+ return { success: false, output: '', error: 'Message must not be blank.' };
670
+ const taskId = asTaskId(task_id);
671
+ const gateway = await gatewayForRun(context.runId);
672
+ const task = gateway.getTask(taskId);
673
+ if (!task)
674
+ return {
675
+ success: false,
676
+ output: '',
677
+ error: `Task ${taskId} does not belong to this parent run.`,
678
+ };
679
+ if (isTerminalAgentTaskState(task.state))
680
+ return {
681
+ success: false,
682
+ output: '',
683
+ error: 'This task has finished; send_message cannot restart it.',
684
+ };
685
+ context.abortSignal.throwIfAborted();
686
+ await gateway.continueTask(taskId, message);
687
+ return {
688
+ success: true,
689
+ output: `Message queued for task ${taskId}; it will be available at the child's next request boundary.`,
690
+ data: { task_id: taskId, status: 'queued' },
691
+ };
692
+ },
693
+ });
694
+ const cancelAgentTool = defineTool({
695
+ name: 'cancel_agent',
696
+ description: 'Request cancellation of one running or queued agent task owned by this run. Other agents and the parent continue. Acceptance is not proof of termination; check agent_task_list or wait_for_task for the terminal outcome. Does not restart finished tasks.',
697
+ inputSchema: mcpJsonSchemaToZod({
698
+ type: 'object',
699
+ properties: {
700
+ task_id: {
701
+ type: 'string',
702
+ description: 'Exact task UUID returned by Agent or agent_task_list.',
703
+ },
704
+ },
705
+ required: ['task_id'],
706
+ additionalProperties: false,
707
+ }),
708
+ category: 'custom',
709
+ permissions: [],
710
+ readOnly: false,
711
+ destructive: false,
712
+ concurrencySafe: false,
713
+ async execute(input, context) {
714
+ context.abortSignal.throwIfAborted();
715
+ const taskId = asTaskId(input.task_id);
716
+ const gateway = await gatewayForRun(context.runId);
717
+ const task = gateway.getTask(taskId);
718
+ if (!task)
719
+ return {
720
+ success: false,
721
+ output: '',
722
+ error: `Task ${taskId} does not belong to this parent run.`,
723
+ };
724
+ if (isTerminalAgentTaskState(task.state))
725
+ return {
726
+ success: true,
727
+ output: `Task ${taskId} already ended: ${agentTaskOutcome(task)}. No cancellation sent.`,
728
+ };
729
+ context.abortSignal.throwIfAborted();
730
+ gateway.cancelTask(taskId, 'user');
731
+ return {
732
+ success: true,
733
+ output: `Cancellation requested for task ${taskId}. Other tasks continue. Use agent_task_list or wait_for_task to confirm its terminal outcome.`,
734
+ data: { task_id: taskId, status: 'cancellation_requested' },
735
+ };
736
+ },
737
+ });
530
738
  let closePromise;
531
739
  const close = () => {
532
740
  if (closePromise)
@@ -535,12 +743,40 @@ export async function createSubagentRuntime(opts) {
535
743
  closePromise = Promise.all([...parents.keys()].map(releaseRun)).then(() => activity.close());
536
744
  return closePromise;
537
745
  };
746
+ const listModels = opts.listModels;
747
+ const modelCatalogueTool = listModels
748
+ ? defineTool({
749
+ name: 'agent_models',
750
+ description: 'Discover connected provider/model IDs and published capabilities for delegation. Search here before choosing a child model; capability metadata is not a quality ranking.',
751
+ inputSchema: mcpJsonSchemaToZod({
752
+ type: 'object',
753
+ properties: {
754
+ query: { type: 'string', description: 'Optional provider or model name filter.' },
755
+ },
756
+ }),
757
+ category: 'custom',
758
+ permissions: [],
759
+ readOnly: true,
760
+ destructive: false,
761
+ concurrencySafe: true,
762
+ async execute(input, context) {
763
+ return {
764
+ success: true,
765
+ output: await listModels(input.query ?? '', context.abortSignal),
766
+ };
767
+ },
768
+ })
769
+ : undefined;
538
770
  return {
771
+ cancelAgentTool,
772
+ modelCatalogueTool,
539
773
  gatewayForRun,
540
774
  completionInboxForRun,
541
775
  releaseRun,
542
776
  agentTool,
543
777
  waitForTaskTool,
778
+ agentTaskListTool,
779
+ sendMessageTool,
544
780
  allowedAgentIds: agentTypeIds,
545
781
  activity,
546
782
  close,
@@ -587,17 +823,32 @@ async function runBlockingAgentTask(input) {
587
823
  cancel(handle);
588
824
  signal.throwIfAborted();
589
825
  }
590
- input.onCreated();
826
+ input.onCreated(handle);
591
827
  input.completionInbox.launched(handle.taskId);
592
828
  const completion = gateway.waitForTask(handle.taskId).then((completed) => {
593
- input.onSettled(completed);
594
- input.onFinished();
829
+ try {
830
+ input.onSettled(completed);
831
+ }
832
+ finally {
833
+ input.onFinished();
834
+ }
595
835
  return { kind: 'completed', handle: completed };
596
836
  }, (error) => {
597
- input.onFailed(error);
598
- input.onFinished();
837
+ try {
838
+ input.onFailed(error);
839
+ }
840
+ finally {
841
+ input.onFinished();
842
+ }
599
843
  throw error;
600
844
  });
845
+ if (input.background && !isTerminalAgentTaskState(handle.state)) {
846
+ // The observer retains activity/definition cleanup after this invocation
847
+ // returns. Parent release still owns cancellation and the tree budget.
848
+ completion.catch(() => { });
849
+ input.completionInbox.expect(handle.taskId);
850
+ return { kind: 'yielded', handle };
851
+ }
601
852
  const inbound = input
602
853
  .waitForInbound?.(wakeController.signal)
603
854
  .then(() => ({ kind: 'inbound' }));
@@ -610,7 +861,7 @@ async function runBlockingAgentTask(input) {
610
861
  // Only the wait has ended. Parent runtime ownership, token reservations and
611
862
  // the completion observer all outlive this tool invocation's local signal.
612
863
  input.completionInbox.expect(handle.taskId);
613
- return { kind: 'yielded', handle };
864
+ return { kind: 'yielded', handle: current ?? handle };
614
865
  }
615
866
  catch (error) {
616
867
  if (handle)
@@ -625,10 +876,19 @@ async function runBlockingAgentTask(input) {
625
876
  wakeController.abort();
626
877
  }
627
878
  }
879
+ function agentTaskOutcome(task) {
880
+ const run = task.result;
881
+ if (run?.status && run.status !== 'completed')
882
+ return run.status;
883
+ if (task.state === 'completed' && run?.stopReason && run.stopReason !== 'end_turn')
884
+ return 'incomplete';
885
+ return task.state;
886
+ }
628
887
  /** Lifecycle completion is not proof the requested task finished successfully. */
629
888
  function completedAgentResult(completed) {
630
889
  const run = completed.result;
631
- const succeeded = completed.state === 'completed' && (run?.status === undefined || run.status === 'completed');
890
+ const status = agentTaskOutcome(completed);
891
+ const succeeded = status === 'completed';
632
892
  const value = run?.structuredOutput ?? run?.result;
633
893
  let resultText = typeof value === 'string' ? value : value === undefined ? '' : JSON.stringify(value);
634
894
  if (!resultText && run?.stopReason && run.stopReason !== 'end_turn') {
@@ -645,19 +905,22 @@ function completedAgentResult(completed) {
645
905
  const stopNote = run?.stopReason && run.stopReason !== 'end_turn'
646
906
  ? `Sub-agent run ended with stop reason "${run.stopReason}". Output may be partial; this does not establish task completion.\n\n`
647
907
  : '';
648
- const output = stopNote + (resultText || '(sub-agent returned no text)');
908
+ // ToolResult.data is host metadata, not necessarily model-visible content.
909
+ // Keep the handle and terminal status separate from arbitrary child output
910
+ // (which may itself contain UUIDs or text such as "Task 1").
911
+ const output = `task_id: ${completed.taskId}\nstatus: ${status}\n\nAgent result:\n${stopNote}${resultText || '(sub-agent returned no text)'}`;
649
912
  return {
650
913
  success: succeeded,
651
914
  output,
652
915
  ...(!succeeded
653
916
  ? {
654
- error: `Sub-agent ${completed.agentId} ${completed.state}: ${run?.lastError ?? ''}\n${output}`,
917
+ error: `Sub-agent ${completed.agentId} ${status}: ${run?.lastError ?? ''}\n${output}`,
655
918
  }
656
919
  : {}),
657
920
  data: {
658
921
  task_id: completed.taskId,
659
922
  state: completed.state,
660
- ...(run?.status ? { status: run.status } : {}),
923
+ status,
661
924
  ...(run?.stopReason ? { stop_reason: run.stopReason } : {}),
662
925
  },
663
926
  };
@@ -671,7 +934,7 @@ function buildDefinition(id, description, systemPrompt, opts,
671
934
  /** This definition's own roster; absent means the parent's working set. */
672
935
  tools = opts.buildTools,
673
936
  /** This definition's model; absent means the session's. */
674
- model = opts.model) {
937
+ model = opts.model, selection) {
675
938
  const agent = new ReactiveAgent({
676
939
  id,
677
940
  name: id,
@@ -691,7 +954,7 @@ model = opts.model) {
691
954
  category: 'general',
692
955
  description,
693
956
  tools: [],
694
- defaults: { model, tokenBudget: 200_000 },
957
+ defaults: { model, tokenBudget: opts.tokenBudget ?? 0 },
695
958
  },
696
959
  // ReactiveAgent is Agent<ReactiveAgentConfig,…>; the registry stores the
697
960
  // erased Agent<BaseAgentConfig,…>. configBuilder supplies the richer config.
@@ -702,12 +965,19 @@ model = opts.model) {
702
965
  // since the parent started, and a sub-agent asserting the stale answer
703
966
  // is worse than one that was never told.
704
967
  const environment = opts.readEnvironment ? await opts.readEnvironment() : null;
968
+ // AgentManager supplies the actual parent run before it stamps the child's
969
+ // own Session ID. All delegated work shares that invoking conversation's
970
+ // upstream billing session, even after the TUI moves to another one.
971
+ const parent = options.parentRunId
972
+ ? await opts.resolveParent(asRunId(options.parentRunId))
973
+ : undefined;
705
974
  return {
706
975
  model: options.model ?? model,
707
- tokenBudget: options.tokenBudget ?? 200_000,
976
+ tokenBudget: options.tokenBudget ?? opts.tokenBudget ?? 0,
708
977
  timeoutMs: options.timeoutMs ?? CLI_INTERACTIVE_RUN_TIMEOUT_MS,
709
978
  maxIterations: 40,
710
- provider: opts.buildProvider(),
979
+ provider: await opts.buildProvider(parent?.sessionId, selection),
980
+ ...(selection?.effort ? { effort: selection.effort } : {}),
711
981
  tools: tools(),
712
982
  systemPrompt: environment ? `${base}\n\n${environment}` : base,
713
983
  ...(opts.projectInstructionContext