oh-my-codex 0.7.6 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +315 -0
- package/README.es.md +296 -17
- package/README.fr.md +315 -0
- package/README.it.md +315 -0
- package/README.ja.md +297 -18
- package/README.ko.md +296 -17
- package/README.md +110 -13
- package/README.pt.md +296 -17
- package/README.ru.md +296 -17
- package/README.tr.md +315 -0
- package/README.vi.md +297 -18
- package/README.zh-TW.md +362 -0
- package/README.zh.md +293 -17
- package/dist/catalog/__tests__/generator.test.js +2 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/catalog/__tests__/schema.test.js +7 -0
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/cli/__tests__/ask.test.d.ts +2 -0
- package/dist/cli/__tests__/ask.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ask.test.js +236 -0
- package/dist/cli/__tests__/ask.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +45 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +85 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.d.ts +2 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +15 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -0
- package/dist/cli/__tests__/ralph.test.js +19 -43
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +2 -0
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +219 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/version.test.d.ts +2 -0
- package/dist/cli/__tests__/version.test.d.ts.map +1 -0
- package/dist/cli/__tests__/version.test.js +21 -0
- package/dist/cli/__tests__/version.test.js.map +1 -0
- package/dist/cli/ask.d.ts +13 -0
- package/dist/cli/ask.d.ts.map +1 -0
- package/dist/cli/ask.js +174 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/constants.d.ts +10 -0
- package/dist/cli/constants.d.ts.map +1 -0
- package/dist/cli/constants.js +10 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/doctor.js +16 -5
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +8 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +150 -52
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ralph.d.ts +3 -11
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +64 -45
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +17 -18
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +257 -0
- package/dist/cli/team.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js +55 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/emulator.test.js +6 -0
- package/dist/hooks/__tests__/emulator.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +44 -22
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +23 -7
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +59 -0
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +264 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +61 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +17 -7
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/openclaw-setup-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/openclaw-setup-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js +61 -0
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.d.ts +2 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js +34 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.d.ts +2 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.js +35 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -0
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +18 -16
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/codebase-map.d.ts.map +1 -1
- package/dist/hooks/codebase-map.js +6 -2
- package/dist/hooks/codebase-map.js.map +1 -1
- package/dist/hooks/emulator.d.ts.map +1 -1
- package/dist/hooks/emulator.js +2 -0
- package/dist/hooks/emulator.js.map +1 -1
- package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk.js +2 -1
- package/dist/hooks/extensibility/sdk.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +6 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +2 -24
- package/dist/hud/index.js.map +1 -1
- package/dist/mcp/__tests__/path-traversal.test.js +9 -227
- package/dist/mcp/__tests__/path-traversal.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-schema.test.js +16 -20
- package/dist/mcp/__tests__/state-server-schema.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-team-tools.test.js +30 -487
- package/dist/mcp/__tests__/state-server-team-tools.test.js.map +1 -1
- package/dist/mcp/code-intel-server.d.ts.map +1 -1
- package/dist/mcp/code-intel-server.js +18 -8
- package/dist/mcp/code-intel-server.js.map +1 -1
- package/dist/mcp/memory-server.js +72 -11
- package/dist/mcp/memory-server.js.map +1 -1
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +4 -1
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +179 -0
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +221 -1111
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/mcp/team-server.d.ts.map +1 -1
- package/dist/mcp/team-server.js +9 -3
- package/dist/mcp/team-server.js.map +1 -1
- package/dist/mcp/trace-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.js +8 -3
- package/dist/mcp/trace-server.js.map +1 -1
- package/dist/notifications/__tests__/dispatch-cooldown.test.d.ts +5 -0
- package/dist/notifications/__tests__/dispatch-cooldown.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/dispatch-cooldown.test.js +100 -0
- package/dist/notifications/__tests__/dispatch-cooldown.test.js.map +1 -0
- package/dist/notifications/__tests__/temp-mode.test.d.ts +2 -0
- package/dist/notifications/__tests__/temp-mode.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/temp-mode.test.js +172 -0
- package/dist/notifications/__tests__/temp-mode.test.js.map +1 -0
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +67 -7
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/dispatch-cooldown.d.ts +36 -0
- package/dist/notifications/dispatch-cooldown.d.ts.map +1 -0
- package/dist/notifications/dispatch-cooldown.js +109 -0
- package/dist/notifications/dispatch-cooldown.js.map +1 -0
- package/dist/notifications/dispatcher.d.ts.map +1 -1
- package/dist/notifications/dispatcher.js +4 -4
- package/dist/notifications/dispatcher.js.map +1 -1
- package/dist/notifications/index.d.ts +5 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +39 -8
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +6 -2
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.d.ts.map +1 -1
- package/dist/notifications/session-registry.js +2 -2
- package/dist/notifications/session-registry.js.map +1 -1
- package/dist/notifications/temp-contract.d.ts +22 -0
- package/dist/notifications/temp-contract.d.ts.map +1 -0
- package/dist/notifications/temp-contract.js +147 -0
- package/dist/notifications/temp-contract.js.map +1 -0
- package/dist/notifications/tmux.js +2 -2
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/notifications/types.d.ts +18 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/config.test.js +81 -0
- package/dist/openclaw/__tests__/config.test.js.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +40 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/config.d.ts +4 -0
- package/dist/openclaw/config.d.ts.map +1 -1
- package/dist/openclaw/config.js +110 -16
- package/dist/openclaw/config.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts +9 -3
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +42 -9
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/openclaw/types.d.ts +5 -1
- package/dist/openclaw/types.d.ts.map +1 -1
- package/dist/ralph/__tests__/persistence.test.js +28 -1
- package/dist/ralph/__tests__/persistence.test.js.map +1 -1
- package/dist/ralph/persistence.d.ts +21 -0
- package/dist/ralph/persistence.d.ts.map +1 -1
- package/dist/ralph/persistence.js +85 -2
- package/dist/ralph/persistence.js.map +1 -1
- package/dist/state/paths.d.ts +3 -0
- package/dist/state/paths.d.ts.map +1 -0
- package/dist/state/paths.js +2 -0
- package/dist/state/paths.js.map +1 -0
- package/dist/team/__tests__/api-interop.test.d.ts +2 -0
- package/dist/team/__tests__/api-interop.test.d.ts.map +1 -0
- package/dist/team/__tests__/api-interop.test.js +1052 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -0
- package/dist/team/__tests__/idle-nudge.test.d.ts +2 -0
- package/dist/team/__tests__/idle-nudge.test.d.ts.map +1 -0
- package/dist/team/__tests__/idle-nudge.test.js +225 -0
- package/dist/team/__tests__/idle-nudge.test.js.map +1 -0
- package/dist/team/__tests__/mcp-comm.test.js +30 -0
- package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +33 -26
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.d.ts +2 -0
- package/dist/team/__tests__/state-root.test.d.ts.map +1 -0
- package/dist/team/__tests__/state-root.test.js +9 -0
- package/dist/team/__tests__/state-root.test.js.map +1 -0
- package/dist/team/__tests__/state.test.js +52 -17
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-ops-contract.test.d.ts +2 -0
- package/dist/team/__tests__/team-ops-contract.test.d.ts.map +1 -0
- package/dist/team/__tests__/team-ops-contract.test.js +90 -0
- package/dist/team/__tests__/team-ops-contract.test.js.map +1 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.d.ts +2 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.d.ts.map +1 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js +176 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.test.js +8 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +29 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worktree.test.js +54 -1
- package/dist/team/__tests__/worktree.test.js.map +1 -1
- package/dist/team/api-interop.d.ts +19 -0
- package/dist/team/api-interop.d.ts.map +1 -0
- package/dist/team/api-interop.js +578 -0
- package/dist/team/api-interop.js.map +1 -0
- package/dist/team/mcp-comm.d.ts.map +1 -1
- package/dist/team/mcp-comm.js +32 -2
- package/dist/team/mcp-comm.js.map +1 -1
- package/dist/team/orchestrator.d.ts +1 -10
- package/dist/team/orchestrator.d.ts.map +1 -1
- package/dist/team/orchestrator.js +8 -0
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/runtime-cli.js +14 -8
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +103 -30
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +33 -12
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/approvals.d.ts +25 -0
- package/dist/team/state/approvals.d.ts.map +1 -0
- package/dist/team/state/approvals.js +31 -0
- package/dist/team/state/approvals.js.map +1 -0
- package/dist/team/state/config.d.ts +2 -0
- package/dist/team/state/config.d.ts.map +1 -0
- package/dist/team/state/config.js +2 -0
- package/dist/team/state/config.js.map +1 -0
- package/dist/team/state/dispatch-lock.d.ts +3 -0
- package/dist/team/state/dispatch-lock.d.ts.map +1 -0
- package/dist/team/state/dispatch-lock.js +81 -0
- package/dist/team/state/dispatch-lock.js.map +1 -0
- package/dist/team/state/dispatch.d.ts +61 -0
- package/dist/team/state/dispatch.d.ts.map +1 -0
- package/dist/team/state/dispatch.js +158 -0
- package/dist/team/state/dispatch.js.map +1 -0
- package/dist/team/state/events.d.ts +2 -0
- package/dist/team/state/events.d.ts.map +1 -0
- package/dist/team/state/events.js +2 -0
- package/dist/team/state/events.js.map +1 -0
- package/dist/team/state/index.d.ts +11 -0
- package/dist/team/state/index.d.ts.map +1 -0
- package/dist/team/state/index.js +11 -0
- package/dist/team/state/index.js.map +1 -0
- package/dist/team/state/io.d.ts +2 -0
- package/dist/team/state/io.d.ts.map +1 -0
- package/dist/team/state/io.js +2 -0
- package/dist/team/state/io.js.map +1 -0
- package/dist/team/state/locks.d.ts +16 -0
- package/dist/team/state/locks.d.ts.map +1 -0
- package/dist/team/state/locks.js +201 -0
- package/dist/team/state/locks.js.map +1 -0
- package/dist/team/state/mailbox.d.ts +39 -0
- package/dist/team/state/mailbox.d.ts.map +1 -0
- package/dist/team/state/mailbox.js +58 -0
- package/dist/team/state/mailbox.js.map +1 -0
- package/dist/team/state/monitor.d.ts +96 -0
- package/dist/team/state/monitor.d.ts.map +1 -0
- package/dist/team/state/monitor.js +163 -0
- package/dist/team/state/monitor.js.map +1 -0
- package/dist/team/state/shutdown.d.ts +2 -0
- package/dist/team/state/shutdown.d.ts.map +1 -0
- package/dist/team/state/shutdown.js +2 -0
- package/dist/team/state/shutdown.js.map +1 -0
- package/dist/team/state/summary.d.ts +2 -0
- package/dist/team/state/summary.d.ts.map +1 -0
- package/dist/team/state/summary.js +2 -0
- package/dist/team/state/summary.js.map +1 -0
- package/dist/team/state/tasks.d.ts +49 -0
- package/dist/team/state/tasks.d.ts.map +1 -0
- package/dist/team/state/tasks.js +182 -0
- package/dist/team/state/tasks.js.map +1 -0
- package/dist/team/state/types.d.ts +281 -0
- package/dist/team/state/types.d.ts.map +1 -0
- package/dist/team/state/types.js +3 -0
- package/dist/team/state/types.js.map +1 -0
- package/dist/team/state/workers.d.ts +2 -0
- package/dist/team/state/workers.d.ts.map +1 -0
- package/dist/team/state/workers.js +2 -0
- package/dist/team/state/workers.js.map +1 -0
- package/dist/team/state-root.d.ts +5 -0
- package/dist/team/state-root.d.ts.map +1 -0
- package/dist/team/state-root.js +8 -0
- package/dist/team/state-root.js.map +1 -0
- package/dist/team/state.d.ts +4 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +200 -881
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +11 -10
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +58 -26
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +43 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/safe-json.d.ts +3 -0
- package/dist/utils/safe-json.d.ts.map +1 -0
- package/dist/utils/safe-json.js +19 -0
- package/dist/utils/safe-json.js.map +1 -0
- package/dist/utils/sleep.d.ts +3 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +15 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/visual/__tests__/verdict.test.d.ts +2 -0
- package/dist/visual/__tests__/verdict.test.d.ts.map +1 -0
- package/dist/visual/__tests__/verdict.test.js +81 -0
- package/dist/visual/__tests__/verdict.test.js.map +1 -0
- package/dist/visual/constants.d.ts +4 -0
- package/dist/visual/constants.d.ts.map +1 -0
- package/dist/visual/constants.js +3 -0
- package/dist/visual/constants.js.map +1 -0
- package/dist/visual/verdict.d.ts +17 -0
- package/dist/visual/verdict.d.ts.map +1 -0
- package/dist/visual/verdict.js +61 -0
- package/dist/visual/verdict.js.map +1 -0
- package/package.json +10 -3
- package/scripts/ask-claude.sh +17 -0
- package/scripts/ask-gemini.sh +14 -0
- package/scripts/demo-claude-workers.sh +241 -0
- package/scripts/demo-team-e2e.sh +179 -0
- package/scripts/fixtures/ask-advisor-stub.js +12 -0
- package/scripts/notify-hook/team-dispatch.js +234 -12
- package/scripts/notify-hook/team-leader-nudge.js +42 -2
- package/scripts/notify-hook/team-worker.js +63 -4
- package/scripts/notify-hook/visual-verdict.js +50 -1
- package/scripts/notify-hook.js +1 -0
- package/scripts/run-provider-advisor.js +179 -0
- package/skills/ask-claude/SKILL.md +61 -0
- package/skills/ask-gemini/SKILL.md +61 -0
- package/skills/autopilot/SKILL.md +32 -2
- package/skills/configure-notifications/SKILL.md +188 -186
- package/skills/deep-interview/SKILL.md +247 -0
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/ralph/SKILL.md +42 -11
- package/skills/ralplan/SKILL.md +17 -0
- package/skills/team/SKILL.md +64 -5
- package/skills/visual-verdict/SKILL.md +76 -0
- package/skills/web-clone/SKILL.md +366 -0
- package/skills/worker/SKILL.md +42 -11
- package/templates/AGENTS.md +9 -0
- package/templates/catalog-manifest.json +39 -18
- package/skills/configure-discord/SKILL.md +0 -256
- package/skills/configure-openclaw/SKILL.md +0 -267
- package/skills/configure-slack/SKILL.md +0 -226
- package/skills/configure-telegram/SKILL.md +0 -232
package/dist/mcp/state-server.js
CHANGED
|
@@ -3,63 +3,34 @@
|
|
|
3
3
|
* Provides state read/write/clear/list tools for workflow modes
|
|
4
4
|
* Storage: .omx/state/{mode}-state.json
|
|
5
5
|
*/
|
|
6
|
-
import { Server } from
|
|
7
|
-
import { StdioServerTransport } from
|
|
8
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from
|
|
9
|
-
import { readFile, writeFile, readdir, mkdir, unlink, rename } from
|
|
10
|
-
import { existsSync
|
|
11
|
-
import {
|
|
12
|
-
import { getAllScopedStatePaths, getReadScopedStateDirs, getReadScopedStatePaths, resolveStateScope, getStateDir, getStatePath, resolveWorkingDirectoryForState, validateSessionId, } from
|
|
13
|
-
import { withModeRuntimeContext } from
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { TEAM_NAME_SAFE_PATTERN, WORKER_NAME_SAFE_PATTERN, TASK_ID_SAFE_PATTERN, TEAM_TASK_STATUSES, TEAM_EVENT_TYPES, TEAM_TASK_APPROVAL_STATUSES, } from '../team/contracts.js';
|
|
19
|
-
import { teamSendMessage as sendDirectMessage, teamBroadcast as broadcastMessage, teamListMailbox as listMailboxMessages, teamMarkMessageDelivered as markMessageDelivered, teamMarkMessageNotified as markMessageNotified, teamCreateTask, teamReadTask, teamListTasks, teamUpdateTask, teamClaimTask, teamTransitionTaskStatus, teamReleaseTaskClaim, teamReadConfig, teamReadManifest, teamReadWorkerStatus, teamReadWorkerHeartbeat, teamUpdateWorkerHeartbeat, teamWriteWorkerInbox, teamWriteWorkerIdentity, teamAppendEvent, teamGetSummary, teamCleanup, teamWriteShutdownRequest, teamReadShutdownAck, teamReadMonitorSnapshot, teamWriteMonitorSnapshot, teamReadTaskApproval, teamWriteTaskApproval, } from '../team/team-ops.js';
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { readFile, writeFile, readdir, mkdir, unlink, rename, } from "fs/promises";
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { getAllScopedStatePaths, getReadScopedStateDirs, getReadScopedStatePaths, resolveStateScope, getStateDir, getStatePath, resolveWorkingDirectoryForState, validateSessionId, } from "./state-paths.js";
|
|
13
|
+
import { withModeRuntimeContext } from "../state/mode-state-context.js";
|
|
14
|
+
import { RALPH_PHASES, validateAndNormalizeRalphState, } from "../ralph/contract.js";
|
|
15
|
+
import { ensureCanonicalRalphArtifacts } from "../ralph/persistence.js";
|
|
16
|
+
import { shouldAutoStartMcpServer } from "./bootstrap.js";
|
|
17
|
+
import { LEGACY_TEAM_MCP_TOOLS, buildLegacyTeamDeprecationHint, } from "../team/api-interop.js";
|
|
20
18
|
const SUPPORTED_MODES = [
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
"autopilot",
|
|
20
|
+
"team",
|
|
21
|
+
"ralph",
|
|
22
|
+
"ultrawork",
|
|
23
|
+
"ultraqa",
|
|
24
|
+
"ralplan",
|
|
23
25
|
];
|
|
24
26
|
const STATE_TOOL_NAMES = new Set([
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
"state_read",
|
|
28
|
+
"state_write",
|
|
29
|
+
"state_clear",
|
|
30
|
+
"state_list_active",
|
|
31
|
+
"state_get_status",
|
|
30
32
|
]);
|
|
31
|
-
const TEAM_COMM_TOOL_NAMES = new Set([
|
|
32
|
-
'team_send_message',
|
|
33
|
-
'team_broadcast',
|
|
34
|
-
'team_mailbox_list',
|
|
35
|
-
'team_mailbox_mark_delivered',
|
|
36
|
-
'team_mailbox_mark_notified',
|
|
37
|
-
'team_create_task',
|
|
38
|
-
'team_read_task',
|
|
39
|
-
'team_list_tasks',
|
|
40
|
-
'team_update_task',
|
|
41
|
-
'team_claim_task',
|
|
42
|
-
'team_transition_task_status',
|
|
43
|
-
'team_release_task_claim',
|
|
44
|
-
'team_read_config',
|
|
45
|
-
'team_read_manifest',
|
|
46
|
-
'team_read_worker_status',
|
|
47
|
-
'team_read_worker_heartbeat',
|
|
48
|
-
'team_update_worker_heartbeat',
|
|
49
|
-
'team_write_worker_inbox',
|
|
50
|
-
'team_write_worker_identity',
|
|
51
|
-
'team_append_event',
|
|
52
|
-
'team_get_summary',
|
|
53
|
-
'team_cleanup',
|
|
54
|
-
'team_write_shutdown_request',
|
|
55
|
-
'team_read_shutdown_ack',
|
|
56
|
-
'team_read_monitor_snapshot',
|
|
57
|
-
'team_write_monitor_snapshot',
|
|
58
|
-
'team_read_task_approval',
|
|
59
|
-
'team_write_task_approval',
|
|
60
|
-
]);
|
|
61
|
-
const TEAM_UPDATE_TASK_MUTABLE_FIELDS = new Set(['subject', 'description', 'blocked_by', 'requires_code_change']);
|
|
62
|
-
const TEAM_UPDATE_TASK_REQUEST_FIELDS = new Set(['team_name', 'task_id', 'workingDirectory', ...TEAM_UPDATE_TASK_MUTABLE_FIELDS]);
|
|
33
|
+
const TEAM_COMM_TOOL_NAMES = new Set([...LEGACY_TEAM_MCP_TOOLS]);
|
|
63
34
|
const stateWriteQueues = new Map();
|
|
64
35
|
async function withStateWriteLock(path, fn) {
|
|
65
36
|
const tail = stateWriteQueues.get(path) ?? Promise.resolve();
|
|
@@ -82,7 +53,7 @@ async function withStateWriteLock(path, fn) {
|
|
|
82
53
|
}
|
|
83
54
|
async function writeAtomicFile(path, data) {
|
|
84
55
|
const tmpPath = `${path}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
|
|
85
|
-
await writeFile(tmpPath, data,
|
|
56
|
+
await writeFile(tmpPath, data, "utf-8");
|
|
86
57
|
try {
|
|
87
58
|
await rename(tmpPath, path);
|
|
88
59
|
}
|
|
@@ -91,585 +62,110 @@ async function writeAtomicFile(path, data) {
|
|
|
91
62
|
throw error;
|
|
92
63
|
}
|
|
93
64
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
function parseValidatedTaskIdArray(value, fieldName) {
|
|
98
|
-
if (!Array.isArray(value)) {
|
|
99
|
-
throw new Error(`${fieldName} must be an array of task IDs (strings)`);
|
|
100
|
-
}
|
|
101
|
-
const taskIds = [];
|
|
102
|
-
for (const item of value) {
|
|
103
|
-
if (typeof item !== 'string') {
|
|
104
|
-
throw new Error(`${fieldName} entries must be strings`);
|
|
105
|
-
}
|
|
106
|
-
const normalized = item.trim();
|
|
107
|
-
if (!TASK_ID_SAFE_PATTERN.test(normalized)) {
|
|
108
|
-
throw new Error(`${fieldName} contains invalid task ID: "${item}"`);
|
|
109
|
-
}
|
|
110
|
-
taskIds.push(normalized);
|
|
111
|
-
}
|
|
112
|
-
return taskIds;
|
|
113
|
-
}
|
|
114
|
-
function teamStateExists(teamName, candidateCwd) {
|
|
115
|
-
if (!TEAM_NAME_SAFE_PATTERN.test(teamName))
|
|
116
|
-
return false;
|
|
117
|
-
const teamRoot = join(candidateCwd, '.omx', 'state', 'team', teamName);
|
|
118
|
-
return (existsSync(join(teamRoot, 'config.json')) ||
|
|
119
|
-
existsSync(join(teamRoot, 'tasks')) ||
|
|
120
|
-
existsSync(teamRoot));
|
|
121
|
-
}
|
|
122
|
-
function parseTeamWorkerEnv(raw) {
|
|
123
|
-
if (typeof raw !== 'string' || raw.trim() === '')
|
|
124
|
-
return null;
|
|
125
|
-
const match = /^([a-z0-9][a-z0-9-]{0,29})\/(worker-\d+)$/.exec(raw.trim());
|
|
126
|
-
if (!match)
|
|
127
|
-
return null;
|
|
128
|
-
return { teamName: match[1], workerName: match[2] };
|
|
129
|
-
}
|
|
130
|
-
function readTeamStateRootFromFile(path) {
|
|
131
|
-
if (!existsSync(path))
|
|
132
|
-
return null;
|
|
133
|
-
try {
|
|
134
|
-
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
135
|
-
return typeof parsed.team_state_root === 'string' && parsed.team_state_root.trim() !== ''
|
|
136
|
-
? parsed.team_state_root.trim()
|
|
137
|
-
: null;
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
function stateRootToWorkingDirectory(stateRoot) {
|
|
144
|
-
const absolute = resolvePath(stateRoot);
|
|
145
|
-
return dirname(dirname(absolute));
|
|
146
|
-
}
|
|
147
|
-
function resolveTeamWorkingDirectoryFromMetadata(teamName, candidateCwd, workerContext) {
|
|
148
|
-
const teamRoot = join(candidateCwd, '.omx', 'state', 'team', teamName);
|
|
149
|
-
if (!existsSync(teamRoot))
|
|
150
|
-
return null;
|
|
151
|
-
if (workerContext?.teamName === teamName) {
|
|
152
|
-
const workerRoot = readTeamStateRootFromFile(join(teamRoot, 'workers', workerContext.workerName, 'identity.json'));
|
|
153
|
-
if (workerRoot)
|
|
154
|
-
return stateRootToWorkingDirectory(workerRoot);
|
|
155
|
-
}
|
|
156
|
-
const fromManifest = readTeamStateRootFromFile(join(teamRoot, 'manifest.v2.json'));
|
|
157
|
-
if (fromManifest)
|
|
158
|
-
return stateRootToWorkingDirectory(fromManifest);
|
|
159
|
-
const fromConfig = readTeamStateRootFromFile(join(teamRoot, 'config.json'));
|
|
160
|
-
if (fromConfig)
|
|
161
|
-
return stateRootToWorkingDirectory(fromConfig);
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
function resolveTeamWorkingDirectory(teamName, preferredCwd) {
|
|
165
|
-
const normalizedTeamName = String(teamName || '').trim();
|
|
166
|
-
if (!normalizedTeamName)
|
|
167
|
-
return preferredCwd;
|
|
168
|
-
const envTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
169
|
-
if (typeof envTeamStateRoot === 'string' && envTeamStateRoot.trim() !== '') {
|
|
170
|
-
return stateRootToWorkingDirectory(envTeamStateRoot.trim());
|
|
171
|
-
}
|
|
172
|
-
const seeds = [];
|
|
173
|
-
for (const seed of [preferredCwd, process.cwd()]) {
|
|
174
|
-
if (typeof seed !== 'string' || seed.trim() === '')
|
|
175
|
-
continue;
|
|
176
|
-
if (!seeds.includes(seed))
|
|
177
|
-
seeds.push(seed);
|
|
178
|
-
}
|
|
179
|
-
const workerContext = parseTeamWorkerEnv(process.env.OMX_TEAM_WORKER);
|
|
180
|
-
for (const seed of seeds) {
|
|
181
|
-
let cursor = seed;
|
|
182
|
-
while (cursor) {
|
|
183
|
-
if (teamStateExists(normalizedTeamName, cursor)) {
|
|
184
|
-
return resolveTeamWorkingDirectoryFromMetadata(normalizedTeamName, cursor, workerContext) ?? cursor;
|
|
185
|
-
}
|
|
186
|
-
const parent = dirname(cursor);
|
|
187
|
-
if (!parent || parent === cursor)
|
|
188
|
-
break;
|
|
189
|
-
cursor = parent;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return preferredCwd;
|
|
193
|
-
}
|
|
194
|
-
const server = new Server({ name: 'omx-state', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
195
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
196
|
-
tools: [
|
|
65
|
+
const server = new Server({ name: "omx-state", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
66
|
+
export function buildStateServerTools() {
|
|
67
|
+
return [
|
|
197
68
|
{
|
|
198
|
-
name:
|
|
199
|
-
description:
|
|
69
|
+
name: "state_read",
|
|
70
|
+
description: "Read state for a specific mode. Returns JSON state data or indicates no state exists.",
|
|
200
71
|
inputSchema: {
|
|
201
|
-
type:
|
|
72
|
+
type: "object",
|
|
202
73
|
properties: {
|
|
203
|
-
mode: {
|
|
204
|
-
|
|
205
|
-
|
|
74
|
+
mode: {
|
|
75
|
+
type: "string",
|
|
76
|
+
enum: [...SUPPORTED_MODES],
|
|
77
|
+
description: "The mode to read state for",
|
|
78
|
+
},
|
|
79
|
+
workingDirectory: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Working directory override",
|
|
82
|
+
},
|
|
83
|
+
session_id: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "Optional session scope ID",
|
|
86
|
+
},
|
|
206
87
|
},
|
|
207
|
-
required: [
|
|
88
|
+
required: ["mode"],
|
|
208
89
|
},
|
|
209
90
|
},
|
|
210
91
|
{
|
|
211
|
-
name:
|
|
212
|
-
description:
|
|
92
|
+
name: "state_write",
|
|
93
|
+
description: "Write/update state for a specific mode. Creates directories if needed.",
|
|
213
94
|
inputSchema: {
|
|
214
|
-
type:
|
|
95
|
+
type: "object",
|
|
215
96
|
properties: {
|
|
216
|
-
mode: { type:
|
|
217
|
-
active: { type:
|
|
218
|
-
iteration: { type:
|
|
219
|
-
max_iterations: { type:
|
|
220
|
-
current_phase: { type:
|
|
221
|
-
task_description: { type:
|
|
222
|
-
started_at: { type:
|
|
223
|
-
completed_at: { type:
|
|
224
|
-
error: { type:
|
|
225
|
-
state: { type:
|
|
226
|
-
workingDirectory: { type:
|
|
227
|
-
session_id: {
|
|
97
|
+
mode: { type: "string", enum: [...SUPPORTED_MODES] },
|
|
98
|
+
active: { type: "boolean" },
|
|
99
|
+
iteration: { type: "number" },
|
|
100
|
+
max_iterations: { type: "number" },
|
|
101
|
+
current_phase: { type: "string" },
|
|
102
|
+
task_description: { type: "string" },
|
|
103
|
+
started_at: { type: "string" },
|
|
104
|
+
completed_at: { type: "string" },
|
|
105
|
+
error: { type: "string" },
|
|
106
|
+
state: { type: "object", description: "Additional custom fields" },
|
|
107
|
+
workingDirectory: { type: "string" },
|
|
108
|
+
session_id: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Optional session scope ID",
|
|
111
|
+
},
|
|
228
112
|
},
|
|
229
|
-
required: [
|
|
113
|
+
required: ["mode"],
|
|
230
114
|
},
|
|
231
115
|
},
|
|
232
116
|
{
|
|
233
|
-
name:
|
|
234
|
-
description:
|
|
117
|
+
name: "state_clear",
|
|
118
|
+
description: "Clear/delete state for a specific mode.",
|
|
235
119
|
inputSchema: {
|
|
236
|
-
type:
|
|
120
|
+
type: "object",
|
|
237
121
|
properties: {
|
|
238
|
-
mode: { type:
|
|
239
|
-
workingDirectory: { type:
|
|
240
|
-
session_id: {
|
|
241
|
-
|
|
122
|
+
mode: { type: "string", enum: [...SUPPORTED_MODES] },
|
|
123
|
+
workingDirectory: { type: "string" },
|
|
124
|
+
session_id: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Optional session scope ID",
|
|
127
|
+
},
|
|
128
|
+
all_sessions: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
description: "Clear matching mode in global and all session scopes",
|
|
131
|
+
},
|
|
242
132
|
},
|
|
243
|
-
required: [
|
|
133
|
+
required: ["mode"],
|
|
244
134
|
},
|
|
245
135
|
},
|
|
246
136
|
{
|
|
247
|
-
name:
|
|
248
|
-
description:
|
|
137
|
+
name: "state_list_active",
|
|
138
|
+
description: "List all currently active modes.",
|
|
249
139
|
inputSchema: {
|
|
250
|
-
type:
|
|
140
|
+
type: "object",
|
|
251
141
|
properties: {
|
|
252
|
-
workingDirectory: { type:
|
|
253
|
-
session_id: {
|
|
142
|
+
workingDirectory: { type: "string" },
|
|
143
|
+
session_id: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Optional session scope ID",
|
|
146
|
+
},
|
|
254
147
|
},
|
|
255
148
|
},
|
|
256
149
|
},
|
|
257
150
|
{
|
|
258
|
-
name:
|
|
259
|
-
description:
|
|
151
|
+
name: "state_get_status",
|
|
152
|
+
description: "Get detailed status for a specific mode or all modes.",
|
|
260
153
|
inputSchema: {
|
|
261
|
-
type:
|
|
154
|
+
type: "object",
|
|
262
155
|
properties: {
|
|
263
|
-
mode: { type:
|
|
264
|
-
workingDirectory: { type:
|
|
265
|
-
session_id: {
|
|
156
|
+
mode: { type: "string", enum: [...SUPPORTED_MODES] },
|
|
157
|
+
workingDirectory: { type: "string" },
|
|
158
|
+
session_id: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Optional session scope ID",
|
|
161
|
+
},
|
|
266
162
|
},
|
|
267
163
|
},
|
|
268
164
|
},
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
type: 'object',
|
|
274
|
-
properties: {
|
|
275
|
-
team_name: { type: 'string', description: 'Sanitized team name (e.g., my-team)' },
|
|
276
|
-
from_worker: { type: 'string', description: 'Sender worker id (e.g., worker-1)' },
|
|
277
|
-
to_worker: { type: 'string', description: 'Recipient worker id (e.g., worker-2 or leader-fixed)' },
|
|
278
|
-
body: { type: 'string', description: 'Message content' },
|
|
279
|
-
workingDirectory: { type: 'string' },
|
|
280
|
-
},
|
|
281
|
-
required: ['team_name', 'from_worker', 'to_worker', 'body'],
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: 'team_broadcast',
|
|
286
|
-
description: 'Broadcast a message from one worker to all other workers in the team.',
|
|
287
|
-
inputSchema: {
|
|
288
|
-
type: 'object',
|
|
289
|
-
properties: {
|
|
290
|
-
team_name: { type: 'string', description: 'Sanitized team name (e.g., my-team)' },
|
|
291
|
-
from_worker: { type: 'string', description: 'Sender worker id (e.g., worker-1)' },
|
|
292
|
-
body: { type: 'string', description: 'Message content' },
|
|
293
|
-
workingDirectory: { type: 'string' },
|
|
294
|
-
},
|
|
295
|
-
required: ['team_name', 'from_worker', 'body'],
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
name: 'team_mailbox_list',
|
|
300
|
-
description: 'List mailbox messages for a specific worker (including leader-fixed).',
|
|
301
|
-
inputSchema: {
|
|
302
|
-
type: 'object',
|
|
303
|
-
properties: {
|
|
304
|
-
team_name: { type: 'string', description: 'Sanitized team name (e.g., my-team)' },
|
|
305
|
-
worker: { type: 'string', description: 'Mailbox owner worker id' },
|
|
306
|
-
include_delivered: { type: 'boolean', description: 'Include delivered messages (default: true)' },
|
|
307
|
-
workingDirectory: { type: 'string' },
|
|
308
|
-
},
|
|
309
|
-
required: ['team_name', 'worker'],
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: 'team_mailbox_mark_delivered',
|
|
314
|
-
description: 'Mark a mailbox message as delivered for a worker.',
|
|
315
|
-
inputSchema: {
|
|
316
|
-
type: 'object',
|
|
317
|
-
properties: {
|
|
318
|
-
team_name: { type: 'string', description: 'Sanitized team name (e.g., my-team)' },
|
|
319
|
-
worker: { type: 'string', description: 'Mailbox owner worker id' },
|
|
320
|
-
message_id: { type: 'string', description: 'Message ID to mark delivered' },
|
|
321
|
-
workingDirectory: { type: 'string' },
|
|
322
|
-
},
|
|
323
|
-
required: ['team_name', 'worker', 'message_id'],
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
name: 'team_mailbox_mark_notified',
|
|
328
|
-
description: 'Mark a mailbox message as notified (tmux trigger sent) for a worker.',
|
|
329
|
-
inputSchema: {
|
|
330
|
-
type: 'object',
|
|
331
|
-
properties: {
|
|
332
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
333
|
-
worker: { type: 'string', description: 'Mailbox owner worker id' },
|
|
334
|
-
message_id: { type: 'string', description: 'Message ID to mark notified' },
|
|
335
|
-
workingDirectory: { type: 'string' },
|
|
336
|
-
},
|
|
337
|
-
required: ['team_name', 'worker', 'message_id'],
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
name: 'team_create_task',
|
|
342
|
-
description: 'Create a new task in the team task list. Returns the created task with auto-incremented ID.',
|
|
343
|
-
inputSchema: {
|
|
344
|
-
type: 'object',
|
|
345
|
-
properties: {
|
|
346
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
347
|
-
subject: { type: 'string', description: 'Task subject/title' },
|
|
348
|
-
description: { type: 'string', description: 'Task description' },
|
|
349
|
-
owner: { type: 'string', description: 'Worker name to assign (optional)' },
|
|
350
|
-
blocked_by: { type: 'array', items: { type: 'string' }, description: 'Task IDs this task depends on' },
|
|
351
|
-
requires_code_change: { type: 'boolean', description: 'Whether the task involves code changes' },
|
|
352
|
-
workingDirectory: { type: 'string' },
|
|
353
|
-
},
|
|
354
|
-
required: ['team_name', 'subject', 'description'],
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: 'team_read_task',
|
|
359
|
-
description: 'Read a single task by ID.',
|
|
360
|
-
inputSchema: {
|
|
361
|
-
type: 'object',
|
|
362
|
-
properties: {
|
|
363
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
364
|
-
task_id: { type: 'string', description: 'Task ID to read' },
|
|
365
|
-
workingDirectory: { type: 'string' },
|
|
366
|
-
},
|
|
367
|
-
required: ['team_name', 'task_id'],
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
name: 'team_list_tasks',
|
|
372
|
-
description: 'List all tasks in a team, sorted by numeric ID.',
|
|
373
|
-
inputSchema: {
|
|
374
|
-
type: 'object',
|
|
375
|
-
properties: {
|
|
376
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
377
|
-
workingDirectory: { type: 'string' },
|
|
378
|
-
},
|
|
379
|
-
required: ['team_name'],
|
|
380
|
-
},
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
name: 'team_update_task',
|
|
384
|
-
description: 'Update non-lifecycle task metadata (subject, description, blocked_by, requires_code_change). Status/owner/result/error are lifecycle fields that must be changed via team_claim_task + team_transition_task_status.',
|
|
385
|
-
inputSchema: {
|
|
386
|
-
type: 'object',
|
|
387
|
-
properties: {
|
|
388
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
389
|
-
task_id: { type: 'string', description: 'Task ID to update' },
|
|
390
|
-
subject: { type: 'string', description: 'Task subject/title' },
|
|
391
|
-
description: { type: 'string', description: 'Task description' },
|
|
392
|
-
blocked_by: { type: 'array', items: { type: 'string' }, description: 'Task IDs this task depends on' },
|
|
393
|
-
requires_code_change: { type: 'boolean', description: 'Whether this task requires a code change' },
|
|
394
|
-
workingDirectory: { type: 'string' },
|
|
395
|
-
},
|
|
396
|
-
required: ['team_name', 'task_id'],
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
name: 'team_claim_task',
|
|
401
|
-
description: 'Atomically claim a task for a worker. Checks dependencies and version.',
|
|
402
|
-
inputSchema: {
|
|
403
|
-
type: 'object',
|
|
404
|
-
properties: {
|
|
405
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
406
|
-
task_id: { type: 'string', description: 'Task ID to claim' },
|
|
407
|
-
worker: { type: 'string', description: 'Worker name claiming the task' },
|
|
408
|
-
expected_version: { type: 'number', description: 'Expected task version for optimistic locking. Omitting does not allow claiming an already in-progress task.' },
|
|
409
|
-
workingDirectory: { type: 'string' },
|
|
410
|
-
},
|
|
411
|
-
required: ['team_name', 'task_id', 'worker'],
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
name: 'team_transition_task_status',
|
|
416
|
-
description: 'Atomically transition task status with claim token validation.',
|
|
417
|
-
inputSchema: {
|
|
418
|
-
type: 'object',
|
|
419
|
-
properties: {
|
|
420
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
421
|
-
task_id: { type: 'string', description: 'Task ID to transition' },
|
|
422
|
-
from: { type: 'string', enum: [...TEAM_TASK_STATUSES] },
|
|
423
|
-
to: { type: 'string', enum: [...TEAM_TASK_STATUSES] },
|
|
424
|
-
claim_token: { type: 'string', description: 'Claim token from team_claim_task' },
|
|
425
|
-
workingDirectory: { type: 'string' },
|
|
426
|
-
},
|
|
427
|
-
required: ['team_name', 'task_id', 'from', 'to', 'claim_token'],
|
|
428
|
-
},
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
name: 'team_release_task_claim',
|
|
432
|
-
description: 'Release a task claim, returning task to pending status.',
|
|
433
|
-
inputSchema: {
|
|
434
|
-
type: 'object',
|
|
435
|
-
properties: {
|
|
436
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
437
|
-
task_id: { type: 'string', description: 'Task ID' },
|
|
438
|
-
claim_token: { type: 'string', description: 'Claim token from the claim operation' },
|
|
439
|
-
worker: { type: 'string', description: 'Worker name that holds the claim' },
|
|
440
|
-
workingDirectory: { type: 'string' },
|
|
441
|
-
},
|
|
442
|
-
required: ['team_name', 'task_id', 'claim_token', 'worker'],
|
|
443
|
-
},
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
name: 'team_read_config',
|
|
447
|
-
description: 'Read team configuration (workers, tmux session, task counter).',
|
|
448
|
-
inputSchema: {
|
|
449
|
-
type: 'object',
|
|
450
|
-
properties: {
|
|
451
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
452
|
-
workingDirectory: { type: 'string' },
|
|
453
|
-
},
|
|
454
|
-
required: ['team_name'],
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
name: 'team_read_manifest',
|
|
459
|
-
description: 'Read team manifest v2 (leader, policy, permissions snapshot).',
|
|
460
|
-
inputSchema: {
|
|
461
|
-
type: 'object',
|
|
462
|
-
properties: {
|
|
463
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
464
|
-
workingDirectory: { type: 'string' },
|
|
465
|
-
},
|
|
466
|
-
required: ['team_name'],
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
name: 'team_read_worker_status',
|
|
471
|
-
description: 'Read current worker status (state, current_task_id, reason).',
|
|
472
|
-
inputSchema: {
|
|
473
|
-
type: 'object',
|
|
474
|
-
properties: {
|
|
475
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
476
|
-
worker: { type: 'string', description: 'Worker name (e.g., worker-1)' },
|
|
477
|
-
workingDirectory: { type: 'string' },
|
|
478
|
-
},
|
|
479
|
-
required: ['team_name', 'worker'],
|
|
480
|
-
},
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
name: 'team_read_worker_heartbeat',
|
|
484
|
-
description: 'Read worker heartbeat (pid, turn count, alive flag).',
|
|
485
|
-
inputSchema: {
|
|
486
|
-
type: 'object',
|
|
487
|
-
properties: {
|
|
488
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
489
|
-
worker: { type: 'string', description: 'Worker name' },
|
|
490
|
-
workingDirectory: { type: 'string' },
|
|
491
|
-
},
|
|
492
|
-
required: ['team_name', 'worker'],
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
{
|
|
496
|
-
name: 'team_update_worker_heartbeat',
|
|
497
|
-
description: 'Write/update a worker heartbeat.',
|
|
498
|
-
inputSchema: {
|
|
499
|
-
type: 'object',
|
|
500
|
-
properties: {
|
|
501
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
502
|
-
worker: { type: 'string', description: 'Worker name' },
|
|
503
|
-
pid: { type: 'number', description: 'Worker process ID' },
|
|
504
|
-
turn_count: { type: 'number', description: 'Cumulative turn count' },
|
|
505
|
-
alive: { type: 'boolean', description: 'Whether the worker is alive' },
|
|
506
|
-
workingDirectory: { type: 'string' },
|
|
507
|
-
},
|
|
508
|
-
required: ['team_name', 'worker', 'pid', 'turn_count', 'alive'],
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
{
|
|
512
|
-
name: 'team_write_worker_inbox',
|
|
513
|
-
description: 'Write a prompt/instruction to a worker inbox file.',
|
|
514
|
-
inputSchema: {
|
|
515
|
-
type: 'object',
|
|
516
|
-
properties: {
|
|
517
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
518
|
-
worker: { type: 'string', description: 'Worker name' },
|
|
519
|
-
content: { type: 'string', description: 'Inbox content (markdown)' },
|
|
520
|
-
workingDirectory: { type: 'string' },
|
|
521
|
-
},
|
|
522
|
-
required: ['team_name', 'worker', 'content'],
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
name: 'team_write_worker_identity',
|
|
527
|
-
description: 'Write worker identity file (name, index, role, assigned tasks).',
|
|
528
|
-
inputSchema: {
|
|
529
|
-
type: 'object',
|
|
530
|
-
properties: {
|
|
531
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
532
|
-
worker: { type: 'string', description: 'Worker name' },
|
|
533
|
-
index: { type: 'number', description: 'Worker index (1-based)' },
|
|
534
|
-
role: { type: 'string', description: 'Agent role/type' },
|
|
535
|
-
assigned_tasks: { type: 'array', items: { type: 'string' }, description: 'Assigned task IDs' },
|
|
536
|
-
pid: { type: 'number', description: 'Worker process ID (optional)' },
|
|
537
|
-
pane_id: { type: 'string', description: 'Tmux pane ID (optional)' },
|
|
538
|
-
working_dir: { type: 'string', description: 'Worker working directory (optional)' },
|
|
539
|
-
worktree_path: { type: 'string', description: 'Worker git worktree path (optional)' },
|
|
540
|
-
worktree_branch: { type: 'string', description: 'Worker git worktree branch (optional)' },
|
|
541
|
-
worktree_detached: { type: 'boolean', description: 'Whether worker worktree is detached (optional)' },
|
|
542
|
-
team_state_root: { type: 'string', description: 'Canonical team state root path (optional)' },
|
|
543
|
-
workingDirectory: { type: 'string' },
|
|
544
|
-
},
|
|
545
|
-
required: ['team_name', 'worker', 'index', 'role'],
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
{
|
|
549
|
-
name: 'team_append_event',
|
|
550
|
-
description: 'Append an event to the team event log (ndjson).',
|
|
551
|
-
inputSchema: {
|
|
552
|
-
type: 'object',
|
|
553
|
-
properties: {
|
|
554
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
555
|
-
type: { type: 'string', enum: [...TEAM_EVENT_TYPES] },
|
|
556
|
-
worker: { type: 'string', description: 'Worker name associated with the event' },
|
|
557
|
-
task_id: { type: 'string', description: 'Related task ID (optional)' },
|
|
558
|
-
message_id: { type: 'string', description: 'Related message ID (optional)' },
|
|
559
|
-
reason: { type: 'string', description: 'Event reason (optional)' },
|
|
560
|
-
workingDirectory: { type: 'string' },
|
|
561
|
-
},
|
|
562
|
-
required: ['team_name', 'type', 'worker'],
|
|
563
|
-
},
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
name: 'team_get_summary',
|
|
567
|
-
description: 'Get team summary with task counts, worker status, and non-reporting detection.',
|
|
568
|
-
inputSchema: {
|
|
569
|
-
type: 'object',
|
|
570
|
-
properties: {
|
|
571
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
572
|
-
workingDirectory: { type: 'string' },
|
|
573
|
-
},
|
|
574
|
-
required: ['team_name'],
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
name: 'team_cleanup',
|
|
579
|
-
description: 'Delete all team state files on disk (config, tasks, workers, events). Does NOT kill tmux panes -- use omx_run_team_cleanup for that.',
|
|
580
|
-
inputSchema: {
|
|
581
|
-
type: 'object',
|
|
582
|
-
properties: {
|
|
583
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
584
|
-
workingDirectory: { type: 'string' },
|
|
585
|
-
},
|
|
586
|
-
required: ['team_name'],
|
|
587
|
-
},
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
name: 'team_write_shutdown_request',
|
|
591
|
-
description: 'Write a shutdown request for a worker.',
|
|
592
|
-
inputSchema: {
|
|
593
|
-
type: 'object',
|
|
594
|
-
properties: {
|
|
595
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
596
|
-
worker: { type: 'string', description: 'Worker name to shut down' },
|
|
597
|
-
requested_by: { type: 'string', description: 'Requester identity (e.g., leader-fixed)' },
|
|
598
|
-
workingDirectory: { type: 'string' },
|
|
599
|
-
},
|
|
600
|
-
required: ['team_name', 'worker', 'requested_by'],
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
name: 'team_read_shutdown_ack',
|
|
605
|
-
description: 'Read a worker shutdown acknowledgment.',
|
|
606
|
-
inputSchema: {
|
|
607
|
-
type: 'object',
|
|
608
|
-
properties: {
|
|
609
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
610
|
-
worker: { type: 'string', description: 'Worker name' },
|
|
611
|
-
min_updated_at: { type: 'string', description: 'ISO timestamp - ignore acks older than this' },
|
|
612
|
-
workingDirectory: { type: 'string' },
|
|
613
|
-
},
|
|
614
|
-
required: ['team_name', 'worker'],
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
name: 'team_read_monitor_snapshot',
|
|
619
|
-
description: 'Read the monitor snapshot (task/worker state from last poll).',
|
|
620
|
-
inputSchema: {
|
|
621
|
-
type: 'object',
|
|
622
|
-
properties: {
|
|
623
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
624
|
-
workingDirectory: { type: 'string' },
|
|
625
|
-
},
|
|
626
|
-
required: ['team_name'],
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
name: 'team_write_monitor_snapshot',
|
|
631
|
-
description: 'Write the monitor snapshot for change detection across poll cycles.',
|
|
632
|
-
inputSchema: {
|
|
633
|
-
type: 'object',
|
|
634
|
-
properties: {
|
|
635
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
636
|
-
snapshot: { type: 'object', description: 'Monitor snapshot data' },
|
|
637
|
-
workingDirectory: { type: 'string' },
|
|
638
|
-
},
|
|
639
|
-
required: ['team_name', 'snapshot'],
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
name: 'team_read_task_approval',
|
|
644
|
-
description: 'Read task approval record (for plan-approval-required policy).',
|
|
645
|
-
inputSchema: {
|
|
646
|
-
type: 'object',
|
|
647
|
-
properties: {
|
|
648
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
649
|
-
task_id: { type: 'string', description: 'Task ID' },
|
|
650
|
-
workingDirectory: { type: 'string' },
|
|
651
|
-
},
|
|
652
|
-
required: ['team_name', 'task_id'],
|
|
653
|
-
},
|
|
654
|
-
},
|
|
655
|
-
{
|
|
656
|
-
name: 'team_write_task_approval',
|
|
657
|
-
description: 'Write a task approval decision.',
|
|
658
|
-
inputSchema: {
|
|
659
|
-
type: 'object',
|
|
660
|
-
properties: {
|
|
661
|
-
team_name: { type: 'string', description: 'Sanitized team name' },
|
|
662
|
-
task_id: { type: 'string', description: 'Task ID' },
|
|
663
|
-
required: { type: 'boolean', description: 'Whether approval was required' },
|
|
664
|
-
status: { type: 'string', enum: [...TEAM_TASK_APPROVAL_STATUSES] },
|
|
665
|
-
reviewer: { type: 'string', description: 'Reviewer identity' },
|
|
666
|
-
decision_reason: { type: 'string', description: 'Reason for the decision' },
|
|
667
|
-
workingDirectory: { type: 'string' },
|
|
668
|
-
},
|
|
669
|
-
required: ['team_name', 'task_id', 'status', 'reviewer', 'decision_reason'],
|
|
670
|
-
},
|
|
671
|
-
},
|
|
672
|
-
],
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
168
|
+
tools: buildStateServerTools(),
|
|
673
169
|
}));
|
|
674
170
|
export async function handleStateToolCall(request) {
|
|
675
171
|
const { name, arguments: args } = request.params;
|
|
@@ -680,7 +176,12 @@ export async function handleStateToolCall(request) {
|
|
|
680
176
|
}
|
|
681
177
|
catch (error) {
|
|
682
178
|
return {
|
|
683
|
-
content: [
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: JSON.stringify({ error: error.message }),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
684
185
|
isError: true,
|
|
685
186
|
};
|
|
686
187
|
}
|
|
@@ -691,7 +192,12 @@ export async function handleStateToolCall(request) {
|
|
|
691
192
|
}
|
|
692
193
|
catch (error) {
|
|
693
194
|
return {
|
|
694
|
-
content: [
|
|
195
|
+
content: [
|
|
196
|
+
{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: JSON.stringify({ error: error.message }),
|
|
199
|
+
},
|
|
200
|
+
],
|
|
695
201
|
isError: true,
|
|
696
202
|
};
|
|
697
203
|
}
|
|
@@ -705,55 +211,54 @@ export async function handleStateToolCall(request) {
|
|
|
705
211
|
if (effectiveSessionId) {
|
|
706
212
|
await mkdir(getStateDir(cwd, effectiveSessionId), { recursive: true });
|
|
707
213
|
}
|
|
214
|
+
const { ensureTmuxHookInitialized } = await import("../cli/tmux-hook.js");
|
|
708
215
|
await ensureTmuxHookInitialized(cwd);
|
|
709
216
|
}
|
|
710
217
|
if (TEAM_COMM_TOOL_NAMES.has(name)) {
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
const rawTaskId = String(args?.task_id || '').trim();
|
|
728
|
-
if (rawTaskId && !TASK_ID_SAFE_PATTERN.test(rawTaskId)) {
|
|
729
|
-
return {
|
|
730
|
-
content: [{ type: 'text', text: JSON.stringify({ error: `Invalid task_id: "${rawTaskId}". Must be a positive integer (digits only, max 20 digits).` }) }],
|
|
731
|
-
isError: true,
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
if (teamName) {
|
|
735
|
-
cwd = resolveTeamWorkingDirectory(teamName, cwd);
|
|
736
|
-
}
|
|
737
|
-
await mkdir(getStateDir(cwd, explicitSessionId), { recursive: true });
|
|
218
|
+
const hint = buildLegacyTeamDeprecationHint(name, args ?? {});
|
|
219
|
+
return {
|
|
220
|
+
content: [
|
|
221
|
+
{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: JSON.stringify({
|
|
224
|
+
error: `MCP tool "${name}" is hard-deprecated. Team mutations now require CLI interop.`,
|
|
225
|
+
code: "deprecated_cli_only",
|
|
226
|
+
hint,
|
|
227
|
+
}),
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
isError: true,
|
|
231
|
+
};
|
|
738
232
|
}
|
|
739
233
|
switch (name) {
|
|
740
|
-
case
|
|
234
|
+
case "state_read": {
|
|
741
235
|
const mode = args.mode;
|
|
742
236
|
if (!SUPPORTED_MODES.includes(mode)) {
|
|
743
237
|
return {
|
|
744
|
-
content: [
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: "text",
|
|
241
|
+
text: JSON.stringify({
|
|
242
|
+
error: `mode must be one of: ${SUPPORTED_MODES.join(", ")}`,
|
|
243
|
+
}),
|
|
244
|
+
},
|
|
245
|
+
],
|
|
745
246
|
isError: true,
|
|
746
247
|
};
|
|
747
248
|
}
|
|
748
249
|
const paths = await getReadScopedStatePaths(mode, cwd, explicitSessionId);
|
|
749
250
|
const path = paths.find((candidate) => existsSync(candidate));
|
|
750
251
|
if (!path) {
|
|
751
|
-
return {
|
|
252
|
+
return {
|
|
253
|
+
content: [
|
|
254
|
+
{ type: "text", text: JSON.stringify({ exists: false, mode }) },
|
|
255
|
+
],
|
|
256
|
+
};
|
|
752
257
|
}
|
|
753
|
-
const data = await readFile(path,
|
|
754
|
-
return { content: [{ type:
|
|
258
|
+
const data = await readFile(path, "utf-8");
|
|
259
|
+
return { content: [{ type: "text", text: data }] };
|
|
755
260
|
}
|
|
756
|
-
case
|
|
261
|
+
case "state_write": {
|
|
757
262
|
const mode = args.mode;
|
|
758
263
|
const path = getStatePath(mode, cwd, effectiveSessionId);
|
|
759
264
|
const { mode: _m, workingDirectory: _w, session_id: _sid, state: customState, ...fields } = args;
|
|
@@ -762,21 +267,29 @@ export async function handleStateToolCall(request) {
|
|
|
762
267
|
let existing = {};
|
|
763
268
|
if (existsSync(path)) {
|
|
764
269
|
try {
|
|
765
|
-
existing = JSON.parse(await readFile(path,
|
|
270
|
+
existing = JSON.parse(await readFile(path, "utf-8"));
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
process.stderr.write("[state-server] Failed to parse state file: " + e + "\n");
|
|
766
274
|
}
|
|
767
|
-
catch { /* start fresh */ }
|
|
768
275
|
}
|
|
769
|
-
const mergedRaw = {
|
|
770
|
-
|
|
276
|
+
const mergedRaw = {
|
|
277
|
+
...existing,
|
|
278
|
+
...fields,
|
|
279
|
+
...(customState || {}),
|
|
280
|
+
};
|
|
281
|
+
if (mode === "ralph") {
|
|
771
282
|
const originalPhase = mergedRaw.current_phase;
|
|
772
283
|
const validation = validateAndNormalizeRalphState(mergedRaw);
|
|
773
284
|
if (!validation.ok || !validation.state) {
|
|
774
|
-
validationError =
|
|
285
|
+
validationError =
|
|
286
|
+
validation.error ||
|
|
287
|
+
`ralph.current_phase must be one of: ${RALPH_PHASES.join(", ")}`;
|
|
775
288
|
return;
|
|
776
289
|
}
|
|
777
|
-
if (typeof originalPhase ===
|
|
778
|
-
|
|
779
|
-
|
|
290
|
+
if (typeof originalPhase === "string" &&
|
|
291
|
+
typeof validation.state.current_phase === "string" &&
|
|
292
|
+
validation.state.current_phase !== originalPhase) {
|
|
780
293
|
validation.state.ralph_phase_normalized_from = originalPhase;
|
|
781
294
|
}
|
|
782
295
|
Object.assign(mergedRaw, validation.state);
|
|
@@ -787,16 +300,25 @@ export async function handleStateToolCall(request) {
|
|
|
787
300
|
});
|
|
788
301
|
if (validationError) {
|
|
789
302
|
return {
|
|
790
|
-
content: [
|
|
791
|
-
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: "text",
|
|
792
306
|
text: JSON.stringify({ error: validationError }),
|
|
793
|
-
}
|
|
307
|
+
},
|
|
308
|
+
],
|
|
794
309
|
isError: true,
|
|
795
310
|
};
|
|
796
311
|
}
|
|
797
|
-
return {
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: "text",
|
|
316
|
+
text: JSON.stringify({ success: true, mode, path }),
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
};
|
|
798
320
|
}
|
|
799
|
-
case
|
|
321
|
+
case "state_clear": {
|
|
800
322
|
const mode = args.mode;
|
|
801
323
|
const allSessions = args.all_sessions === true;
|
|
802
324
|
if (!allSessions) {
|
|
@@ -804,7 +326,14 @@ export async function handleStateToolCall(request) {
|
|
|
804
326
|
if (existsSync(path)) {
|
|
805
327
|
await unlink(path);
|
|
806
328
|
}
|
|
807
|
-
return {
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: JSON.stringify({ cleared: true, mode, path }),
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
};
|
|
808
337
|
}
|
|
809
338
|
const removedPaths = [];
|
|
810
339
|
const paths = await getAllScopedStatePaths(mode, cwd);
|
|
@@ -815,20 +344,22 @@ export async function handleStateToolCall(request) {
|
|
|
815
344
|
removedPaths.push(path);
|
|
816
345
|
}
|
|
817
346
|
return {
|
|
818
|
-
content: [
|
|
819
|
-
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
820
350
|
text: JSON.stringify({
|
|
821
351
|
cleared: true,
|
|
822
352
|
mode,
|
|
823
353
|
all_sessions: true,
|
|
824
354
|
removed: removedPaths.length,
|
|
825
355
|
paths: removedPaths,
|
|
826
|
-
warning:
|
|
356
|
+
warning: "all_sessions clears global and session-scoped state files",
|
|
827
357
|
}),
|
|
828
|
-
}
|
|
358
|
+
},
|
|
359
|
+
],
|
|
829
360
|
};
|
|
830
361
|
}
|
|
831
|
-
case
|
|
362
|
+
case "state_list_active": {
|
|
832
363
|
const stateDirs = await getReadScopedStateDirs(cwd, explicitSessionId);
|
|
833
364
|
const active = [];
|
|
834
365
|
const seenModes = new Set();
|
|
@@ -837,24 +368,30 @@ export async function handleStateToolCall(request) {
|
|
|
837
368
|
continue;
|
|
838
369
|
const files = await readdir(stateDir);
|
|
839
370
|
for (const f of files) {
|
|
840
|
-
if (!f.endsWith(
|
|
371
|
+
if (!f.endsWith("-state.json"))
|
|
841
372
|
continue;
|
|
842
|
-
const mode = f.replace(
|
|
373
|
+
const mode = f.replace("-state.json", "");
|
|
843
374
|
if (seenModes.has(mode))
|
|
844
375
|
continue;
|
|
845
376
|
seenModes.add(mode);
|
|
846
377
|
try {
|
|
847
|
-
const data = JSON.parse(await readFile(join(stateDir, f),
|
|
378
|
+
const data = JSON.parse(await readFile(join(stateDir, f), "utf-8"));
|
|
848
379
|
if (data.active) {
|
|
849
380
|
active.push(mode);
|
|
850
381
|
}
|
|
851
382
|
}
|
|
852
|
-
catch {
|
|
383
|
+
catch (e) {
|
|
384
|
+
process.stderr.write("[state-server] Failed to parse state file: " + e + "\n");
|
|
385
|
+
}
|
|
853
386
|
}
|
|
854
387
|
}
|
|
855
|
-
return {
|
|
388
|
+
return {
|
|
389
|
+
content: [
|
|
390
|
+
{ type: "text", text: JSON.stringify({ active_modes: active }) },
|
|
391
|
+
],
|
|
392
|
+
};
|
|
856
393
|
}
|
|
857
|
-
case
|
|
394
|
+
case "state_get_status": {
|
|
858
395
|
const mode = args?.mode;
|
|
859
396
|
const stateDirs = await getReadScopedStateDirs(cwd, explicitSessionId);
|
|
860
397
|
const statuses = {};
|
|
@@ -864,481 +401,54 @@ export async function handleStateToolCall(request) {
|
|
|
864
401
|
continue;
|
|
865
402
|
const files = await readdir(stateDir);
|
|
866
403
|
for (const f of files) {
|
|
867
|
-
if (!f.endsWith(
|
|
404
|
+
if (!f.endsWith("-state.json"))
|
|
868
405
|
continue;
|
|
869
|
-
const m = f.replace(
|
|
406
|
+
const m = f.replace("-state.json", "");
|
|
870
407
|
if (mode && m !== mode)
|
|
871
408
|
continue;
|
|
872
409
|
if (seenModes.has(m))
|
|
873
410
|
continue;
|
|
874
411
|
seenModes.add(m);
|
|
875
412
|
try {
|
|
876
|
-
const data = JSON.parse(await readFile(join(stateDir, f),
|
|
877
|
-
statuses[m] = {
|
|
413
|
+
const data = JSON.parse(await readFile(join(stateDir, f), "utf-8"));
|
|
414
|
+
statuses[m] = {
|
|
415
|
+
active: data.active,
|
|
416
|
+
phase: data.current_phase,
|
|
417
|
+
path: join(stateDir, f),
|
|
418
|
+
data,
|
|
419
|
+
};
|
|
878
420
|
}
|
|
879
421
|
catch {
|
|
880
|
-
statuses[m] = { error:
|
|
422
|
+
statuses[m] = { error: "malformed state file" };
|
|
881
423
|
}
|
|
882
424
|
}
|
|
883
425
|
}
|
|
884
|
-
return { content: [{ type: 'text', text: JSON.stringify({ statuses }) }] };
|
|
885
|
-
}
|
|
886
|
-
case 'team_send_message': {
|
|
887
|
-
const teamName = String(args.team_name || '').trim();
|
|
888
|
-
const fromWorker = String(args.from_worker || '').trim();
|
|
889
|
-
const toWorker = String(args.to_worker || '').trim();
|
|
890
|
-
const body = String(args.body || '').trim();
|
|
891
|
-
if (!fromWorker) {
|
|
892
|
-
return {
|
|
893
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
894
|
-
error: 'from_worker is required. You must identify yourself. Check your worker name in your inbox file or AGENTS.md overlay.',
|
|
895
|
-
hint: 'Your worker name was set when you were spawned (e.g., "worker-1", "worker-2", or "leader-fixed").'
|
|
896
|
-
}) }],
|
|
897
|
-
isError: true,
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
if (!teamName || !toWorker || !body) {
|
|
901
|
-
return {
|
|
902
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, from_worker, to_worker, body are required' }) }],
|
|
903
|
-
isError: true,
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
const message = await sendDirectMessage(teamName, fromWorker, toWorker, body, cwd);
|
|
907
|
-
return {
|
|
908
|
-
content: [{ type: 'text', text: JSON.stringify({ ok: true, message }) }],
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
case 'team_broadcast': {
|
|
912
|
-
const teamName = String(args.team_name || '').trim();
|
|
913
|
-
const fromWorker = String(args.from_worker || '').trim();
|
|
914
|
-
const body = String(args.body || '').trim();
|
|
915
|
-
if (!teamName || !fromWorker || !body) {
|
|
916
|
-
return {
|
|
917
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, from_worker, body are required' }) }],
|
|
918
|
-
isError: true,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
const messages = await broadcastMessage(teamName, fromWorker, body, cwd);
|
|
922
426
|
return {
|
|
923
|
-
content: [{ type:
|
|
427
|
+
content: [{ type: "text", text: JSON.stringify({ statuses }) }],
|
|
924
428
|
};
|
|
925
429
|
}
|
|
926
|
-
|
|
927
|
-
const teamName = String(args.team_name || '').trim();
|
|
928
|
-
const worker = String(args.worker || '').trim();
|
|
929
|
-
const includeDelivered = args.include_delivered !== false;
|
|
930
|
-
if (!teamName || !worker) {
|
|
931
|
-
return {
|
|
932
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and worker are required' }) }],
|
|
933
|
-
isError: true,
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
const all = await listMailboxMessages(teamName, worker, cwd);
|
|
937
|
-
const messages = includeDelivered ? all : all.filter((m) => !m.delivered_at);
|
|
938
|
-
return {
|
|
939
|
-
content: [{ type: 'text', text: JSON.stringify({ ok: true, worker, count: messages.length, messages }) }],
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
case 'team_mailbox_mark_delivered': {
|
|
943
|
-
const teamName = String(args.team_name || '').trim();
|
|
944
|
-
const worker = String(args.worker || '').trim();
|
|
945
|
-
const messageId = String(args.message_id || '').trim();
|
|
946
|
-
if (!teamName || !worker || !messageId) {
|
|
947
|
-
return {
|
|
948
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, worker, message_id are required' }) }],
|
|
949
|
-
isError: true,
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
const updated = await markMessageDelivered(teamName, worker, messageId, cwd);
|
|
430
|
+
default:
|
|
953
431
|
return {
|
|
954
|
-
content: [{ type:
|
|
432
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
433
|
+
isError: true,
|
|
955
434
|
};
|
|
956
|
-
}
|
|
957
|
-
case 'team_mailbox_mark_notified': {
|
|
958
|
-
const teamName = String(args.team_name || '').trim();
|
|
959
|
-
const worker = String(args.worker || '').trim();
|
|
960
|
-
const messageId = String(args.message_id || '').trim();
|
|
961
|
-
if (!teamName || !worker || !messageId) {
|
|
962
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, worker, message_id are required' }) }], isError: true };
|
|
963
|
-
}
|
|
964
|
-
const notified = await markMessageNotified(teamName, worker, messageId, cwd);
|
|
965
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, notified, worker, message_id: messageId }) }] };
|
|
966
|
-
}
|
|
967
|
-
case 'team_create_task': {
|
|
968
|
-
const teamName = String(args.team_name || '').trim();
|
|
969
|
-
const subject = String(args.subject || '').trim();
|
|
970
|
-
const description = String(args.description || '').trim();
|
|
971
|
-
if (!teamName || !subject || !description) {
|
|
972
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, subject, description are required' }) }], isError: true };
|
|
973
|
-
}
|
|
974
|
-
const owner = args.owner;
|
|
975
|
-
const blockedBy = args.blocked_by;
|
|
976
|
-
const requiresCodeChange = args.requires_code_change;
|
|
977
|
-
const task = await teamCreateTask(teamName, {
|
|
978
|
-
subject, description, status: 'pending',
|
|
979
|
-
owner: owner || undefined,
|
|
980
|
-
blocked_by: blockedBy,
|
|
981
|
-
requires_code_change: requiresCodeChange,
|
|
982
|
-
}, cwd);
|
|
983
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, task }) }] };
|
|
984
|
-
}
|
|
985
|
-
case 'team_read_task': {
|
|
986
|
-
const teamName = String(args.team_name || '').trim();
|
|
987
|
-
const taskId = String(args.task_id || '').trim();
|
|
988
|
-
if (!teamName || !taskId) {
|
|
989
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and task_id are required' }) }], isError: true };
|
|
990
|
-
}
|
|
991
|
-
const task = await teamReadTask(teamName, taskId, cwd);
|
|
992
|
-
if (!task)
|
|
993
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'task_not_found' }) }] };
|
|
994
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, task }) }] };
|
|
995
|
-
}
|
|
996
|
-
case 'team_list_tasks': {
|
|
997
|
-
const teamName = String(args.team_name || '').trim();
|
|
998
|
-
if (!teamName) {
|
|
999
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name is required' }) }], isError: true };
|
|
1000
|
-
}
|
|
1001
|
-
const tasks = await teamListTasks(teamName, cwd);
|
|
1002
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, count: tasks.length, tasks }) }] };
|
|
1003
|
-
}
|
|
1004
|
-
case 'team_update_task': {
|
|
1005
|
-
const teamName = String(args.team_name || '').trim();
|
|
1006
|
-
const taskId = String(args.task_id || '').trim();
|
|
1007
|
-
if (!teamName || !taskId) {
|
|
1008
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and task_id are required' }) }], isError: true };
|
|
1009
|
-
}
|
|
1010
|
-
const lifecycleFields = ['status', 'owner', 'result', 'error'];
|
|
1011
|
-
const presentLifecycleFields = lifecycleFields.filter((f) => f in args);
|
|
1012
|
-
if (presentLifecycleFields.length > 0) {
|
|
1013
|
-
return {
|
|
1014
|
-
content: [{
|
|
1015
|
-
type: 'text',
|
|
1016
|
-
text: JSON.stringify({
|
|
1017
|
-
error: `team_update_task cannot mutate lifecycle fields: ${presentLifecycleFields.join(', ')}. Use team_claim_task + team_transition_task_status to change task status/owner/result/error.`,
|
|
1018
|
-
}),
|
|
1019
|
-
}],
|
|
1020
|
-
isError: true,
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
const unexpectedFields = Object.keys(args).filter((field) => !TEAM_UPDATE_TASK_REQUEST_FIELDS.has(field));
|
|
1024
|
-
if (unexpectedFields.length > 0) {
|
|
1025
|
-
return {
|
|
1026
|
-
content: [{
|
|
1027
|
-
type: 'text',
|
|
1028
|
-
text: JSON.stringify({
|
|
1029
|
-
error: `team_update_task received unsupported fields: ${unexpectedFields.join(', ')}. Allowed mutable fields: subject, description, blocked_by, requires_code_change.`,
|
|
1030
|
-
}),
|
|
1031
|
-
}],
|
|
1032
|
-
isError: true,
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
const updates = {};
|
|
1036
|
-
if ('subject' in args) {
|
|
1037
|
-
const subject = args.subject;
|
|
1038
|
-
if (typeof subject !== 'string') {
|
|
1039
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'subject must be a string when provided' }) }], isError: true };
|
|
1040
|
-
}
|
|
1041
|
-
updates.subject = subject.trim();
|
|
1042
|
-
}
|
|
1043
|
-
if ('description' in args) {
|
|
1044
|
-
const description = args.description;
|
|
1045
|
-
if (typeof description !== 'string') {
|
|
1046
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'description must be a string when provided' }) }], isError: true };
|
|
1047
|
-
}
|
|
1048
|
-
updates.description = description.trim();
|
|
1049
|
-
}
|
|
1050
|
-
if ('requires_code_change' in args) {
|
|
1051
|
-
const requiresCodeChange = args.requires_code_change;
|
|
1052
|
-
if (typeof requiresCodeChange !== 'boolean') {
|
|
1053
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'requires_code_change must be a boolean when provided' }) }], isError: true };
|
|
1054
|
-
}
|
|
1055
|
-
updates.requires_code_change = requiresCodeChange;
|
|
1056
|
-
}
|
|
1057
|
-
if ('blocked_by' in args) {
|
|
1058
|
-
try {
|
|
1059
|
-
updates.blocked_by = parseValidatedTaskIdArray(args.blocked_by, 'blocked_by');
|
|
1060
|
-
}
|
|
1061
|
-
catch (error) {
|
|
1062
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
const task = await teamUpdateTask(teamName, taskId, updates, cwd);
|
|
1066
|
-
if (!task)
|
|
1067
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'task_not_found' }) }] };
|
|
1068
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, task }) }] };
|
|
1069
|
-
}
|
|
1070
|
-
case 'team_claim_task': {
|
|
1071
|
-
const teamName = String(args.team_name || '').trim();
|
|
1072
|
-
const taskId = String(args.task_id || '').trim();
|
|
1073
|
-
const worker = String(args.worker || '').trim();
|
|
1074
|
-
if (!teamName || !taskId || !worker) {
|
|
1075
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, task_id, worker are required' }) }], isError: true };
|
|
1076
|
-
}
|
|
1077
|
-
const rawExpectedVersion = args.expected_version;
|
|
1078
|
-
if (rawExpectedVersion !== undefined && (!isFiniteInteger(rawExpectedVersion) || rawExpectedVersion < 1)) {
|
|
1079
|
-
return {
|
|
1080
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'expected_version must be a positive integer when provided' }) }],
|
|
1081
|
-
isError: true,
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
const expectedVersion = rawExpectedVersion;
|
|
1085
|
-
const result = await teamClaimTask(teamName, taskId, worker, expectedVersion ?? null, cwd);
|
|
1086
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
1087
|
-
}
|
|
1088
|
-
case 'team_transition_task_status': {
|
|
1089
|
-
const teamName = String(args.team_name || '').trim();
|
|
1090
|
-
const taskId = String(args.task_id || '').trim();
|
|
1091
|
-
const from = String(args.from || '').trim();
|
|
1092
|
-
const to = String(args.to || '').trim();
|
|
1093
|
-
const claimToken = String(args.claim_token || '').trim();
|
|
1094
|
-
if (!teamName || !taskId || !from || !to || !claimToken) {
|
|
1095
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, task_id, from, to, claim_token are required' }) }], isError: true };
|
|
1096
|
-
}
|
|
1097
|
-
const allowed = new Set(TEAM_TASK_STATUSES);
|
|
1098
|
-
if (!allowed.has(from) || !allowed.has(to)) {
|
|
1099
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'from and to must be valid task statuses' }) }], isError: true };
|
|
1100
|
-
}
|
|
1101
|
-
const result = await teamTransitionTaskStatus(teamName, taskId, from, to, claimToken, cwd);
|
|
1102
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
1103
|
-
}
|
|
1104
|
-
case 'team_release_task_claim': {
|
|
1105
|
-
const teamName = String(args.team_name || '').trim();
|
|
1106
|
-
const taskId = String(args.task_id || '').trim();
|
|
1107
|
-
const claimToken = String(args.claim_token || '').trim();
|
|
1108
|
-
const worker = String(args.worker || '').trim();
|
|
1109
|
-
if (!teamName || !taskId || !claimToken || !worker) {
|
|
1110
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, task_id, claim_token, worker are required' }) }], isError: true };
|
|
1111
|
-
}
|
|
1112
|
-
const result = await teamReleaseTaskClaim(teamName, taskId, claimToken, worker, cwd);
|
|
1113
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
1114
|
-
}
|
|
1115
|
-
case 'team_read_config': {
|
|
1116
|
-
const teamName = String(args.team_name || '').trim();
|
|
1117
|
-
if (!teamName) {
|
|
1118
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name is required' }) }], isError: true };
|
|
1119
|
-
}
|
|
1120
|
-
const config = await teamReadConfig(teamName, cwd);
|
|
1121
|
-
if (!config)
|
|
1122
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'team_not_found' }) }] };
|
|
1123
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, config }) }] };
|
|
1124
|
-
}
|
|
1125
|
-
case 'team_read_manifest': {
|
|
1126
|
-
const teamName = String(args.team_name || '').trim();
|
|
1127
|
-
if (!teamName) {
|
|
1128
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name is required' }) }], isError: true };
|
|
1129
|
-
}
|
|
1130
|
-
const manifest = await teamReadManifest(teamName, cwd);
|
|
1131
|
-
if (!manifest)
|
|
1132
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'manifest_not_found' }) }] };
|
|
1133
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, manifest }) }] };
|
|
1134
|
-
}
|
|
1135
|
-
case 'team_read_worker_status': {
|
|
1136
|
-
const teamName = String(args.team_name || '').trim();
|
|
1137
|
-
const worker = String(args.worker || '').trim();
|
|
1138
|
-
if (!teamName || !worker) {
|
|
1139
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and worker are required' }) }], isError: true };
|
|
1140
|
-
}
|
|
1141
|
-
const status = await teamReadWorkerStatus(teamName, worker, cwd);
|
|
1142
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker, status }) }] };
|
|
1143
|
-
}
|
|
1144
|
-
case 'team_read_worker_heartbeat': {
|
|
1145
|
-
const teamName = String(args.team_name || '').trim();
|
|
1146
|
-
const worker = String(args.worker || '').trim();
|
|
1147
|
-
if (!teamName || !worker) {
|
|
1148
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and worker are required' }) }], isError: true };
|
|
1149
|
-
}
|
|
1150
|
-
const heartbeat = await teamReadWorkerHeartbeat(teamName, worker, cwd);
|
|
1151
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker, heartbeat }) }] };
|
|
1152
|
-
}
|
|
1153
|
-
case 'team_update_worker_heartbeat': {
|
|
1154
|
-
const teamName = String(args.team_name || '').trim();
|
|
1155
|
-
const worker = String(args.worker || '').trim();
|
|
1156
|
-
const pid = args.pid;
|
|
1157
|
-
const turnCount = args.turn_count;
|
|
1158
|
-
const alive = args.alive;
|
|
1159
|
-
if (!teamName || !worker || typeof pid !== 'number' || typeof turnCount !== 'number' || typeof alive !== 'boolean') {
|
|
1160
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, worker, pid, turn_count, alive are required' }) }], isError: true };
|
|
1161
|
-
}
|
|
1162
|
-
await teamUpdateWorkerHeartbeat(teamName, worker, { pid, turn_count: turnCount, alive, last_turn_at: new Date().toISOString() }, cwd);
|
|
1163
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker }) }] };
|
|
1164
|
-
}
|
|
1165
|
-
case 'team_write_worker_inbox': {
|
|
1166
|
-
const teamName = String(args.team_name || '').trim();
|
|
1167
|
-
const worker = String(args.worker || '').trim();
|
|
1168
|
-
const content = String(args.content || '').trim();
|
|
1169
|
-
if (!teamName || !worker || !content) {
|
|
1170
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, worker, content are required' }) }], isError: true };
|
|
1171
|
-
}
|
|
1172
|
-
await teamWriteWorkerInbox(teamName, worker, content, cwd);
|
|
1173
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker }) }] };
|
|
1174
|
-
}
|
|
1175
|
-
case 'team_write_worker_identity': {
|
|
1176
|
-
const teamName = String(args.team_name || '').trim();
|
|
1177
|
-
const worker = String(args.worker || '').trim();
|
|
1178
|
-
const index = args.index;
|
|
1179
|
-
const role = String(args.role || '').trim();
|
|
1180
|
-
if (!teamName || !worker || typeof index !== 'number' || !role) {
|
|
1181
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, worker, index, role are required' }) }], isError: true };
|
|
1182
|
-
}
|
|
1183
|
-
const assignedTasks = args.assigned_tasks ?? [];
|
|
1184
|
-
const pid = args.pid;
|
|
1185
|
-
const paneId = args.pane_id;
|
|
1186
|
-
const workingDir = args.working_dir;
|
|
1187
|
-
const worktreePath = args.worktree_path;
|
|
1188
|
-
const worktreeBranch = args.worktree_branch;
|
|
1189
|
-
const worktreeDetached = args.worktree_detached;
|
|
1190
|
-
const teamStateRoot = args.team_state_root;
|
|
1191
|
-
await teamWriteWorkerIdentity(teamName, worker, {
|
|
1192
|
-
name: worker,
|
|
1193
|
-
index,
|
|
1194
|
-
role,
|
|
1195
|
-
assigned_tasks: assignedTasks,
|
|
1196
|
-
pid,
|
|
1197
|
-
pane_id: paneId,
|
|
1198
|
-
working_dir: workingDir,
|
|
1199
|
-
worktree_path: worktreePath,
|
|
1200
|
-
worktree_branch: worktreeBranch,
|
|
1201
|
-
worktree_detached: worktreeDetached,
|
|
1202
|
-
team_state_root: teamStateRoot,
|
|
1203
|
-
}, cwd);
|
|
1204
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker }) }] };
|
|
1205
|
-
}
|
|
1206
|
-
case 'team_append_event': {
|
|
1207
|
-
const teamName = String(args.team_name || '').trim();
|
|
1208
|
-
const eventType = String(args.type || '').trim();
|
|
1209
|
-
const worker = String(args.worker || '').trim();
|
|
1210
|
-
if (!teamName || !eventType || !worker) {
|
|
1211
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, type, worker are required' }) }], isError: true };
|
|
1212
|
-
}
|
|
1213
|
-
if (!TEAM_EVENT_TYPES.includes(eventType)) {
|
|
1214
|
-
return {
|
|
1215
|
-
content: [{
|
|
1216
|
-
type: 'text',
|
|
1217
|
-
text: JSON.stringify({ error: `type must be one of: ${TEAM_EVENT_TYPES.join(', ')}` }),
|
|
1218
|
-
}],
|
|
1219
|
-
isError: true,
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
const event = await teamAppendEvent(teamName, {
|
|
1223
|
-
type: eventType,
|
|
1224
|
-
worker,
|
|
1225
|
-
task_id: args.task_id,
|
|
1226
|
-
message_id: args.message_id ?? null,
|
|
1227
|
-
reason: args.reason,
|
|
1228
|
-
}, cwd);
|
|
1229
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, event }) }] };
|
|
1230
|
-
}
|
|
1231
|
-
case 'team_get_summary': {
|
|
1232
|
-
const teamName = String(args.team_name || '').trim();
|
|
1233
|
-
if (!teamName) {
|
|
1234
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name is required' }) }], isError: true };
|
|
1235
|
-
}
|
|
1236
|
-
const summary = await teamGetSummary(teamName, cwd);
|
|
1237
|
-
if (!summary)
|
|
1238
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'team_not_found' }) }] };
|
|
1239
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, summary }) }] };
|
|
1240
|
-
}
|
|
1241
|
-
case 'team_cleanup': {
|
|
1242
|
-
const teamName = String(args.team_name || '').trim();
|
|
1243
|
-
if (!teamName) {
|
|
1244
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name is required' }) }], isError: true };
|
|
1245
|
-
}
|
|
1246
|
-
await teamCleanup(teamName, cwd);
|
|
1247
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, team_name: teamName }) }] };
|
|
1248
|
-
}
|
|
1249
|
-
case 'team_write_shutdown_request': {
|
|
1250
|
-
const teamName = String(args.team_name || '').trim();
|
|
1251
|
-
const worker = String(args.worker || '').trim();
|
|
1252
|
-
const requestedBy = String(args.requested_by || '').trim();
|
|
1253
|
-
if (!teamName || !worker || !requestedBy) {
|
|
1254
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, worker, requested_by are required' }) }], isError: true };
|
|
1255
|
-
}
|
|
1256
|
-
await teamWriteShutdownRequest(teamName, worker, requestedBy, cwd);
|
|
1257
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker }) }] };
|
|
1258
|
-
}
|
|
1259
|
-
case 'team_read_shutdown_ack': {
|
|
1260
|
-
const teamName = String(args.team_name || '').trim();
|
|
1261
|
-
const worker = String(args.worker || '').trim();
|
|
1262
|
-
if (!teamName || !worker) {
|
|
1263
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and worker are required' }) }], isError: true };
|
|
1264
|
-
}
|
|
1265
|
-
const minUpdatedAt = args.min_updated_at;
|
|
1266
|
-
const ack = await teamReadShutdownAck(teamName, worker, cwd, minUpdatedAt);
|
|
1267
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, worker, ack }) }] };
|
|
1268
|
-
}
|
|
1269
|
-
case 'team_read_monitor_snapshot': {
|
|
1270
|
-
const teamName = String(args.team_name || '').trim();
|
|
1271
|
-
if (!teamName) {
|
|
1272
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name is required' }) }], isError: true };
|
|
1273
|
-
}
|
|
1274
|
-
const snapshot = await teamReadMonitorSnapshot(teamName, cwd);
|
|
1275
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, snapshot }) }] };
|
|
1276
|
-
}
|
|
1277
|
-
case 'team_write_monitor_snapshot': {
|
|
1278
|
-
const teamName = String(args.team_name || '').trim();
|
|
1279
|
-
const snapshot = args.snapshot;
|
|
1280
|
-
if (!teamName || !snapshot) {
|
|
1281
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and snapshot are required' }) }], isError: true };
|
|
1282
|
-
}
|
|
1283
|
-
await teamWriteMonitorSnapshot(teamName, snapshot, cwd);
|
|
1284
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true }) }] };
|
|
1285
|
-
}
|
|
1286
|
-
case 'team_read_task_approval': {
|
|
1287
|
-
const teamName = String(args.team_name || '').trim();
|
|
1288
|
-
const taskId = String(args.task_id || '').trim();
|
|
1289
|
-
if (!teamName || !taskId) {
|
|
1290
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name and task_id are required' }) }], isError: true };
|
|
1291
|
-
}
|
|
1292
|
-
const approval = await teamReadTaskApproval(teamName, taskId, cwd);
|
|
1293
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, approval }) }] };
|
|
1294
|
-
}
|
|
1295
|
-
case 'team_write_task_approval': {
|
|
1296
|
-
const teamName = String(args.team_name || '').trim();
|
|
1297
|
-
const taskId = String(args.task_id || '').trim();
|
|
1298
|
-
const status = String(args.status || '').trim();
|
|
1299
|
-
const reviewer = String(args.reviewer || '').trim();
|
|
1300
|
-
const decisionReason = String(args.decision_reason || '').trim();
|
|
1301
|
-
if (!teamName || !taskId || !status || !reviewer || !decisionReason) {
|
|
1302
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'team_name, task_id, status, reviewer, decision_reason are required' }) }], isError: true };
|
|
1303
|
-
}
|
|
1304
|
-
if (!TEAM_TASK_APPROVAL_STATUSES.includes(status)) {
|
|
1305
|
-
return {
|
|
1306
|
-
content: [{
|
|
1307
|
-
type: 'text',
|
|
1308
|
-
text: JSON.stringify({ error: `status must be one of: ${TEAM_TASK_APPROVAL_STATUSES.join(', ')}` }),
|
|
1309
|
-
}],
|
|
1310
|
-
isError: true,
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
const rawRequired = args.required;
|
|
1314
|
-
if (rawRequired !== undefined && typeof rawRequired !== 'boolean') {
|
|
1315
|
-
return { content: [{ type: 'text', text: JSON.stringify({ error: 'required must be a boolean when provided' }) }], isError: true };
|
|
1316
|
-
}
|
|
1317
|
-
const required = rawRequired !== false;
|
|
1318
|
-
await teamWriteTaskApproval(teamName, {
|
|
1319
|
-
task_id: taskId,
|
|
1320
|
-
required,
|
|
1321
|
-
status: status,
|
|
1322
|
-
reviewer,
|
|
1323
|
-
decision_reason: decisionReason,
|
|
1324
|
-
decided_at: new Date().toISOString(),
|
|
1325
|
-
}, cwd);
|
|
1326
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, task_id: taskId, status }) }] };
|
|
1327
|
-
}
|
|
1328
|
-
default:
|
|
1329
|
-
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
1330
435
|
}
|
|
1331
436
|
}
|
|
1332
437
|
catch (error) {
|
|
1333
438
|
return {
|
|
1334
|
-
content: [
|
|
439
|
+
content: [
|
|
440
|
+
{
|
|
441
|
+
type: "text",
|
|
442
|
+
text: JSON.stringify({ error: error.message }),
|
|
443
|
+
},
|
|
444
|
+
],
|
|
1335
445
|
isError: true,
|
|
1336
446
|
};
|
|
1337
447
|
}
|
|
1338
448
|
}
|
|
1339
449
|
server.setRequestHandler(CallToolRequestSchema, handleStateToolCall);
|
|
1340
450
|
// Start server
|
|
1341
|
-
if (shouldAutoStartMcpServer(
|
|
451
|
+
if (shouldAutoStartMcpServer("state")) {
|
|
1342
452
|
const transport = new StdioServerTransport();
|
|
1343
453
|
server.connect(transport).catch(console.error);
|
|
1344
454
|
}
|