@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.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/events/factory.d.ts +6 -0
- package/dist/events/factory.d.ts.map +1 -0
- package/dist/events/factory.js +14 -0
- package/dist/events/factory.js.map +1 -0
- package/dist/events/log.d.ts +92 -0
- package/dist/events/log.d.ts.map +1 -0
- package/dist/events/log.js +244 -0
- package/dist/events/log.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +27 -0
- package/dist/logger.js.map +1 -0
- package/dist/permissions/engine.d.ts +94 -0
- package/dist/permissions/engine.d.ts.map +1 -0
- package/dist/permissions/engine.js +203 -0
- package/dist/permissions/engine.js.map +1 -0
- package/dist/permissions/resolvers.d.ts +8 -0
- package/dist/permissions/resolvers.d.ts.map +1 -0
- package/dist/permissions/resolvers.js +8 -0
- package/dist/permissions/resolvers.js.map +1 -0
- package/dist/plugins/discovery.d.ts +9 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +112 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/host-options.d.ts +72 -0
- package/dist/plugins/host-options.d.ts.map +1 -0
- package/dist/plugins/host-options.js +2 -0
- package/dist/plugins/host-options.js.map +1 -0
- package/dist/plugins/host.d.ts +53 -0
- package/dist/plugins/host.d.ts.map +1 -0
- package/dist/plugins/host.js +269 -0
- package/dist/plugins/host.js.map +1 -0
- package/dist/plugins/lifecycle.d.ts +31 -0
- package/dist/plugins/lifecycle.d.ts.map +1 -0
- package/dist/plugins/lifecycle.js +100 -0
- package/dist/plugins/lifecycle.js.map +1 -0
- package/dist/plugins/loader.d.ts +7 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +68 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/package-requirements.d.ts +16 -0
- package/dist/plugins/package-requirements.d.ts.map +1 -0
- package/dist/plugins/package-requirements.js +52 -0
- package/dist/plugins/package-requirements.js.map +1 -0
- package/dist/plugins/registry-kinds.d.ts +58 -0
- package/dist/plugins/registry-kinds.d.ts.map +1 -0
- package/dist/plugins/registry-kinds.js +139 -0
- package/dist/plugins/registry-kinds.js.map +1 -0
- package/dist/plugins/toposort.d.ts +24 -0
- package/dist/plugins/toposort.d.ts.map +1 -0
- package/dist/plugins/toposort.js +67 -0
- package/dist/plugins/toposort.js.map +1 -0
- package/dist/preferences.d.ts +40 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +58 -0
- package/dist/preferences.js.map +1 -0
- package/dist/registries/active-backend-registry.d.ts +65 -0
- package/dist/registries/active-backend-registry.d.ts.map +1 -0
- package/dist/registries/active-backend-registry.js +108 -0
- package/dist/registries/active-backend-registry.js.map +1 -0
- package/dist/registries/active-def-registry.d.ts +59 -0
- package/dist/registries/active-def-registry.d.ts.map +1 -0
- package/dist/registries/active-def-registry.js +63 -0
- package/dist/registries/active-def-registry.js.map +1 -0
- package/dist/registries/agents.d.ts +16 -0
- package/dist/registries/agents.d.ts.map +1 -0
- package/dist/registries/agents.js +17 -0
- package/dist/registries/agents.js.map +1 -0
- package/dist/registries/cache-strategies.d.ts +12 -0
- package/dist/registries/cache-strategies.d.ts.map +1 -0
- package/dist/registries/cache-strategies.js +13 -0
- package/dist/registries/cache-strategies.js.map +1 -0
- package/dist/registries/channels.d.ts +16 -0
- package/dist/registries/channels.d.ts.map +1 -0
- package/dist/registries/channels.js +35 -0
- package/dist/registries/channels.js.map +1 -0
- package/dist/registries/commands.d.ts +29 -0
- package/dist/registries/commands.d.ts.map +1 -0
- package/dist/registries/commands.js +72 -0
- package/dist/registries/commands.js.map +1 -0
- package/dist/registries/compactors.d.ts +12 -0
- package/dist/registries/compactors.d.ts.map +1 -0
- package/dist/registries/compactors.js +13 -0
- package/dist/registries/compactors.js.map +1 -0
- package/dist/registries/def-map-registry.d.ts +33 -0
- package/dist/registries/def-map-registry.d.ts.map +1 -0
- package/dist/registries/def-map-registry.js +36 -0
- package/dist/registries/def-map-registry.js.map +1 -0
- package/dist/registries/embedders.d.ts +16 -0
- package/dist/registries/embedders.d.ts.map +1 -0
- package/dist/registries/embedders.js +17 -0
- package/dist/registries/embedders.js.map +1 -0
- package/dist/registries/isolators.d.ts +21 -0
- package/dist/registries/isolators.d.ts.map +1 -0
- package/dist/registries/isolators.js +32 -0
- package/dist/registries/isolators.js.map +1 -0
- package/dist/registries/modes.d.ts +28 -0
- package/dist/registries/modes.d.ts.map +1 -0
- package/dist/registries/modes.js +81 -0
- package/dist/registries/modes.js.map +1 -0
- package/dist/registries/providers.d.ts +44 -0
- package/dist/registries/providers.d.ts.map +1 -0
- package/dist/registries/providers.js +95 -0
- package/dist/registries/providers.js.map +1 -0
- package/dist/registries/skills.d.ts +24 -0
- package/dist/registries/skills.d.ts.map +1 -0
- package/dist/registries/skills.js +81 -0
- package/dist/registries/skills.js.map +1 -0
- package/dist/registries/surfaces.d.ts +13 -0
- package/dist/registries/surfaces.d.ts.map +1 -0
- package/dist/registries/surfaces.js +14 -0
- package/dist/registries/surfaces.js.map +1 -0
- package/dist/registries/synthesizers.d.ts +26 -0
- package/dist/registries/synthesizers.d.ts.map +1 -0
- package/dist/registries/synthesizers.js +34 -0
- package/dist/registries/synthesizers.js.map +1 -0
- package/dist/registries/tools.d.ts +49 -0
- package/dist/registries/tools.d.ts.map +1 -0
- package/dist/registries/tools.js +98 -0
- package/dist/registries/tools.js.map +1 -0
- package/dist/registries/transcribers.d.ts +17 -0
- package/dist/registries/transcribers.d.ts.map +1 -0
- package/dist/registries/transcribers.js +18 -0
- package/dist/registries/transcribers.js.map +1 -0
- package/dist/registries/tunnel-providers.d.ts +12 -0
- package/dist/registries/tunnel-providers.d.ts.map +1 -0
- package/dist/registries/tunnel-providers.js +13 -0
- package/dist/registries/tunnel-providers.js.map +1 -0
- package/dist/registries/view-renderers.d.ts +13 -0
- package/dist/registries/view-renderers.d.ts.map +1 -0
- package/dist/registries/view-renderers.js +14 -0
- package/dist/registries/view-renderers.js.map +1 -0
- package/dist/registries/workflow-executors.d.ts +13 -0
- package/dist/registries/workflow-executors.d.ts.map +1 -0
- package/dist/registries/workflow-executors.js +14 -0
- package/dist/registries/workflow-executors.js.map +1 -0
- package/dist/requirements.d.ts +59 -0
- package/dist/requirements.d.ts.map +1 -0
- package/dist/requirements.js +149 -0
- package/dist/requirements.js.map +1 -0
- package/dist/run-turn.d.ts +6 -0
- package/dist/run-turn.d.ts.map +1 -0
- package/dist/run-turn.js +141 -0
- package/dist/run-turn.js.map +1 -0
- package/dist/session-runtime.d.ts +67 -0
- package/dist/session-runtime.d.ts.map +1 -0
- package/dist/session-runtime.js +2 -0
- package/dist/session-runtime.js.map +1 -0
- package/dist/session.d.ts +203 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +399 -0
- package/dist/session.js.map +1 -0
- package/dist/sessions/persistence.d.ts +184 -0
- package/dist/sessions/persistence.d.ts.map +1 -0
- package/dist/sessions/persistence.js +442 -0
- package/dist/sessions/persistence.js.map +1 -0
- package/dist/setup-agent.d.ts +91 -0
- package/dist/setup-agent.d.ts.map +1 -0
- package/dist/setup-agent.js +138 -0
- package/dist/setup-agent.js.map +1 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/loader.d.ts +16 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +99 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/parse.d.ts +5 -0
- package/dist/skills/parse.d.ts.map +1 -0
- package/dist/skills/parse.js +8 -0
- package/dist/skills/parse.js.map +1 -0
- package/dist/skills/synthesize-draft.d.ts +8 -0
- package/dist/skills/synthesize-draft.d.ts.map +1 -0
- package/dist/skills/synthesize-draft.js +32 -0
- package/dist/skills/synthesize-draft.js.map +1 -0
- package/dist/skills/synthesize.d.ts +33 -0
- package/dist/skills/synthesize.d.ts.map +1 -0
- package/dist/skills/synthesize.js +241 -0
- package/dist/skills/synthesize.js.map +1 -0
- package/dist/subagents/events.d.ts +20 -0
- package/dist/subagents/events.d.ts.map +1 -0
- package/dist/subagents/events.js +146 -0
- package/dist/subagents/events.js.map +1 -0
- package/dist/subagents/registry.d.ts +40 -0
- package/dist/subagents/registry.d.ts.map +1 -0
- package/dist/subagents/registry.js +42 -0
- package/dist/subagents/registry.js.map +1 -0
- package/dist/subagents/run-child.d.ts +43 -0
- package/dist/subagents/run-child.d.ts.map +1 -0
- package/dist/subagents/run-child.js +276 -0
- package/dist/subagents/run-child.js.map +1 -0
- package/dist/subagents/spawn.d.ts +8 -0
- package/dist/subagents/spawn.d.ts.map +1 -0
- package/dist/subagents/spawn.js +8 -0
- package/dist/subagents/spawn.js.map +1 -0
- package/dist/subagents/tools.d.ts +4 -0
- package/dist/subagents/tools.d.ts.map +1 -0
- package/dist/subagents/tools.js +13 -0
- package/dist/subagents/tools.js.map +1 -0
- package/dist/subagents.d.ts +3 -0
- package/dist/subagents.d.ts.map +1 -0
- package/dist/subagents.js +3 -0
- package/dist/subagents.js.map +1 -0
- package/dist/surfaces/host.d.ts +53 -0
- package/dist/surfaces/host.d.ts.map +1 -0
- package/dist/surfaces/host.js +177 -0
- package/dist/surfaces/host.js.map +1 -0
- package/dist/tunnel/localhost.d.ts +7 -0
- package/dist/tunnel/localhost.d.ts.map +1 -0
- package/dist/tunnel/localhost.js +21 -0
- package/dist/tunnel/localhost.js.map +1 -0
- package/dist/usage-stats.d.ts +42 -0
- package/dist/usage-stats.d.ts.map +1 -0
- package/dist/usage-stats.js +83 -0
- package/dist/usage-stats.js.map +1 -0
- package/dist/view/default-renderer.d.ts +7 -0
- package/dist/view/default-renderer.d.ts.map +1 -0
- package/dist/view/default-renderer.js +14 -0
- package/dist/view/default-renderer.js.map +1 -0
- package/dist/view/parse.d.ts +7 -0
- package/dist/view/parse.d.ts.map +1 -0
- package/dist/view/parse.js +479 -0
- package/dist/view/parse.js.map +1 -0
- package/package.json +61 -0
- package/src/events/factory.ts +27 -0
- package/src/events/log.test.ts +470 -0
- package/src/events/log.ts +256 -0
- package/src/index.ts +102 -0
- package/src/logger.ts +46 -0
- package/src/permissions/engine.test.ts +206 -0
- package/src/permissions/engine.ts +247 -0
- package/src/permissions/resolvers.test.ts +40 -0
- package/src/permissions/resolvers.ts +17 -0
- package/src/plugins/discovery.test.ts +115 -0
- package/src/plugins/discovery.ts +124 -0
- package/src/plugins/host-options.ts +73 -0
- package/src/plugins/host-view-tunnel.test.ts +86 -0
- package/src/plugins/host.test.ts +434 -0
- package/src/plugins/host.ts +327 -0
- package/src/plugins/lifecycle.test.ts +187 -0
- package/src/plugins/lifecycle.ts +136 -0
- package/src/plugins/loader.ts +79 -0
- package/src/plugins/package-requirements.ts +50 -0
- package/src/plugins/registry-kinds.test.ts +162 -0
- package/src/plugins/registry-kinds.ts +210 -0
- package/src/plugins/toposort.test.ts +66 -0
- package/src/plugins/toposort.ts +69 -0
- package/src/preferences.test.ts +105 -0
- package/src/preferences.ts +84 -0
- package/src/registries/active-backend-registry.ts +142 -0
- package/src/registries/active-def-registry.test.ts +103 -0
- package/src/registries/active-def-registry.ts +97 -0
- package/src/registries/agents.ts +18 -0
- package/src/registries/cache-strategies.ts +14 -0
- package/src/registries/channels.ts +42 -0
- package/src/registries/commands.test.ts +71 -0
- package/src/registries/commands.ts +78 -0
- package/src/registries/compactors.ts +14 -0
- package/src/registries/def-map-registry.test.ts +67 -0
- package/src/registries/def-map-registry.ts +59 -0
- package/src/registries/embedders.test.ts +54 -0
- package/src/registries/embedders.ts +18 -0
- package/src/registries/isolators.test.ts +43 -0
- package/src/registries/isolators.ts +38 -0
- package/src/registries/modes.test.ts +66 -0
- package/src/registries/modes.ts +80 -0
- package/src/registries/providers.test.ts +50 -0
- package/src/registries/providers.ts +97 -0
- package/src/registries/semantics.test.ts +117 -0
- package/src/registries/skills.ts +87 -0
- package/src/registries/surfaces.ts +18 -0
- package/src/registries/synthesizers.test.ts +63 -0
- package/src/registries/synthesizers.ts +36 -0
- package/src/registries/tools.test.ts +142 -0
- package/src/registries/tools.ts +145 -0
- package/src/registries/transcribers.test.ts +70 -0
- package/src/registries/transcribers.ts +19 -0
- package/src/registries/tunnel-providers.test.ts +30 -0
- package/src/registries/tunnel-providers.ts +14 -0
- package/src/registries/view-renderers.test.ts +68 -0
- package/src/registries/view-renderers.ts +15 -0
- package/src/registries/workflow-executors.test.ts +53 -0
- package/src/registries/workflow-executors.ts +15 -0
- package/src/requirements.targetinfo.test.ts +70 -0
- package/src/requirements.test.ts +111 -0
- package/src/requirements.ts +262 -0
- package/src/run-turn.test.ts +178 -0
- package/src/run-turn.ts +156 -0
- package/src/session-runtime.ts +75 -0
- package/src/session.test.ts +216 -0
- package/src/session.ts +491 -0
- package/src/sessions/persistence.flush.test.ts +100 -0
- package/src/sessions/persistence.ready.test.ts +91 -0
- package/src/sessions/persistence.test.ts +274 -0
- package/src/sessions/persistence.ts +498 -0
- package/src/setup-agent.test.ts +72 -0
- package/src/setup-agent.ts +189 -0
- package/src/skills/index.ts +14 -0
- package/src/skills/loader.test.ts +102 -0
- package/src/skills/loader.ts +114 -0
- package/src/skills/parse.test.ts +59 -0
- package/src/skills/parse.ts +11 -0
- package/src/skills/synthesize-draft.test.ts +79 -0
- package/src/skills/synthesize-draft.ts +46 -0
- package/src/skills/synthesize.test.ts +170 -0
- package/src/skills/synthesize.ts +304 -0
- package/src/subagents/events.test.ts +56 -0
- package/src/subagents/events.ts +194 -0
- package/src/subagents/registry.ts +62 -0
- package/src/subagents/run-child.test.ts +185 -0
- package/src/subagents/run-child.ts +425 -0
- package/src/subagents/spawn.ts +12 -0
- package/src/subagents/tools.ts +19 -0
- package/src/subagents.ts +2 -0
- package/src/surfaces/host.test.ts +330 -0
- package/src/surfaces/host.ts +192 -0
- package/src/tunnel/localhost.test.ts +22 -0
- package/src/tunnel/localhost.ts +21 -0
- package/src/usage-stats.test.ts +77 -0
- package/src/usage-stats.ts +119 -0
- package/src/view/default-renderer.test.ts +29 -0
- package/src/view/default-renderer.ts +14 -0
- package/src/view/parse-extended.test.ts +255 -0
- package/src/view/parse.test.ts +232 -0
- 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
|
+
}
|
package/src/subagents.ts
ADDED