macro-agent 0.1.0 → 0.1.1
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/.claude/settings.local.json +3 -1
- package/.sudocode/specs.jsonl +4 -0
- package/CLAUDE.md +16 -14
- package/README.md +11 -29
- package/dist/acp/macro-agent.d.ts +15 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +131 -35
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +32 -1
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +65 -1
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +464 -183
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +37 -6
- package/dist/api/server.js.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token.d.ts +41 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +73 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli/acp.d.ts +2 -23
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +127 -61
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +147 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +268 -181
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/parse-args.d.ts +20 -0
- package/dist/cli/parse-args.d.ts.map +1 -0
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli/parse-args.js.map +1 -0
- package/dist/cli/stable-instance-id.d.ts +8 -0
- package/dist/cli/stable-instance-id.d.ts.map +1 -0
- package/dist/cli/stable-instance-id.js +14 -0
- package/dist/cli/stable-instance-id.js.map +1 -0
- package/dist/config/project-config.d.ts +74 -7
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +123 -20
- package/dist/config/project-config.js.map +1 -1
- package/dist/map/adapter/acp-over-map.d.ts +17 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +384 -23
- package/dist/map/adapter/acp-over-map.js.map +1 -1
- package/dist/map/adapter/connection-manager.d.ts.map +1 -1
- package/dist/map/adapter/connection-manager.js +3 -0
- package/dist/map/adapter/connection-manager.js.map +1 -1
- package/dist/map/adapter/event-log.d.ts +87 -0
- package/dist/map/adapter/event-log.d.ts.map +1 -0
- package/dist/map/adapter/event-log.js +122 -0
- package/dist/map/adapter/event-log.js.map +1 -0
- package/dist/map/adapter/event-translator.js +6 -6
- package/dist/map/adapter/event-translator.js.map +1 -1
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
- package/dist/map/adapter/extensions/index.d.ts +10 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +34 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
- package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
- package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
- package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
- package/dist/map/adapter/extensions/rename.d.ts +29 -0
- package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
- package/dist/map/adapter/extensions/rename.js +49 -0
- package/dist/map/adapter/extensions/rename.js.map +1 -0
- package/dist/map/adapter/extensions/task.d.ts.map +1 -1
- package/dist/map/adapter/extensions/task.js +10 -0
- package/dist/map/adapter/extensions/task.js.map +1 -1
- package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
- package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
- package/dist/map/adapter/extensions/update-metadata.js +67 -0
- package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
- package/dist/map/adapter/index.d.ts +2 -1
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +8 -2
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/interface.d.ts +2 -0
- package/dist/map/adapter/interface.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.d.ts +3 -0
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +258 -35
- package/dist/map/adapter/map-adapter.js.map +1 -1
- package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
- package/dist/map/adapter/subscription-manager.js +5 -1
- package/dist/map/adapter/subscription-manager.js.map +1 -1
- package/dist/map/adapter/types.d.ts +2 -0
- package/dist/map/adapter/types.d.ts.map +1 -1
- package/dist/mcp/map-client.d.ts +39 -0
- package/dist/mcp/map-client.d.ts.map +1 -0
- package/dist/mcp/map-client.js +129 -0
- package/dist/mcp/map-client.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +14 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +113 -85
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/types.d.ts +9 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/metrics/metrics.js +1 -1
- package/dist/metrics/metrics.js.map +1 -1
- package/dist/roles/capabilities.d.ts +3 -1
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +17 -7
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/config-loader.d.ts +6 -6
- package/dist/roles/config-loader.d.ts.map +1 -1
- package/dist/roles/config-loader.js +6 -6
- package/dist/roles/config-loader.js.map +1 -1
- package/dist/roles/registry.d.ts +2 -2
- package/dist/roles/registry.js +2 -2
- package/dist/server/combined-server.d.ts +20 -0
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +107 -8
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts +2 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +69 -20
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +18 -0
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/store/types/events.d.ts +1 -1
- package/dist/store/types/events.d.ts.map +1 -1
- package/dist/task/backend/index.d.ts +47 -29
- package/dist/task/backend/index.d.ts.map +1 -1
- package/dist/task/backend/index.js +109 -71
- package/dist/task/backend/index.js.map +1 -1
- package/dist/task/backend/memory.d.ts +1 -0
- package/dist/task/backend/memory.d.ts.map +1 -1
- package/dist/task/backend/memory.js +3 -0
- package/dist/task/backend/memory.js.map +1 -1
- package/dist/task/backend/opentasks/backend.d.ts +140 -0
- package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
- package/dist/task/backend/opentasks/backend.js +1023 -0
- package/dist/task/backend/opentasks/backend.js.map +1 -0
- package/dist/task/backend/opentasks/client.d.ts +337 -0
- package/dist/task/backend/opentasks/client.d.ts.map +1 -0
- package/dist/task/backend/opentasks/client.js +225 -0
- package/dist/task/backend/opentasks/client.js.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.js +195 -0
- package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
- package/dist/task/backend/opentasks/index.d.ts +21 -0
- package/dist/task/backend/opentasks/index.d.ts.map +1 -0
- package/dist/task/backend/opentasks/index.js +21 -0
- package/dist/task/backend/opentasks/index.js.map +1 -0
- package/dist/task/backend/opentasks/mapping.d.ts +48 -0
- package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
- package/dist/task/backend/opentasks/mapping.js +77 -0
- package/dist/task/backend/opentasks/mapping.js.map +1 -0
- package/dist/task/backend/types.d.ts +33 -53
- package/dist/task/backend/types.d.ts.map +1 -1
- package/dist/task/backend/types.js +7 -11
- package/dist/task/backend/types.js.map +1 -1
- package/dist/task/backend/unified-tool-provider.d.ts +57 -0
- package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
- package/dist/task/backend/unified-tool-provider.js +623 -0
- package/dist/task/backend/unified-tool-provider.js.map +1 -0
- package/dist/teams/team-loader.d.ts +2 -2
- package/dist/teams/team-loader.js +3 -3
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +2 -0
- package/dist/teams/team-runtime.js.map +1 -1
- package/docs/architecture.md +7 -6
- package/docs/configuration.md +26 -62
- package/docs/implementation-details.md +5 -5
- package/docs/implementation-summary.md +17 -17
- package/docs/plan-self-driving-support.md +4 -4
- package/docs/spec-self-driving-support.md +10 -10
- package/docs/team-templates.md +2 -2
- package/docs/teams.md +3 -3
- package/docs/troubleshooting.md +10 -11
- package/package.json +6 -4
- package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
- package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
- package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
- package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
- package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
- package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
- package/src/acp/__tests__/integration.test.ts +56 -31
- package/src/acp/__tests__/macro-agent.test.ts +16 -7
- package/src/acp/macro-agent.ts +170 -36
- package/src/acp/types.ts +46 -1
- package/src/agent/__tests__/agent-manager.test.ts +228 -2
- package/src/agent/agent-manager.ts +714 -261
- package/src/agent/types.ts +3 -1
- package/src/api/server.ts +41 -7
- package/src/auth/__tests__/token.test.ts +100 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/token.ts +82 -0
- package/src/cli/__tests__/acp.test.ts +1 -1
- package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
- package/src/cli/acp.ts +130 -72
- package/src/cli/index.ts +120 -14
- package/src/cli/mcp.ts +311 -207
- package/src/cli/parse-args.ts +54 -0
- package/src/cli/stable-instance-id.ts +14 -0
- package/src/config/project-config.ts +190 -27
- package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
- package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
- package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
- package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
- package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
- package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
- package/src/map/adapter/__tests__/event-log.test.ts +527 -0
- package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
- package/src/map/adapter/__tests__/extensions.test.ts +408 -0
- package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
- package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
- package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
- package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
- package/src/map/adapter/acp-over-map.ts +678 -66
- package/src/map/adapter/connection-manager.ts +3 -0
- package/src/map/adapter/event-log.ts +208 -0
- package/src/map/adapter/event-translator.ts +6 -6
- package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
- package/src/map/adapter/extensions/index.ts +60 -0
- package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
- package/src/map/adapter/extensions/task.ts +11 -0
- package/src/map/adapter/extensions/update-metadata.ts +126 -0
- package/src/map/adapter/index.ts +28 -0
- package/src/map/adapter/interface.ts +2 -0
- package/src/map/adapter/map-adapter.ts +312 -47
- package/src/map/adapter/subscription-manager.ts +5 -1
- package/src/map/adapter/types.ts +2 -0
- package/src/mcp/__tests__/map-client.test.ts +386 -0
- package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
- package/src/mcp/__tests__/mcp-server.test.ts +100 -1
- package/src/mcp/map-client.ts +177 -0
- package/src/mcp/mcp-server.ts +191 -100
- package/src/mcp/types.ts +6 -1
- package/src/metrics/metrics.ts +1 -1
- package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
- package/src/roles/__tests__/config-loader.test.ts +7 -7
- package/src/roles/capabilities.ts +17 -7
- package/src/roles/config-loader.ts +6 -6
- package/src/roles/registry.ts +2 -2
- package/src/server/__tests__/combined-server.test.ts +94 -21
- package/src/server/combined-server.ts +189 -33
- package/src/steering/__tests__/steering-integration.test.ts +1 -1
- package/src/store/__tests__/event-store.test.ts +196 -1
- package/src/store/__tests__/instance.test.ts +3 -3
- package/src/store/event-store.ts +80 -21
- package/src/store/types/agents.ts +15 -0
- package/src/store/types/events.ts +1 -1
- package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
- package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
- package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
- package/src/task/backend/index.ts +156 -106
- package/src/task/backend/memory.ts +4 -0
- package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
- package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
- package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
- package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
- package/src/task/backend/opentasks/backend.ts +1323 -0
- package/src/task/backend/opentasks/client.ts +652 -0
- package/src/task/backend/opentasks/daemon-manager.ts +253 -0
- package/src/task/backend/opentasks/index.ts +69 -0
- package/src/task/backend/opentasks/mapping.ts +94 -0
- package/src/task/backend/types.ts +42 -66
- package/src/task/backend/unified-tool-provider.ts +779 -0
- package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
- package/src/teams/team-loader.ts +3 -3
- package/src/teams/team-runtime.ts +2 -0
- package/test_fixtures/README.md +2 -3
- package/test_fixtures/fixtures/index.ts +0 -3
- package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
- package/test_fixtures/fixtures/repos/index.ts +1 -3
- package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
- package/test_fixtures/fixtures/repos/types.ts +0 -11
- package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
- package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
- package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
- package/vitest.config.ts +1 -1
- package/vitest.e2e.config.ts +1 -1
- package/vitest.setup.ts +1 -30
- package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
- package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
- package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
- package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
- package/.macro-agent/teams/self-driving/team.yaml +0 -103
- package/.macro-agent/teams/structured/prompts/developer.md +0 -26
- package/.macro-agent/teams/structured/prompts/lead.md +0 -25
- package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
- package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
- package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
- package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
- package/.macro-agent/teams/structured/team.yaml +0 -89
- package/docs/sudocode-integration.md +0 -383
- package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
- package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
- package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
- package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
- package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
- package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
- package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
- package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
- package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
- package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
- package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
- package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
- package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
- package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
- package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
- package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
- package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
- package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
- package/src/task/backend/sudocode/backend.ts +0 -1237
- package/src/task/backend/sudocode/client.ts +0 -515
- package/src/task/backend/sudocode/index.ts +0 -120
- package/src/task/backend/sudocode/mapping.ts +0 -93
- package/src/task/backend/sudocode/server-client.ts +0 -522
- package/src/task/backend/sudocode/standalone-client.ts +0 -623
- package/src/task/backend/sudocode/sync-policy.ts +0 -387
- package/src/task/backend/sudocode/tools.ts +0 -896
- package/src/task/backend/tool-provider.ts +0 -506
- package/test_fixtures/fixtures/sudocode/index.ts +0 -29
- package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
- package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Configuration Loader
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Layered configuration system with priority (highest to lowest):
|
|
5
|
+
* 1. Environment variables (MACRO_*, OPENTASKS_*)
|
|
6
|
+
* 2. Project config: .multiagent/config.json
|
|
7
|
+
* 3. Global config: ~/.multiagent/config.json
|
|
5
8
|
*
|
|
6
9
|
* @module config/project-config
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
import * as fs from "fs";
|
|
13
|
+
import * as os from "os";
|
|
10
14
|
import * as path from "path";
|
|
11
15
|
|
|
12
16
|
// =============================================================================
|
|
@@ -14,9 +18,49 @@ import * as path from "path";
|
|
|
14
18
|
// =============================================================================
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
21
|
+
* Typed configuration schema for multiagent.
|
|
18
22
|
*
|
|
19
|
-
* Loaded from .
|
|
23
|
+
* Loaded from .multiagent/config.json (project or global).
|
|
24
|
+
* Environment variables override file-based config.
|
|
25
|
+
*/
|
|
26
|
+
export interface MultiagentConfig {
|
|
27
|
+
/** Team template name to load on startup */
|
|
28
|
+
team?: string;
|
|
29
|
+
|
|
30
|
+
/** Server port (default: 3001) */
|
|
31
|
+
port?: number;
|
|
32
|
+
|
|
33
|
+
/** Server host (default: "localhost") */
|
|
34
|
+
host?: string;
|
|
35
|
+
|
|
36
|
+
/** Authentication config */
|
|
37
|
+
auth?: {
|
|
38
|
+
/** Disable auth entirely */
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
/** Server secret token */
|
|
41
|
+
secret?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Task backend config */
|
|
45
|
+
task?: {
|
|
46
|
+
/** Backend type: "memory" | "opentasks" */
|
|
47
|
+
backend?: string;
|
|
48
|
+
/** OpenTasks-specific config */
|
|
49
|
+
opentasks?: {
|
|
50
|
+
/** Path to OpenTasks daemon socket */
|
|
51
|
+
socket_path?: string;
|
|
52
|
+
/** Auto-start central daemon (default: true) */
|
|
53
|
+
auto_start?: boolean;
|
|
54
|
+
/** Central daemon location (default: ~/.multiagent/opentasks) */
|
|
55
|
+
central_path?: string;
|
|
56
|
+
/** Auto-connect project .opentasks/ on agent spawn (default: true) */
|
|
57
|
+
connect_on_spawn?: boolean;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Legacy project config interface (backwards compatibility).
|
|
20
64
|
*/
|
|
21
65
|
export interface ProjectConfig {
|
|
22
66
|
/** Team template name to load on startup */
|
|
@@ -31,38 +75,21 @@ export interface ProjectConfig {
|
|
|
31
75
|
// =============================================================================
|
|
32
76
|
|
|
33
77
|
/** Config directory name */
|
|
34
|
-
export const CONFIG_DIR = ".
|
|
78
|
+
export const CONFIG_DIR = ".multiagent";
|
|
35
79
|
|
|
36
80
|
/** Config file name */
|
|
37
81
|
export const CONFIG_FILE = "config.json";
|
|
38
82
|
|
|
39
83
|
// =============================================================================
|
|
40
|
-
// Loader
|
|
84
|
+
// JSON Loader (shared)
|
|
41
85
|
// =============================================================================
|
|
42
86
|
|
|
43
87
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* @param projectPath - Project root directory (default: process.cwd())
|
|
47
|
-
* @returns Absolute path to .macro-agent/config.json
|
|
48
|
-
*/
|
|
49
|
-
export function getProjectConfigPath(projectPath?: string): string {
|
|
50
|
-
const root = projectPath ?? process.cwd();
|
|
51
|
-
return path.join(root, CONFIG_DIR, CONFIG_FILE);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Load project configuration from .macro-agent/config.json.
|
|
56
|
-
*
|
|
57
|
-
* Returns empty config if the file doesn't exist.
|
|
88
|
+
* Load and parse a JSON config file.
|
|
89
|
+
* Returns empty object if file doesn't exist.
|
|
58
90
|
* Throws on invalid JSON.
|
|
59
|
-
*
|
|
60
|
-
* @param projectPath - Project root directory (default: process.cwd())
|
|
61
|
-
* @returns Parsed ProjectConfig
|
|
62
91
|
*/
|
|
63
|
-
|
|
64
|
-
const configPath = getProjectConfigPath(projectPath);
|
|
65
|
-
|
|
92
|
+
function loadJsonConfig(configPath: string): Record<string, unknown> {
|
|
66
93
|
if (!fs.existsSync(configPath)) {
|
|
67
94
|
return {};
|
|
68
95
|
}
|
|
@@ -78,7 +105,7 @@ export function loadProjectConfig(projectPath?: string): ProjectConfig {
|
|
|
78
105
|
configPath
|
|
79
106
|
);
|
|
80
107
|
}
|
|
81
|
-
return parsed as
|
|
108
|
+
return parsed as Record<string, unknown>;
|
|
82
109
|
} catch (error) {
|
|
83
110
|
if (error instanceof ProjectConfigError) throw error;
|
|
84
111
|
throw new ProjectConfigError(
|
|
@@ -89,6 +116,142 @@ export function loadProjectConfig(projectPath?: string): ProjectConfig {
|
|
|
89
116
|
}
|
|
90
117
|
}
|
|
91
118
|
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// Path Resolution
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the project config file path.
|
|
125
|
+
*
|
|
126
|
+
* @param projectPath - Project root directory (default: process.cwd())
|
|
127
|
+
* @returns Absolute path to .multiagent/config.json
|
|
128
|
+
*/
|
|
129
|
+
export function getProjectConfigPath(projectPath?: string): string {
|
|
130
|
+
const root = projectPath ?? process.cwd();
|
|
131
|
+
return path.join(root, CONFIG_DIR, CONFIG_FILE);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the global config file path.
|
|
136
|
+
*
|
|
137
|
+
* @returns Absolute path to ~/.multiagent/config.json
|
|
138
|
+
*/
|
|
139
|
+
export function getGlobalConfigPath(): string {
|
|
140
|
+
return path.join(os.homedir(), CONFIG_DIR, CONFIG_FILE);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// Individual Loaders
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Load project configuration from .multiagent/config.json.
|
|
149
|
+
*
|
|
150
|
+
* Returns empty config if the file doesn't exist.
|
|
151
|
+
* Throws on invalid JSON.
|
|
152
|
+
*
|
|
153
|
+
* @param projectPath - Project root directory (default: process.cwd())
|
|
154
|
+
* @returns Parsed ProjectConfig
|
|
155
|
+
*/
|
|
156
|
+
export function loadProjectConfig(projectPath?: string): ProjectConfig {
|
|
157
|
+
return loadJsonConfig(getProjectConfigPath(projectPath)) as ProjectConfig;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Load global configuration from ~/.multiagent/config.json.
|
|
162
|
+
*
|
|
163
|
+
* Returns empty config if the file doesn't exist.
|
|
164
|
+
* Throws on invalid JSON.
|
|
165
|
+
*
|
|
166
|
+
* @returns Parsed config
|
|
167
|
+
*/
|
|
168
|
+
export function loadGlobalConfig(): MultiagentConfig {
|
|
169
|
+
return loadJsonConfig(getGlobalConfigPath()) as MultiagentConfig;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// Merged Config (layered)
|
|
174
|
+
// =============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Deep merge two objects. Source values override target values.
|
|
178
|
+
* Only merges plain objects recursively; arrays and primitives are replaced.
|
|
179
|
+
*/
|
|
180
|
+
function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T {
|
|
181
|
+
const result = { ...target };
|
|
182
|
+
|
|
183
|
+
for (const key of Object.keys(source)) {
|
|
184
|
+
const sourceVal = source[key];
|
|
185
|
+
const targetVal = (result as Record<string, unknown>)[key];
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
sourceVal !== undefined &&
|
|
189
|
+
sourceVal !== null &&
|
|
190
|
+
typeof sourceVal === "object" &&
|
|
191
|
+
!Array.isArray(sourceVal) &&
|
|
192
|
+
typeof targetVal === "object" &&
|
|
193
|
+
targetVal !== null &&
|
|
194
|
+
!Array.isArray(targetVal)
|
|
195
|
+
) {
|
|
196
|
+
(result as Record<string, unknown>)[key] = deepMerge(
|
|
197
|
+
targetVal as Record<string, unknown>,
|
|
198
|
+
sourceVal as Record<string, unknown>
|
|
199
|
+
);
|
|
200
|
+
} else if (sourceVal !== undefined) {
|
|
201
|
+
(result as Record<string, unknown>)[key] = sourceVal;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Load merged configuration with layered priority:
|
|
210
|
+
* 1. Environment variables (highest)
|
|
211
|
+
* 2. Project .multiagent/config.json
|
|
212
|
+
* 3. Global ~/.multiagent/config.json (lowest)
|
|
213
|
+
*
|
|
214
|
+
* Only server-level settings are merged. Agent-level env vars
|
|
215
|
+
* (MACRO_AGENT_ID, MACRO_SERVER_URL, etc.) are internal wiring
|
|
216
|
+
* and not part of this config.
|
|
217
|
+
*
|
|
218
|
+
* @param projectPath - Project root directory (default: process.cwd())
|
|
219
|
+
* @returns Fully merged MultiagentConfig
|
|
220
|
+
*/
|
|
221
|
+
export function loadMergedConfig(projectPath?: string): MultiagentConfig {
|
|
222
|
+
const globalConfig = loadGlobalConfig();
|
|
223
|
+
const projectConfig = loadProjectConfig(projectPath);
|
|
224
|
+
|
|
225
|
+
// Layer 1: global (lowest priority)
|
|
226
|
+
// Layer 2: project overrides global
|
|
227
|
+
const merged: MultiagentConfig = deepMerge(
|
|
228
|
+
globalConfig as Record<string, unknown>,
|
|
229
|
+
projectConfig as Record<string, unknown>,
|
|
230
|
+
) as MultiagentConfig;
|
|
231
|
+
|
|
232
|
+
// Layer 3: env vars override everything (server-level only)
|
|
233
|
+
if (process.env.MACRO_TASK_BACKEND) {
|
|
234
|
+
merged.task = { ...(merged.task ?? {}), backend: process.env.MACRO_TASK_BACKEND };
|
|
235
|
+
}
|
|
236
|
+
if (process.env.OPENTASKS_SOCKET_PATH) {
|
|
237
|
+
merged.task = {
|
|
238
|
+
...(merged.task ?? {}),
|
|
239
|
+
opentasks: {
|
|
240
|
+
...(merged.task?.opentasks ?? {}),
|
|
241
|
+
socket_path: process.env.OPENTASKS_SOCKET_PATH,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (process.env.MACRO_SERVER_SECRET) {
|
|
246
|
+
merged.auth = { ...(merged.auth ?? {}), secret: process.env.MACRO_SERVER_SECRET };
|
|
247
|
+
}
|
|
248
|
+
if (process.env.MACRO_NO_AUTH === "true") {
|
|
249
|
+
merged.auth = { ...(merged.auth ?? {}), disabled: true };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return merged;
|
|
253
|
+
}
|
|
254
|
+
|
|
92
255
|
// =============================================================================
|
|
93
256
|
// Errors
|
|
94
257
|
// =============================================================================
|
|
@@ -74,7 +74,7 @@ function createCascadeAdapter(harness: TestHarness): CascadeAgentManager {
|
|
|
74
74
|
}
|
|
75
75
|
// Update agent state in EventStore
|
|
76
76
|
harness.eventStore.emit({
|
|
77
|
-
type: "
|
|
77
|
+
type: "stop",
|
|
78
78
|
source: { agent_id: agentId },
|
|
79
79
|
payload: { agent_id: agentId, reason },
|
|
80
80
|
});
|
|
@@ -742,8 +742,26 @@ describe("MAPAdapter handleStopAgent (map/agents/stop)", () => {
|
|
|
742
742
|
).rejects.toThrow("Failed to stop agent: Agent not found");
|
|
743
743
|
});
|
|
744
744
|
|
|
745
|
-
it("should emit agent.state.changed event via
|
|
746
|
-
|
|
745
|
+
it("should emit agent.state.changed event via lifecycle listener", async () => {
|
|
746
|
+
// Capture lifecycle callback so we can fire it from the mock terminate
|
|
747
|
+
let lifecycleCallback: ((event: unknown) => void) | undefined;
|
|
748
|
+
|
|
749
|
+
await setupAdapter({
|
|
750
|
+
onLifecycleEvent: vi.fn().mockImplementation((cb: (event: unknown) => void) => {
|
|
751
|
+
lifecycleCallback = cb;
|
|
752
|
+
return () => {};
|
|
753
|
+
}),
|
|
754
|
+
terminate: vi.fn().mockImplementation(async () => {
|
|
755
|
+
// Simulate what real agentManager.terminate() does: fire lifecycle
|
|
756
|
+
if (lifecycleCallback) {
|
|
757
|
+
lifecycleCallback({
|
|
758
|
+
type: "stopped",
|
|
759
|
+
agent: { id: "agent-1", name: "test-agent", role: "worker", state: "stopped" },
|
|
760
|
+
reason: "user stopped",
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}),
|
|
764
|
+
} as unknown as Partial<AgentManager>);
|
|
747
765
|
|
|
748
766
|
const impl = adapter as unknown as {
|
|
749
767
|
handleStopAgent: (
|
|
@@ -761,10 +779,10 @@ describe("MAPAdapter handleStopAgent (map/agents/stop)", () => {
|
|
|
761
779
|
{ agentId: "agent-1" as AgentId, reason: "user stopped" },
|
|
762
780
|
);
|
|
763
781
|
|
|
764
|
-
// Verify emitEvent was called with the right event shape
|
|
782
|
+
// Verify emitEvent was called with the right event shape via lifecycle
|
|
765
783
|
expect(emitSpy).toHaveBeenCalledWith(
|
|
766
784
|
expect.objectContaining({
|
|
767
|
-
type: "
|
|
785
|
+
type: "agent_state_changed",
|
|
768
786
|
agentId: "agent-1",
|
|
769
787
|
data: expect.objectContaining({
|
|
770
788
|
agentId: "agent-1",
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP-over-MAP _macro/getModels Tests
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the _macro/getModels extension method correctly returns
|
|
5
|
+
* model information from the agent's session via the ACP-over-MAP handler.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
9
|
+
import { ACPOverMAPHandler } from "../acp-over-map.js";
|
|
10
|
+
import type { ACPEnvelope } from "../acp-over-map.js";
|
|
11
|
+
import { createEventStore, type EventStore } from "../../../store/event-store.js";
|
|
12
|
+
import type { AgentManager } from "../../../agent/agent-manager.js";
|
|
13
|
+
import type { TaskManager } from "../../../task/task-manager.js";
|
|
14
|
+
import type { Agent, Task } from "../../../store/types/index.js";
|
|
15
|
+
import type { AgentId } from "../../../store/types/index.js";
|
|
16
|
+
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────
|
|
18
|
+
// Helpers
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function createMockAgent(overrides: Partial<Agent> = {}): Agent {
|
|
22
|
+
return {
|
|
23
|
+
id: "agent-1" as AgentId,
|
|
24
|
+
session_id: "session-1",
|
|
25
|
+
state: "running",
|
|
26
|
+
task: "Test task",
|
|
27
|
+
task_id: "task-1",
|
|
28
|
+
parent: null,
|
|
29
|
+
lineage: [],
|
|
30
|
+
config: {},
|
|
31
|
+
cwd: "/test/cwd",
|
|
32
|
+
plan: [],
|
|
33
|
+
created_at: Date.now(),
|
|
34
|
+
started_at: Date.now(),
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createMockTask(overrides: Partial<Task> = {}): Task {
|
|
40
|
+
return {
|
|
41
|
+
id: "task-1",
|
|
42
|
+
description: "Test task",
|
|
43
|
+
status: "in_progress",
|
|
44
|
+
created_by: "agent-1",
|
|
45
|
+
created_at: Date.now(),
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createMockAgentManager(
|
|
51
|
+
sessionOverride?: unknown,
|
|
52
|
+
): AgentManager {
|
|
53
|
+
const mockAgent = createMockAgent();
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
spawn: vi.fn().mockResolvedValue({
|
|
57
|
+
id: "agent-new",
|
|
58
|
+
session_id: "session-new",
|
|
59
|
+
agent: createMockAgent({ id: "agent-new" as AgentId, session_id: "session-new" }),
|
|
60
|
+
session: {},
|
|
61
|
+
}),
|
|
62
|
+
get: vi.fn().mockReturnValue(mockAgent),
|
|
63
|
+
list: vi.fn().mockReturnValue([mockAgent]),
|
|
64
|
+
listHeadManagers: vi.fn().mockReturnValue([mockAgent]),
|
|
65
|
+
getChildren: vi.fn().mockReturnValue([]),
|
|
66
|
+
getHierarchy: vi.fn().mockReturnValue({
|
|
67
|
+
root: { agent: mockAgent, children: [] },
|
|
68
|
+
depth: 1,
|
|
69
|
+
totalAgents: 1,
|
|
70
|
+
}),
|
|
71
|
+
getOrCreateHeadManager: vi.fn().mockResolvedValue({
|
|
72
|
+
id: "agent-1",
|
|
73
|
+
session_id: "session-1",
|
|
74
|
+
agent: mockAgent,
|
|
75
|
+
session: {},
|
|
76
|
+
}),
|
|
77
|
+
hasActiveSession: vi.fn().mockReturnValue(true),
|
|
78
|
+
resume: vi.fn().mockResolvedValue({
|
|
79
|
+
id: "agent-1",
|
|
80
|
+
session_id: "session-1",
|
|
81
|
+
agent: mockAgent,
|
|
82
|
+
session: {},
|
|
83
|
+
}),
|
|
84
|
+
terminate: vi.fn().mockResolvedValue(undefined),
|
|
85
|
+
prompt: vi.fn().mockReturnValue({
|
|
86
|
+
[Symbol.asyncIterator]: async function* () {},
|
|
87
|
+
}),
|
|
88
|
+
getSession: vi.fn().mockReturnValue(sessionOverride ?? null),
|
|
89
|
+
onLifecycleEvent: vi.fn().mockReturnValue(() => {}),
|
|
90
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
91
|
+
respondToPermission: vi.fn().mockReturnValue(true),
|
|
92
|
+
cancelPermission: vi.fn().mockReturnValue(true),
|
|
93
|
+
} as unknown as AgentManager;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createMockTaskManager(): TaskManager {
|
|
97
|
+
return {
|
|
98
|
+
get: vi.fn().mockReturnValue(createMockTask()),
|
|
99
|
+
list: vi.fn().mockReturnValue([createMockTask()]),
|
|
100
|
+
create: vi.fn().mockReturnValue(createMockTask()),
|
|
101
|
+
} as unknown as TaskManager;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Build an ACP envelope for processRequest */
|
|
105
|
+
function envelope(
|
|
106
|
+
streamId: string,
|
|
107
|
+
method: string,
|
|
108
|
+
params?: unknown,
|
|
109
|
+
sessionId?: string,
|
|
110
|
+
): ACPEnvelope {
|
|
111
|
+
return {
|
|
112
|
+
acp: {
|
|
113
|
+
jsonrpc: "2.0",
|
|
114
|
+
id: `${streamId}-${method}-${Date.now()}`,
|
|
115
|
+
method,
|
|
116
|
+
params,
|
|
117
|
+
},
|
|
118
|
+
acpContext: {
|
|
119
|
+
streamId,
|
|
120
|
+
sessionId,
|
|
121
|
+
direction: "client-to-agent",
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─────────────────────────────────────────────────────────────────
|
|
127
|
+
// Tests
|
|
128
|
+
// ─────────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
describe("ACP-over-MAP _macro/getModels", () => {
|
|
131
|
+
let eventStore: EventStore;
|
|
132
|
+
let handler: ACPOverMAPHandler;
|
|
133
|
+
|
|
134
|
+
afterEach(async () => {
|
|
135
|
+
await eventStore.close();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
async function setup(sessionOverride?: unknown) {
|
|
139
|
+
eventStore = await createEventStore({ inMemory: true });
|
|
140
|
+
const agentManager = createMockAgentManager(sessionOverride);
|
|
141
|
+
const taskManager = createMockTaskManager();
|
|
142
|
+
|
|
143
|
+
handler = new ACPOverMAPHandler({
|
|
144
|
+
agentManager,
|
|
145
|
+
eventStore,
|
|
146
|
+
taskManager,
|
|
147
|
+
defaultCwd: "/test/cwd",
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return { agentManager, taskManager };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Register an agent in the EventStore so session mapper can resolve it */
|
|
154
|
+
function registerAgent(agentId: string, sessionId: string): void {
|
|
155
|
+
eventStore.emit({
|
|
156
|
+
type: "spawn",
|
|
157
|
+
source: { agent_id: agentId },
|
|
158
|
+
payload: {
|
|
159
|
+
agent_id: agentId,
|
|
160
|
+
session_id: sessionId,
|
|
161
|
+
task: "Test task",
|
|
162
|
+
task_id: "task-1",
|
|
163
|
+
cwd: "/test/cwd",
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
eventStore.emit({
|
|
167
|
+
type: "lifecycle",
|
|
168
|
+
source: { agent_id: agentId },
|
|
169
|
+
payload: {
|
|
170
|
+
agent_id: agentId,
|
|
171
|
+
action: "started",
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Initialize a stream and create a session, returning the sessionId */
|
|
177
|
+
async function initAndCreateSession(
|
|
178
|
+
streamId: string,
|
|
179
|
+
targetAgentId: AgentId = "agent-1" as AgentId,
|
|
180
|
+
): Promise<string> {
|
|
181
|
+
await handler.processRequest(
|
|
182
|
+
targetAgentId,
|
|
183
|
+
envelope(streamId, "initialize", {
|
|
184
|
+
protocolVersion: 1,
|
|
185
|
+
capabilities: {},
|
|
186
|
+
clientInfo: { name: "test", version: "1.0" },
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const sessionResult = await handler.processRequest(
|
|
191
|
+
targetAgentId,
|
|
192
|
+
envelope(streamId, "session/new", {
|
|
193
|
+
cwd: "/test",
|
|
194
|
+
mcpServers: [],
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const sessionId = (sessionResult.acp.result as { sessionId?: string })?.sessionId;
|
|
199
|
+
if (!sessionId) throw new Error("session/new did not return sessionId");
|
|
200
|
+
|
|
201
|
+
registerAgent(targetAgentId, sessionId);
|
|
202
|
+
|
|
203
|
+
return sessionId;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
it("should return empty models when session is not found", async () => {
|
|
207
|
+
// getSession returns null — no session available
|
|
208
|
+
await setup(null);
|
|
209
|
+
|
|
210
|
+
const streamId = "test-stream-1";
|
|
211
|
+
const agentId = "agent-1" as AgentId;
|
|
212
|
+
const sessionId = await initAndCreateSession(streamId, agentId);
|
|
213
|
+
|
|
214
|
+
const result = await handler.processRequest(
|
|
215
|
+
agentId,
|
|
216
|
+
envelope(streamId, "_macro/getModels", { sessionId }),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
expect(result.acp.error).toBeUndefined();
|
|
220
|
+
const data = result.acp.result as {
|
|
221
|
+
currentModelId: string | null;
|
|
222
|
+
availableModels: Array<{ modelId: string; name: string }>;
|
|
223
|
+
};
|
|
224
|
+
expect(data.currentModelId).toBeNull();
|
|
225
|
+
expect(data.availableModels).toEqual([]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should return models from session.models when available", async () => {
|
|
229
|
+
// Mock session with models array (like Claude Code returns)
|
|
230
|
+
const mockSession = {
|
|
231
|
+
id: "acp-session-123",
|
|
232
|
+
models: ["default", "sonnet"],
|
|
233
|
+
};
|
|
234
|
+
await setup(mockSession);
|
|
235
|
+
|
|
236
|
+
const streamId = "test-stream-2";
|
|
237
|
+
const agentId = "agent-1" as AgentId;
|
|
238
|
+
const sessionId = await initAndCreateSession(streamId, agentId);
|
|
239
|
+
|
|
240
|
+
const result = await handler.processRequest(
|
|
241
|
+
agentId,
|
|
242
|
+
envelope(streamId, "_macro/getModels", { sessionId }),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(result.acp.error).toBeUndefined();
|
|
246
|
+
const data = result.acp.result as {
|
|
247
|
+
currentModelId: string | null;
|
|
248
|
+
availableModels: Array<{ modelId: string; name: string }>;
|
|
249
|
+
};
|
|
250
|
+
expect(data.currentModelId).toBe("default");
|
|
251
|
+
expect(data.availableModels).toEqual([
|
|
252
|
+
{ modelId: "default", name: "default" },
|
|
253
|
+
{ modelId: "sonnet", name: "sonnet" },
|
|
254
|
+
]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should return models from clientHandler when available", async () => {
|
|
258
|
+
// Mock session with clientHandler.getSessionModelInfo
|
|
259
|
+
const mockSession = {
|
|
260
|
+
id: "acp-session-456",
|
|
261
|
+
models: ["fallback-model"],
|
|
262
|
+
clientHandler: {
|
|
263
|
+
getSessionModelInfo: (_id: string) => ({
|
|
264
|
+
currentModelId: "opus",
|
|
265
|
+
availableModels: [
|
|
266
|
+
{ modelId: "opus", name: "Claude Opus 4" },
|
|
267
|
+
{ modelId: "sonnet", name: "Claude Sonnet 4.5" },
|
|
268
|
+
],
|
|
269
|
+
}),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
await setup(mockSession);
|
|
273
|
+
|
|
274
|
+
const streamId = "test-stream-3";
|
|
275
|
+
const agentId = "agent-1" as AgentId;
|
|
276
|
+
const sessionId = await initAndCreateSession(streamId, agentId);
|
|
277
|
+
|
|
278
|
+
const result = await handler.processRequest(
|
|
279
|
+
agentId,
|
|
280
|
+
envelope(streamId, "_macro/getModels", { sessionId }),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
expect(result.acp.error).toBeUndefined();
|
|
284
|
+
const data = result.acp.result as {
|
|
285
|
+
currentModelId: string | null;
|
|
286
|
+
availableModels: Array<{ modelId: string; name: string }>;
|
|
287
|
+
};
|
|
288
|
+
// clientHandler should take priority over session.models
|
|
289
|
+
expect(data.currentModelId).toBe("opus");
|
|
290
|
+
expect(data.availableModels).toEqual([
|
|
291
|
+
{ modelId: "opus", name: "Claude Opus 4" },
|
|
292
|
+
{ modelId: "sonnet", name: "Claude Sonnet 4.5" },
|
|
293
|
+
]);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("should fall back to session.models when clientHandler returns empty", async () => {
|
|
297
|
+
// clientHandler returns empty, should fall back to session.models
|
|
298
|
+
const mockSession = {
|
|
299
|
+
id: "acp-session-789",
|
|
300
|
+
models: ["haiku"],
|
|
301
|
+
clientHandler: {
|
|
302
|
+
getSessionModelInfo: () => ({
|
|
303
|
+
currentModelId: null,
|
|
304
|
+
availableModels: [],
|
|
305
|
+
}),
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
await setup(mockSession);
|
|
309
|
+
|
|
310
|
+
const streamId = "test-stream-4";
|
|
311
|
+
const agentId = "agent-1" as AgentId;
|
|
312
|
+
const sessionId = await initAndCreateSession(streamId, agentId);
|
|
313
|
+
|
|
314
|
+
const result = await handler.processRequest(
|
|
315
|
+
agentId,
|
|
316
|
+
envelope(streamId, "_macro/getModels", { sessionId }),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
expect(result.acp.error).toBeUndefined();
|
|
320
|
+
const data = result.acp.result as {
|
|
321
|
+
currentModelId: string | null;
|
|
322
|
+
availableModels: Array<{ modelId: string; name: string }>;
|
|
323
|
+
};
|
|
324
|
+
expect(data.currentModelId).toBe("haiku");
|
|
325
|
+
expect(data.availableModels).toEqual([
|
|
326
|
+
{ modelId: "haiku", name: "haiku" },
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should return empty when session has no models and no clientHandler", async () => {
|
|
331
|
+
// Session exists but has no models
|
|
332
|
+
const mockSession = {
|
|
333
|
+
id: "acp-session-empty",
|
|
334
|
+
models: [],
|
|
335
|
+
};
|
|
336
|
+
await setup(mockSession);
|
|
337
|
+
|
|
338
|
+
const streamId = "test-stream-5";
|
|
339
|
+
const agentId = "agent-1" as AgentId;
|
|
340
|
+
const sessionId = await initAndCreateSession(streamId, agentId);
|
|
341
|
+
|
|
342
|
+
const result = await handler.processRequest(
|
|
343
|
+
agentId,
|
|
344
|
+
envelope(streamId, "_macro/getModels", { sessionId }),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
expect(result.acp.error).toBeUndefined();
|
|
348
|
+
const data = result.acp.result as {
|
|
349
|
+
currentModelId: string | null;
|
|
350
|
+
availableModels: Array<{ modelId: string; name: string }>;
|
|
351
|
+
};
|
|
352
|
+
expect(data.currentModelId).toBeNull();
|
|
353
|
+
expect(data.availableModels).toEqual([]);
|
|
354
|
+
});
|
|
355
|
+
});
|