macro-agent 0.0.17 → 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 +17 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +183 -55
- 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 +23 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +482 -55
- 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 +4 -0
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +302 -30
- 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 +7 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +91 -8
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +23 -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__/history.test.ts +8 -4
- 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 +230 -62
- 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 +820 -0
- package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
- package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
- 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 +777 -92
- 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 +373 -38
- 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 +236 -1
- package/src/store/__tests__/instance.test.ts +3 -3
- package/src/store/event-store.ts +109 -8
- package/src/store/types/agents.ts +16 -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
package/src/store/event-store.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
EventFilter,
|
|
24
24
|
Agent,
|
|
25
25
|
AgentState,
|
|
26
|
+
AgentMetadataUpdate,
|
|
26
27
|
Task,
|
|
27
28
|
TaskStatus,
|
|
28
29
|
QueuedMessage,
|
|
@@ -131,8 +132,8 @@ function createTabularBetterSqlite3Persister(
|
|
|
131
132
|
undefined,
|
|
132
133
|
// onIgnoredError
|
|
133
134
|
(error: any) => console.warn('[EventStore] Persister error:', error),
|
|
134
|
-
// destroy
|
|
135
|
-
() =>
|
|
135
|
+
// destroy — do NOT close DB here; close() handles it after the persister drains
|
|
136
|
+
() => {},
|
|
136
137
|
// persist mode (1 = StoreOnly)
|
|
137
138
|
1 as any,
|
|
138
139
|
// thing (the db instance)
|
|
@@ -203,6 +204,8 @@ export interface EventStore {
|
|
|
203
204
|
// Agent view
|
|
204
205
|
getAgent(agentId: AgentId): Agent | null;
|
|
205
206
|
listAgents(filter?: { state?: AgentState; parent?: AgentId | null }): Agent[];
|
|
207
|
+
updateAgentPlan(agentId: AgentId, plan: Array<{ content: string; priority: string; status: string }>): void;
|
|
208
|
+
updateAgentMetadata(agentId: AgentId, updates: AgentMetadataUpdate): void;
|
|
206
209
|
|
|
207
210
|
// Task view
|
|
208
211
|
getTask(taskId: TaskId): Task | null;
|
|
@@ -519,6 +522,51 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
|
|
|
519
522
|
return agents;
|
|
520
523
|
}
|
|
521
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Update agent metadata fields (name, plan, metadata).
|
|
527
|
+
* Only provided fields are updated. Metadata is shallow-merged with existing.
|
|
528
|
+
*/
|
|
529
|
+
function updateAgentMetadata(
|
|
530
|
+
agentId: AgentId,
|
|
531
|
+
updates: AgentMetadataUpdate,
|
|
532
|
+
): void {
|
|
533
|
+
const row = store.getRow('agents', agentId);
|
|
534
|
+
if (!row.id) return;
|
|
535
|
+
|
|
536
|
+
const partial: Record<string, string | number | boolean> = {
|
|
537
|
+
last_activity_at: Date.now(),
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
if (updates.name !== undefined) {
|
|
541
|
+
partial.name = updates.name;
|
|
542
|
+
}
|
|
543
|
+
if (updates.plan !== undefined) {
|
|
544
|
+
partial.plan = JSON.stringify(updates.plan);
|
|
545
|
+
}
|
|
546
|
+
if (updates.metadata !== undefined) {
|
|
547
|
+
// Shallow merge with existing metadata
|
|
548
|
+
const existing = row.metadata ? JSON.parse(row.metadata as string) : {};
|
|
549
|
+
partial.metadata = JSON.stringify({ ...existing, ...updates.metadata });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
store.setPartialRow('agents', agentId, partial);
|
|
553
|
+
|
|
554
|
+
const agent = rowToAgent(store.getRow('agents', agentId));
|
|
555
|
+
notifyAgentChange(agentId, agent);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Update an agent's plan entries (persisted to SQLite via TinyBase).
|
|
560
|
+
* Convenience wrapper around updateAgentMetadata.
|
|
561
|
+
*/
|
|
562
|
+
function updateAgentPlan(
|
|
563
|
+
agentId: AgentId,
|
|
564
|
+
plan: Array<{ content: string; priority: string; status: string }>,
|
|
565
|
+
): void {
|
|
566
|
+
updateAgentMetadata(agentId, { plan });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
|
|
522
570
|
/**
|
|
523
571
|
* Get task by ID
|
|
524
572
|
*/
|
|
@@ -927,8 +975,18 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
|
|
|
927
975
|
*/
|
|
928
976
|
async function close(): Promise<void> {
|
|
929
977
|
if (persister) {
|
|
978
|
+
// Stop auto-save first to prevent race conditions between
|
|
979
|
+
// auto-save callbacks and the explicit save/destroy sequence.
|
|
980
|
+
await persister.stopAutoSave();
|
|
930
981
|
await persister.save();
|
|
982
|
+
// Destroy the persister (removes store listeners) BEFORE closing the DB.
|
|
983
|
+
// The destroy callback is a no-op — we close the DB ourselves below
|
|
984
|
+
// after giving TinyBase's internal async queues time to drain.
|
|
931
985
|
persister.destroy();
|
|
986
|
+
// Allow any in-flight TinyBase microtasks to settle before closing
|
|
987
|
+
// the database connection. Without this, pending writes from store
|
|
988
|
+
// change listeners can race against db.close().
|
|
989
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
932
990
|
}
|
|
933
991
|
if (db) {
|
|
934
992
|
db.close();
|
|
@@ -1203,6 +1261,8 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
|
|
|
1203
1261
|
// Views
|
|
1204
1262
|
getAgent,
|
|
1205
1263
|
listAgents,
|
|
1264
|
+
updateAgentPlan,
|
|
1265
|
+
updateAgentMetadata,
|
|
1206
1266
|
getTask,
|
|
1207
1267
|
listTasks,
|
|
1208
1268
|
getMessages,
|
|
@@ -1269,6 +1329,25 @@ function initializeTables(store: Store): void {
|
|
|
1269
1329
|
* Rebuild materialized views from the event log
|
|
1270
1330
|
*/
|
|
1271
1331
|
function rebuildViews(store: Store): void {
|
|
1332
|
+
// Preserve out-of-band agent fields that aren't derived from events.
|
|
1333
|
+
// These fields are written directly (not through events),
|
|
1334
|
+
// so they would be lost when we clear and replay.
|
|
1335
|
+
const OUT_OF_BAND_FIELDS = ['plan', 'name', 'metadata'] as const;
|
|
1336
|
+
const savedOutOfBand = new Map<string, Record<string, string>>();
|
|
1337
|
+
for (const rowId of store.getRowIds('agents')) {
|
|
1338
|
+
const row = store.getRow('agents', rowId);
|
|
1339
|
+
const saved: Record<string, string> = {};
|
|
1340
|
+
for (const field of OUT_OF_BAND_FIELDS) {
|
|
1341
|
+
const val = row[field] as string | undefined;
|
|
1342
|
+
if (val && val !== '' && val !== '[]') {
|
|
1343
|
+
saved[field] = val;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (Object.keys(saved).length > 0) {
|
|
1347
|
+
savedOutOfBand.set(rowId, saved);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1272
1351
|
// Clear existing views
|
|
1273
1352
|
for (const rowId of store.getRowIds('agents')) {
|
|
1274
1353
|
store.delRow('agents', rowId);
|
|
@@ -1324,6 +1403,14 @@ function rebuildViews(store: Store): void {
|
|
|
1324
1403
|
for (const event of events) {
|
|
1325
1404
|
applyEventToViews(store, event, noop, noop, noop, noop, noop, noop);
|
|
1326
1405
|
}
|
|
1406
|
+
|
|
1407
|
+
// Restore out-of-band agent fields preserved before the wipe
|
|
1408
|
+
for (const [agentId, fields] of savedOutOfBand) {
|
|
1409
|
+
const row = store.getRow('agents', agentId);
|
|
1410
|
+
if (row.id) {
|
|
1411
|
+
store.setPartialRow('agents', agentId, fields);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1327
1414
|
}
|
|
1328
1415
|
|
|
1329
1416
|
/**
|
|
@@ -1343,8 +1430,8 @@ function applyEventToViews(
|
|
|
1343
1430
|
case 'spawn':
|
|
1344
1431
|
applySpawnEvent(store, event, notifyAgentChange);
|
|
1345
1432
|
break;
|
|
1346
|
-
case '
|
|
1347
|
-
|
|
1433
|
+
case 'stop':
|
|
1434
|
+
applyStopEvent(store, event, notifyAgentChange);
|
|
1348
1435
|
break;
|
|
1349
1436
|
case 'status':
|
|
1350
1437
|
applyStatusEvent(store, event, notifyAgentChange);
|
|
@@ -1411,6 +1498,7 @@ function applySpawnEvent(
|
|
|
1411
1498
|
|
|
1412
1499
|
store.setRow('agents', agentId, {
|
|
1413
1500
|
id: agentId,
|
|
1501
|
+
name: '',
|
|
1414
1502
|
session_id: payload.session_id,
|
|
1415
1503
|
provider_session_id: '',
|
|
1416
1504
|
parent: parent ?? '',
|
|
@@ -1422,6 +1510,8 @@ function applySpawnEvent(
|
|
|
1422
1510
|
role: payload.role ?? '',
|
|
1423
1511
|
config: JSON.stringify(payload.config ?? {}),
|
|
1424
1512
|
cwd: payload.cwd ?? process.cwd(),
|
|
1513
|
+
plan: '[]',
|
|
1514
|
+
metadata: '',
|
|
1425
1515
|
created_at: event.timestamp,
|
|
1426
1516
|
started_at: 0,
|
|
1427
1517
|
stopped_at: 0,
|
|
@@ -1433,9 +1523,9 @@ function applySpawnEvent(
|
|
|
1433
1523
|
}
|
|
1434
1524
|
|
|
1435
1525
|
/**
|
|
1436
|
-
* Apply
|
|
1526
|
+
* Apply stop event to agents view
|
|
1437
1527
|
*/
|
|
1438
|
-
function
|
|
1528
|
+
function applyStopEvent(
|
|
1439
1529
|
store: Store,
|
|
1440
1530
|
event: Event,
|
|
1441
1531
|
notify: (agentId: AgentId, agent: Agent | null) => void,
|
|
@@ -1577,6 +1667,7 @@ function applyTaskEvent(
|
|
|
1577
1667
|
description: string;
|
|
1578
1668
|
parent_task?: TaskId;
|
|
1579
1669
|
inputs?: Record<string, unknown>;
|
|
1670
|
+
tags?: string[];
|
|
1580
1671
|
retryPolicy?: unknown;
|
|
1581
1672
|
};
|
|
1582
1673
|
store.setRow('tasks', taskId, {
|
|
@@ -1587,6 +1678,7 @@ function applyTaskEvent(
|
|
|
1587
1678
|
parent_task: details.parent_task ?? '',
|
|
1588
1679
|
subtasks: JSON.stringify([]),
|
|
1589
1680
|
blockers: JSON.stringify([]),
|
|
1681
|
+
tags: details.tags ? JSON.stringify(details.tags) : '',
|
|
1590
1682
|
created_at: event.timestamp,
|
|
1591
1683
|
started_at: 0,
|
|
1592
1684
|
completed_at: 0,
|
|
@@ -1633,10 +1725,15 @@ function applyTaskEvent(
|
|
|
1633
1725
|
break;
|
|
1634
1726
|
}
|
|
1635
1727
|
}
|
|
1636
|
-
|
|
1728
|
+
const updates: Record<string, string | number | boolean> = {
|
|
1637
1729
|
assigned_agent: '',
|
|
1638
1730
|
agent_history: JSON.stringify(history),
|
|
1639
|
-
}
|
|
1731
|
+
};
|
|
1732
|
+
// Reset to pending if task was only assigned (not yet started)
|
|
1733
|
+
if (existing.status === 'assigned') {
|
|
1734
|
+
updates.status = 'pending';
|
|
1735
|
+
}
|
|
1736
|
+
store.setPartialRow('tasks', taskId, updates);
|
|
1640
1737
|
break;
|
|
1641
1738
|
}
|
|
1642
1739
|
case 'status_change': {
|
|
@@ -1759,6 +1856,7 @@ function rowToAgent(row: Record<string, unknown>): Agent {
|
|
|
1759
1856
|
const stopReason = row.stop_reason as string;
|
|
1760
1857
|
return {
|
|
1761
1858
|
id: row.id as AgentId,
|
|
1859
|
+
name: (row.name as string) || undefined,
|
|
1762
1860
|
session_id: row.session_id as string,
|
|
1763
1861
|
provider_session_id: (row.provider_session_id as string) || undefined,
|
|
1764
1862
|
parent: (row.parent as string) || null,
|
|
@@ -1770,6 +1868,8 @@ function rowToAgent(row: Record<string, unknown>): Agent {
|
|
|
1770
1868
|
role: (row.role as string) || undefined,
|
|
1771
1869
|
config: row.config ? JSON.parse(row.config as string) : {},
|
|
1772
1870
|
cwd: (row.cwd as string) || process.cwd(),
|
|
1871
|
+
plan: row.plan ? JSON.parse(row.plan as string) : [],
|
|
1872
|
+
metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined,
|
|
1773
1873
|
created_at: row.created_at as Timestamp,
|
|
1774
1874
|
started_at: (row.started_at as number) || undefined,
|
|
1775
1875
|
stopped_at: (row.stopped_at as number) || undefined,
|
|
@@ -1797,6 +1897,7 @@ function rowToTask(row: Record<string, unknown>): Task {
|
|
|
1797
1897
|
outputs: row.outputs ? JSON.parse(row.outputs as string) : undefined,
|
|
1798
1898
|
artifacts: row.artifacts ? JSON.parse(row.artifacts as string) : undefined,
|
|
1799
1899
|
agent_history: row.agent_history ? JSON.parse(row.agent_history as string) : undefined,
|
|
1900
|
+
tags: row.tags ? JSON.parse(row.tags as string) : undefined,
|
|
1800
1901
|
retryPolicy: row.retry_policy ? JSON.parse(row.retry_policy as string) : undefined,
|
|
1801
1902
|
retryState: row.retry_state ? JSON.parse(row.retry_state as string) : undefined,
|
|
1802
1903
|
};
|
|
@@ -28,6 +28,8 @@ export interface AgentConfig {
|
|
|
28
28
|
// Agent record in materialized view
|
|
29
29
|
export interface Agent {
|
|
30
30
|
id: AgentId;
|
|
31
|
+
/** Optional display name (set via rename, not derived from events) */
|
|
32
|
+
name?: string;
|
|
31
33
|
session_id: SessionId;
|
|
32
34
|
/** Session ID from the underlying agent provider (e.g., Claude Code UUID for --resume) */
|
|
33
35
|
provider_session_id?: string;
|
|
@@ -40,9 +42,23 @@ export interface Agent {
|
|
|
40
42
|
role?: string;
|
|
41
43
|
config: AgentConfig;
|
|
42
44
|
cwd: string;
|
|
45
|
+
plan: Array<{ content: string; priority: string; status: string }>;
|
|
46
|
+
/** Arbitrary metadata (persisted out-of-band, not derived from events) */
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
43
48
|
created_at: Timestamp;
|
|
44
49
|
started_at?: Timestamp;
|
|
45
50
|
stopped_at?: Timestamp;
|
|
46
51
|
/** Last time this agent emitted an event (for health monitoring) */
|
|
47
52
|
last_activity_at?: Timestamp;
|
|
48
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Partial update for agent metadata fields.
|
|
57
|
+
* All fields are optional — only provided fields are updated.
|
|
58
|
+
* `metadata` is merged (shallow) with existing metadata.
|
|
59
|
+
*/
|
|
60
|
+
export interface AgentMetadataUpdate {
|
|
61
|
+
name?: string;
|
|
62
|
+
plan?: Array<{ content: string; priority: string; status: string }>;
|
|
63
|
+
metadata?: Record<string, unknown>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for createTaskBackend factory function
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Memory backend returns no socketPath
|
|
6
|
+
* - OpenTasks backend with explicit socketPath returns it
|
|
7
|
+
* - OpenTasks backend with autoStart returns daemon's socketPath
|
|
8
|
+
* - Unknown backend type throws
|
|
9
|
+
*
|
|
10
|
+
* @module task/backend/__tests__/create-task-backend.test
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
14
|
+
import { createEventStore, type EventStore } from "../../../store/event-store.js";
|
|
15
|
+
|
|
16
|
+
// Mock opentasks to avoid real daemon operations
|
|
17
|
+
vi.mock("opentasks", () => ({
|
|
18
|
+
checkExistingDaemon: vi.fn(),
|
|
19
|
+
createDaemonWithStore: vi.fn(),
|
|
20
|
+
OpenTasksClient: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Mock the opentasks client module
|
|
24
|
+
vi.mock("../opentasks/client.js", () => ({
|
|
25
|
+
IPCOpenTasksClient: vi.fn().mockImplementation(() => ({
|
|
26
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
disconnect: vi.fn(),
|
|
28
|
+
isConnected: vi.fn().mockReturnValue(true),
|
|
29
|
+
call: vi.fn().mockResolvedValue({}),
|
|
30
|
+
createIssue: vi.fn(),
|
|
31
|
+
getIssue: vi.fn(),
|
|
32
|
+
updateIssue: vi.fn(),
|
|
33
|
+
deleteIssue: vi.fn(),
|
|
34
|
+
listIssues: vi.fn(),
|
|
35
|
+
getReadyIssues: vi.fn(),
|
|
36
|
+
createEdge: vi.fn(),
|
|
37
|
+
removeEdge: vi.fn(),
|
|
38
|
+
getBlockers: vi.fn(),
|
|
39
|
+
getBlocking: vi.fn(),
|
|
40
|
+
task: vi.fn(),
|
|
41
|
+
taskTransition: vi.fn(),
|
|
42
|
+
taskReady: vi.fn(),
|
|
43
|
+
taskAssign: vi.fn(),
|
|
44
|
+
taskValidActions: vi.fn(),
|
|
45
|
+
listProviders: vi.fn(),
|
|
46
|
+
})),
|
|
47
|
+
createOpenTasksClient: vi.fn().mockImplementation(async () => ({
|
|
48
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
49
|
+
disconnect: vi.fn(),
|
|
50
|
+
isConnected: vi.fn().mockReturnValue(true),
|
|
51
|
+
call: vi.fn().mockResolvedValue({}),
|
|
52
|
+
createIssue: vi.fn(),
|
|
53
|
+
getIssue: vi.fn(),
|
|
54
|
+
updateIssue: vi.fn(),
|
|
55
|
+
deleteIssue: vi.fn(),
|
|
56
|
+
listIssues: vi.fn(),
|
|
57
|
+
getReadyIssues: vi.fn(),
|
|
58
|
+
createEdge: vi.fn(),
|
|
59
|
+
removeEdge: vi.fn(),
|
|
60
|
+
getBlockers: vi.fn(),
|
|
61
|
+
getBlocking: vi.fn(),
|
|
62
|
+
task: vi.fn(),
|
|
63
|
+
taskTransition: vi.fn(),
|
|
64
|
+
taskReady: vi.fn(),
|
|
65
|
+
taskAssign: vi.fn(),
|
|
66
|
+
taskValidActions: vi.fn(),
|
|
67
|
+
listProviders: vi.fn(),
|
|
68
|
+
})),
|
|
69
|
+
OpenTasksClientError: class extends Error {
|
|
70
|
+
code: string;
|
|
71
|
+
constructor(msg: string, code: string) {
|
|
72
|
+
super(msg);
|
|
73
|
+
this.code = code;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Mock the opentasks backend
|
|
79
|
+
vi.mock("../opentasks/backend.js", () => ({
|
|
80
|
+
OpenTasksTaskBackend: vi.fn().mockImplementation(() => ({
|
|
81
|
+
create: vi.fn(),
|
|
82
|
+
get: vi.fn(),
|
|
83
|
+
update: vi.fn(),
|
|
84
|
+
list: vi.fn(),
|
|
85
|
+
delete: vi.fn(),
|
|
86
|
+
onChange: vi.fn(),
|
|
87
|
+
})),
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Mock daemon-manager
|
|
91
|
+
vi.mock("../opentasks/daemon-manager.js", () => ({
|
|
92
|
+
DaemonManager: vi.fn().mockImplementation(() => ({
|
|
93
|
+
ensureDaemon: vi.fn().mockResolvedValue({
|
|
94
|
+
client: {
|
|
95
|
+
connect: vi.fn(),
|
|
96
|
+
disconnect: vi.fn(),
|
|
97
|
+
isConnected: vi.fn().mockReturnValue(true),
|
|
98
|
+
},
|
|
99
|
+
socketPath: "/tmp/auto-daemon.sock",
|
|
100
|
+
ownsDaemon: true,
|
|
101
|
+
}),
|
|
102
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
103
|
+
connectProject: vi.fn().mockResolvedValue(undefined),
|
|
104
|
+
getConnectedProjects: vi.fn().mockReturnValue([]),
|
|
105
|
+
})),
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
describe("createTaskBackend", () => {
|
|
109
|
+
let eventStore: EventStore;
|
|
110
|
+
|
|
111
|
+
beforeEach(async () => {
|
|
112
|
+
eventStore = await createEventStore({ inMemory: true });
|
|
113
|
+
vi.clearAllMocks();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(async () => {
|
|
117
|
+
await eventStore.close();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return no socketPath for memory backend", async () => {
|
|
121
|
+
const { createTaskBackend } = await import("../index.js");
|
|
122
|
+
|
|
123
|
+
const result = await createTaskBackend(
|
|
124
|
+
{ backend: { type: "memory" } },
|
|
125
|
+
eventStore
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(result.backend).toBeDefined();
|
|
129
|
+
expect(result.socketPath).toBeUndefined();
|
|
130
|
+
expect(result.shutdown).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should return explicit socketPath for opentasks with socketPath config", async () => {
|
|
134
|
+
const { createTaskBackend } = await import("../index.js");
|
|
135
|
+
|
|
136
|
+
const result = await createTaskBackend(
|
|
137
|
+
{ backend: { type: "opentasks", socketPath: "/tmp/explicit.sock" } },
|
|
138
|
+
eventStore
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(result.backend).toBeDefined();
|
|
142
|
+
expect(result.socketPath).toBe("/tmp/explicit.sock");
|
|
143
|
+
expect(result.openTasksClient).toBeDefined();
|
|
144
|
+
expect(result.shutdown).toBeDefined();
|
|
145
|
+
// No connectProject when using direct socket (no DaemonManager)
|
|
146
|
+
expect(result.connectProject).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should return daemon socketPath for opentasks with autoStart", async () => {
|
|
150
|
+
const { createTaskBackend } = await import("../index.js");
|
|
151
|
+
|
|
152
|
+
const result = await createTaskBackend(
|
|
153
|
+
{ backend: { type: "opentasks" } }, // autoStart defaults to true
|
|
154
|
+
eventStore
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(result.backend).toBeDefined();
|
|
158
|
+
expect(result.socketPath).toBe("/tmp/auto-daemon.sock");
|
|
159
|
+
expect(result.openTasksClient).toBeDefined();
|
|
160
|
+
expect(result.shutdown).toBeDefined();
|
|
161
|
+
expect(result.connectProject).toBeDefined();
|
|
162
|
+
expect(result.getConnectedProjects).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should return no socketPath for opentasks with autoStart disabled", async () => {
|
|
166
|
+
const { createTaskBackend } = await import("../index.js");
|
|
167
|
+
|
|
168
|
+
const result = await createTaskBackend(
|
|
169
|
+
{ backend: { type: "opentasks", autoStart: false } },
|
|
170
|
+
eventStore
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(result.backend).toBeDefined();
|
|
174
|
+
// autoStart=false, no socketPath in config → socket path unknown
|
|
175
|
+
expect(result.socketPath).toBeUndefined();
|
|
176
|
+
expect(result.openTasksClient).toBeDefined();
|
|
177
|
+
expect(result.shutdown).toBeDefined();
|
|
178
|
+
// No DaemonManager when autoStart is false
|
|
179
|
+
expect(result.connectProject).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should throw for unknown backend type", async () => {
|
|
183
|
+
const { createTaskBackend } = await import("../index.js");
|
|
184
|
+
|
|
185
|
+
await expect(
|
|
186
|
+
createTaskBackend(
|
|
187
|
+
{ backend: { type: "unknown" as any } },
|
|
188
|
+
eventStore
|
|
189
|
+
)
|
|
190
|
+
).rejects.toThrow("Unknown backend type");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should call backend.close() before daemon shutdown", async () => {
|
|
194
|
+
const { createTaskBackend } = await import("../index.js");
|
|
195
|
+
|
|
196
|
+
const result = await createTaskBackend(
|
|
197
|
+
{ backend: { type: "opentasks" } }, // autoStart defaults to true
|
|
198
|
+
eventStore
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(result.shutdown).toBeDefined();
|
|
202
|
+
|
|
203
|
+
// Track call order
|
|
204
|
+
const callOrder: string[] = [];
|
|
205
|
+
const { DaemonManager } = await import("../opentasks/daemon-manager.js");
|
|
206
|
+
const mockInstance = vi.mocked(DaemonManager).mock.results[0]?.value;
|
|
207
|
+
if (mockInstance) {
|
|
208
|
+
mockInstance.shutdown.mockImplementation(async () => {
|
|
209
|
+
callOrder.push("daemon_shutdown");
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// backend.close() is called within the shutdown wrapper
|
|
214
|
+
const backend = result.backend as any;
|
|
215
|
+
const originalClose = backend.close?.bind(backend);
|
|
216
|
+
backend.close = vi.fn(async () => {
|
|
217
|
+
callOrder.push("backend_close");
|
|
218
|
+
if (originalClose) await originalClose();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await result.shutdown!();
|
|
222
|
+
|
|
223
|
+
expect(callOrder).toEqual(["backend_close", "daemon_shutdown"]);
|
|
224
|
+
});
|
|
225
|
+
});
|