@moxxy/core 0.3.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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/events/factory.d.ts +6 -0
  4. package/dist/events/factory.d.ts.map +1 -0
  5. package/dist/events/factory.js +14 -0
  6. package/dist/events/factory.js.map +1 -0
  7. package/dist/events/log.d.ts +92 -0
  8. package/dist/events/log.d.ts.map +1 -0
  9. package/dist/events/log.js +244 -0
  10. package/dist/events/log.js.map +1 -0
  11. package/dist/index.d.ts +40 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +41 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/logger.d.ts +16 -0
  16. package/dist/logger.d.ts.map +1 -0
  17. package/dist/logger.js +27 -0
  18. package/dist/logger.js.map +1 -0
  19. package/dist/permissions/engine.d.ts +94 -0
  20. package/dist/permissions/engine.d.ts.map +1 -0
  21. package/dist/permissions/engine.js +203 -0
  22. package/dist/permissions/engine.js.map +1 -0
  23. package/dist/permissions/resolvers.d.ts +8 -0
  24. package/dist/permissions/resolvers.d.ts.map +1 -0
  25. package/dist/permissions/resolvers.js +8 -0
  26. package/dist/permissions/resolvers.js.map +1 -0
  27. package/dist/plugins/discovery.d.ts +9 -0
  28. package/dist/plugins/discovery.d.ts.map +1 -0
  29. package/dist/plugins/discovery.js +112 -0
  30. package/dist/plugins/discovery.js.map +1 -0
  31. package/dist/plugins/host-options.d.ts +72 -0
  32. package/dist/plugins/host-options.d.ts.map +1 -0
  33. package/dist/plugins/host-options.js +2 -0
  34. package/dist/plugins/host-options.js.map +1 -0
  35. package/dist/plugins/host.d.ts +53 -0
  36. package/dist/plugins/host.d.ts.map +1 -0
  37. package/dist/plugins/host.js +269 -0
  38. package/dist/plugins/host.js.map +1 -0
  39. package/dist/plugins/lifecycle.d.ts +31 -0
  40. package/dist/plugins/lifecycle.d.ts.map +1 -0
  41. package/dist/plugins/lifecycle.js +100 -0
  42. package/dist/plugins/lifecycle.js.map +1 -0
  43. package/dist/plugins/loader.d.ts +7 -0
  44. package/dist/plugins/loader.d.ts.map +1 -0
  45. package/dist/plugins/loader.js +68 -0
  46. package/dist/plugins/loader.js.map +1 -0
  47. package/dist/plugins/package-requirements.d.ts +16 -0
  48. package/dist/plugins/package-requirements.d.ts.map +1 -0
  49. package/dist/plugins/package-requirements.js +52 -0
  50. package/dist/plugins/package-requirements.js.map +1 -0
  51. package/dist/plugins/registry-kinds.d.ts +58 -0
  52. package/dist/plugins/registry-kinds.d.ts.map +1 -0
  53. package/dist/plugins/registry-kinds.js +139 -0
  54. package/dist/plugins/registry-kinds.js.map +1 -0
  55. package/dist/plugins/toposort.d.ts +24 -0
  56. package/dist/plugins/toposort.d.ts.map +1 -0
  57. package/dist/plugins/toposort.js +67 -0
  58. package/dist/plugins/toposort.js.map +1 -0
  59. package/dist/preferences.d.ts +40 -0
  60. package/dist/preferences.d.ts.map +1 -0
  61. package/dist/preferences.js +58 -0
  62. package/dist/preferences.js.map +1 -0
  63. package/dist/registries/active-backend-registry.d.ts +65 -0
  64. package/dist/registries/active-backend-registry.d.ts.map +1 -0
  65. package/dist/registries/active-backend-registry.js +108 -0
  66. package/dist/registries/active-backend-registry.js.map +1 -0
  67. package/dist/registries/active-def-registry.d.ts +59 -0
  68. package/dist/registries/active-def-registry.d.ts.map +1 -0
  69. package/dist/registries/active-def-registry.js +63 -0
  70. package/dist/registries/active-def-registry.js.map +1 -0
  71. package/dist/registries/agents.d.ts +16 -0
  72. package/dist/registries/agents.d.ts.map +1 -0
  73. package/dist/registries/agents.js +17 -0
  74. package/dist/registries/agents.js.map +1 -0
  75. package/dist/registries/cache-strategies.d.ts +12 -0
  76. package/dist/registries/cache-strategies.d.ts.map +1 -0
  77. package/dist/registries/cache-strategies.js +13 -0
  78. package/dist/registries/cache-strategies.js.map +1 -0
  79. package/dist/registries/channels.d.ts +16 -0
  80. package/dist/registries/channels.d.ts.map +1 -0
  81. package/dist/registries/channels.js +35 -0
  82. package/dist/registries/channels.js.map +1 -0
  83. package/dist/registries/commands.d.ts +29 -0
  84. package/dist/registries/commands.d.ts.map +1 -0
  85. package/dist/registries/commands.js +72 -0
  86. package/dist/registries/commands.js.map +1 -0
  87. package/dist/registries/compactors.d.ts +12 -0
  88. package/dist/registries/compactors.d.ts.map +1 -0
  89. package/dist/registries/compactors.js +13 -0
  90. package/dist/registries/compactors.js.map +1 -0
  91. package/dist/registries/def-map-registry.d.ts +33 -0
  92. package/dist/registries/def-map-registry.d.ts.map +1 -0
  93. package/dist/registries/def-map-registry.js +36 -0
  94. package/dist/registries/def-map-registry.js.map +1 -0
  95. package/dist/registries/embedders.d.ts +16 -0
  96. package/dist/registries/embedders.d.ts.map +1 -0
  97. package/dist/registries/embedders.js +17 -0
  98. package/dist/registries/embedders.js.map +1 -0
  99. package/dist/registries/isolators.d.ts +21 -0
  100. package/dist/registries/isolators.d.ts.map +1 -0
  101. package/dist/registries/isolators.js +32 -0
  102. package/dist/registries/isolators.js.map +1 -0
  103. package/dist/registries/modes.d.ts +28 -0
  104. package/dist/registries/modes.d.ts.map +1 -0
  105. package/dist/registries/modes.js +81 -0
  106. package/dist/registries/modes.js.map +1 -0
  107. package/dist/registries/providers.d.ts +44 -0
  108. package/dist/registries/providers.d.ts.map +1 -0
  109. package/dist/registries/providers.js +95 -0
  110. package/dist/registries/providers.js.map +1 -0
  111. package/dist/registries/skills.d.ts +24 -0
  112. package/dist/registries/skills.d.ts.map +1 -0
  113. package/dist/registries/skills.js +81 -0
  114. package/dist/registries/skills.js.map +1 -0
  115. package/dist/registries/surfaces.d.ts +13 -0
  116. package/dist/registries/surfaces.d.ts.map +1 -0
  117. package/dist/registries/surfaces.js +14 -0
  118. package/dist/registries/surfaces.js.map +1 -0
  119. package/dist/registries/synthesizers.d.ts +26 -0
  120. package/dist/registries/synthesizers.d.ts.map +1 -0
  121. package/dist/registries/synthesizers.js +34 -0
  122. package/dist/registries/synthesizers.js.map +1 -0
  123. package/dist/registries/tools.d.ts +49 -0
  124. package/dist/registries/tools.d.ts.map +1 -0
  125. package/dist/registries/tools.js +98 -0
  126. package/dist/registries/tools.js.map +1 -0
  127. package/dist/registries/transcribers.d.ts +17 -0
  128. package/dist/registries/transcribers.d.ts.map +1 -0
  129. package/dist/registries/transcribers.js +18 -0
  130. package/dist/registries/transcribers.js.map +1 -0
  131. package/dist/registries/tunnel-providers.d.ts +12 -0
  132. package/dist/registries/tunnel-providers.d.ts.map +1 -0
  133. package/dist/registries/tunnel-providers.js +13 -0
  134. package/dist/registries/tunnel-providers.js.map +1 -0
  135. package/dist/registries/view-renderers.d.ts +13 -0
  136. package/dist/registries/view-renderers.d.ts.map +1 -0
  137. package/dist/registries/view-renderers.js +14 -0
  138. package/dist/registries/view-renderers.js.map +1 -0
  139. package/dist/registries/workflow-executors.d.ts +13 -0
  140. package/dist/registries/workflow-executors.d.ts.map +1 -0
  141. package/dist/registries/workflow-executors.js +14 -0
  142. package/dist/registries/workflow-executors.js.map +1 -0
  143. package/dist/requirements.d.ts +59 -0
  144. package/dist/requirements.d.ts.map +1 -0
  145. package/dist/requirements.js +149 -0
  146. package/dist/requirements.js.map +1 -0
  147. package/dist/run-turn.d.ts +6 -0
  148. package/dist/run-turn.d.ts.map +1 -0
  149. package/dist/run-turn.js +141 -0
  150. package/dist/run-turn.js.map +1 -0
  151. package/dist/session-runtime.d.ts +67 -0
  152. package/dist/session-runtime.d.ts.map +1 -0
  153. package/dist/session-runtime.js +2 -0
  154. package/dist/session-runtime.js.map +1 -0
  155. package/dist/session.d.ts +203 -0
  156. package/dist/session.d.ts.map +1 -0
  157. package/dist/session.js +399 -0
  158. package/dist/session.js.map +1 -0
  159. package/dist/sessions/persistence.d.ts +184 -0
  160. package/dist/sessions/persistence.d.ts.map +1 -0
  161. package/dist/sessions/persistence.js +442 -0
  162. package/dist/sessions/persistence.js.map +1 -0
  163. package/dist/setup-agent.d.ts +91 -0
  164. package/dist/setup-agent.d.ts.map +1 -0
  165. package/dist/setup-agent.js +138 -0
  166. package/dist/setup-agent.js.map +1 -0
  167. package/dist/skills/index.d.ts +4 -0
  168. package/dist/skills/index.d.ts.map +1 -0
  169. package/dist/skills/index.js +4 -0
  170. package/dist/skills/index.js.map +1 -0
  171. package/dist/skills/loader.d.ts +16 -0
  172. package/dist/skills/loader.d.ts.map +1 -0
  173. package/dist/skills/loader.js +99 -0
  174. package/dist/skills/loader.js.map +1 -0
  175. package/dist/skills/parse.d.ts +5 -0
  176. package/dist/skills/parse.d.ts.map +1 -0
  177. package/dist/skills/parse.js +8 -0
  178. package/dist/skills/parse.js.map +1 -0
  179. package/dist/skills/synthesize-draft.d.ts +8 -0
  180. package/dist/skills/synthesize-draft.d.ts.map +1 -0
  181. package/dist/skills/synthesize-draft.js +32 -0
  182. package/dist/skills/synthesize-draft.js.map +1 -0
  183. package/dist/skills/synthesize.d.ts +33 -0
  184. package/dist/skills/synthesize.d.ts.map +1 -0
  185. package/dist/skills/synthesize.js +241 -0
  186. package/dist/skills/synthesize.js.map +1 -0
  187. package/dist/subagents/events.d.ts +20 -0
  188. package/dist/subagents/events.d.ts.map +1 -0
  189. package/dist/subagents/events.js +146 -0
  190. package/dist/subagents/events.js.map +1 -0
  191. package/dist/subagents/registry.d.ts +40 -0
  192. package/dist/subagents/registry.d.ts.map +1 -0
  193. package/dist/subagents/registry.js +42 -0
  194. package/dist/subagents/registry.js.map +1 -0
  195. package/dist/subagents/run-child.d.ts +43 -0
  196. package/dist/subagents/run-child.d.ts.map +1 -0
  197. package/dist/subagents/run-child.js +276 -0
  198. package/dist/subagents/run-child.js.map +1 -0
  199. package/dist/subagents/spawn.d.ts +8 -0
  200. package/dist/subagents/spawn.d.ts.map +1 -0
  201. package/dist/subagents/spawn.js +8 -0
  202. package/dist/subagents/spawn.js.map +1 -0
  203. package/dist/subagents/tools.d.ts +4 -0
  204. package/dist/subagents/tools.d.ts.map +1 -0
  205. package/dist/subagents/tools.js +13 -0
  206. package/dist/subagents/tools.js.map +1 -0
  207. package/dist/subagents.d.ts +3 -0
  208. package/dist/subagents.d.ts.map +1 -0
  209. package/dist/subagents.js +3 -0
  210. package/dist/subagents.js.map +1 -0
  211. package/dist/surfaces/host.d.ts +53 -0
  212. package/dist/surfaces/host.d.ts.map +1 -0
  213. package/dist/surfaces/host.js +177 -0
  214. package/dist/surfaces/host.js.map +1 -0
  215. package/dist/tunnel/localhost.d.ts +7 -0
  216. package/dist/tunnel/localhost.d.ts.map +1 -0
  217. package/dist/tunnel/localhost.js +21 -0
  218. package/dist/tunnel/localhost.js.map +1 -0
  219. package/dist/usage-stats.d.ts +42 -0
  220. package/dist/usage-stats.d.ts.map +1 -0
  221. package/dist/usage-stats.js +83 -0
  222. package/dist/usage-stats.js.map +1 -0
  223. package/dist/view/default-renderer.d.ts +7 -0
  224. package/dist/view/default-renderer.d.ts.map +1 -0
  225. package/dist/view/default-renderer.js +14 -0
  226. package/dist/view/default-renderer.js.map +1 -0
  227. package/dist/view/parse.d.ts +7 -0
  228. package/dist/view/parse.d.ts.map +1 -0
  229. package/dist/view/parse.js +479 -0
  230. package/dist/view/parse.js.map +1 -0
  231. package/package.json +61 -0
  232. package/src/events/factory.ts +27 -0
  233. package/src/events/log.test.ts +470 -0
  234. package/src/events/log.ts +256 -0
  235. package/src/index.ts +102 -0
  236. package/src/logger.ts +46 -0
  237. package/src/permissions/engine.test.ts +206 -0
  238. package/src/permissions/engine.ts +247 -0
  239. package/src/permissions/resolvers.test.ts +40 -0
  240. package/src/permissions/resolvers.ts +17 -0
  241. package/src/plugins/discovery.test.ts +115 -0
  242. package/src/plugins/discovery.ts +124 -0
  243. package/src/plugins/host-options.ts +73 -0
  244. package/src/plugins/host-view-tunnel.test.ts +86 -0
  245. package/src/plugins/host.test.ts +434 -0
  246. package/src/plugins/host.ts +327 -0
  247. package/src/plugins/lifecycle.test.ts +187 -0
  248. package/src/plugins/lifecycle.ts +136 -0
  249. package/src/plugins/loader.ts +79 -0
  250. package/src/plugins/package-requirements.ts +50 -0
  251. package/src/plugins/registry-kinds.test.ts +162 -0
  252. package/src/plugins/registry-kinds.ts +210 -0
  253. package/src/plugins/toposort.test.ts +66 -0
  254. package/src/plugins/toposort.ts +69 -0
  255. package/src/preferences.test.ts +105 -0
  256. package/src/preferences.ts +84 -0
  257. package/src/registries/active-backend-registry.ts +142 -0
  258. package/src/registries/active-def-registry.test.ts +103 -0
  259. package/src/registries/active-def-registry.ts +97 -0
  260. package/src/registries/agents.ts +18 -0
  261. package/src/registries/cache-strategies.ts +14 -0
  262. package/src/registries/channels.ts +42 -0
  263. package/src/registries/commands.test.ts +71 -0
  264. package/src/registries/commands.ts +78 -0
  265. package/src/registries/compactors.ts +14 -0
  266. package/src/registries/def-map-registry.test.ts +67 -0
  267. package/src/registries/def-map-registry.ts +59 -0
  268. package/src/registries/embedders.test.ts +54 -0
  269. package/src/registries/embedders.ts +18 -0
  270. package/src/registries/isolators.test.ts +43 -0
  271. package/src/registries/isolators.ts +38 -0
  272. package/src/registries/modes.test.ts +66 -0
  273. package/src/registries/modes.ts +80 -0
  274. package/src/registries/providers.test.ts +50 -0
  275. package/src/registries/providers.ts +97 -0
  276. package/src/registries/semantics.test.ts +117 -0
  277. package/src/registries/skills.ts +87 -0
  278. package/src/registries/surfaces.ts +18 -0
  279. package/src/registries/synthesizers.test.ts +63 -0
  280. package/src/registries/synthesizers.ts +36 -0
  281. package/src/registries/tools.test.ts +142 -0
  282. package/src/registries/tools.ts +145 -0
  283. package/src/registries/transcribers.test.ts +70 -0
  284. package/src/registries/transcribers.ts +19 -0
  285. package/src/registries/tunnel-providers.test.ts +30 -0
  286. package/src/registries/tunnel-providers.ts +14 -0
  287. package/src/registries/view-renderers.test.ts +68 -0
  288. package/src/registries/view-renderers.ts +15 -0
  289. package/src/registries/workflow-executors.test.ts +53 -0
  290. package/src/registries/workflow-executors.ts +15 -0
  291. package/src/requirements.targetinfo.test.ts +70 -0
  292. package/src/requirements.test.ts +111 -0
  293. package/src/requirements.ts +262 -0
  294. package/src/run-turn.test.ts +178 -0
  295. package/src/run-turn.ts +156 -0
  296. package/src/session-runtime.ts +75 -0
  297. package/src/session.test.ts +216 -0
  298. package/src/session.ts +491 -0
  299. package/src/sessions/persistence.flush.test.ts +100 -0
  300. package/src/sessions/persistence.ready.test.ts +91 -0
  301. package/src/sessions/persistence.test.ts +274 -0
  302. package/src/sessions/persistence.ts +498 -0
  303. package/src/setup-agent.test.ts +72 -0
  304. package/src/setup-agent.ts +189 -0
  305. package/src/skills/index.ts +14 -0
  306. package/src/skills/loader.test.ts +102 -0
  307. package/src/skills/loader.ts +114 -0
  308. package/src/skills/parse.test.ts +59 -0
  309. package/src/skills/parse.ts +11 -0
  310. package/src/skills/synthesize-draft.test.ts +79 -0
  311. package/src/skills/synthesize-draft.ts +46 -0
  312. package/src/skills/synthesize.test.ts +170 -0
  313. package/src/skills/synthesize.ts +304 -0
  314. package/src/subagents/events.test.ts +56 -0
  315. package/src/subagents/events.ts +194 -0
  316. package/src/subagents/registry.ts +62 -0
  317. package/src/subagents/run-child.test.ts +185 -0
  318. package/src/subagents/run-child.ts +425 -0
  319. package/src/subagents/spawn.ts +12 -0
  320. package/src/subagents/tools.ts +19 -0
  321. package/src/subagents.ts +2 -0
  322. package/src/surfaces/host.test.ts +330 -0
  323. package/src/surfaces/host.ts +192 -0
  324. package/src/tunnel/localhost.test.ts +22 -0
  325. package/src/tunnel/localhost.ts +21 -0
  326. package/src/usage-stats.test.ts +77 -0
  327. package/src/usage-stats.ts +119 -0
  328. package/src/view/default-renderer.test.ts +29 -0
  329. package/src/view/default-renderer.ts +14 -0
  330. package/src/view/parse-extended.test.ts +255 -0
  331. package/src/view/parse.test.ts +232 -0
  332. package/src/view/parse.ts +520 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Subagent runtime — turns the SDK's `SubagentSpawner` interface into a
3
+ * working factory that spawns child modes sharing the parent Session's
4
+ * registries.
5
+ *
6
+ * Each child gets:
7
+ * - Its own `EventLog` (isolated history, no cross-talk).
8
+ * - Its own `sessionId` + `turnId` (so hooks / tool ctxs see distinct ids).
9
+ * - The parent's providers, tools (optionally filtered), skills, permissions,
10
+ * plugin host, and abort signal.
11
+ *
12
+ * As the child runs, this module streams its events into the parent's log
13
+ * as `plugin_event` records with `subagent_*` subtypes — so the TUI, JSON
14
+ * exporters, and other subscribers see live progress without waiting for
15
+ * the child's final message. The captured final assistant message is
16
+ * returned to the spawner caller via the `SubagentResult`.
17
+ *
18
+ * A child spawned with `retainSession: true` keeps its log + context alive in
19
+ * the {@link RetainedChildSession} registry so `continue()` can append an
20
+ * operator reply and run it again — the workflow `awaitInput` pause/resume
21
+ * flow. `subagent_completed` is deferred until that follow-up turn (or a
22
+ * `release()`).
23
+ */
24
+
25
+ import type {
26
+ EmittedEvent,
27
+ ModeContext,
28
+ MoxxyEvent,
29
+ SessionId,
30
+ StopReason,
31
+ SubagentContinueArgs,
32
+ SubagentResult,
33
+ SubagentSpawner,
34
+ SubagentSpec,
35
+ ToolRegistry,
36
+ TurnId,
37
+ } from '@moxxy/sdk';
38
+ import { EventLog } from '../events/log.js';
39
+ import { newSessionId, newTurnId } from '../events/factory.js';
40
+ import type { SessionRuntime } from '../session-runtime.js';
41
+ import {
42
+ emitSubagentCompleted,
43
+ emitSubagentStart,
44
+ emitSubagentWarning,
45
+ streamChildEventToParent,
46
+ } from './events.js';
47
+ import { buildFilteredToolRegistry } from './tools.js';
48
+ import {
49
+ getRetainedChild,
50
+ registerRetainedChild,
51
+ releaseRetainedChild,
52
+ type RetainedChildSession,
53
+ } from './registry.js';
54
+
55
+ export interface SubagentRuntime {
56
+ readonly parentSession: SessionRuntime;
57
+ readonly parentTurnId: TurnId;
58
+ readonly parentSignal: AbortSignal;
59
+ readonly parentModel: string;
60
+ }
61
+
62
+ type ResolvedStrategy =
63
+ | { strategy: ReturnType<SessionRuntime['modes']['list']>[number]; strategyName: string }
64
+ | { failure: SubagentResult };
65
+
66
+ export async function runChildTurn(args: {
67
+ rt: SubagentRuntime;
68
+ spec: SubagentSpec;
69
+ retainSession: boolean;
70
+ }): Promise<SubagentResult> {
71
+ const { rt, spec, retainSession } = args;
72
+ const { parentSession, parentTurnId } = rt;
73
+ const childSessionId = newSessionId();
74
+ const childTurnId = newTurnId();
75
+ const label = spec.label ?? `subagent-${String(childSessionId).slice(-6)}`;
76
+ const requestedStrategy = spec.mode ?? 'default';
77
+
78
+ const resolved = await resolveStrategy(
79
+ parentSession,
80
+ parentTurnId,
81
+ label,
82
+ childSessionId,
83
+ spec,
84
+ requestedStrategy,
85
+ );
86
+ if ('failure' in resolved) return resolved.failure;
87
+ const { strategy, strategyName } = resolved;
88
+
89
+ const toolRegistry: ToolRegistry =
90
+ spec.allowedTools && spec.allowedTools.length > 0
91
+ ? buildFilteredToolRegistry(parentSession.tools, new Set(spec.allowedTools))
92
+ : parentSession.tools;
93
+
94
+ const childModel = await resolveChildModel(rt, spec, label, childSessionId);
95
+
96
+ const childLog = new EventLog();
97
+ // The nested spawner's parentModel must be the CHILD's effective model —
98
+ // building it from the original rt would make grandchildren silently
99
+ // revert to the grandparent's model.
100
+ const spawner = createSubagentSpawner({ ...rt, parentModel: childModel });
101
+ const childCtx = buildChildContext(
102
+ rt,
103
+ spec,
104
+ childModel,
105
+ childSessionId,
106
+ childTurnId,
107
+ toolRegistry,
108
+ childLog,
109
+ spawner,
110
+ );
111
+ const capture = await executeChildLoop({
112
+ rt,
113
+ spec,
114
+ label,
115
+ childSessionId,
116
+ childTurnId,
117
+ childLog,
118
+ childCtx,
119
+ strategy,
120
+ strategyName,
121
+ emitCompleted: !retainSession,
122
+ });
123
+
124
+ if (retainSession) {
125
+ registerRetainedChild({
126
+ label,
127
+ childSessionId,
128
+ childTurnId,
129
+ childLog,
130
+ childCtx,
131
+ spec,
132
+ strategy,
133
+ strategyName,
134
+ parentSession,
135
+ parentTurnId,
136
+ });
137
+ }
138
+
139
+ return capture.result;
140
+ }
141
+
142
+ export async function continueChildTurn(args: {
143
+ childSessionId: SessionId;
144
+ prompt: string;
145
+ label?: string;
146
+ }): Promise<SubagentResult> {
147
+ const retained = getRetainedChild(args.childSessionId);
148
+ if (!retained) {
149
+ throw new Error(`no retained subagent session for "${String(args.childSessionId)}"`);
150
+ }
151
+
152
+ await retained.childLog.append({
153
+ type: 'user_prompt',
154
+ sessionId: retained.childSessionId,
155
+ turnId: retained.childTurnId,
156
+ source: 'user',
157
+ text: args.prompt,
158
+ });
159
+
160
+ const rt: SubagentRuntime = {
161
+ parentSession: retained.parentSession,
162
+ parentTurnId: retained.parentTurnId,
163
+ parentSignal: retained.childCtx.signal,
164
+ parentModel: retained.childCtx.model,
165
+ };
166
+
167
+ const capture = await executeChildLoop({
168
+ rt,
169
+ spec: retained.spec,
170
+ label: args.label ?? retained.label,
171
+ childSessionId: retained.childSessionId,
172
+ childTurnId: retained.childTurnId,
173
+ childLog: retained.childLog,
174
+ childCtx: retained.childCtx,
175
+ strategy: retained.strategy,
176
+ strategyName: retained.strategyName,
177
+ emitCompleted: true,
178
+ skipStartEvent: true,
179
+ });
180
+
181
+ releaseRetainedChild(args.childSessionId);
182
+ return capture.result;
183
+ }
184
+
185
+ async function executeChildLoop(args: {
186
+ rt: SubagentRuntime;
187
+ spec: SubagentSpec;
188
+ label: string;
189
+ childSessionId: ReturnType<typeof newSessionId>;
190
+ childTurnId: TurnId;
191
+ childLog: EventLog;
192
+ childCtx: ModeContext;
193
+ strategy: RetainedChildSession['strategy'];
194
+ strategyName: string;
195
+ emitCompleted: boolean;
196
+ skipStartEvent?: boolean;
197
+ }): Promise<{ result: SubagentResult }> {
198
+ const {
199
+ rt,
200
+ spec,
201
+ label,
202
+ childSessionId,
203
+ childTurnId,
204
+ childLog,
205
+ childCtx,
206
+ strategy,
207
+ strategyName,
208
+ emitCompleted,
209
+ skipStartEvent,
210
+ } = args;
211
+ const { parentSession, parentTurnId } = rt;
212
+
213
+ const capture = {
214
+ text: '',
215
+ stopReason: 'end_turn' as StopReason,
216
+ error: null as string | null,
217
+ tokensUsed: 0,
218
+ };
219
+
220
+ const unsubCapture = childLog.subscribe((e) => {
221
+ if (e.type === 'assistant_message') {
222
+ if (e.content) capture.text = e.content;
223
+ if (e.stopReason) capture.stopReason = e.stopReason;
224
+ } else if (e.type === 'provider_response') {
225
+ // Sum every provider call's tokens so the parent can show how much this
226
+ // child consumed (input + output, matching a context-meter reading).
227
+ capture.tokensUsed += (e.inputTokens ?? 0) + (e.outputTokens ?? 0);
228
+ } else if (e.type === 'error' && e.kind === 'fatal') {
229
+ capture.error = e.message;
230
+ }
231
+ });
232
+
233
+ const unsubStream = childLog.subscribe((childEvt) =>
234
+ streamChildEventToParent(parentSession, parentTurnId, label, childSessionId, childEvt),
235
+ );
236
+
237
+ if (!skipStartEvent) {
238
+ await emitSubagentStart(parentSession, parentTurnId, label, childSessionId, spec, strategyName);
239
+ // Seed the child's log with its user_prompt so projection works for
240
+ // tool-use-style strategies (which read user_prompt events from the log).
241
+ await childLog.append({
242
+ type: 'user_prompt',
243
+ sessionId: childSessionId,
244
+ turnId: childTurnId,
245
+ source: 'user',
246
+ text: spec.prompt,
247
+ });
248
+ }
249
+
250
+ try {
251
+ for await (const _ of strategy.run(childCtx)) {
252
+ void _;
253
+ }
254
+ } catch (err) {
255
+ capture.error = err instanceof Error ? err.message : String(err);
256
+ } finally {
257
+ unsubStream();
258
+ unsubCapture();
259
+ }
260
+
261
+ const result: SubagentResult = {
262
+ label,
263
+ childSessionId,
264
+ text: capture.text,
265
+ stopReason: capture.error ? ('error' as StopReason) : capture.stopReason,
266
+ ...(capture.error ? { error: { message: capture.error } } : {}),
267
+ };
268
+
269
+ if (emitCompleted) {
270
+ await emitSubagentCompleted(
271
+ parentSession,
272
+ parentTurnId,
273
+ label,
274
+ childSessionId,
275
+ capture.text,
276
+ result.stopReason,
277
+ capture.error,
278
+ spec.agentType ?? 'default',
279
+ capture.tokensUsed,
280
+ );
281
+ }
282
+
283
+ return { result };
284
+ }
285
+
286
+ async function resolveStrategy(
287
+ parentSession: SessionRuntime,
288
+ parentTurnId: TurnId,
289
+ label: string,
290
+ childSessionId: ReturnType<typeof newSessionId>,
291
+ spec: SubagentSpec,
292
+ requestedStrategy: string,
293
+ ): Promise<ResolvedStrategy> {
294
+ // Look up the requested strategy in the parent's loop registry. The
295
+ // registry only exposes list() / getActive(), so we scan.
296
+ const exact = parentSession.modes.list().find((s) => s.name === requestedStrategy);
297
+ if (exact) return { strategy: exact, strategyName: requestedStrategy };
298
+
299
+ // Fall back to the default mode if the model invented a name
300
+ // (e.g. "react"). Failing the child outright wastes the user's turn —
301
+ // any reasonable agent task can run on the default mode. We surface the
302
+ // fallback as a non-fatal warning event so the operator sees it.
303
+ const fallback = parentSession.modes.list().find((s) => s.name === 'default');
304
+ if (fallback) {
305
+ await emitSubagentWarning(
306
+ parentSession,
307
+ parentTurnId,
308
+ label,
309
+ childSessionId,
310
+ `unknown mode "${requestedStrategy}" — falling back to "default"`,
311
+ );
312
+ return { strategy: fallback, strategyName: 'default' };
313
+ }
314
+
315
+ // No default mode either — that's a config error, not a model mistake.
316
+ await emitSubagentStart(parentSession, parentTurnId, label, childSessionId, spec, requestedStrategy);
317
+ const errorMsg = `Subagent failed: unknown mode "${requestedStrategy}" and no fallback available`;
318
+ await emitSubagentCompleted(parentSession, parentTurnId, label, childSessionId, '', 'error', errorMsg, spec.agentType ?? 'default', 0);
319
+ return {
320
+ failure: {
321
+ label,
322
+ childSessionId,
323
+ text: '',
324
+ stopReason: 'error' as StopReason,
325
+ error: { message: errorMsg },
326
+ },
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Resolve the child's effective model. A `spec.model` usually comes from the
332
+ * calling LLM (dispatch_agent's free-form string field), which sometimes
333
+ * hallucinates training-era ids ("claude-3-5-sonnet", "gpt-4o") — running the
334
+ * child on those would 404 or silently land on a different vendor model. When
335
+ * the active provider publishes a models list and the requested id isn't in
336
+ * it, fall back to the parent's model with a warning (mirroring the
337
+ * unknown-mode fallback above) instead of hard-erroring. Providers with an
338
+ * EMPTY models list (sparse admin-registered vendors — see the
339
+ * resolveModelContext caveat in @moxxy/sdk) skip validation entirely: we
340
+ * can't tell a typo from a legitimate unlisted id there.
341
+ */
342
+ async function resolveChildModel(
343
+ rt: SubagentRuntime,
344
+ spec: SubagentSpec,
345
+ label: string,
346
+ childSessionId: ReturnType<typeof newSessionId>,
347
+ ): Promise<string> {
348
+ const { parentSession, parentTurnId, parentModel } = rt;
349
+ const requested = spec.model;
350
+ if (requested === undefined || requested === parentModel) return parentModel;
351
+ const models = parentSession.providers.getActive().models;
352
+ if (models.length > 0 && !models.some((m) => m.id === requested)) {
353
+ await emitSubagentWarning(
354
+ parentSession,
355
+ parentTurnId,
356
+ label,
357
+ childSessionId,
358
+ `unknown model "${requested}" — falling back to parent model "${parentModel}"`,
359
+ );
360
+ return parentModel;
361
+ }
362
+ return requested;
363
+ }
364
+
365
+ function buildChildContext(
366
+ rt: SubagentRuntime,
367
+ spec: SubagentSpec,
368
+ model: string,
369
+ childSessionId: ReturnType<typeof newSessionId>,
370
+ childTurnId: TurnId,
371
+ toolRegistry: ToolRegistry,
372
+ childLog: EventLog,
373
+ spawner: SubagentSpawner,
374
+ ): ModeContext {
375
+ const { parentSession, parentSignal } = rt;
376
+ // Children share the parent's working dir + environment — mirror them onto
377
+ // the child ModeContext so its tool dispatcher hands onToolCall hooks the
378
+ // real cwd/env (matching the parent turn) rather than empty placeholders.
379
+ const parentAppCtx = parentSession.appContext();
380
+ return {
381
+ sessionId: childSessionId,
382
+ turnId: childTurnId,
383
+ cwd: parentAppCtx.cwd,
384
+ env: parentAppCtx.env,
385
+ model,
386
+ ...(spec.systemPrompt !== undefined ? { systemPrompt: spec.systemPrompt } : {}),
387
+ provider: parentSession.providers.getActive(),
388
+ tools: toolRegistry,
389
+ skills: parentSession.skills,
390
+ log: childLog,
391
+ compactor: parentSession.compactors.getActive(),
392
+ cacheStrategy: parentSession.cacheStrategies.getActive(),
393
+ ...(parentSession.elisionSettings ? { elision: parentSession.elisionSettings } : {}),
394
+ ...(parentSession.lazyTools ? { lazyTools: true } : {}),
395
+ permissions: parentSession.resolver,
396
+ // Intentionally no `approval` — fanning approval gates out to N
397
+ // children in parallel would prompt the user N times. Strategies
398
+ // that absolutely need approval can be invoked at the parent level.
399
+ hooks: parentSession.dispatcher,
400
+ pluginHost: parentSession.pluginHost,
401
+ signal: parentSignal, // child cancels when parent cancels
402
+ maxIterations: spec.maxIterations ?? 50,
403
+ subagents: spawner,
404
+ emit: (event: EmittedEvent): Promise<MoxxyEvent> => childLog.append(event),
405
+ };
406
+ }
407
+
408
+ export function createSubagentSpawner(rt: SubagentRuntime): SubagentSpawner {
409
+ return {
410
+ async spawn(spec) {
411
+ return runChildTurn({ rt, spec, retainSession: spec.retainSession === true });
412
+ },
413
+ async spawnAll(specs) {
414
+ return Promise.all(
415
+ specs.map((s) => runChildTurn({ rt, spec: s, retainSession: s.retainSession === true })),
416
+ );
417
+ },
418
+ async continue(args: SubagentContinueArgs) {
419
+ return continueChildTurn(args);
420
+ },
421
+ release(childSessionId: SessionId) {
422
+ releaseRetainedChild(childSessionId);
423
+ },
424
+ };
425
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Back-compat entry point for the subagent runtime. The implementation lives
3
+ * in `run-child.ts` (which adds retained-session `continue()`/`release()` for
4
+ * the workflow `awaitInput` pause/resume flow); this module just re-exports it
5
+ * so existing `subagents/spawn.js` importers keep working.
6
+ */
7
+ export {
8
+ createSubagentSpawner,
9
+ runChildTurn,
10
+ continueChildTurn,
11
+ type SubagentRuntime,
12
+ } from './run-child.js';
@@ -0,0 +1,19 @@
1
+ import type { ToolDef, ToolRegistry } from '@moxxy/sdk';
2
+ import type { ToolRegistry as CoreToolRegistry } from '../registries/tools.js';
3
+
4
+ export function buildFilteredToolRegistry(
5
+ parent: CoreToolRegistry,
6
+ allowed: Set<string>,
7
+ ): ToolRegistry {
8
+ return {
9
+ list: (): ReadonlyArray<ToolDef> => parent.list().filter((t) => allowed.has(t.name)),
10
+ get: (name: string): ToolDef | undefined =>
11
+ allowed.has(name) ? parent.get(name) : undefined,
12
+ execute: (name, input, signal, opts) => {
13
+ if (!allowed.has(name)) {
14
+ return Promise.reject(new Error(`Tool ${name} not allowed in this subagent`));
15
+ }
16
+ return parent.execute(name, input, signal, opts);
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,2 @@
1
+ export { createSubagentSpawner, type SubagentRuntime } from './subagents/spawn.js';
2
+ export { clearRetainedChildren } from './subagents/registry.js';