macro-agent 0.1.0 → 0.1.2
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/issues.jsonl +28 -0
- package/.sudocode/specs.jsonl +8 -0
- package/CLAUDE.md +25 -17
- 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 +544 -200
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +8 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/server.d.ts +8 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +136 -8
- package/dist/api/server.js.map +1 -1
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.d.ts.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 +197 -61
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +152 -16
- 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 +279 -173
- 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 +85 -7
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +133 -20
- package/dist/config/project-config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/handlers/index.d.ts +7 -3
- package/dist/lifecycle/handlers/index.d.ts.map +1 -1
- package/dist/lifecycle/handlers/index.js +25 -8
- package/dist/lifecycle/handlers/index.js.map +1 -1
- package/dist/lifecycle/types.d.ts +2 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.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 +13 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +61 -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/streams.d.ts +95 -0
- package/dist/map/adapter/extensions/streams.d.ts.map +1 -0
- package/dist/map/adapter/extensions/streams.js +515 -0
- package/dist/map/adapter/extensions/streams.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 +10 -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 +3 -1
- 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 +16 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +125 -88
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/tools/done.d.ts.map +1 -1
- package/dist/mcp/tools/done.js +18 -0
- package/dist/mcp/tools/done.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/builtin/coordinator.d.ts.map +1 -1
- package/dist/roles/builtin/coordinator.js +2 -1
- package/dist/roles/builtin/coordinator.js.map +1 -1
- package/dist/roles/builtin/integrator.d.ts.map +1 -1
- package/dist/roles/builtin/integrator.js +2 -1
- package/dist/roles/builtin/integrator.js.map +1 -1
- package/dist/roles/builtin/worker.d.ts.map +1 -1
- package/dist/roles/builtin/worker.js +3 -1
- package/dist/roles/builtin/worker.js.map +1 -1
- package/dist/roles/capabilities.d.ts +9 -1
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +27 -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 +8 -7
- 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/roles/types.d.ts +3 -1
- package/dist/roles/types.d.ts.map +1 -1
- package/dist/server/combined-server.d.ts +28 -1
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +111 -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 +80 -24
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/instance.d.ts +1 -1
- package/dist/store/instance.d.ts.map +1 -1
- package/dist/store/instance.js +2 -2
- package/dist/store/instance.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/index.d.ts +3 -1
- package/dist/teams/index.d.ts.map +1 -1
- package/dist/teams/index.js +2 -0
- package/dist/teams/index.js.map +1 -1
- package/dist/teams/seed-defaults.d.ts +20 -0
- package/dist/teams/seed-defaults.d.ts.map +1 -0
- package/dist/teams/seed-defaults.js +71 -0
- package/dist/teams/seed-defaults.js.map +1 -0
- package/dist/teams/team-loader.d.ts +7 -3
- package/dist/teams/team-loader.d.ts.map +1 -1
- package/dist/teams/team-loader.js +156 -164
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-manager.d.ts +112 -0
- package/dist/teams/team-manager.d.ts.map +1 -0
- package/dist/teams/team-manager.js +305 -0
- package/dist/teams/team-manager.js.map +1 -0
- package/dist/teams/team-runtime.d.ts +125 -19
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +529 -119
- package/dist/teams/team-runtime.js.map +1 -1
- package/dist/teams/types.d.ts +41 -151
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/teams/types.js +2 -3
- package/dist/teams/types.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 +76 -3
- package/docs/troubleshooting.md +10 -11
- package/package.json +7 -4
- package/references/minimem/.claude/settings.json +7 -0
- package/references/minimem/.sudocode/issues.jsonl +18 -0
- package/references/minimem/.sudocode/specs.jsonl +1 -0
- package/references/minimem/CLAUDE.md +310 -0
- package/references/minimem/README.md +562 -0
- package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
- package/references/minimem/claude-plugin/.mcp.json +7 -0
- package/references/minimem/claude-plugin/README.md +158 -0
- package/references/minimem/claude-plugin/commands/recall.md +47 -0
- package/references/minimem/claude-plugin/commands/remember.md +41 -0
- package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
- package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
- package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
- package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
- package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
- package/references/minimem/media/banner.png +0 -0
- package/references/minimem/package-lock.json +5373 -0
- package/references/minimem/package.json +72 -0
- package/references/minimem/scripts/postbuild.js +35 -0
- package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
- package/references/minimem/src/__tests__/errors.test.ts +265 -0
- package/references/minimem/src/__tests__/helpers.ts +199 -0
- package/references/minimem/src/__tests__/internal.test.ts +407 -0
- package/references/minimem/src/__tests__/knowledge.test.ts +287 -0
- package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
- package/references/minimem/src/__tests__/session.test.ts +190 -0
- package/references/minimem/src/cli/__tests__/commands.test.ts +759 -0
- package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
- package/references/minimem/src/cli/commands/append.ts +76 -0
- package/references/minimem/src/cli/commands/config.ts +262 -0
- package/references/minimem/src/cli/commands/conflicts.ts +413 -0
- package/references/minimem/src/cli/commands/daemon.ts +169 -0
- package/references/minimem/src/cli/commands/index.ts +12 -0
- package/references/minimem/src/cli/commands/init.ts +88 -0
- package/references/minimem/src/cli/commands/mcp.ts +177 -0
- package/references/minimem/src/cli/commands/push-pull.ts +213 -0
- package/references/minimem/src/cli/commands/search.ts +158 -0
- package/references/minimem/src/cli/commands/status.ts +84 -0
- package/references/minimem/src/cli/commands/sync-init.ts +290 -0
- package/references/minimem/src/cli/commands/sync.ts +70 -0
- package/references/minimem/src/cli/commands/upsert.ts +197 -0
- package/references/minimem/src/cli/config.ts +584 -0
- package/references/minimem/src/cli/index.ts +264 -0
- package/references/minimem/src/cli/shared.ts +161 -0
- package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
- package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
- package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
- package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
- package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
- package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
- package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
- package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
- package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
- package/references/minimem/src/cli/sync/central.ts +292 -0
- package/references/minimem/src/cli/sync/conflicts.ts +204 -0
- package/references/minimem/src/cli/sync/daemon.ts +407 -0
- package/references/minimem/src/cli/sync/detection.ts +138 -0
- package/references/minimem/src/cli/sync/index.ts +107 -0
- package/references/minimem/src/cli/sync/operations.ts +373 -0
- package/references/minimem/src/cli/sync/registry.ts +279 -0
- package/references/minimem/src/cli/sync/state.ts +355 -0
- package/references/minimem/src/cli/sync/validation.ts +206 -0
- package/references/minimem/src/cli/sync/watcher.ts +234 -0
- package/references/minimem/src/cli/version.ts +34 -0
- package/references/minimem/src/core/index.ts +9 -0
- package/references/minimem/src/core/indexer.ts +628 -0
- package/references/minimem/src/core/searcher.ts +221 -0
- package/references/minimem/src/db/schema.ts +183 -0
- package/references/minimem/src/db/sqlite-vec.ts +24 -0
- package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
- package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
- package/references/minimem/src/embeddings/batch-openai.ts +409 -0
- package/references/minimem/src/embeddings/embeddings.ts +434 -0
- package/references/minimem/src/index.ts +109 -0
- package/references/minimem/src/internal.ts +299 -0
- package/references/minimem/src/minimem.ts +1276 -0
- package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
- package/references/minimem/src/search/graph.ts +234 -0
- package/references/minimem/src/search/hybrid.ts +151 -0
- package/references/minimem/src/search/search.ts +256 -0
- package/references/minimem/src/server/__tests__/mcp.test.ts +341 -0
- package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
- package/references/minimem/src/server/mcp.ts +326 -0
- package/references/minimem/src/server/tools.ts +720 -0
- package/references/minimem/src/session.ts +460 -0
- package/references/minimem/tsconfig.json +19 -0
- package/references/minimem/tsup.config.ts +26 -0
- package/references/minimem/vitest.config.ts +24 -0
- package/references/openteams/.claude/settings.json +6 -0
- package/references/openteams/README.md +1 -0
- package/references/openteams/SKILL.md +341 -0
- package/references/openteams/design.md +411 -0
- package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +16 -0
- package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +24 -0
- package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +25 -0
- package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +21 -0
- package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +20 -0
- package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +17 -0
- package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +23 -0
- package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +27 -0
- package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +21 -0
- package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +16 -0
- package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/roles/analyst.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/architect.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/developer.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/master.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/pm.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/qa.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +8 -0
- package/references/openteams/examples/bmad-method/team.yaml +161 -0
- package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/ROLE.md +17 -0
- package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/debugger/ROLE.md +25 -0
- package/references/openteams/examples/get-shit-done/prompts/debugger/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/executor/ROLE.md +34 -0
- package/references/openteams/examples/get-shit-done/prompts/executor/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/integration-checker/ROLE.md +18 -0
- package/references/openteams/examples/get-shit-done/prompts/integration-checker/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/orchestrator/ROLE.md +42 -0
- package/references/openteams/examples/get-shit-done/prompts/orchestrator/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/phase-researcher/ROLE.md +15 -0
- package/references/openteams/examples/get-shit-done/prompts/phase-researcher/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/plan-checker/ROLE.md +17 -0
- package/references/openteams/examples/get-shit-done/prompts/plan-checker/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/planner/ROLE.md +28 -0
- package/references/openteams/examples/get-shit-done/prompts/planner/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/project-researcher/ROLE.md +16 -0
- package/references/openteams/examples/get-shit-done/prompts/project-researcher/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/ROLE.md +13 -0
- package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/roadmapper/ROLE.md +14 -0
- package/references/openteams/examples/get-shit-done/prompts/roadmapper/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/verifier/ROLE.md +19 -0
- package/references/openteams/examples/get-shit-done/prompts/verifier/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/roles/codebase-mapper.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/debugger.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/executor.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/integration-checker.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/orchestrator.yaml +9 -0
- package/references/openteams/examples/get-shit-done/roles/phase-researcher.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/plan-checker.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/planner.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/project-researcher.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/research-synthesizer.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/roadmapper.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/verifier.yaml +8 -0
- package/references/openteams/examples/get-shit-done/team.yaml +154 -0
- package/references/openteams/package-lock.json +2181 -0
- package/references/openteams/package.json +48 -0
- package/references/openteams/schema/role.schema.json +125 -0
- package/references/openteams/schema/team.schema.json +284 -0
- package/references/openteams/src/cli/agent.ts +104 -0
- package/references/openteams/src/cli/cli.test.ts +381 -0
- package/references/openteams/src/cli/generate.ts +220 -0
- package/references/openteams/src/cli/message.ts +241 -0
- package/references/openteams/src/cli/task.ts +154 -0
- package/references/openteams/src/cli/team.ts +104 -0
- package/references/openteams/src/cli/template.ts +207 -0
- package/references/openteams/src/cli.ts +45 -0
- package/references/openteams/src/db/database.test.ts +185 -0
- package/references/openteams/src/db/database.ts +240 -0
- package/references/openteams/src/generators/agent-prompt-generator.test.ts +332 -0
- package/references/openteams/src/generators/agent-prompt-generator.ts +521 -0
- package/references/openteams/src/generators/package-generator.test.ts +129 -0
- package/references/openteams/src/generators/package-generator.ts +102 -0
- package/references/openteams/src/generators/skill-generator.test.ts +246 -0
- package/references/openteams/src/generators/skill-generator.ts +374 -0
- package/references/openteams/src/index.ts +104 -0
- package/references/openteams/src/services/agent-service.test.ts +158 -0
- package/references/openteams/src/services/agent-service.ts +84 -0
- package/references/openteams/src/services/communication-service.test.ts +455 -0
- package/references/openteams/src/services/communication-service.ts +371 -0
- package/references/openteams/src/services/message-service.test.ts +342 -0
- package/references/openteams/src/services/message-service.ts +203 -0
- package/references/openteams/src/services/task-service.test.ts +434 -0
- package/references/openteams/src/services/task-service.ts +239 -0
- package/references/openteams/src/services/team-service.test.ts +181 -0
- package/references/openteams/src/services/team-service.ts +139 -0
- package/references/openteams/src/services/template-service.test.ts +306 -0
- package/references/openteams/src/services/template-service.ts +182 -0
- package/references/openteams/src/spawner/acp-factory.ts +96 -0
- package/references/openteams/src/spawner/interface.ts +31 -0
- package/references/openteams/src/spawner/mock.test.ts +93 -0
- package/references/openteams/src/spawner/mock.ts +59 -0
- package/references/openteams/src/template/loader.test.ts +1319 -0
- package/references/openteams/src/template/loader.ts +698 -0
- package/references/openteams/src/template/types.ts +200 -0
- package/references/openteams/src/types.ts +205 -0
- package/references/openteams/tsconfig.json +18 -0
- package/references/openteams/vitest.config.ts +9 -0
- package/references/skill-tree/.claude/settings.json +6 -0
- package/references/skill-tree/.sudocode/issues.jsonl +11 -0
- package/references/skill-tree/.sudocode/specs.jsonl +1 -0
- package/references/skill-tree/CLAUDE.md +150 -0
- package/references/skill-tree/README.md +324 -0
- package/references/skill-tree/docs/GAPS_v1.md +221 -0
- package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
- package/references/skill-tree/docs/TODOS.md +91 -0
- package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
- package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
- package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
- package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
- package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
- package/references/skill-tree/docs/scraper/README.md +170 -0
- package/references/skill-tree/examples/basic-usage.ts +190 -0
- package/references/skill-tree/package-lock.json +1509 -0
- package/references/skill-tree/package.json +66 -0
- package/references/skill-tree/scraper/README.md +123 -0
- package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
- package/references/skill-tree/scraper/docs/PLAN.md +336 -0
- package/references/skill-tree/scraper/drizzle.config.ts +10 -0
- package/references/skill-tree/scraper/package-lock.json +6329 -0
- package/references/skill-tree/scraper/package.json +68 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
- package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
- package/references/skill-tree/scraper/tsup.config.ts +14 -0
- package/references/skill-tree/scraper/vitest.config.ts +17 -0
- package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
- package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
- package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
- package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
- package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
- package/references/skill-tree/test/run-all.ts +106 -0
- package/references/skill-tree/test/utils.ts +128 -0
- package/references/skill-tree/vitest.config.ts +16 -0
- 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 +809 -285
- package/src/agent/types.ts +12 -1
- package/src/api/__tests__/server.test.ts +203 -4
- package/src/api/server.ts +169 -10
- package/src/api/types.ts +3 -1
- 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 +197 -72
- package/src/cli/index.ts +125 -15
- package/src/cli/mcp.ts +315 -197
- package/src/cli/parse-args.ts +54 -0
- package/src/cli/stable-instance-id.ts +14 -0
- package/src/config/project-config.ts +214 -27
- package/src/index.ts +3 -0
- package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
- package/src/lifecycle/__tests__/handlers.test.ts +53 -0
- package/src/lifecycle/handlers/index.ts +25 -8
- package/src/lifecycle/types.ts +3 -0
- 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__/stream-extensions.test.ts +494 -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 +96 -0
- package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
- package/src/map/adapter/extensions/streams.ts +839 -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 +33 -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 +10 -1
- 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 +205 -103
- package/src/mcp/tools/done.ts +19 -0
- 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/builtin/coordinator.ts +2 -0
- package/src/roles/builtin/integrator.ts +2 -0
- package/src/roles/builtin/worker.ts +3 -0
- package/src/roles/capabilities.ts +28 -7
- package/src/roles/config-loader.ts +8 -7
- package/src/roles/registry.ts +2 -2
- package/src/roles/types.ts +7 -0
- package/src/server/__tests__/combined-server.test.ts +94 -21
- package/src/server/combined-server.ts +203 -33
- package/src/steering/__tests__/steering-integration.test.ts +1 -1
- package/src/store/__tests__/event-store-oob.test.ts +109 -0
- 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 +92 -23
- package/src/store/instance.ts +2 -2
- package/src/store/types/agents.ts +20 -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__/memory-pull-mode.test.ts +153 -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 +256 -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/CLAUDE.md +180 -0
- package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
- package/src/teams/__tests__/e2e/workspace-isolation.e2e.test.ts +1263 -0
- package/src/teams/__tests__/team-manager.test.ts +814 -0
- package/src/teams/__tests__/team-system.test.ts +1291 -8
- package/src/teams/index.ts +21 -3
- package/src/teams/seed-defaults.ts +79 -0
- package/src/teams/team-loader.ts +202 -236
- package/src/teams/team-manager.ts +387 -0
- package/src/teams/team-runtime.ts +592 -121
- package/src/teams/types.ts +99 -200
- 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,1237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SudocodeTaskBackend Implementation
|
|
3
|
-
*
|
|
4
|
-
* Implements TaskBackend using EventStore for local task storage and
|
|
5
|
-
* SudocodeClient for external issue data and dependency tracking.
|
|
6
|
-
*
|
|
7
|
-
* @module task/backend/sudocode/backend
|
|
8
|
-
* @see s-8472 Pluggable Task Backend Integration with Sudocode
|
|
9
|
-
* @see i-2gwa 7A.5: Implement SudocodeTaskBackend core
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { nanoid } from "nanoid";
|
|
13
|
-
import type { EventStore } from "../../../store/event-store.js";
|
|
14
|
-
import type {
|
|
15
|
-
Task,
|
|
16
|
-
TaskStatus,
|
|
17
|
-
AgentId,
|
|
18
|
-
TaskId,
|
|
19
|
-
AgentHistoryEntry,
|
|
20
|
-
} from "../../../store/types/index.js";
|
|
21
|
-
import type {
|
|
22
|
-
TaskBackend,
|
|
23
|
-
ExtendedTask,
|
|
24
|
-
CreateTaskOptions,
|
|
25
|
-
UpdateTaskOptions,
|
|
26
|
-
TaskFilter,
|
|
27
|
-
TaskOutputs,
|
|
28
|
-
TaskError,
|
|
29
|
-
SubtaskStatus,
|
|
30
|
-
AssignOptions,
|
|
31
|
-
TaskChangeCallback,
|
|
32
|
-
TaskChangeEvent,
|
|
33
|
-
Unsubscribe,
|
|
34
|
-
} from "../types.js";
|
|
35
|
-
import type { SudocodeClient, IssueChangeCallback } from "./client.js";
|
|
36
|
-
import { mapSudocodeStatus, isIssueComplete } from "./mapping.js";
|
|
37
|
-
import type { SyncPolicy, SyncEventCallback, SyncEvent } from "./sync-policy.js";
|
|
38
|
-
import {
|
|
39
|
-
SyncPolicyEngine,
|
|
40
|
-
defaultSyncPolicy,
|
|
41
|
-
createSyncPolicyEngine,
|
|
42
|
-
} from "./sync-policy.js";
|
|
43
|
-
|
|
44
|
-
// Valid status transitions
|
|
45
|
-
const VALID_STATUS_TRANSITIONS: Record<TaskStatus, TaskStatus[]> = {
|
|
46
|
-
pending: ["assigned", "in_progress", "failed"],
|
|
47
|
-
assigned: ["in_progress", "pending", "failed"],
|
|
48
|
-
in_progress: ["completed", "failed", "pending"],
|
|
49
|
-
completed: [],
|
|
50
|
-
failed: ["pending"],
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Error thrown by SudocodeTaskBackend operations
|
|
55
|
-
*/
|
|
56
|
-
export class SudocodeTaskBackendError extends Error {
|
|
57
|
-
constructor(
|
|
58
|
-
message: string,
|
|
59
|
-
public readonly code: string,
|
|
60
|
-
public readonly taskId?: TaskId
|
|
61
|
-
) {
|
|
62
|
-
super(message);
|
|
63
|
-
this.name = "SudocodeTaskBackendError";
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* SudocodeTaskBackend Configuration
|
|
69
|
-
*/
|
|
70
|
-
export interface SudocodeTaskBackendConfig {
|
|
71
|
-
/** Path to sudocode project root */
|
|
72
|
-
projectPath?: string;
|
|
73
|
-
|
|
74
|
-
/** Whether to sync task status with issue status */
|
|
75
|
-
syncStatus?: boolean;
|
|
76
|
-
|
|
77
|
-
/** Whether to auto-close issues when tasks complete */
|
|
78
|
-
autoCloseIssues?: boolean;
|
|
79
|
-
|
|
80
|
-
/** Sync policy configuration */
|
|
81
|
-
syncPolicy?: Partial<SyncPolicy>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const DEFAULT_CONFIG: Required<Omit<SudocodeTaskBackendConfig, "syncPolicy">> = {
|
|
85
|
-
projectPath: process.cwd(),
|
|
86
|
-
syncStatus: true,
|
|
87
|
-
autoCloseIssues: false,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* SudocodeTaskBackend implements TaskBackend using:
|
|
92
|
-
* - EventStore for local task storage and events
|
|
93
|
-
* - SudocodeClient for issue data and dependency tracking
|
|
94
|
-
*
|
|
95
|
-
* Key features:
|
|
96
|
-
* - Tasks can be bound to sudocode issues via external_id
|
|
97
|
-
* - isBlocked computed from sudocode's issue blockers
|
|
98
|
-
* - listReady uses sudocode's ready issues API
|
|
99
|
-
* - Status changes can optionally sync to issues
|
|
100
|
-
*/
|
|
101
|
-
export class SudocodeTaskBackend implements TaskBackend {
|
|
102
|
-
private readonly config: Required<Omit<SudocodeTaskBackendConfig, "syncPolicy">>;
|
|
103
|
-
private readonly tasksByIssue: Map<string, Set<TaskId>> = new Map();
|
|
104
|
-
private readonly issueByTask: Map<TaskId, string> = new Map();
|
|
105
|
-
private readonly syncEngine: SyncPolicyEngine;
|
|
106
|
-
private issueChangeUnsubscribe?: Unsubscribe;
|
|
107
|
-
|
|
108
|
-
constructor(
|
|
109
|
-
private readonly eventStore: EventStore,
|
|
110
|
-
private readonly client: SudocodeClient,
|
|
111
|
-
config?: SudocodeTaskBackendConfig
|
|
112
|
-
) {
|
|
113
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
114
|
-
this.syncEngine = createSyncPolicyEngine(
|
|
115
|
-
config?.syncPolicy ?? {},
|
|
116
|
-
this
|
|
117
|
-
);
|
|
118
|
-
this.rebuildIndex();
|
|
119
|
-
this.subscribeToIssueChanges();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
-
// Index Management
|
|
124
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Rebuild the task-by-issue index from existing tasks
|
|
128
|
-
*/
|
|
129
|
-
private rebuildIndex(): void {
|
|
130
|
-
this.tasksByIssue.clear();
|
|
131
|
-
this.issueByTask.clear();
|
|
132
|
-
const tasks = this.eventStore.listTasks();
|
|
133
|
-
|
|
134
|
-
for (const task of tasks) {
|
|
135
|
-
const externalId = this.getTaskExternalId(task);
|
|
136
|
-
if (externalId) {
|
|
137
|
-
this.addToIndex(externalId, task.id);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Add a task to the issue index
|
|
144
|
-
*/
|
|
145
|
-
private addToIndex(issueId: string, taskId: TaskId): void {
|
|
146
|
-
// Update issue -> tasks map
|
|
147
|
-
if (!this.tasksByIssue.has(issueId)) {
|
|
148
|
-
this.tasksByIssue.set(issueId, new Set());
|
|
149
|
-
}
|
|
150
|
-
this.tasksByIssue.get(issueId)!.add(taskId);
|
|
151
|
-
|
|
152
|
-
// Update task -> issue map
|
|
153
|
-
this.issueByTask.set(taskId, issueId);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Remove a task from the issue index
|
|
158
|
-
*/
|
|
159
|
-
private removeFromIndex(issueId: string, taskId: TaskId): void {
|
|
160
|
-
// Update issue -> tasks map
|
|
161
|
-
const tasks = this.tasksByIssue.get(issueId);
|
|
162
|
-
if (tasks) {
|
|
163
|
-
tasks.delete(taskId);
|
|
164
|
-
if (tasks.size === 0) {
|
|
165
|
-
this.tasksByIssue.delete(issueId);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Update task -> issue map
|
|
170
|
-
this.issueByTask.delete(taskId);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get tasks bound to an issue
|
|
175
|
-
*/
|
|
176
|
-
getTasksByIssue(issueId: string): TaskId[] {
|
|
177
|
-
return Array.from(this.tasksByIssue.get(issueId) ?? []);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get the issue a task is bound to
|
|
182
|
-
*/
|
|
183
|
-
getIssueForTask(taskId: TaskId): string | undefined {
|
|
184
|
-
return this.issueByTask.get(taskId);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Bind a task to a sudocode issue.
|
|
189
|
-
* @param taskId Task ID to bind
|
|
190
|
-
* @param issueId Issue ID to bind to
|
|
191
|
-
* @throws If task or issue not found
|
|
192
|
-
*/
|
|
193
|
-
async bindToIssue(taskId: TaskId, issueId: string): Promise<void> {
|
|
194
|
-
const task = this.eventStore.getTask(taskId);
|
|
195
|
-
if (!task) {
|
|
196
|
-
throw new SudocodeTaskBackendError(
|
|
197
|
-
`Task not found: ${taskId}`,
|
|
198
|
-
"TASK_NOT_FOUND",
|
|
199
|
-
taskId
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Verify issue exists
|
|
204
|
-
const issue = await this.client.getIssue(issueId);
|
|
205
|
-
if (!issue) {
|
|
206
|
-
throw new SudocodeTaskBackendError(
|
|
207
|
-
`Issue not found: ${issueId}`,
|
|
208
|
-
"ISSUE_NOT_FOUND"
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Check if already bound to a different issue
|
|
213
|
-
const currentIssue = this.getIssueForTask(taskId);
|
|
214
|
-
if (currentIssue && currentIssue !== issueId) {
|
|
215
|
-
// Remove from old index
|
|
216
|
-
this.removeFromIndex(currentIssue, taskId);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Update task with external_id
|
|
220
|
-
this.eventStore.emit({
|
|
221
|
-
type: "task",
|
|
222
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
223
|
-
payload: {
|
|
224
|
-
task_id: taskId,
|
|
225
|
-
action: "status_change",
|
|
226
|
-
details: {
|
|
227
|
-
outputs: { external_id: issueId },
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Update index
|
|
233
|
-
this.addToIndex(issueId, taskId);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Unbind a task from its current issue.
|
|
238
|
-
* @param taskId Task ID to unbind
|
|
239
|
-
* @throws If task not found
|
|
240
|
-
*/
|
|
241
|
-
async unbindFromIssue(taskId: TaskId): Promise<void> {
|
|
242
|
-
const task = this.eventStore.getTask(taskId);
|
|
243
|
-
if (!task) {
|
|
244
|
-
throw new SudocodeTaskBackendError(
|
|
245
|
-
`Task not found: ${taskId}`,
|
|
246
|
-
"TASK_NOT_FOUND",
|
|
247
|
-
taskId
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const issueId = this.getIssueForTask(taskId);
|
|
252
|
-
if (!issueId) {
|
|
253
|
-
// Not bound, nothing to do
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Clear external_id in task outputs
|
|
258
|
-
this.eventStore.emit({
|
|
259
|
-
type: "task",
|
|
260
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
261
|
-
payload: {
|
|
262
|
-
task_id: taskId,
|
|
263
|
-
action: "status_change",
|
|
264
|
-
details: {
|
|
265
|
-
outputs: { external_id: null },
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
// Remove from index
|
|
271
|
-
this.removeFromIndex(issueId, taskId);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
-
// Issue Change Subscription
|
|
276
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Subscribe to sudocode issue changes for status sync
|
|
280
|
-
*/
|
|
281
|
-
private subscribeToIssueChanges(): void {
|
|
282
|
-
if (!this.config.syncStatus) return;
|
|
283
|
-
|
|
284
|
-
const callback: IssueChangeCallback = (event) => {
|
|
285
|
-
// Handle via sync policy engine
|
|
286
|
-
this.syncEngine.handleIssueChange(event).catch(() => {
|
|
287
|
-
// Ignore errors from sync engine
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Additionally handle status mapping for non-closed status changes
|
|
291
|
-
// (closed status is handled by the sync engine based on policy)
|
|
292
|
-
if (
|
|
293
|
-
event.type === "status_changed" &&
|
|
294
|
-
event.issue &&
|
|
295
|
-
event.issue.status !== "closed"
|
|
296
|
-
) {
|
|
297
|
-
const taskIds = this.getTasksByIssue(event.issueId);
|
|
298
|
-
const newStatus = mapSudocodeStatus(event.issue.status);
|
|
299
|
-
|
|
300
|
-
for (const taskId of taskIds) {
|
|
301
|
-
const task = this.eventStore.getTask(taskId);
|
|
302
|
-
if (task && task.status !== newStatus) {
|
|
303
|
-
// Only update if the task isn't in a terminal state
|
|
304
|
-
// Also preserve "assigned" status - it's a macro-agent concept
|
|
305
|
-
// that will transition to in_progress when start() is called
|
|
306
|
-
if (
|
|
307
|
-
task.status !== "completed" &&
|
|
308
|
-
task.status !== "failed" &&
|
|
309
|
-
task.status !== "assigned"
|
|
310
|
-
) {
|
|
311
|
-
// Emit status change event
|
|
312
|
-
this.eventStore.emit({
|
|
313
|
-
type: "task",
|
|
314
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
315
|
-
payload: {
|
|
316
|
-
task_id: taskId,
|
|
317
|
-
action: "status_change",
|
|
318
|
-
details: { status: newStatus },
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
this.issueChangeUnsubscribe = this.client.onIssueChange(callback);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
331
|
-
// Lifecycle
|
|
332
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
333
|
-
|
|
334
|
-
async create(options: CreateTaskOptions): Promise<ExtendedTask> {
|
|
335
|
-
const taskId = `task_${nanoid(12)}`;
|
|
336
|
-
|
|
337
|
-
// Validate parent task exists if specified
|
|
338
|
-
if (options.parent_task) {
|
|
339
|
-
const parent = this.eventStore.getTask(options.parent_task);
|
|
340
|
-
if (!parent) {
|
|
341
|
-
throw new SudocodeTaskBackendError(
|
|
342
|
-
`Parent task not found: ${options.parent_task}`,
|
|
343
|
-
"PARENT_TASK_NOT_FOUND",
|
|
344
|
-
options.parent_task
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Validate external_id (issue) exists if specified
|
|
350
|
-
if (options.external_id) {
|
|
351
|
-
const issue = await this.client.getIssue(options.external_id);
|
|
352
|
-
if (!issue) {
|
|
353
|
-
throw new SudocodeTaskBackendError(
|
|
354
|
-
`Issue not found: ${options.external_id}`,
|
|
355
|
-
"ISSUE_NOT_FOUND"
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Emit task created event
|
|
361
|
-
this.eventStore.emit({
|
|
362
|
-
type: "task",
|
|
363
|
-
source: { agent_id: options.created_by },
|
|
364
|
-
payload: {
|
|
365
|
-
task_id: taskId,
|
|
366
|
-
action: "created",
|
|
367
|
-
details: {
|
|
368
|
-
description: options.description,
|
|
369
|
-
parent_task: options.parent_task,
|
|
370
|
-
external_id: options.external_id,
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// Update parent's subtasks array if this is a subtask
|
|
376
|
-
if (options.parent_task) {
|
|
377
|
-
this.eventStore.emit({
|
|
378
|
-
type: "task",
|
|
379
|
-
source: { agent_id: options.created_by },
|
|
380
|
-
payload: {
|
|
381
|
-
task_id: options.parent_task,
|
|
382
|
-
action: "status_change",
|
|
383
|
-
details: {
|
|
384
|
-
subtask_added: taskId,
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Add to issue index if bound
|
|
391
|
-
if (options.external_id) {
|
|
392
|
-
this.addToIndex(options.external_id, taskId);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const task = this.eventStore.getTask(taskId)!;
|
|
396
|
-
return this.toExtendedTask(task);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async get(id: TaskId): Promise<ExtendedTask | null> {
|
|
400
|
-
const task = this.eventStore.getTask(id);
|
|
401
|
-
if (!task) return null;
|
|
402
|
-
return this.toExtendedTask(task);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
async update(id: TaskId, updates: UpdateTaskOptions): Promise<ExtendedTask> {
|
|
406
|
-
const task = this.eventStore.getTask(id);
|
|
407
|
-
if (!task) {
|
|
408
|
-
throw new SudocodeTaskBackendError(
|
|
409
|
-
`Task not found: ${id}`,
|
|
410
|
-
"TASK_NOT_FOUND",
|
|
411
|
-
id
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const source = task.assigned_agent ?? task.created_by;
|
|
416
|
-
|
|
417
|
-
// Handle status update with validation
|
|
418
|
-
if (updates.status !== undefined) {
|
|
419
|
-
const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
|
|
420
|
-
if (!validTransitions.includes(updates.status)) {
|
|
421
|
-
throw new SudocodeTaskBackendError(
|
|
422
|
-
`Invalid status transition: ${task.status} -> ${updates.status}`,
|
|
423
|
-
"INVALID_STATUS_TRANSITION",
|
|
424
|
-
id
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
this.eventStore.emit({
|
|
429
|
-
type: "task",
|
|
430
|
-
source: { agent_id: source },
|
|
431
|
-
payload: {
|
|
432
|
-
task_id: id,
|
|
433
|
-
action: "status_change",
|
|
434
|
-
details: { status: updates.status },
|
|
435
|
-
},
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Handle other updates
|
|
440
|
-
if (updates.outputs !== undefined) {
|
|
441
|
-
this.eventStore.emit({
|
|
442
|
-
type: "task",
|
|
443
|
-
source: { agent_id: source },
|
|
444
|
-
payload: {
|
|
445
|
-
task_id: id,
|
|
446
|
-
action: "status_change",
|
|
447
|
-
details: { outputs: updates.outputs },
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (updates.artifacts !== undefined) {
|
|
453
|
-
this.eventStore.emit({
|
|
454
|
-
type: "task",
|
|
455
|
-
source: { agent_id: source },
|
|
456
|
-
payload: {
|
|
457
|
-
task_id: id,
|
|
458
|
-
action: "status_change",
|
|
459
|
-
details: { artifacts: updates.artifacts },
|
|
460
|
-
},
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (updates.description !== undefined) {
|
|
465
|
-
this.eventStore.emit({
|
|
466
|
-
type: "task",
|
|
467
|
-
source: { agent_id: source },
|
|
468
|
-
payload: {
|
|
469
|
-
task_id: id,
|
|
470
|
-
action: "status_change",
|
|
471
|
-
details: { description: updates.description },
|
|
472
|
-
},
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const updated = this.eventStore.getTask(id)!;
|
|
477
|
-
return this.toExtendedTask(updated);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async delete(_id: TaskId): Promise<void> {
|
|
481
|
-
// Tasks are immutable in event-sourced system - no delete operation
|
|
482
|
-
// This matches InMemoryTaskBackend behavior for backend parity
|
|
483
|
-
throw new SudocodeTaskBackendError(
|
|
484
|
-
"Delete operation not supported - tasks are immutable",
|
|
485
|
-
"NOT_SUPPORTED"
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
490
|
-
// Status Transitions
|
|
491
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
492
|
-
|
|
493
|
-
async assign(
|
|
494
|
-
id: TaskId,
|
|
495
|
-
agentId: AgentId,
|
|
496
|
-
options?: AssignOptions
|
|
497
|
-
): Promise<void> {
|
|
498
|
-
const task = this.eventStore.getTask(id);
|
|
499
|
-
if (!task) {
|
|
500
|
-
throw new SudocodeTaskBackendError(
|
|
501
|
-
`Task not found: ${id}`,
|
|
502
|
-
"TASK_NOT_FOUND",
|
|
503
|
-
id
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
this.eventStore.emit({
|
|
508
|
-
type: "task",
|
|
509
|
-
source: { agent_id: agentId },
|
|
510
|
-
payload: {
|
|
511
|
-
task_id: id,
|
|
512
|
-
action: "assigned",
|
|
513
|
-
details: {
|
|
514
|
-
agent_id: agentId,
|
|
515
|
-
role: options?.role,
|
|
516
|
-
},
|
|
517
|
-
},
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
// Note: We don't sync to sudocode on assign because "assigned" is a
|
|
521
|
-
// macro-agent concept (task has a worker but work hasn't started).
|
|
522
|
-
// Sudocode "in_progress" is synced when start() is called.
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async unassign(id: TaskId): Promise<void> {
|
|
526
|
-
const task = this.eventStore.getTask(id);
|
|
527
|
-
if (!task) {
|
|
528
|
-
throw new SudocodeTaskBackendError(
|
|
529
|
-
`Task not found: ${id}`,
|
|
530
|
-
"TASK_NOT_FOUND",
|
|
531
|
-
id
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!task.assigned_agent) {
|
|
536
|
-
throw new SudocodeTaskBackendError(
|
|
537
|
-
`Task is not assigned: ${id}`,
|
|
538
|
-
"TASK_NOT_ASSIGNED",
|
|
539
|
-
id
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
this.eventStore.emit({
|
|
544
|
-
type: "task",
|
|
545
|
-
source: { agent_id: task.assigned_agent },
|
|
546
|
-
payload: {
|
|
547
|
-
task_id: id,
|
|
548
|
-
action: "unassigned",
|
|
549
|
-
details: {
|
|
550
|
-
agent_id: task.assigned_agent,
|
|
551
|
-
},
|
|
552
|
-
},
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
async start(id: TaskId): Promise<void> {
|
|
557
|
-
const task = this.eventStore.getTask(id);
|
|
558
|
-
if (!task) {
|
|
559
|
-
throw new SudocodeTaskBackendError(
|
|
560
|
-
`Task not found: ${id}`,
|
|
561
|
-
"TASK_NOT_FOUND",
|
|
562
|
-
id
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
|
|
567
|
-
if (!validTransitions.includes("in_progress")) {
|
|
568
|
-
throw new SudocodeTaskBackendError(
|
|
569
|
-
`Invalid status transition: ${task.status} -> in_progress`,
|
|
570
|
-
"INVALID_STATUS_TRANSITION",
|
|
571
|
-
id
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
this.eventStore.emit({
|
|
576
|
-
type: "task",
|
|
577
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
578
|
-
payload: {
|
|
579
|
-
task_id: id,
|
|
580
|
-
action: "status_change",
|
|
581
|
-
details: { status: "in_progress" },
|
|
582
|
-
},
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
// Update issue status
|
|
586
|
-
const externalId = this.getTaskExternalId(task);
|
|
587
|
-
if (externalId && this.config.syncStatus) {
|
|
588
|
-
try {
|
|
589
|
-
await this.client.updateIssue(externalId, { status: "in_progress" });
|
|
590
|
-
} catch {
|
|
591
|
-
// Ignore errors syncing to sudocode
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
async complete(id: TaskId, outputs?: TaskOutputs): Promise<void> {
|
|
597
|
-
const task = this.eventStore.getTask(id);
|
|
598
|
-
if (!task) {
|
|
599
|
-
throw new SudocodeTaskBackendError(
|
|
600
|
-
`Task not found: ${id}`,
|
|
601
|
-
"TASK_NOT_FOUND",
|
|
602
|
-
id
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
|
|
607
|
-
if (!validTransitions.includes("completed")) {
|
|
608
|
-
throw new SudocodeTaskBackendError(
|
|
609
|
-
`Invalid status transition: ${task.status} -> completed`,
|
|
610
|
-
"INVALID_STATUS_TRANSITION",
|
|
611
|
-
id
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Add outputs if provided
|
|
616
|
-
if (outputs) {
|
|
617
|
-
if (outputs.data) {
|
|
618
|
-
this.eventStore.emit({
|
|
619
|
-
type: "task",
|
|
620
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
621
|
-
payload: {
|
|
622
|
-
task_id: id,
|
|
623
|
-
action: "status_change",
|
|
624
|
-
details: { outputs: outputs.data },
|
|
625
|
-
},
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
if (outputs.artifacts) {
|
|
629
|
-
this.eventStore.emit({
|
|
630
|
-
type: "task",
|
|
631
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
632
|
-
payload: {
|
|
633
|
-
task_id: id,
|
|
634
|
-
action: "status_change",
|
|
635
|
-
details: { artifacts: outputs.artifacts },
|
|
636
|
-
},
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
this.eventStore.emit({
|
|
642
|
-
type: "task",
|
|
643
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
644
|
-
payload: {
|
|
645
|
-
task_id: id,
|
|
646
|
-
action: "completed",
|
|
647
|
-
details: {},
|
|
648
|
-
},
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
// Optionally close the issue
|
|
652
|
-
const externalId = this.getTaskExternalId(task);
|
|
653
|
-
if (externalId && this.config.autoCloseIssues) {
|
|
654
|
-
try {
|
|
655
|
-
await this.client.updateIssue(externalId, { status: "closed" });
|
|
656
|
-
} catch {
|
|
657
|
-
// Ignore errors syncing to sudocode
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
async fail(id: TaskId, error: TaskError): Promise<void> {
|
|
663
|
-
const task = this.eventStore.getTask(id);
|
|
664
|
-
if (!task) {
|
|
665
|
-
throw new SudocodeTaskBackendError(
|
|
666
|
-
`Task not found: ${id}`,
|
|
667
|
-
"TASK_NOT_FOUND",
|
|
668
|
-
id
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
|
|
673
|
-
if (!validTransitions.includes("failed")) {
|
|
674
|
-
throw new SudocodeTaskBackendError(
|
|
675
|
-
`Invalid status transition: ${task.status} -> failed`,
|
|
676
|
-
"INVALID_STATUS_TRANSITION",
|
|
677
|
-
id
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Store error info in outputs
|
|
682
|
-
this.eventStore.emit({
|
|
683
|
-
type: "task",
|
|
684
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
685
|
-
payload: {
|
|
686
|
-
task_id: id,
|
|
687
|
-
action: "status_change",
|
|
688
|
-
details: {
|
|
689
|
-
outputs: {
|
|
690
|
-
error: {
|
|
691
|
-
message: error.message,
|
|
692
|
-
code: error.code,
|
|
693
|
-
details: error.details,
|
|
694
|
-
},
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
this.eventStore.emit({
|
|
701
|
-
type: "task",
|
|
702
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
703
|
-
payload: {
|
|
704
|
-
task_id: id,
|
|
705
|
-
action: "failed",
|
|
706
|
-
details: {},
|
|
707
|
-
},
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
712
|
-
// Queries
|
|
713
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
714
|
-
|
|
715
|
-
async list(filter?: TaskFilter): Promise<ExtendedTask[]> {
|
|
716
|
-
let tasks = this.eventStore.listTasks();
|
|
717
|
-
|
|
718
|
-
if (filter) {
|
|
719
|
-
// Filter by status
|
|
720
|
-
if (filter.status) {
|
|
721
|
-
const statuses = Array.isArray(filter.status)
|
|
722
|
-
? filter.status
|
|
723
|
-
: [filter.status];
|
|
724
|
-
tasks = tasks.filter((t) => statuses.includes(t.status));
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (filter.assigned_agent) {
|
|
728
|
-
tasks = tasks.filter((t) => t.assigned_agent === filter.assigned_agent);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (filter.parent_task) {
|
|
732
|
-
tasks = tasks.filter((t) => t.parent_task === filter.parent_task);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (filter.created_by) {
|
|
736
|
-
tasks = tasks.filter((t) => t.created_by === filter.created_by);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
if (filter.rootTasksOnly) {
|
|
740
|
-
tasks = tasks.filter((t) => !t.parent_task);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Convert to ExtendedTask (async because of issue lookups)
|
|
745
|
-
const extended = await Promise.all(
|
|
746
|
-
tasks.map((t) => this.toExtendedTask(t))
|
|
747
|
-
);
|
|
748
|
-
|
|
749
|
-
// Filter blocked if needed
|
|
750
|
-
if (!filter?.includeBlocked) {
|
|
751
|
-
return extended.filter((t) => !t.isBlocked);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return extended;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
async listReady(filter?: TaskFilter): Promise<ExtendedTask[]> {
|
|
758
|
-
// Get pending/assigned tasks with isBlocked computed
|
|
759
|
-
const tasks = await this.list({
|
|
760
|
-
...filter,
|
|
761
|
-
status: filter?.status ?? ["pending", "assigned"],
|
|
762
|
-
includeBlocked: true, // We'll filter manually
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
// Filter to ready tasks
|
|
766
|
-
// isBlocked already checks both local and sudocode blockers,
|
|
767
|
-
// respecting local task completion over stale sudocode state.
|
|
768
|
-
const readyTasks: ExtendedTask[] = [];
|
|
769
|
-
|
|
770
|
-
for (const task of tasks) {
|
|
771
|
-
if (task.isBlocked) {
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// For bound tasks, also check issue status directly
|
|
776
|
-
// (excludes tasks bound to issues with "blocked" status)
|
|
777
|
-
if (task.external_id) {
|
|
778
|
-
try {
|
|
779
|
-
const issue = await this.client.getIssue(task.external_id);
|
|
780
|
-
if (issue && issue.status === "blocked") {
|
|
781
|
-
continue;
|
|
782
|
-
}
|
|
783
|
-
} catch {
|
|
784
|
-
// If we can't fetch issue, include the task
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
readyTasks.push(task);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return readyTasks;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
async getChildren(parentId: TaskId): Promise<ExtendedTask[]> {
|
|
795
|
-
const tasks = this.eventStore.listTasks();
|
|
796
|
-
const children = tasks.filter((t) => t.parent_task === parentId);
|
|
797
|
-
return Promise.all(children.map((t) => this.toExtendedTask(t)));
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
async getSubtaskStatus(parentId: TaskId): Promise<SubtaskStatus> {
|
|
801
|
-
const children = await this.getChildren(parentId);
|
|
802
|
-
|
|
803
|
-
const status: SubtaskStatus = {
|
|
804
|
-
total: children.length,
|
|
805
|
-
pending: 0,
|
|
806
|
-
assigned: 0,
|
|
807
|
-
in_progress: 0,
|
|
808
|
-
completed: 0,
|
|
809
|
-
failed: 0,
|
|
810
|
-
allCompleted: false,
|
|
811
|
-
anyFailed: false,
|
|
812
|
-
};
|
|
813
|
-
|
|
814
|
-
for (const task of children) {
|
|
815
|
-
switch (task.status) {
|
|
816
|
-
case "pending":
|
|
817
|
-
status.pending++;
|
|
818
|
-
break;
|
|
819
|
-
case "assigned":
|
|
820
|
-
status.assigned++;
|
|
821
|
-
break;
|
|
822
|
-
case "in_progress":
|
|
823
|
-
status.in_progress++;
|
|
824
|
-
break;
|
|
825
|
-
case "completed":
|
|
826
|
-
status.completed++;
|
|
827
|
-
break;
|
|
828
|
-
case "failed":
|
|
829
|
-
status.failed++;
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
status.allCompleted =
|
|
835
|
-
status.total > 0 && status.completed === status.total;
|
|
836
|
-
status.anyFailed = status.failed > 0;
|
|
837
|
-
|
|
838
|
-
return status;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
842
|
-
// Hierarchy
|
|
843
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
844
|
-
|
|
845
|
-
async createSubtask(
|
|
846
|
-
parentId: TaskId,
|
|
847
|
-
options: CreateTaskOptions
|
|
848
|
-
): Promise<ExtendedTask> {
|
|
849
|
-
return this.create({
|
|
850
|
-
...options,
|
|
851
|
-
parent_task: parentId,
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
856
|
-
// Dependencies
|
|
857
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
858
|
-
|
|
859
|
-
async addBlocker(taskId: TaskId, blockerId: TaskId): Promise<void> {
|
|
860
|
-
const task = this.eventStore.getTask(taskId);
|
|
861
|
-
if (!task) {
|
|
862
|
-
throw new SudocodeTaskBackendError(
|
|
863
|
-
`Task not found: ${taskId}`,
|
|
864
|
-
"TASK_NOT_FOUND",
|
|
865
|
-
taskId
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const blocker = this.eventStore.getTask(blockerId);
|
|
870
|
-
if (!blocker) {
|
|
871
|
-
throw new SudocodeTaskBackendError(
|
|
872
|
-
`Blocker task not found: ${blockerId}`,
|
|
873
|
-
"TASK_NOT_FOUND",
|
|
874
|
-
blockerId
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// Always track locally via EventStore
|
|
879
|
-
this.eventStore.emit({
|
|
880
|
-
type: "task",
|
|
881
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
882
|
-
payload: {
|
|
883
|
-
task_id: taskId,
|
|
884
|
-
action: "blocker_added",
|
|
885
|
-
details: { blocker_id: blockerId },
|
|
886
|
-
},
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
// If both tasks are bound to sudocode issues, create a sudocode relationship
|
|
890
|
-
const taskIssueId = this.getIssueForTask(taskId);
|
|
891
|
-
const blockerIssueId = this.getIssueForTask(blockerId);
|
|
892
|
-
if (taskIssueId && blockerIssueId) {
|
|
893
|
-
try {
|
|
894
|
-
// In sudocode, "A blocks B" means A must complete before B
|
|
895
|
-
// So we create: blockerIssue blocks taskIssue
|
|
896
|
-
await this.client.createLink(blockerIssueId, taskIssueId, "blocks");
|
|
897
|
-
} catch {
|
|
898
|
-
// Log but don't fail - local tracking is the source of truth
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
async removeBlocker(taskId: TaskId, blockerId: TaskId): Promise<void> {
|
|
904
|
-
const task = this.eventStore.getTask(taskId);
|
|
905
|
-
if (!task) {
|
|
906
|
-
throw new SudocodeTaskBackendError(
|
|
907
|
-
`Task not found: ${taskId}`,
|
|
908
|
-
"TASK_NOT_FOUND",
|
|
909
|
-
taskId
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Always update local EventStore tracking
|
|
914
|
-
this.eventStore.emit({
|
|
915
|
-
type: "task",
|
|
916
|
-
source: { agent_id: task.assigned_agent ?? task.created_by },
|
|
917
|
-
payload: {
|
|
918
|
-
task_id: taskId,
|
|
919
|
-
action: "blocker_removed",
|
|
920
|
-
details: { blocker_id: blockerId },
|
|
921
|
-
},
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
// If both tasks are bound to sudocode issues, remove the sudocode relationship
|
|
925
|
-
const taskIssueId = this.getIssueForTask(taskId);
|
|
926
|
-
const blockerIssueId = this.getIssueForTask(blockerId);
|
|
927
|
-
if (taskIssueId && blockerIssueId) {
|
|
928
|
-
try {
|
|
929
|
-
await this.client.removeLink(blockerIssueId, taskIssueId, "blocks");
|
|
930
|
-
} catch {
|
|
931
|
-
// Log but don't fail - local tracking is the source of truth
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
async getBlockers(taskId: TaskId): Promise<ExtendedTask[]> {
|
|
937
|
-
const task = this.eventStore.getTask(taskId);
|
|
938
|
-
if (!task) {
|
|
939
|
-
throw new SudocodeTaskBackendError(
|
|
940
|
-
`Task not found: ${taskId}`,
|
|
941
|
-
"TASK_NOT_FOUND",
|
|
942
|
-
taskId
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// Track blockers by ID to avoid duplicates
|
|
947
|
-
const blockerMap = new Map<TaskId, ExtendedTask>();
|
|
948
|
-
|
|
949
|
-
// Get local task blockers
|
|
950
|
-
const localBlockerIds = task.blockers ?? [];
|
|
951
|
-
for (const blockerId of localBlockerIds) {
|
|
952
|
-
const blocker = this.eventStore.getTask(blockerId);
|
|
953
|
-
if (blocker && !blockerMap.has(blockerId)) {
|
|
954
|
-
blockerMap.set(blockerId, await this.toExtendedTask(blocker));
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// If task is bound to an issue, also get sudocode blockers
|
|
959
|
-
const taskIssueId = this.getIssueForTask(taskId);
|
|
960
|
-
if (taskIssueId) {
|
|
961
|
-
try {
|
|
962
|
-
const issueBlockers = await this.client.getBlockers(taskIssueId);
|
|
963
|
-
for (const issueBlocker of issueBlockers) {
|
|
964
|
-
// Find tasks bound to this blocking issue
|
|
965
|
-
const blockerTaskIds = this.getTasksByIssue(issueBlocker.id);
|
|
966
|
-
for (const blockerTaskId of blockerTaskIds) {
|
|
967
|
-
if (!blockerMap.has(blockerTaskId)) {
|
|
968
|
-
const blockerTask = this.eventStore.getTask(blockerTaskId);
|
|
969
|
-
if (blockerTask) {
|
|
970
|
-
blockerMap.set(
|
|
971
|
-
blockerTaskId,
|
|
972
|
-
await this.toExtendedTask(blockerTask)
|
|
973
|
-
);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
} catch {
|
|
979
|
-
// Ignore errors fetching sudocode blockers - local is source of truth
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
return Array.from(blockerMap.values());
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
async getBlocking(taskId: TaskId): Promise<ExtendedTask[]> {
|
|
987
|
-
const task = this.eventStore.getTask(taskId);
|
|
988
|
-
if (!task) {
|
|
989
|
-
throw new SudocodeTaskBackendError(
|
|
990
|
-
`Task not found: ${taskId}`,
|
|
991
|
-
"TASK_NOT_FOUND",
|
|
992
|
-
taskId
|
|
993
|
-
);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// Track blocked tasks by ID to avoid duplicates
|
|
997
|
-
const blockingMap = new Map<TaskId, ExtendedTask>();
|
|
998
|
-
|
|
999
|
-
// Find all local tasks that have this task in their blockers
|
|
1000
|
-
const allTasks = this.eventStore.listTasks();
|
|
1001
|
-
const localBlocking = allTasks.filter((t) => t.blockers?.includes(taskId));
|
|
1002
|
-
for (const blockedTask of localBlocking) {
|
|
1003
|
-
if (!blockingMap.has(blockedTask.id)) {
|
|
1004
|
-
blockingMap.set(blockedTask.id, await this.toExtendedTask(blockedTask));
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// If task is bound to an issue, also get sudocode blocking
|
|
1009
|
-
const taskIssueId = this.getIssueForTask(taskId);
|
|
1010
|
-
if (taskIssueId) {
|
|
1011
|
-
try {
|
|
1012
|
-
const issueBlocking = await this.client.getBlocking(taskIssueId);
|
|
1013
|
-
for (const blockedIssue of issueBlocking) {
|
|
1014
|
-
// Find tasks bound to this blocked issue
|
|
1015
|
-
const blockedTaskIds = this.getTasksByIssue(blockedIssue.id);
|
|
1016
|
-
for (const blockedTaskId of blockedTaskIds) {
|
|
1017
|
-
if (!blockingMap.has(blockedTaskId)) {
|
|
1018
|
-
const blockedTask = this.eventStore.getTask(blockedTaskId);
|
|
1019
|
-
if (blockedTask) {
|
|
1020
|
-
blockingMap.set(
|
|
1021
|
-
blockedTaskId,
|
|
1022
|
-
await this.toExtendedTask(blockedTask)
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
} catch {
|
|
1029
|
-
// Ignore errors fetching sudocode blocking - local is source of truth
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
return Array.from(blockingMap.values());
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1037
|
-
// History
|
|
1038
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1039
|
-
|
|
1040
|
-
async getAgentHistory(taskId: TaskId): Promise<AgentHistoryEntry[]> {
|
|
1041
|
-
const task = this.eventStore.getTask(taskId);
|
|
1042
|
-
if (!task) {
|
|
1043
|
-
throw new SudocodeTaskBackendError(
|
|
1044
|
-
`Task not found: ${taskId}`,
|
|
1045
|
-
"TASK_NOT_FOUND",
|
|
1046
|
-
taskId
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
return task.agent_history ?? [];
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1054
|
-
// Event Subscriptions
|
|
1055
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1056
|
-
|
|
1057
|
-
onTaskChange(callback: TaskChangeCallback): Unsubscribe;
|
|
1058
|
-
onTaskChange(taskId: TaskId, callback: TaskChangeCallback): Unsubscribe;
|
|
1059
|
-
onTaskChange(
|
|
1060
|
-
callbackOrTaskId: TaskChangeCallback | TaskId,
|
|
1061
|
-
maybeCallback?: TaskChangeCallback
|
|
1062
|
-
): Unsubscribe {
|
|
1063
|
-
const filterTaskId =
|
|
1064
|
-
typeof callbackOrTaskId === "string" ? callbackOrTaskId : undefined;
|
|
1065
|
-
const callback =
|
|
1066
|
-
typeof callbackOrTaskId === "function"
|
|
1067
|
-
? callbackOrTaskId
|
|
1068
|
-
: maybeCallback!;
|
|
1069
|
-
|
|
1070
|
-
// Wrap EventStore's onTaskChange
|
|
1071
|
-
return this.eventStore.onTaskChange((taskId, task) => {
|
|
1072
|
-
if (filterTaskId && taskId !== filterTaskId) {
|
|
1073
|
-
return;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Build TaskChangeEvent using sync conversion (checks local blockers only)
|
|
1077
|
-
const event: TaskChangeEvent = {
|
|
1078
|
-
type: task ? "updated" : "deleted",
|
|
1079
|
-
taskId,
|
|
1080
|
-
task: task ? this.toExtendedTaskSync(task) : ({} as ExtendedTask),
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
callback(event);
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1088
|
-
// Sync Policy
|
|
1089
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1090
|
-
|
|
1091
|
-
/**
|
|
1092
|
-
* Get the current sync policy
|
|
1093
|
-
*/
|
|
1094
|
-
getSyncPolicy(): SyncPolicy {
|
|
1095
|
-
return this.syncEngine.getPolicy();
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
/**
|
|
1099
|
-
* Subscribe to sync events from the policy engine
|
|
1100
|
-
*/
|
|
1101
|
-
onSyncEvent(callback: SyncEventCallback): Unsubscribe {
|
|
1102
|
-
return this.syncEngine.onSyncEvent(callback);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1106
|
-
// Cleanup
|
|
1107
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
* Close the backend and release resources
|
|
1111
|
-
*/
|
|
1112
|
-
close(): void {
|
|
1113
|
-
if (this.issueChangeUnsubscribe) {
|
|
1114
|
-
this.issueChangeUnsubscribe();
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1119
|
-
// Private Helpers
|
|
1120
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Get the external_id from a task (if stored in outputs)
|
|
1124
|
-
*/
|
|
1125
|
-
private getTaskExternalId(task: Task): string | undefined {
|
|
1126
|
-
// First check in-memory index (populated during create and rebuildIndex)
|
|
1127
|
-
const fromIndex = this.issueByTask.get(task.id);
|
|
1128
|
-
if (fromIndex) {
|
|
1129
|
-
return fromIndex;
|
|
1130
|
-
}
|
|
1131
|
-
// Fall back to checking outputs (for persistence after restart via bindToIssue)
|
|
1132
|
-
const outputs = task.outputs as Record<string, unknown> | undefined;
|
|
1133
|
-
if (outputs?.external_id && typeof outputs.external_id === "string") {
|
|
1134
|
-
return outputs.external_id;
|
|
1135
|
-
}
|
|
1136
|
-
return undefined;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
/**
|
|
1140
|
-
* Convert a Task to ExtendedTask with computed isBlocked field.
|
|
1141
|
-
* Checks both local blockers and sudocode issue blockers.
|
|
1142
|
-
*
|
|
1143
|
-
* For sudocode issue blockers, a blocker is considered resolved if:
|
|
1144
|
-
* 1. The issue itself is closed/completed, OR
|
|
1145
|
-
* 2. Any local task bound to that issue is completed (local takes precedence)
|
|
1146
|
-
*/
|
|
1147
|
-
private async toExtendedTask(task: Task): Promise<ExtendedTask> {
|
|
1148
|
-
let isBlocked = false;
|
|
1149
|
-
|
|
1150
|
-
// Check local blockers first
|
|
1151
|
-
const localBlockerIds = task.blockers ?? [];
|
|
1152
|
-
for (const blockerId of localBlockerIds) {
|
|
1153
|
-
const blocker = this.eventStore.getTask(blockerId);
|
|
1154
|
-
if (blocker && blocker.status !== "completed") {
|
|
1155
|
-
isBlocked = true;
|
|
1156
|
-
break;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// Check sudocode issue blockers if task is bound
|
|
1161
|
-
if (!isBlocked) {
|
|
1162
|
-
const externalId = this.getTaskExternalId(task);
|
|
1163
|
-
if (externalId) {
|
|
1164
|
-
try {
|
|
1165
|
-
const issueBlockers = await this.client.getBlockers(externalId);
|
|
1166
|
-
for (const blocker of issueBlockers) {
|
|
1167
|
-
// Check if any local task bound to this blocking issue is completed
|
|
1168
|
-
// Local task completion takes precedence over issue status
|
|
1169
|
-
const localTasksForBlocker = this.getTasksByIssue(blocker.id);
|
|
1170
|
-
const hasCompletedLocalTask = localTasksForBlocker.some(
|
|
1171
|
-
(taskId) => {
|
|
1172
|
-
const blockerTask = this.eventStore.getTask(taskId);
|
|
1173
|
-
return blockerTask && blockerTask.status === "completed";
|
|
1174
|
-
}
|
|
1175
|
-
);
|
|
1176
|
-
|
|
1177
|
-
// Blocker is unresolved if:
|
|
1178
|
-
// - No local task is completed for this issue, AND
|
|
1179
|
-
// - The issue itself is not complete
|
|
1180
|
-
if (!hasCompletedLocalTask && !isIssueComplete(blocker.status)) {
|
|
1181
|
-
isBlocked = true;
|
|
1182
|
-
break;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
} catch {
|
|
1186
|
-
// If we can't fetch blockers, assume not blocked
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
return {
|
|
1192
|
-
...task,
|
|
1193
|
-
isBlocked,
|
|
1194
|
-
external_id: this.getTaskExternalId(task),
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Synchronous version of toExtendedTask that only checks local blockers.
|
|
1200
|
-
* Used in callbacks where async is not possible.
|
|
1201
|
-
* Note: Does not check sudocode issue blockers - only local task blockers.
|
|
1202
|
-
*/
|
|
1203
|
-
private toExtendedTaskSync(task: Task): ExtendedTask {
|
|
1204
|
-
let isBlocked = false;
|
|
1205
|
-
|
|
1206
|
-
// Check local blockers only (sync operation)
|
|
1207
|
-
const localBlockerIds = task.blockers ?? [];
|
|
1208
|
-
for (const blockerId of localBlockerIds) {
|
|
1209
|
-
const blocker = this.eventStore.getTask(blockerId);
|
|
1210
|
-
if (blocker && blocker.status !== "completed") {
|
|
1211
|
-
isBlocked = true;
|
|
1212
|
-
break;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
return {
|
|
1217
|
-
...task,
|
|
1218
|
-
isBlocked,
|
|
1219
|
-
external_id: this.getTaskExternalId(task),
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
/**
|
|
1225
|
-
* Create a SudocodeTaskBackend instance.
|
|
1226
|
-
*
|
|
1227
|
-
* @param eventStore - EventStore for local task storage
|
|
1228
|
-
* @param client - SudocodeClient for issue access
|
|
1229
|
-
* @param config - Optional configuration
|
|
1230
|
-
*/
|
|
1231
|
-
export function createSudocodeTaskBackend(
|
|
1232
|
-
eventStore: EventStore,
|
|
1233
|
-
client: SudocodeClient,
|
|
1234
|
-
config?: SudocodeTaskBackendConfig
|
|
1235
|
-
): SudocodeTaskBackend {
|
|
1236
|
-
return new SudocodeTaskBackend(eventStore, client, config);
|
|
1237
|
-
}
|