@stoneforge/smithy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +114 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +7 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/orchestrator-api.d.ts +153 -0
- package/dist/api/orchestrator-api.d.ts.map +1 -0
- package/dist/api/orchestrator-api.js +374 -0
- package/dist/api/orchestrator-api.js.map +1 -0
- package/dist/bin/sf.d.ts +3 -0
- package/dist/bin/sf.d.ts.map +1 -0
- package/dist/bin/sf.js +10 -0
- package/dist/bin/sf.js.map +1 -0
- package/dist/cli/commands/agent.d.ts +20 -0
- package/dist/cli/commands/agent.d.ts.map +1 -0
- package/dist/cli/commands/agent.js +861 -0
- package/dist/cli/commands/agent.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +14 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +272 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/dispatch.d.ts +9 -0
- package/dist/cli/commands/dispatch.d.ts.map +1 -0
- package/dist/cli/commands/dispatch.js +128 -0
- package/dist/cli/commands/dispatch.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +11 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +246 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/pool.d.ts +21 -0
- package/dist/cli/commands/pool.d.ts.map +1 -0
- package/dist/cli/commands/pool.js +762 -0
- package/dist/cli/commands/pool.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +54 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +57 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/task.d.ts +36 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +889 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/test-orchestration.d.ts +32 -0
- package/dist/cli/commands/test-orchestration.d.ts.map +1 -0
- package/dist/cli/commands/test-orchestration.js +392 -0
- package/dist/cli/commands/test-orchestration.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/plugin.d.ts +23 -0
- package/dist/cli/plugin.d.ts.map +1 -0
- package/dist/cli/plugin.js +36 -0
- package/dist/cli/plugin.js.map +1 -0
- package/dist/git/index.d.ts +10 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +12 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/merge.d.ts +79 -0
- package/dist/git/merge.d.ts.map +1 -0
- package/dist/git/merge.js +254 -0
- package/dist/git/merge.js.map +1 -0
- package/dist/git/worktree-manager.d.ts +299 -0
- package/dist/git/worktree-manager.d.ts.map +1 -0
- package/dist/git/worktree-manager.js +744 -0
- package/dist/git/worktree-manager.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/director.md +272 -0
- package/dist/prompts/index.d.ts +100 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +294 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/message-triage.md +50 -0
- package/dist/prompts/persistent-worker.md +240 -0
- package/dist/prompts/steward-base.md +64 -0
- package/dist/prompts/steward-docs.md +118 -0
- package/dist/prompts/steward-health.md +39 -0
- package/dist/prompts/steward-merge.md +168 -0
- package/dist/prompts/steward-ops.md +28 -0
- package/dist/prompts/steward-reminder.md +26 -0
- package/dist/prompts/worker.md +282 -0
- package/dist/providers/claude/headless.d.ts +18 -0
- package/dist/providers/claude/headless.d.ts.map +1 -0
- package/dist/providers/claude/headless.js +307 -0
- package/dist/providers/claude/headless.js.map +1 -0
- package/dist/providers/claude/index.d.ts +24 -0
- package/dist/providers/claude/index.d.ts.map +1 -0
- package/dist/providers/claude/index.js +80 -0
- package/dist/providers/claude/index.js.map +1 -0
- package/dist/providers/claude/interactive.d.ts +21 -0
- package/dist/providers/claude/interactive.d.ts.map +1 -0
- package/dist/providers/claude/interactive.js +142 -0
- package/dist/providers/claude/interactive.js.map +1 -0
- package/dist/providers/codex/event-mapper.d.ts +91 -0
- package/dist/providers/codex/event-mapper.d.ts.map +1 -0
- package/dist/providers/codex/event-mapper.js +299 -0
- package/dist/providers/codex/event-mapper.js.map +1 -0
- package/dist/providers/codex/headless.d.ts +20 -0
- package/dist/providers/codex/headless.d.ts.map +1 -0
- package/dist/providers/codex/headless.js +174 -0
- package/dist/providers/codex/headless.js.map +1 -0
- package/dist/providers/codex/index.d.ts +30 -0
- package/dist/providers/codex/index.d.ts.map +1 -0
- package/dist/providers/codex/index.js +55 -0
- package/dist/providers/codex/index.js.map +1 -0
- package/dist/providers/codex/interactive.d.ts +21 -0
- package/dist/providers/codex/interactive.d.ts.map +1 -0
- package/dist/providers/codex/interactive.js +141 -0
- package/dist/providers/codex/interactive.js.map +1 -0
- package/dist/providers/codex/jsonrpc-client.d.ts +52 -0
- package/dist/providers/codex/jsonrpc-client.d.ts.map +1 -0
- package/dist/providers/codex/jsonrpc-client.js +141 -0
- package/dist/providers/codex/jsonrpc-client.js.map +1 -0
- package/dist/providers/codex/server-manager.d.ts +100 -0
- package/dist/providers/codex/server-manager.d.ts.map +1 -0
- package/dist/providers/codex/server-manager.js +153 -0
- package/dist/providers/codex/server-manager.js.map +1 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +19 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/opencode/async-queue.d.ts +21 -0
- package/dist/providers/opencode/async-queue.d.ts.map +1 -0
- package/dist/providers/opencode/async-queue.js +51 -0
- package/dist/providers/opencode/async-queue.js.map +1 -0
- package/dist/providers/opencode/event-mapper.d.ts +132 -0
- package/dist/providers/opencode/event-mapper.d.ts.map +1 -0
- package/dist/providers/opencode/event-mapper.js +204 -0
- package/dist/providers/opencode/event-mapper.js.map +1 -0
- package/dist/providers/opencode/headless.d.ts +25 -0
- package/dist/providers/opencode/headless.d.ts.map +1 -0
- package/dist/providers/opencode/headless.js +190 -0
- package/dist/providers/opencode/headless.js.map +1 -0
- package/dist/providers/opencode/index.d.ts +33 -0
- package/dist/providers/opencode/index.d.ts.map +1 -0
- package/dist/providers/opencode/index.js +42 -0
- package/dist/providers/opencode/index.js.map +1 -0
- package/dist/providers/opencode/interactive.d.ts +21 -0
- package/dist/providers/opencode/interactive.d.ts.map +1 -0
- package/dist/providers/opencode/interactive.js +135 -0
- package/dist/providers/opencode/interactive.js.map +1 -0
- package/dist/providers/opencode/server-manager.d.ts +145 -0
- package/dist/providers/opencode/server-manager.d.ts.map +1 -0
- package/dist/providers/opencode/server-manager.js +163 -0
- package/dist/providers/opencode/server-manager.js.map +1 -0
- package/dist/providers/registry.d.ts +38 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +82 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +144 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +25 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/runtime/event-utils.d.ts +8 -0
- package/dist/runtime/event-utils.d.ts.map +1 -0
- package/dist/runtime/event-utils.js +23 -0
- package/dist/runtime/event-utils.js.map +1 -0
- package/dist/runtime/handoff.d.ts +195 -0
- package/dist/runtime/handoff.d.ts.map +1 -0
- package/dist/runtime/handoff.js +332 -0
- package/dist/runtime/handoff.js.map +1 -0
- package/dist/runtime/index.d.ts +17 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +60 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/message-mapper.d.ts +99 -0
- package/dist/runtime/message-mapper.d.ts.map +1 -0
- package/dist/runtime/message-mapper.js +202 -0
- package/dist/runtime/message-mapper.js.map +1 -0
- package/dist/runtime/predecessor-query.d.ts +212 -0
- package/dist/runtime/predecessor-query.d.ts.map +1 -0
- package/dist/runtime/predecessor-query.js +283 -0
- package/dist/runtime/predecessor-query.js.map +1 -0
- package/dist/runtime/session-manager.d.ts +466 -0
- package/dist/runtime/session-manager.d.ts.map +1 -0
- package/dist/runtime/session-manager.js +986 -0
- package/dist/runtime/session-manager.js.map +1 -0
- package/dist/runtime/spawner.d.ts +407 -0
- package/dist/runtime/spawner.d.ts.map +1 -0
- package/dist/runtime/spawner.js +781 -0
- package/dist/runtime/spawner.js.map +1 -0
- package/dist/server/config.d.ts +22 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +59 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/daemon-state.d.ts +50 -0
- package/dist/server/daemon-state.d.ts.map +1 -0
- package/dist/server/daemon-state.js +100 -0
- package/dist/server/daemon-state.js.map +1 -0
- package/dist/server/events-websocket.d.ts +32 -0
- package/dist/server/events-websocket.d.ts.map +1 -0
- package/dist/server/events-websocket.js +96 -0
- package/dist/server/events-websocket.js.map +1 -0
- package/dist/server/formatters.d.ts +94 -0
- package/dist/server/formatters.d.ts.map +1 -0
- package/dist/server/formatters.js +142 -0
- package/dist/server/formatters.js.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +153 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lsp-websocket.d.ts +33 -0
- package/dist/server/lsp-websocket.d.ts.map +1 -0
- package/dist/server/lsp-websocket.js +161 -0
- package/dist/server/lsp-websocket.js.map +1 -0
- package/dist/server/routes/agents.d.ts +9 -0
- package/dist/server/routes/agents.d.ts.map +1 -0
- package/dist/server/routes/agents.js +369 -0
- package/dist/server/routes/agents.js.map +1 -0
- package/dist/server/routes/daemon.d.ts +13 -0
- package/dist/server/routes/daemon.d.ts.map +1 -0
- package/dist/server/routes/daemon.js +187 -0
- package/dist/server/routes/daemon.js.map +1 -0
- package/dist/server/routes/events.d.ts +23 -0
- package/dist/server/routes/events.d.ts.map +1 -0
- package/dist/server/routes/events.js +282 -0
- package/dist/server/routes/events.js.map +1 -0
- package/dist/server/routes/extensions.d.ts +9 -0
- package/dist/server/routes/extensions.d.ts.map +1 -0
- package/dist/server/routes/extensions.js +202 -0
- package/dist/server/routes/extensions.js.map +1 -0
- package/dist/server/routes/health.d.ts +7 -0
- package/dist/server/routes/health.d.ts.map +1 -0
- package/dist/server/routes/health.js +33 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/index.d.ts +21 -0
- package/dist/server/routes/index.d.ts.map +1 -0
- package/dist/server/routes/index.js +21 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/server/routes/lsp.d.ts +9 -0
- package/dist/server/routes/lsp.d.ts.map +1 -0
- package/dist/server/routes/lsp.js +50 -0
- package/dist/server/routes/lsp.js.map +1 -0
- package/dist/server/routes/plugins.d.ts +9 -0
- package/dist/server/routes/plugins.d.ts.map +1 -0
- package/dist/server/routes/plugins.js +109 -0
- package/dist/server/routes/plugins.js.map +1 -0
- package/dist/server/routes/pools.d.ts +9 -0
- package/dist/server/routes/pools.d.ts.map +1 -0
- package/dist/server/routes/pools.js +189 -0
- package/dist/server/routes/pools.js.map +1 -0
- package/dist/server/routes/scheduler.d.ts +9 -0
- package/dist/server/routes/scheduler.d.ts.map +1 -0
- package/dist/server/routes/scheduler.js +162 -0
- package/dist/server/routes/scheduler.js.map +1 -0
- package/dist/server/routes/sessions.d.ts +27 -0
- package/dist/server/routes/sessions.d.ts.map +1 -0
- package/dist/server/routes/sessions.js +773 -0
- package/dist/server/routes/sessions.js.map +1 -0
- package/dist/server/routes/tasks.d.ts +9 -0
- package/dist/server/routes/tasks.d.ts.map +1 -0
- package/dist/server/routes/tasks.js +954 -0
- package/dist/server/routes/tasks.js.map +1 -0
- package/dist/server/routes/upload.d.ts +8 -0
- package/dist/server/routes/upload.d.ts.map +1 -0
- package/dist/server/routes/upload.js +40 -0
- package/dist/server/routes/upload.js.map +1 -0
- package/dist/server/routes/workflows.d.ts +9 -0
- package/dist/server/routes/workflows.d.ts.map +1 -0
- package/dist/server/routes/workflows.js +532 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/routes/workspace-files.d.ts +12 -0
- package/dist/server/routes/workspace-files.d.ts.map +1 -0
- package/dist/server/routes/workspace-files.js +520 -0
- package/dist/server/routes/workspace-files.js.map +1 -0
- package/dist/server/routes/worktrees.d.ts +9 -0
- package/dist/server/routes/worktrees.d.ts.map +1 -0
- package/dist/server/routes/worktrees.js +94 -0
- package/dist/server/routes/worktrees.js.map +1 -0
- package/dist/server/server.d.ts +14 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +258 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/services/lsp-manager.d.ts +93 -0
- package/dist/server/services/lsp-manager.d.ts.map +1 -0
- package/dist/server/services/lsp-manager.js +291 -0
- package/dist/server/services/lsp-manager.js.map +1 -0
- package/dist/server/services/session-messages.d.ts +61 -0
- package/dist/server/services/session-messages.d.ts.map +1 -0
- package/dist/server/services/session-messages.js +101 -0
- package/dist/server/services/session-messages.js.map +1 -0
- package/dist/server/services.d.ts +35 -0
- package/dist/server/services.d.ts.map +1 -0
- package/dist/server/services.js +159 -0
- package/dist/server/services.js.map +1 -0
- package/dist/server/static.d.ts +18 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +71 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +7 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/websocket.d.ts +16 -0
- package/dist/server/websocket.d.ts.map +1 -0
- package/dist/server/websocket.js +143 -0
- package/dist/server/websocket.js.map +1 -0
- package/dist/services/agent-pool-service.d.ts +181 -0
- package/dist/services/agent-pool-service.d.ts.map +1 -0
- package/dist/services/agent-pool-service.js +590 -0
- package/dist/services/agent-pool-service.js.map +1 -0
- package/dist/services/agent-registry.d.ts +185 -0
- package/dist/services/agent-registry.d.ts.map +1 -0
- package/dist/services/agent-registry.js +432 -0
- package/dist/services/agent-registry.js.map +1 -0
- package/dist/services/dispatch-daemon.d.ts +429 -0
- package/dist/services/dispatch-daemon.d.ts.map +1 -0
- package/dist/services/dispatch-daemon.js +1833 -0
- package/dist/services/dispatch-daemon.js.map +1 -0
- package/dist/services/dispatch-service.d.ts +148 -0
- package/dist/services/dispatch-service.d.ts.map +1 -0
- package/dist/services/dispatch-service.js +170 -0
- package/dist/services/dispatch-service.js.map +1 -0
- package/dist/services/docs-steward-service.d.ts +199 -0
- package/dist/services/docs-steward-service.d.ts.map +1 -0
- package/dist/services/docs-steward-service.js +599 -0
- package/dist/services/docs-steward-service.js.map +1 -0
- package/dist/services/health-steward-service.d.ts +446 -0
- package/dist/services/health-steward-service.d.ts.map +1 -0
- package/dist/services/health-steward-service.js +866 -0
- package/dist/services/health-steward-service.js.map +1 -0
- package/dist/services/index.d.ts +26 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +111 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/merge-request-provider.d.ts +59 -0
- package/dist/services/merge-request-provider.d.ts.map +1 -0
- package/dist/services/merge-request-provider.js +89 -0
- package/dist/services/merge-request-provider.js.map +1 -0
- package/dist/services/merge-steward-service.d.ts +268 -0
- package/dist/services/merge-steward-service.d.ts.map +1 -0
- package/dist/services/merge-steward-service.js +568 -0
- package/dist/services/merge-steward-service.js.map +1 -0
- package/dist/services/plugin-executor.d.ts +247 -0
- package/dist/services/plugin-executor.d.ts.map +1 -0
- package/dist/services/plugin-executor.js +451 -0
- package/dist/services/plugin-executor.js.map +1 -0
- package/dist/services/role-definition-service.d.ts +117 -0
- package/dist/services/role-definition-service.d.ts.map +1 -0
- package/dist/services/role-definition-service.js +289 -0
- package/dist/services/role-definition-service.js.map +1 -0
- package/dist/services/steward-scheduler.d.ts +336 -0
- package/dist/services/steward-scheduler.d.ts.map +1 -0
- package/dist/services/steward-scheduler.js +732 -0
- package/dist/services/steward-scheduler.js.map +1 -0
- package/dist/services/task-assignment-service.d.ts +291 -0
- package/dist/services/task-assignment-service.d.ts.map +1 -0
- package/dist/services/task-assignment-service.js +454 -0
- package/dist/services/task-assignment-service.js.map +1 -0
- package/dist/services/worker-task-service.d.ts +202 -0
- package/dist/services/worker-task-service.d.ts.map +1 -0
- package/dist/services/worker-task-service.js +228 -0
- package/dist/services/worker-task-service.js.map +1 -0
- package/dist/testing/index.d.ts +13 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/orchestration-tests.d.ts +62 -0
- package/dist/testing/orchestration-tests.d.ts.map +1 -0
- package/dist/testing/orchestration-tests.js +1115 -0
- package/dist/testing/orchestration-tests.js.map +1 -0
- package/dist/testing/test-context.d.ts +171 -0
- package/dist/testing/test-context.d.ts.map +1 -0
- package/dist/testing/test-context.js +665 -0
- package/dist/testing/test-context.js.map +1 -0
- package/dist/testing/test-prompts.d.ts +46 -0
- package/dist/testing/test-prompts.d.ts.map +1 -0
- package/dist/testing/test-prompts.js +140 -0
- package/dist/testing/test-prompts.js.map +1 -0
- package/dist/testing/test-utils.d.ts +200 -0
- package/dist/testing/test-utils.d.ts.map +1 -0
- package/dist/testing/test-utils.js +378 -0
- package/dist/testing/test-utils.js.map +1 -0
- package/dist/types/agent-pool.d.ts +215 -0
- package/dist/types/agent-pool.d.ts.map +1 -0
- package/dist/types/agent-pool.js +143 -0
- package/dist/types/agent-pool.js.map +1 -0
- package/dist/types/agent.d.ts +265 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +127 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +40 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/message-types.d.ts +294 -0
- package/dist/types/message-types.d.ts.map +1 -0
- package/dist/types/message-types.js +354 -0
- package/dist/types/message-types.js.map +1 -0
- package/dist/types/role-definition.d.ts +272 -0
- package/dist/types/role-definition.d.ts.map +1 -0
- package/dist/types/role-definition.js +144 -0
- package/dist/types/role-definition.js.map +1 -0
- package/dist/types/task-meta.d.ts +248 -0
- package/dist/types/task-meta.d.ts.map +1 -0
- package/dist/types/task-meta.js +213 -0
- package/dist/types/task-meta.js.map +1 -0
- package/package.json +120 -0
- package/web/assets/abap-BrgZPUOV.js +6 -0
- package/web/assets/apex-DyP6w7ZV.js +6 -0
- package/web/assets/azcli-BaLxmfj-.js +6 -0
- package/web/assets/bat-CFOPXBzS.js +6 -0
- package/web/assets/bicep-BfEKNvv3.js +7 -0
- package/web/assets/cameligo-BFG1Mk7z.js +6 -0
- package/web/assets/clojure-DTECt2xU.js +6 -0
- package/web/assets/codicon-DCmgc-ay.ttf +0 -0
- package/web/assets/coffee-CDGzqUPQ.js +6 -0
- package/web/assets/cpp-CLLBncYj.js +6 -0
- package/web/assets/csharp-dUCx_-0o.js +6 -0
- package/web/assets/csp-5Rap-vPy.js +6 -0
- package/web/assets/css-D3h14YRZ.js +8 -0
- package/web/assets/cssMode-DMo-5YLA.js +9 -0
- package/web/assets/cypher-DrQuvNYM.js +6 -0
- package/web/assets/dart-CFKIUWau.js +6 -0
- package/web/assets/dockerfile-Zznr-cwX.js +6 -0
- package/web/assets/ecl-Ce3n6wWz.js +6 -0
- package/web/assets/elixir-deUWdS0T.js +6 -0
- package/web/assets/flow9-i9-g7ZhI.js +6 -0
- package/web/assets/freemarker2-D4qgkQzN.js +8 -0
- package/web/assets/fsharp-CzKuDChf.js +6 -0
- package/web/assets/go-Cphgjts3.js +6 -0
- package/web/assets/graphql-Cg7bfA9N.js +6 -0
- package/web/assets/handlebars-CXFvNjQC.js +6 -0
- package/web/assets/hcl-0cvrggvQ.js +6 -0
- package/web/assets/html-oyuB_D-B.js +6 -0
- package/web/assets/htmlMode-iWuZ24-r.js +9 -0
- package/web/assets/index-DqP-_E4F.css +32 -0
- package/web/assets/index-R1cylSgw.js +1665 -0
- package/web/assets/ini-Drc7WvVn.js +6 -0
- package/web/assets/java-B_fMsGYe.js +6 -0
- package/web/assets/javascript-CRIkN2Pg.js +6 -0
- package/web/assets/jsonMode-DVDkDgex.js +15 -0
- package/web/assets/julia-Bqgm2twL.js +6 -0
- package/web/assets/kotlin-BSkB5QuD.js +6 -0
- package/web/assets/less-BsTHnhdd.js +7 -0
- package/web/assets/lexon-YWi4-JPR.js +6 -0
- package/web/assets/liquid-CSfldbB5.js +6 -0
- package/web/assets/lua-nf6ki56Z.js +6 -0
- package/web/assets/m3-Cpb6xl2v.js +6 -0
- package/web/assets/markdown-DSZPf7rp.js +6 -0
- package/web/assets/mdx-Dd58iymR.js +6 -0
- package/web/assets/mips-B_c3zf-v.js +6 -0
- package/web/assets/monaco-editor-B4lwqA13.js +751 -0
- package/web/assets/monaco-editor-CQpyCxOA.css +1 -0
- package/web/assets/msdax-rUNN04Wq.js +6 -0
- package/web/assets/mysql-DDwshQtU.js +6 -0
- package/web/assets/objective-c-B5zXfXm9.js +6 -0
- package/web/assets/pascal-CXOwvkN_.js +6 -0
- package/web/assets/pascaligo-Bc-ZgV77.js +6 -0
- package/web/assets/perl-CwNk8-XU.js +6 -0
- package/web/assets/pgsql-tGk8EFnU.js +6 -0
- package/web/assets/php-CpIb_Oan.js +6 -0
- package/web/assets/pla-B03wrqEc.js +6 -0
- package/web/assets/postiats-BKlk5iyT.js +6 -0
- package/web/assets/powerquery-Bhzvs7bI.js +6 -0
- package/web/assets/powershell-Dd3NCNK9.js +6 -0
- package/web/assets/protobuf-COyEY5Pt.js +7 -0
- package/web/assets/pug-BaJupSGV.js +6 -0
- package/web/assets/python-XWrMqdhO.js +6 -0
- package/web/assets/qsharp-DXyYeYxl.js +6 -0
- package/web/assets/r-CdQndTaG.js +6 -0
- package/web/assets/razor-DPlhCpIs.js +6 -0
- package/web/assets/redis-CVwtpugi.js +6 -0
- package/web/assets/redshift-25W9uPmb.js +6 -0
- package/web/assets/restructuredtext-DfzH4Xui.js +6 -0
- package/web/assets/router-vendor-DHlGizSU.js +41 -0
- package/web/assets/ruby-Cp1zYvxS.js +6 -0
- package/web/assets/rust-D5C2fndG.js +6 -0
- package/web/assets/sb-CDntyWJ8.js +6 -0
- package/web/assets/scala-BoFRg7Ot.js +6 -0
- package/web/assets/scheme-Bio4gycK.js +6 -0
- package/web/assets/scss-4Ik7cdeQ.js +8 -0
- package/web/assets/shell-CX-rkNHf.js +6 -0
- package/web/assets/solidity-Tw7wswEv.js +6 -0
- package/web/assets/sophia-C5WLch3f.js +6 -0
- package/web/assets/sparql-DHaeiCBh.js +6 -0
- package/web/assets/sql-CCSDG5nI.js +6 -0
- package/web/assets/st-pnP8ivHi.js +6 -0
- package/web/assets/swift-DwJ7jVG9.js +8 -0
- package/web/assets/systemverilog-B9Xyijhd.js +6 -0
- package/web/assets/tcl-DnHyzjbg.js +6 -0
- package/web/assets/tsMode-BbA1Jbf3.js +16 -0
- package/web/assets/twig-CPajHgWi.js +6 -0
- package/web/assets/typescript-DcLHYzvH.js +6 -0
- package/web/assets/typespec-D-MeaMDU.js +6 -0
- package/web/assets/ui-vendor-BSco96uv.js +51 -0
- package/web/assets/utils-vendor-DaJ2Dubl.js +911 -0
- package/web/assets/vb-DgyLZaXg.js +6 -0
- package/web/assets/wgsl-DYQUnd45.js +303 -0
- package/web/assets/xml-xKYS3dO6.js +6 -0
- package/web/assets/yaml-CNmlXqzH.js +6 -0
- package/web/favicon.ico +0 -0
- package/web/index.html +22 -0
- package/web/logo.png +0 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Routes
|
|
3
|
+
*
|
|
4
|
+
* CRUD and dispatch operations for tasks.
|
|
5
|
+
*/
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { createTask, createDocument, TaskStatus, ElementType, Priority, Complexity, ContentType, updateTaskStatus } from '@stoneforge/core';
|
|
8
|
+
import { updateOrchestratorTaskMeta } from '../../index.js';
|
|
9
|
+
import { formatTaskResponse } from '../formatters.js';
|
|
10
|
+
/**
|
|
11
|
+
* Hydrate the description from descriptionRef if it exists
|
|
12
|
+
*/
|
|
13
|
+
async function hydrateTaskDescription(task, api) {
|
|
14
|
+
if (task.descriptionRef) {
|
|
15
|
+
try {
|
|
16
|
+
const doc = await api.get(task.descriptionRef);
|
|
17
|
+
if (doc && doc.type === 'document') {
|
|
18
|
+
return doc.content;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Document not found or error - fall back to null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Format a task response with hydrated description
|
|
29
|
+
*/
|
|
30
|
+
async function formatTaskWithDescription(task, api) {
|
|
31
|
+
const hydratedDescription = await hydrateTaskDescription(task, api);
|
|
32
|
+
return formatTaskResponse(task, hydratedDescription);
|
|
33
|
+
}
|
|
34
|
+
export function createTaskRoutes(services) {
|
|
35
|
+
const { api, agentRegistry, taskAssignmentService, dispatchService, workerTaskService } = services;
|
|
36
|
+
const app = new Hono();
|
|
37
|
+
// GET /api/tasks - List tasks
|
|
38
|
+
app.get('/api/tasks', async (c) => {
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(c.req.url);
|
|
41
|
+
const statusParam = url.searchParams.get('status');
|
|
42
|
+
const assigneeParam = url.searchParams.get('assignee');
|
|
43
|
+
const unassignedParam = url.searchParams.get('unassigned');
|
|
44
|
+
const searchParam = url.searchParams.get('search');
|
|
45
|
+
// Helper to filter tasks by search query (case-insensitive title match)
|
|
46
|
+
const filterBySearch = (taskList) => {
|
|
47
|
+
if (!searchParam)
|
|
48
|
+
return taskList;
|
|
49
|
+
const query = searchParam.toLowerCase();
|
|
50
|
+
return taskList.filter((t) => t.title.toLowerCase().includes(query));
|
|
51
|
+
};
|
|
52
|
+
if (unassignedParam === 'true') {
|
|
53
|
+
const unassignedTasks = await taskAssignmentService.getUnassignedTasks();
|
|
54
|
+
const filtered = filterBySearch(unassignedTasks);
|
|
55
|
+
return c.json({ tasks: filtered.map((t) => formatTaskResponse(t)) });
|
|
56
|
+
}
|
|
57
|
+
if (assigneeParam) {
|
|
58
|
+
const agentAssignments = await taskAssignmentService.getAgentTasks(assigneeParam);
|
|
59
|
+
const agentTasks = agentAssignments.map((a) => a.task);
|
|
60
|
+
let filtered = statusParam
|
|
61
|
+
? agentTasks.filter((t) => t.status === TaskStatus[statusParam.toUpperCase()])
|
|
62
|
+
: agentTasks;
|
|
63
|
+
filtered = filterBySearch(filtered);
|
|
64
|
+
return c.json({ tasks: filtered.map((t) => formatTaskResponse(t)) });
|
|
65
|
+
}
|
|
66
|
+
const allElements = await api.list({ type: ElementType.TASK, limit: 10000 });
|
|
67
|
+
const tasks = allElements.filter((e) => e.type === ElementType.TASK);
|
|
68
|
+
// Filter out tombstoned (deleted) tasks
|
|
69
|
+
let filtered = tasks.filter((t) => t.status !== TaskStatus.TOMBSTONE);
|
|
70
|
+
// Apply status filter if provided
|
|
71
|
+
if (statusParam) {
|
|
72
|
+
filtered = filtered.filter((t) => t.status === TaskStatus[statusParam.toUpperCase()]);
|
|
73
|
+
}
|
|
74
|
+
filtered = filterBySearch(filtered);
|
|
75
|
+
return c.json({ tasks: filtered.map((t) => formatTaskResponse(t)) });
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('[orchestrator] Failed to list tasks:', error);
|
|
79
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// GET /api/tasks/unassigned
|
|
83
|
+
app.get('/api/tasks/unassigned', async (c) => {
|
|
84
|
+
try {
|
|
85
|
+
const tasks = await taskAssignmentService.getUnassignedTasks();
|
|
86
|
+
return c.json({ tasks: tasks.map((t) => formatTaskResponse(t)) });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('[orchestrator] Failed to list unassigned tasks:', error);
|
|
90
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// POST /api/tasks - Create task
|
|
94
|
+
app.post('/api/tasks', async (c) => {
|
|
95
|
+
try {
|
|
96
|
+
const body = (await c.req.json());
|
|
97
|
+
if (!body.title) {
|
|
98
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: 'title is required' } }, 400);
|
|
99
|
+
}
|
|
100
|
+
// Handle priority as either number (1-5) or string
|
|
101
|
+
const priorityStringMap = {
|
|
102
|
+
critical: Priority.CRITICAL,
|
|
103
|
+
high: Priority.HIGH,
|
|
104
|
+
medium: Priority.MEDIUM,
|
|
105
|
+
low: Priority.LOW,
|
|
106
|
+
};
|
|
107
|
+
let priority;
|
|
108
|
+
if (typeof body.priority === 'number') {
|
|
109
|
+
priority = body.priority;
|
|
110
|
+
}
|
|
111
|
+
else if (body.priority) {
|
|
112
|
+
priority = priorityStringMap[body.priority];
|
|
113
|
+
}
|
|
114
|
+
// Handle complexity as either number (1-5) or string
|
|
115
|
+
const complexityStringMap = {
|
|
116
|
+
trivial: Complexity.TRIVIAL,
|
|
117
|
+
simple: Complexity.SIMPLE,
|
|
118
|
+
medium: Complexity.MEDIUM,
|
|
119
|
+
complex: Complexity.COMPLEX,
|
|
120
|
+
very_complex: Complexity.VERY_COMPLEX,
|
|
121
|
+
};
|
|
122
|
+
let complexity;
|
|
123
|
+
if (typeof body.complexity === 'number') {
|
|
124
|
+
complexity = body.complexity;
|
|
125
|
+
}
|
|
126
|
+
else if (body.complexity) {
|
|
127
|
+
complexity = complexityStringMap[body.complexity];
|
|
128
|
+
}
|
|
129
|
+
const metadata = {};
|
|
130
|
+
if (body.description) {
|
|
131
|
+
metadata.description = body.description;
|
|
132
|
+
}
|
|
133
|
+
const createdBy = (body.createdBy ?? 'el-0000');
|
|
134
|
+
// Map status string to TaskStatus enum
|
|
135
|
+
let status;
|
|
136
|
+
if (body.status) {
|
|
137
|
+
const statusMap = {
|
|
138
|
+
backlog: TaskStatus.BACKLOG,
|
|
139
|
+
open: TaskStatus.OPEN,
|
|
140
|
+
in_progress: TaskStatus.IN_PROGRESS,
|
|
141
|
+
blocked: TaskStatus.BLOCKED,
|
|
142
|
+
review: TaskStatus.REVIEW,
|
|
143
|
+
deferred: TaskStatus.DEFERRED,
|
|
144
|
+
closed: TaskStatus.CLOSED,
|
|
145
|
+
};
|
|
146
|
+
status = statusMap[body.status];
|
|
147
|
+
if (!status) {
|
|
148
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: `Invalid status: ${body.status}` } }, 400);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const taskData = await createTask({
|
|
152
|
+
title: body.title,
|
|
153
|
+
status,
|
|
154
|
+
priority,
|
|
155
|
+
complexity,
|
|
156
|
+
taskType: body.taskType,
|
|
157
|
+
assignee: body.assignee ? body.assignee : undefined,
|
|
158
|
+
tags: body.tags,
|
|
159
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
160
|
+
createdBy,
|
|
161
|
+
});
|
|
162
|
+
const savedTask = await api.create(taskData);
|
|
163
|
+
return c.json({ task: formatTaskResponse(savedTask) }, 201);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error('[orchestrator] Failed to create task:', error);
|
|
167
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// PATCH /api/tasks/:id - Update task
|
|
171
|
+
app.patch('/api/tasks/:id', async (c) => {
|
|
172
|
+
try {
|
|
173
|
+
const taskId = c.req.param('id');
|
|
174
|
+
const body = (await c.req.json());
|
|
175
|
+
const task = await api.get(taskId);
|
|
176
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
177
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
178
|
+
}
|
|
179
|
+
const updates = {};
|
|
180
|
+
if (body.title !== undefined)
|
|
181
|
+
updates.title = body.title;
|
|
182
|
+
if (body.description !== undefined) {
|
|
183
|
+
// Description is stored in metadata
|
|
184
|
+
const existingMeta = (task.metadata ?? {});
|
|
185
|
+
updates.metadata = { ...existingMeta, description: body.description };
|
|
186
|
+
}
|
|
187
|
+
if (body.status !== undefined) {
|
|
188
|
+
const statusMap = {
|
|
189
|
+
backlog: TaskStatus.BACKLOG,
|
|
190
|
+
open: TaskStatus.OPEN,
|
|
191
|
+
in_progress: TaskStatus.IN_PROGRESS,
|
|
192
|
+
blocked: TaskStatus.BLOCKED,
|
|
193
|
+
review: TaskStatus.REVIEW,
|
|
194
|
+
deferred: TaskStatus.DEFERRED,
|
|
195
|
+
closed: TaskStatus.CLOSED,
|
|
196
|
+
};
|
|
197
|
+
const mappedStatus = statusMap[body.status];
|
|
198
|
+
if (!mappedStatus) {
|
|
199
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: `Invalid status: ${body.status}` } }, 400);
|
|
200
|
+
}
|
|
201
|
+
updates.status = mappedStatus;
|
|
202
|
+
}
|
|
203
|
+
if (body.priority !== undefined)
|
|
204
|
+
updates.priority = body.priority;
|
|
205
|
+
if (body.complexity !== undefined)
|
|
206
|
+
updates.complexity = body.complexity;
|
|
207
|
+
if (body.assignee !== undefined)
|
|
208
|
+
updates.assignee = body.assignee || null;
|
|
209
|
+
if (body.owner !== undefined)
|
|
210
|
+
updates.owner = body.owner || null;
|
|
211
|
+
if (body.deadline !== undefined)
|
|
212
|
+
updates.deadline = body.deadline || null;
|
|
213
|
+
if (body.tags !== undefined)
|
|
214
|
+
updates.tags = body.tags;
|
|
215
|
+
// Handle mergeStatus update (nested in metadata.orchestrator)
|
|
216
|
+
if (body.mergeStatus !== undefined) {
|
|
217
|
+
const validMergeStatuses = ['pending', 'testing', 'merging', 'merged', 'conflict', 'test_failed', 'failed', 'not_applicable'];
|
|
218
|
+
if (!validMergeStatuses.includes(body.mergeStatus)) {
|
|
219
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: `Invalid mergeStatus: ${body.mergeStatus}` } }, 400);
|
|
220
|
+
}
|
|
221
|
+
const existingMeta = (task.metadata ?? {});
|
|
222
|
+
const existingOrchestrator = (existingMeta.orchestrator ?? {});
|
|
223
|
+
updates.metadata = {
|
|
224
|
+
...existingMeta,
|
|
225
|
+
orchestrator: {
|
|
226
|
+
...existingOrchestrator,
|
|
227
|
+
mergeStatus: body.mergeStatus,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (Object.keys(updates).length === 0) {
|
|
232
|
+
return c.json({ task: formatTaskResponse(task) });
|
|
233
|
+
}
|
|
234
|
+
const updatedTask = await api.update(taskId, updates);
|
|
235
|
+
return c.json({ task: formatTaskResponse(updatedTask) });
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error('[orchestrator] Failed to update task:', error);
|
|
239
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
// GET /api/tasks/:id
|
|
243
|
+
app.get('/api/tasks/:id', async (c) => {
|
|
244
|
+
try {
|
|
245
|
+
const taskId = c.req.param('id');
|
|
246
|
+
const task = await api.get(taskId);
|
|
247
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
248
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
249
|
+
}
|
|
250
|
+
let assignmentInfo = null;
|
|
251
|
+
if (task.assignee) {
|
|
252
|
+
const agent = await agentRegistry.getAgent(task.assignee);
|
|
253
|
+
if (agent) {
|
|
254
|
+
const meta = task.metadata?.orchestrator;
|
|
255
|
+
assignmentInfo = {
|
|
256
|
+
agent: {
|
|
257
|
+
id: agent.id,
|
|
258
|
+
name: agent.name,
|
|
259
|
+
role: agent.metadata?.agent?.agentRole,
|
|
260
|
+
},
|
|
261
|
+
branch: meta?.branch,
|
|
262
|
+
worktree: meta?.worktree,
|
|
263
|
+
sessionId: meta?.sessionId,
|
|
264
|
+
mergeStatus: meta?.mergeStatus,
|
|
265
|
+
startedAt: meta?.startedAt,
|
|
266
|
+
completedAt: meta?.completedAt,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Hydrate description from descriptionRef if it exists
|
|
271
|
+
const formattedTask = await formatTaskWithDescription(task, api);
|
|
272
|
+
return c.json({ task: formattedTask, assignment: assignmentInfo });
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error('[orchestrator] Failed to get task:', error);
|
|
276
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
// DELETE /api/tasks/:id - Soft delete task
|
|
280
|
+
app.delete('/api/tasks/:id', async (c) => {
|
|
281
|
+
try {
|
|
282
|
+
const taskId = c.req.param('id');
|
|
283
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
284
|
+
const task = await api.get(taskId);
|
|
285
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
286
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
287
|
+
}
|
|
288
|
+
// Soft delete by setting status to tombstone
|
|
289
|
+
await api.update(taskId, {
|
|
290
|
+
status: TaskStatus.TOMBSTONE,
|
|
291
|
+
deletedAt: new Date().toISOString(),
|
|
292
|
+
deleteReason: body.reason,
|
|
293
|
+
});
|
|
294
|
+
return c.json({ success: true });
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
console.error('[orchestrator] Failed to delete task:', error);
|
|
298
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
// POST /api/tasks/bulk-delete - Bulk soft delete tasks
|
|
302
|
+
app.post('/api/tasks/bulk-delete', async (c) => {
|
|
303
|
+
try {
|
|
304
|
+
const body = (await c.req.json());
|
|
305
|
+
if (!Array.isArray(body.ids) || body.ids.length === 0) {
|
|
306
|
+
return c.json({ error: { code: 'BAD_REQUEST', message: 'ids must be a non-empty array' } }, 400);
|
|
307
|
+
}
|
|
308
|
+
const results = [];
|
|
309
|
+
for (const id of body.ids) {
|
|
310
|
+
try {
|
|
311
|
+
const task = await api.get(id);
|
|
312
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
313
|
+
results.push({ id, success: false, error: 'Task not found' });
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
await api.update(id, {
|
|
317
|
+
status: TaskStatus.TOMBSTONE,
|
|
318
|
+
deletedAt: new Date().toISOString(),
|
|
319
|
+
});
|
|
320
|
+
results.push({ id, success: true });
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
results.push({ id, success: false, error: String(err) });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return c.json({ success: true, results });
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error('[orchestrator] Failed to bulk delete tasks:', error);
|
|
330
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
// POST /api/tasks/:id/start - Start task (set to in_progress)
|
|
334
|
+
app.post('/api/tasks/:id/start', async (c) => {
|
|
335
|
+
try {
|
|
336
|
+
const taskId = c.req.param('id');
|
|
337
|
+
const task = await api.get(taskId);
|
|
338
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
339
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
340
|
+
}
|
|
341
|
+
if (task.status !== TaskStatus.OPEN) {
|
|
342
|
+
return c.json({ error: { code: 'INVALID_STATE', message: 'Task must be in open status to start' } }, 400);
|
|
343
|
+
}
|
|
344
|
+
const updatedTask = await api.update(taskId, {
|
|
345
|
+
status: TaskStatus.IN_PROGRESS,
|
|
346
|
+
});
|
|
347
|
+
return c.json({ task: formatTaskResponse(updatedTask) });
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error('[orchestrator] Failed to start task:', error);
|
|
351
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
// POST /api/tasks/:id/dispatch
|
|
355
|
+
app.post('/api/tasks/:id/dispatch', async (c) => {
|
|
356
|
+
try {
|
|
357
|
+
const taskId = c.req.param('id');
|
|
358
|
+
const body = (await c.req.json());
|
|
359
|
+
if (!body.agentId) {
|
|
360
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: 'agentId is required' } }, 400);
|
|
361
|
+
}
|
|
362
|
+
const task = await api.get(taskId);
|
|
363
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
364
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
365
|
+
}
|
|
366
|
+
const agent = await agentRegistry.getAgent(body.agentId);
|
|
367
|
+
if (!agent) {
|
|
368
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Agent not found' } }, 404);
|
|
369
|
+
}
|
|
370
|
+
const result = await dispatchService.dispatch(taskId, body.agentId, {
|
|
371
|
+
priority: body.priority,
|
|
372
|
+
restart: body.restart,
|
|
373
|
+
markAsStarted: body.markAsStarted,
|
|
374
|
+
branch: body.branch,
|
|
375
|
+
worktree: body.worktree,
|
|
376
|
+
notificationMessage: body.notificationMessage,
|
|
377
|
+
dispatchedBy: body.dispatchedBy,
|
|
378
|
+
});
|
|
379
|
+
return c.json({
|
|
380
|
+
success: true,
|
|
381
|
+
task: formatTaskResponse(result.task),
|
|
382
|
+
agent: { id: result.agent.id, name: result.agent.name },
|
|
383
|
+
notification: { id: result.notification.id, channelId: result.channel.id },
|
|
384
|
+
isNewAssignment: result.isNewAssignment,
|
|
385
|
+
dispatchedAt: result.dispatchedAt,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
console.error('[orchestrator] Failed to dispatch task:', error);
|
|
390
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
// POST /api/tasks/:id/start-worker
|
|
394
|
+
app.post('/api/tasks/:id/start-worker', async (c) => {
|
|
395
|
+
try {
|
|
396
|
+
const taskId = c.req.param('id');
|
|
397
|
+
const body = (await c.req.json());
|
|
398
|
+
if (!body.agentId) {
|
|
399
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: 'agentId is required' } }, 400);
|
|
400
|
+
}
|
|
401
|
+
const task = await api.get(taskId);
|
|
402
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
403
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
404
|
+
}
|
|
405
|
+
const agent = await agentRegistry.getAgent(body.agentId);
|
|
406
|
+
if (!agent) {
|
|
407
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Agent not found' } }, 404);
|
|
408
|
+
}
|
|
409
|
+
const result = await workerTaskService.startWorkerOnTask(taskId, body.agentId, {
|
|
410
|
+
branch: body.branch,
|
|
411
|
+
worktreePath: body.worktreePath,
|
|
412
|
+
baseBranch: body.baseBranch,
|
|
413
|
+
additionalPrompt: body.additionalPrompt,
|
|
414
|
+
skipWorktree: body.skipWorktree,
|
|
415
|
+
workingDirectory: body.workingDirectory,
|
|
416
|
+
priority: body.priority,
|
|
417
|
+
performedBy: body.performedBy,
|
|
418
|
+
});
|
|
419
|
+
return c.json({
|
|
420
|
+
success: true,
|
|
421
|
+
task: formatTaskResponse(result.task),
|
|
422
|
+
agent: { id: result.agent.id, name: result.agent.name },
|
|
423
|
+
session: {
|
|
424
|
+
id: result.session.id,
|
|
425
|
+
providerSessionId: result.session.providerSessionId,
|
|
426
|
+
status: result.session.status,
|
|
427
|
+
workingDirectory: result.session.workingDirectory,
|
|
428
|
+
},
|
|
429
|
+
worktree: result.worktree
|
|
430
|
+
? { path: result.worktree.path, branch: result.worktree.branch, branchCreated: result.worktree.branchCreated }
|
|
431
|
+
: null,
|
|
432
|
+
dispatch: {
|
|
433
|
+
notificationId: result.dispatch.notification.id,
|
|
434
|
+
channelId: result.dispatch.channel.id,
|
|
435
|
+
isNewAssignment: result.dispatch.isNewAssignment,
|
|
436
|
+
},
|
|
437
|
+
startedAt: result.startedAt,
|
|
438
|
+
}, 201);
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
const errorMessage = String(error);
|
|
442
|
+
if (errorMessage.includes('not a worker')) {
|
|
443
|
+
return c.json({ error: { code: 'INVALID_AGENT', message: 'Agent is not a worker' } }, 400);
|
|
444
|
+
}
|
|
445
|
+
console.error('[orchestrator] Failed to start worker on task:', error);
|
|
446
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: errorMessage } }, 500);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
// POST /api/tasks/:id/complete
|
|
450
|
+
app.post('/api/tasks/:id/complete', async (c) => {
|
|
451
|
+
try {
|
|
452
|
+
const taskId = c.req.param('id');
|
|
453
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
454
|
+
const task = await api.get(taskId);
|
|
455
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
456
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
457
|
+
}
|
|
458
|
+
const result = await workerTaskService.completeTask(taskId, {
|
|
459
|
+
summary: body.summary,
|
|
460
|
+
commitHash: body.commitHash,
|
|
461
|
+
performedBy: body.performedBy,
|
|
462
|
+
});
|
|
463
|
+
return c.json({
|
|
464
|
+
success: true,
|
|
465
|
+
task: formatTaskResponse(result.task),
|
|
466
|
+
worktree: result.worktree
|
|
467
|
+
? { path: result.worktree.path, branch: result.worktree.branch, state: result.worktree.state }
|
|
468
|
+
: null,
|
|
469
|
+
readyForMerge: result.readyForMerge,
|
|
470
|
+
completedAt: result.completedAt,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
console.error('[orchestrator] Failed to complete task:', error);
|
|
475
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
// POST /api/tasks/:id/reset - Reset task to open, clearing all work-in-progress data
|
|
479
|
+
app.post('/api/tasks/:id/reset', async (c) => {
|
|
480
|
+
try {
|
|
481
|
+
const taskId = c.req.param('id');
|
|
482
|
+
const task = await api.get(taskId);
|
|
483
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
484
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
485
|
+
}
|
|
486
|
+
// Only allow reset if task has an assignee or is in_progress/review/closed
|
|
487
|
+
const canReset = task.assignee ||
|
|
488
|
+
task.status === TaskStatus.IN_PROGRESS ||
|
|
489
|
+
task.status === TaskStatus.REVIEW ||
|
|
490
|
+
task.status === TaskStatus.CLOSED;
|
|
491
|
+
if (!canReset) {
|
|
492
|
+
return c.json({ error: { code: 'BAD_REQUEST', message: 'Task cannot be reset - no assignee or work-in-progress state' } }, 400);
|
|
493
|
+
}
|
|
494
|
+
// Update status to OPEN (clears closedAt)
|
|
495
|
+
const updated = updateTaskStatus(task, { status: TaskStatus.OPEN });
|
|
496
|
+
// Clear assignee
|
|
497
|
+
updated.assignee = undefined;
|
|
498
|
+
updated.closeReason = undefined;
|
|
499
|
+
// Clear all orchestrator metadata
|
|
500
|
+
const existingMeta = updated.metadata?.orchestrator;
|
|
501
|
+
if (existingMeta) {
|
|
502
|
+
const reconciliationCount = existingMeta.reconciliationCount ?? 0;
|
|
503
|
+
updated.metadata = updateOrchestratorTaskMeta(updated.metadata, {
|
|
504
|
+
// Clear all orchestrator fields
|
|
505
|
+
branch: undefined,
|
|
506
|
+
worktree: undefined,
|
|
507
|
+
mergeStatus: undefined,
|
|
508
|
+
mergedAt: undefined,
|
|
509
|
+
mergeFailureReason: undefined,
|
|
510
|
+
assignedAgent: undefined,
|
|
511
|
+
sessionId: undefined,
|
|
512
|
+
startedAt: undefined,
|
|
513
|
+
completedAt: undefined,
|
|
514
|
+
completionSummary: undefined,
|
|
515
|
+
lastCommitHash: undefined,
|
|
516
|
+
testRunCount: undefined,
|
|
517
|
+
lastTestResult: undefined,
|
|
518
|
+
lastSyncResult: undefined,
|
|
519
|
+
reconciliationCount: reconciliationCount + 1,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
// Save the update
|
|
523
|
+
await api.update(taskId, updated, {
|
|
524
|
+
expectedUpdatedAt: task.updatedAt,
|
|
525
|
+
});
|
|
526
|
+
// Re-fetch the task to get the latest state
|
|
527
|
+
const finalTask = await api.get(taskId);
|
|
528
|
+
const hydratedDescription = await hydrateTaskDescription(finalTask, api);
|
|
529
|
+
return c.json({
|
|
530
|
+
success: true,
|
|
531
|
+
task: formatTaskResponse(finalTask, hydratedDescription),
|
|
532
|
+
resetAt: finalTask.updatedAt,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
console.error('[orchestrator] Failed to reset task:', error);
|
|
537
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
// POST /api/tasks/:id/reopen
|
|
541
|
+
app.post('/api/tasks/:id/reopen', async (c) => {
|
|
542
|
+
try {
|
|
543
|
+
const taskId = c.req.param('id');
|
|
544
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
545
|
+
const task = await api.get(taskId);
|
|
546
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
547
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
548
|
+
}
|
|
549
|
+
if (task.status !== TaskStatus.CLOSED) {
|
|
550
|
+
return c.json({ error: { code: 'BAD_REQUEST', message: `Task is not closed (status: ${task.status})` } }, 400);
|
|
551
|
+
}
|
|
552
|
+
// Update status to OPEN (clears closedAt)
|
|
553
|
+
const updated = updateTaskStatus(task, { status: TaskStatus.OPEN });
|
|
554
|
+
// Clear assignee and closeReason
|
|
555
|
+
updated.assignee = undefined;
|
|
556
|
+
updated.closeReason = undefined;
|
|
557
|
+
// Clear orchestrator metadata fields while preserving branch/worktree/handoff info
|
|
558
|
+
const existingMeta = updated.metadata?.orchestrator;
|
|
559
|
+
if (existingMeta) {
|
|
560
|
+
const reconciliationCount = existingMeta.reconciliationCount ?? 0;
|
|
561
|
+
updated.metadata = updateOrchestratorTaskMeta(updated.metadata, {
|
|
562
|
+
mergeStatus: undefined,
|
|
563
|
+
mergedAt: undefined,
|
|
564
|
+
mergeFailureReason: undefined,
|
|
565
|
+
assignedAgent: undefined,
|
|
566
|
+
sessionId: undefined,
|
|
567
|
+
startedAt: undefined,
|
|
568
|
+
completedAt: undefined,
|
|
569
|
+
completionSummary: undefined,
|
|
570
|
+
lastCommitHash: undefined,
|
|
571
|
+
testRunCount: undefined,
|
|
572
|
+
lastTestResult: undefined,
|
|
573
|
+
lastSyncResult: undefined,
|
|
574
|
+
reconciliationCount: reconciliationCount + 1,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
// Save the update
|
|
578
|
+
await api.update(taskId, updated, {
|
|
579
|
+
expectedUpdatedAt: task.updatedAt,
|
|
580
|
+
});
|
|
581
|
+
// If message provided, append to or create description document
|
|
582
|
+
if (body.message) {
|
|
583
|
+
const reopenLine = `**Re-opened** — Task was closed but incomplete. Message: ${body.message}`;
|
|
584
|
+
if (task.descriptionRef) {
|
|
585
|
+
const doc = await api.get(task.descriptionRef);
|
|
586
|
+
if (doc) {
|
|
587
|
+
await api.update(task.descriptionRef, {
|
|
588
|
+
content: doc.content + '\n\n' + reopenLine,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
const newDoc = await createDocument({
|
|
594
|
+
content: reopenLine,
|
|
595
|
+
contentType: ContentType.MARKDOWN,
|
|
596
|
+
createdBy: 'orchestrator',
|
|
597
|
+
});
|
|
598
|
+
const created = await api.create(newDoc);
|
|
599
|
+
await api.update(taskId, { descriptionRef: created.id });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Re-fetch the task to get the latest state (including any descriptionRef changes)
|
|
603
|
+
const finalTask = await api.get(taskId);
|
|
604
|
+
const hydratedDescription = await hydrateTaskDescription(finalTask, api);
|
|
605
|
+
return c.json({
|
|
606
|
+
success: true,
|
|
607
|
+
task: formatTaskResponse(finalTask, hydratedDescription),
|
|
608
|
+
reopenedAt: finalTask.updatedAt,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
console.error('[orchestrator] Failed to reopen task:', error);
|
|
613
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
// GET /api/tasks/:id/context
|
|
617
|
+
app.get('/api/tasks/:id/context', async (c) => {
|
|
618
|
+
try {
|
|
619
|
+
const taskId = c.req.param('id');
|
|
620
|
+
const url = new URL(c.req.url);
|
|
621
|
+
const workerIdParam = url.searchParams.get('workerId');
|
|
622
|
+
const additionalInstructions = url.searchParams.get('additionalInstructions') ?? undefined;
|
|
623
|
+
const task = await api.get(taskId);
|
|
624
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
625
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
626
|
+
}
|
|
627
|
+
// Use workerId from query param, or fall back to task assignee
|
|
628
|
+
const workerId = workerIdParam || task.assignee;
|
|
629
|
+
if (!workerId) {
|
|
630
|
+
return c.json({ error: { code: 'BAD_REQUEST', message: 'workerId query parameter required when task has no assignee' } }, 400);
|
|
631
|
+
}
|
|
632
|
+
const context = await workerTaskService.getTaskContext(taskId);
|
|
633
|
+
const prompt = await workerTaskService.buildTaskContextPrompt(taskId, workerId, additionalInstructions);
|
|
634
|
+
return c.json({ task: { id: task.id, title: task.title }, context, prompt });
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
console.error('[orchestrator] Failed to get task context:', error);
|
|
638
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
// POST /api/tasks/:id/cleanup
|
|
642
|
+
app.post('/api/tasks/:id/cleanup', async (c) => {
|
|
643
|
+
try {
|
|
644
|
+
const taskId = c.req.param('id');
|
|
645
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
646
|
+
const task = await api.get(taskId);
|
|
647
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
648
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
649
|
+
}
|
|
650
|
+
const success = await workerTaskService.cleanupTask(taskId, body.deleteBranch ?? false);
|
|
651
|
+
return c.json({ success, taskId, deletedBranch: success && (body.deleteBranch ?? false) });
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
console.error('[orchestrator] Failed to cleanup task:', error);
|
|
655
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
// ============================================================================
|
|
659
|
+
// Task Attachments Endpoints
|
|
660
|
+
// ============================================================================
|
|
661
|
+
// GET /api/tasks/:id/attachments - Get all documents attached to a task
|
|
662
|
+
app.get('/api/tasks/:id/attachments', async (c) => {
|
|
663
|
+
try {
|
|
664
|
+
const taskId = c.req.param('id');
|
|
665
|
+
// Verify task exists
|
|
666
|
+
const task = await api.get(taskId);
|
|
667
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
668
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
669
|
+
}
|
|
670
|
+
// Get all dependencies where this task references a document
|
|
671
|
+
const dependencies = await api.getDependencies(taskId);
|
|
672
|
+
const attachmentDeps = dependencies.filter((dep) => dep.blockedId === taskId && dep.type === 'references');
|
|
673
|
+
// Get the document details for each attachment
|
|
674
|
+
const attachments = await Promise.all(attachmentDeps.map(async (dep) => {
|
|
675
|
+
const doc = await api.get(dep.blockerId);
|
|
676
|
+
if (doc && doc.type === 'document') {
|
|
677
|
+
return doc;
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
680
|
+
}));
|
|
681
|
+
// Filter out nulls (in case documents were deleted)
|
|
682
|
+
return c.json(attachments.filter(Boolean));
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
console.error('[orchestrator] Failed to get task attachments:', error);
|
|
686
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
// POST /api/tasks/:id/attachments - Attach a document to a task
|
|
690
|
+
app.post('/api/tasks/:id/attachments', async (c) => {
|
|
691
|
+
try {
|
|
692
|
+
const taskId = c.req.param('id');
|
|
693
|
+
const body = (await c.req.json());
|
|
694
|
+
// Validate document ID
|
|
695
|
+
if (!body.documentId || typeof body.documentId !== 'string') {
|
|
696
|
+
return c.json({ error: { code: 'VALIDATION_ERROR', message: 'documentId is required' } }, 400);
|
|
697
|
+
}
|
|
698
|
+
// Verify task exists
|
|
699
|
+
const task = await api.get(taskId);
|
|
700
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
701
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
702
|
+
}
|
|
703
|
+
// Verify document exists
|
|
704
|
+
const doc = await api.get(body.documentId);
|
|
705
|
+
if (!doc || doc.type !== 'document') {
|
|
706
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Document not found' } }, 404);
|
|
707
|
+
}
|
|
708
|
+
// Check if already attached
|
|
709
|
+
const existingDeps = await api.getDependencies(taskId);
|
|
710
|
+
const alreadyAttached = existingDeps.some((dep) => dep.blockedId === taskId && dep.blockerId === body.documentId && dep.type === 'references');
|
|
711
|
+
if (alreadyAttached) {
|
|
712
|
+
return c.json({ error: { code: 'VALIDATION_ERROR', message: 'Document is already attached to this task' } }, 400);
|
|
713
|
+
}
|
|
714
|
+
// Create the references dependency
|
|
715
|
+
await api.addDependency({
|
|
716
|
+
blockedId: taskId,
|
|
717
|
+
blockerId: body.documentId,
|
|
718
|
+
type: 'references',
|
|
719
|
+
actor: body.actor || 'el-0000',
|
|
720
|
+
});
|
|
721
|
+
return c.json(doc, 201);
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
console.error('[orchestrator] Failed to attach document to task:', error);
|
|
725
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
// DELETE /api/tasks/:id/attachments/:docId - Remove a document attachment
|
|
729
|
+
app.delete('/api/tasks/:id/attachments/:docId', async (c) => {
|
|
730
|
+
try {
|
|
731
|
+
const taskId = c.req.param('id');
|
|
732
|
+
const docId = c.req.param('docId');
|
|
733
|
+
// Verify task exists
|
|
734
|
+
const task = await api.get(taskId);
|
|
735
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
736
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
737
|
+
}
|
|
738
|
+
// Find the attachment dependency
|
|
739
|
+
const dependencies = await api.getDependencies(taskId);
|
|
740
|
+
const attachmentDep = dependencies.find((dep) => dep.blockedId === taskId && dep.blockerId === docId && dep.type === 'references');
|
|
741
|
+
if (!attachmentDep) {
|
|
742
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Attachment not found' } }, 404);
|
|
743
|
+
}
|
|
744
|
+
// Remove the dependency
|
|
745
|
+
await api.removeDependency(taskId, docId, 'references');
|
|
746
|
+
return c.json({ success: true, taskId, documentId: docId });
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
console.error('[orchestrator] Failed to remove task attachment:', error);
|
|
750
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
// ============================================================================
|
|
754
|
+
// Task Dependencies Endpoints
|
|
755
|
+
// ============================================================================
|
|
756
|
+
// Blocking dependency types that prevent a task from proceeding
|
|
757
|
+
const BLOCKING_DEPENDENCY_TYPES = ['blocks', 'parent-child', 'awaits'];
|
|
758
|
+
// GET /api/tasks/:id/dependency-tasks - Get hydrated dependency info for UI display
|
|
759
|
+
app.get('/api/tasks/:id/dependency-tasks', async (c) => {
|
|
760
|
+
try {
|
|
761
|
+
const taskId = c.req.param('id');
|
|
762
|
+
// Verify task exists
|
|
763
|
+
const task = await api.get(taskId);
|
|
764
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
765
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
766
|
+
}
|
|
767
|
+
// Get tasks that block this task (blockedBy)
|
|
768
|
+
const dependencies = await api.getDependencies(taskId);
|
|
769
|
+
const blockingDeps = dependencies.filter((dep) => dep.blockedId === taskId && BLOCKING_DEPENDENCY_TYPES.includes(dep.type));
|
|
770
|
+
// Get tasks that this task blocks (blocks)
|
|
771
|
+
const dependents = await api.getDependents(taskId);
|
|
772
|
+
const blockedDeps = dependents.filter((dep) => dep.blockerId === taskId && BLOCKING_DEPENDENCY_TYPES.includes(dep.type));
|
|
773
|
+
// Hydrate task details for blockedBy
|
|
774
|
+
const blockedBy = await Promise.all(blockingDeps.map(async (dep) => {
|
|
775
|
+
const blockerTask = await api.get(dep.blockerId);
|
|
776
|
+
if (blockerTask && blockerTask.type === ElementType.TASK) {
|
|
777
|
+
return {
|
|
778
|
+
dependencyType: dep.type,
|
|
779
|
+
task: {
|
|
780
|
+
id: blockerTask.id,
|
|
781
|
+
title: blockerTask.title,
|
|
782
|
+
status: blockerTask.status,
|
|
783
|
+
priority: blockerTask.priority,
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}));
|
|
789
|
+
// Hydrate task details for blocks
|
|
790
|
+
const blocks = await Promise.all(blockedDeps.map(async (dep) => {
|
|
791
|
+
const blockedTask = await api.get(dep.blockedId);
|
|
792
|
+
if (blockedTask && blockedTask.type === ElementType.TASK) {
|
|
793
|
+
return {
|
|
794
|
+
dependencyType: dep.type,
|
|
795
|
+
task: {
|
|
796
|
+
id: blockedTask.id,
|
|
797
|
+
title: blockedTask.title,
|
|
798
|
+
status: blockedTask.status,
|
|
799
|
+
priority: blockedTask.priority,
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}));
|
|
805
|
+
// Filter out nulls
|
|
806
|
+
const filteredBlockedBy = blockedBy.filter(Boolean);
|
|
807
|
+
const filteredBlocks = blocks.filter(Boolean);
|
|
808
|
+
// Calculate progress (resolved = closed blockers)
|
|
809
|
+
const resolvedCount = filteredBlockedBy.filter((dep) => {
|
|
810
|
+
return dep?.task?.status === TaskStatus.CLOSED;
|
|
811
|
+
}).length;
|
|
812
|
+
return c.json({
|
|
813
|
+
blockedBy: filteredBlockedBy,
|
|
814
|
+
blocks: filteredBlocks,
|
|
815
|
+
progress: {
|
|
816
|
+
resolved: resolvedCount,
|
|
817
|
+
total: filteredBlockedBy.length,
|
|
818
|
+
},
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
console.error('[orchestrator] Failed to get task dependencies:', error);
|
|
823
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
// POST /api/tasks/:id/dependencies - Add a blocking dependency to a task
|
|
827
|
+
app.post('/api/tasks/:id/dependencies', async (c) => {
|
|
828
|
+
try {
|
|
829
|
+
const taskId = c.req.param('id');
|
|
830
|
+
const body = (await c.req.json());
|
|
831
|
+
if (!body.blockerId) {
|
|
832
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: 'blockerId is required' } }, 400);
|
|
833
|
+
}
|
|
834
|
+
const blockerId = body.blockerId;
|
|
835
|
+
const dependencyType = body.type || 'blocks';
|
|
836
|
+
// Verify both tasks exist
|
|
837
|
+
const task = await api.get(taskId);
|
|
838
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
839
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
840
|
+
}
|
|
841
|
+
const blockerTask = await api.get(blockerId);
|
|
842
|
+
if (!blockerTask || blockerTask.type !== ElementType.TASK) {
|
|
843
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Blocker task not found' } }, 404);
|
|
844
|
+
}
|
|
845
|
+
// Prevent self-reference
|
|
846
|
+
if (taskId === blockerId) {
|
|
847
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: 'A task cannot block itself' } }, 400);
|
|
848
|
+
}
|
|
849
|
+
// Add the dependency (blocker blocks this task)
|
|
850
|
+
await api.addDependency({
|
|
851
|
+
blockedId: taskId,
|
|
852
|
+
blockerId: blockerId,
|
|
853
|
+
type: dependencyType,
|
|
854
|
+
actor: 'el-0000', // System actor for UI operations
|
|
855
|
+
});
|
|
856
|
+
return c.json({
|
|
857
|
+
success: true,
|
|
858
|
+
dependency: {
|
|
859
|
+
blockedId: taskId,
|
|
860
|
+
blockerId: blockerId,
|
|
861
|
+
type: dependencyType,
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
console.error('[orchestrator] Failed to add dependency:', error);
|
|
867
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
// DELETE /api/tasks/:id/dependencies/:blockerId - Remove a blocking dependency from a task
|
|
871
|
+
app.delete('/api/tasks/:id/dependencies/:blockerId', async (c) => {
|
|
872
|
+
try {
|
|
873
|
+
const taskId = c.req.param('id');
|
|
874
|
+
const blockerId = c.req.param('blockerId');
|
|
875
|
+
// Verify task exists
|
|
876
|
+
const task = await api.get(taskId);
|
|
877
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
878
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
879
|
+
}
|
|
880
|
+
// Find the dependency to get its type
|
|
881
|
+
const dependencies = await api.getDependencies(taskId);
|
|
882
|
+
const dependency = dependencies.find((dep) => dep.blockerId === blockerId && BLOCKING_DEPENDENCY_TYPES.includes(dep.type));
|
|
883
|
+
if (!dependency) {
|
|
884
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Dependency not found' } }, 404);
|
|
885
|
+
}
|
|
886
|
+
// Remove the dependency with the correct type
|
|
887
|
+
await api.removeDependency(taskId, blockerId, dependency.type, 'el-0000');
|
|
888
|
+
return c.json({
|
|
889
|
+
success: true,
|
|
890
|
+
taskId,
|
|
891
|
+
blockerId,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
catch (error) {
|
|
895
|
+
console.error('[orchestrator] Failed to remove dependency:', error);
|
|
896
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
// ============================================================================
|
|
900
|
+
// Merge Status Endpoints
|
|
901
|
+
// ============================================================================
|
|
902
|
+
// Valid merge status values
|
|
903
|
+
const VALID_MERGE_STATUSES = [
|
|
904
|
+
'pending',
|
|
905
|
+
'testing',
|
|
906
|
+
'merging',
|
|
907
|
+
'merged',
|
|
908
|
+
'conflict',
|
|
909
|
+
'test_failed',
|
|
910
|
+
'failed',
|
|
911
|
+
'not_applicable',
|
|
912
|
+
];
|
|
913
|
+
// PATCH /api/tasks/:id/merge-status - Update merge status
|
|
914
|
+
app.patch('/api/tasks/:id/merge-status', async (c) => {
|
|
915
|
+
try {
|
|
916
|
+
const taskId = c.req.param('id');
|
|
917
|
+
const body = (await c.req.json());
|
|
918
|
+
if (!body.mergeStatus) {
|
|
919
|
+
return c.json({ error: { code: 'INVALID_INPUT', message: 'mergeStatus is required' } }, 400);
|
|
920
|
+
}
|
|
921
|
+
if (!VALID_MERGE_STATUSES.includes(body.mergeStatus)) {
|
|
922
|
+
return c.json({
|
|
923
|
+
error: {
|
|
924
|
+
code: 'INVALID_INPUT',
|
|
925
|
+
message: `Invalid mergeStatus. Must be one of: ${VALID_MERGE_STATUSES.join(', ')}`,
|
|
926
|
+
},
|
|
927
|
+
}, 400);
|
|
928
|
+
}
|
|
929
|
+
const task = await api.get(taskId);
|
|
930
|
+
if (!task || task.type !== ElementType.TASK) {
|
|
931
|
+
return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
|
|
932
|
+
}
|
|
933
|
+
// Update orchestrator metadata with new merge status
|
|
934
|
+
const existingMeta = (task.metadata ?? {});
|
|
935
|
+
const updatedMeta = updateOrchestratorTaskMeta(existingMeta, {
|
|
936
|
+
mergeStatus: body.mergeStatus,
|
|
937
|
+
// If setting to merged, also set mergedAt timestamp
|
|
938
|
+
...(body.mergeStatus === 'merged' ? { mergedAt: new Date().toISOString() } : {}),
|
|
939
|
+
// Clear failure reason if status is no longer a failure state
|
|
940
|
+
...(body.mergeStatus !== 'conflict' && body.mergeStatus !== 'test_failed' && body.mergeStatus !== 'failed'
|
|
941
|
+
? { mergeFailureReason: undefined }
|
|
942
|
+
: {}),
|
|
943
|
+
});
|
|
944
|
+
const updatedTask = await api.update(taskId, { metadata: updatedMeta });
|
|
945
|
+
return c.json({ task: formatTaskResponse(updatedTask) });
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
console.error('[orchestrator] Failed to update merge status:', error);
|
|
949
|
+
return c.json({ error: { code: 'INTERNAL_ERROR', message: String(error) } }, 500);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
return app;
|
|
953
|
+
}
|
|
954
|
+
//# sourceMappingURL=tasks.js.map
|