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,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Status Mapping Utilities for Sudocode Integration
|
|
3
|
-
*
|
|
4
|
-
* Maps between sudocode issue statuses and macro-agent task statuses.
|
|
5
|
-
*
|
|
6
|
-
* @module task/backend/sudocode/mapping
|
|
7
|
-
* @see s-8472 Pluggable Task Backend Integration with Sudocode
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { TaskStatus } from "../../../store/types/index.js";
|
|
11
|
-
import type { IssueStatus } from "./client.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Map sudocode issue status to macro-agent task status.
|
|
15
|
-
*
|
|
16
|
-
* Sudocode statuses:
|
|
17
|
-
* - open: Not started
|
|
18
|
-
* - in_progress: Currently being worked on
|
|
19
|
-
* - blocked: Blocked by dependencies (handled via isBlocked flag)
|
|
20
|
-
* - closed: Completed
|
|
21
|
-
*
|
|
22
|
-
* Macro-agent statuses:
|
|
23
|
-
* - pending: Not started
|
|
24
|
-
* - assigned: Assigned to an agent but not started
|
|
25
|
-
* - in_progress: Currently executing
|
|
26
|
-
* - completed: Finished successfully
|
|
27
|
-
* - failed: Finished with error
|
|
28
|
-
*/
|
|
29
|
-
export function mapSudocodeStatus(sudocodeStatus: IssueStatus): TaskStatus {
|
|
30
|
-
switch (sudocodeStatus) {
|
|
31
|
-
case "open":
|
|
32
|
-
return "pending";
|
|
33
|
-
case "in_progress":
|
|
34
|
-
return "in_progress";
|
|
35
|
-
case "blocked":
|
|
36
|
-
// Blocked issues map to pending - the isBlocked flag handles blocking
|
|
37
|
-
return "pending";
|
|
38
|
-
case "closed":
|
|
39
|
-
return "completed";
|
|
40
|
-
default:
|
|
41
|
-
return "pending";
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Map macro-agent task status to sudocode issue status.
|
|
47
|
-
*
|
|
48
|
-
* Note: Some task statuses don't have direct sudocode equivalents:
|
|
49
|
-
* - assigned: No equivalent in sudocode (maps to in_progress)
|
|
50
|
-
* - failed: No equivalent in sudocode (maps to closed with error flag)
|
|
51
|
-
*/
|
|
52
|
-
export function mapTaskStatus(taskStatus: TaskStatus): IssueStatus {
|
|
53
|
-
switch (taskStatus) {
|
|
54
|
-
case "pending":
|
|
55
|
-
return "open";
|
|
56
|
-
case "assigned":
|
|
57
|
-
// Assigned tasks are considered in_progress in sudocode
|
|
58
|
-
return "in_progress";
|
|
59
|
-
case "in_progress":
|
|
60
|
-
return "in_progress";
|
|
61
|
-
case "completed":
|
|
62
|
-
return "closed";
|
|
63
|
-
case "failed":
|
|
64
|
-
// Failed tasks are closed in sudocode (failure tracked in outputs)
|
|
65
|
-
return "closed";
|
|
66
|
-
default:
|
|
67
|
-
return "open";
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Map issue priority to numeric priority.
|
|
73
|
-
* Sudocode uses 0 (highest) to 4 (lowest).
|
|
74
|
-
* This matches the TaskBackend convention.
|
|
75
|
-
*/
|
|
76
|
-
export function mapIssuePriority(priority: number): number {
|
|
77
|
-
return Math.max(0, Math.min(4, priority));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Check if a sudocode issue status indicates completion.
|
|
82
|
-
*/
|
|
83
|
-
export function isIssueComplete(status: IssueStatus): boolean {
|
|
84
|
-
return status === "closed";
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Check if a sudocode issue is blocked.
|
|
89
|
-
* Note: This is explicit 'blocked' status, not dependency-based blocking.
|
|
90
|
-
*/
|
|
91
|
-
export function isIssueBlocked(status: IssueStatus): boolean {
|
|
92
|
-
return status === "blocked";
|
|
93
|
-
}
|
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ServerClient - SudocodeClient implementation for managed mode
|
|
3
|
-
*
|
|
4
|
-
* Connects to sudocode server via REST API and WebSocket for real-time events.
|
|
5
|
-
* This is used when macro-agent is launched as a subprocess by the sudocode server.
|
|
6
|
-
*
|
|
7
|
-
* @module task/backend/sudocode/server-client
|
|
8
|
-
* @see s-8472 Pluggable Task Backend Integration with Sudocode
|
|
9
|
-
* @see i-5rrj 7A.2: Implement ServerClient (managed mode)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type {
|
|
13
|
-
SudocodeClient,
|
|
14
|
-
ServerClientConfig,
|
|
15
|
-
ListIssuesOptions,
|
|
16
|
-
ListSpecsOptions,
|
|
17
|
-
UpdateIssueInput,
|
|
18
|
-
FeedbackInput,
|
|
19
|
-
IssueChangeCallback,
|
|
20
|
-
IssueChangeEvent,
|
|
21
|
-
Unsubscribe,
|
|
22
|
-
} from "./client.js";
|
|
23
|
-
|
|
24
|
-
import type {
|
|
25
|
-
Issue,
|
|
26
|
-
Spec,
|
|
27
|
-
RelationshipType,
|
|
28
|
-
} from "@sudocode-ai/types";
|
|
29
|
-
|
|
30
|
-
// =============================================================================
|
|
31
|
-
// Types
|
|
32
|
-
// =============================================================================
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Standard API response format from sudocode server
|
|
36
|
-
*/
|
|
37
|
-
interface ApiResponse<T> {
|
|
38
|
-
success: boolean;
|
|
39
|
-
data: T | null;
|
|
40
|
-
message?: string;
|
|
41
|
-
error_data?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* WebSocket message format from sudocode server
|
|
46
|
-
*/
|
|
47
|
-
interface WsMessage {
|
|
48
|
-
type: string;
|
|
49
|
-
projectId?: string;
|
|
50
|
-
entityId?: string;
|
|
51
|
-
action?: string;
|
|
52
|
-
data?: unknown;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// =============================================================================
|
|
56
|
-
// ServerClient Implementation
|
|
57
|
-
// =============================================================================
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* ServerClient - REST + WebSocket client for managed mode
|
|
61
|
-
*
|
|
62
|
-
* Connects to a running sudocode server for all operations.
|
|
63
|
-
* Uses WebSocket for real-time event subscriptions with polling fallback.
|
|
64
|
-
*/
|
|
65
|
-
export class ServerClient implements SudocodeClient {
|
|
66
|
-
private config: ServerClientConfig;
|
|
67
|
-
private _ready: boolean = false;
|
|
68
|
-
private ws: WebSocket | null = null;
|
|
69
|
-
private wsReconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
70
|
-
private wsReconnectAttempts: number = 0;
|
|
71
|
-
private readonly maxReconnectAttempts: number = 10;
|
|
72
|
-
private readonly reconnectDelay: number = 1000;
|
|
73
|
-
private subscribers: Map<string, Set<IssueChangeCallback>> = new Map();
|
|
74
|
-
private globalSubscribers: Set<IssueChangeCallback> = new Set();
|
|
75
|
-
|
|
76
|
-
constructor(config: ServerClientConfig) {
|
|
77
|
-
this.config = config;
|
|
78
|
-
this.initWebSocket();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─── HTTP Helpers ────────────────────────────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
private get headers(): Record<string, string> {
|
|
84
|
-
const headers: Record<string, string> = {
|
|
85
|
-
"Content-Type": "application/json",
|
|
86
|
-
};
|
|
87
|
-
if (this.config.projectId) {
|
|
88
|
-
headers["X-Project-ID"] = this.config.projectId;
|
|
89
|
-
}
|
|
90
|
-
return headers;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private async request<T>(
|
|
94
|
-
method: string,
|
|
95
|
-
path: string,
|
|
96
|
-
body?: unknown
|
|
97
|
-
): Promise<T> {
|
|
98
|
-
const url = `${this.config.serverUrl}${path}`;
|
|
99
|
-
const options: RequestInit = {
|
|
100
|
-
method,
|
|
101
|
-
headers: this.headers,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
if (body !== undefined) {
|
|
105
|
-
options.body = JSON.stringify(body);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const response = await fetch(url, options);
|
|
109
|
-
const json = (await response.json()) as ApiResponse<T>;
|
|
110
|
-
|
|
111
|
-
if (!json.success) {
|
|
112
|
-
throw new Error(json.message || json.error_data || "Request failed");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return json.data as T;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private async get<T>(path: string): Promise<T> {
|
|
119
|
-
return this.request<T>("GET", path);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private async post<T>(path: string, body?: unknown): Promise<T> {
|
|
123
|
-
return this.request<T>("POST", path, body);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private async put<T>(path: string, body?: unknown): Promise<T> {
|
|
127
|
-
return this.request<T>("PUT", path, body);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private async delete<T>(path: string, body?: unknown): Promise<T> {
|
|
131
|
-
const url = `${this.config.serverUrl}${path}`;
|
|
132
|
-
const options: RequestInit = {
|
|
133
|
-
method: "DELETE",
|
|
134
|
-
headers: this.headers,
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
if (body !== undefined) {
|
|
138
|
-
options.body = JSON.stringify(body);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const response = await fetch(url, options);
|
|
142
|
-
const json = (await response.json()) as ApiResponse<T>;
|
|
143
|
-
|
|
144
|
-
if (!json.success) {
|
|
145
|
-
throw new Error(json.message || json.error_data || "Request failed");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return json.data as T;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ─── WebSocket Management ────────────────────────────────────────────────────
|
|
152
|
-
|
|
153
|
-
private initWebSocket(): void {
|
|
154
|
-
const wsUrl =
|
|
155
|
-
this.config.wsUrl ??
|
|
156
|
-
this.config.serverUrl.replace(/^http/, "ws") + "/ws";
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
this.ws = new WebSocket(wsUrl);
|
|
160
|
-
|
|
161
|
-
this.ws.onopen = () => {
|
|
162
|
-
this._ready = true;
|
|
163
|
-
this.wsReconnectAttempts = 0;
|
|
164
|
-
console.log("[ServerClient] WebSocket connected");
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
this.ws.onclose = () => {
|
|
168
|
-
this._ready = false;
|
|
169
|
-
console.log("[ServerClient] WebSocket disconnected");
|
|
170
|
-
this.scheduleReconnect();
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
this.ws.onerror = (error) => {
|
|
174
|
-
console.error("[ServerClient] WebSocket error:", error);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
this.ws.onmessage = (event) => {
|
|
178
|
-
this.handleWsMessage(event.data);
|
|
179
|
-
};
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error("[ServerClient] Failed to initialize WebSocket:", error);
|
|
182
|
-
this._ready = false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private scheduleReconnect(): void {
|
|
187
|
-
if (this.wsReconnectTimer) {
|
|
188
|
-
clearTimeout(this.wsReconnectTimer);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (this.wsReconnectAttempts >= this.maxReconnectAttempts) {
|
|
192
|
-
console.error(
|
|
193
|
-
"[ServerClient] Max reconnect attempts reached, giving up"
|
|
194
|
-
);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const delay = this.reconnectDelay * Math.pow(2, this.wsReconnectAttempts);
|
|
199
|
-
this.wsReconnectAttempts++;
|
|
200
|
-
|
|
201
|
-
console.log(
|
|
202
|
-
`[ServerClient] Scheduling reconnect in ${delay}ms (attempt ${this.wsReconnectAttempts})`
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
this.wsReconnectTimer = setTimeout(() => {
|
|
206
|
-
this.initWebSocket();
|
|
207
|
-
}, delay);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private handleWsMessage(data: string): void {
|
|
211
|
-
try {
|
|
212
|
-
const message = JSON.parse(data) as WsMessage;
|
|
213
|
-
|
|
214
|
-
// Handle issue-related messages
|
|
215
|
-
if (message.type === "issue" && message.entityId && message.action) {
|
|
216
|
-
const event: IssueChangeEvent = {
|
|
217
|
-
type: this.mapWsAction(message.action),
|
|
218
|
-
issueId: message.entityId,
|
|
219
|
-
issue: message.data as Issue | undefined,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// Notify global subscribers
|
|
223
|
-
for (const callback of this.globalSubscribers) {
|
|
224
|
-
try {
|
|
225
|
-
callback(event);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
console.error(
|
|
228
|
-
"[ServerClient] Error in global subscriber callback:",
|
|
229
|
-
error
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Notify issue-specific subscribers
|
|
235
|
-
const issueSubscribers = this.subscribers.get(message.entityId);
|
|
236
|
-
if (issueSubscribers) {
|
|
237
|
-
for (const callback of issueSubscribers) {
|
|
238
|
-
try {
|
|
239
|
-
callback(event);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error(
|
|
242
|
-
"[ServerClient] Error in issue subscriber callback:",
|
|
243
|
-
error
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.error("[ServerClient] Failed to parse WebSocket message:", error);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private mapWsAction(
|
|
255
|
-
action: string
|
|
256
|
-
): IssueChangeEvent["type"] {
|
|
257
|
-
switch (action) {
|
|
258
|
-
case "created":
|
|
259
|
-
return "created";
|
|
260
|
-
case "updated":
|
|
261
|
-
return "updated";
|
|
262
|
-
case "deleted":
|
|
263
|
-
return "deleted";
|
|
264
|
-
case "status_changed":
|
|
265
|
-
return "status_changed";
|
|
266
|
-
default:
|
|
267
|
-
return "updated";
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ─── Issue Operations ────────────────────────────────────────────────────────
|
|
272
|
-
|
|
273
|
-
async getIssue(id: string): Promise<Issue | null> {
|
|
274
|
-
try {
|
|
275
|
-
return await this.get<Issue>(`/api/issues/${id}`);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
if (error instanceof Error && error.message.includes("not found")) {
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
throw error;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async listIssues(filter?: ListIssuesOptions): Promise<Issue[]> {
|
|
285
|
-
const params = new URLSearchParams();
|
|
286
|
-
|
|
287
|
-
if (filter?.status) {
|
|
288
|
-
params.set("status", filter.status);
|
|
289
|
-
}
|
|
290
|
-
if (filter?.priority !== undefined) {
|
|
291
|
-
params.set("priority", filter.priority.toString());
|
|
292
|
-
}
|
|
293
|
-
if (filter?.search) {
|
|
294
|
-
params.set("search", filter.search);
|
|
295
|
-
}
|
|
296
|
-
if (filter?.archived !== undefined) {
|
|
297
|
-
params.set("archived", filter.archived.toString());
|
|
298
|
-
}
|
|
299
|
-
if (filter?.limit !== undefined) {
|
|
300
|
-
params.set("limit", filter.limit.toString());
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const queryString = params.toString();
|
|
304
|
-
const path = queryString ? `/api/issues?${queryString}` : "/api/issues";
|
|
305
|
-
|
|
306
|
-
return this.get<Issue[]>(path);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async getReadyIssues(): Promise<Issue[]> {
|
|
310
|
-
// Use the project status endpoint which returns ready issues
|
|
311
|
-
const status = await this.get<{
|
|
312
|
-
ready_issues: Array<{ id: string; title: string; priority: number }>;
|
|
313
|
-
}>("/api/project/status");
|
|
314
|
-
|
|
315
|
-
// Fetch full issue details for each ready issue
|
|
316
|
-
const issues: Issue[] = [];
|
|
317
|
-
for (const item of status.ready_issues) {
|
|
318
|
-
const issue = await this.getIssue(item.id);
|
|
319
|
-
if (issue) {
|
|
320
|
-
issues.push(issue);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return issues;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async updateIssue(id: string, updates: UpdateIssueInput): Promise<Issue> {
|
|
328
|
-
return this.put<Issue>(`/api/issues/${id}`, updates);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// ─── Relationship Operations ─────────────────────────────────────────────────
|
|
332
|
-
|
|
333
|
-
async createLink(
|
|
334
|
-
from: string,
|
|
335
|
-
to: string,
|
|
336
|
-
type: RelationshipType
|
|
337
|
-
): Promise<void> {
|
|
338
|
-
const fromType = from.startsWith("s-") ? "spec" : "issue";
|
|
339
|
-
const toType = to.startsWith("s-") ? "spec" : "issue";
|
|
340
|
-
|
|
341
|
-
await this.post("/api/relationships", {
|
|
342
|
-
from_id: from,
|
|
343
|
-
from_type: fromType,
|
|
344
|
-
to_id: to,
|
|
345
|
-
to_type: toType,
|
|
346
|
-
relationship_type: type,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async removeLink(
|
|
351
|
-
from: string,
|
|
352
|
-
to: string,
|
|
353
|
-
type: RelationshipType
|
|
354
|
-
): Promise<void> {
|
|
355
|
-
const fromType = from.startsWith("s-") ? "spec" : "issue";
|
|
356
|
-
const toType = to.startsWith("s-") ? "spec" : "issue";
|
|
357
|
-
|
|
358
|
-
await this.delete("/api/relationships", {
|
|
359
|
-
from_id: from,
|
|
360
|
-
from_type: fromType,
|
|
361
|
-
to_id: to,
|
|
362
|
-
to_type: toType,
|
|
363
|
-
relationship_type: type,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
async getBlockers(issueId: string): Promise<Issue[]> {
|
|
368
|
-
// Get incoming "blocks" relationships (issues that block this one)
|
|
369
|
-
const relationships = await this.get<
|
|
370
|
-
Array<{ from_id: string; from_type: string }>
|
|
371
|
-
>(
|
|
372
|
-
`/api/relationships/issue/${issueId}/incoming?relationship_type=blocks`
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
// Fetch full issue details for each blocker
|
|
376
|
-
const blockers: Issue[] = [];
|
|
377
|
-
for (const rel of relationships) {
|
|
378
|
-
if (rel.from_type === "issue") {
|
|
379
|
-
const issue = await this.getIssue(rel.from_id);
|
|
380
|
-
if (issue) {
|
|
381
|
-
blockers.push(issue);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return blockers;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async getBlocking(issueId: string): Promise<Issue[]> {
|
|
390
|
-
// Get outgoing "blocks" relationships (issues this one blocks)
|
|
391
|
-
const relationships = await this.get<
|
|
392
|
-
Array<{ to_id: string; to_type: string }>
|
|
393
|
-
>(
|
|
394
|
-
`/api/relationships/issue/${issueId}/outgoing?relationship_type=blocks`
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
// Fetch full issue details for each blocked issue
|
|
398
|
-
const blocked: Issue[] = [];
|
|
399
|
-
for (const rel of relationships) {
|
|
400
|
-
if (rel.to_type === "issue") {
|
|
401
|
-
const issue = await this.getIssue(rel.to_id);
|
|
402
|
-
if (issue) {
|
|
403
|
-
blocked.push(issue);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return blocked;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// ─── Spec Operations ─────────────────────────────────────────────────────────
|
|
412
|
-
|
|
413
|
-
async getSpec(id: string): Promise<Spec | null> {
|
|
414
|
-
try {
|
|
415
|
-
return await this.get<Spec>(`/api/specs/${id}`);
|
|
416
|
-
} catch (error) {
|
|
417
|
-
if (error instanceof Error && error.message.includes("not found")) {
|
|
418
|
-
return null;
|
|
419
|
-
}
|
|
420
|
-
throw error;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
async listSpecs(filter?: ListSpecsOptions): Promise<Spec[]> {
|
|
425
|
-
const params = new URLSearchParams();
|
|
426
|
-
|
|
427
|
-
if (filter?.search) {
|
|
428
|
-
params.set("search", filter.search);
|
|
429
|
-
}
|
|
430
|
-
if (filter?.limit !== undefined) {
|
|
431
|
-
params.set("limit", filter.limit.toString());
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const queryString = params.toString();
|
|
435
|
-
const path = queryString ? `/api/specs?${queryString}` : "/api/specs";
|
|
436
|
-
|
|
437
|
-
return this.get<Spec[]>(path);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// ─── Feedback Operations ─────────────────────────────────────────────────────
|
|
441
|
-
|
|
442
|
-
async addFeedback(
|
|
443
|
-
fromIssueId: string | undefined,
|
|
444
|
-
toId: string,
|
|
445
|
-
feedback: FeedbackInput
|
|
446
|
-
): Promise<void> {
|
|
447
|
-
await this.post("/api/feedback", {
|
|
448
|
-
from_id: fromIssueId,
|
|
449
|
-
to_id: toId,
|
|
450
|
-
feedback_type: feedback.type,
|
|
451
|
-
content: feedback.content,
|
|
452
|
-
agent: feedback.agent,
|
|
453
|
-
line: feedback.anchor?.line,
|
|
454
|
-
text: feedback.anchor?.text,
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// ─── Event Subscription ──────────────────────────────────────────────────────
|
|
459
|
-
|
|
460
|
-
onIssueChange(callback: IssueChangeCallback): Unsubscribe;
|
|
461
|
-
onIssueChange(issueId: string, callback: IssueChangeCallback): Unsubscribe;
|
|
462
|
-
onIssueChange(
|
|
463
|
-
callbackOrId: IssueChangeCallback | string,
|
|
464
|
-
maybeCallback?: IssueChangeCallback
|
|
465
|
-
): Unsubscribe {
|
|
466
|
-
if (typeof callbackOrId === "function") {
|
|
467
|
-
// Global subscription
|
|
468
|
-
const callback = callbackOrId;
|
|
469
|
-
this.globalSubscribers.add(callback);
|
|
470
|
-
|
|
471
|
-
return () => {
|
|
472
|
-
this.globalSubscribers.delete(callback);
|
|
473
|
-
};
|
|
474
|
-
} else {
|
|
475
|
-
// Issue-specific subscription
|
|
476
|
-
const issueId = callbackOrId;
|
|
477
|
-
const callback = maybeCallback!;
|
|
478
|
-
|
|
479
|
-
if (!this.subscribers.has(issueId)) {
|
|
480
|
-
this.subscribers.set(issueId, new Set());
|
|
481
|
-
}
|
|
482
|
-
this.subscribers.get(issueId)!.add(callback);
|
|
483
|
-
|
|
484
|
-
return () => {
|
|
485
|
-
const subs = this.subscribers.get(issueId);
|
|
486
|
-
if (subs) {
|
|
487
|
-
subs.delete(callback);
|
|
488
|
-
if (subs.size === 0) {
|
|
489
|
-
this.subscribers.delete(issueId);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ─── Lifecycle ───────────────────────────────────────────────────────────────
|
|
497
|
-
|
|
498
|
-
isReady(): boolean {
|
|
499
|
-
return this._ready;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
close(): void {
|
|
503
|
-
this._ready = false;
|
|
504
|
-
|
|
505
|
-
// Clear reconnect timer
|
|
506
|
-
if (this.wsReconnectTimer) {
|
|
507
|
-
clearTimeout(this.wsReconnectTimer);
|
|
508
|
-
this.wsReconnectTimer = null;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Close WebSocket
|
|
512
|
-
if (this.ws) {
|
|
513
|
-
this.ws.onclose = null; // Prevent reconnect attempt
|
|
514
|
-
this.ws.close();
|
|
515
|
-
this.ws = null;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Clear subscribers
|
|
519
|
-
this.globalSubscribers.clear();
|
|
520
|
-
this.subscribers.clear();
|
|
521
|
-
}
|
|
522
|
-
}
|