@xrkseek/harness 0.7.6 → 0.7.8

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 (171) hide show
  1. package/node_modules/@xrkseek/a2a/dist/inbound.d.ts.map +1 -1
  2. package/node_modules/@xrkseek/a2a/dist/inbound.js +72 -2
  3. package/node_modules/@xrkseek/a2a/dist/inbound.js.map +1 -1
  4. package/node_modules/@xrkseek/core-tools/dist/subagent-prompt.d.ts.map +1 -1
  5. package/node_modules/@xrkseek/core-tools/dist/subagent-prompt.js +2 -0
  6. package/node_modules/@xrkseek/core-tools/dist/subagent-prompt.js.map +1 -1
  7. package/node_modules/@xrkseek/exec-voice/README.md +4 -3
  8. package/node_modules/@xrkseek/exec-voice/dist/browser/index.d.ts +1 -0
  9. package/node_modules/@xrkseek/exec-voice/dist/browser/index.d.ts.map +1 -1
  10. package/node_modules/@xrkseek/exec-voice/dist/browser/index.js +1 -0
  11. package/node_modules/@xrkseek/exec-voice/dist/browser/index.js.map +1 -1
  12. package/node_modules/@xrkseek/exec-voice/dist/browser/wav-capture.d.ts +91 -0
  13. package/node_modules/@xrkseek/exec-voice/dist/browser/wav-capture.d.ts.map +1 -0
  14. package/node_modules/@xrkseek/exec-voice/dist/browser/wav-capture.js +269 -0
  15. package/node_modules/@xrkseek/exec-voice/dist/browser/wav-capture.js.map +1 -0
  16. package/node_modules/@xrkseek/exec-voice/dist/download-http.d.ts +46 -0
  17. package/node_modules/@xrkseek/exec-voice/dist/download-http.d.ts.map +1 -0
  18. package/node_modules/@xrkseek/exec-voice/dist/download-http.js +223 -0
  19. package/node_modules/@xrkseek/exec-voice/dist/download-http.js.map +1 -0
  20. package/node_modules/@xrkseek/exec-voice/dist/engine-ids.d.ts +19 -0
  21. package/node_modules/@xrkseek/exec-voice/dist/engine-ids.d.ts.map +1 -0
  22. package/node_modules/@xrkseek/exec-voice/dist/engine-ids.js +36 -0
  23. package/node_modules/@xrkseek/exec-voice/dist/engine-ids.js.map +1 -0
  24. package/node_modules/@xrkseek/exec-voice/dist/engines.d.ts +60 -0
  25. package/node_modules/@xrkseek/exec-voice/dist/engines.d.ts.map +1 -0
  26. package/node_modules/@xrkseek/exec-voice/dist/engines.js +130 -0
  27. package/node_modules/@xrkseek/exec-voice/dist/engines.js.map +1 -0
  28. package/node_modules/@xrkseek/exec-voice/dist/format.d.ts +13 -0
  29. package/node_modules/@xrkseek/exec-voice/dist/format.d.ts.map +1 -1
  30. package/node_modules/@xrkseek/exec-voice/dist/format.js +22 -3
  31. package/node_modules/@xrkseek/exec-voice/dist/format.js.map +1 -1
  32. package/node_modules/@xrkseek/exec-voice/dist/index.d.ts +23 -31
  33. package/node_modules/@xrkseek/exec-voice/dist/index.d.ts.map +1 -1
  34. package/node_modules/@xrkseek/exec-voice/dist/index.js +17 -66
  35. package/node_modules/@xrkseek/exec-voice/dist/index.js.map +1 -1
  36. package/node_modules/@xrkseek/exec-voice/dist/readiness.d.ts +14 -8
  37. package/node_modules/@xrkseek/exec-voice/dist/readiness.d.ts.map +1 -1
  38. package/node_modules/@xrkseek/exec-voice/dist/readiness.js +36 -27
  39. package/node_modules/@xrkseek/exec-voice/dist/readiness.js.map +1 -1
  40. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-assets.d.ts +48 -0
  41. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-assets.d.ts.map +1 -0
  42. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-assets.js +64 -0
  43. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-assets.js.map +1 -0
  44. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-local.d.ts +24 -0
  45. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-local.d.ts.map +1 -1
  46. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-local.js +66 -4
  47. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-local.js.map +1 -1
  48. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-runtime.d.ts +78 -0
  49. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-runtime.d.ts.map +1 -0
  50. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-runtime.js +332 -0
  51. package/node_modules/@xrkseek/exec-voice/dist/sensevoice-runtime.js.map +1 -0
  52. package/node_modules/@xrkseek/exec-voice/dist/system-tts.d.ts +40 -0
  53. package/node_modules/@xrkseek/exec-voice/dist/system-tts.d.ts.map +1 -0
  54. package/node_modules/@xrkseek/exec-voice/dist/system-tts.js +260 -0
  55. package/node_modules/@xrkseek/exec-voice/dist/system-tts.js.map +1 -0
  56. package/node_modules/@xrkseek/exec-voice/dist/tools.d.ts +13 -12
  57. package/node_modules/@xrkseek/exec-voice/dist/tools.d.ts.map +1 -1
  58. package/node_modules/@xrkseek/exec-voice/dist/tools.js +12 -6
  59. package/node_modules/@xrkseek/exec-voice/dist/tools.js.map +1 -1
  60. package/node_modules/@xrkseek/exec-voice/dist/types.d.ts +3 -1
  61. package/node_modules/@xrkseek/exec-voice/dist/types.d.ts.map +1 -1
  62. package/node_modules/@xrkseek/exec-voice/dist/types.js.map +1 -1
  63. package/node_modules/@xrkseek/exec-voice/package.json +39 -31
  64. package/node_modules/@xrkseek/preset-harness/dist/preset.d.ts +4 -3
  65. package/node_modules/@xrkseek/preset-harness/dist/preset.d.ts.map +1 -1
  66. package/node_modules/@xrkseek/preset-harness/dist/preset.js +31 -18
  67. package/node_modules/@xrkseek/preset-harness/dist/preset.js.map +1 -1
  68. package/node_modules/@xrkseek/preset-harness/dist/tsconfig.tsbuildinfo +1 -1
  69. package/node_modules/@xrkseek/preset-minimal/dist/tsconfig.tsbuildinfo +1 -1
  70. package/node_modules/@xrkseek/preset-server/dist/tsconfig.tsbuildinfo +1 -1
  71. package/node_modules/@xrkseek/protocol/dist/sidebar-previews.d.ts +2 -0
  72. package/node_modules/@xrkseek/protocol/dist/sidebar-previews.d.ts.map +1 -1
  73. package/node_modules/@xrkseek/protocol/dist/sidebar-previews.js.map +1 -1
  74. package/node_modules/@xrkseek/server-face/dist/adapt/subagent-notice.d.ts +21 -2
  75. package/node_modules/@xrkseek/server-face/dist/adapt/subagent-notice.d.ts.map +1 -1
  76. package/node_modules/@xrkseek/server-face/dist/adapt/subagent-notice.js +56 -7
  77. package/node_modules/@xrkseek/server-face/dist/adapt/subagent-notice.js.map +1 -1
  78. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.d.ts.map +1 -1
  79. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.js +26 -3
  80. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.js.map +1 -1
  81. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.d.ts.map +1 -1
  82. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.js +4 -0
  83. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.js.map +1 -1
  84. package/node_modules/@xrkseek/server-face/dist/dispatch.d.ts.map +1 -1
  85. package/node_modules/@xrkseek/server-face/dist/dispatch.js +16 -0
  86. package/node_modules/@xrkseek/server-face/dist/dispatch.js.map +1 -1
  87. package/node_modules/@xrkseek/server-face/dist/handlers/voice.d.ts +30 -0
  88. package/node_modules/@xrkseek/server-face/dist/handlers/voice.d.ts.map +1 -0
  89. package/node_modules/@xrkseek/server-face/dist/handlers/voice.js +195 -0
  90. package/node_modules/@xrkseek/server-face/dist/handlers/voice.js.map +1 -0
  91. package/node_modules/@xrkseek/server-face/dist/model-catalog.d.ts +15 -0
  92. package/node_modules/@xrkseek/server-face/dist/model-catalog.d.ts.map +1 -1
  93. package/node_modules/@xrkseek/server-face/dist/model-catalog.js +44 -10
  94. package/node_modules/@xrkseek/server-face/dist/model-catalog.js.map +1 -1
  95. package/node_modules/@xrkseek/server-face/dist/runtime.d.ts.map +1 -1
  96. package/node_modules/@xrkseek/server-face/dist/runtime.js +7 -2
  97. package/node_modules/@xrkseek/server-face/dist/runtime.js.map +1 -1
  98. package/node_modules/@xrkseek/server-face/dist/select-session-model.d.ts.map +1 -1
  99. package/node_modules/@xrkseek/server-face/dist/select-session-model.js +68 -1
  100. package/node_modules/@xrkseek/server-face/dist/select-session-model.js.map +1 -1
  101. package/node_modules/@xrkseek/server-face/dist/session-posture.js +1 -1
  102. package/node_modules/@xrkseek/server-face/dist/session-posture.js.map +1 -1
  103. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.d.ts +4 -0
  104. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.d.ts.map +1 -1
  105. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.js +16 -0
  106. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.js.map +1 -1
  107. package/node_modules/@xrkseek/server-face/dist/session-tools.d.ts.map +1 -1
  108. package/node_modules/@xrkseek/server-face/dist/session-tools.js +2 -0
  109. package/node_modules/@xrkseek/server-face/dist/session-tools.js.map +1 -1
  110. package/node_modules/@xrkseek/server-face/dist/settings-document.d.ts.map +1 -1
  111. package/node_modules/@xrkseek/server-face/dist/settings-document.js +56 -1
  112. package/node_modules/@xrkseek/server-face/dist/settings-document.js.map +1 -1
  113. package/node_modules/@xrkseek/server-face/dist/settings-schemas.d.ts.map +1 -1
  114. package/node_modules/@xrkseek/server-face/dist/settings-schemas.js +42 -12
  115. package/node_modules/@xrkseek/server-face/dist/settings-schemas.js.map +1 -1
  116. package/node_modules/@xrkseek/server-face/dist/subagent-tools.d.ts +43 -0
  117. package/node_modules/@xrkseek/server-face/dist/subagent-tools.d.ts.map +1 -1
  118. package/node_modules/@xrkseek/server-face/dist/subagent-tools.js +293 -24
  119. package/node_modules/@xrkseek/server-face/dist/subagent-tools.js.map +1 -1
  120. package/node_modules/@xrkseek/server-face/dist/wire/paths.d.ts.map +1 -1
  121. package/node_modules/@xrkseek/server-face/dist/wire/paths.js +6 -0
  122. package/node_modules/@xrkseek/server-face/dist/wire/paths.js.map +1 -1
  123. package/node_modules/@xrkseek/server-face/dist/workspace-orchestrate-tools.d.ts.map +1 -1
  124. package/node_modules/@xrkseek/server-face/dist/workspace-orchestrate-tools.js +4 -1
  125. package/node_modules/@xrkseek/server-face/dist/workspace-orchestrate-tools.js.map +1 -1
  126. package/node_modules/@xrkseek/server-face/package.json +1 -0
  127. package/node_modules/@xrkseek/server-host/dist/a2a-inbound-public.d.ts.map +1 -1
  128. package/node_modules/@xrkseek/server-host/dist/a2a-inbound-public.js +8 -1
  129. package/node_modules/@xrkseek/server-host/dist/a2a-inbound-public.js.map +1 -1
  130. package/node_modules/@xrkseek/server-host/dist/index.d.ts +1 -1
  131. package/node_modules/@xrkseek/server-host/dist/index.d.ts.map +1 -1
  132. package/node_modules/@xrkseek/server-host/dist/index.js +44 -45
  133. package/node_modules/@xrkseek/server-host/dist/index.js.map +1 -1
  134. package/node_modules/@xrkseek/server-host/dist/sidebar-face-bridge.d.ts.map +1 -1
  135. package/node_modules/@xrkseek/server-host/dist/sidebar-face-bridge.js +2 -0
  136. package/node_modules/@xrkseek/server-host/dist/sidebar-face-bridge.js.map +1 -1
  137. package/node_modules/@xrkseek/server-http/dist/dsh-compat/auto-review-tool-pre.d.ts.map +1 -1
  138. package/node_modules/@xrkseek/server-http/dist/dsh-compat/auto-review-tool-pre.js +5 -2
  139. package/node_modules/@xrkseek/server-http/dist/dsh-compat/auto-review-tool-pre.js.map +1 -1
  140. package/node_modules/@xrkseek/server-http/dist/dsh-compat/our-free-model-host.d.ts +15 -0
  141. package/node_modules/@xrkseek/server-http/dist/dsh-compat/our-free-model-host.d.ts.map +1 -1
  142. package/node_modules/@xrkseek/server-http/dist/dsh-compat/our-free-model-host.js +61 -3
  143. package/node_modules/@xrkseek/server-http/dist/dsh-compat/our-free-model-host.js.map +1 -1
  144. package/node_modules/sherpa-onnx-node/README.md +11 -0
  145. package/node_modules/sherpa-onnx-node/addon-static-import.js +76 -0
  146. package/node_modules/sherpa-onnx-node/addon.js +98 -0
  147. package/node_modules/sherpa-onnx-node/audio-tagg.js +45 -0
  148. package/node_modules/sherpa-onnx-node/keyword-spotter.js +68 -0
  149. package/node_modules/sherpa-onnx-node/non-streaming-asr.js +158 -0
  150. package/node_modules/sherpa-onnx-node/non-streaming-speaker-diarization.js +38 -0
  151. package/node_modules/sherpa-onnx-node/non-streaming-speech-denoiser.js +32 -0
  152. package/node_modules/sherpa-onnx-node/non-streaming-tts.js +117 -0
  153. package/node_modules/sherpa-onnx-node/online-speech-denoiser.js +46 -0
  154. package/node_modules/sherpa-onnx-node/package.json +68 -0
  155. package/node_modules/sherpa-onnx-node/punctuation.js +43 -0
  156. package/node_modules/sherpa-onnx-node/resampler.js +80 -0
  157. package/node_modules/sherpa-onnx-node/sherpa-onnx.js +49 -0
  158. package/node_modules/sherpa-onnx-node/speaker-identification.js +152 -0
  159. package/node_modules/sherpa-onnx-node/spoken-language-identification.js +38 -0
  160. package/node_modules/sherpa-onnx-node/streaming-asr.js +181 -0
  161. package/node_modules/sherpa-onnx-node/types.js +833 -0
  162. package/node_modules/sherpa-onnx-node/vad.js +134 -0
  163. package/node_modules/sherpa-onnx-win-x64/README.md +3 -0
  164. package/node_modules/sherpa-onnx-win-x64/index.js +1 -0
  165. package/node_modules/sherpa-onnx-win-x64/onnxruntime.dll +0 -0
  166. package/node_modules/sherpa-onnx-win-x64/onnxruntime_providers_shared.dll +0 -0
  167. package/node_modules/sherpa-onnx-win-x64/package.json +65 -0
  168. package/node_modules/sherpa-onnx-win-x64/sherpa-onnx-c-api.dll +0 -0
  169. package/node_modules/sherpa-onnx-win-x64/sherpa-onnx-cxx-api.dll +0 -0
  170. package/node_modules/sherpa-onnx-win-x64/sherpa-onnx.node +0 -0
  171. package/package.json +5 -1
@@ -15,12 +15,12 @@ import { ensureSessionWorkingDirectory, resolveSessionCwd, } from "./session-cwd
15
15
  import { canvasWorkspaceIdForSession } from "./canvas-tools.js";
16
16
  import { refuseCrossAgentHomeLane } from "./agent-home-boundary.js";
17
17
  import { orchestratorSurface } from "./session-kind.js";
18
- import { closeAgentDescription, followupTaskDescription, interruptAgentDescription, listAgentsDescription, liveRosterCatalog, sendMessageDescription, sessionToolContext, subagentDescription, teamGraphDescription, waitAgentDescription, } from "./session-tool-copy.js";
18
+ import { closeAgentDescription, followupTaskDescription, interruptAgentDescription, listAgentsDescription, liveRosterCatalog, listModelsDescription, sendMessageDescription, sessionToolContext, setAgentModelDescription, subagentDescription, teamGraphDescription, waitAgentDescription, } from "./session-tool-copy.js";
19
19
  import { IM_WORKSPACE_ID } from "./im-workspace.js";
20
20
  import { resolveDefaultAgentPreset } from "./settings-document.js";
21
21
  import { ExternalAgentError, parseExternalAgentKind, parseExternalAgentProduct, runExternalAgentTurn, supportsExternalContinuable, startExternalContinuable, isChildSessionActive, } from "./external-agent-runtime.js";
22
22
  import { createSubagentWorktree, finalizeSubagentWorktree, formatWorktreeResult, worktreeContextNote, worktreeSkippedForRemote, } from "./subagent-worktree.js";
23
- import { resolveSessionModelSelection } from "./model-catalog.js";
23
+ import { buildFaceModelCatalog, resolveSessionModelSelection, } from "./model-catalog.js";
24
24
  import { modelSessionCapProblem, resolveModelSessionQuota, } from "./model-session-cap.js";
25
25
  import { selectSessionModel } from "./select-session-model.js";
26
26
  import { appendOutputContract, buildOutputSchemaRetryMessage, coerceOutputSchema, validateOutputAgainstSchema, } from "./agent-team-output.js";
@@ -200,20 +200,67 @@ function taskIdConflict(runtime, taskIdHint) {
200
200
  ? `subagent: task_id ${taskIdHint} is already bound to child ${boundChild}; use a different task_id`
201
201
  : undefined;
202
202
  }
203
+ /** `provider/model · effort x` for tool receipts. */
204
+ function routeLabel(selection) {
205
+ const route = `${selection.provider}/${selection.model}`;
206
+ return selection.reasoningEffort
207
+ ? `${route} · effort ${selection.reasoningEffort}`
208
+ : route;
209
+ }
210
+ /**
211
+ * Receipt line for send_message / followup_task / set_agent_model: what the
212
+ * child is **actually** on after the pin, not what was merely requested.
213
+ */
214
+ export function formatPinnedRouteReceipt(childSessionId, pin) {
215
+ const routeMoved = pin.before.provider !== pin.after.provider ||
216
+ pin.before.model !== pin.after.model;
217
+ if (routeMoved) {
218
+ return `route: ${pin.before.provider}/${pin.before.model} → ${routeLabel(pin.after)}`;
219
+ }
220
+ if (pin.changed) {
221
+ // Effort-only switch: the route stays, the thinking level moves.
222
+ const from = pin.before.reasoningEffort ?? "default";
223
+ const to = pin.after.reasoningEffort ?? "default";
224
+ return `route: ${pin.after.provider}/${pin.after.model} (unchanged) · effort ${from} → ${to}`;
225
+ }
226
+ return `route: ${childSessionId} already on ${routeLabel(pin.after)} (unchanged)`;
227
+ }
203
228
  /**
204
- * Optional LLM target for a freshly created child.
229
+ * Pin an **existing** child onto `provider/model` + `reasoning_effort`.
205
230
  *
206
- * Precedence: the call's own `provider` / `model` / `reasoning_effort` →
207
- * the parent's route (copied at `session.create`). `model` alone inherits the
208
- * parent's current provider; `provider` alone is rejected.
231
+ * Hot switch: `createSessionRoutingLlm` re-reads `sessionModels` on every
232
+ * model request, so a running child picks the new route on its next request
233
+ * (the in-flight one is untouched — never restart a turn to switch).
234
+ *
235
+ * Authorization is the lineage link: a session may only repin a child it
236
+ * spawned. `reasoningEffort` alone keeps the child's current provider/model;
237
+ * `model` alone keeps the child's current provider. External runtimes own
238
+ * their model, so any Face-side pin there is refused rather than faked.
209
239
  */
210
- async function applySubagentModelOverride(runtime, args) {
240
+ export async function pinChildRoute(runtime, args) {
241
+ const childSessionId = args.childSessionId.trim();
242
+ if (!runtime.store.has(childSessionId)) {
243
+ return {
244
+ ok: false,
245
+ message: `subagent: child session ${childSessionId} not found`,
246
+ };
247
+ }
248
+ if (!runtime.subagents.get(args.parentSessionId, childSessionId)) {
249
+ return {
250
+ ok: false,
251
+ message: `subagent: ${childSessionId} is not your child — ` +
252
+ "only a session this one spawned can be repinned",
253
+ };
254
+ }
211
255
  const providerRaw = args.provider?.trim() ?? "";
212
256
  const modelRaw = args.model?.trim() ?? "";
213
257
  const effortRaw = args.reasoningEffort?.trim() ?? "";
258
+ const external = args.skipExternal === true ||
259
+ runtime.externalAgents.has(childSessionId) ||
260
+ runtime.externalAgents.kind(childSessionId) !== undefined;
214
261
  // Refuse loudly rather than silently: an explicit model on an external
215
262
  // child would otherwise read as honored when the subprocess ignores it.
216
- if (args.skipExternal && (providerRaw || modelRaw || effortRaw)) {
263
+ if (external && (providerRaw || modelRaw || effortRaw)) {
217
264
  return {
218
265
  ok: false,
219
266
  message: "subagent: model / provider / reasoning_effort do not apply to an external runtime " +
@@ -221,22 +268,23 @@ async function applySubagentModelOverride(runtime, args) {
221
268
  "Drop the argument, or use runtime=in-process.",
222
269
  };
223
270
  }
224
- // No per-call pin: leave the child on the parent's selection that
225
- // `session.create` already copied into sessionModels.
226
- if (!providerRaw && !modelRaw && !effortRaw)
227
- return { ok: true };
228
- const model = modelRaw || "";
229
- const effort = effortRaw || "";
230
- if (!model) {
271
+ const before = resolveSessionModelSelection(runtime, childSessionId);
272
+ // No pin requested: report the route the child is already on.
273
+ if (!providerRaw && !modelRaw && !effortRaw) {
274
+ return { ok: true, before, after: before, changed: false };
275
+ }
276
+ const provider = providerRaw || before.provider;
277
+ const model = modelRaw || before.model;
278
+ const effort = effortRaw || before.reasoningEffort || "";
279
+ if (!provider || !model) {
231
280
  return {
232
281
  ok: false,
233
- message: "subagent: model is required when overriding provider or reasoning_effort",
282
+ message: `subagent: ${childSessionId} has no pinned provider/model yet — ` +
283
+ "pass both (list_models shows routable pairs)",
234
284
  };
235
285
  }
236
- const provider = providerRaw
237
- || resolveSessionModelSelection(runtime, args.parentSessionId).provider;
238
286
  const selected = await selectSessionModel(runtime, {
239
- sessionId: args.childSessionId,
287
+ sessionId: childSessionId,
240
288
  provider,
241
289
  model,
242
290
  ...(effort ? { reasoningEffort: effort } : {}),
@@ -247,7 +295,40 @@ async function applySubagentModelOverride(runtime, args) {
247
295
  message: `subagent model: ${formatToolError(selected.error)}`,
248
296
  };
249
297
  }
250
- return { ok: true };
298
+ const after = selected.value.selected;
299
+ const changed = before.provider !== after.provider ||
300
+ before.model !== after.model ||
301
+ (before.reasoningEffort ?? "") !== (after.reasoningEffort ?? "");
302
+ return { ok: true, before, after, changed };
303
+ }
304
+ /**
305
+ * Optional LLM target for a freshly created child (thin spawn wrapper).
306
+ *
307
+ * Precedence: the call's own `provider` / `model` / `reasoning_effort` →
308
+ * the route `session.create` already copied from the parent.
309
+ */
310
+ async function applySubagentModelOverride(runtime, args) {
311
+ const pinned = await pinChildRoute(runtime, args);
312
+ return pinned.ok ? { ok: true } : { ok: false, message: pinned.message };
313
+ }
314
+ /** Spawn receipt line — the child's **actual** route, never the prediction. */
315
+ function childRouteReceipt(runtime, childSessionId) {
316
+ const selection = resolveSessionModelSelection(runtime, childSessionId);
317
+ return selection.provider && selection.model
318
+ ? `route: ${routeLabel(selection)}`
319
+ : "route: unpinned — the child resolves provider/model per request";
320
+ }
321
+ /** Only strings reach the pin; anything else stays an "argument omitted". */
322
+ function routeArgsFrom(a) {
323
+ const out = {};
324
+ if (typeof a.provider === "string")
325
+ out.provider = a.provider;
326
+ if (typeof a.model === "string")
327
+ out.model = a.model;
328
+ if (typeof a.reasoning_effort === "string") {
329
+ out.reasoningEffort = a.reasoning_effort;
330
+ }
331
+ return out;
251
332
  }
252
333
  function createSubagentTool(options) {
253
334
  const foregroundWaitMs = options.foregroundWaitMs ?? FOREGROUND_WAIT_MS;
@@ -609,6 +690,7 @@ function createSubagentTool(options) {
609
690
  (spawnRole && spawnRole !== "default"
610
691
  ? ` · role ${spawnRole}`
611
692
  : ""),
693
+ `route: external (${runtimeKind}) · child CLI owns the model`,
612
694
  childResumeHint(childId),
613
695
  ].join("\n"),
614
696
  };
@@ -625,7 +707,8 @@ function createSubagentTool(options) {
625
707
  : {}),
626
708
  });
627
709
  return {
628
- content: `[external:${result.kind}]\n${result.text}`,
710
+ content: `[external:${result.kind}]\n${result.text}\n` +
711
+ `route: external (${result.kind}) · child CLI owns the model`,
629
712
  };
630
713
  }
631
714
  catch (err) {
@@ -988,6 +1071,7 @@ function createSubagentTool(options) {
988
1071
  outputSchema
989
1072
  ? "OUTPUT CONTRACT attached; result will be schema-checked on idle."
990
1073
  : undefined,
1074
+ childRouteReceipt(options.runtime, childId),
991
1075
  childResumeHint(childId),
992
1076
  worktreeLine(true) || undefined,
993
1077
  ]
@@ -1111,7 +1195,11 @@ function createSubagentTool(options) {
1111
1195
  const extra = worktreeLine(isChildSessionActive(options.runtime, childId));
1112
1196
  if (!text) {
1113
1197
  return {
1114
- content: [`(subagent ${childId} finished with no assistant text)`, extra]
1198
+ content: [
1199
+ `(subagent ${childId} finished with no assistant text)`,
1200
+ childRouteReceipt(options.runtime, childId),
1201
+ extra,
1202
+ ]
1115
1203
  .filter(Boolean)
1116
1204
  .join("\n"),
1117
1205
  };
@@ -1125,6 +1213,7 @@ function createSubagentTool(options) {
1125
1213
  content: [
1126
1214
  boundChildAnswer(options.parentSessionId, childId, text),
1127
1215
  schemaLine,
1216
+ childRouteReceipt(options.runtime, childId),
1128
1217
  extra,
1129
1218
  ]
1130
1219
  .filter(Boolean)
@@ -1204,6 +1293,18 @@ function createSendMessageTool(options) {
1204
1293
  type: "string",
1205
1294
  description: "steer (default) or queue.",
1206
1295
  },
1296
+ provider: {
1297
+ type: "string",
1298
+ description: "Optional route repin before the prompt; defaults to the child's current provider.",
1299
+ },
1300
+ model: {
1301
+ type: "string",
1302
+ description: "Optional model repin before the prompt; needs provider unless the child is pinned.",
1303
+ },
1304
+ reasoning_effort: {
1305
+ type: "string",
1306
+ description: "Optional thinking level for the repinned route (keeps provider/model when alone).",
1307
+ },
1207
1308
  },
1208
1309
  required: ["agent_id", "message"],
1209
1310
  },
@@ -1218,6 +1319,15 @@ function createSendMessageTool(options) {
1218
1319
  };
1219
1320
  }
1220
1321
  const delivery = a.delivery === "queue" || a.delivery === "steer" ? a.delivery : "steer";
1322
+ // Repin first: a refused pin must send nothing (the caller believed the
1323
+ // child moved; delivering on the old quota is exactly the waste to avoid).
1324
+ const pin = await pinChildRoute(options.runtime, {
1325
+ parentSessionId: options.parentSessionId,
1326
+ childSessionId: childId,
1327
+ ...routeArgsFrom(a),
1328
+ });
1329
+ if (!pin.ok)
1330
+ return { content: pin.message, isError: true };
1221
1331
  const prompted = await dispatchFaceMethod(options.runtime, "subagent.prompt", `tool-sa-sm-${childId}`, {
1222
1332
  parentSessionId: options.parentSessionId,
1223
1333
  childSessionId: childId,
@@ -1234,7 +1344,12 @@ function createSendMessageTool(options) {
1234
1344
  }
1235
1345
  // Task-board resume is owned by `subagent.prompt` (the SoT for every
1236
1346
  // delivery route); calling it again here double-bumped the revision.
1237
- return { content: `sent to ${childId} (${delivery})` };
1347
+ return {
1348
+ content: [
1349
+ `sent to ${childId} (${delivery})`,
1350
+ formatPinnedRouteReceipt(childId, pin),
1351
+ ].join("\n"),
1352
+ };
1238
1353
  },
1239
1354
  };
1240
1355
  }
@@ -1446,6 +1561,18 @@ function createFollowupTaskTool(options) {
1446
1561
  type: "string",
1447
1562
  description: "New task text for the child (triggers a turn).",
1448
1563
  },
1564
+ provider: {
1565
+ type: "string",
1566
+ description: "Optional route repin before the turn; defaults to the child's current provider.",
1567
+ },
1568
+ model: {
1569
+ type: "string",
1570
+ description: "Optional model repin before the turn; needs provider unless the child is pinned.",
1571
+ },
1572
+ reasoning_effort: {
1573
+ type: "string",
1574
+ description: "Optional thinking level for the repinned route (keeps provider/model when alone).",
1575
+ },
1449
1576
  },
1450
1577
  required: ["agent_id", "message"],
1451
1578
  },
@@ -1459,6 +1586,15 @@ function createFollowupTaskTool(options) {
1459
1586
  isError: true,
1460
1587
  };
1461
1588
  }
1589
+ // Repin first: an out-of-quota child must not be woken on the dead
1590
+ // route — that is the whole "manager rescues the child" flow.
1591
+ const pin = await pinChildRoute(options.runtime, {
1592
+ parentSessionId: options.parentSessionId,
1593
+ childSessionId: childId,
1594
+ ...routeArgsFrom(a),
1595
+ });
1596
+ if (!pin.ok)
1597
+ return { content: pin.message, isError: true };
1462
1598
  const prompted = await dispatchFaceMethod(options.runtime, "subagent.prompt", `tool-sa-fu-${childId}`, {
1463
1599
  parentSessionId: options.parentSessionId,
1464
1600
  childSessionId: childId,
@@ -1473,7 +1609,138 @@ function createFollowupTaskTool(options) {
1473
1609
  isError: true,
1474
1610
  };
1475
1611
  }
1476
- return { content: `followup_task sent to ${childId} (steer)` };
1612
+ return {
1613
+ content: [
1614
+ `followup_task sent to ${childId} (steer)`,
1615
+ formatPinnedRouteReceipt(childId, pin),
1616
+ ].join("\n"),
1617
+ };
1618
+ },
1619
+ };
1620
+ }
1621
+ /** Total output lines for `list_models` — the catalog can hold hundreds of routes. */
1622
+ const LIST_MODELS_MAX_LINES = 80;
1623
+ /**
1624
+ * Parent repairs a child's route: the escape hatch when the child's model ran
1625
+ * out of quota mid-task. Hot switch — the next model request uses it.
1626
+ */
1627
+ function createSetAgentModelTool(options) {
1628
+ return {
1629
+ name: "set_agent_model",
1630
+ description: "Repin a child subagent's provider / model / reasoning effort (own children only).",
1631
+ dynamicSchema: () => ({
1632
+ description: setAgentModelDescription(),
1633
+ }),
1634
+ parameters: {
1635
+ type: "object",
1636
+ properties: {
1637
+ agent_id: {
1638
+ type: "string",
1639
+ description: "Child session id from subagent / list_agents.",
1640
+ },
1641
+ provider: {
1642
+ type: "string",
1643
+ description: "Provider id; omit to keep the child's current provider.",
1644
+ },
1645
+ model: {
1646
+ type: "string",
1647
+ description: "Model id; omit to keep the child's current model.",
1648
+ },
1649
+ reasoning_effort: {
1650
+ type: "string",
1651
+ description: "Thinking level id; omit to keep the child's current effort.",
1652
+ },
1653
+ },
1654
+ required: ["agent_id"],
1655
+ },
1656
+ async execute(args) {
1657
+ const a = args;
1658
+ const childId = String(a.agent_id ?? "").trim();
1659
+ if (!childId) {
1660
+ return { content: "set_agent_model requires agent_id", isError: true };
1661
+ }
1662
+ const route = routeArgsFrom(a);
1663
+ if (!route.provider && !route.model && !route.reasoningEffort) {
1664
+ return {
1665
+ content: "set_agent_model: pass at least one of provider / model / reasoning_effort " +
1666
+ "(list_models shows routable pairs, efforts and free slots)",
1667
+ isError: true,
1668
+ };
1669
+ }
1670
+ const pin = await pinChildRoute(options.runtime, {
1671
+ parentSessionId: options.parentSessionId,
1672
+ childSessionId: childId,
1673
+ ...route,
1674
+ });
1675
+ if (!pin.ok)
1676
+ return { content: pin.message, isError: true };
1677
+ return {
1678
+ content: [
1679
+ formatPinnedRouteReceipt(childId, pin),
1680
+ "applies to the child's next model request — a live turn is not interrupted",
1681
+ ].join("\n"),
1682
+ };
1683
+ },
1684
+ };
1685
+ }
1686
+ /** Compact routable catalog: pick a child route (+ effort) and read slot use. */
1687
+ function createListModelsTool(options) {
1688
+ return {
1689
+ name: "list_models",
1690
+ description: "List routable models with efforts and live session slots (read-only).",
1691
+ dynamicSchema: () => ({
1692
+ description: listModelsDescription(),
1693
+ }),
1694
+ parameters: { type: "object", properties: {} },
1695
+ isConcurrencySafe: () => true,
1696
+ async execute() {
1697
+ const runtime = options.runtime;
1698
+ const here = resolveSessionModelSelection(runtime, options.parentSessionId);
1699
+ const { groups } = buildFaceModelCatalog(runtime);
1700
+ const total = groups.reduce((n, group) => n + group.models.length, 0);
1701
+ const header = [
1702
+ `models ${total}`,
1703
+ ...(here.provider && here.model ? [`here ${routeLabel(here)}`] : []),
1704
+ ].join(" · ");
1705
+ if (total === 0) {
1706
+ return {
1707
+ content: `${header}\n` +
1708
+ "(no routable models — add a provider in Settings → Models, then retry)",
1709
+ };
1710
+ }
1711
+ // 1 header line is always shown; a truncated tail line ("… N more")
1712
+ // eats one more, so the whole reply never exceeds the line budget.
1713
+ const rowBudget = LIST_MODELS_MAX_LINES - 1;
1714
+ const truncated = total > rowBudget;
1715
+ const rowCap = truncated ? rowBudget - 1 : rowBudget;
1716
+ const rows = [];
1717
+ outer: for (const group of groups) {
1718
+ for (const entry of group.models) {
1719
+ if (rows.length >= rowCap)
1720
+ break outer;
1721
+ const bits = [];
1722
+ const efforts = entry.reasoning?.efforts.map((effort) => effort.id);
1723
+ if (efforts && efforts.length > 0) {
1724
+ bits.push(`efforts: ${efforts.join("|")}`);
1725
+ }
1726
+ const defaultEffort = entry.reasoning?.defaultEffort;
1727
+ if (defaultEffort)
1728
+ bits.push(`default ${defaultEffort}`);
1729
+ const quota = resolveModelSessionQuota(runtime, {
1730
+ provider: group.id,
1731
+ model: entry.id,
1732
+ });
1733
+ bits.push(`slots ${quota.using}/${quota.max}`);
1734
+ if (here.provider === group.id && here.model === entry.id) {
1735
+ bits.push("here");
1736
+ }
1737
+ rows.push(`- ${group.id}/${entry.id} ${bits.join(" ")}`);
1738
+ }
1739
+ }
1740
+ const lines = [header, ...rows];
1741
+ if (total > rows.length)
1742
+ lines.push(`… ${total - rows.length} more`);
1743
+ return { content: lines.join("\n") };
1477
1744
  },
1478
1745
  };
1479
1746
  }
@@ -1894,7 +2161,9 @@ export function bindSubagentTools(tools, options) {
1894
2161
  for (const tool of [
1895
2162
  createSubagentTool(options),
1896
2163
  createListAgentsTool(options),
2164
+ createListModelsTool(options),
1897
2165
  createSendMessageTool(options),
2166
+ createSetAgentModelTool(options),
1898
2167
  createTeamGraphTool(options),
1899
2168
  createFollowupTaskTool(options),
1900
2169
  createWaitAgentTool(options),