@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent <-> parent log bridge. Wraps child `MoxxyEvent`s into
|
|
3
|
+
* `plugin_event` envelopes on the parent log so the TUI / exporters can
|
|
4
|
+
* render live progress, and emits the start/completed bookend envelopes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
MoxxyEvent,
|
|
9
|
+
SessionId,
|
|
10
|
+
StopReason,
|
|
11
|
+
SubagentSpec,
|
|
12
|
+
TurnId,
|
|
13
|
+
} from '@moxxy/sdk';
|
|
14
|
+
import { asPluginId } from '@moxxy/sdk';
|
|
15
|
+
import type { SessionRuntime } from '../session-runtime.js';
|
|
16
|
+
|
|
17
|
+
export const SUBAGENT_PLUGIN_ID = asPluginId('@moxxy/subagents');
|
|
18
|
+
|
|
19
|
+
export async function emitSubagentStart(
|
|
20
|
+
parentSession: SessionRuntime,
|
|
21
|
+
parentTurnId: TurnId,
|
|
22
|
+
label: string,
|
|
23
|
+
childSessionId: SessionId,
|
|
24
|
+
spec: SubagentSpec,
|
|
25
|
+
mode: string,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
await parentSession.log.append({
|
|
28
|
+
type: 'plugin_event',
|
|
29
|
+
sessionId: parentSession.id,
|
|
30
|
+
turnId: parentTurnId,
|
|
31
|
+
source: 'plugin',
|
|
32
|
+
pluginId: SUBAGENT_PLUGIN_ID,
|
|
33
|
+
subtype: 'subagent_started',
|
|
34
|
+
payload: {
|
|
35
|
+
label,
|
|
36
|
+
childSessionId: String(childSessionId),
|
|
37
|
+
prompt: spec.prompt,
|
|
38
|
+
mode,
|
|
39
|
+
agentType: spec.agentType ?? 'default',
|
|
40
|
+
...(spec.model ? { model: spec.model } : {}),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function emitSubagentCompleted(
|
|
46
|
+
parentSession: SessionRuntime,
|
|
47
|
+
parentTurnId: TurnId,
|
|
48
|
+
label: string,
|
|
49
|
+
childSessionId: SessionId,
|
|
50
|
+
text: string,
|
|
51
|
+
stopReason: StopReason,
|
|
52
|
+
errorMessage: string | null,
|
|
53
|
+
agentType: string,
|
|
54
|
+
tokensUsed: number,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
await parentSession.log.append({
|
|
57
|
+
type: 'plugin_event',
|
|
58
|
+
sessionId: parentSession.id,
|
|
59
|
+
turnId: parentTurnId,
|
|
60
|
+
source: 'plugin',
|
|
61
|
+
pluginId: SUBAGENT_PLUGIN_ID,
|
|
62
|
+
subtype: 'subagent_completed',
|
|
63
|
+
payload: {
|
|
64
|
+
label,
|
|
65
|
+
childSessionId: String(childSessionId),
|
|
66
|
+
text,
|
|
67
|
+
stopReason,
|
|
68
|
+
agentType,
|
|
69
|
+
tokensUsed,
|
|
70
|
+
...(errorMessage ? { error: errorMessage } : {}),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function emitSubagentWarning(
|
|
76
|
+
parentSession: SessionRuntime,
|
|
77
|
+
parentTurnId: TurnId,
|
|
78
|
+
label: string,
|
|
79
|
+
childSessionId: SessionId,
|
|
80
|
+
message: string,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
await parentSession.log.append({
|
|
83
|
+
type: 'plugin_event',
|
|
84
|
+
sessionId: parentSession.id,
|
|
85
|
+
turnId: parentTurnId,
|
|
86
|
+
source: 'plugin',
|
|
87
|
+
pluginId: SUBAGENT_PLUGIN_ID,
|
|
88
|
+
subtype: 'subagent_warning',
|
|
89
|
+
payload: {
|
|
90
|
+
label,
|
|
91
|
+
childSessionId: String(childSessionId),
|
|
92
|
+
message,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Map each interesting child event to a parent `plugin_event` so the TUI
|
|
99
|
+
* can render the subagent's progress in real time. Noisy / book-keeping
|
|
100
|
+
* events (mode_iteration, provider_request, provider_response,
|
|
101
|
+
* assistant_message — covered by the explicit `subagent_completed`) are
|
|
102
|
+
* filtered out to keep the parent log lean.
|
|
103
|
+
*/
|
|
104
|
+
export async function streamChildEventToParent(
|
|
105
|
+
parentSession: SessionRuntime,
|
|
106
|
+
parentTurnId: TurnId,
|
|
107
|
+
label: string,
|
|
108
|
+
childSessionId: SessionId,
|
|
109
|
+
childEvt: MoxxyEvent,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const mapped = mapChildEvent(label, childSessionId, childEvt);
|
|
112
|
+
if (!mapped) return;
|
|
113
|
+
try {
|
|
114
|
+
await parentSession.log.append({
|
|
115
|
+
type: 'plugin_event',
|
|
116
|
+
sessionId: parentSession.id,
|
|
117
|
+
turnId: parentTurnId,
|
|
118
|
+
source: 'plugin',
|
|
119
|
+
pluginId: SUBAGENT_PLUGIN_ID,
|
|
120
|
+
subtype: mapped.subtype,
|
|
121
|
+
payload: mapped.payload,
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// Forwarding a child progress event is best-effort: a parent-log append
|
|
125
|
+
// failure must not abort the subagent run. EventLog.append already swallows
|
|
126
|
+
// listener errors, so this rejection would otherwise vanish with zero
|
|
127
|
+
// trace (the surface just stops updating); surface it so a chronic
|
|
128
|
+
// forwarding failure during a long subagent run is at least diagnosable.
|
|
129
|
+
process.stderr.write(
|
|
130
|
+
`moxxy: dropped subagent progress event (${mapped.subtype}) — parent log append failed: ` +
|
|
131
|
+
`${err instanceof Error ? err.message : String(err)}\n`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mapChildEvent(
|
|
137
|
+
label: string,
|
|
138
|
+
childSessionId: SessionId,
|
|
139
|
+
childEvt: MoxxyEvent,
|
|
140
|
+
): { subtype: string; payload: Record<string, unknown> } | null {
|
|
141
|
+
const payload: Record<string, unknown> = {
|
|
142
|
+
label,
|
|
143
|
+
childSessionId: String(childSessionId),
|
|
144
|
+
};
|
|
145
|
+
switch (childEvt.type) {
|
|
146
|
+
case 'assistant_chunk':
|
|
147
|
+
payload.delta = childEvt.delta;
|
|
148
|
+
return { subtype: 'subagent_chunk', payload };
|
|
149
|
+
case 'tool_call_requested':
|
|
150
|
+
payload.name = childEvt.name;
|
|
151
|
+
payload.input = childEvt.input;
|
|
152
|
+
payload.callId = String(childEvt.callId);
|
|
153
|
+
return { subtype: 'subagent_tool_call', payload };
|
|
154
|
+
case 'tool_result':
|
|
155
|
+
payload.callId = String(childEvt.callId);
|
|
156
|
+
payload.ok = childEvt.ok;
|
|
157
|
+
if (childEvt.ok) payload.output = childEvt.output;
|
|
158
|
+
else payload.error = childEvt.error;
|
|
159
|
+
return { subtype: 'subagent_tool_result', payload };
|
|
160
|
+
case 'error':
|
|
161
|
+
payload.kind = childEvt.kind;
|
|
162
|
+
payload.message = childEvt.message;
|
|
163
|
+
return { subtype: 'subagent_error', payload };
|
|
164
|
+
case 'abort':
|
|
165
|
+
payload.reason = childEvt.reason;
|
|
166
|
+
return { subtype: 'subagent_abort', payload };
|
|
167
|
+
case 'plugin_event':
|
|
168
|
+
return mapNestedPluginEvent(label, payload, childEvt);
|
|
169
|
+
default:
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function mapNestedPluginEvent(
|
|
175
|
+
label: string,
|
|
176
|
+
payload: Record<string, unknown>,
|
|
177
|
+
childEvt: Extract<MoxxyEvent, { type: 'plugin_event' }>,
|
|
178
|
+
): { subtype: string; payload: Record<string, unknown> } | null {
|
|
179
|
+
// Bubble nested subagent events too, so a grand-child's progress
|
|
180
|
+
// surfaces all the way up. We strip the nested label-prefix to
|
|
181
|
+
// keep things compact; payload retains the chain via the embedded
|
|
182
|
+
// childSessionId.
|
|
183
|
+
const nestedSubtype = childEvt.subtype;
|
|
184
|
+
if (typeof nestedSubtype !== 'string' || !nestedSubtype.startsWith('subagent_')) return null;
|
|
185
|
+
const nestedPayload = childEvt.payload;
|
|
186
|
+
if (nestedPayload && typeof nestedPayload === 'object') {
|
|
187
|
+
for (const [k, v] of Object.entries(nestedPayload as Record<string, unknown>)) {
|
|
188
|
+
if (k !== 'label' && k !== 'childSessionId') payload[k] = v;
|
|
189
|
+
}
|
|
190
|
+
// Preserve the chain via a `via` field naming the immediate parent label.
|
|
191
|
+
payload.via = label;
|
|
192
|
+
}
|
|
193
|
+
return { subtype: nestedSubtype, payload };
|
|
194
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retained-child session registry — backs the `awaitInput` pause/resume flow.
|
|
3
|
+
*
|
|
4
|
+
* A subagent spawned with `retainSession: true` keeps its `EventLog` and
|
|
5
|
+
* `ModeContext` alive after its first turn so a later `continue()` can append
|
|
6
|
+
* an operator reply and run it again. The registry is a process-local map
|
|
7
|
+
* keyed by child session id; entries are dropped on `continue` or `release`.
|
|
8
|
+
*
|
|
9
|
+
* Each entry records its `parentSession`, so {@link clearRetainedChildren}
|
|
10
|
+
* drops only the children belonging to the session that is closing. The map is
|
|
11
|
+
* process-wide (one per module), but a `Session.close()` must NOT wipe another
|
|
12
|
+
* live session's paused children — that would break their `continue()` in any
|
|
13
|
+
* host that runs >1 Session per process (the desktop in-process path, tests).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ModeContext, SessionId, SubagentSpec, TurnId } from '@moxxy/sdk';
|
|
17
|
+
import type { EventLog } from '../events/log.js';
|
|
18
|
+
import type { SessionRuntime } from '../session-runtime.js';
|
|
19
|
+
|
|
20
|
+
export interface RetainedChildSession {
|
|
21
|
+
readonly label: string;
|
|
22
|
+
readonly childSessionId: SessionId;
|
|
23
|
+
readonly childTurnId: TurnId;
|
|
24
|
+
readonly childLog: EventLog;
|
|
25
|
+
readonly childCtx: ModeContext;
|
|
26
|
+
readonly spec: SubagentSpec;
|
|
27
|
+
readonly strategy: ReturnType<SessionRuntime['modes']['list']>[number];
|
|
28
|
+
readonly strategyName: string;
|
|
29
|
+
readonly parentSession: SessionRuntime;
|
|
30
|
+
readonly parentTurnId: TurnId;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const retained = new Map<string, RetainedChildSession>();
|
|
34
|
+
|
|
35
|
+
export function registerRetainedChild(session: RetainedChildSession): void {
|
|
36
|
+
retained.set(String(session.childSessionId), session);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getRetainedChild(childSessionId: SessionId): RetainedChildSession | undefined {
|
|
40
|
+
return retained.get(String(childSessionId));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function releaseRetainedChild(childSessionId: SessionId): void {
|
|
44
|
+
retained.delete(String(childSessionId));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Drop retained children. With a `parentSessionId`, drop ONLY the children
|
|
49
|
+
* whose `parentSession` is that session — so one session's `close()` can't wipe
|
|
50
|
+
* another live session's paused `awaitInput` children. Without an id (legacy /
|
|
51
|
+
* test-teardown callers), clear everything.
|
|
52
|
+
*/
|
|
53
|
+
export function clearRetainedChildren(parentSessionId?: SessionId): void {
|
|
54
|
+
if (parentSessionId === undefined) {
|
|
55
|
+
retained.clear();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const target = String(parentSessionId);
|
|
59
|
+
for (const [childId, entry] of retained) {
|
|
60
|
+
if (String(entry.parentSession.id) === target) retained.delete(childId);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { ModeContext, ModeDef, MoxxyEvent, ProviderDef } from '@moxxy/sdk';
|
|
3
|
+
import { defineMode, defineProvider, definePlugin } from '@moxxy/sdk';
|
|
4
|
+
import { Session } from '../session.js';
|
|
5
|
+
import { runChildTurn, type SubagentRuntime } from './run-child.js';
|
|
6
|
+
|
|
7
|
+
// A mode that reports the model it was handed — lets tests observe the
|
|
8
|
+
// child's EFFECTIVE model through the returned result text.
|
|
9
|
+
function makeEchoModelMode(): ModeDef {
|
|
10
|
+
return defineMode({
|
|
11
|
+
name: 'echo-model',
|
|
12
|
+
run: async function* (ctx: ModeContext): AsyncIterable<MoxxyEvent> {
|
|
13
|
+
await ctx.emit({
|
|
14
|
+
type: 'assistant_message',
|
|
15
|
+
sessionId: ctx.sessionId,
|
|
16
|
+
turnId: ctx.turnId,
|
|
17
|
+
source: 'assistant',
|
|
18
|
+
content: ctx.model,
|
|
19
|
+
stopReason: 'end_turn',
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// A mode that spawns a grandchild (no model override) and reports what model
|
|
26
|
+
// the grandchild ran on — exercises nested-spawner model inheritance.
|
|
27
|
+
function makeNestedSpawnMode(): ModeDef {
|
|
28
|
+
return defineMode({
|
|
29
|
+
name: 'nested-spawn',
|
|
30
|
+
run: async function* (ctx: ModeContext): AsyncIterable<MoxxyEvent> {
|
|
31
|
+
const result = await ctx.subagents!.spawn({ prompt: 'echo', mode: 'echo-model' });
|
|
32
|
+
await ctx.emit({
|
|
33
|
+
type: 'assistant_message',
|
|
34
|
+
sessionId: ctx.sessionId,
|
|
35
|
+
turnId: ctx.turnId,
|
|
36
|
+
source: 'assistant',
|
|
37
|
+
content: result.text,
|
|
38
|
+
stopReason: 'end_turn',
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function makeProvider(modelIds: ReadonlyArray<string>): ProviderDef {
|
|
45
|
+
const models = modelIds.map((id) => ({ id }));
|
|
46
|
+
return defineProvider({
|
|
47
|
+
name: 'listed',
|
|
48
|
+
models,
|
|
49
|
+
createClient: () => ({
|
|
50
|
+
name: 'listed',
|
|
51
|
+
models,
|
|
52
|
+
stream: async function* () {
|
|
53
|
+
// unused — the test modes don't call into the provider.
|
|
54
|
+
},
|
|
55
|
+
countTokens: async () => 0,
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildSession(modelIds: ReadonlyArray<string>): Session {
|
|
61
|
+
const session = new Session({ cwd: '/tmp', silent: true });
|
|
62
|
+
session.pluginHost.registerStatic(
|
|
63
|
+
definePlugin({
|
|
64
|
+
name: 'test-subagent-models',
|
|
65
|
+
version: '0.0.0',
|
|
66
|
+
providers: [makeProvider(modelIds)],
|
|
67
|
+
modes: [makeEchoModelMode(), makeNestedSpawnMode()],
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
session.providers.setActive('listed');
|
|
71
|
+
session.modes.setActive('echo-model');
|
|
72
|
+
return session;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildRuntime(session: Session, parentModel: string): SubagentRuntime {
|
|
76
|
+
return {
|
|
77
|
+
parentSession: session,
|
|
78
|
+
parentTurnId: session.startTurn().turnId,
|
|
79
|
+
parentSignal: new AbortController().signal,
|
|
80
|
+
parentModel,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function collectParentEvents(session: Session): MoxxyEvent[] {
|
|
85
|
+
const events: MoxxyEvent[] = [];
|
|
86
|
+
session.log.subscribe((e) => void events.push(e));
|
|
87
|
+
return events;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function findWarning(events: ReadonlyArray<MoxxyEvent>): string | undefined {
|
|
91
|
+
const evt = events.find((e) => e.type === 'plugin_event' && e.subtype === 'subagent_warning');
|
|
92
|
+
return evt?.type === 'plugin_event' ? (evt.payload as { message: string }).message : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe('runChildTurn model resolution', () => {
|
|
96
|
+
it('falls back to the parent model (with a warning) on an unknown model id', async () => {
|
|
97
|
+
const session = buildSession(['parent-model', 'cheap-model']);
|
|
98
|
+
const events = collectParentEvents(session);
|
|
99
|
+
const rt = buildRuntime(session, 'parent-model');
|
|
100
|
+
|
|
101
|
+
const result = await runChildTurn({
|
|
102
|
+
rt,
|
|
103
|
+
spec: { prompt: 'echo', mode: 'echo-model', model: 'claude-3-5-sonnet' },
|
|
104
|
+
retainSession: false,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(result.text).toBe('parent-model');
|
|
108
|
+
expect(findWarning(events)).toBe(
|
|
109
|
+
'unknown model "claude-3-5-sonnet" — falling back to parent model "parent-model"',
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('honors a model id the active provider lists, without warning', async () => {
|
|
114
|
+
const session = buildSession(['parent-model', 'cheap-model']);
|
|
115
|
+
const events = collectParentEvents(session);
|
|
116
|
+
const rt = buildRuntime(session, 'parent-model');
|
|
117
|
+
|
|
118
|
+
const result = await runChildTurn({
|
|
119
|
+
rt,
|
|
120
|
+
spec: { prompt: 'echo', mode: 'echo-model', model: 'cheap-model' },
|
|
121
|
+
retainSession: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(result.text).toBe('cheap-model');
|
|
125
|
+
expect(findWarning(events)).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('inherits the parent model when no override is given', async () => {
|
|
129
|
+
const session = buildSession(['parent-model']);
|
|
130
|
+
const rt = buildRuntime(session, 'parent-model');
|
|
131
|
+
|
|
132
|
+
const result = await runChildTurn({
|
|
133
|
+
rt,
|
|
134
|
+
spec: { prompt: 'echo', mode: 'echo-model' },
|
|
135
|
+
retainSession: false,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result.text).toBe('parent-model');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('skips validation when the provider publishes no models (sparse providers)', async () => {
|
|
142
|
+
const session = buildSession([]);
|
|
143
|
+
const events = collectParentEvents(session);
|
|
144
|
+
const rt = buildRuntime(session, 'parent-model');
|
|
145
|
+
|
|
146
|
+
const result = await runChildTurn({
|
|
147
|
+
rt,
|
|
148
|
+
spec: { prompt: 'echo', mode: 'echo-model', model: 'unlisted-but-real' },
|
|
149
|
+
retainSession: false,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result.text).toBe('unlisted-but-real');
|
|
153
|
+
expect(findWarning(events)).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('does not warn when the override IS the parent model, even if unlisted', async () => {
|
|
157
|
+
const session = buildSession(['some-other-model']);
|
|
158
|
+
const events = collectParentEvents(session);
|
|
159
|
+
const rt = buildRuntime(session, 'unlisted-parent');
|
|
160
|
+
|
|
161
|
+
const result = await runChildTurn({
|
|
162
|
+
rt,
|
|
163
|
+
spec: { prompt: 'echo', mode: 'echo-model', model: 'unlisted-parent' },
|
|
164
|
+
retainSession: false,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(result.text).toBe('unlisted-parent');
|
|
168
|
+
expect(findWarning(events)).toBeUndefined();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('grandchildren inherit the child effective model, not the grandparent model', async () => {
|
|
172
|
+
const session = buildSession(['parent-model', 'cheap-model']);
|
|
173
|
+
const rt = buildRuntime(session, 'parent-model');
|
|
174
|
+
|
|
175
|
+
const result = await runChildTurn({
|
|
176
|
+
rt,
|
|
177
|
+
spec: { prompt: 'spawn nested', mode: 'nested-spawn', model: 'cheap-model' },
|
|
178
|
+
retainSession: false,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// The grandchild echoes its own ctx.model — it must see the child's
|
|
182
|
+
// resolved model ('cheap-model'), not the original parent's.
|
|
183
|
+
expect(result.text).toBe('cheap-model');
|
|
184
|
+
});
|
|
185
|
+
});
|