@jawcode-dev/coding-agent 1.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/CHANGELOG.md +376 -0
- package/README.md +37 -0
- package/dist/types/async/index.d.ts +2 -0
- package/dist/types/async/job-manager.d.ts +294 -0
- package/dist/types/async/support.d.ts +2 -0
- package/dist/types/autoresearch/dashboard.d.ts +4 -0
- package/dist/types/autoresearch/git.d.ts +36 -0
- package/dist/types/autoresearch/helpers.d.ts +24 -0
- package/dist/types/autoresearch/index.d.ts +2 -0
- package/dist/types/autoresearch/state.d.ts +17 -0
- package/dist/types/autoresearch/storage.d.ts +142 -0
- package/dist/types/autoresearch/tools/init-experiment.d.ts +31 -0
- package/dist/types/autoresearch/tools/log-experiment.d.ts +23 -0
- package/dist/types/autoresearch/tools/run-experiment.d.ts +8 -0
- package/dist/types/autoresearch/tools/update-notes.d.ts +12 -0
- package/dist/types/autoresearch/types.d.ts +154 -0
- package/dist/types/capability/context-file.d.ts +17 -0
- package/dist/types/capability/extension-module.d.ts +15 -0
- package/dist/types/capability/extension.d.ts +28 -0
- package/dist/types/capability/fs.d.ts +20 -0
- package/dist/types/capability/hook.d.ts +19 -0
- package/dist/types/capability/index.d.ts +80 -0
- package/dist/types/capability/instruction.d.ts +17 -0
- package/dist/types/capability/mcp.d.ts +45 -0
- package/dist/types/capability/prompt.d.ts +15 -0
- package/dist/types/capability/rule.d.ts +61 -0
- package/dist/types/capability/settings.d.ts +15 -0
- package/dist/types/capability/skill.d.ts +36 -0
- package/dist/types/capability/slash-command.d.ts +17 -0
- package/dist/types/capability/ssh.d.ts +23 -0
- package/dist/types/capability/system-prompt.d.ts +15 -0
- package/dist/types/capability/tool.d.ts +19 -0
- package/dist/types/capability/types.d.ts +154 -0
- package/dist/types/cli/agents-cli.d.ts +12 -0
- package/dist/types/cli/args.d.ts +56 -0
- package/dist/types/cli/auth-broker-cli.d.ts +25 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
- package/dist/types/cli/classify-install-target.d.ts +18 -0
- package/dist/types/cli/commands/init-xdg.d.ts +1 -0
- package/dist/types/cli/config-cli.d.ts +22 -0
- package/dist/types/cli/fast-help.d.ts +1 -0
- package/dist/types/cli/file-processor.d.ts +11 -0
- package/dist/types/cli/grep-cli.d.ts +17 -0
- package/dist/types/cli/initial-message.d.ts +17 -0
- package/dist/types/cli/list-models.d.ts +30 -0
- package/dist/types/cli/notify-cli.d.ts +29 -0
- package/dist/types/cli/plugin-cli.d.ts +32 -0
- package/dist/types/cli/read-cli.d.ts +4 -0
- package/dist/types/cli/session-picker.d.ts +3 -0
- package/dist/types/cli/setup-cli.d.ts +45 -0
- package/dist/types/cli/shell-cli.d.ts +8 -0
- package/dist/types/cli/skills-cli.d.ts +9 -0
- package/dist/types/cli/ssh-cli.d.ts +21 -0
- package/dist/types/cli/stats-cli.d.ts +17 -0
- package/dist/types/cli/update-cli.d.ts +41 -0
- package/dist/types/cli/web-search-cli.d.ts +20 -0
- package/dist/types/cli/worktree-cli.d.ts +26 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/commands/acp.d.ts +12 -0
- package/dist/types/commands/agents.d.ts +34 -0
- package/dist/types/commands/auth-broker.d.ts +54 -0
- package/dist/types/commands/auth-gateway.d.ts +32 -0
- package/dist/types/commands/chat.d.ts +18 -0
- package/dist/types/commands/codex-native-hook.d.ts +6 -0
- package/dist/types/commands/commit.d.ts +27 -0
- package/dist/types/commands/config.d.ts +30 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/coordinator.d.ts +19 -0
- package/dist/types/commands/goal.d.ts +31 -0
- package/dist/types/commands/grep.d.ts +42 -0
- package/dist/types/commands/harness.d.ts +50 -0
- package/dist/types/commands/interview.d.ts +51 -0
- package/dist/types/commands/launch.d.ts +127 -0
- package/dist/types/commands/mcp-serve.d.ts +24 -0
- package/dist/types/commands/memory.d.ts +19 -0
- package/dist/types/commands/notify.d.ts +30 -0
- package/dist/types/commands/orchestrate.d.ts +48 -0
- package/dist/types/commands/planphase.d.ts +7 -0
- package/dist/types/commands/plugin.d.ts +52 -0
- package/dist/types/commands/ralplan.d.ts +7 -0
- package/dist/types/commands/read.d.ts +15 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +95 -0
- package/dist/types/commands/shell.d.ts +21 -0
- package/dist/types/commands/skills.d.ts +26 -0
- package/dist/types/commands/ssh.d.ts +48 -0
- package/dist/types/commands/state.d.ts +7 -0
- package/dist/types/commands/stats.d.ts +25 -0
- package/dist/types/commands/team.d.ts +24 -0
- package/dist/types/commands/ultragoal.d.ts +8 -0
- package/dist/types/commands/update.d.ts +20 -0
- package/dist/types/commands/web-search.d.ts +33 -0
- package/dist/types/commands/worktree.d.ts +31 -0
- package/dist/types/commit/agentic/agent.d.ts +31 -0
- package/dist/types/commit/agentic/fallback.d.ts +5 -0
- package/dist/types/commit/agentic/index.d.ts +2 -0
- package/dist/types/commit/agentic/state.d.ts +58 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +19 -0
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +10 -0
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +9 -0
- package/dist/types/commit/agentic/tools/git-overview.d.ts +9 -0
- package/dist/types/commit/agentic/tools/index.d.ts +16 -0
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +28 -0
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +36 -0
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +7 -0
- package/dist/types/commit/agentic/tools/schemas.d.ts +27 -0
- package/dist/types/commit/agentic/tools/split-commit.d.ts +53 -0
- package/dist/types/commit/agentic/topo-sort.d.ts +4 -0
- package/dist/types/commit/agentic/trivial.d.ts +7 -0
- package/dist/types/commit/agentic/validation.d.ts +20 -0
- package/dist/types/commit/analysis/conventional.d.ts +22 -0
- package/dist/types/commit/analysis/index.d.ts +4 -0
- package/dist/types/commit/analysis/scope.d.ts +6 -0
- package/dist/types/commit/analysis/summary.d.ts +19 -0
- package/dist/types/commit/analysis/validation.d.ts +8 -0
- package/dist/types/commit/changelog/detect.d.ts +2 -0
- package/dist/types/commit/changelog/generate.d.ts +30 -0
- package/dist/types/commit/changelog/index.d.ts +30 -0
- package/dist/types/commit/changelog/parse.d.ts +2 -0
- package/dist/types/commit/cli.d.ts +3 -0
- package/dist/types/commit/git/diff.d.ts +5 -0
- package/dist/types/commit/index.d.ts +4 -0
- package/dist/types/commit/map-reduce/index.d.ts +28 -0
- package/dist/types/commit/map-reduce/map-phase.d.ts +17 -0
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +13 -0
- package/dist/types/commit/map-reduce/utils.d.ts +2 -0
- package/dist/types/commit/message.d.ts +2 -0
- package/dist/types/commit/model-selection.d.ts +15 -0
- package/dist/types/commit/pipeline.d.ts +5 -0
- package/dist/types/commit/shared-llm.d.ts +54 -0
- package/dist/types/commit/types.d.ts +78 -0
- package/dist/types/commit/utils/exclusions.d.ts +4 -0
- package/dist/types/commit/utils.d.ts +20 -0
- package/dist/types/config/config-file.d.ts +58 -0
- package/dist/types/config/file-lock.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +359 -0
- package/dist/types/config/model-equivalence.d.ts +24 -0
- package/dist/types/config/model-profile-activation.d.ts +36 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +445 -0
- package/dist/types/config/model-resolver.d.ts +217 -0
- package/dist/types/config/models-config-schema.d.ts +652 -0
- package/dist/types/config/oauth-provider-aliases.d.ts +1 -0
- package/dist/types/config/prompt-templates.d.ts +32 -0
- package/dist/types/config/resolve-config-value.d.ts +17 -0
- package/dist/types/config/settings-schema.d.ts +3683 -0
- package/dist/types/config/settings.d.ts +134 -0
- package/dist/types/config/skill-settings-defaults.d.ts +21 -0
- package/dist/types/config.d.ts +66 -0
- package/dist/types/coordinator/contract.d.ts +4 -0
- package/dist/types/coordinator-mcp/policy.d.ts +24 -0
- package/dist/types/coordinator-mcp/safety.d.ts +26 -0
- package/dist/types/coordinator-mcp/server.d.ts +52 -0
- package/dist/types/cursor.d.ts +24 -0
- package/dist/types/dap/client.d.ts +38 -0
- package/dist/types/dap/config.d.ts +6 -0
- package/dist/types/dap/index.d.ts +4 -0
- package/dist/types/dap/session.d.ts +108 -0
- package/dist/types/dap/types.d.ts +524 -0
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/index.d.ts +15 -0
- package/dist/types/debug/log-formatting.d.ts +4 -0
- package/dist/types/debug/log-viewer.d.ts +68 -0
- package/dist/types/debug/profiler.d.ts +24 -0
- package/dist/types/debug/raw-sse-buffer.d.ts +44 -0
- package/dist/types/debug/raw-sse.d.ts +16 -0
- package/dist/types/debug/report-bundle.d.ts +55 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/debug/system-info.d.ts +26 -0
- package/dist/types/defaults/jwc-defaults.d.ts +69 -0
- package/dist/types/discovery/agents-md.d.ts +1 -0
- package/dist/types/discovery/agents.d.ts +12 -0
- package/dist/types/discovery/builtin.d.ts +1 -0
- package/dist/types/discovery/claude-plugins.d.ts +1 -0
- package/dist/types/discovery/claude.d.ts +1 -0
- package/dist/types/discovery/cli-jaw.d.ts +1 -0
- package/dist/types/discovery/cline.d.ts +1 -0
- package/dist/types/discovery/codex.d.ts +1 -0
- package/dist/types/discovery/cursor.d.ts +16 -0
- package/dist/types/discovery/gemini.d.ts +1 -0
- package/dist/types/discovery/github.d.ts +1 -0
- package/dist/types/discovery/helpers.d.ts +278 -0
- package/dist/types/discovery/index.d.ts +49 -0
- package/dist/types/discovery/mcp-json.d.ts +1 -0
- package/dist/types/discovery/opencode.d.ts +1 -0
- package/dist/types/discovery/plugin-dir-roots.d.ts +15 -0
- package/dist/types/discovery/ssh.d.ts +1 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +5 -0
- package/dist/types/discovery/vscode.d.ts +1 -0
- package/dist/types/discovery/windsurf.d.ts +13 -0
- package/dist/types/edit/apply-patch/index.d.ts +35 -0
- package/dist/types/edit/apply-patch/parser.d.ts +34 -0
- package/dist/types/edit/diff.d.ts +75 -0
- package/dist/types/edit/file-read-cache.d.ts +25 -0
- package/dist/types/edit/index.d.ts +58 -0
- package/dist/types/edit/modes/apply-patch.d.ts +24 -0
- package/dist/types/edit/modes/patch.d.ts +99 -0
- package/dist/types/edit/modes/replace.d.ts +149 -0
- package/dist/types/edit/normalize.d.ts +43 -0
- package/dist/types/edit/notebook.d.ts +23 -0
- package/dist/types/edit/read-file.d.ts +2 -0
- package/dist/types/edit/renderer.d.ts +110 -0
- package/dist/types/edit/streaming.d.ts +66 -0
- package/dist/types/eval/backend.d.ts +40 -0
- package/dist/types/eval/index.d.ts +4 -0
- package/dist/types/eval/js/context-manager.d.ts +24 -0
- package/dist/types/eval/js/executor.d.ts +28 -0
- package/dist/types/eval/js/index.d.ts +10 -0
- package/dist/types/eval/js/shared/helpers.d.ts +38 -0
- package/dist/types/eval/js/shared/indirect-eval.d.ts +14 -0
- package/dist/types/eval/js/shared/prelude.d.ts +1 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -0
- package/dist/types/eval/js/shared/runtime.d.ts +47 -0
- package/dist/types/eval/js/shared/types.d.ts +24 -0
- package/dist/types/eval/js/tool-bridge.d.ts +18 -0
- package/dist/types/eval/js/worker-core.d.ts +5 -0
- package/dist/types/eval/js/worker-entry.d.ts +1 -0
- package/dist/types/eval/js/worker-protocol.d.ts +77 -0
- package/dist/types/eval/py/display.d.ts +25 -0
- package/dist/types/eval/py/executor.d.ts +85 -0
- package/dist/types/eval/py/index.d.ts +10 -0
- package/dist/types/eval/py/kernel.d.ts +63 -0
- package/dist/types/eval/py/prelude.d.ts +1 -0
- package/dist/types/eval/py/runtime.d.ts +21 -0
- package/dist/types/eval/py/tool-bridge.d.ts +20 -0
- package/dist/types/eval/types.d.ts +52 -0
- package/dist/types/exa/factory.d.ts +13 -0
- package/dist/types/exa/index.d.ts +20 -0
- package/dist/types/exa/mcp-client.d.ts +45 -0
- package/dist/types/exa/render.d.ts +19 -0
- package/dist/types/exa/researcher.d.ts +9 -0
- package/dist/types/exa/search.d.ts +9 -0
- package/dist/types/exa/types.d.ts +155 -0
- package/dist/types/exa/websets.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +58 -0
- package/dist/types/exec/exec.d.ts +25 -0
- package/dist/types/exec/idle-timeout-watchdog.d.ts +18 -0
- package/dist/types/exec/non-interactive-env.d.ts +4 -0
- package/dist/types/export/custom-share.d.ts +20 -0
- package/dist/types/export/html/index.d.ts +10 -0
- package/dist/types/export/html/template.generated.d.ts +1 -0
- package/dist/types/export/html/template.macro.d.ts +5 -0
- package/dist/types/export/ttsr.d.ts +44 -0
- package/dist/types/extensibility/custom-commands/bundled/ci-green/index.d.ts +9 -0
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +10 -0
- package/dist/types/extensibility/custom-commands/index.d.ts +2 -0
- package/dist/types/extensibility/custom-commands/loader.d.ts +29 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +106 -0
- package/dist/types/extensibility/custom-tools/index.d.ts +6 -0
- package/dist/types/extensibility/custom-tools/loader.d.ts +69 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +227 -0
- package/dist/types/extensibility/custom-tools/wrapper.d.ts +23 -0
- package/dist/types/extensibility/extensions/compact-handler.d.ts +26 -0
- package/dist/types/extensibility/extensions/get-commands-handler.d.ts +29 -0
- package/dist/types/extensibility/extensions/index.d.ts +8 -0
- package/dist/types/extensibility/extensions/loader.d.ts +43 -0
- package/dist/types/extensibility/extensions/runner.d.ts +134 -0
- package/dist/types/extensibility/extensions/types.d.ts +916 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +54 -0
- package/dist/types/extensibility/hooks/index.d.ts +5 -0
- package/dist/types/extensibility/hooks/loader.d.ts +89 -0
- package/dist/types/extensibility/hooks/runner.d.ts +126 -0
- package/dist/types/extensibility/hooks/tool-wrapper.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +431 -0
- package/dist/types/extensibility/jwc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/jwc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/jwc-plugins/injection.d.ts +31 -0
- package/dist/types/extensibility/jwc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/jwc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/jwc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/jwc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/jwc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/jwc-plugins/types.d.ts +64 -0
- package/dist/types/extensibility/jwc-plugins/validation.d.ts +4 -0
- package/dist/types/extensibility/plugins/doctor.d.ts +3 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +34 -0
- package/dist/types/extensibility/plugins/index.d.ts +7 -0
- package/dist/types/extensibility/plugins/installer.d.ts +5 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
- package/dist/types/extensibility/plugins/loader.d.ts +31 -0
- package/dist/types/extensibility/plugins/manager.d.ts +65 -0
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +41 -0
- package/dist/types/extensibility/plugins/marketplace/fetcher.d.ts +48 -0
- package/dist/types/extensibility/plugins/marketplace/index.d.ts +6 -0
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +57 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +34 -0
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +28 -0
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +130 -0
- package/dist/types/extensibility/plugins/parser.d.ts +52 -0
- package/dist/types/extensibility/plugins/types.d.ts +149 -0
- package/dist/types/extensibility/shared-events.d.ts +280 -0
- package/dist/types/extensibility/skills.d.ts +82 -0
- package/dist/types/extensibility/slash-commands.d.ts +49 -0
- package/dist/types/extensibility/tool-proxy.d.ts +4 -0
- package/dist/types/extensibility/typebox.d.ts +155 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/goals/goal-planning-start.d.ts +7 -0
- package/dist/types/goals/index.d.ts +3 -0
- package/dist/types/goals/runtime.d.ts +62 -0
- package/dist/types/goals/state.d.ts +40 -0
- package/dist/types/goals/tools/goal-tool.d.ts +62 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +55 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +26 -0
- package/dist/types/harness-control-plane/receipts.d.ts +132 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +24 -0
- package/dist/types/harness-control-plane/storage.d.ts +60 -0
- package/dist/types/harness-control-plane/types.d.ts +179 -0
- package/dist/types/hashline/anchors.d.ts +20 -0
- package/dist/types/hashline/apply.d.ts +14 -0
- package/dist/types/hashline/constants.d.ts +19 -0
- package/dist/types/hashline/diff-preview.d.ts +2 -0
- package/dist/types/hashline/diff.d.ts +17 -0
- package/dist/types/hashline/execute.d.ts +4 -0
- package/dist/types/hashline/hash.d.ts +123 -0
- package/dist/types/hashline/index.d.ts +13 -0
- package/dist/types/hashline/input.d.ts +11 -0
- package/dist/types/hashline/parser.d.ts +7 -0
- package/dist/types/hashline/prefixes.d.ts +7 -0
- package/dist/types/hashline/recovery.d.ts +27 -0
- package/dist/types/hashline/stream.d.ts +2 -0
- package/dist/types/hashline/types.d.ts +77 -0
- package/dist/types/hindsight/backend.d.ts +13 -0
- package/dist/types/hindsight/bank.d.ts +54 -0
- package/dist/types/hindsight/client.d.ts +224 -0
- package/dist/types/hindsight/config.d.ts +51 -0
- package/dist/types/hindsight/content.d.ts +71 -0
- package/dist/types/hindsight/index.d.ts +8 -0
- package/dist/types/hindsight/mental-models.d.ts +125 -0
- package/dist/types/hindsight/state.d.ts +99 -0
- package/dist/types/hindsight/transcript.d.ts +28 -0
- package/dist/types/hooks/codex-native-hooks-config.d.ts +29 -0
- package/dist/types/hooks/native-skill-hook.d.ts +16 -0
- package/dist/types/hooks/skill-keywords.d.ts +18 -0
- package/dist/types/hooks/skill-state.d.ts +88 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +12 -0
- package/dist/types/internal-urls/artifact-protocol.d.ts +6 -0
- package/dist/types/internal-urls/docs-index.generated.d.ts +2 -0
- package/dist/types/internal-urls/index.d.ts +20 -0
- package/dist/types/internal-urls/issue-pr-protocol.d.ts +17 -0
- package/dist/types/internal-urls/json-query.d.ts +30 -0
- package/dist/types/internal-urls/jwc-protocol.d.ts +19 -0
- package/dist/types/internal-urls/local-protocol.d.ts +39 -0
- package/dist/types/internal-urls/mcp-protocol.d.ts +12 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +23 -0
- package/dist/types/internal-urls/parse.d.ts +19 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/internal-urls/router.d.ts +14 -0
- package/dist/types/internal-urls/rule-protocol.d.ts +6 -0
- package/dist/types/internal-urls/skill-protocol.d.ts +13 -0
- package/dist/types/internal-urls/types.d.ts +109 -0
- package/dist/types/jaw-interview/structured-renderer.d.ts +20 -0
- package/dist/types/jwc-runtime/actor-registry.d.ts +76 -0
- package/dist/types/jwc-runtime/agent-identity.d.ts +6 -0
- package/dist/types/jwc-runtime/cli-jaw-home.d.ts +1 -0
- package/dist/types/jwc-runtime/cli-jaw-vocab.d.ts +15 -0
- package/dist/types/jwc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/jwc-runtime/goal-cli.d.ts +7 -0
- package/dist/types/jwc-runtime/goal-engine.d.ts +193 -0
- package/dist/types/jwc-runtime/goal-guard.d.ts +26 -0
- package/dist/types/jwc-runtime/goal-mode-request.d.ts +78 -0
- package/dist/types/jwc-runtime/jaw-interview-runtime.d.ts +41 -0
- package/dist/types/jwc-runtime/launch-tmux.d.ts +70 -0
- package/dist/types/jwc-runtime/launch-worktree.d.ts +46 -0
- package/dist/types/jwc-runtime/legacy-storage.d.ts +19 -0
- package/dist/types/jwc-runtime/memory-runtime.d.ts +16 -0
- package/dist/types/jwc-runtime/orchestrate-runtime.d.ts +12 -0
- package/dist/types/jwc-runtime/orchestrate-state.d.ts +246 -0
- package/dist/types/jwc-runtime/plan-writer.d.ts +26 -0
- package/dist/types/jwc-runtime/restricted-role-agent-bash.d.ts +2 -0
- package/dist/types/jwc-runtime/session-state-sidecar.d.ts +17 -0
- package/dist/types/jwc-runtime/stage-skill-map.d.ts +7 -0
- package/dist/types/jwc-runtime/state-graph.d.ts +4 -0
- package/dist/types/jwc-runtime/state-migrations.d.ts +34 -0
- package/dist/types/jwc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/jwc-runtime/state-runtime.d.ts +37 -0
- package/dist/types/jwc-runtime/state-schema.d.ts +324 -0
- package/dist/types/jwc-runtime/state-validation.d.ts +6 -0
- package/dist/types/jwc-runtime/state-writer.d.ts +147 -0
- package/dist/types/jwc-runtime/team-runtime.d.ts +349 -0
- package/dist/types/jwc-runtime/tmux-common.d.ts +49 -0
- package/dist/types/jwc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/jwc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/jwc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/lsp/client.d.ts +66 -0
- package/dist/types/lsp/clients/biome-client.d.ts +16 -0
- package/dist/types/lsp/clients/index.d.ts +19 -0
- package/dist/types/lsp/clients/lsp-linter-client.d.ts +16 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +20 -0
- package/dist/types/lsp/config.d.ts +65 -0
- package/dist/types/lsp/edits.d.ts +23 -0
- package/dist/types/lsp/index.d.ts +130 -0
- package/dist/types/lsp/lspmux.d.ts +58 -0
- package/dist/types/lsp/render.d.ts +32 -0
- package/dist/types/lsp/startup-events.d.ts +11 -0
- package/dist/types/lsp/types.d.ts +302 -0
- package/dist/types/lsp/utils.d.ts +108 -0
- package/dist/types/main.d.ts +72 -0
- package/dist/types/mem0/backend.d.ts +2 -0
- package/dist/types/mem0/client.d.ts +49 -0
- package/dist/types/mem0/config.d.ts +24 -0
- package/dist/types/mem0/index.d.ts +5 -0
- package/dist/types/mem0/scope.d.ts +12 -0
- package/dist/types/mem0/state.d.ts +36 -0
- package/dist/types/memories/index.d.ts +28 -0
- package/dist/types/memories/local-query.d.ts +112 -0
- package/dist/types/memories/memory-config.d.ts +46 -0
- package/dist/types/memories/memory-fts.d.ts +51 -0
- package/dist/types/memories/memory-model-resolution.d.ts +20 -0
- package/dist/types/memories/memory-quality.d.ts +27 -0
- package/dist/types/memories/storage.d.ts +110 -0
- package/dist/types/memory-backend/index.d.ts +4 -0
- package/dist/types/memory-backend/local-backend.d.ts +9 -0
- package/dist/types/memory-backend/off-backend.d.ts +7 -0
- package/dist/types/memory-backend/resolve.d.ts +16 -0
- package/dist/types/memory-backend/types.d.ts +64 -0
- package/dist/types/migrate-config-dir-startup.d.ts +1 -0
- package/dist/types/migrate-config-dir.d.ts +22 -0
- package/dist/types/modes/acp/acp-agent.d.ts +61 -0
- package/dist/types/modes/acp/acp-client-bridge.d.ts +9 -0
- package/dist/types/modes/acp/acp-event-mapper.d.ts +34 -0
- package/dist/types/modes/acp/acp-mode.d.ts +5 -0
- package/dist/types/modes/acp/index.d.ts +2 -0
- package/dist/types/modes/acp/terminal-auth.d.ts +6 -0
- package/dist/types/modes/background-row-model.d.ts +3 -0
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +46 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/_cli-jaw-entry.d.ts +31 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +21 -0
- package/dist/types/modes/components/assistant-message.d.ts +33 -0
- package/dist/types/modes/components/background-footer-detail.d.ts +6 -0
- package/dist/types/modes/components/background-footer-panel-model.d.ts +18 -0
- package/dist/types/modes/components/background-footer-panel.d.ts +19 -0
- package/dist/types/modes/components/bash-execution.d.ts +37 -0
- package/dist/types/modes/components/bordered-loader.d.ts +11 -0
- package/dist/types/modes/components/branch-summary-message.d.ts +15 -0
- package/dist/types/modes/components/btw-panel.d.ts +16 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -0
- package/dist/types/modes/components/composer-chrome.d.ts +7 -0
- package/dist/types/modes/components/composer-footer.d.ts +43 -0
- package/dist/types/modes/components/countdown-timer.d.ts +14 -0
- package/dist/types/modes/components/custom-editor.d.ts +80 -0
- package/dist/types/modes/components/custom-message.d.ts +17 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
- package/dist/types/modes/components/diff.d.ts +11 -0
- package/dist/types/modes/components/dynamic-border.d.ts +14 -0
- package/dist/types/modes/components/eval-execution.d.ts +31 -0
- package/dist/types/modes/components/execution-shared.d.ts +46 -0
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +26 -0
- package/dist/types/modes/components/extensions/extension-list.d.ts +39 -0
- package/dist/types/modes/components/extensions/index.d.ts +8 -0
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +8 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +50 -0
- package/dist/types/modes/components/extensions/types.d.ts +151 -0
- package/dist/types/modes/components/footer.d.ts +30 -0
- package/dist/types/modes/components/full-transcript-overlay.d.ts +21 -0
- package/dist/types/modes/components/help-selector.d.ts +38 -0
- package/dist/types/modes/components/history-search.d.ts +7 -0
- package/dist/types/modes/components/hook-editor.d.ts +18 -0
- package/dist/types/modes/components/hook-input.d.ts +15 -0
- package/dist/types/modes/components/hook-message.d.ts +15 -0
- package/dist/types/modes/components/hook-selector.d.ts +64 -0
- package/dist/types/modes/components/index.d.ts +35 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/keybinding-hints.d.ts +40 -0
- package/dist/types/modes/components/login-dialog.d.ts +32 -0
- package/dist/types/modes/components/message-frame.d.ts +42 -0
- package/dist/types/modes/components/model-selector.d.ts +53 -0
- package/dist/types/modes/components/oauth-selector.d.ts +14 -0
- package/dist/types/modes/components/pabcd-border.d.ts +8 -0
- package/dist/types/modes/components/plugin-selector.d.ts +26 -0
- package/dist/types/modes/components/plugin-settings.d.ts +66 -0
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +7 -0
- package/dist/types/modes/components/queue-mode-selector.d.ts +9 -0
- package/dist/types/modes/components/quota-panel.d.ts +21 -0
- package/dist/types/modes/components/read-tool-group.d.ts +34 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +25 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +11 -0
- package/dist/types/modes/components/session-selector.d.ts +30 -0
- package/dist/types/modes/components/settings-defs.d.ts +50 -0
- package/dist/types/modes/components/settings-selector.d.ts +56 -0
- package/dist/types/modes/components/show-images-selector.d.ts +9 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +2 -0
- package/dist/types/modes/components/skill-message.d.ts +11 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +22 -0
- package/dist/types/modes/components/status-line/index.d.ts +4 -0
- package/dist/types/modes/components/status-line/presets.d.ts +3 -0
- package/dist/types/modes/components/status-line/segments.d.ts +5 -0
- package/dist/types/modes/components/status-line/separators.d.ts +3 -0
- package/dist/types/modes/components/status-line/token-rate.d.ts +10 -0
- package/dist/types/modes/components/status-line/types.d.ts +92 -0
- package/dist/types/modes/components/status-line/workflow-readers.d.ts +21 -0
- package/dist/types/modes/components/status-line.d.ts +82 -0
- package/dist/types/modes/components/theme-selector.d.ts +10 -0
- package/dist/types/modes/components/thinking-selector.d.ts +10 -0
- package/dist/types/modes/components/todo-reminder.d.ts +13 -0
- package/dist/types/modes/components/tool-execution.d.ts +69 -0
- package/dist/types/modes/components/tool-transcript-overlay.d.ts +18 -0
- package/dist/types/modes/components/tree-selector.d.ts +31 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +15 -0
- package/dist/types/modes/components/user-message-selector.d.ts +28 -0
- package/dist/types/modes/components/user-message.d.ts +8 -0
- package/dist/types/modes/components/visual-truncate.d.ts +19 -0
- package/dist/types/modes/components/welcome.d.ts +42 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +10 -0
- package/dist/types/modes/controllers/command-controller-shared.d.ts +47 -0
- package/dist/types/modes/controllers/command-controller.d.ts +39 -0
- package/dist/types/modes/controllers/event-controller.d.ts +12 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +80 -0
- package/dist/types/modes/controllers/input-controller.d.ts +49 -0
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +10 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +78 -0
- package/dist/types/modes/controllers/ssh-command-controller.d.ts +10 -0
- package/dist/types/modes/controllers/todo-command-controller.d.ts +7 -0
- package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
- package/dist/types/modes/index.d.ts +10 -0
- package/dist/types/modes/interactive-mode.d.ts +295 -0
- package/dist/types/modes/jobs-observer.d.ts +67 -0
- package/dist/types/modes/oauth-manual-input.d.ts +8 -0
- package/dist/types/modes/print-mode.d.ts +27 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +46 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -0
- package/dist/types/modes/rpc/host-uris.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +267 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +67 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +765 -0
- package/dist/types/modes/runtime-init.d.ts +21 -0
- package/dist/types/modes/session-observer-registry.d.ts +26 -0
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +38 -0
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +56 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/jaw-interview-gate.d.ts +58 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/shared.d.ts +15 -0
- package/dist/types/modes/theme/defaults/index.d.ts +499 -0
- package/dist/types/modes/theme/mermaid-cache.d.ts +9 -0
- package/dist/types/modes/theme/shimmer.d.ts +38 -0
- package/dist/types/modes/theme/theme.d.ts +274 -0
- package/dist/types/modes/types.d.ts +311 -0
- package/dist/types/modes/utils/abort-message.d.ts +4 -0
- package/dist/types/modes/utils/compaction-progress.d.ts +20 -0
- package/dist/types/modes/utils/context-usage.d.ts +53 -0
- package/dist/types/modes/utils/help-markdown.d.ts +17 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +5 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +10 -0
- package/dist/types/modes/utils/session-transcript-replay.d.ts +24 -0
- package/dist/types/modes/utils/tools-markdown.d.ts +5 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +77 -0
- package/dist/types/notifications/ask-bridge.d.ts +36 -0
- package/dist/types/notifications/config-command-parser.d.ts +27 -0
- package/dist/types/notifications/config.d.ts +37 -0
- package/dist/types/notifications/daemon-control.d.ts +29 -0
- package/dist/types/notifications/daemon-engine.d.ts +64 -0
- package/dist/types/notifications/daemon-inbound.d.ts +93 -0
- package/dist/types/notifications/daemon-loop.d.ts +28 -0
- package/dist/types/notifications/daemon-owner.d.ts +25 -0
- package/dist/types/notifications/daemon-runtime.d.ts +32 -0
- package/dist/types/notifications/discovery.d.ts +45 -0
- package/dist/types/notifications/index.d.ts +34 -0
- package/dist/types/notifications/lifecycle-audit.d.ts +60 -0
- package/dist/types/notifications/lifecycle-command-parser.d.ts +19 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +45 -0
- package/dist/types/notifications/notification-redaction.d.ts +36 -0
- package/dist/types/notifications/protocol.d.ts +43 -0
- package/dist/types/notifications/remote-answer.d.ts +78 -0
- package/dist/types/notifications/reply-bridge.d.ts +21 -0
- package/dist/types/notifications/server.d.ts +38 -0
- package/dist/types/notifications/session-lifecycle.d.ts +22 -0
- package/dist/types/notifications/session-registry.d.ts +31 -0
- package/dist/types/notifications/telegram-api.d.ts +93 -0
- package/dist/types/notifications/telegram-ask-keyboard.d.ts +57 -0
- package/dist/types/notifications/telegram-callback-ingest.d.ts +49 -0
- package/dist/types/notifications/telegram-inbound-router.d.ts +57 -0
- package/dist/types/notifications/telegram-media-policy.d.ts +27 -0
- package/dist/types/notifications/telegram-media-render.d.ts +29 -0
- package/dist/types/notifications/telegram-message-ingest.d.ts +54 -0
- package/dist/types/notifications/telegram-pairing.d.ts +21 -0
- package/dist/types/notifications/threaded-lifecycle.d.ts +23 -0
- package/dist/types/notifications/threaded-shutdown.d.ts +19 -0
- package/dist/types/notifications/threaded-surface.d.ts +71 -0
- package/dist/types/notifications/transport-shell.d.ts +47 -0
- package/dist/types/notifications/transport-state.d.ts +44 -0
- package/dist/types/notifications/workspace-path-confinement.d.ts +12 -0
- package/dist/types/plan-mode/approved-plan.d.ts +49 -0
- package/dist/types/plan-mode/state.d.ts +6 -0
- package/dist/types/registry/agent-registry.d.ts +62 -0
- package/dist/types/reminders/star-reminder.d.ts +115 -0
- package/dist/types/runtime-mcp/client.d.ts +74 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +53 -0
- package/dist/types/runtime-mcp/config.d.ts +75 -0
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +7 -0
- package/dist/types/runtime-mcp/index.d.ts +18 -0
- package/dist/types/runtime-mcp/json-rpc.d.ts +22 -0
- package/dist/types/runtime-mcp/loader.d.ts +43 -0
- package/dist/types/runtime-mcp/manager.d.ts +193 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +40 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +59 -0
- package/dist/types/runtime-mcp/render.d.ts +25 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +16 -0
- package/dist/types/runtime-mcp/smithery-connect.d.ts +38 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +51 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +86 -0
- package/dist/types/runtime-mcp/tool-cache.d.ts +8 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +36 -0
- package/dist/types/runtime-mcp/transports/index.d.ts +5 -0
- package/dist/types/runtime-mcp/transports/stdio.d.ts +28 -0
- package/dist/types/runtime-mcp/types.d.ts +333 -0
- package/dist/types/sdk.d.ts +234 -0
- package/dist/types/secrets/index.d.ts +10 -0
- package/dist/types/secrets/obfuscator.d.ts +23 -0
- package/dist/types/secrets/regex.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +1052 -0
- package/dist/types/session/agent-storage.d.ts +100 -0
- package/dist/types/session/artifacts.d.ts +61 -0
- package/dist/types/session/auth-broker-config.d.ts +13 -0
- package/dist/types/session/auth-storage.d.ts +6 -0
- package/dist/types/session/blob-store.d.ts +112 -0
- package/dist/types/session/client-bridge.d.ts +88 -0
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/session/history-storage.d.ts +16 -0
- package/dist/types/session/messages.d.ts +165 -0
- package/dist/types/session/pabcd-stage-header.d.ts +18 -0
- package/dist/types/session/session-dump-format.d.ts +22 -0
- package/dist/types/session/session-manager.d.ts +571 -0
- package/dist/types/session/session-storage.d.ts +88 -0
- package/dist/types/session/streaming-output.d.ts +196 -0
- package/dist/types/session/tool-choice-queue.d.ts +78 -0
- package/dist/types/session/yield-queue.d.ts +24 -0
- package/dist/types/setup/hermes-setup.d.ts +71 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +9 -0
- package/dist/types/setup/provider-onboarding.d.ts +52 -0
- package/dist/types/skill-state/active-state.d.ts +115 -0
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/skill-state/jaw-interview-mutation-guard.d.ts +33 -0
- package/dist/types/skill-state/workflow-hud.d.ts +76 -0
- package/dist/types/skill-state/workflow-state-contract.d.ts +60 -0
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +18 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +24 -0
- package/dist/types/slash-commands/helpers/context-report.d.ts +7 -0
- package/dist/types/slash-commands/helpers/format.d.ts +10 -0
- package/dist/types/slash-commands/helpers/mcp.d.ts +3 -0
- package/dist/types/slash-commands/helpers/parse.d.ts +33 -0
- package/dist/types/slash-commands/helpers/ssh.d.ts +3 -0
- package/dist/types/slash-commands/helpers/todo.d.ts +3 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +11 -0
- package/dist/types/slash-commands/types.d.ts +120 -0
- package/dist/types/ssh/config-writer.d.ts +49 -0
- package/dist/types/ssh/connection-manager.d.ts +33 -0
- package/dist/types/ssh/ssh-executor.d.ts +37 -0
- package/dist/types/ssh/sshfs-mount.d.ts +6 -0
- package/dist/types/ssh/utils.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +9 -0
- package/dist/types/stt/index.d.ts +3 -0
- package/dist/types/stt/recorder.d.ts +13 -0
- package/dist/types/stt/setup.d.ts +18 -0
- package/dist/types/stt/stt-controller.d.ts +16 -0
- package/dist/types/stt/transcriber.d.ts +16 -0
- package/dist/types/system-prompt.d.ts +100 -0
- package/dist/types/task/agents.d.ts +28 -0
- package/dist/types/task/commands.d.ts +35 -0
- package/dist/types/task/discovery.d.ts +19 -0
- package/dist/types/task/executor.d.ts +113 -0
- package/dist/types/task/fork-context-advisory.d.ts +13 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +43 -0
- package/dist/types/task/jwc-command.d.ts +7 -0
- package/dist/types/task/model-presets.d.ts +17 -0
- package/dist/types/task/name-generator.d.ts +16 -0
- package/dist/types/task/output-manager.d.ts +36 -0
- package/dist/types/task/parallel.d.ts +34 -0
- package/dist/types/task/receipt.d.ts +86 -0
- package/dist/types/task/render.d.ts +28 -0
- package/dist/types/task/roi-reconciliation.d.ts +27 -0
- package/dist/types/task/simple-mode.d.ts +8 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/subprocess-tool-registry.d.ts +72 -0
- package/dist/types/task/types.d.ts +506 -0
- package/dist/types/task/worktree.d.ts +94 -0
- package/dist/types/thinking.d.ts +31 -0
- package/dist/types/tool-discovery/tool-index.d.ts +113 -0
- package/dist/types/tools/archive-reader.d.ts +40 -0
- package/dist/types/tools/ask.d.ts +152 -0
- package/dist/types/tools/ast-edit.d.ts +77 -0
- package/dist/types/tools/ast-grep.d.ts +69 -0
- package/dist/types/tools/auto-generated-guard.d.ts +17 -0
- package/dist/types/tools/background.d.ts +124 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
- package/dist/types/tools/bash-command-fixup.d.ts +11 -0
- package/dist/types/tools/bash-interactive.d.ts +16 -0
- package/dist/types/tools/bash-interceptor.d.ts +24 -0
- package/dist/types/tools/bash-pty-selection.d.ts +7 -0
- package/dist/types/tools/bash-skill-urls.d.ts +31 -0
- package/dist/types/tools/bash.d.ts +156 -0
- package/dist/types/tools/browser/actions.d.ts +54 -0
- package/dist/types/tools/browser/attach.d.ts +34 -0
- package/dist/types/tools/browser/launch.d.ts +62 -0
- package/dist/types/tools/browser/readable.d.ts +17 -0
- package/dist/types/tools/browser/registry.d.ts +42 -0
- package/dist/types/tools/browser/render.d.ts +50 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +144 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +63 -0
- package/dist/types/tools/browser/tab-worker-entry.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +19 -0
- package/dist/types/tools/browser.d.ts +194 -0
- package/dist/types/tools/calculator.d.ts +76 -0
- package/dist/types/tools/checkpoint.d.ts +63 -0
- package/dist/types/tools/computer-use-backend.d.ts +6 -0
- package/dist/types/tools/computer-use.d.ts +100 -0
- package/dist/types/tools/conflict-detect.d.ts +205 -0
- package/dist/types/tools/context.d.ts +19 -0
- package/dist/types/tools/cron.d.ts +116 -0
- package/dist/types/tools/debug.d.ts +209 -0
- package/dist/types/tools/eval.d.ts +108 -0
- package/dist/types/tools/fetch.d.ts +61 -0
- package/dist/types/tools/file-recorder.d.ts +13 -0
- package/dist/types/tools/find.d.ts +95 -0
- package/dist/types/tools/fs-cache-invalidation.d.ts +15 -0
- package/dist/types/tools/gh-format.d.ts +6 -0
- package/dist/types/tools/gh-renderer.d.ts +25 -0
- package/dist/types/tools/gh.d.ts +356 -0
- package/dist/types/tools/github-cache.d.ts +103 -0
- package/dist/types/tools/grouped-file-output.d.ts +36 -0
- package/dist/types/tools/hindsight-recall.d.ts +21 -0
- package/dist/types/tools/hindsight-reflect.d.ts +23 -0
- package/dist/types/tools/hindsight-retain.d.ts +27 -0
- package/dist/types/tools/image-gen.d.ts +79 -0
- package/dist/types/tools/index.d.ts +281 -0
- package/dist/types/tools/inspect-image-renderer.d.ts +26 -0
- package/dist/types/tools/inspect-image.d.ts +31 -0
- package/dist/types/tools/irc.d.ts +79 -0
- package/dist/types/tools/job.d.ts +65 -0
- package/dist/types/tools/json-tree.d.ts +23 -0
- package/dist/types/tools/jtd-to-json-schema.d.ts +29 -0
- package/dist/types/tools/jtd-to-typescript.d.ts +26 -0
- package/dist/types/tools/jtd-utils.d.ts +42 -0
- package/dist/types/tools/list-limit.d.ts +12 -0
- package/dist/types/tools/match-line-format.d.ts +11 -0
- package/dist/types/tools/monitor.d.ts +65 -0
- package/dist/types/tools/output-meta.d.ts +204 -0
- package/dist/types/tools/path-utils.d.ts +147 -0
- package/dist/types/tools/plan-mode-guard.d.ts +18 -0
- package/dist/types/tools/read.d.ts +85 -0
- package/dist/types/tools/recipe/index.d.ts +45 -0
- package/dist/types/tools/recipe/render.d.ts +36 -0
- package/dist/types/tools/recipe/runner.d.ts +60 -0
- package/dist/types/tools/recipe/runners/cargo.d.ts +16 -0
- package/dist/types/tools/recipe/runners/index.d.ts +2 -0
- package/dist/types/tools/recipe/runners/just.d.ts +2 -0
- package/dist/types/tools/recipe/runners/make.d.ts +2 -0
- package/dist/types/tools/recipe/runners/pkg.d.ts +2 -0
- package/dist/types/tools/recipe/runners/task.d.ts +2 -0
- package/dist/types/tools/render-mermaid.d.ts +37 -0
- package/dist/types/tools/render-utils.d.ts +158 -0
- package/dist/types/tools/renderers.d.ts +26 -0
- package/dist/types/tools/resolve.d.ts +90 -0
- package/dist/types/tools/review.d.ts +63 -0
- package/dist/types/tools/search-tool-bm25.d.ts +65 -0
- package/dist/types/tools/search.d.ts +93 -0
- package/dist/types/tools/skill.d.ts +47 -0
- package/dist/types/tools/sqlite-reader.d.ts +93 -0
- package/dist/types/tools/ssh.d.ts +67 -0
- package/dist/types/tools/subagent-render.d.ts +25 -0
- package/dist/types/tools/subagent.d.ts +87 -0
- package/dist/types/tools/telegram-send.d.ts +53 -0
- package/dist/types/tools/todo-write.d.ts +120 -0
- package/dist/types/tools/tool-errors.d.ts +33 -0
- package/dist/types/tools/tool-result.d.ts +30 -0
- package/dist/types/tools/tool-timeouts.d.ts +51 -0
- package/dist/types/tools/vim.d.ts +58 -0
- package/dist/types/tools/write.d.ts +59 -0
- package/dist/types/tools/yield.d.ts +25 -0
- package/dist/types/tui/code-cell.d.ts +32 -0
- package/dist/types/tui/file-list.d.ts +22 -0
- package/dist/types/tui/hyperlink.d.ts +42 -0
- package/dist/types/tui/index.d.ts +11 -0
- package/dist/types/tui/output-block.d.ts +28 -0
- package/dist/types/tui/status-line.d.ts +18 -0
- package/dist/types/tui/tree-list.d.ts +19 -0
- package/dist/types/tui/types.d.ts +13 -0
- package/dist/types/tui/utils.d.ts +36 -0
- package/dist/types/utils/changelog.d.ts +36 -0
- package/dist/types/utils/clipboard.d.ts +22 -0
- package/dist/types/utils/command-args.d.ts +9 -0
- package/dist/types/utils/commit-message-generator.d.ts +7 -0
- package/dist/types/utils/edit-mode.d.ts +13 -0
- package/dist/types/utils/event-bus.d.ts +6 -0
- package/dist/types/utils/external-editor.d.ts +17 -0
- package/dist/types/utils/file-display-mode.d.ts +27 -0
- package/dist/types/utils/file-mentions.d.ts +11 -0
- package/dist/types/utils/git.d.ts +361 -0
- package/dist/types/utils/image-loading.d.ts +26 -0
- package/dist/types/utils/image-resize.d.ts +39 -0
- package/dist/types/utils/lang-from-path.d.ts +8 -0
- package/dist/types/utils/markit.d.ts +7 -0
- package/dist/types/utils/open.d.ts +2 -0
- package/dist/types/utils/session-color.d.ts +10 -0
- package/dist/types/utils/shell-snapshot.d.ts +5 -0
- package/dist/types/utils/sixel.d.ts +21 -0
- package/dist/types/utils/title-generator.d.ts +31 -0
- package/dist/types/utils/tokenizer-download.d.ts +4 -0
- package/dist/types/utils/tokenizer-encoding.d.ts +7 -0
- package/dist/types/utils/tool-choice.d.ts +20 -0
- package/dist/types/utils/tools-manager.d.ts +9 -0
- package/dist/types/vim/buffer.d.ts +41 -0
- package/dist/types/vim/commands.d.ts +6 -0
- package/dist/types/vim/engine.d.ts +47 -0
- package/dist/types/vim/parser.d.ts +3 -0
- package/dist/types/vim/render.d.ts +25 -0
- package/dist/types/vim/types.d.ts +182 -0
- package/dist/types/web/kagi.d.ts +23 -0
- package/dist/types/web/parallel.d.ts +58 -0
- package/dist/types/web/public-fetch-url.d.ts +1 -0
- package/dist/types/web/scrapers/artifacthub.d.ts +6 -0
- package/dist/types/web/scrapers/arxiv.d.ts +5 -0
- package/dist/types/web/scrapers/aur.d.ts +5 -0
- package/dist/types/web/scrapers/biorxiv.d.ts +5 -0
- package/dist/types/web/scrapers/bluesky.d.ts +5 -0
- package/dist/types/web/scrapers/brew.d.ts +5 -0
- package/dist/types/web/scrapers/cheatsh.d.ts +8 -0
- package/dist/types/web/scrapers/chocolatey.d.ts +5 -0
- package/dist/types/web/scrapers/choosealicense.d.ts +2 -0
- package/dist/types/web/scrapers/cisa-kev.d.ts +5 -0
- package/dist/types/web/scrapers/clojars.d.ts +5 -0
- package/dist/types/web/scrapers/coingecko.d.ts +5 -0
- package/dist/types/web/scrapers/crates-io.d.ts +5 -0
- package/dist/types/web/scrapers/crossref.d.ts +2 -0
- package/dist/types/web/scrapers/devto.d.ts +5 -0
- package/dist/types/web/scrapers/discogs.d.ts +8 -0
- package/dist/types/web/scrapers/discourse.d.ts +5 -0
- package/dist/types/web/scrapers/dockerhub.d.ts +5 -0
- package/dist/types/web/scrapers/docs-rs.d.ts +2 -0
- package/dist/types/web/scrapers/fdroid.d.ts +5 -0
- package/dist/types/web/scrapers/firefox-addons.d.ts +2 -0
- package/dist/types/web/scrapers/flathub.d.ts +2 -0
- package/dist/types/web/scrapers/github-gist.d.ts +5 -0
- package/dist/types/web/scrapers/github.d.ts +12 -0
- package/dist/types/web/scrapers/gitlab.d.ts +5 -0
- package/dist/types/web/scrapers/go-pkg.d.ts +5 -0
- package/dist/types/web/scrapers/hackage.d.ts +5 -0
- package/dist/types/web/scrapers/hackernews.d.ts +2 -0
- package/dist/types/web/scrapers/hex.d.ts +5 -0
- package/dist/types/web/scrapers/huggingface.d.ts +2 -0
- package/dist/types/web/scrapers/iacr.d.ts +5 -0
- package/dist/types/web/scrapers/index.d.ts +84 -0
- package/dist/types/web/scrapers/jetbrains-marketplace.d.ts +2 -0
- package/dist/types/web/scrapers/lemmy.d.ts +2 -0
- package/dist/types/web/scrapers/lobsters.d.ts +5 -0
- package/dist/types/web/scrapers/mastodon.d.ts +5 -0
- package/dist/types/web/scrapers/maven.d.ts +6 -0
- package/dist/types/web/scrapers/mdn.d.ts +2 -0
- package/dist/types/web/scrapers/metacpan.d.ts +5 -0
- package/dist/types/web/scrapers/musicbrainz.d.ts +5 -0
- package/dist/types/web/scrapers/npm.d.ts +5 -0
- package/dist/types/web/scrapers/nuget.d.ts +5 -0
- package/dist/types/web/scrapers/nvd.d.ts +5 -0
- package/dist/types/web/scrapers/ollama.d.ts +2 -0
- package/dist/types/web/scrapers/open-vsx.d.ts +5 -0
- package/dist/types/web/scrapers/opencorporates.d.ts +5 -0
- package/dist/types/web/scrapers/openlibrary.d.ts +5 -0
- package/dist/types/web/scrapers/orcid.d.ts +5 -0
- package/dist/types/web/scrapers/osv.d.ts +5 -0
- package/dist/types/web/scrapers/packagist.d.ts +5 -0
- package/dist/types/web/scrapers/pub-dev.d.ts +5 -0
- package/dist/types/web/scrapers/pubmed.d.ts +5 -0
- package/dist/types/web/scrapers/pypi.d.ts +5 -0
- package/dist/types/web/scrapers/rawg.d.ts +2 -0
- package/dist/types/web/scrapers/readthedocs.d.ts +2 -0
- package/dist/types/web/scrapers/reddit.d.ts +5 -0
- package/dist/types/web/scrapers/repology.d.ts +5 -0
- package/dist/types/web/scrapers/rfc.d.ts +5 -0
- package/dist/types/web/scrapers/rubygems.d.ts +5 -0
- package/dist/types/web/scrapers/searchcode.d.ts +2 -0
- package/dist/types/web/scrapers/sec-edgar.d.ts +5 -0
- package/dist/types/web/scrapers/semantic-scholar.d.ts +2 -0
- package/dist/types/web/scrapers/snapcraft.d.ts +2 -0
- package/dist/types/web/scrapers/sourcegraph.d.ts +2 -0
- package/dist/types/web/scrapers/spdx.d.ts +5 -0
- package/dist/types/web/scrapers/spotify.d.ts +8 -0
- package/dist/types/web/scrapers/stackoverflow.d.ts +6 -0
- package/dist/types/web/scrapers/terraform.d.ts +5 -0
- package/dist/types/web/scrapers/tldr.d.ts +7 -0
- package/dist/types/web/scrapers/twitter.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +78 -0
- package/dist/types/web/scrapers/utils.d.ts +26 -0
- package/dist/types/web/scrapers/vimeo.d.ts +5 -0
- package/dist/types/web/scrapers/vscode-marketplace.d.ts +5 -0
- package/dist/types/web/scrapers/w3c.d.ts +2 -0
- package/dist/types/web/scrapers/wikidata.d.ts +5 -0
- package/dist/types/web/scrapers/wikipedia.d.ts +5 -0
- package/dist/types/web/scrapers/youtube.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +95 -0
- package/dist/types/web/search/provider.d.ts +35 -0
- package/dist/types/web/search/providers/anthropic.d.ts +32 -0
- package/dist/types/web/search/providers/base.d.ts +90 -0
- package/dist/types/web/search/providers/brave.d.ts +27 -0
- package/dist/types/web/search/providers/codex.d.ts +35 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
- package/dist/types/web/search/providers/exa.d.ts +52 -0
- package/dist/types/web/search/providers/gemini.d.ts +59 -0
- package/dist/types/web/search/providers/jina.d.ts +26 -0
- package/dist/types/web/search/providers/kagi.d.ts +24 -0
- package/dist/types/web/search/providers/kimi.d.ts +27 -0
- package/dist/types/web/search/providers/parallel.d.ts +15 -0
- package/dist/types/web/search/providers/perplexity.d.ts +38 -0
- package/dist/types/web/search/providers/searxng.d.ts +44 -0
- package/dist/types/web/search/providers/synthetic.d.ts +21 -0
- package/dist/types/web/search/providers/tavily.d.ts +29 -0
- package/dist/types/web/search/providers/utils.d.ts +52 -0
- package/dist/types/web/search/providers/xai.d.ts +62 -0
- package/dist/types/web/search/providers/zai.d.ts +28 -0
- package/dist/types/web/search/render.d.ts +35 -0
- package/dist/types/web/search/types.d.ts +348 -0
- package/dist/types/web/search/utils.d.ts +4 -0
- package/dist/types/workspace-tree.d.ts +42 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +104 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/extensions/README.md +120 -0
- package/examples/extensions/api-demo.ts +79 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +31 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +549 -0
- package/examples/extensions/reload-runtime.ts +38 -0
- package/examples/extensions/tools.ts +144 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +46 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +82 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/README.md +172 -0
- package/package.json +539 -0
- package/scripts/build-binary.ts +84 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-docs-index.ts +71 -0
- package/scripts/generate-template.ts +33 -0
- package/src/async/index.ts +2 -0
- package/src/async/job-manager.ts +1299 -0
- package/src/async/support.ts +6 -0
- package/src/autoresearch/command-resume.md +14 -0
- package/src/autoresearch/dashboard.ts +446 -0
- package/src/autoresearch/git.ts +319 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +536 -0
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +103 -0
- package/src/autoresearch/resume-message.md +10 -0
- package/src/autoresearch/state.ts +273 -0
- package/src/autoresearch/storage.ts +699 -0
- package/src/autoresearch/tools/init-experiment.ts +272 -0
- package/src/autoresearch/tools/log-experiment.ts +524 -0
- package/src/autoresearch/tools/run-experiment.ts +475 -0
- package/src/autoresearch/tools/update-notes.ts +109 -0
- package/src/autoresearch/types.ts +168 -0
- package/src/bun-imports.d.ts +28 -0
- package/src/capability/context-file.ts +44 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +107 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +436 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +74 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +244 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +56 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +168 -0
- package/src/cli/agents-cli.ts +141 -0
- package/src/cli/args.ts +309 -0
- package/src/cli/auth-broker-cli.ts +747 -0
- package/src/cli/auth-gateway-cli.ts +411 -0
- package/src/cli/classify-install-target.ts +50 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/config-cli.ts +426 -0
- package/src/cli/fast-help.ts +80 -0
- package/src/cli/file-processor.ts +123 -0
- package/src/cli/grep-cli.ts +160 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/list-models.ts +194 -0
- package/src/cli/notify-cli.ts +135 -0
- package/src/cli/plugin-cli.ts +942 -0
- package/src/cli/read-cli.ts +57 -0
- package/src/cli/session-picker.ts +52 -0
- package/src/cli/setup-cli.ts +537 -0
- package/src/cli/shell-cli.ts +176 -0
- package/src/cli/skills-cli.ts +88 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/stats-cli.ts +238 -0
- package/src/cli/update-cli.ts +468 -0
- package/src/cli/web-search-cli.ts +133 -0
- package/src/cli/worktree-cli.ts +291 -0
- package/src/cli.ts +150 -0
- package/src/commands/acp.ts +24 -0
- package/src/commands/agents.ts +57 -0
- package/src/commands/auth-broker.ts +96 -0
- package/src/commands/auth-gateway.ts +63 -0
- package/src/commands/chat.ts +25 -0
- package/src/commands/codex-native-hook.ts +12 -0
- package/src/commands/commit.ts +46 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/coordinator.ts +71 -0
- package/src/commands/goal.ts +37 -0
- package/src/commands/grep.ts +48 -0
- package/src/commands/harness.ts +1137 -0
- package/src/commands/interview.ts +42 -0
- package/src/commands/launch.ts +178 -0
- package/src/commands/mcp-serve.ts +63 -0
- package/src/commands/memory.ts +34 -0
- package/src/commands/notify.ts +45 -0
- package/src/commands/orchestrate.ts +55 -0
- package/src/commands/planphase.ts +19 -0
- package/src/commands/plugin.ts +78 -0
- package/src/commands/ralplan.ts +25 -0
- package/src/commands/read.ts +35 -0
- package/src/commands/session.ts +151 -0
- package/src/commands/setup.ts +85 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/skills.ts +49 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/state.ts +25 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/team.ts +217 -0
- package/src/commands/ultragoal.ts +40 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +42 -0
- package/src/commands/worktree.ts +57 -0
- package/src/commit/agentic/agent.ts +316 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +356 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +127 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +81 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +144 -0
- package/src/commit/agentic/tools/propose-commit.ts +109 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +23 -0
- package/src/commit/agentic/tools/split-commit.ts +238 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +64 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +105 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +97 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +49 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +51 -0
- package/src/commit/pipeline.ts +244 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/shared-llm.ts +77 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +58 -0
- package/src/config/config-file.ts +232 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +553 -0
- package/src/config/mcp-schema.json +230 -0
- package/src/config/model-equivalence.ts +816 -0
- package/src/config/model-profile-activation.ts +165 -0
- package/src/config/model-profiles.ts +188 -0
- package/src/config/model-registry.ts +2751 -0
- package/src/config/model-resolver.ts +1355 -0
- package/src/config/models-config-schema.ts +254 -0
- package/src/config/oauth-provider-aliases.ts +49 -0
- package/src/config/prompt-templates.ts +310 -0
- package/src/config/resolve-config-value.ts +94 -0
- package/src/config/settings-schema.ts +3235 -0
- package/src/config/settings.ts +979 -0
- package/src/config/skill-settings-defaults.ts +35 -0
- package/src/config.ts +212 -0
- package/src/coordinator/contract.ts +20 -0
- package/src/coordinator-mcp/policy.ts +174 -0
- package/src/coordinator-mcp/safety.ts +80 -0
- package/src/coordinator-mcp/server.ts +1319 -0
- package/src/cursor.ts +372 -0
- package/src/dap/client.ts +689 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1306 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/index.ts +459 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +908 -0
- package/src/debug/profiler.ts +162 -0
- package/src/debug/raw-sse-buffer.ts +270 -0
- package/src/debug/raw-sse.ts +213 -0
- package/src/debug/report-bundle.ts +365 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/debug/system-info.ts +111 -0
- package/src/defaults/jwc/mcp-defaults.json +8 -0
- package/src/defaults/jwc/skill-fragments/browse/web-ai.md +130 -0
- package/src/defaults/jwc/skills/browse/SKILL.md +113 -0
- package/src/defaults/jwc/skills/goal/SKILL.md +323 -0
- package/src/defaults/jwc/skills/goal/ai-slop-cleaner.md +61 -0
- package/src/defaults/jwc/skills/jaw-interview/SKILL.md +891 -0
- package/src/defaults/jwc/skills/jaw-interview/auto-answer-uncertain.md +37 -0
- package/src/defaults/jwc/skills/jaw-interview/auto-research-greenfield.md +42 -0
- package/src/defaults/jwc/skills/plan/SKILL.md +201 -0
- package/src/defaults/jwc/skills/search/SKILL.md +120 -0
- package/src/defaults/jwc/skills/team/SKILL.md +441 -0
- package/src/defaults/jwc-defaults.ts +316 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +230 -0
- package/src/discovery/builtin.ts +932 -0
- package/src/discovery/claude-plugins.ts +407 -0
- package/src/discovery/claude.ts +313 -0
- package/src/discovery/cli-jaw.ts +36 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +330 -0
- package/src/discovery/cursor.ts +220 -0
- package/src/discovery/gemini.ts +383 -0
- package/src/discovery/github.ts +118 -0
- package/src/discovery/helpers.ts +1010 -0
- package/src/discovery/index.ts +77 -0
- package/src/discovery/mcp-json.ts +171 -0
- package/src/discovery/opencode.ts +398 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/discovery/vscode.ts +105 -0
- package/src/discovery/windsurf.ts +147 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +873 -0
- package/src/edit/file-read-cache.ts +95 -0
- package/src/edit/index.ts +528 -0
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +53 -0
- package/src/edit/modes/patch.ts +1835 -0
- package/src/edit/modes/replace.ts +1161 -0
- package/src/edit/normalize.ts +375 -0
- package/src/edit/notebook.ts +222 -0
- package/src/edit/read-file.ts +25 -0
- package/src/edit/renderer.ts +683 -0
- package/src/edit/streaming.ts +561 -0
- package/src/eval/backend.ts +43 -0
- package/src/eval/index.ts +4 -0
- package/src/eval/js/context-manager.ts +414 -0
- package/src/eval/js/executor.ts +134 -0
- package/src/eval/js/index.ts +46 -0
- package/src/eval/js/shared/helpers.ts +237 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +70 -0
- package/src/eval/js/shared/rewrite-imports.ts +415 -0
- package/src/eval/js/shared/runtime.ts +301 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +144 -0
- package/src/eval/js/worker-core.ts +146 -0
- package/src/eval/js/worker-entry.ts +24 -0
- package/src/eval/js/worker-protocol.ts +41 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +622 -0
- package/src/eval/py/index.ts +58 -0
- package/src/eval/py/kernel.ts +683 -0
- package/src/eval/py/prelude.py +462 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +910 -0
- package/src/eval/py/runtime.ts +210 -0
- package/src/eval/py/tool-bridge.ts +137 -0
- package/src/eval/types.ts +48 -0
- package/src/exa/factory.ts +60 -0
- package/src/exa/index.ts +27 -0
- package/src/exa/mcp-client.ts +364 -0
- package/src/exa/render.ts +244 -0
- package/src/exa/researcher.ts +36 -0
- package/src/exa/search.ts +47 -0
- package/src/exa/types.ts +166 -0
- package/src/exa/websets.ts +248 -0
- package/src/exec/bash-executor.ts +362 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +73 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +168 -0
- package/src/export/html/template.css +1060 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +47 -0
- package/src/export/html/template.js +2268 -0
- package/src/export/html/template.macro.ts +25 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/ttsr.ts +444 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +238 -0
- package/src/extensibility/custom-commands/types.ts +113 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +245 -0
- package/src/extensibility/custom-tools/types.ts +255 -0
- package/src/extensibility/custom-tools/wrapper.ts +47 -0
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/get-commands-handler.ts +80 -0
- package/src/extensibility/extensions/index.ts +15 -0
- package/src/extensibility/extensions/loader.ts +555 -0
- package/src/extensibility/extensions/runner.ts +900 -0
- package/src/extensibility/extensions/types.ts +1314 -0
- package/src/extensibility/extensions/wrapper.ts +189 -0
- package/src/extensibility/hooks/index.ts +5 -0
- package/src/extensibility/hooks/loader.ts +257 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +107 -0
- package/src/extensibility/hooks/types.ts +599 -0
- package/src/extensibility/jwc-plugins/activation.ts +87 -0
- package/src/extensibility/jwc-plugins/index.ts +9 -0
- package/src/extensibility/jwc-plugins/injection.ts +114 -0
- package/src/extensibility/jwc-plugins/loader.ts +131 -0
- package/src/extensibility/jwc-plugins/paths.ts +66 -0
- package/src/extensibility/jwc-plugins/schema.ts +79 -0
- package/src/extensibility/jwc-plugins/state.ts +29 -0
- package/src/extensibility/jwc-plugins/tools.ts +47 -0
- package/src/extensibility/jwc-plugins/types.ts +97 -0
- package/src/extensibility/jwc-plugins/validation.ts +76 -0
- package/src/extensibility/plugins/doctor.ts +66 -0
- package/src/extensibility/plugins/git-url.ts +281 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +336 -0
- package/src/extensibility/plugins/loader.ts +288 -0
- package/src/extensibility/plugins/manager.ts +739 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +770 -0
- package/src/extensibility/plugins/marketplace/registry.ts +196 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +191 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/shared-events.ts +344 -0
- package/src/extensibility/skills.ts +449 -0
- package/src/extensibility/slash-commands.ts +249 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/extensibility/typebox.ts +418 -0
- package/src/extensibility/utils.ts +44 -0
- package/src/goals/goal-planning-start.ts +21 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +418 -0
- package/src/goals/state.ts +70 -0
- package/src/goals/tools/goal-tool.ts +230 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -0
- package/src/harness-control-plane/finalize.ts +306 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +664 -0
- package/src/harness-control-plane/phase-rollup.ts +96 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipt-ingest.ts +127 -0
- package/src/harness-control-plane/receipt-spool.ts +147 -0
- package/src/harness-control-plane/receipts.ts +320 -0
- package/src/harness-control-plane/rpc-adapter.ts +282 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +119 -0
- package/src/harness-control-plane/storage.ts +423 -0
- package/src/harness-control-plane/types.ts +240 -0
- package/src/hashline/anchors.ts +113 -0
- package/src/hashline/apply.ts +737 -0
- package/src/hashline/bigrams.json +649 -0
- package/src/hashline/constants.ts +25 -0
- package/src/hashline/diff-preview.ts +43 -0
- package/src/hashline/diff.ts +56 -0
- package/src/hashline/execute.ts +267 -0
- package/src/hashline/grammar.lark +21 -0
- package/src/hashline/hash.ts +196 -0
- package/src/hashline/index.ts +13 -0
- package/src/hashline/input.ts +130 -0
- package/src/hashline/parser.ts +246 -0
- package/src/hashline/prefixes.ts +101 -0
- package/src/hashline/recovery.ts +113 -0
- package/src/hashline/stream.ts +123 -0
- package/src/hashline/types.ts +68 -0
- package/src/hindsight/backend.ts +205 -0
- package/src/hindsight/bank.ts +131 -0
- package/src/hindsight/client.ts +598 -0
- package/src/hindsight/config.ts +175 -0
- package/src/hindsight/content.ts +211 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +382 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +469 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/hooks/codex-native-hooks-config.ts +143 -0
- package/src/hooks/native-skill-hook.ts +305 -0
- package/src/hooks/skill-keywords.ts +95 -0
- package/src/hooks/skill-state.ts +654 -0
- package/src/index.ts +56 -0
- package/src/internal-urls/agent-protocol.ts +176 -0
- package/src/internal-urls/artifact-protocol.ts +75 -0
- package/src/internal-urls/docs-index.generated.ts +92 -0
- package/src/internal-urls/index.ts +21 -0
- package/src/internal-urls/issue-pr-protocol.ts +577 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/jwc-protocol.ts +91 -0
- package/src/internal-urls/local-protocol.ts +249 -0
- package/src/internal-urls/mcp-protocol.ts +151 -0
- package/src/internal-urls/memory-protocol.ts +165 -0
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/registry-helpers.ts +28 -0
- package/src/internal-urls/router.ts +79 -0
- package/src/internal-urls/rule-protocol.ts +38 -0
- package/src/internal-urls/skill-protocol.ts +103 -0
- package/src/internal-urls/types.ts +114 -0
- package/src/jaw-interview/structured-renderer.ts +83 -0
- package/src/jwc-runtime/actor-registry.ts +365 -0
- package/src/jwc-runtime/agent-identity.ts +16 -0
- package/src/jwc-runtime/cli-jaw-home.ts +14 -0
- package/src/jwc-runtime/cli-jaw-vocab.ts +69 -0
- package/src/jwc-runtime/cli-write-receipt.ts +31 -0
- package/src/jwc-runtime/goal-cli.ts +428 -0
- package/src/jwc-runtime/goal-engine.ts +2214 -0
- package/src/jwc-runtime/goal-guard.ts +300 -0
- package/src/jwc-runtime/goal-mode-request.ts +331 -0
- package/src/jwc-runtime/jaw-interview-runtime.ts +809 -0
- package/src/jwc-runtime/launch-tmux.ts +296 -0
- package/src/jwc-runtime/launch-worktree.ts +291 -0
- package/src/jwc-runtime/legacy-storage.ts +132 -0
- package/src/jwc-runtime/memory-runtime.ts +434 -0
- package/src/jwc-runtime/orchestrate-runtime.ts +612 -0
- package/src/jwc-runtime/orchestrate-state.ts +395 -0
- package/src/jwc-runtime/plan-writer.ts +661 -0
- package/src/jwc-runtime/restricted-role-agent-bash.ts +5 -0
- package/src/jwc-runtime/session-state-sidecar.ts +89 -0
- package/src/jwc-runtime/stage-skill-map.ts +48 -0
- package/src/jwc-runtime/state-graph.ts +86 -0
- package/src/jwc-runtime/state-migrations.ts +183 -0
- package/src/jwc-runtime/state-renderer.ts +340 -0
- package/src/jwc-runtime/state-runtime.ts +1978 -0
- package/src/jwc-runtime/state-schema.ts +218 -0
- package/src/jwc-runtime/state-validation.ts +50 -0
- package/src/jwc-runtime/state-writer.ts +765 -0
- package/src/jwc-runtime/team-runtime.ts +4181 -0
- package/src/jwc-runtime/tmux-common.ts +130 -0
- package/src/jwc-runtime/tmux-sessions.ts +169 -0
- package/src/jwc-runtime/workflow-command-ref.ts +240 -0
- package/src/jwc-runtime/workflow-manifest.generated.json +1606 -0
- package/src/jwc-runtime/workflow-manifest.ts +427 -0
- package/src/lsp/client.ts +974 -0
- package/src/lsp/clients/biome-client.ts +202 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +93 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +492 -0
- package/src/lsp/defaults.json +493 -0
- package/src/lsp/edits.ts +166 -0
- package/src/lsp/index.ts +2282 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/render.ts +692 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +443 -0
- package/src/lsp/utils.ts +678 -0
- package/src/main.ts +1159 -0
- package/src/mem0/backend.ts +75 -0
- package/src/mem0/client.ts +122 -0
- package/src/mem0/config.ts +82 -0
- package/src/mem0/index.ts +5 -0
- package/src/mem0/scope.ts +54 -0
- package/src/mem0/state.ts +142 -0
- package/src/memories/index.ts +1111 -0
- package/src/memories/local-query.ts +699 -0
- package/src/memories/memory-config.ts +175 -0
- package/src/memories/memory-fts.ts +179 -0
- package/src/memories/memory-model-resolution.ts +84 -0
- package/src/memories/memory-quality.ts +163 -0
- package/src/memories/storage.ts +588 -0
- package/src/memory-backend/index.ts +4 -0
- package/src/memory-backend/local-backend.ts +50 -0
- package/src/memory-backend/off-backend.ts +16 -0
- package/src/memory-backend/resolve.ts +27 -0
- package/src/memory-backend/types.ts +82 -0
- package/src/migrate-config-dir-startup.ts +15 -0
- package/src/migrate-config-dir.ts +80 -0
- package/src/modes/acp/acp-agent.ts +2218 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +912 -0
- package/src/modes/acp/acp-mode.ts +23 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/background-row-model.ts +17 -0
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +644 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/_cli-jaw-entry.ts +36 -0
- package/src/modes/components/agent-dashboard.ts +1120 -0
- package/src/modes/components/assistant-message.ts +361 -0
- package/src/modes/components/background-footer-detail.ts +27 -0
- package/src/modes/components/background-footer-panel-model.ts +96 -0
- package/src/modes/components/background-footer-panel.ts +89 -0
- package/src/modes/components/bash-execution.ts +261 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +65 -0
- package/src/modes/components/btw-panel.ts +104 -0
- package/src/modes/components/compaction-summary-message.ts +71 -0
- package/src/modes/components/composer-chrome.ts +42 -0
- package/src/modes/components/composer-footer.ts +113 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +515 -0
- package/src/modes/components/custom-message.ts +85 -0
- package/src/modes/components/custom-provider-wizard.ts +318 -0
- package/src/modes/components/diff.ts +363 -0
- package/src/modes/components/dynamic-border.ts +25 -0
- package/src/modes/components/eval-execution.ts +196 -0
- package/src/modes/components/execution-shared.ts +112 -0
- package/src/modes/components/extensions/extension-dashboard.ts +350 -0
- package/src/modes/components/extensions/extension-list.ts +492 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +317 -0
- package/src/modes/components/extensions/state-manager.ts +628 -0
- package/src/modes/components/extensions/types.ts +191 -0
- package/src/modes/components/footer.ts +270 -0
- package/src/modes/components/full-transcript-overlay.ts +133 -0
- package/src/modes/components/help-selector.ts +274 -0
- package/src/modes/components/history-search.ts +158 -0
- package/src/modes/components/hook-editor.ts +151 -0
- package/src/modes/components/hook-input.ts +79 -0
- package/src/modes/components/hook-message.ts +68 -0
- package/src/modes/components/hook-selector.ts +873 -0
- package/src/modes/components/index.ts +36 -0
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1653 -0
- package/src/modes/components/oauth-selector.ts +248 -0
- package/src/modes/components/pabcd-border.ts +98 -0
- package/src/modes/components/plugin-selector.ts +95 -0
- package/src/modes/components/plugin-settings.ts +489 -0
- package/src/modes/components/provider-onboarding-selector.ts +94 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/quota-panel.ts +101 -0
- package/src/modes/components/read-tool-group.ts +295 -0
- package/src/modes/components/runtime-mcp-add-wizard.ts +1341 -0
- package/src/modes/components/session-observer-overlay.ts +839 -0
- package/src/modes/components/session-selector.ts +343 -0
- package/src/modes/components/settings-defs.ts +175 -0
- package/src/modes/components/settings-selector.ts +644 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-hud/render.ts +93 -0
- package/src/modes/components/skill-message.ts +110 -0
- package/src/modes/components/status-line/context-thresholds.ts +68 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/index.ts +4 -0
- package/src/modes/components/status-line/presets.ts +107 -0
- package/src/modes/components/status-line/segments.ts +657 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/token-rate.ts +66 -0
- package/src/modes/components/status-line/types.ts +102 -0
- package/src/modes/components/status-line/workflow-readers.ts +97 -0
- package/src/modes/components/status-line.ts +917 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +52 -0
- package/src/modes/components/todo-reminder.ts +40 -0
- package/src/modes/components/tool-execution.ts +990 -0
- package/src/modes/components/tool-transcript-overlay.ts +81 -0
- package/src/modes/components/tree-selector.ts +930 -0
- package/src/modes/components/ttsr-notification.ts +100 -0
- package/src/modes/components/user-message-selector.ts +141 -0
- package/src/modes/components/user-message.ts +53 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +522 -0
- package/src/modes/controllers/btw-controller.ts +105 -0
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +1703 -0
- package/src/modes/controllers/event-controller.ts +1054 -0
- package/src/modes/controllers/extension-ui-controller.ts +1010 -0
- package/src/modes/controllers/input-controller.ts +1422 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +1934 -0
- package/src/modes/controllers/selector-controller.ts +1605 -0
- package/src/modes/controllers/ssh-command-controller.ts +384 -0
- package/src/modes/controllers/todo-command-controller.ts +485 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/index.ts +35 -0
- package/src/modes/interactive-mode.ts +2938 -0
- package/src/modes/jobs-observer.ts +352 -0
- package/src/modes/oauth-manual-input.ts +42 -0
- package/src/modes/print-mode.ts +121 -0
- package/src/modes/prompt-action-autocomplete.ts +409 -0
- package/src/modes/rpc/host-tools.ts +1 -0
- package/src/modes/rpc/host-uris.ts +1 -0
- package/src/modes/rpc/rpc-client.ts +914 -0
- package/src/modes/rpc/rpc-mode.ts +784 -0
- package/src/modes/rpc/rpc-types.ts +601 -0
- package/src/modes/runtime-init.ts +116 -0
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
- package/src/modes/shared/agent-wire/command-contract.ts +18 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +438 -0
- package/src/modes/shared/agent-wire/command-validation.ts +164 -0
- package/src/modes/shared/agent-wire/event-contract.ts +148 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +141 -0
- package/src/modes/shared/agent-wire/event-observation.ts +424 -0
- package/src/modes/shared/agent-wire/handshake.ts +157 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/jaw-interview-gate.ts +253 -0
- package/src/modes/shared/agent-wire/protocol.ts +46 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +91 -0
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
- package/src/modes/shared.ts +55 -0
- package/src/modes/theme/defaults/abyss-bite-light.json +124 -0
- package/src/modes/theme/defaults/abyss-bite.json +124 -0
- package/src/modes/theme/defaults/blue-crab.json +126 -0
- package/src/modes/theme/defaults/index.ts +11 -0
- package/src/modes/theme/defaults/red-claw.json +123 -0
- package/src/modes/theme/mermaid-cache.ts +29 -0
- package/src/modes/theme/shimmer.ts +219 -0
- package/src/modes/theme/theme-schema.json +429 -0
- package/src/modes/theme/theme.ts +2472 -0
- package/src/modes/types.ts +343 -0
- package/src/modes/utils/abort-message.ts +41 -0
- package/src/modes/utils/compaction-progress.ts +174 -0
- package/src/modes/utils/context-usage.ts +398 -0
- package/src/modes/utils/help-markdown.ts +50 -0
- package/src/modes/utils/hotkeys-markdown.ts +61 -0
- package/src/modes/utils/keybinding-matchers.ts +30 -0
- package/src/modes/utils/session-transcript-replay.ts +520 -0
- package/src/modes/utils/tools-markdown.ts +27 -0
- package/src/modes/utils/ui-helpers.ts +846 -0
- package/src/notifications/ask-bridge.ts +74 -0
- package/src/notifications/config-command-parser.ts +76 -0
- package/src/notifications/config.ts +102 -0
- package/src/notifications/daemon-control.ts +73 -0
- package/src/notifications/daemon-engine.ts +165 -0
- package/src/notifications/daemon-inbound.ts +220 -0
- package/src/notifications/daemon-loop.ts +60 -0
- package/src/notifications/daemon-owner.ts +53 -0
- package/src/notifications/daemon-runtime.ts +84 -0
- package/src/notifications/discovery.ts +119 -0
- package/src/notifications/index.ts +34 -0
- package/src/notifications/lifecycle-audit.ts +94 -0
- package/src/notifications/lifecycle-command-parser.ts +51 -0
- package/src/notifications/lifecycle-control-runtime.ts +127 -0
- package/src/notifications/notification-redaction.ts +57 -0
- package/src/notifications/protocol.ts +61 -0
- package/src/notifications/remote-answer.ts +241 -0
- package/src/notifications/reply-bridge.ts +85 -0
- package/src/notifications/server.ts +280 -0
- package/src/notifications/session-lifecycle.ts +46 -0
- package/src/notifications/session-registry.ts +164 -0
- package/src/notifications/telegram-api.ts +259 -0
- package/src/notifications/telegram-ask-keyboard.ts +137 -0
- package/src/notifications/telegram-callback-ingest.ts +126 -0
- package/src/notifications/telegram-inbound-router.ts +166 -0
- package/src/notifications/telegram-media-policy.ts +50 -0
- package/src/notifications/telegram-media-render.ts +40 -0
- package/src/notifications/telegram-message-ingest.ts +121 -0
- package/src/notifications/telegram-pairing.ts +69 -0
- package/src/notifications/threaded-lifecycle.ts +27 -0
- package/src/notifications/threaded-shutdown.ts +37 -0
- package/src/notifications/threaded-surface.ts +187 -0
- package/src/notifications/transport-shell.ts +152 -0
- package/src/notifications/transport-state.ts +161 -0
- package/src/notifications/workspace-path-confinement.ts +65 -0
- package/src/plan-mode/approved-plan.ts +163 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/prompts/agents/architect.md +95 -0
- package/src/prompts/agents/critic.md +68 -0
- package/src/prompts/agents/executor.md +64 -0
- package/src/prompts/agents/explore.md +58 -0
- package/src/prompts/agents/frontmatter.md +14 -0
- package/src/prompts/agents/init.md +34 -0
- package/src/prompts/agents/plan.md +49 -0
- package/src/prompts/agents/planner.md +63 -0
- package/src/prompts/agents/reviewer.md +141 -0
- package/src/prompts/agents/task.md +16 -0
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/goals/goal-continuation.md +33 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/jaw/orchestrate-a.md +20 -0
- package/src/prompts/jaw/orchestrate-audit-architect.md +17 -0
- package/src/prompts/jaw/orchestrate-audit-planner.md +17 -0
- package/src/prompts/jaw/orchestrate-b.md +17 -0
- package/src/prompts/jaw/orchestrate-c.md +18 -0
- package/src/prompts/jaw/orchestrate-d.md +32 -0
- package/src/prompts/jaw/orchestrate-i.md +17 -0
- package/src/prompts/jaw/orchestrate-p.md +23 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/read-path.md +10 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/memories/unavailable.md +9 -0
- package/src/prompts/review-request.md +70 -0
- package/src/prompts/system/agent-creation-architect.md +75 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +2 -0
- package/src/prompts/system/custom-system-prompt.md +64 -0
- package/src/prompts/system/eager-todo.md +13 -0
- package/src/prompts/system/executor-self-fork.md +3 -0
- package/src/prompts/system/irc-incoming.md +8 -0
- package/src/prompts/system/plan-mode-active.md +116 -0
- package/src/prompts/system/plan-mode-approved.md +28 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +14 -0
- package/src/prompts/system/plan-mode-subagent.md +34 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/project-prompt.md +46 -0
- package/src/prompts/system/subagent-system-prompt.md +89 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +12 -0
- package/src/prompts/system/system-prompt.md +409 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/system/web-search.md +25 -0
- package/src/prompts/tools/apply-patch.md +65 -0
- package/src/prompts/tools/ask.md +29 -0
- package/src/prompts/tools/ast-edit.md +39 -0
- package/src/prompts/tools/ast-grep.md +42 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/background.md +19 -0
- package/src/prompts/tools/bash.md +50 -0
- package/src/prompts/tools/browser.md +14 -0
- package/src/prompts/tools/calculator.md +10 -0
- package/src/prompts/tools/checkpoint.md +16 -0
- package/src/prompts/tools/computer-use.md +15 -0
- package/src/prompts/tools/cron.md +25 -0
- package/src/prompts/tools/debug.md +33 -0
- package/src/prompts/tools/eval.md +75 -0
- package/src/prompts/tools/find.md +34 -0
- package/src/prompts/tools/github.md +20 -0
- package/src/prompts/tools/goal.md +20 -0
- package/src/prompts/tools/hashline.md +130 -0
- package/src/prompts/tools/image-gen.md +7 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +32 -0
- package/src/prompts/tools/irc.md +49 -0
- package/src/prompts/tools/job.md +22 -0
- package/src/prompts/tools/lsp.md +42 -0
- package/src/prompts/tools/monitor.md +32 -0
- package/src/prompts/tools/patch.md +70 -0
- package/src/prompts/tools/read.md +82 -0
- package/src/prompts/tools/recall.md +6 -0
- package/src/prompts/tools/recipe.md +16 -0
- package/src/prompts/tools/reflect.md +6 -0
- package/src/prompts/tools/render-mermaid.md +9 -0
- package/src/prompts/tools/replace.md +36 -0
- package/src/prompts/tools/resolve.md +9 -0
- package/src/prompts/tools/retain.md +7 -0
- package/src/prompts/tools/rewind.md +13 -0
- package/src/prompts/tools/search-tool-bm25.md +37 -0
- package/src/prompts/tools/search.md +25 -0
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/ssh.md +35 -0
- package/src/prompts/tools/subagent.md +56 -0
- package/src/prompts/tools/task-summary.md +22 -0
- package/src/prompts/tools/task.md +106 -0
- package/src/prompts/tools/todo-write.md +50 -0
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-registry.ts +140 -0
- package/src/reminders/star-reminder.ts +422 -0
- package/src/runtime-mcp/client.ts +485 -0
- package/src/runtime-mcp/config-writer.ts +225 -0
- package/src/runtime-mcp/config.ts +365 -0
- package/src/runtime-mcp/discoverable-tool-metadata.ts +25 -0
- package/src/runtime-mcp/index.ts +29 -0
- package/src/runtime-mcp/json-rpc.ts +84 -0
- package/src/runtime-mcp/loader.ts +124 -0
- package/src/runtime-mcp/manager.ts +1211 -0
- package/src/runtime-mcp/oauth-discovery.ts +349 -0
- package/src/runtime-mcp/oauth-flow.ts +409 -0
- package/src/runtime-mcp/render.ts +123 -0
- package/src/runtime-mcp/smithery-auth.ts +104 -0
- package/src/runtime-mcp/smithery-connect.ts +145 -0
- package/src/runtime-mcp/smithery-registry.ts +477 -0
- package/src/runtime-mcp/tool-bridge.ts +416 -0
- package/src/runtime-mcp/tool-cache.ts +117 -0
- package/src/runtime-mcp/transports/http.ts +503 -0
- package/src/runtime-mcp/transports/index.ts +6 -0
- package/src/runtime-mcp/transports/stdio.ts +326 -0
- package/src/runtime-mcp/types.ts +423 -0
- package/src/sdk.ts +2248 -0
- package/src/secrets/index.ts +120 -0
- package/src/secrets/obfuscator.ts +352 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +10343 -0
- package/src/session/agent-storage.ts +466 -0
- package/src/session/artifacts.ts +135 -0
- package/src/session/auth-broker-config.ts +102 -0
- package/src/session/auth-storage.ts +23 -0
- package/src/session/blob-store.ts +372 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/contribution-prep.ts +320 -0
- package/src/session/history-storage.ts +311 -0
- package/src/session/messages.ts +406 -0
- package/src/session/pabcd-stage-header.ts +77 -0
- package/src/session/session-dump-format.ts +209 -0
- package/src/session/session-manager.ts +3914 -0
- package/src/session/session-storage.ts +415 -0
- package/src/session/streaming-output.ts +1114 -0
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/session/yield-queue.ts +155 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
- package/src/setup/hermes-setup.ts +429 -0
- package/src/setup/model-onboarding-guidance.ts +41 -0
- package/src/setup/provider-onboarding.ts +358 -0
- package/src/skill-state/active-state.ts +788 -0
- package/src/skill-state/initial-phase.ts +21 -0
- package/src/skill-state/jaw-interview-mutation-guard.ts +490 -0
- package/src/skill-state/workflow-hud.ts +208 -0
- package/src/skill-state/workflow-state-contract.ts +167 -0
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/acp-builtins.ts +55 -0
- package/src/slash-commands/builtin-registry.ts +1879 -0
- package/src/slash-commands/helpers/context-report.ts +149 -0
- package/src/slash-commands/helpers/format.ts +46 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +195 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +105 -0
- package/src/slash-commands/types.ts +127 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +482 -0
- package/src/ssh/ssh-executor.ts +133 -0
- package/src/ssh/sshfs-mount.ts +140 -0
- package/src/ssh/utils.ts +8 -0
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/system-prompt.ts +661 -0
- package/src/task/agents.ts +189 -0
- package/src/task/commands.ts +131 -0
- package/src/task/discovery.ts +136 -0
- package/src/task/executor.ts +1664 -0
- package/src/task/fork-context-advisory.ts +99 -0
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +2173 -0
- package/src/task/jwc-command.ts +26 -0
- package/src/task/model-presets.ts +53 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/output-manager.ts +108 -0
- package/src/task/parallel.ts +116 -0
- package/src/task/receipt.ts +304 -0
- package/src/task/render.ts +1075 -0
- package/src/task/roi-reconciliation.ts +90 -0
- package/src/task/simple-mode.ts +27 -0
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +433 -0
- package/src/task/worktree.ts +506 -0
- package/src/thinking.ts +93 -0
- package/src/tool-discovery/tool-index.ts +408 -0
- package/src/tools/archive-reader.ts +321 -0
- package/src/tools/ask.ts +1011 -0
- package/src/tools/ast-edit.ts +555 -0
- package/src/tools/ast-grep.ts +424 -0
- package/src/tools/auto-generated-guard.ts +305 -0
- package/src/tools/background.ts +500 -0
- package/src/tools/bash-allowed-prefixes.ts +169 -0
- package/src/tools/bash-command-fixup.ts +37 -0
- package/src/tools/bash-interactive.ts +388 -0
- package/src/tools/bash-interceptor.ts +67 -0
- package/src/tools/bash-pty-selection.ts +15 -0
- package/src/tools/bash-skill-urls.ts +248 -0
- package/src/tools/bash.ts +1242 -0
- package/src/tools/browser/actions.ts +189 -0
- package/src/tools/browser/attach.ts +175 -0
- package/src/tools/browser/launch.ts +651 -0
- package/src/tools/browser/readable.ts +95 -0
- package/src/tools/browser/registry.ts +194 -0
- package/src/tools/browser/render.ts +212 -0
- package/src/tools/browser/tab-protocol.ts +105 -0
- package/src/tools/browser/tab-supervisor.ts +577 -0
- package/src/tools/browser/tab-worker-entry.ts +21 -0
- package/src/tools/browser/tab-worker.ts +1054 -0
- package/src/tools/browser.ts +380 -0
- package/src/tools/calculator.ts +540 -0
- package/src/tools/checkpoint.ts +134 -0
- package/src/tools/computer-use-backend.ts +75 -0
- package/src/tools/computer-use.ts +228 -0
- package/src/tools/conflict-detect.ts +672 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/cron.ts +713 -0
- package/src/tools/debug.ts +1014 -0
- package/src/tools/eval.ts +1101 -0
- package/src/tools/fetch.ts +1485 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/find.ts +543 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +428 -0
- package/src/tools/gh.ts +3737 -0
- package/src/tools/github-cache.ts +548 -0
- package/src/tools/grouped-file-output.ts +96 -0
- package/src/tools/hindsight-recall.ts +66 -0
- package/src/tools/hindsight-reflect.ts +55 -0
- package/src/tools/hindsight-retain.ts +54 -0
- package/src/tools/image-gen.ts +1284 -0
- package/src/tools/index.ts +580 -0
- package/src/tools/inspect-image-renderer.ts +103 -0
- package/src/tools/inspect-image.ts +167 -0
- package/src/tools/irc.ts +239 -0
- package/src/tools/job.ts +530 -0
- package/src/tools/json-tree.ts +243 -0
- package/src/tools/jtd-to-json-schema.ts +219 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/match-line-format.ts +22 -0
- package/src/tools/monitor.ts +255 -0
- package/src/tools/output-meta.ts +754 -0
- package/src/tools/path-utils.ts +741 -0
- package/src/tools/plan-mode-guard.ts +68 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/read.ts +2333 -0
- package/src/tools/recipe/index.ts +80 -0
- package/src/tools/recipe/render.ts +19 -0
- package/src/tools/recipe/runner.ts +219 -0
- package/src/tools/recipe/runners/cargo.ts +131 -0
- package/src/tools/recipe/runners/index.ts +8 -0
- package/src/tools/recipe/runners/just.ts +73 -0
- package/src/tools/recipe/runners/make.ts +101 -0
- package/src/tools/recipe/runners/pkg.ts +167 -0
- package/src/tools/recipe/runners/task.ts +72 -0
- package/src/tools/render-mermaid.ts +68 -0
- package/src/tools/render-utils.ts +774 -0
- package/src/tools/renderers.ts +79 -0
- package/src/tools/resolve.ts +258 -0
- package/src/tools/review.ts +252 -0
- package/src/tools/search-tool-bm25.ts +364 -0
- package/src/tools/search.ts +787 -0
- package/src/tools/skill.ts +170 -0
- package/src/tools/sqlite-reader.ts +736 -0
- package/src/tools/ssh.ts +310 -0
- package/src/tools/subagent-render.ts +160 -0
- package/src/tools/subagent.ts +698 -0
- package/src/tools/telegram-send.ts +178 -0
- package/src/tools/todo-write.ts +695 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +86 -0
- package/src/tools/tool-timeouts.ts +30 -0
- package/src/tools/vim.ts +949 -0
- package/src/tools/write.ts +953 -0
- package/src/tools/yield.ts +268 -0
- package/src/tui/code-cell.ts +201 -0
- package/src/tui/file-list.ts +55 -0
- package/src/tui/hyperlink.ts +126 -0
- package/src/tui/index.ts +12 -0
- package/src/tui/output-block.ts +150 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +84 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/utils/changelog.ts +121 -0
- package/src/utils/clipboard.ts +156 -0
- package/src/utils/command-args.ts +76 -0
- package/src/utils/commit-message-generator.ts +135 -0
- package/src/utils/edit-mode.ts +42 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +65 -0
- package/src/utils/file-display-mode.ts +45 -0
- package/src/utils/file-mentions.ts +376 -0
- package/src/utils/git.ts +1536 -0
- package/src/utils/image-loading.ts +102 -0
- package/src/utils/image-resize.ts +309 -0
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/markit.ts +89 -0
- package/src/utils/open.ts +20 -0
- package/src/utils/session-color.ts +43 -0
- package/src/utils/shell-snapshot.ts +187 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/title-generator.ts +237 -0
- package/src/utils/tokenizer-download.ts +106 -0
- package/src/utils/tokenizer-encoding.ts +29 -0
- package/src/utils/tool-choice.ts +62 -0
- package/src/utils/tools-manager.ts +363 -0
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2409 -0
- package/src/vim/parser.ts +134 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
- package/src/web/kagi.ts +183 -0
- package/src/web/parallel.ts +349 -0
- package/src/web/public-fetch-url.ts +74 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +97 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +653 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +452 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +140 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +173 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +120 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +189 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +93 -0
- package/src/web/scrapers/types.ts +358 -0
- package/src/web/scrapers/utils.ts +133 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +319 -0
- package/src/web/search/index.ts +416 -0
- package/src/web/search/provider.ts +236 -0
- package/src/web/search/providers/anthropic.ts +319 -0
- package/src/web/search/providers/base.ts +102 -0
- package/src/web/search/providers/brave.ts +149 -0
- package/src/web/search/providers/codex.ts +598 -0
- package/src/web/search/providers/duckduckgo.ts +279 -0
- package/src/web/search/providers/exa.ts +193 -0
- package/src/web/search/providers/gemini.ts +462 -0
- package/src/web/search/providers/jina.ts +101 -0
- package/src/web/search/providers/kagi.ts +75 -0
- package/src/web/search/providers/kimi.ts +171 -0
- package/src/web/search/providers/parallel.ts +210 -0
- package/src/web/search/providers/perplexity.ts +575 -0
- package/src/web/search/providers/searxng.ts +309 -0
- package/src/web/search/providers/synthetic.ts +108 -0
- package/src/web/search/providers/tavily.ts +173 -0
- package/src/web/search/providers/utils.ts +128 -0
- package/src/web/search/providers/xai.ts +174 -0
- package/src/web/search/providers/zai.ts +320 -0
- package/src/web/search/render.ts +299 -0
- package/src/web/search/types.ts +445 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +286 -0
|
@@ -0,0 +1,4181 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
5
|
+
import { buildTeamHudSummary as buildWorkflowTeamHudSummary } from "../skill-state/workflow-hud";
|
|
6
|
+
import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
|
|
7
|
+
|
|
8
|
+
import { applyJwcTmuxProfile } from "./launch-tmux";
|
|
9
|
+
import {
|
|
10
|
+
AlreadyExistsError,
|
|
11
|
+
appendJsonl as appendJsonlAudited,
|
|
12
|
+
appendText,
|
|
13
|
+
createJsonNoClobber,
|
|
14
|
+
deleteIfOwned,
|
|
15
|
+
removeFileAudited,
|
|
16
|
+
writeJsonAtomic,
|
|
17
|
+
writeReport,
|
|
18
|
+
writeWorkflowEnvelopeAtomic,
|
|
19
|
+
} from "./state-writer";
|
|
20
|
+
import { buildJwcTmuxExactOptionTarget, GJC_TMUX_PROFILE_OPTION, GJC_TMUX_PROFILE_VALUE } from "./tmux-common";
|
|
21
|
+
|
|
22
|
+
export type JwcTeamPhase = "starting" | "running" | "awaiting_integration" | "complete" | "failed" | "cancelled";
|
|
23
|
+
export type JwcTeamTaskStatus = "pending" | "blocked" | "in_progress" | "completed" | "failed";
|
|
24
|
+
export type JwcWorkerStatusState = "idle" | "working" | "blocked" | "done" | "failed" | "draining" | "unknown";
|
|
25
|
+
export type JwcTeamWorkerLifecycleState =
|
|
26
|
+
| "starting"
|
|
27
|
+
| "ready"
|
|
28
|
+
| "working"
|
|
29
|
+
| "draining"
|
|
30
|
+
| "stopped"
|
|
31
|
+
| "failed"
|
|
32
|
+
| "unknown";
|
|
33
|
+
export type JwcTeamShutdownMode = "graceful" | "force" | "abort";
|
|
34
|
+
|
|
35
|
+
export const GJC_TEAM_DEFAULT_WORKERS = 3;
|
|
36
|
+
export const GJC_TEAM_MAX_WORKERS = 20;
|
|
37
|
+
const GJC_TEAM_WORKER_CLI_ENV = "GJC_TEAM_WORKER_CLI";
|
|
38
|
+
const GJC_TEAM_WORKER_CLI_MAP_ENV = "GJC_TEAM_WORKER_CLI_MAP";
|
|
39
|
+
|
|
40
|
+
export type JwcTeamWorkerCli = "gjc";
|
|
41
|
+
type JwcTeamWorkerCliMode = "auto" | JwcTeamWorkerCli;
|
|
42
|
+
|
|
43
|
+
export interface JwcTeamLeader {
|
|
44
|
+
session_id: string;
|
|
45
|
+
pane_id: string;
|
|
46
|
+
cwd: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface JwcTeamWorker {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
index: number;
|
|
53
|
+
agent_type: string;
|
|
54
|
+
role: string;
|
|
55
|
+
pane_id?: string;
|
|
56
|
+
status: "starting" | "idle" | "busy" | "stopped";
|
|
57
|
+
last_heartbeat: string;
|
|
58
|
+
assigned_tasks: string[];
|
|
59
|
+
worktree_repo_root?: string;
|
|
60
|
+
worktree_path?: string;
|
|
61
|
+
worktree_branch?: string | null;
|
|
62
|
+
worktree_detached?: boolean;
|
|
63
|
+
worktree_created?: boolean;
|
|
64
|
+
worktree_base_ref?: string;
|
|
65
|
+
team_state_root?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface JwcTeamTaskClaim {
|
|
69
|
+
owner: string;
|
|
70
|
+
token: string;
|
|
71
|
+
leased_until: string;
|
|
72
|
+
}
|
|
73
|
+
export type JwcTeamTaskCompletionEvidenceKind = "command" | "inspection" | "artifact";
|
|
74
|
+
export type JwcTeamTaskCompletionEvidenceStatus = "passed" | "failed" | "not_run" | "verified" | "rejected";
|
|
75
|
+
|
|
76
|
+
export interface JwcTeamTaskCompletionEvidenceItem {
|
|
77
|
+
kind: JwcTeamTaskCompletionEvidenceKind;
|
|
78
|
+
status: JwcTeamTaskCompletionEvidenceStatus;
|
|
79
|
+
summary: string;
|
|
80
|
+
command?: string;
|
|
81
|
+
artifact?: string;
|
|
82
|
+
location?: string;
|
|
83
|
+
output?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface JwcTeamTaskCompletionEvidence {
|
|
87
|
+
summary: string;
|
|
88
|
+
items: JwcTeamTaskCompletionEvidenceItem[];
|
|
89
|
+
files?: string[];
|
|
90
|
+
notes?: string;
|
|
91
|
+
recorded_by: string;
|
|
92
|
+
recorded_at: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface JwcTeamTask {
|
|
96
|
+
id: string;
|
|
97
|
+
subject: string;
|
|
98
|
+
description: string;
|
|
99
|
+
title: string;
|
|
100
|
+
objective: string;
|
|
101
|
+
status: JwcTeamTaskStatus;
|
|
102
|
+
assignee?: string;
|
|
103
|
+
owner?: string;
|
|
104
|
+
result?: string;
|
|
105
|
+
completion_evidence?: JwcTeamTaskCompletionEvidence;
|
|
106
|
+
error?: string;
|
|
107
|
+
blocked_by?: string[];
|
|
108
|
+
depends_on?: string[];
|
|
109
|
+
lane?: string;
|
|
110
|
+
required_role?: string;
|
|
111
|
+
allowed_roles?: string[];
|
|
112
|
+
version: number;
|
|
113
|
+
claim?: JwcTeamTaskClaim;
|
|
114
|
+
created_at: string;
|
|
115
|
+
updated_at: string;
|
|
116
|
+
completed_at?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type JwcTeamWorktreeMode =
|
|
120
|
+
| { enabled: false }
|
|
121
|
+
| { enabled: true; detached: true; name: null }
|
|
122
|
+
| { enabled: true; detached: false; name: string };
|
|
123
|
+
|
|
124
|
+
export interface JwcTeamConfig {
|
|
125
|
+
team_name: string;
|
|
126
|
+
display_name: string;
|
|
127
|
+
requested_name: string;
|
|
128
|
+
task: string;
|
|
129
|
+
agent_type: string;
|
|
130
|
+
worker_count: number;
|
|
131
|
+
max_workers: number;
|
|
132
|
+
state_root: string;
|
|
133
|
+
worker_command: string;
|
|
134
|
+
worker_cli_plan: JwcTeamWorkerCli[];
|
|
135
|
+
tmux_command: string;
|
|
136
|
+
tmux_session: string;
|
|
137
|
+
tmux_session_name: string;
|
|
138
|
+
tmux_target: string;
|
|
139
|
+
workspace_mode: "direct" | "worktree";
|
|
140
|
+
dry_run: boolean;
|
|
141
|
+
leader: JwcTeamLeader;
|
|
142
|
+
leader_cwd: string;
|
|
143
|
+
team_state_root: string;
|
|
144
|
+
workers: JwcTeamWorker[];
|
|
145
|
+
created_at: string;
|
|
146
|
+
updated_at: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type JwcTeamIntegrationStatus =
|
|
150
|
+
| "idle"
|
|
151
|
+
| "integrated"
|
|
152
|
+
| "integration_failed"
|
|
153
|
+
| "merge_conflict"
|
|
154
|
+
| "cherry_pick_conflict"
|
|
155
|
+
| "rebase_conflict";
|
|
156
|
+
|
|
157
|
+
export interface JwcTeamWorkerIntegrationState {
|
|
158
|
+
last_seen_head?: string;
|
|
159
|
+
last_integrated_head?: string;
|
|
160
|
+
last_leader_head?: string;
|
|
161
|
+
last_rebased_leader_head?: string;
|
|
162
|
+
status?: JwcTeamIntegrationStatus;
|
|
163
|
+
conflict_commit?: string;
|
|
164
|
+
conflict_files?: string[];
|
|
165
|
+
updated_at?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface JwcTeamMonitorSnapshot {
|
|
169
|
+
integration_by_worker: Record<string, JwcTeamWorkerIntegrationState>;
|
|
170
|
+
updated_at: string;
|
|
171
|
+
}
|
|
172
|
+
export interface JwcTeamWorkerLifecycle {
|
|
173
|
+
worker: string;
|
|
174
|
+
lifecycle_state: JwcTeamWorkerLifecycleState;
|
|
175
|
+
worker_status_state: JwcWorkerStatusState;
|
|
176
|
+
pane_id?: string;
|
|
177
|
+
pid?: number;
|
|
178
|
+
started_at?: string;
|
|
179
|
+
updated_at: string;
|
|
180
|
+
stopped_at?: string;
|
|
181
|
+
stop_reason?: string;
|
|
182
|
+
shutdown_request_id?: string;
|
|
183
|
+
shutdown_requested_at?: string;
|
|
184
|
+
shutdown_acknowledged_at?: string;
|
|
185
|
+
shutdown_ack_status?: string;
|
|
186
|
+
shutdown_mode?: JwcTeamShutdownMode;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type JwcTeamNotificationDeliveryState =
|
|
190
|
+
| "pending"
|
|
191
|
+
| "sent"
|
|
192
|
+
| "queued"
|
|
193
|
+
| "deferred"
|
|
194
|
+
| "failed"
|
|
195
|
+
| "delivered"
|
|
196
|
+
| "acknowledged";
|
|
197
|
+
|
|
198
|
+
export type JwcTeamPaneAttemptResult = "sent" | "queued" | "deferred" | "failed";
|
|
199
|
+
|
|
200
|
+
export interface JwcTeamNotification {
|
|
201
|
+
id: string;
|
|
202
|
+
kind: "mailbox_message" | "worker_lifecycle" | "invalid_attempt";
|
|
203
|
+
team_name: string;
|
|
204
|
+
recipient: string;
|
|
205
|
+
source: { type: "message" | "task" | "worker" | "event"; id: string };
|
|
206
|
+
idempotency_key?: string;
|
|
207
|
+
delivery_state: JwcTeamNotificationDeliveryState;
|
|
208
|
+
pane_attempt_result?: JwcTeamPaneAttemptResult;
|
|
209
|
+
pane_attempt_reason?: string;
|
|
210
|
+
pane_attempt_at?: string;
|
|
211
|
+
created_at: string;
|
|
212
|
+
updated_at: string;
|
|
213
|
+
replay_count: number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface JwcTeamNotificationSummary {
|
|
217
|
+
total: number;
|
|
218
|
+
replay_eligible: number;
|
|
219
|
+
by_state: Record<JwcTeamNotificationDeliveryState, number>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface JwcTeamSnapshot {
|
|
223
|
+
team_name: string;
|
|
224
|
+
display_name: string;
|
|
225
|
+
phase: JwcTeamPhase;
|
|
226
|
+
state_dir: string;
|
|
227
|
+
tmux_session: string;
|
|
228
|
+
tmux_session_name: string;
|
|
229
|
+
tmux_target: string;
|
|
230
|
+
task_total: number;
|
|
231
|
+
task_counts: Record<JwcTeamTaskStatus, number>;
|
|
232
|
+
workers: JwcTeamWorker[];
|
|
233
|
+
integration_by_worker?: Record<string, JwcTeamWorkerIntegrationState>;
|
|
234
|
+
worker_lifecycle_by_id: Record<string, JwcTeamWorkerLifecycle>;
|
|
235
|
+
notification_summary: JwcTeamNotificationSummary;
|
|
236
|
+
updated_at: string;
|
|
237
|
+
}
|
|
238
|
+
export interface JwcTeamSnapshotOptions {
|
|
239
|
+
reconcileNotifications?: boolean;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface JwcTeamStartOptions {
|
|
243
|
+
workerCount: number;
|
|
244
|
+
agentType: string;
|
|
245
|
+
task: string;
|
|
246
|
+
teamName?: string;
|
|
247
|
+
worktreeMode?: JwcTeamWorktreeMode;
|
|
248
|
+
cwd?: string;
|
|
249
|
+
env?: NodeJS.ProcessEnv;
|
|
250
|
+
dryRun?: boolean;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface JwcTeamApiClaimResult {
|
|
254
|
+
ok: boolean;
|
|
255
|
+
task?: JwcTeamTask;
|
|
256
|
+
worker_id?: string;
|
|
257
|
+
claim_token?: string;
|
|
258
|
+
reason?: string;
|
|
259
|
+
}
|
|
260
|
+
export type JwcTeamLivenessRecoveryReason =
|
|
261
|
+
| "claim_expired"
|
|
262
|
+
| "stale_heartbeat"
|
|
263
|
+
| "missing_pane"
|
|
264
|
+
| "worker_lifecycle_failed"
|
|
265
|
+
| "worker_lifecycle_stopped";
|
|
266
|
+
|
|
267
|
+
export interface JwcTeamRecoveredClaim {
|
|
268
|
+
task_id: string;
|
|
269
|
+
worker: string;
|
|
270
|
+
reasons: JwcTeamLivenessRecoveryReason[];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface JwcTeamLivenessRecoveryResult {
|
|
274
|
+
recovered_claims: JwcTeamRecoveredClaim[];
|
|
275
|
+
stale_workers: Record<string, JwcTeamLivenessRecoveryReason[]>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export interface JwcTeamMailboxMessage {
|
|
279
|
+
message_id: string;
|
|
280
|
+
from_worker: string;
|
|
281
|
+
to_worker: string;
|
|
282
|
+
body: string;
|
|
283
|
+
created_at: string;
|
|
284
|
+
delivered_at?: string;
|
|
285
|
+
notified_at?: string;
|
|
286
|
+
idempotency_key?: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function taskReceiptFields(teamName: string, task: JwcTeamTask): Record<string, unknown> {
|
|
290
|
+
return {
|
|
291
|
+
team_name: teamName,
|
|
292
|
+
task_id: task.id,
|
|
293
|
+
status: task.status,
|
|
294
|
+
owner: task.owner,
|
|
295
|
+
worker_id: task.claim?.owner ?? task.owner ?? task.assignee,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function mailboxMessageReceiptFields(teamName: string, message: JwcTeamMailboxMessage): Record<string, unknown> {
|
|
300
|
+
return {
|
|
301
|
+
team_name: teamName,
|
|
302
|
+
message_id: message.message_id,
|
|
303
|
+
from_worker: message.from_worker,
|
|
304
|
+
to_worker: message.to_worker,
|
|
305
|
+
delivered: Boolean(message.delivered_at),
|
|
306
|
+
notified: Boolean(message.notified_at),
|
|
307
|
+
delivered_at: message.delivered_at,
|
|
308
|
+
notified_at: message.notified_at,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function notificationReceiptFields(notification: JwcTeamNotification): Record<string, unknown> {
|
|
313
|
+
return {
|
|
314
|
+
team_name: notification.team_name,
|
|
315
|
+
notification_id: notification.id,
|
|
316
|
+
recipient: notification.recipient,
|
|
317
|
+
source_type: notification.source.type,
|
|
318
|
+
source_id: notification.source.id,
|
|
319
|
+
delivery_state: notification.delivery_state,
|
|
320
|
+
pane_attempt_result: notification.pane_attempt_result,
|
|
321
|
+
pane_attempt_reason: notification.pane_attempt_reason,
|
|
322
|
+
replay_count: notification.replay_count,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function notificationSummaryReceipt(
|
|
327
|
+
teamName: string,
|
|
328
|
+
result: { notifications: JwcTeamNotification[]; summary: JwcTeamNotificationSummary },
|
|
329
|
+
): Record<string, unknown> {
|
|
330
|
+
return {
|
|
331
|
+
team_name: teamName,
|
|
332
|
+
notification_ids: result.notifications.map(notification => notification.id),
|
|
333
|
+
delivery_states: result.notifications.map(notification => notification.delivery_state),
|
|
334
|
+
summary: result.summary,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
interface FsError {
|
|
339
|
+
code?: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function normalizeJwcTeamWorkerCliMode(
|
|
343
|
+
raw: string | undefined,
|
|
344
|
+
sourceEnv = GJC_TEAM_WORKER_CLI_ENV,
|
|
345
|
+
): JwcTeamWorkerCliMode {
|
|
346
|
+
const normalized = String(raw ?? "auto")
|
|
347
|
+
.trim()
|
|
348
|
+
.toLowerCase();
|
|
349
|
+
if (normalized === "" || normalized === "auto") return "auto";
|
|
350
|
+
if (normalized === "gjc") return "gjc";
|
|
351
|
+
if (normalized === "codex" || normalized === "claude" || normalized === "gemini") {
|
|
352
|
+
throw new Error(`Unsupported ${sourceEnv} value "${raw}". GJC team launches GJC teammate sessions only.`);
|
|
353
|
+
}
|
|
354
|
+
throw new Error(`Invalid ${sourceEnv} value "${raw}". Expected: auto or gjc`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function resolveJwcTeamWorkerCli(env: NodeJS.ProcessEnv = process.env): JwcTeamWorkerCli {
|
|
358
|
+
const mode = normalizeJwcTeamWorkerCliMode(env[GJC_TEAM_WORKER_CLI_ENV]);
|
|
359
|
+
return mode === "auto" ? "gjc" : mode;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function resolveJwcTeamWorkerCliPlan(
|
|
363
|
+
workerCount: number,
|
|
364
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
365
|
+
): JwcTeamWorkerCli[] {
|
|
366
|
+
if (!Number.isInteger(workerCount) || workerCount < 1) {
|
|
367
|
+
throw new Error(`workerCount must be >= 1 (got ${workerCount})`);
|
|
368
|
+
}
|
|
369
|
+
normalizeJwcTeamWorkerCliMode(env[GJC_TEAM_WORKER_CLI_ENV]);
|
|
370
|
+
const rawMap = String(env[GJC_TEAM_WORKER_CLI_MAP_ENV] ?? "").trim();
|
|
371
|
+
if (rawMap === "") {
|
|
372
|
+
const cli = resolveJwcTeamWorkerCli(env);
|
|
373
|
+
return Array.from({ length: workerCount }, () => cli);
|
|
374
|
+
}
|
|
375
|
+
const entries = rawMap.split(",").map(entry => entry.trim());
|
|
376
|
+
if (entries.length === 0 || entries.every(entry => entry.length === 0)) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`Invalid ${GJC_TEAM_WORKER_CLI_MAP_ENV} value "${env[GJC_TEAM_WORKER_CLI_MAP_ENV]}". Expected: auto or gjc`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (entries.some(entry => entry.length === 0)) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Invalid ${GJC_TEAM_WORKER_CLI_MAP_ENV} value "${env[GJC_TEAM_WORKER_CLI_MAP_ENV]}". Empty entries are not allowed.`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (entries.length !== 1 && entries.length !== workerCount) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Invalid ${GJC_TEAM_WORKER_CLI_MAP_ENV} length ${entries.length}; expected 1 or ${workerCount} comma-separated values.`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const expanded = entries.length === 1 ? Array.from({ length: workerCount }, () => entries[0] ?? "") : entries;
|
|
392
|
+
return expanded.map(entry => {
|
|
393
|
+
const mode = normalizeJwcTeamWorkerCliMode(entry, GJC_TEAM_WORKER_CLI_MAP_ENV);
|
|
394
|
+
return mode === "auto" ? "gjc" : mode;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function translateJwcWorkerLaunchArgsForCli(workerCli: JwcTeamWorkerCli, args: string[]): string[] {
|
|
399
|
+
if (workerCli !== "gjc") {
|
|
400
|
+
throw new Error(`Unsupported team worker CLI "${workerCli}". GJC team launches GJC teammate sessions only.`);
|
|
401
|
+
}
|
|
402
|
+
return [...args];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
interface JwcTmuxLeaderContext {
|
|
406
|
+
sessionName: string;
|
|
407
|
+
windowIndex: string;
|
|
408
|
+
leaderPaneId: string;
|
|
409
|
+
target: string;
|
|
410
|
+
}
|
|
411
|
+
export interface JwcTeamEvent {
|
|
412
|
+
event_id: string;
|
|
413
|
+
ts: string;
|
|
414
|
+
type: string;
|
|
415
|
+
worker?: string;
|
|
416
|
+
task_id?: string;
|
|
417
|
+
message?: string;
|
|
418
|
+
data?: Record<string, unknown>;
|
|
419
|
+
}
|
|
420
|
+
export interface JwcTeamTraceEvent {
|
|
421
|
+
schema_version: 1;
|
|
422
|
+
trace_id: string;
|
|
423
|
+
span_id: string;
|
|
424
|
+
source_event_id: string;
|
|
425
|
+
event_type: string;
|
|
426
|
+
ts: string;
|
|
427
|
+
worker?: string;
|
|
428
|
+
task_id?: string;
|
|
429
|
+
message?: string;
|
|
430
|
+
evidence_refs?: string[];
|
|
431
|
+
data?: Record<string, unknown>;
|
|
432
|
+
}
|
|
433
|
+
interface WorkerStatusFile {
|
|
434
|
+
state: JwcWorkerStatusState;
|
|
435
|
+
current_task_id?: string;
|
|
436
|
+
reason?: string;
|
|
437
|
+
updated_at: string;
|
|
438
|
+
}
|
|
439
|
+
interface WorkerHeartbeatFile {
|
|
440
|
+
pid: number;
|
|
441
|
+
last_turn_at: string;
|
|
442
|
+
turn_count: number;
|
|
443
|
+
alive: boolean;
|
|
444
|
+
}
|
|
445
|
+
interface GitResult {
|
|
446
|
+
ok: boolean;
|
|
447
|
+
stdout: string;
|
|
448
|
+
stderr: string;
|
|
449
|
+
}
|
|
450
|
+
interface JwcTeamCommitHygieneEntry {
|
|
451
|
+
recorded_at: string;
|
|
452
|
+
operation:
|
|
453
|
+
| "auto_checkpoint"
|
|
454
|
+
| "leader_integration_attempt"
|
|
455
|
+
| "integration_merge"
|
|
456
|
+
| "integration_cherry_pick"
|
|
457
|
+
| "cross_rebase";
|
|
458
|
+
worker_name: string;
|
|
459
|
+
task_id?: string;
|
|
460
|
+
status: "applied" | "skipped" | "conflict" | "failed";
|
|
461
|
+
operational_commit?: string | null;
|
|
462
|
+
source_commit?: string;
|
|
463
|
+
leader_head_before?: string;
|
|
464
|
+
leader_head_after?: string | null;
|
|
465
|
+
worker_head_before?: string | null;
|
|
466
|
+
worker_head_after?: string | null;
|
|
467
|
+
worktree_path?: string;
|
|
468
|
+
detail: string;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
interface JwcWorkerIntegrationDedupeState {
|
|
472
|
+
last_requested_fingerprint?: string;
|
|
473
|
+
last_requested_head?: string | null;
|
|
474
|
+
last_requested_status?: JwcWorkerCheckpointClassification["kind"];
|
|
475
|
+
last_requested_at?: string;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export interface JwcWorkerIntegrationAttemptRequestResult {
|
|
479
|
+
requested: boolean;
|
|
480
|
+
reason: "requested" | "not_worker" | "missing_worktree" | "no_changes" | "deduped" | "git_error";
|
|
481
|
+
worker?: string;
|
|
482
|
+
team_name?: string;
|
|
483
|
+
fingerprint?: string;
|
|
484
|
+
head?: string | null;
|
|
485
|
+
status?: JwcWorkerCheckpointClassification["kind"];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function isJwcTeamTaskStatus(value: string): value is JwcTeamTaskStatus {
|
|
489
|
+
return ["pending", "blocked", "in_progress", "completed", "failed"].includes(value);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function parseJwcTeamTaskStatus(value: unknown, allowLegacyComplete = false): JwcTeamTaskStatus {
|
|
493
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
494
|
+
if (allowLegacyComplete && raw === "complete") return "completed";
|
|
495
|
+
if (isJwcTeamTaskStatus(raw)) return raw;
|
|
496
|
+
throw new Error(`invalid_task_status:${raw}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export const GJC_TEAM_API_OPERATIONS = [
|
|
500
|
+
"send-message",
|
|
501
|
+
"broadcast",
|
|
502
|
+
"mailbox-list",
|
|
503
|
+
"mailbox-mark-delivered",
|
|
504
|
+
"mailbox-mark-notified",
|
|
505
|
+
"notification-list",
|
|
506
|
+
"notification-read",
|
|
507
|
+
"notification-replay",
|
|
508
|
+
"notification-mark-pane-attempt",
|
|
509
|
+
"worker-startup-ack",
|
|
510
|
+
"create-task",
|
|
511
|
+
"read-task",
|
|
512
|
+
"list-tasks",
|
|
513
|
+
"update-task",
|
|
514
|
+
"claim-task",
|
|
515
|
+
"transition-task-status",
|
|
516
|
+
"transition-task",
|
|
517
|
+
"release-task-claim",
|
|
518
|
+
"read-config",
|
|
519
|
+
"read-manifest",
|
|
520
|
+
"read-worker-status",
|
|
521
|
+
"update-worker-status",
|
|
522
|
+
"read-worker-heartbeat",
|
|
523
|
+
"recover-stale-claims",
|
|
524
|
+
"update-worker-heartbeat",
|
|
525
|
+
"write-worker-inbox",
|
|
526
|
+
"write-worker-identity",
|
|
527
|
+
"append-event",
|
|
528
|
+
"read-events",
|
|
529
|
+
"read-traces",
|
|
530
|
+
"await-event",
|
|
531
|
+
"write-shutdown-request",
|
|
532
|
+
"read-shutdown-ack",
|
|
533
|
+
"read-monitor-snapshot",
|
|
534
|
+
"write-monitor-snapshot",
|
|
535
|
+
"read-task-approval",
|
|
536
|
+
"write-task-approval",
|
|
537
|
+
] as const;
|
|
538
|
+
|
|
539
|
+
function now(): string {
|
|
540
|
+
return new Date().toISOString();
|
|
541
|
+
}
|
|
542
|
+
function isEnoent(error: unknown): error is FsError {
|
|
543
|
+
return typeof error === "object" && error !== null && "code" in error && (error as FsError).code === "ENOENT";
|
|
544
|
+
}
|
|
545
|
+
function stateWriterOptions(filePath: string, category: "state" | "ledger" | "report" | "prune", verb: string) {
|
|
546
|
+
const resolved = path.resolve(filePath);
|
|
547
|
+
const marker = `${path.sep}.jwc${path.sep}`;
|
|
548
|
+
const markerIndex = resolved.indexOf(marker);
|
|
549
|
+
const cwd = markerIndex >= 0 ? resolved.slice(0, markerIndex) : process.cwd();
|
|
550
|
+
return { cwd, audit: { category, verb, owner: "jwc-runtime" as const } };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function sanitizeName(value: string): string {
|
|
554
|
+
const sanitized = value
|
|
555
|
+
.toLowerCase()
|
|
556
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
557
|
+
.replace(/-+/g, "-")
|
|
558
|
+
.replace(/^-|-$/g, "")
|
|
559
|
+
.slice(0, 40)
|
|
560
|
+
.replace(/-$/, "");
|
|
561
|
+
return sanitized || "team";
|
|
562
|
+
}
|
|
563
|
+
function shortHash(value: string): string {
|
|
564
|
+
return Bun.hash(value).toString(16).slice(0, 8).padStart(8, "0");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function stableHash(value: string): string {
|
|
568
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
569
|
+
}
|
|
570
|
+
function makeTeamName(task: string, env: NodeJS.ProcessEnv): string {
|
|
571
|
+
const basis = [task, env.GJC_SESSION_ID, env.CODEX_SESSION_ID, env.TMUX_PANE, env.TMUX, now()]
|
|
572
|
+
.filter(Boolean)
|
|
573
|
+
.join(":");
|
|
574
|
+
const prefix = sanitizeName(task).slice(0, 30).replace(/-$/, "") || "team";
|
|
575
|
+
return `${prefix}-${shortHash(basis)}`;
|
|
576
|
+
}
|
|
577
|
+
function teamDir(stateRoot: string, teamName: string): string {
|
|
578
|
+
return path.join(stateRoot, sanitizeName(teamName));
|
|
579
|
+
}
|
|
580
|
+
function shellQuote(value: string): string {
|
|
581
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
582
|
+
}
|
|
583
|
+
function safePathSegment(kind: string, value: string): string {
|
|
584
|
+
assertSafeId(kind, value);
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
587
|
+
function taskPath(dir: string, taskId: string): string {
|
|
588
|
+
return path.join(dir, "tasks", `${safePathSegment("task_id", taskId)}.json`);
|
|
589
|
+
}
|
|
590
|
+
function mailboxPath(dir: string, worker: string): string {
|
|
591
|
+
return path.join(dir, "mailbox", `${safePathSegment("worker_id", worker)}.json`);
|
|
592
|
+
}
|
|
593
|
+
function mailboxDirPath(dir: string, worker: string): string {
|
|
594
|
+
return path.join(dir, "mailbox", safePathSegment("worker_id", worker));
|
|
595
|
+
}
|
|
596
|
+
function mailboxMessagePath(dir: string, worker: string, messageId: string): string {
|
|
597
|
+
return path.join(mailboxDirPath(dir, worker), `${safePathSegment("message_id", messageId)}.json`);
|
|
598
|
+
}
|
|
599
|
+
function notificationPath(dir: string, notificationId: string): string {
|
|
600
|
+
return path.join(dir, "notifications", `${safePathSegment("notification_id", notificationId)}.json`);
|
|
601
|
+
}
|
|
602
|
+
function workerDir(dir: string, worker: string): string {
|
|
603
|
+
return path.join(dir, "workers", safePathSegment("worker_id", worker));
|
|
604
|
+
}
|
|
605
|
+
function workerLifecyclePath(dir: string, worker: string): string {
|
|
606
|
+
return path.join(workerDir(dir, worker), "lifecycle.json");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function tracePath(dir: string): string {
|
|
610
|
+
return path.join(dir, "trace.jsonl");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function traceErrorPath(dir: string): string {
|
|
614
|
+
return path.join(dir, "trace-errors.jsonl");
|
|
615
|
+
}
|
|
616
|
+
function isSafeId(value: string): boolean {
|
|
617
|
+
return (
|
|
618
|
+
/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(value) &&
|
|
619
|
+
!value.includes("..") &&
|
|
620
|
+
!value.includes("/") &&
|
|
621
|
+
!value.includes("\\")
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
function assertSafeId(kind: string, value: string): void {
|
|
625
|
+
if (!isSafeId(value)) throw new Error(`invalid_${kind}:${value}`);
|
|
626
|
+
}
|
|
627
|
+
function isLeaderRecipient(value: string): boolean {
|
|
628
|
+
return value === "leader-fixed";
|
|
629
|
+
}
|
|
630
|
+
function assertKnownWorker(config: JwcTeamConfig, worker: string, allowLeader = false): void {
|
|
631
|
+
assertSafeId("worker_id", worker);
|
|
632
|
+
if (allowLeader && isLeaderRecipient(worker)) return;
|
|
633
|
+
if (!config.workers.some(candidate => candidate.id === worker)) throw new Error(`unknown_worker:${worker}`);
|
|
634
|
+
}
|
|
635
|
+
function findKnownWorker(config: JwcTeamConfig, worker: string): JwcTeamWorker {
|
|
636
|
+
assertKnownWorker(config, worker);
|
|
637
|
+
const found = config.workers.find(candidate => candidate.id === worker);
|
|
638
|
+
if (!found) throw new Error(`unknown_worker:${worker}`);
|
|
639
|
+
return found;
|
|
640
|
+
}
|
|
641
|
+
function assertKnownParticipant(config: JwcTeamConfig, worker: string): void {
|
|
642
|
+
assertKnownWorker(config, worker, true);
|
|
643
|
+
}
|
|
644
|
+
function messageNotificationId(teamName: string, recipient: string, messageId: string): string {
|
|
645
|
+
return `ntf-${stableHash(["mailbox_message", teamName, recipient, messageId].join(":"))}`;
|
|
646
|
+
}
|
|
647
|
+
function messageIdFor(input: {
|
|
648
|
+
teamName: string;
|
|
649
|
+
fromWorker: string;
|
|
650
|
+
toWorker: string;
|
|
651
|
+
body: string;
|
|
652
|
+
idempotencyKey?: string;
|
|
653
|
+
createdKey: string;
|
|
654
|
+
}): string {
|
|
655
|
+
return `msg-${stableHash([input.teamName, input.fromWorker, input.toWorker, input.idempotencyKey ?? input.body, input.createdKey].join(":"))}`;
|
|
656
|
+
}
|
|
657
|
+
function workerIntegrationDedupePath(dir: string, worker: string): string {
|
|
658
|
+
return path.join(workerDir(dir, worker), "posttooluse-dedupe.json");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export function resolveJwcTeamStateRoot(cwd = process.cwd(), env: NodeJS.ProcessEnv = process.env): string {
|
|
662
|
+
const explicit = env.GJC_TEAM_STATE_ROOT?.trim();
|
|
663
|
+
if (explicit) return path.resolve(cwd, explicit);
|
|
664
|
+
return path.join(cwd, ".jwc", "state", "team");
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async function readJsonFile<T>(filePath: string): Promise<T | null> {
|
|
668
|
+
try {
|
|
669
|
+
return (await Bun.file(filePath).json()) as T;
|
|
670
|
+
} catch (error) {
|
|
671
|
+
if (isEnoent(error)) return null;
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function stateCategoryForJsonPath(filePath: string): "state" | "ledger" {
|
|
676
|
+
return filePath.endsWith(".jsonl") || filePath.includes(`${path.sep}telemetry${path.sep}`) ? "ledger" : "state";
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async function writeJsonFile(filePath: string, value: unknown): Promise<void> {
|
|
680
|
+
await writeJsonAtomic(filePath, value, stateWriterOptions(filePath, stateCategoryForJsonPath(filePath), "write"));
|
|
681
|
+
}
|
|
682
|
+
async function writeJsonFileNoClobber(filePath: string, value: unknown): Promise<boolean> {
|
|
683
|
+
try {
|
|
684
|
+
await createJsonNoClobber(
|
|
685
|
+
filePath,
|
|
686
|
+
value,
|
|
687
|
+
stateWriterOptions(filePath, stateCategoryForJsonPath(filePath), "create"),
|
|
688
|
+
);
|
|
689
|
+
return true;
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (error instanceof AlreadyExistsError) return false;
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
async function appendJsonl(filePath: string, value: unknown): Promise<void> {
|
|
696
|
+
await appendJsonlAudited(filePath, value, stateWriterOptions(filePath, "ledger", "append"));
|
|
697
|
+
}
|
|
698
|
+
function traceIdForTeam(dir: string): string {
|
|
699
|
+
return `trace-${stableHash(path.basename(dir))}`;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function evidenceRefsForEvent(event: JwcTeamEvent): string[] | undefined {
|
|
703
|
+
const refs: string[] = [];
|
|
704
|
+
if (event.task_id && event.type === "task_transitioned" && event.data && "completion_evidence" in event.data)
|
|
705
|
+
refs.push(`task:${event.task_id}:completion_evidence`);
|
|
706
|
+
if (event.task_id && event.type === "task_claim_recovered") refs.push(`task:${event.task_id}:claim_recovery`);
|
|
707
|
+
if (event.worker && event.type.startsWith("worker_")) refs.push(`worker:${event.worker}`);
|
|
708
|
+
return refs.length > 0 ? refs : undefined;
|
|
709
|
+
}
|
|
710
|
+
function pickString(value: unknown): string | undefined {
|
|
711
|
+
return typeof value === "string" ? value : undefined;
|
|
712
|
+
}
|
|
713
|
+
function pickNumber(value: unknown): number | undefined {
|
|
714
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
715
|
+
}
|
|
716
|
+
function pickBoolean(value: unknown): boolean | undefined {
|
|
717
|
+
return typeof value === "boolean" ? value : undefined;
|
|
718
|
+
}
|
|
719
|
+
function pickStringArray(value: unknown): string[] | undefined {
|
|
720
|
+
return Array.isArray(value) && value.every(item => typeof item === "string") ? value : undefined;
|
|
721
|
+
}
|
|
722
|
+
function setIfDefined(record: Record<string, unknown>, key: string, value: unknown): void {
|
|
723
|
+
if (value !== undefined) record[key] = value;
|
|
724
|
+
}
|
|
725
|
+
function messageBodyTraceProjection(body: string | undefined): Record<string, unknown> {
|
|
726
|
+
if (body === undefined) return {};
|
|
727
|
+
return {
|
|
728
|
+
body_byte_length: Buffer.byteLength(body, "utf8"),
|
|
729
|
+
body_sha256: createHash("sha256").update(body).digest("hex"),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function traceDataForEvent(event: JwcTeamEvent): Record<string, unknown> | undefined {
|
|
733
|
+
const source = event.data ?? {};
|
|
734
|
+
const data: Record<string, unknown> = {};
|
|
735
|
+
switch (event.type) {
|
|
736
|
+
case "message_sent": {
|
|
737
|
+
setIfDefined(data, "to_worker", pickString(source.to_worker));
|
|
738
|
+
setIfDefined(data, "message_id", pickString(source.message_id));
|
|
739
|
+
Object.assign(data, messageBodyTraceProjection(pickString(event.message)));
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
case "message_acknowledged":
|
|
743
|
+
case "message_notified": {
|
|
744
|
+
setIfDefined(data, "message_id", pickString(event.message));
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
case "team_started": {
|
|
748
|
+
setIfDefined(data, "worker_count", pickNumber(source.worker_count));
|
|
749
|
+
setIfDefined(data, "agent_type", pickString(source.agent_type));
|
|
750
|
+
setIfDefined(data, "workspace_mode", pickString(source.workspace_mode));
|
|
751
|
+
setIfDefined(data, "dry_run", pickBoolean(source.dry_run));
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case "task_claim_recovered": {
|
|
755
|
+
setIfDefined(data, "reasons", pickStringArray(source.reasons));
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
case "task_transitioned": {
|
|
759
|
+
setIfDefined(data, "status", pickString(source.status));
|
|
760
|
+
const evidence = source.completion_evidence;
|
|
761
|
+
if (typeof evidence === "object" && evidence !== null) {
|
|
762
|
+
const evidenceRecord = evidence as Record<string, unknown>;
|
|
763
|
+
data.completion_evidence = {
|
|
764
|
+
recorded_by: pickString(evidenceRecord.recorded_by),
|
|
765
|
+
item_count: pickNumber(evidenceRecord.item_count),
|
|
766
|
+
verified_item_count: pickNumber(evidenceRecord.verified_item_count),
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
case "worker_integration_attempt_requested": {
|
|
772
|
+
setIfDefined(data, "worker_name", pickString(source.worker_name));
|
|
773
|
+
setIfDefined(data, "worker_head", pickString(source.worker_head));
|
|
774
|
+
setIfDefined(data, "status", pickString(source.status));
|
|
775
|
+
if (Array.isArray(source.files)) data.file_count = source.files.length;
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
case "worker_lifecycle_nudge": {
|
|
779
|
+
setIfDefined(data, "condition", pickString(source.condition));
|
|
780
|
+
setIfDefined(data, "severity", pickString(source.severity));
|
|
781
|
+
setIfDefined(data, "fingerprint", pickString(source.fingerprint));
|
|
782
|
+
setIfDefined(data, "auto_action_taken", pickBoolean(source.auto_action_taken));
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
case "team_shutdown": {
|
|
786
|
+
setIfDefined(data, "phase", pickString(source.phase));
|
|
787
|
+
setIfDefined(data, "shutdown_request_id", pickString(source.shutdown_request_id));
|
|
788
|
+
setIfDefined(data, "graceful_shutdown_complete", pickBoolean(source.graceful_shutdown_complete));
|
|
789
|
+
if (Array.isArray(source.evidence_failures)) data.evidence_failure_count = source.evidence_failures.length;
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case "worker_status_updated": {
|
|
793
|
+
setIfDefined(data, "status", pickString(source.status));
|
|
794
|
+
setIfDefined(data, "current_task_id", pickString(source.current_task_id));
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
case "worker_shutdown_requested": {
|
|
798
|
+
setIfDefined(data, "requested_by", pickString(source.requested_by));
|
|
799
|
+
setIfDefined(data, "request_id", pickString(source.request_id));
|
|
800
|
+
setIfDefined(data, "mode", pickString(source.mode));
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return Object.keys(data).length > 0 ? data : undefined;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async function appendTraceForEvent(dir: string, event: JwcTeamEvent): Promise<void> {
|
|
808
|
+
const evidenceRefs = evidenceRefsForEvent(event);
|
|
809
|
+
const traceData = traceDataForEvent(event);
|
|
810
|
+
const trace: JwcTeamTraceEvent = {
|
|
811
|
+
schema_version: 1,
|
|
812
|
+
trace_id: traceIdForTeam(dir),
|
|
813
|
+
span_id: `span-${stableHash(event.event_id)}`,
|
|
814
|
+
source_event_id: event.event_id,
|
|
815
|
+
event_type: event.type,
|
|
816
|
+
ts: event.ts,
|
|
817
|
+
...(event.worker ? { worker: event.worker } : {}),
|
|
818
|
+
...(event.task_id ? { task_id: event.task_id } : {}),
|
|
819
|
+
...(traceData ? { data: traceData } : {}),
|
|
820
|
+
...(evidenceRefs ? { evidence_refs: evidenceRefs } : {}),
|
|
821
|
+
};
|
|
822
|
+
try {
|
|
823
|
+
await appendJsonl(tracePath(dir), trace);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
try {
|
|
826
|
+
await appendJsonl(traceErrorPath(dir), {
|
|
827
|
+
ts: now(),
|
|
828
|
+
source_event_id: event.event_id,
|
|
829
|
+
error: error instanceof Error ? error.message : String(error),
|
|
830
|
+
});
|
|
831
|
+
} catch {
|
|
832
|
+
// Trace append failure must not break legacy events.jsonl compatibility.
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function appendEvent(dir: string, event: Omit<JwcTeamEvent, "ts" | "event_id">): Promise<JwcTeamEvent> {
|
|
837
|
+
const full = { event_id: `evt-${Date.now()}-${Math.random().toString(16).slice(2)}`, ts: now(), ...event };
|
|
838
|
+
await appendJsonl(path.join(dir, "events.jsonl"), full);
|
|
839
|
+
await appendTraceForEvent(dir, full);
|
|
840
|
+
return full;
|
|
841
|
+
}
|
|
842
|
+
async function appendTelemetry(
|
|
843
|
+
dir: string,
|
|
844
|
+
event: { type: string; message: string; data?: Record<string, unknown> },
|
|
845
|
+
): Promise<void> {
|
|
846
|
+
await appendJsonl(path.join(dir, "telemetry.jsonl"), { ts: now(), ...event });
|
|
847
|
+
}
|
|
848
|
+
async function readConfig(dir: string): Promise<JwcTeamConfig> {
|
|
849
|
+
const config = await readJsonFile<JwcTeamConfig>(path.join(dir, "config.json"));
|
|
850
|
+
if (!config) throw new Error(`team_config_not_found:${dir}`);
|
|
851
|
+
const tmuxSessionName = config.tmux_session_name ?? config.tmux_session?.split(":")[0] ?? "";
|
|
852
|
+
return {
|
|
853
|
+
...config,
|
|
854
|
+
max_workers: config.max_workers ?? GJC_TEAM_MAX_WORKERS,
|
|
855
|
+
tmux_command: config.tmux_command ?? resolveJwcTmuxCommand(),
|
|
856
|
+
tmux_session: tmuxSessionName,
|
|
857
|
+
tmux_session_name: tmuxSessionName,
|
|
858
|
+
tmux_target: config.tmux_target ?? config.tmux_session ?? tmuxSessionName,
|
|
859
|
+
dry_run: config.dry_run ?? config.tmux_session_name === "dry-run",
|
|
860
|
+
leader_cwd: config.leader_cwd ?? config.leader.cwd,
|
|
861
|
+
team_state_root: config.team_state_root ?? config.state_root,
|
|
862
|
+
worker_cli_plan: config.worker_cli_plan ?? Array.from({ length: config.worker_count }, () => "gjc"),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
async function readPhase(dir: string): Promise<JwcTeamPhase> {
|
|
866
|
+
const phase = await readJsonFile<{ current_phase?: JwcTeamPhase }>(path.join(dir, "phase.json"));
|
|
867
|
+
return phase?.current_phase ?? "running";
|
|
868
|
+
}
|
|
869
|
+
async function writePhase(dir: string, phase: JwcTeamPhase): Promise<void> {
|
|
870
|
+
await writeJsonFile(path.join(dir, "phase.json"), { current_phase: phase, updated_at: now() });
|
|
871
|
+
}
|
|
872
|
+
function isJwcWorkerStatusState(value: string): value is JwcWorkerStatusState {
|
|
873
|
+
return ["idle", "working", "blocked", "done", "failed", "draining", "unknown"].includes(value);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function parseJwcWorkerStatusState(value: unknown): JwcWorkerStatusState {
|
|
877
|
+
return typeof value === "string" && isJwcWorkerStatusState(value) ? value : "unknown";
|
|
878
|
+
}
|
|
879
|
+
function parseRequiredJwcWorkerStatusState(value: unknown): JwcWorkerStatusState {
|
|
880
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
881
|
+
if (isJwcWorkerStatusState(raw)) return raw;
|
|
882
|
+
throw new Error(`invalid_worker_status:${raw}`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function lifecycleStateForWorkerStatus(status: JwcWorkerStatusState): JwcTeamWorkerLifecycleState {
|
|
886
|
+
switch (status) {
|
|
887
|
+
case "working":
|
|
888
|
+
return "working";
|
|
889
|
+
case "draining":
|
|
890
|
+
return "draining";
|
|
891
|
+
case "failed":
|
|
892
|
+
return "failed";
|
|
893
|
+
case "unknown":
|
|
894
|
+
return "unknown";
|
|
895
|
+
case "idle":
|
|
896
|
+
case "blocked":
|
|
897
|
+
case "done":
|
|
898
|
+
return "ready";
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function parseJwcTeamShutdownMode(value: unknown): JwcTeamShutdownMode {
|
|
903
|
+
const raw = typeof value === "string" ? value.trim() : "graceful";
|
|
904
|
+
if (raw === "graceful" || raw === "force" || raw === "abort") return raw;
|
|
905
|
+
throw new Error(`invalid_shutdown_mode:${raw}`);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function isJwcTeamWorkerLifecycleState(value: string): value is JwcTeamWorkerLifecycleState {
|
|
909
|
+
return ["starting", "ready", "working", "draining", "stopped", "failed", "unknown"].includes(value);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function parseJwcTeamWorkerLifecycleState(value: unknown): JwcTeamWorkerLifecycleState {
|
|
913
|
+
return typeof value === "string" && isJwcTeamWorkerLifecycleState(value) ? value : "unknown";
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function readWorkerStatusFile(dir: string, worker: string): Promise<WorkerStatusFile> {
|
|
917
|
+
return (
|
|
918
|
+
(await readJsonFile<WorkerStatusFile>(path.join(workerDir(dir, worker), "status.json"))) ?? {
|
|
919
|
+
state: "unknown",
|
|
920
|
+
updated_at: now(),
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
async function readWorkerLifecycleRecord(dir: string, worker: JwcTeamWorker): Promise<JwcTeamWorkerLifecycle> {
|
|
926
|
+
const workerStatus = await readWorkerStatusFile(dir, worker.id);
|
|
927
|
+
const heartbeat = await readJsonFile<WorkerHeartbeatFile>(path.join(workerDir(dir, worker.id), "heartbeat.json"));
|
|
928
|
+
const rawLifecycle = await readJsonFile<Partial<JwcTeamWorkerLifecycle>>(workerLifecyclePath(dir, worker.id));
|
|
929
|
+
const shutdownAck = await readJsonFile<Record<string, unknown>>(
|
|
930
|
+
path.join(workerDir(dir, worker.id), "shutdown-ack.json"),
|
|
931
|
+
);
|
|
932
|
+
const lifecycle: JwcTeamWorkerLifecycle = {
|
|
933
|
+
worker: worker.id,
|
|
934
|
+
lifecycle_state: parseJwcTeamWorkerLifecycleState(rawLifecycle?.lifecycle_state),
|
|
935
|
+
worker_status_state: parseJwcWorkerStatusState(workerStatus.state),
|
|
936
|
+
pane_id: worker.pane_id ?? rawLifecycle?.pane_id,
|
|
937
|
+
updated_at: rawLifecycle?.updated_at ?? workerStatus.updated_at ?? now(),
|
|
938
|
+
};
|
|
939
|
+
if (typeof rawLifecycle?.pid === "number") lifecycle.pid = rawLifecycle.pid;
|
|
940
|
+
else if (typeof heartbeat?.pid === "number") lifecycle.pid = heartbeat.pid;
|
|
941
|
+
if (rawLifecycle?.started_at) lifecycle.started_at = rawLifecycle.started_at;
|
|
942
|
+
if (rawLifecycle?.stopped_at) lifecycle.stopped_at = rawLifecycle.stopped_at;
|
|
943
|
+
if (rawLifecycle?.stop_reason) lifecycle.stop_reason = rawLifecycle.stop_reason;
|
|
944
|
+
if (rawLifecycle?.shutdown_request_id) lifecycle.shutdown_request_id = rawLifecycle.shutdown_request_id;
|
|
945
|
+
if (rawLifecycle?.shutdown_requested_at) lifecycle.shutdown_requested_at = rawLifecycle.shutdown_requested_at;
|
|
946
|
+
if (
|
|
947
|
+
rawLifecycle?.shutdown_mode === "graceful" ||
|
|
948
|
+
rawLifecycle?.shutdown_mode === "force" ||
|
|
949
|
+
rawLifecycle?.shutdown_mode === "abort"
|
|
950
|
+
)
|
|
951
|
+
lifecycle.shutdown_mode = rawLifecycle.shutdown_mode;
|
|
952
|
+
if (typeof shutdownAck?.acknowledged_at === "string")
|
|
953
|
+
lifecycle.shutdown_acknowledged_at = shutdownAck.acknowledged_at;
|
|
954
|
+
if (typeof shutdownAck?.status === "string") lifecycle.shutdown_ack_status = shutdownAck.status;
|
|
955
|
+
return lifecycle;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
async function readWorkerLifecycleById(
|
|
959
|
+
dir: string,
|
|
960
|
+
config: JwcTeamConfig,
|
|
961
|
+
): Promise<Record<string, JwcTeamWorkerLifecycle>> {
|
|
962
|
+
const entries = await Promise.all(config.workers.map(worker => readWorkerLifecycleRecord(dir, worker)));
|
|
963
|
+
return Object.fromEntries(entries.map(entry => [entry.worker, entry]));
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
async function writeWorkerLifecycleRecord(
|
|
967
|
+
dir: string,
|
|
968
|
+
worker: JwcTeamWorker,
|
|
969
|
+
lifecycleState: JwcTeamWorkerLifecycleState,
|
|
970
|
+
updates: Partial<JwcTeamWorkerLifecycle> = {},
|
|
971
|
+
): Promise<JwcTeamWorkerLifecycle> {
|
|
972
|
+
const current = await readWorkerLifecycleRecord(dir, worker);
|
|
973
|
+
const next: JwcTeamWorkerLifecycle = {
|
|
974
|
+
...current,
|
|
975
|
+
...updates,
|
|
976
|
+
worker: worker.id,
|
|
977
|
+
lifecycle_state: lifecycleState,
|
|
978
|
+
worker_status_state: current.worker_status_state,
|
|
979
|
+
pane_id: updates.pane_id ?? worker.pane_id ?? current.pane_id,
|
|
980
|
+
updated_at: now(),
|
|
981
|
+
};
|
|
982
|
+
await writeJsonFile(workerLifecyclePath(dir, worker.id), next);
|
|
983
|
+
return next;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async function writeWorkerLifecycleForConfig(
|
|
987
|
+
dir: string,
|
|
988
|
+
config: JwcTeamConfig,
|
|
989
|
+
lifecycleState: JwcTeamWorkerLifecycleState,
|
|
990
|
+
updatesFor: (worker: JwcTeamWorker) => Partial<JwcTeamWorkerLifecycle> = () => ({}),
|
|
991
|
+
): Promise<Record<string, JwcTeamWorkerLifecycle>> {
|
|
992
|
+
const entries = await Promise.all(
|
|
993
|
+
config.workers.map(worker => writeWorkerLifecycleRecord(dir, worker, lifecycleState, updatesFor(worker))),
|
|
994
|
+
);
|
|
995
|
+
return Object.fromEntries(entries.map(entry => [entry.worker, entry]));
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function teamModeStatePath(): string {
|
|
999
|
+
return path.join(".jwc", "state", "team-state.json");
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export async function persistJwcTeamModeStateSummary(snapshot: JwcTeamSnapshot, cwd = process.cwd()): Promise<void> {
|
|
1003
|
+
const active = snapshot.phase !== "complete" && snapshot.phase !== "cancelled";
|
|
1004
|
+
const updatedAt = now();
|
|
1005
|
+
await writeWorkflowEnvelopeAtomic(
|
|
1006
|
+
teamModeStatePath(),
|
|
1007
|
+
{
|
|
1008
|
+
skill: "team",
|
|
1009
|
+
version: WORKFLOW_STATE_VERSION,
|
|
1010
|
+
active,
|
|
1011
|
+
current_phase: snapshot.phase,
|
|
1012
|
+
team_name: snapshot.team_name,
|
|
1013
|
+
task_counts: snapshot.task_counts,
|
|
1014
|
+
updated_at: updatedAt,
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
cwd,
|
|
1018
|
+
receipt: {
|
|
1019
|
+
cwd,
|
|
1020
|
+
skill: "team",
|
|
1021
|
+
owner: "jwc-runtime",
|
|
1022
|
+
command: "jwc team sync-team-summary",
|
|
1023
|
+
nowIso: updatedAt,
|
|
1024
|
+
},
|
|
1025
|
+
audit: { category: "state", verb: "sync-team-summary", owner: "jwc-runtime", skill: "team" },
|
|
1026
|
+
},
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function appendLivenessRecoveryReason(
|
|
1031
|
+
reasons: JwcTeamLivenessRecoveryReason[],
|
|
1032
|
+
reason: JwcTeamLivenessRecoveryReason,
|
|
1033
|
+
): void {
|
|
1034
|
+
if (!reasons.includes(reason)) reasons.push(reason);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function isPastTimestamp(value: string | undefined): boolean {
|
|
1038
|
+
if (!value) return false;
|
|
1039
|
+
const timestamp = Date.parse(value);
|
|
1040
|
+
return Number.isFinite(timestamp) && timestamp <= Date.now();
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function readClaimRecord(value: unknown): JwcTeamTaskClaim | undefined {
|
|
1044
|
+
if (!isRecord(value)) return undefined;
|
|
1045
|
+
const owner = typeof value.owner === "string" ? value.owner : "";
|
|
1046
|
+
const token = typeof value.token === "string" ? value.token : "";
|
|
1047
|
+
const leasedUntil = typeof value.leased_until === "string" ? value.leased_until : "";
|
|
1048
|
+
if (!owner || !token || !leasedUntil) return undefined;
|
|
1049
|
+
return { owner, token, leased_until: leasedUntil };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function isWorkerHeartbeatStale(
|
|
1053
|
+
worker: JwcTeamWorker,
|
|
1054
|
+
heartbeat: WorkerHeartbeatFile | null,
|
|
1055
|
+
env: NodeJS.ProcessEnv,
|
|
1056
|
+
): boolean {
|
|
1057
|
+
const thresholdMs = parseDurationEnv(env, "GJC_TEAM_HEARTBEAT_STALE_MS", 120_000);
|
|
1058
|
+
if (thresholdMs <= 0) return false;
|
|
1059
|
+
const heartbeatAt = Date.parse(heartbeat?.last_turn_at ?? worker.last_heartbeat);
|
|
1060
|
+
return Number.isFinite(heartbeatAt) && Date.now() - heartbeatAt >= thresholdMs;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
async function detectJwcTeamWorkerLivenessReasons(
|
|
1064
|
+
dir: string,
|
|
1065
|
+
config: JwcTeamConfig,
|
|
1066
|
+
worker: JwcTeamWorker,
|
|
1067
|
+
env: NodeJS.ProcessEnv,
|
|
1068
|
+
): Promise<JwcTeamLivenessRecoveryReason[]> {
|
|
1069
|
+
const reasons: JwcTeamLivenessRecoveryReason[] = [];
|
|
1070
|
+
const lifecycle = await readWorkerLifecycleRecord(dir, worker);
|
|
1071
|
+
const heartbeat = await readJsonFile<WorkerHeartbeatFile>(path.join(workerDir(dir, worker.id), "heartbeat.json"));
|
|
1072
|
+
if (lifecycle.lifecycle_state === "failed") appendLivenessRecoveryReason(reasons, "worker_lifecycle_failed");
|
|
1073
|
+
if (lifecycle.lifecycle_state === "stopped") appendLivenessRecoveryReason(reasons, "worker_lifecycle_stopped");
|
|
1074
|
+
if (isWorkerHeartbeatStale(worker, heartbeat, env)) appendLivenessRecoveryReason(reasons, "stale_heartbeat");
|
|
1075
|
+
if (!config.dry_run && (!worker.pane_id?.startsWith("%") || !paneBelongsToTeamTarget(config, worker.pane_id)))
|
|
1076
|
+
appendLivenessRecoveryReason(reasons, "missing_pane");
|
|
1077
|
+
return reasons;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
async function reconcileJwcTeamStaleClaims(
|
|
1081
|
+
teamName: string,
|
|
1082
|
+
dir: string,
|
|
1083
|
+
config: JwcTeamConfig,
|
|
1084
|
+
env: NodeJS.ProcessEnv,
|
|
1085
|
+
): Promise<JwcTeamLivenessRecoveryResult> {
|
|
1086
|
+
const staleWorkers: Record<string, JwcTeamLivenessRecoveryReason[]> = {};
|
|
1087
|
+
for (const worker of config.workers) {
|
|
1088
|
+
const reasons = await detectJwcTeamWorkerLivenessReasons(dir, config, worker, env);
|
|
1089
|
+
if (reasons.length === 0) continue;
|
|
1090
|
+
staleWorkers[worker.id] = reasons;
|
|
1091
|
+
if (reasons.includes("missing_pane") && reasons.includes("worker_lifecycle_stopped") === false) {
|
|
1092
|
+
await writeWorkerLifecycleRecord(dir, worker, "failed", { stop_reason: "pane_missing" });
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const recoveredClaims: JwcTeamRecoveredClaim[] = [];
|
|
1097
|
+
for (const task of await readTasks(dir)) {
|
|
1098
|
+
if (task.status === "completed" || task.status === "failed") continue;
|
|
1099
|
+
const claimPath = path.join(dir, "claims", `${task.id}.json`);
|
|
1100
|
+
const diskClaim = readClaimRecord(await readJsonFile<unknown>(claimPath));
|
|
1101
|
+
const claim = task.claim ?? diskClaim;
|
|
1102
|
+
if (!claim) continue;
|
|
1103
|
+
|
|
1104
|
+
const reasons = [...(staleWorkers[claim.owner] ?? [])];
|
|
1105
|
+
if (isPastTimestamp(claim.leased_until)) appendLivenessRecoveryReason(reasons, "claim_expired");
|
|
1106
|
+
if (reasons.length === 0) continue;
|
|
1107
|
+
|
|
1108
|
+
await fs.rm(claimPath, { force: true });
|
|
1109
|
+
recoveredClaims.push({ task_id: task.id, worker: claim.owner, reasons });
|
|
1110
|
+
if (task.status !== "in_progress") {
|
|
1111
|
+
await appendEvent(dir, {
|
|
1112
|
+
type: "task_claim_recovered",
|
|
1113
|
+
task_id: task.id,
|
|
1114
|
+
worker: claim.owner,
|
|
1115
|
+
message: "Removed stale task claim file",
|
|
1116
|
+
data: { reasons },
|
|
1117
|
+
});
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const recoveredTask = normalizeTask({
|
|
1122
|
+
...task,
|
|
1123
|
+
status: "pending",
|
|
1124
|
+
assignee: undefined,
|
|
1125
|
+
claim: undefined,
|
|
1126
|
+
version: task.version + 1,
|
|
1127
|
+
updated_at: now(),
|
|
1128
|
+
});
|
|
1129
|
+
await writeTask(dir, recoveredTask);
|
|
1130
|
+
await appendEvent(dir, {
|
|
1131
|
+
type: "task_claim_recovered",
|
|
1132
|
+
task_id: task.id,
|
|
1133
|
+
worker: claim.owner,
|
|
1134
|
+
message: "Recovered task from stale worker claim",
|
|
1135
|
+
data: { reasons },
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (recoveredClaims.length > 0)
|
|
1140
|
+
await appendTelemetry(dir, {
|
|
1141
|
+
type: "team_liveness_recovery",
|
|
1142
|
+
message: `Recovered ${recoveredClaims.length} stale team task claim(s)`,
|
|
1143
|
+
data: { team_name: teamName, recovered_claims: recoveredClaims },
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
return { recovered_claims: recoveredClaims, stale_workers: staleWorkers };
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export async function recoverJwcTeamStaleClaims(
|
|
1150
|
+
teamName: string,
|
|
1151
|
+
cwd = process.cwd(),
|
|
1152
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1153
|
+
): Promise<JwcTeamLivenessRecoveryResult> {
|
|
1154
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
1155
|
+
const config = await readConfig(dir);
|
|
1156
|
+
return reconcileJwcTeamStaleClaims(teamName, dir, config, env);
|
|
1157
|
+
}
|
|
1158
|
+
function normalizeOptionalTaskString(value: unknown): string | undefined {
|
|
1159
|
+
if (typeof value !== "string") return undefined;
|
|
1160
|
+
const trimmed = value.trim();
|
|
1161
|
+
return trimmed || undefined;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function normalizeOptionalTaskStringArray(value: unknown): string[] | undefined {
|
|
1165
|
+
if (!Array.isArray(value)) return undefined;
|
|
1166
|
+
const items = Array.from(
|
|
1167
|
+
new Set(value.map(item => (typeof item === "string" ? item.trim() : "")).filter(item => item.length > 0)),
|
|
1168
|
+
).sort();
|
|
1169
|
+
return items.length > 0 ? items : undefined;
|
|
1170
|
+
}
|
|
1171
|
+
type JwcTeamTaskMetadataInput = Partial<
|
|
1172
|
+
Pick<JwcTeamTask, "owner" | "lane" | "required_role" | "allowed_roles" | "depends_on" | "blocked_by">
|
|
1173
|
+
>;
|
|
1174
|
+
|
|
1175
|
+
function taskMetadataFromInput(input: Record<string, unknown>, includeOwner = false): JwcTeamTaskMetadataInput {
|
|
1176
|
+
const metadata: JwcTeamTaskMetadataInput = {};
|
|
1177
|
+
const owner = normalizeOptionalTaskString(input.owner);
|
|
1178
|
+
const lane = normalizeOptionalTaskString(input.lane);
|
|
1179
|
+
const requiredRole = normalizeOptionalTaskString(input.required_role ?? input.requiredRole);
|
|
1180
|
+
const allowedRoles = normalizeOptionalTaskStringArray(input.allowed_roles ?? input.allowedRoles);
|
|
1181
|
+
const dependsOn = normalizeOptionalTaskStringArray(input.depends_on ?? input.dependsOn);
|
|
1182
|
+
const blockedBy = normalizeOptionalTaskStringArray(input.blocked_by ?? input.blockedBy);
|
|
1183
|
+
if (includeOwner && owner) metadata.owner = owner;
|
|
1184
|
+
if (lane) metadata.lane = lane;
|
|
1185
|
+
if (requiredRole) metadata.required_role = requiredRole;
|
|
1186
|
+
if (allowedRoles) metadata.allowed_roles = allowedRoles;
|
|
1187
|
+
if (dependsOn) metadata.depends_on = dependsOn;
|
|
1188
|
+
if (blockedBy) metadata.blocked_by = blockedBy;
|
|
1189
|
+
return metadata;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function normalizeTask(raw: JwcTeamTask): JwcTeamTask {
|
|
1193
|
+
const status = raw.status === ("complete" as JwcTeamTaskStatus) ? "completed" : raw.status;
|
|
1194
|
+
return {
|
|
1195
|
+
...raw,
|
|
1196
|
+
status,
|
|
1197
|
+
subject: raw.subject ?? raw.title,
|
|
1198
|
+
description: raw.description ?? raw.objective,
|
|
1199
|
+
title: raw.title ?? raw.subject,
|
|
1200
|
+
objective: raw.objective ?? raw.description,
|
|
1201
|
+
version: raw.version ?? 1,
|
|
1202
|
+
lane: normalizeOptionalTaskString(raw.lane),
|
|
1203
|
+
required_role: normalizeOptionalTaskString(raw.required_role),
|
|
1204
|
+
allowed_roles: normalizeOptionalTaskStringArray(raw.allowed_roles),
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const GJC_TEAM_INTEGRATION_ATTENTION_STATUSES = new Set<JwcTeamIntegrationStatus>([
|
|
1209
|
+
"integration_failed",
|
|
1210
|
+
"merge_conflict",
|
|
1211
|
+
"cherry_pick_conflict",
|
|
1212
|
+
"rebase_conflict",
|
|
1213
|
+
]);
|
|
1214
|
+
const GJC_TEAM_INTEGRATION_SETTLED_STATUSES = new Set<JwcTeamIntegrationStatus>(["idle", "integrated"]);
|
|
1215
|
+
|
|
1216
|
+
async function hasPendingJwcTeamIntegration(
|
|
1217
|
+
dir: string,
|
|
1218
|
+
config: JwcTeamConfig,
|
|
1219
|
+
monitor: JwcTeamMonitorSnapshot | null,
|
|
1220
|
+
): Promise<boolean> {
|
|
1221
|
+
for (const worker of config.workers) {
|
|
1222
|
+
const integration = monitor?.integration_by_worker?.[worker.id];
|
|
1223
|
+
if (integration?.status && GJC_TEAM_INTEGRATION_ATTENTION_STATUSES.has(integration.status)) return true;
|
|
1224
|
+
|
|
1225
|
+
const request = await readJsonFile<JwcWorkerIntegrationDedupeState>(workerIntegrationDedupePath(dir, worker.id));
|
|
1226
|
+
if (!request?.last_requested_at) continue;
|
|
1227
|
+
if (!integration?.status || !integration.updated_at) return true;
|
|
1228
|
+
if (GJC_TEAM_INTEGRATION_ATTENTION_STATUSES.has(integration.status)) return true;
|
|
1229
|
+
if (
|
|
1230
|
+
GJC_TEAM_INTEGRATION_SETTLED_STATUSES.has(integration.status) &&
|
|
1231
|
+
integration.updated_at >= request.last_requested_at
|
|
1232
|
+
) {
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
return true;
|
|
1236
|
+
}
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async function resolveJwcTeamSnapshotPhase(
|
|
1241
|
+
dir: string,
|
|
1242
|
+
config: JwcTeamConfig,
|
|
1243
|
+
storedPhase: JwcTeamPhase,
|
|
1244
|
+
tasks: JwcTeamTask[],
|
|
1245
|
+
monitor: JwcTeamMonitorSnapshot | null,
|
|
1246
|
+
): Promise<JwcTeamPhase> {
|
|
1247
|
+
if (storedPhase !== "running") return storedPhase;
|
|
1248
|
+
if (tasks.length === 0 || !tasks.every(isJwcTeamTaskCompletionVerified)) return storedPhase;
|
|
1249
|
+
return (await hasPendingJwcTeamIntegration(dir, config, monitor)) ? "awaiting_integration" : storedPhase;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1253
|
+
return typeof value === "object" && value != null;
|
|
1254
|
+
}
|
|
1255
|
+
const GJC_TEAM_COMPLETION_EVIDENCE_SUMMARY_MAX = 4_000;
|
|
1256
|
+
const GJC_TEAM_COMPLETION_EVIDENCE_OUTPUT_MAX = 8_000;
|
|
1257
|
+
const GJC_TEAM_COMMAND_EVIDENCE_STATUSES = new Set<JwcTeamTaskCompletionEvidenceStatus>([
|
|
1258
|
+
"passed",
|
|
1259
|
+
"failed",
|
|
1260
|
+
"not_run",
|
|
1261
|
+
]);
|
|
1262
|
+
const GJC_TEAM_VERIFICATION_EVIDENCE_STATUSES = new Set<JwcTeamTaskCompletionEvidenceStatus>(["verified", "rejected"]);
|
|
1263
|
+
|
|
1264
|
+
function completionEvidenceError(taskId: string, field: string): Error {
|
|
1265
|
+
return new Error(`invalid_completion_evidence:${taskId}:${field}`);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function trimRequiredCompletionEvidenceString(
|
|
1269
|
+
taskId: string,
|
|
1270
|
+
field: string,
|
|
1271
|
+
value: unknown,
|
|
1272
|
+
maxLength = GJC_TEAM_COMPLETION_EVIDENCE_SUMMARY_MAX,
|
|
1273
|
+
): string {
|
|
1274
|
+
if (typeof value !== "string") throw completionEvidenceError(taskId, field);
|
|
1275
|
+
const trimmed = value.trim();
|
|
1276
|
+
if (!trimmed || trimmed.length > maxLength) throw completionEvidenceError(taskId, field);
|
|
1277
|
+
return trimmed;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function trimOptionalCompletionEvidenceString(
|
|
1281
|
+
taskId: string,
|
|
1282
|
+
field: string,
|
|
1283
|
+
value: unknown,
|
|
1284
|
+
maxLength = GJC_TEAM_COMPLETION_EVIDENCE_OUTPUT_MAX,
|
|
1285
|
+
): string | undefined {
|
|
1286
|
+
if (value == null) return undefined;
|
|
1287
|
+
if (typeof value !== "string") throw completionEvidenceError(taskId, field);
|
|
1288
|
+
const trimmed = value.trim();
|
|
1289
|
+
if (!trimmed) return undefined;
|
|
1290
|
+
if (trimmed.length > maxLength) throw completionEvidenceError(taskId, field);
|
|
1291
|
+
return trimmed;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function normalizeJwcTeamCompletionEvidenceStatus(
|
|
1295
|
+
taskId: string,
|
|
1296
|
+
kind: JwcTeamTaskCompletionEvidenceKind,
|
|
1297
|
+
value: unknown,
|
|
1298
|
+
): JwcTeamTaskCompletionEvidenceStatus {
|
|
1299
|
+
const status = trimRequiredCompletionEvidenceString(taskId, "items.status", value);
|
|
1300
|
+
const allowed = kind === "command" ? GJC_TEAM_COMMAND_EVIDENCE_STATUSES : GJC_TEAM_VERIFICATION_EVIDENCE_STATUSES;
|
|
1301
|
+
if (!allowed.has(status as JwcTeamTaskCompletionEvidenceStatus))
|
|
1302
|
+
throw completionEvidenceError(taskId, "items.status");
|
|
1303
|
+
return status as JwcTeamTaskCompletionEvidenceStatus;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function normalizeJwcTeamCompletionEvidenceItem(taskId: string, value: unknown): JwcTeamTaskCompletionEvidenceItem {
|
|
1307
|
+
if (!isRecord(value) || Array.isArray(value)) throw completionEvidenceError(taskId, "items");
|
|
1308
|
+
const kind = trimRequiredCompletionEvidenceString(taskId, "items.kind", value.kind);
|
|
1309
|
+
if (kind !== "command" && kind !== "inspection" && kind !== "artifact")
|
|
1310
|
+
throw completionEvidenceError(taskId, "items.kind");
|
|
1311
|
+
const status = normalizeJwcTeamCompletionEvidenceStatus(taskId, kind, value.status);
|
|
1312
|
+
const item: JwcTeamTaskCompletionEvidenceItem = {
|
|
1313
|
+
kind,
|
|
1314
|
+
status,
|
|
1315
|
+
summary: trimRequiredCompletionEvidenceString(taskId, "items.summary", value.summary),
|
|
1316
|
+
};
|
|
1317
|
+
const command = trimOptionalCompletionEvidenceString(taskId, "items.command", value.command);
|
|
1318
|
+
const artifact = trimOptionalCompletionEvidenceString(taskId, "items.artifact", value.artifact);
|
|
1319
|
+
const location = trimOptionalCompletionEvidenceString(taskId, "items.location", value.location);
|
|
1320
|
+
const output = trimOptionalCompletionEvidenceString(taskId, "items.output", value.output);
|
|
1321
|
+
if (kind === "command" && !command) throw completionEvidenceError(taskId, "items.command");
|
|
1322
|
+
if (command) item.command = command;
|
|
1323
|
+
if (artifact) item.artifact = artifact;
|
|
1324
|
+
if (location) item.location = location;
|
|
1325
|
+
if (output) item.output = output;
|
|
1326
|
+
return item;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function normalizeJwcTeamCompletionEvidenceFiles(taskId: string, value: unknown): string[] | undefined {
|
|
1330
|
+
if (value == null) return undefined;
|
|
1331
|
+
if (!Array.isArray(value)) throw completionEvidenceError(taskId, "files");
|
|
1332
|
+
const files = new Set<string>();
|
|
1333
|
+
for (const entry of value) {
|
|
1334
|
+
if (typeof entry !== "string") throw completionEvidenceError(taskId, "files");
|
|
1335
|
+
const filePath = entry.trim().replace(/\\/g, "/");
|
|
1336
|
+
if (!filePath || filePath.includes("\0") || path.isAbsolute(filePath) || filePath.split("/").includes("..")) {
|
|
1337
|
+
throw completionEvidenceError(taskId, "files");
|
|
1338
|
+
}
|
|
1339
|
+
files.add(filePath);
|
|
1340
|
+
}
|
|
1341
|
+
return files.size > 0 ? [...files].sort() : undefined;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function isJwcTeamCompletionEvidenceItemVerified(item: JwcTeamTaskCompletionEvidenceItem): boolean {
|
|
1345
|
+
return (
|
|
1346
|
+
(item.kind === "command" && item.status === "passed") ||
|
|
1347
|
+
((item.kind === "inspection" || item.kind === "artifact") && item.status === "verified")
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function normalizeJwcTeamTaskCompletionEvidence(
|
|
1352
|
+
taskId: string,
|
|
1353
|
+
owner: string,
|
|
1354
|
+
input: unknown,
|
|
1355
|
+
recordedAt = now(),
|
|
1356
|
+
): JwcTeamTaskCompletionEvidence {
|
|
1357
|
+
if (!isRecord(input) || Array.isArray(input)) throw new Error(`completion_evidence_required:${taskId}`);
|
|
1358
|
+
const itemsValue = input.items;
|
|
1359
|
+
if (!Array.isArray(itemsValue) || itemsValue.length === 0) throw completionEvidenceError(taskId, "items");
|
|
1360
|
+
const items = itemsValue.map(item => normalizeJwcTeamCompletionEvidenceItem(taskId, item));
|
|
1361
|
+
if (!items.some(isJwcTeamCompletionEvidenceItemVerified))
|
|
1362
|
+
throw new Error(`completion_evidence_no_verified_item:${taskId}`);
|
|
1363
|
+
const evidence: JwcTeamTaskCompletionEvidence = {
|
|
1364
|
+
summary: trimRequiredCompletionEvidenceString(taskId, "summary", input.summary),
|
|
1365
|
+
items,
|
|
1366
|
+
recorded_by: owner,
|
|
1367
|
+
recorded_at: recordedAt,
|
|
1368
|
+
};
|
|
1369
|
+
const files = normalizeJwcTeamCompletionEvidenceFiles(taskId, input.files);
|
|
1370
|
+
const notes = trimOptionalCompletionEvidenceString(taskId, "notes", input.notes);
|
|
1371
|
+
if (files) evidence.files = files;
|
|
1372
|
+
if (notes) evidence.notes = notes;
|
|
1373
|
+
return evidence;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function getJwcTeamTaskCompletionEvidenceFailure(task: JwcTeamTask): string | null {
|
|
1377
|
+
if (task.status !== "completed") return `task_not_completed:${task.id}`;
|
|
1378
|
+
const evidence = task.completion_evidence;
|
|
1379
|
+
if (!isRecord(evidence) || Array.isArray(evidence)) return `completion_evidence_required:${task.id}`;
|
|
1380
|
+
if (typeof evidence.recorded_by !== "string" || evidence.recorded_by.trim().length === 0)
|
|
1381
|
+
return `invalid_completion_evidence:${task.id}:recorded_by`;
|
|
1382
|
+
if (typeof evidence.recorded_at !== "string" || evidence.recorded_at.trim().length === 0)
|
|
1383
|
+
return `invalid_completion_evidence:${task.id}:recorded_at`;
|
|
1384
|
+
try {
|
|
1385
|
+
normalizeJwcTeamTaskCompletionEvidence(task.id, evidence.recorded_by.trim(), evidence, evidence.recorded_at);
|
|
1386
|
+
return null;
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
return error instanceof Error ? error.message : `invalid_completion_evidence:${task.id}:unknown`;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function isJwcTeamTaskCompletionVerified(task: JwcTeamTask): boolean {
|
|
1393
|
+
return getJwcTeamTaskCompletionEvidenceFailure(task) == null;
|
|
1394
|
+
}
|
|
1395
|
+
function roleValuesForWorker(worker: JwcTeamWorker): Set<string> {
|
|
1396
|
+
return new Set([worker.role, worker.agent_type].map(value => value.trim()).filter(value => value.length > 0));
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
function getJwcTeamTaskClaimEligibilityReason(
|
|
1400
|
+
task: JwcTeamTask,
|
|
1401
|
+
worker: JwcTeamWorker,
|
|
1402
|
+
tasks: JwcTeamTask[],
|
|
1403
|
+
): string | null {
|
|
1404
|
+
if (task.status !== "pending") return `task_not_pending:${task.id}`;
|
|
1405
|
+
if (task.owner && task.owner !== worker.id) return `task_owner_mismatch:${task.id}:${task.owner}`;
|
|
1406
|
+
if (task.assignee && task.assignee !== worker.id) return `task_assignee_mismatch:${task.id}:${task.assignee}`;
|
|
1407
|
+
|
|
1408
|
+
const workerRoles = roleValuesForWorker(worker);
|
|
1409
|
+
if (task.required_role && !workerRoles.has(task.required_role))
|
|
1410
|
+
return `task_role_mismatch:${task.id}:${task.required_role}`;
|
|
1411
|
+
if (task.allowed_roles?.length && !task.allowed_roles.some(role => workerRoles.has(role)))
|
|
1412
|
+
return `task_role_mismatch:${task.id}:${task.allowed_roles.join(",")}`;
|
|
1413
|
+
|
|
1414
|
+
if (task.blocked_by?.length) return `task_blocked:${task.id}:${task.blocked_by.join(",")}`;
|
|
1415
|
+
for (const dependencyId of task.depends_on ?? []) {
|
|
1416
|
+
const dependency = tasks.find(candidate => candidate.id === dependencyId);
|
|
1417
|
+
if (!dependency || !isJwcTeamTaskCompletionVerified(dependency))
|
|
1418
|
+
return `task_dependency_incomplete:${task.id}:${dependencyId}`;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
return null;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
async function getActiveClaimReason(dir: string, task: JwcTeamTask): Promise<string | null> {
|
|
1425
|
+
const claimPath = path.join(dir, "claims", `${task.id}.json`);
|
|
1426
|
+
const diskClaim = readClaimRecord(await readJsonFile<unknown>(claimPath));
|
|
1427
|
+
const claim = task.claim ?? diskClaim;
|
|
1428
|
+
if (!claim || isPastTimestamp(claim.leased_until)) return null;
|
|
1429
|
+
return `task_already_claimed:${task.id}`;
|
|
1430
|
+
}
|
|
1431
|
+
function isJwcTeamTaskRecord(value: unknown): value is JwcTeamTask {
|
|
1432
|
+
return (
|
|
1433
|
+
isRecord(value) &&
|
|
1434
|
+
typeof value.id === "string" &&
|
|
1435
|
+
typeof value.status === "string" &&
|
|
1436
|
+
(isJwcTeamTaskStatus(value.status) || value.status === "complete") &&
|
|
1437
|
+
(typeof value.subject === "string" || typeof value.title === "string") &&
|
|
1438
|
+
(typeof value.description === "string" || typeof value.objective === "string")
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
function isJwcTeamTaskFile(entry: { isFile(): boolean; name: string }): boolean {
|
|
1442
|
+
return entry.isFile() && entry.name.endsWith(".json") && !entry.name.endsWith(".evidence.json");
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
async function readTasks(dir: string): Promise<JwcTeamTask[]> {
|
|
1446
|
+
try {
|
|
1447
|
+
const entries = await fs.readdir(path.join(dir, "tasks"), { withFileTypes: true });
|
|
1448
|
+
const tasks = await Promise.all(
|
|
1449
|
+
entries.filter(isJwcTeamTaskFile).map(entry => readJsonFile<unknown>(path.join(dir, "tasks", entry.name))),
|
|
1450
|
+
);
|
|
1451
|
+
return tasks
|
|
1452
|
+
.filter(isJwcTeamTaskRecord)
|
|
1453
|
+
.map(normalizeTask)
|
|
1454
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
if (isEnoent(error)) return [];
|
|
1457
|
+
throw error;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
async function writeTask(dir: string, task: JwcTeamTask): Promise<void> {
|
|
1461
|
+
await writeJsonFile(taskPath(dir, task.id), normalizeTask(task));
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
async function findTeamDir(
|
|
1465
|
+
teamName: string,
|
|
1466
|
+
cwd = process.cwd(),
|
|
1467
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1468
|
+
): Promise<string> {
|
|
1469
|
+
const root = resolveJwcTeamStateRoot(cwd, env);
|
|
1470
|
+
const exact = teamDir(root, teamName);
|
|
1471
|
+
if (await readJsonFile<JwcTeamConfig>(path.join(exact, "config.json"))) return exact;
|
|
1472
|
+
const candidates = await listJwcTeams(cwd, env);
|
|
1473
|
+
const input = sanitizeName(teamName);
|
|
1474
|
+
const matches = candidates.filter(
|
|
1475
|
+
candidate => candidate.team_name === input || sanitizeName(candidate.display_name) === input,
|
|
1476
|
+
);
|
|
1477
|
+
if (matches.length === 1) return matches[0].state_dir;
|
|
1478
|
+
if (matches.length > 1)
|
|
1479
|
+
throw new Error(`ambiguous_team_name:${teamName}:${matches.map(match => match.team_name).join(",")}`);
|
|
1480
|
+
throw new Error(`team_not_found:${teamName}`);
|
|
1481
|
+
}
|
|
1482
|
+
function buildWorkers(count: number, agentType: string, stateRoot?: string): JwcTeamWorker[] {
|
|
1483
|
+
return Array.from({ length: count }, (_, index) => {
|
|
1484
|
+
const id = `worker-${index + 1}`;
|
|
1485
|
+
return {
|
|
1486
|
+
id,
|
|
1487
|
+
name: id,
|
|
1488
|
+
index: index + 1,
|
|
1489
|
+
agent_type: agentType,
|
|
1490
|
+
role: agentType,
|
|
1491
|
+
status: "starting",
|
|
1492
|
+
last_heartbeat: now(),
|
|
1493
|
+
assigned_tasks: [],
|
|
1494
|
+
team_state_root: stateRoot,
|
|
1495
|
+
};
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
function sanitizePathToken(value: string): string {
|
|
1499
|
+
return sanitizeName(value) || "default";
|
|
1500
|
+
}
|
|
1501
|
+
function runGitResult(cwd: string, args: string[]): GitResult {
|
|
1502
|
+
const result = Bun.spawnSync(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
1503
|
+
return {
|
|
1504
|
+
ok: result.exitCode === 0,
|
|
1505
|
+
stdout: result.stdout.toString().trim(),
|
|
1506
|
+
stderr: result.stderr.toString().trim(),
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
function runGit(cwd: string, args: string[]): string {
|
|
1510
|
+
const result = runGitResult(cwd, args);
|
|
1511
|
+
if (result.ok) return result.stdout;
|
|
1512
|
+
throw new Error(result.stderr || `git ${args.join(" ")} failed`);
|
|
1513
|
+
}
|
|
1514
|
+
function tryRunGit(cwd: string, args: string[]): string | null {
|
|
1515
|
+
const result = runGitResult(cwd, args);
|
|
1516
|
+
return result.ok ? result.stdout : null;
|
|
1517
|
+
}
|
|
1518
|
+
function isGitRepository(cwd: string): boolean {
|
|
1519
|
+
return tryRunGit(cwd, ["rev-parse", "--show-toplevel"]) != null;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function parseWorktreeMode(args: string[]): { mode: JwcTeamWorktreeMode; remainingArgs: string[] } {
|
|
1523
|
+
let mode: JwcTeamWorktreeMode = { enabled: false };
|
|
1524
|
+
const remainingArgs: string[] = [];
|
|
1525
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1526
|
+
const arg = args[index] ?? "";
|
|
1527
|
+
if (arg === "--worktree" || arg === "-w") {
|
|
1528
|
+
const next = args[index + 1];
|
|
1529
|
+
if (typeof next === "string" && next.length > 0 && !next.startsWith("-") && !next.includes(":")) {
|
|
1530
|
+
mode = { enabled: true, detached: false, name: next };
|
|
1531
|
+
index += 1;
|
|
1532
|
+
} else mode = { enabled: true, detached: true, name: null };
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (arg.startsWith("--worktree=")) {
|
|
1536
|
+
const name = arg.slice("--worktree=".length).trim();
|
|
1537
|
+
mode = name ? { enabled: true, detached: false, name } : { enabled: true, detached: true, name: null };
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (arg.startsWith("-w=") || (arg.startsWith("-w") && arg.length > 2)) {
|
|
1541
|
+
const name = arg.startsWith("-w=") ? arg.slice("-w=".length).trim() : arg.slice(2).trim();
|
|
1542
|
+
mode = name ? { enabled: true, detached: false, name } : { enabled: true, detached: true, name: null };
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
remainingArgs.push(arg);
|
|
1546
|
+
}
|
|
1547
|
+
return { mode, remainingArgs };
|
|
1548
|
+
}
|
|
1549
|
+
function resolveDefaultWorktreeMode(mode?: JwcTeamWorktreeMode): JwcTeamWorktreeMode {
|
|
1550
|
+
return mode?.enabled ? mode : { enabled: true, detached: true, name: null };
|
|
1551
|
+
}
|
|
1552
|
+
function branchExists(repoRoot: string, branchName: string): boolean {
|
|
1553
|
+
return (
|
|
1554
|
+
Bun.spawnSync(["git", "show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], {
|
|
1555
|
+
cwd: repoRoot,
|
|
1556
|
+
stdout: "ignore",
|
|
1557
|
+
stderr: "ignore",
|
|
1558
|
+
}).exitCode === 0
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
function worktreeIsDirty(worktreePath: string): boolean {
|
|
1562
|
+
return runGit(worktreePath, ["status", "--porcelain"]).trim().length > 0;
|
|
1563
|
+
}
|
|
1564
|
+
function worktreeHead(worktreePath: string): string {
|
|
1565
|
+
return runGit(worktreePath, ["rev-parse", "HEAD"]);
|
|
1566
|
+
}
|
|
1567
|
+
async function pathExists(filePath: string): Promise<boolean> {
|
|
1568
|
+
try {
|
|
1569
|
+
await fs.access(filePath);
|
|
1570
|
+
return true;
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
if (isEnoent(error)) return false;
|
|
1573
|
+
throw error;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
function findWorktreePath(repoRoot: string, worktreePath: string): string | null {
|
|
1577
|
+
const raw = runGit(repoRoot, ["worktree", "list", "--porcelain"]);
|
|
1578
|
+
const resolved = path.resolve(worktreePath);
|
|
1579
|
+
for (const line of raw.split(/\r?\n/))
|
|
1580
|
+
if (line.startsWith("worktree ") && path.resolve(line.slice("worktree ".length)) === resolved) return resolved;
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
async function ensureWorkerWorktree(
|
|
1584
|
+
cwd: string,
|
|
1585
|
+
dir: string,
|
|
1586
|
+
teamName: string,
|
|
1587
|
+
worker: JwcTeamWorker,
|
|
1588
|
+
mode: JwcTeamWorktreeMode,
|
|
1589
|
+
): Promise<JwcTeamWorker> {
|
|
1590
|
+
if (!mode.enabled) return worker;
|
|
1591
|
+
if (!isGitRepository(cwd)) throw new Error(`team_worktree_requires_git_repo:${cwd}`);
|
|
1592
|
+
const repoRoot = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
1593
|
+
const baseRef = runGit(repoRoot, ["rev-parse", "HEAD"]);
|
|
1594
|
+
const worktreePath = path.join(dir, "worktrees", worker.id);
|
|
1595
|
+
const existing = findWorktreePath(repoRoot, worktreePath);
|
|
1596
|
+
let created = false;
|
|
1597
|
+
const branchName = mode.detached
|
|
1598
|
+
? null
|
|
1599
|
+
: `${mode.name}/${sanitizePathToken(teamName)}/${sanitizePathToken(worker.id)}`;
|
|
1600
|
+
if (existing) {
|
|
1601
|
+
if (worktreeIsDirty(worktreePath)) throw new Error(`worktree_dirty:${worktreePath}`);
|
|
1602
|
+
if (mode.detached && worktreeHead(worktreePath) !== baseRef) throw new Error(`worktree_stale:${worktreePath}`);
|
|
1603
|
+
} else {
|
|
1604
|
+
if (await pathExists(worktreePath)) throw new Error(`worktree_path_conflict:${worktreePath}`);
|
|
1605
|
+
await fs.mkdir(path.dirname(worktreePath), { recursive: true });
|
|
1606
|
+
const args = mode.detached
|
|
1607
|
+
? ["worktree", "add", "--detach", worktreePath, baseRef]
|
|
1608
|
+
: branchExists(repoRoot, branchName ?? "")
|
|
1609
|
+
? ["worktree", "add", worktreePath, branchName ?? ""]
|
|
1610
|
+
: ["worktree", "add", "-b", branchName ?? "", worktreePath, baseRef];
|
|
1611
|
+
runGit(repoRoot, args);
|
|
1612
|
+
created = true;
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
...worker,
|
|
1616
|
+
worktree_repo_root: repoRoot,
|
|
1617
|
+
worktree_path: path.resolve(worktreePath),
|
|
1618
|
+
worktree_branch: branchName,
|
|
1619
|
+
worktree_detached: mode.detached,
|
|
1620
|
+
worktree_created: created,
|
|
1621
|
+
worktree_base_ref: baseRef,
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
export function resolveJwcTmuxCommand(env: NodeJS.ProcessEnv = process.env): string {
|
|
1626
|
+
return env.GJC_TEAM_TMUX_COMMAND?.trim() || "tmux";
|
|
1627
|
+
}
|
|
1628
|
+
function buildTeamTmuxLeaderRequirementMessage(detail?: string): string {
|
|
1629
|
+
const suffix = detail?.trim() ? `:${detail.trim()}` : "";
|
|
1630
|
+
return `gjc_team_requires_tmux_leader: run \`gjc --tmux\` first, then run \`jwc team ...\` inside that tmux-backed leader session, or use \`jwc team --dry-run\` for state-only smoke tests${suffix}`;
|
|
1631
|
+
}
|
|
1632
|
+
function readJwcTmuxProfileValue(tmuxCommand: string, sessionName: string): string {
|
|
1633
|
+
const result = Bun.spawnSync(
|
|
1634
|
+
[tmuxCommand, "show-options", "-qv", "-t", buildJwcTmuxExactOptionTarget(sessionName), GJC_TMUX_PROFILE_OPTION],
|
|
1635
|
+
{
|
|
1636
|
+
stdout: "pipe",
|
|
1637
|
+
stderr: "pipe",
|
|
1638
|
+
},
|
|
1639
|
+
);
|
|
1640
|
+
if (result.exitCode !== 0) return "";
|
|
1641
|
+
return result.stdout.toString().trim();
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function tagJwcTmuxSessionAsLeader(tmuxCommand: string, sessionName: string): boolean {
|
|
1645
|
+
// Write GJC's @gjc-profile ownership tag onto the current session. Uses the
|
|
1646
|
+
// exact-session option target (`=${sessionName}:`) — the same form as
|
|
1647
|
+
// readJwcTmuxProfileValue — so the set/get round-trip targets the same session.
|
|
1648
|
+
// Mirrors gjc tagTmuxSessionAsGjcLeader (gjc-runtime/team-runtime.ts:1829-1845).
|
|
1649
|
+
const result = Bun.spawnSync(
|
|
1650
|
+
[
|
|
1651
|
+
tmuxCommand,
|
|
1652
|
+
"set-option",
|
|
1653
|
+
"-t",
|
|
1654
|
+
buildJwcTmuxExactOptionTarget(sessionName),
|
|
1655
|
+
GJC_TMUX_PROFILE_OPTION,
|
|
1656
|
+
GJC_TMUX_PROFILE_VALUE,
|
|
1657
|
+
],
|
|
1658
|
+
{
|
|
1659
|
+
stdout: "pipe",
|
|
1660
|
+
stderr: "pipe",
|
|
1661
|
+
},
|
|
1662
|
+
);
|
|
1663
|
+
return result.exitCode === 0;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function readCurrentTmuxLeaderContext(tmuxCommand: string, env: NodeJS.ProcessEnv): JwcTmuxLeaderContext {
|
|
1667
|
+
const paneTarget = env.TMUX_PANE?.trim();
|
|
1668
|
+
const args = paneTarget
|
|
1669
|
+
? ["display-message", "-p", "-t", paneTarget, "#S:#I #{pane_id}"]
|
|
1670
|
+
: ["display-message", "-p", "#S:#I #{pane_id}"];
|
|
1671
|
+
const result = Bun.spawnSync([tmuxCommand, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
1672
|
+
if (result.exitCode !== 0) throw new Error(buildTeamTmuxLeaderRequirementMessage(result.stderr.toString()));
|
|
1673
|
+
const [sessionAndWindow = "", leaderPaneId = ""] = result.stdout.toString().trim().split(/\s+/);
|
|
1674
|
+
const [sessionName = "", windowIndex = ""] = sessionAndWindow.split(":");
|
|
1675
|
+
if (!sessionName || !windowIndex || !leaderPaneId.startsWith("%"))
|
|
1676
|
+
throw new Error(buildTeamTmuxLeaderRequirementMessage(`invalid_tmux_context:${result.stdout.toString().trim()}`));
|
|
1677
|
+
if (readJwcTmuxProfileValue(tmuxCommand, sessionName) !== GJC_TMUX_PROFILE_VALUE) {
|
|
1678
|
+
// Self-heal: adopt any real tmux leader the process is already inside —
|
|
1679
|
+
// including a session created outside `gjc --tmux`, or a `gjc --tmux` pane
|
|
1680
|
+
// that lost its @gjc-profile tag mid-startup — by writing the ownership tag
|
|
1681
|
+
// and reading it back. A real tmux round-trips the user option and is
|
|
1682
|
+
// adopted; a provider that drops it (e.g. psmux, a non-tmux multiplexer)
|
|
1683
|
+
// fails the readback and stays rejected as foreign/unmanaged. The retag is
|
|
1684
|
+
// scoped to the session display-message already resolved for our own pane,
|
|
1685
|
+
// so it is not a foreign-session takeover. (gjc team-runtime.ts:1871-1886)
|
|
1686
|
+
const tagged = tagJwcTmuxSessionAsLeader(tmuxCommand, sessionName);
|
|
1687
|
+
if (!tagged || readJwcTmuxProfileValue(tmuxCommand, sessionName) !== GJC_TMUX_PROFILE_VALUE)
|
|
1688
|
+
throw new Error(buildTeamTmuxLeaderRequirementMessage(`unmanaged_tmux_session:${sessionName}`));
|
|
1689
|
+
}
|
|
1690
|
+
return { sessionName, windowIndex, leaderPaneId, target: `${sessionName}:${windowIndex}` };
|
|
1691
|
+
}
|
|
1692
|
+
export function resolveJwcWorkerCommand(cwd = process.cwd(), env: NodeJS.ProcessEnv = process.env): string {
|
|
1693
|
+
const explicit = env.GJC_TEAM_WORKER_COMMAND?.trim();
|
|
1694
|
+
if (explicit) return explicit;
|
|
1695
|
+
const entrypoint = process.argv[1];
|
|
1696
|
+
if (entrypoint?.endsWith(".ts"))
|
|
1697
|
+
return `${shellQuote(process.execPath)} ${shellQuote(path.resolve(cwd, entrypoint))}`;
|
|
1698
|
+
const base = entrypoint ? path.basename(entrypoint) : "";
|
|
1699
|
+
if (entrypoint && (base.startsWith("jwc") || base.startsWith("gjc")))
|
|
1700
|
+
return shellQuote(path.resolve(cwd, entrypoint));
|
|
1701
|
+
return "jwc";
|
|
1702
|
+
}
|
|
1703
|
+
function buildWorkerCommand(config: JwcTeamConfig, worker: JwcTeamWorker): string {
|
|
1704
|
+
const workspace = worker.worktree_path
|
|
1705
|
+
? `Worker worktree: ${worker.worktree_path}.`
|
|
1706
|
+
: `Worker cwd: ${config.leader.cwd}.`;
|
|
1707
|
+
const prompt = [
|
|
1708
|
+
`You are ${worker.id} in gjc team ${config.team_name}.`,
|
|
1709
|
+
`Team state root: ${config.state_root}.`,
|
|
1710
|
+
workspace,
|
|
1711
|
+
`Team brief (context only): ${config.task}`,
|
|
1712
|
+
"Before implementation, claim your worker-owned task and treat the claimed task record as the source of truth. Do not implement directly from the broad team brief.",
|
|
1713
|
+
`Before claiming work, send startup ACK: gjc team api worker-startup-ack --input '{"team_name":"${config.team_name}","worker_id":"${worker.id}","protocol_version":"1"}' --json.`,
|
|
1714
|
+
`Use gjc team api update-worker-status to report task-local activity, then claim-task/transition-task-status with this worker id; keep heartbeat current during long work, record completion_evidence (summary plus a passed command or verified inspection/artifact item) before completed, and do not mutate leader-owned goal state.`,
|
|
1715
|
+
].join("\n");
|
|
1716
|
+
const env = [
|
|
1717
|
+
`GJC_TEAM_WORKER=${shellQuote(`${config.team_name}/${worker.id}`)}`,
|
|
1718
|
+
`GJC_TEAM_INTERNAL_WORKER=${shellQuote(`${config.team_name}/${worker.id}`)}`,
|
|
1719
|
+
`GJC_TEAM_NAME=${shellQuote(config.team_name)}`,
|
|
1720
|
+
`GJC_TEAM_WORKER_ID=${shellQuote(worker.id)}`,
|
|
1721
|
+
`GJC_TEAM_STATE_ROOT=${shellQuote(config.state_root)}`,
|
|
1722
|
+
`GJC_TEAM_LEADER_CWD=${shellQuote(config.leader.cwd)}`,
|
|
1723
|
+
`GJC_TEAM_DISPLAY_NAME=${shellQuote(config.display_name)}`,
|
|
1724
|
+
...(worker.worktree_path ? [`GJC_TEAM_WORKTREE_PATH=${shellQuote(worker.worktree_path)}`] : []),
|
|
1725
|
+
];
|
|
1726
|
+
return `${env.join(" ")} ${config.worker_command} ${shellQuote(prompt)}`;
|
|
1727
|
+
}
|
|
1728
|
+
interface JwcTeamInitialLane {
|
|
1729
|
+
label: string;
|
|
1730
|
+
title: string;
|
|
1731
|
+
body: string;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function normalizeLaneId(label: string): string {
|
|
1735
|
+
return `lane-${sanitizeName(label).toLowerCase() || stableHash(label).slice(0, 8)}`;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function parseExplicitTeamLanes(task: string): JwcTeamInitialLane[] {
|
|
1739
|
+
const lines = task.split(/\r?\n/);
|
|
1740
|
+
const lanes: JwcTeamInitialLane[] = [];
|
|
1741
|
+
let current: { label: string; title: string; body: string[] } | null = null;
|
|
1742
|
+
const laneHeading = /^#{2,6}\s+Lane\s+([A-Za-z0-9]+)\s*(?:[—–-]\s*(.+))?\s*$/;
|
|
1743
|
+
const boundaryHeading = /^#{1,6}\s+(?:Integration Owner|Verification Plan|ADR|Approval State)\b/i;
|
|
1744
|
+
|
|
1745
|
+
for (const line of lines) {
|
|
1746
|
+
const match = line.match(laneHeading);
|
|
1747
|
+
if (match) {
|
|
1748
|
+
if (current) lanes.push({ ...current, body: current.body.join("\n").trim() });
|
|
1749
|
+
current = {
|
|
1750
|
+
label: match[1] ?? `${lanes.length + 1}`,
|
|
1751
|
+
title: (match[2] ?? `Lane ${match[1] ?? lanes.length + 1}`).trim(),
|
|
1752
|
+
body: [],
|
|
1753
|
+
};
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
if (current && boundaryHeading.test(line)) {
|
|
1757
|
+
lanes.push({ ...current, body: current.body.join("\n").trim() });
|
|
1758
|
+
current = null;
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
if (current) current.body.push(line);
|
|
1762
|
+
}
|
|
1763
|
+
if (current) lanes.push({ ...current, body: current.body.join("\n").trim() });
|
|
1764
|
+
return lanes.filter(lane => lane.body.length > 0 || lane.title.length > 0);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function hasAmbiguousLaneSplitIntent(task: string): boolean {
|
|
1768
|
+
return (
|
|
1769
|
+
/\bsplit\s+lanes?\s*:/i.test(task) || /\blanes?\s*:\s*[A-Z]\b/i.test(task) || /\bLane\s+[A-Z]\s*[—–-]/.test(task)
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function buildInitialTasks(task: string, workers: JwcTeamWorker[]): JwcTeamTask[] {
|
|
1774
|
+
const lanes = parseExplicitTeamLanes(task);
|
|
1775
|
+
if (lanes.length > 0)
|
|
1776
|
+
return lanes.map((lane, index) => {
|
|
1777
|
+
const worker = workers[index % workers.length];
|
|
1778
|
+
if (!worker) throw new Error("team_lane_requires_worker");
|
|
1779
|
+
const laneTitle = `Lane ${lane.label} — ${lane.title}`;
|
|
1780
|
+
const objective = [`${laneTitle}`, lane.body].filter(part => part.trim().length > 0).join("\n\n");
|
|
1781
|
+
return {
|
|
1782
|
+
id: `task-${index + 1}`,
|
|
1783
|
+
subject: laneTitle,
|
|
1784
|
+
description: objective,
|
|
1785
|
+
title: laneTitle,
|
|
1786
|
+
objective,
|
|
1787
|
+
status: "pending",
|
|
1788
|
+
owner: worker.id,
|
|
1789
|
+
lane: normalizeLaneId(lane.label),
|
|
1790
|
+
required_role: worker.role,
|
|
1791
|
+
version: 1,
|
|
1792
|
+
created_at: now(),
|
|
1793
|
+
updated_at: now(),
|
|
1794
|
+
};
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
if (workers.length > 1 && hasAmbiguousLaneSplitIntent(task))
|
|
1798
|
+
throw new Error(
|
|
1799
|
+
"ambiguous_team_lane_split: multi-worker team launch mentions lanes but does not provide explicit markdown lane sections such as `### Lane A — Title`",
|
|
1800
|
+
);
|
|
1801
|
+
|
|
1802
|
+
return workers.map(worker => ({
|
|
1803
|
+
id: `task-${worker.index}`,
|
|
1804
|
+
subject: `Execute team brief (${worker.id})`,
|
|
1805
|
+
description: task,
|
|
1806
|
+
title: `Execute team brief (${worker.id})`,
|
|
1807
|
+
objective: task,
|
|
1808
|
+
status: "pending",
|
|
1809
|
+
owner: worker.id,
|
|
1810
|
+
version: 1,
|
|
1811
|
+
created_at: now(),
|
|
1812
|
+
updated_at: now(),
|
|
1813
|
+
}));
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
async function startTmuxSession(
|
|
1817
|
+
config: JwcTeamConfig,
|
|
1818
|
+
dir: string,
|
|
1819
|
+
dryRun: boolean,
|
|
1820
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1821
|
+
): Promise<JwcTeamWorker[]> {
|
|
1822
|
+
if (dryRun) return config.workers.map(worker => ({ ...worker, pane_id: `%dry-run-${worker.id}` }));
|
|
1823
|
+
const rollbackPaneIds: string[] = [];
|
|
1824
|
+
try {
|
|
1825
|
+
const workers: JwcTeamWorker[] = [];
|
|
1826
|
+
let rightStackRootPaneId: string | null = null;
|
|
1827
|
+
for (const worker of config.workers) {
|
|
1828
|
+
const splitDirection: string = worker.index === 1 ? "-h" : "-v";
|
|
1829
|
+
const splitTarget: string =
|
|
1830
|
+
worker.index === 1 ? config.leader.pane_id : (rightStackRootPaneId ?? config.leader.pane_id);
|
|
1831
|
+
const split: Bun.SyncSubprocess<"pipe", "pipe"> = Bun.spawnSync(
|
|
1832
|
+
[
|
|
1833
|
+
config.tmux_command,
|
|
1834
|
+
"split-window",
|
|
1835
|
+
splitDirection,
|
|
1836
|
+
"-t",
|
|
1837
|
+
splitTarget,
|
|
1838
|
+
"-d",
|
|
1839
|
+
"-P",
|
|
1840
|
+
"-F",
|
|
1841
|
+
"#{pane_id}",
|
|
1842
|
+
"-c",
|
|
1843
|
+
worker.worktree_path ?? config.leader.cwd,
|
|
1844
|
+
buildWorkerCommand(config, worker),
|
|
1845
|
+
],
|
|
1846
|
+
{ stdout: "pipe", stderr: "pipe" },
|
|
1847
|
+
);
|
|
1848
|
+
if (split.exitCode !== 0)
|
|
1849
|
+
throw new Error(split.stderr.toString().trim() || `tmux_split_failed:${config.tmux_target}:${worker.id}`);
|
|
1850
|
+
const paneId: string = split.stdout.toString().trim().split(/\r?\n/)[0]?.trim() ?? "";
|
|
1851
|
+
if (!paneId.startsWith("%")) throw new Error(`tmux_split_missing_pane:${config.tmux_target}:${worker.id}`);
|
|
1852
|
+
rollbackPaneIds.push(paneId);
|
|
1853
|
+
if (worker.index === 1) rightStackRootPaneId = paneId;
|
|
1854
|
+
workers.push({ ...worker, pane_id: paneId });
|
|
1855
|
+
}
|
|
1856
|
+
Bun.spawnSync([config.tmux_command, "select-layout", "-t", config.tmux_target, "main-vertical"], {
|
|
1857
|
+
stdout: "ignore",
|
|
1858
|
+
stderr: "ignore",
|
|
1859
|
+
});
|
|
1860
|
+
const widthResult = Bun.spawnSync(
|
|
1861
|
+
[config.tmux_command, "display-message", "-p", "-t", config.tmux_target, "#{window_width}"],
|
|
1862
|
+
{ stdout: "pipe", stderr: "ignore" },
|
|
1863
|
+
);
|
|
1864
|
+
const width = Number.parseInt(widthResult.stdout.toString().trim(), 10);
|
|
1865
|
+
if (Number.isFinite(width) && width >= 40) {
|
|
1866
|
+
Bun.spawnSync(
|
|
1867
|
+
[
|
|
1868
|
+
config.tmux_command,
|
|
1869
|
+
"set-window-option",
|
|
1870
|
+
"-t",
|
|
1871
|
+
config.tmux_target,
|
|
1872
|
+
"main-pane-width",
|
|
1873
|
+
String(Math.floor(width / 2)),
|
|
1874
|
+
],
|
|
1875
|
+
{ stdout: "ignore", stderr: "ignore" },
|
|
1876
|
+
);
|
|
1877
|
+
Bun.spawnSync([config.tmux_command, "select-layout", "-t", config.tmux_target, "main-vertical"], {
|
|
1878
|
+
stdout: "ignore",
|
|
1879
|
+
stderr: "ignore",
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
const profileResult = applyJwcTmuxProfile({
|
|
1883
|
+
tmuxCommand: config.tmux_command,
|
|
1884
|
+
target: config.tmux_target,
|
|
1885
|
+
cwd: config.leader.cwd,
|
|
1886
|
+
env,
|
|
1887
|
+
});
|
|
1888
|
+
await appendTelemetry(dir, {
|
|
1889
|
+
type: "tmux_profile_applied",
|
|
1890
|
+
message: profileResult.skipped
|
|
1891
|
+
? "Skipped GJC scoped tmux profile"
|
|
1892
|
+
: "Applied GJC scoped tmux profile to team tmux target",
|
|
1893
|
+
data: {
|
|
1894
|
+
tmux_target: config.tmux_target,
|
|
1895
|
+
command_count: profileResult.commands.length,
|
|
1896
|
+
failure_count: profileResult.failures.length,
|
|
1897
|
+
},
|
|
1898
|
+
});
|
|
1899
|
+
await appendTelemetry(dir, {
|
|
1900
|
+
type: "tmux_started",
|
|
1901
|
+
message: "Started gjc team worker panes in current tmux window",
|
|
1902
|
+
data: { tmux_target: config.tmux_target, panes: workers.map(worker => worker.pane_id).filter(Boolean) },
|
|
1903
|
+
});
|
|
1904
|
+
return workers;
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
for (const paneId of rollbackPaneIds)
|
|
1907
|
+
Bun.spawnSync([config.tmux_command, "kill-pane", "-t", paneId], { stdout: "ignore", stderr: "ignore" });
|
|
1908
|
+
throw error;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
function paneBelongsToTeamTarget(config: JwcTeamConfig, paneId: string): boolean {
|
|
1912
|
+
if (paneId === config.leader.pane_id) return false;
|
|
1913
|
+
const result = Bun.spawnSync([config.tmux_command, "display-message", "-p", "-t", paneId, "#S:#I #{pane_id}"], {
|
|
1914
|
+
stdout: "pipe",
|
|
1915
|
+
stderr: "ignore",
|
|
1916
|
+
});
|
|
1917
|
+
if (result.exitCode !== 0) return false;
|
|
1918
|
+
const [target = "", detectedPaneId = ""] = result.stdout.toString().trim().split(/\s+/);
|
|
1919
|
+
return target === config.tmux_target && detectedPaneId === paneId;
|
|
1920
|
+
}
|
|
1921
|
+
function killWorkerPanes(config: JwcTeamConfig): void {
|
|
1922
|
+
for (const worker of config.workers)
|
|
1923
|
+
if (worker.pane_id?.startsWith("%") && paneBelongsToTeamTarget(config, worker.pane_id))
|
|
1924
|
+
Bun.spawnSync([config.tmux_command, "kill-pane", "-t", worker.pane_id], {
|
|
1925
|
+
stdout: "ignore",
|
|
1926
|
+
stderr: "ignore",
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
async function rollbackCreatedWorktrees(workers: JwcTeamWorker[]): Promise<void> {
|
|
1930
|
+
for (const worker of workers.filter(worker => worker.worktree_created).reverse())
|
|
1931
|
+
if (worker.worktree_repo_root && worker.worktree_path)
|
|
1932
|
+
Bun.spawnSync(["git", "worktree", "remove", "--force", worker.worktree_path], {
|
|
1933
|
+
cwd: worker.worktree_repo_root,
|
|
1934
|
+
stdout: "ignore",
|
|
1935
|
+
stderr: "ignore",
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
async function removeCleanCreatedWorktrees(workers: JwcTeamWorker[]): Promise<void> {
|
|
1939
|
+
for (const worker of workers.filter(worker => worker.worktree_created).reverse())
|
|
1940
|
+
if (worker.worktree_repo_root && worker.worktree_path && !worktreeIsDirty(worker.worktree_path))
|
|
1941
|
+
Bun.spawnSync(["git", "worktree", "remove", worker.worktree_path], {
|
|
1942
|
+
cwd: worker.worktree_repo_root,
|
|
1943
|
+
stdout: "ignore",
|
|
1944
|
+
stderr: "ignore",
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
function monitorSnapshotPath(dir: string): string {
|
|
1949
|
+
return path.join(dir, "monitor-snapshot.json");
|
|
1950
|
+
}
|
|
1951
|
+
function integrationReportPath(dir: string): string {
|
|
1952
|
+
return path.join(dir, "integration-report.md");
|
|
1953
|
+
}
|
|
1954
|
+
function commitHygieneLedgerPath(config: JwcTeamConfig): string {
|
|
1955
|
+
return path.join(config.leader_cwd, ".jwc", "reports", "team-commit-hygiene", `${config.team_name}.ledger.json`);
|
|
1956
|
+
}
|
|
1957
|
+
function integrationNowState(
|
|
1958
|
+
status: JwcTeamIntegrationStatus,
|
|
1959
|
+
): Pick<JwcTeamWorkerIntegrationState, "status" | "updated_at"> {
|
|
1960
|
+
return { status, updated_at: now() };
|
|
1961
|
+
}
|
|
1962
|
+
async function appendIntegrationReport(
|
|
1963
|
+
dir: string,
|
|
1964
|
+
entry: { worker: string; operation: "merge" | "cherry-pick" | "rebase"; files: string[]; detail: string },
|
|
1965
|
+
): Promise<void> {
|
|
1966
|
+
const line = `- [${now()}] ${entry.worker}: ${entry.operation}; files=${entry.files.join(",") || "unknown"}; ${entry.detail}\n`;
|
|
1967
|
+
if (await pathExists(integrationReportPath(dir)))
|
|
1968
|
+
await appendText(
|
|
1969
|
+
integrationReportPath(dir),
|
|
1970
|
+
line,
|
|
1971
|
+
stateWriterOptions(integrationReportPath(dir), "report", "append"),
|
|
1972
|
+
);
|
|
1973
|
+
else
|
|
1974
|
+
await writeReport(
|
|
1975
|
+
integrationReportPath(dir),
|
|
1976
|
+
`# Integration Report\n\n${line}`,
|
|
1977
|
+
stateWriterOptions(integrationReportPath(dir), "report", "write"),
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
async function appendCommitHygieneEntries(config: JwcTeamConfig, entries: JwcTeamCommitHygieneEntry[]): Promise<void> {
|
|
1981
|
+
if (entries.length === 0) return;
|
|
1982
|
+
const ledgerPath = commitHygieneLedgerPath(config);
|
|
1983
|
+
const existing = (await readJsonFile<{ version: number; entries: JwcTeamCommitHygieneEntry[] }>(ledgerPath)) ?? {
|
|
1984
|
+
version: 1,
|
|
1985
|
+
entries: [],
|
|
1986
|
+
};
|
|
1987
|
+
await writeJsonFile(ledgerPath, { version: 1, entries: [...existing.entries, ...entries] });
|
|
1988
|
+
}
|
|
1989
|
+
function resolveHead(cwd: string): string | null {
|
|
1990
|
+
return tryRunGit(cwd, ["rev-parse", "HEAD"]);
|
|
1991
|
+
}
|
|
1992
|
+
function isAncestor(cwd: string, ancestor: string, descendant: string): boolean {
|
|
1993
|
+
return runGitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok;
|
|
1994
|
+
}
|
|
1995
|
+
function listCommitRange(cwd: string, baseRef: string, headRef: string): string[] {
|
|
1996
|
+
const result = runGitResult(cwd, ["rev-list", "--reverse", `${baseRef}..${headRef}`]);
|
|
1997
|
+
if (!result.ok || !result.stdout) return [];
|
|
1998
|
+
return result.stdout
|
|
1999
|
+
.split(/\r?\n/)
|
|
2000
|
+
.map(line => line.trim())
|
|
2001
|
+
.filter(Boolean);
|
|
2002
|
+
}
|
|
2003
|
+
function listConflictFiles(cwd: string): string[] {
|
|
2004
|
+
const result = runGitResult(cwd, ["diff", "--name-only", "--diff-filter=U"]);
|
|
2005
|
+
if (!result.ok || !result.stdout) return [];
|
|
2006
|
+
return result.stdout
|
|
2007
|
+
.split(/\r?\n/)
|
|
2008
|
+
.map(line => line.trim())
|
|
2009
|
+
.filter(Boolean);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
export type JwcWorkerCheckpointClassification =
|
|
2013
|
+
| { kind: "clean"; files: string[] }
|
|
2014
|
+
| { kind: "eligible"; files: string[] }
|
|
2015
|
+
| { kind: "protected_only"; files: string[] }
|
|
2016
|
+
| { kind: "conflicted"; files: string[] }
|
|
2017
|
+
| { kind: "git_error"; files: string[]; detail: string };
|
|
2018
|
+
|
|
2019
|
+
const UNMERGED_GIT_STATUS_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
2020
|
+
const PROTECTED_WORKER_CHECKPOINT_PREFIXES = [".jwc/state/", ".jwc/logs/", ".jwc/reports/", ".jwc/tmp/", ".jwc/goal/"];
|
|
2021
|
+
|
|
2022
|
+
function parsePorcelainStatusFiles(stdout: string): string[] {
|
|
2023
|
+
return stdout
|
|
2024
|
+
.split(/\r?\n/)
|
|
2025
|
+
.map(line => line.trimEnd())
|
|
2026
|
+
.filter(Boolean)
|
|
2027
|
+
.map(line => line.slice(3).trim())
|
|
2028
|
+
.filter(Boolean);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function normalizeGitStatusPath(filePath: string): string {
|
|
2032
|
+
return (filePath.split(" -> ").at(-1) ?? filePath).replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
export function classifyJwcTeamCheckpointFiles(files: string[]): { eligible: string[]; protected: string[] } {
|
|
2036
|
+
const eligible: string[] = [];
|
|
2037
|
+
const protectedFiles: string[] = [];
|
|
2038
|
+
for (const file of files) {
|
|
2039
|
+
const normalized = normalizeGitStatusPath(file);
|
|
2040
|
+
if (
|
|
2041
|
+
PROTECTED_WORKER_CHECKPOINT_PREFIXES.some(
|
|
2042
|
+
prefix => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix),
|
|
2043
|
+
)
|
|
2044
|
+
)
|
|
2045
|
+
protectedFiles.push(file);
|
|
2046
|
+
else eligible.push(file);
|
|
2047
|
+
}
|
|
2048
|
+
return { eligible, protected: protectedFiles };
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
export function classifyWorkerCheckpointStatus(cwd: string): JwcWorkerCheckpointClassification {
|
|
2052
|
+
const status = runGitResult(cwd, ["status", "--porcelain", "-uall"]);
|
|
2053
|
+
if (!status.ok) {
|
|
2054
|
+
return { kind: "git_error", files: [], detail: status.stderr || status.stdout || "git status failed" };
|
|
2055
|
+
}
|
|
2056
|
+
if (!status.stdout.trim()) return { kind: "clean", files: [] };
|
|
2057
|
+
const files = parsePorcelainStatusFiles(status.stdout);
|
|
2058
|
+
const hasUnmergedStatus = status.stdout
|
|
2059
|
+
.split(/\r?\n/)
|
|
2060
|
+
.filter(Boolean)
|
|
2061
|
+
.some(line => UNMERGED_GIT_STATUS_CODES.has(line.slice(0, 2)));
|
|
2062
|
+
const conflictFiles = listConflictFiles(cwd);
|
|
2063
|
+
if (hasUnmergedStatus || conflictFiles.length > 0) {
|
|
2064
|
+
return { kind: "conflicted", files: conflictFiles.length > 0 ? conflictFiles : files };
|
|
2065
|
+
}
|
|
2066
|
+
const classified = classifyJwcTeamCheckpointFiles(files);
|
|
2067
|
+
if (classified.eligible.length === 0 && classified.protected.length > 0)
|
|
2068
|
+
return { kind: "protected_only", files: classified.protected };
|
|
2069
|
+
return { kind: "eligible", files: classified.eligible };
|
|
2070
|
+
}
|
|
2071
|
+
async function appendIntegrationEvent(
|
|
2072
|
+
dir: string,
|
|
2073
|
+
type: string,
|
|
2074
|
+
worker: JwcTeamWorker,
|
|
2075
|
+
data: Record<string, unknown>,
|
|
2076
|
+
): Promise<void> {
|
|
2077
|
+
await appendEvent(dir, {
|
|
2078
|
+
type,
|
|
2079
|
+
worker: worker.id,
|
|
2080
|
+
task_id: worker.assigned_tasks[0],
|
|
2081
|
+
message: typeof data.summary === "string" ? data.summary : type,
|
|
2082
|
+
data,
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
async function notifyLeader(
|
|
2086
|
+
config: JwcTeamConfig,
|
|
2087
|
+
worker: JwcTeamWorker,
|
|
2088
|
+
body: string,
|
|
2089
|
+
cwd: string,
|
|
2090
|
+
env: NodeJS.ProcessEnv,
|
|
2091
|
+
): Promise<void> {
|
|
2092
|
+
await sendJwcTeamMessage(config.team_name, worker.id, "leader-fixed", body, cwd, env).catch(() => undefined);
|
|
2093
|
+
}
|
|
2094
|
+
async function notifyWorker(
|
|
2095
|
+
config: JwcTeamConfig,
|
|
2096
|
+
worker: JwcTeamWorker,
|
|
2097
|
+
body: string,
|
|
2098
|
+
cwd: string,
|
|
2099
|
+
env: NodeJS.ProcessEnv,
|
|
2100
|
+
): Promise<void> {
|
|
2101
|
+
await sendJwcTeamMessage(config.team_name, "leader-fixed", worker.id, body, cwd, env).catch(() => undefined);
|
|
2102
|
+
}
|
|
2103
|
+
async function notifyIntegrationConflict(
|
|
2104
|
+
config: JwcTeamConfig,
|
|
2105
|
+
worker: JwcTeamWorker,
|
|
2106
|
+
body: string,
|
|
2107
|
+
cwd: string,
|
|
2108
|
+
env: NodeJS.ProcessEnv,
|
|
2109
|
+
): Promise<void> {
|
|
2110
|
+
await Promise.all([notifyLeader(config, worker, body, cwd, env), notifyWorker(config, worker, body, cwd, env)]);
|
|
2111
|
+
}
|
|
2112
|
+
function autoCommitDirtyWorker(worker: JwcTeamWorker): {
|
|
2113
|
+
committed: boolean;
|
|
2114
|
+
commit: string | null;
|
|
2115
|
+
classification: JwcWorkerCheckpointClassification | null;
|
|
2116
|
+
} {
|
|
2117
|
+
const empty = { committed: false, commit: null, classification: null };
|
|
2118
|
+
if (!worker.worktree_path) return empty;
|
|
2119
|
+
const classification = classifyWorkerCheckpointStatus(worker.worktree_path);
|
|
2120
|
+
if (classification.kind !== "eligible") return { ...empty, classification };
|
|
2121
|
+
if (!runGitResult(worker.worktree_path, ["add", "--", ...classification.files]).ok)
|
|
2122
|
+
return { ...empty, classification };
|
|
2123
|
+
const message = `gjc(team): auto-checkpoint ${worker.id} [${worker.assigned_tasks[0] ?? "unknown"}]`;
|
|
2124
|
+
if (!runGitResult(worker.worktree_path, ["commit", "--no-verify", "-m", message]).ok)
|
|
2125
|
+
return { ...empty, classification };
|
|
2126
|
+
return { committed: true, commit: resolveHead(worker.worktree_path), classification };
|
|
2127
|
+
}
|
|
2128
|
+
function workerMergeRef(worker: JwcTeamWorker, workerHead: string): string {
|
|
2129
|
+
if (!worker.worktree_path) return workerHead;
|
|
2130
|
+
const branch = tryRunGit(worker.worktree_path, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
2131
|
+
return !branch || branch === "HEAD" ? workerHead : branch;
|
|
2132
|
+
}
|
|
2133
|
+
async function integrateJwcWorkerCommits(
|
|
2134
|
+
config: JwcTeamConfig,
|
|
2135
|
+
dir: string,
|
|
2136
|
+
previous: JwcTeamMonitorSnapshot | null,
|
|
2137
|
+
cwd: string,
|
|
2138
|
+
env: NodeJS.ProcessEnv,
|
|
2139
|
+
): Promise<Record<string, JwcTeamWorkerIntegrationState>> {
|
|
2140
|
+
const integrationByWorker: Record<string, JwcTeamWorkerIntegrationState> = {
|
|
2141
|
+
...(previous?.integration_by_worker ?? {}),
|
|
2142
|
+
};
|
|
2143
|
+
const hygieneEntries: JwcTeamCommitHygieneEntry[] = [];
|
|
2144
|
+
const leaderCwd = config.leader_cwd || cwd;
|
|
2145
|
+
const cycleLeaderHead = resolveHead(leaderCwd);
|
|
2146
|
+
for (const worker of config.workers) {
|
|
2147
|
+
if (!worker.worktree_path || !worker.worktree_repo_root || !(await pathExists(worker.worktree_path))) continue;
|
|
2148
|
+
const { committed, commit } = autoCommitDirtyWorker(worker);
|
|
2149
|
+
if (!committed) continue;
|
|
2150
|
+
await appendIntegrationEvent(dir, "worker_auto_commit", worker, {
|
|
2151
|
+
worker_name: worker.id,
|
|
2152
|
+
commit_hash: commit,
|
|
2153
|
+
worktree_path: worker.worktree_path,
|
|
2154
|
+
summary: `auto-committed dirty worktree for ${worker.id}`,
|
|
2155
|
+
});
|
|
2156
|
+
hygieneEntries.push({
|
|
2157
|
+
recorded_at: now(),
|
|
2158
|
+
operation: "auto_checkpoint",
|
|
2159
|
+
worker_name: worker.id,
|
|
2160
|
+
task_id: worker.assigned_tasks[0],
|
|
2161
|
+
status: "applied",
|
|
2162
|
+
operational_commit: commit,
|
|
2163
|
+
worktree_path: worker.worktree_path,
|
|
2164
|
+
detail: "Dirty worker worktree checkpointed before integration.",
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
for (const worker of config.workers) {
|
|
2169
|
+
if (!worker.worktree_path || !worker.worktree_repo_root || !(await pathExists(worker.worktree_path))) continue;
|
|
2170
|
+
const leaderHead = resolveHead(leaderCwd);
|
|
2171
|
+
const workerHead = resolveHead(worker.worktree_path);
|
|
2172
|
+
const state: JwcTeamWorkerIntegrationState = {
|
|
2173
|
+
...(integrationByWorker[worker.id] ?? {}),
|
|
2174
|
+
last_leader_head: leaderHead ?? integrationByWorker[worker.id]?.last_leader_head,
|
|
2175
|
+
};
|
|
2176
|
+
if (!leaderHead || !workerHead) {
|
|
2177
|
+
integrationByWorker[worker.id] = state;
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
state.last_seen_head = workerHead;
|
|
2181
|
+
if (isAncestor(leaderCwd, workerHead, "HEAD")) {
|
|
2182
|
+
integrationByWorker[worker.id] = {
|
|
2183
|
+
...state,
|
|
2184
|
+
last_integrated_head: workerHead,
|
|
2185
|
+
...integrationNowState("idle"),
|
|
2186
|
+
};
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (isAncestor(worker.worktree_path, leaderHead, workerHead)) {
|
|
2190
|
+
const mergeRef = workerMergeRef(worker, workerHead);
|
|
2191
|
+
const merge = runGitResult(leaderCwd, ["merge", "--no-ff", "-m", `gjc(team): merge ${worker.id}`, mergeRef]);
|
|
2192
|
+
if (merge.ok) {
|
|
2193
|
+
const newLeaderHead = resolveHead(leaderCwd);
|
|
2194
|
+
if (newLeaderHead && newLeaderHead !== leaderHead && isAncestor(leaderCwd, workerHead, "HEAD")) {
|
|
2195
|
+
integrationByWorker[worker.id] = {
|
|
2196
|
+
...state,
|
|
2197
|
+
last_integrated_head: workerHead,
|
|
2198
|
+
last_leader_head: newLeaderHead,
|
|
2199
|
+
conflict_commit: undefined,
|
|
2200
|
+
conflict_files: undefined,
|
|
2201
|
+
...integrationNowState("integrated"),
|
|
2202
|
+
};
|
|
2203
|
+
await appendIntegrationEvent(dir, "worker_merge_applied", worker, {
|
|
2204
|
+
worker_name: worker.id,
|
|
2205
|
+
worker_head: workerHead,
|
|
2206
|
+
leader_head_before: leaderHead,
|
|
2207
|
+
leader_head_after: newLeaderHead,
|
|
2208
|
+
worktree_path: worker.worktree_path,
|
|
2209
|
+
summary: `merged ${worker.id} into leader`,
|
|
2210
|
+
});
|
|
2211
|
+
await notifyLeader(
|
|
2212
|
+
config,
|
|
2213
|
+
worker,
|
|
2214
|
+
`INTEGRATED: merged ${worker.id} ${workerHead.slice(0, 12)} into leader.`,
|
|
2215
|
+
cwd,
|
|
2216
|
+
env,
|
|
2217
|
+
);
|
|
2218
|
+
hygieneEntries.push({
|
|
2219
|
+
recorded_at: now(),
|
|
2220
|
+
operation: "integration_merge",
|
|
2221
|
+
worker_name: worker.id,
|
|
2222
|
+
task_id: worker.assigned_tasks[0],
|
|
2223
|
+
status: "applied",
|
|
2224
|
+
operational_commit: newLeaderHead,
|
|
2225
|
+
source_commit: workerHead,
|
|
2226
|
+
leader_head_before: leaderHead,
|
|
2227
|
+
leader_head_after: newLeaderHead,
|
|
2228
|
+
worktree_path: worker.worktree_path,
|
|
2229
|
+
detail: "Leader created a runtime merge commit to integrate worker history.",
|
|
2230
|
+
});
|
|
2231
|
+
} else {
|
|
2232
|
+
integrationByWorker[worker.id] = { ...state, ...integrationNowState("integration_failed") };
|
|
2233
|
+
hygieneEntries.push({
|
|
2234
|
+
recorded_at: now(),
|
|
2235
|
+
operation: "integration_merge",
|
|
2236
|
+
worker_name: worker.id,
|
|
2237
|
+
task_id: worker.assigned_tasks[0],
|
|
2238
|
+
status: "failed",
|
|
2239
|
+
source_commit: workerHead,
|
|
2240
|
+
leader_head_before: leaderHead,
|
|
2241
|
+
leader_head_after: newLeaderHead,
|
|
2242
|
+
worktree_path: worker.worktree_path,
|
|
2243
|
+
detail: "Runtime merge succeeded but did not advance the leader head.",
|
|
2244
|
+
});
|
|
2245
|
+
await notifyLeader(
|
|
2246
|
+
config,
|
|
2247
|
+
worker,
|
|
2248
|
+
`INTEGRATION FAILED: merge for ${worker.id} did not advance leader HEAD.`,
|
|
2249
|
+
cwd,
|
|
2250
|
+
env,
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
} else {
|
|
2254
|
+
const conflictFiles = listConflictFiles(leaderCwd);
|
|
2255
|
+
runGitResult(leaderCwd, ["merge", "--abort"]);
|
|
2256
|
+
integrationByWorker[worker.id] = {
|
|
2257
|
+
...state,
|
|
2258
|
+
conflict_commit: workerHead,
|
|
2259
|
+
conflict_files: conflictFiles,
|
|
2260
|
+
...integrationNowState("merge_conflict"),
|
|
2261
|
+
};
|
|
2262
|
+
await appendIntegrationEvent(dir, "worker_merge_conflict", worker, {
|
|
2263
|
+
worker_name: worker.id,
|
|
2264
|
+
worker_head: workerHead,
|
|
2265
|
+
conflict_files: conflictFiles,
|
|
2266
|
+
stderr: merge.stderr || merge.stdout,
|
|
2267
|
+
summary: `merge conflict for ${worker.id}`,
|
|
2268
|
+
});
|
|
2269
|
+
await appendIntegrationReport(dir, {
|
|
2270
|
+
worker: worker.id,
|
|
2271
|
+
operation: "merge",
|
|
2272
|
+
files: conflictFiles,
|
|
2273
|
+
detail: `merge --no-ff failed and was aborted: ${(merge.stderr || merge.stdout).slice(0, 200)}`,
|
|
2274
|
+
});
|
|
2275
|
+
await notifyIntegrationConflict(
|
|
2276
|
+
config,
|
|
2277
|
+
worker,
|
|
2278
|
+
`CONFLICT: merge failed for ${worker.id}; files: ${conflictFiles.join(",") || "unknown"}. Manual resolution required; runtime aborted the merge and did not auto-resolve.`,
|
|
2279
|
+
cwd,
|
|
2280
|
+
env,
|
|
2281
|
+
);
|
|
2282
|
+
hygieneEntries.push({
|
|
2283
|
+
recorded_at: now(),
|
|
2284
|
+
operation: "integration_merge",
|
|
2285
|
+
worker_name: worker.id,
|
|
2286
|
+
task_id: worker.assigned_tasks[0],
|
|
2287
|
+
status: "conflict",
|
|
2288
|
+
source_commit: workerHead,
|
|
2289
|
+
leader_head_before: leaderHead,
|
|
2290
|
+
leader_head_after: resolveHead(leaderCwd),
|
|
2291
|
+
worktree_path: worker.worktree_path,
|
|
2292
|
+
detail: `Runtime merge failed and was aborted: ${(merge.stderr || merge.stdout).slice(0, 200)}`,
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
const baseline =
|
|
2299
|
+
state.last_integrated_head &&
|
|
2300
|
+
tryRunGit(worker.worktree_path, ["rev-parse", "--verify", state.last_integrated_head])
|
|
2301
|
+
? state.last_integrated_head
|
|
2302
|
+
: leaderHead;
|
|
2303
|
+
const commits = listCommitRange(worker.worktree_path, baseline, workerHead);
|
|
2304
|
+
for (const commit of commits) {
|
|
2305
|
+
const pick = runGitResult(leaderCwd, ["cherry-pick", "--allow-empty", commit]);
|
|
2306
|
+
if (!pick.ok) {
|
|
2307
|
+
const conflictFiles = listConflictFiles(leaderCwd);
|
|
2308
|
+
runGitResult(leaderCwd, ["cherry-pick", "--abort"]);
|
|
2309
|
+
integrationByWorker[worker.id] = {
|
|
2310
|
+
...state,
|
|
2311
|
+
conflict_commit: commit,
|
|
2312
|
+
conflict_files: conflictFiles,
|
|
2313
|
+
...integrationNowState("cherry_pick_conflict"),
|
|
2314
|
+
};
|
|
2315
|
+
await appendIntegrationEvent(dir, "worker_cherry_pick_conflict", worker, {
|
|
2316
|
+
worker_name: worker.id,
|
|
2317
|
+
commit,
|
|
2318
|
+
conflict_files: conflictFiles,
|
|
2319
|
+
stderr: pick.stderr || pick.stdout,
|
|
2320
|
+
summary: `cherry-pick conflict for ${worker.id}`,
|
|
2321
|
+
});
|
|
2322
|
+
await appendIntegrationReport(dir, {
|
|
2323
|
+
worker: worker.id,
|
|
2324
|
+
operation: "cherry-pick",
|
|
2325
|
+
files: conflictFiles,
|
|
2326
|
+
detail: `cherry-pick failed and was aborted: ${(pick.stderr || pick.stdout).slice(0, 200)}`,
|
|
2327
|
+
});
|
|
2328
|
+
await notifyIntegrationConflict(
|
|
2329
|
+
config,
|
|
2330
|
+
worker,
|
|
2331
|
+
`CONFLICT: cherry-pick failed for ${worker.id}; files: ${conflictFiles.join(",") || "unknown"}. Manual resolution required; runtime aborted the cherry-pick and did not auto-resolve.`,
|
|
2332
|
+
cwd,
|
|
2333
|
+
env,
|
|
2334
|
+
);
|
|
2335
|
+
hygieneEntries.push({
|
|
2336
|
+
recorded_at: now(),
|
|
2337
|
+
operation: "integration_cherry_pick",
|
|
2338
|
+
worker_name: worker.id,
|
|
2339
|
+
task_id: worker.assigned_tasks[0],
|
|
2340
|
+
status: "conflict",
|
|
2341
|
+
source_commit: commit,
|
|
2342
|
+
leader_head_before: leaderHead,
|
|
2343
|
+
leader_head_after: resolveHead(leaderCwd),
|
|
2344
|
+
worktree_path: worker.worktree_path,
|
|
2345
|
+
detail: `Runtime cherry-pick failed and was aborted: ${(pick.stderr || pick.stdout).slice(0, 200)}`,
|
|
2346
|
+
});
|
|
2347
|
+
break;
|
|
2348
|
+
}
|
|
2349
|
+
const newLeaderHead = resolveHead(leaderCwd);
|
|
2350
|
+
if (!newLeaderHead || newLeaderHead === leaderHead) {
|
|
2351
|
+
integrationByWorker[worker.id] = { ...state, ...integrationNowState("integration_failed") };
|
|
2352
|
+
hygieneEntries.push({
|
|
2353
|
+
recorded_at: now(),
|
|
2354
|
+
operation: "integration_cherry_pick",
|
|
2355
|
+
worker_name: worker.id,
|
|
2356
|
+
task_id: worker.assigned_tasks[0],
|
|
2357
|
+
status: "failed",
|
|
2358
|
+
source_commit: commit,
|
|
2359
|
+
leader_head_before: leaderHead,
|
|
2360
|
+
leader_head_after: newLeaderHead,
|
|
2361
|
+
worktree_path: worker.worktree_path,
|
|
2362
|
+
detail: "Runtime cherry-pick did not advance the leader head.",
|
|
2363
|
+
});
|
|
2364
|
+
break;
|
|
2365
|
+
}
|
|
2366
|
+
integrationByWorker[worker.id] = {
|
|
2367
|
+
...state,
|
|
2368
|
+
last_integrated_head: commit,
|
|
2369
|
+
last_leader_head: newLeaderHead,
|
|
2370
|
+
conflict_commit: undefined,
|
|
2371
|
+
conflict_files: undefined,
|
|
2372
|
+
...integrationNowState("integrated"),
|
|
2373
|
+
};
|
|
2374
|
+
await appendIntegrationEvent(dir, "worker_cherry_pick_applied", worker, {
|
|
2375
|
+
worker_name: worker.id,
|
|
2376
|
+
commit,
|
|
2377
|
+
leader_head_before: leaderHead,
|
|
2378
|
+
leader_head_after: newLeaderHead,
|
|
2379
|
+
worktree_path: worker.worktree_path,
|
|
2380
|
+
summary: `cherry-picked ${commit.slice(0, 12)} from ${worker.id}`,
|
|
2381
|
+
});
|
|
2382
|
+
await notifyLeader(
|
|
2383
|
+
config,
|
|
2384
|
+
worker,
|
|
2385
|
+
`INTEGRATED: cherry-picked ${commit.slice(0, 12)} from ${worker.id}.`,
|
|
2386
|
+
cwd,
|
|
2387
|
+
env,
|
|
2388
|
+
);
|
|
2389
|
+
hygieneEntries.push({
|
|
2390
|
+
recorded_at: now(),
|
|
2391
|
+
operation: "integration_cherry_pick",
|
|
2392
|
+
worker_name: worker.id,
|
|
2393
|
+
task_id: worker.assigned_tasks[0],
|
|
2394
|
+
status: "applied",
|
|
2395
|
+
operational_commit: newLeaderHead,
|
|
2396
|
+
source_commit: commit,
|
|
2397
|
+
leader_head_before: leaderHead,
|
|
2398
|
+
leader_head_after: newLeaderHead,
|
|
2399
|
+
worktree_path: worker.worktree_path,
|
|
2400
|
+
detail: "Leader cherry-picked diverged worker history.",
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
const newLeaderHead = resolveHead(leaderCwd);
|
|
2406
|
+
if (cycleLeaderHead && newLeaderHead && cycleLeaderHead !== newLeaderHead) {
|
|
2407
|
+
for (const worker of config.workers) {
|
|
2408
|
+
if (!worker.worktree_path || !(await pathExists(worker.worktree_path))) continue;
|
|
2409
|
+
const status = await readJwcWorkerStatus(config.team_name, worker.id, cwd, env);
|
|
2410
|
+
if (!["idle", "done", "failed"].includes(status.state)) {
|
|
2411
|
+
await appendIntegrationEvent(dir, "worker_cross_rebase_skipped", worker, {
|
|
2412
|
+
worker_name: worker.id,
|
|
2413
|
+
worker_state: status.state,
|
|
2414
|
+
leader_head: newLeaderHead,
|
|
2415
|
+
summary: `skipped cross-rebase for ${worker.id}`,
|
|
2416
|
+
});
|
|
2417
|
+
hygieneEntries.push({
|
|
2418
|
+
recorded_at: now(),
|
|
2419
|
+
operation: "cross_rebase",
|
|
2420
|
+
worker_name: worker.id,
|
|
2421
|
+
task_id: worker.assigned_tasks[0],
|
|
2422
|
+
status: "skipped",
|
|
2423
|
+
leader_head_after: newLeaderHead,
|
|
2424
|
+
worktree_path: worker.worktree_path,
|
|
2425
|
+
detail: `Worker state ${status.state} is not eligible for automatic cross-rebase.`,
|
|
2426
|
+
});
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
if (worktreeIsDirty(worker.worktree_path)) {
|
|
2430
|
+
hygieneEntries.push({
|
|
2431
|
+
recorded_at: now(),
|
|
2432
|
+
operation: "cross_rebase",
|
|
2433
|
+
worker_name: worker.id,
|
|
2434
|
+
task_id: worker.assigned_tasks[0],
|
|
2435
|
+
status: "skipped",
|
|
2436
|
+
leader_head_after: newLeaderHead,
|
|
2437
|
+
worktree_path: worker.worktree_path,
|
|
2438
|
+
detail: "Worker worktree is dirty after integration; automatic cross-rebase skipped.",
|
|
2439
|
+
});
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
const before = resolveHead(worker.worktree_path);
|
|
2443
|
+
const rebase = runGitResult(worker.worktree_path, ["rebase", newLeaderHead]);
|
|
2444
|
+
if (rebase.ok) {
|
|
2445
|
+
const after = resolveHead(worker.worktree_path);
|
|
2446
|
+
integrationByWorker[worker.id] = {
|
|
2447
|
+
...(integrationByWorker[worker.id] ?? {}),
|
|
2448
|
+
last_rebased_leader_head: newLeaderHead,
|
|
2449
|
+
conflict_commit: undefined,
|
|
2450
|
+
conflict_files: undefined,
|
|
2451
|
+
...integrationNowState("idle"),
|
|
2452
|
+
};
|
|
2453
|
+
await appendIntegrationEvent(dir, "worker_cross_rebase_applied", worker, {
|
|
2454
|
+
worker_name: worker.id,
|
|
2455
|
+
leader_head: newLeaderHead,
|
|
2456
|
+
worktree_path: worker.worktree_path,
|
|
2457
|
+
summary: `cross-rebased ${worker.id}`,
|
|
2458
|
+
});
|
|
2459
|
+
hygieneEntries.push({
|
|
2460
|
+
recorded_at: now(),
|
|
2461
|
+
operation: "cross_rebase",
|
|
2462
|
+
worker_name: worker.id,
|
|
2463
|
+
task_id: worker.assigned_tasks[0],
|
|
2464
|
+
status: "applied",
|
|
2465
|
+
operational_commit: after,
|
|
2466
|
+
leader_head_after: newLeaderHead,
|
|
2467
|
+
worker_head_before: before,
|
|
2468
|
+
worker_head_after: after,
|
|
2469
|
+
worktree_path: worker.worktree_path,
|
|
2470
|
+
detail: "Runtime rebase moved worker history onto updated leader head.",
|
|
2471
|
+
});
|
|
2472
|
+
} else {
|
|
2473
|
+
const conflictFiles = listConflictFiles(worker.worktree_path);
|
|
2474
|
+
runGitResult(worker.worktree_path, ["rebase", "--abort"]);
|
|
2475
|
+
integrationByWorker[worker.id] = {
|
|
2476
|
+
...(integrationByWorker[worker.id] ?? {}),
|
|
2477
|
+
conflict_commit: before ?? newLeaderHead,
|
|
2478
|
+
conflict_files: conflictFiles,
|
|
2479
|
+
...integrationNowState("rebase_conflict"),
|
|
2480
|
+
};
|
|
2481
|
+
await appendIntegrationEvent(dir, "worker_cross_rebase_conflict", worker, {
|
|
2482
|
+
worker_name: worker.id,
|
|
2483
|
+
leader_head: newLeaderHead,
|
|
2484
|
+
conflict_files: conflictFiles,
|
|
2485
|
+
stderr: rebase.stderr || rebase.stdout,
|
|
2486
|
+
summary: `cross-rebase conflict for ${worker.id}`,
|
|
2487
|
+
});
|
|
2488
|
+
await appendIntegrationReport(dir, {
|
|
2489
|
+
worker: worker.id,
|
|
2490
|
+
operation: "rebase",
|
|
2491
|
+
files: conflictFiles,
|
|
2492
|
+
detail: `rebase failed and was aborted: ${(rebase.stderr || rebase.stdout).slice(0, 200)}`,
|
|
2493
|
+
});
|
|
2494
|
+
await notifyIntegrationConflict(
|
|
2495
|
+
config,
|
|
2496
|
+
worker,
|
|
2497
|
+
`CONFLICT: cross-rebase failed for ${worker.id}; files: ${conflictFiles.join(",") || "unknown"}. Manual resolution required; runtime aborted the rebase and did not auto-resolve.`,
|
|
2498
|
+
cwd,
|
|
2499
|
+
env,
|
|
2500
|
+
);
|
|
2501
|
+
hygieneEntries.push({
|
|
2502
|
+
recorded_at: now(),
|
|
2503
|
+
operation: "cross_rebase",
|
|
2504
|
+
worker_name: worker.id,
|
|
2505
|
+
task_id: worker.assigned_tasks[0],
|
|
2506
|
+
status: "conflict",
|
|
2507
|
+
leader_head_after: newLeaderHead,
|
|
2508
|
+
worker_head_before: before,
|
|
2509
|
+
worker_head_after: resolveHead(worker.worktree_path),
|
|
2510
|
+
worktree_path: worker.worktree_path,
|
|
2511
|
+
detail: `Runtime cross-rebase failed and was aborted: ${(rebase.stderr || rebase.stdout).slice(0, 200)}`,
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
await appendCommitHygieneEntries(config, hygieneEntries);
|
|
2517
|
+
return integrationByWorker;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
async function initializeStateDirs(dir: string, workers: JwcTeamWorker[]): Promise<void> {
|
|
2521
|
+
// Empty mailbox directories are runtime state, so they must exist before messages arrive.
|
|
2522
|
+
await fs.mkdir(path.join(dir, "mailbox"), { recursive: true });
|
|
2523
|
+
for (const worker of workers) {
|
|
2524
|
+
await fs.mkdir(mailboxDirPath(dir, worker.id), { recursive: true });
|
|
2525
|
+
await writeJsonFile(mailboxPath(dir, worker.id), { messages: [] });
|
|
2526
|
+
await writeJsonFile(path.join(workerDir(dir, worker.id), "status.json"), { state: "idle", updated_at: now() });
|
|
2527
|
+
await writeJsonFile(workerLifecyclePath(dir, worker.id), {
|
|
2528
|
+
worker: worker.id,
|
|
2529
|
+
lifecycle_state: "starting",
|
|
2530
|
+
worker_status_state: "idle",
|
|
2531
|
+
updated_at: now(),
|
|
2532
|
+
} satisfies JwcTeamWorkerLifecycle);
|
|
2533
|
+
await writeJsonFile(path.join(workerDir(dir, worker.id), "heartbeat.json"), {
|
|
2534
|
+
pid: 0,
|
|
2535
|
+
last_turn_at: now(),
|
|
2536
|
+
turn_count: 0,
|
|
2537
|
+
alive: true,
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
// Empty leader mailbox directory is runtime state, so it must exist before messages arrive.
|
|
2541
|
+
await fs.mkdir(mailboxDirPath(dir, "leader-fixed"), { recursive: true });
|
|
2542
|
+
await writeJsonFile(mailboxPath(dir, "leader-fixed"), { messages: [] });
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
export async function startJwcTeam(options: JwcTeamStartOptions): Promise<JwcTeamSnapshot> {
|
|
2546
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2547
|
+
const env = options.env ?? process.env;
|
|
2548
|
+
if (!Number.isInteger(options.workerCount) || options.workerCount < 1 || options.workerCount > GJC_TEAM_MAX_WORKERS)
|
|
2549
|
+
throw new Error(`invalid_team_worker_count:${options.workerCount}:expected_1_${GJC_TEAM_MAX_WORKERS}`);
|
|
2550
|
+
const workerCliPlan = resolveJwcTeamWorkerCliPlan(options.workerCount, env);
|
|
2551
|
+
const stateRoot = resolveJwcTeamStateRoot(cwd, env);
|
|
2552
|
+
const teamName = sanitizeName(options.teamName ?? makeTeamName(options.task, env));
|
|
2553
|
+
const displayName = sanitizeName(options.teamName ?? options.task).slice(0, 30) || teamName;
|
|
2554
|
+
const dir = teamDir(stateRoot, teamName);
|
|
2555
|
+
const createdAt = now();
|
|
2556
|
+
const worktreeMode = resolveDefaultWorktreeMode(options.worktreeMode);
|
|
2557
|
+
const tmuxCommand = resolveJwcTmuxCommand(env);
|
|
2558
|
+
const tmuxContext = options.dryRun
|
|
2559
|
+
? { sessionName: "dry-run", windowIndex: "0", leaderPaneId: "%dry-run-leader", target: "dry-run:0" }
|
|
2560
|
+
: readCurrentTmuxLeaderContext(tmuxCommand, env);
|
|
2561
|
+
const initialWorkers = buildWorkers(options.workerCount, options.agentType, stateRoot);
|
|
2562
|
+
const initialTasks = buildInitialTasks(options.task, initialWorkers);
|
|
2563
|
+
const workers: JwcTeamWorker[] = [];
|
|
2564
|
+
try {
|
|
2565
|
+
for (const worker of initialWorkers)
|
|
2566
|
+
workers.push(options.dryRun ? worker : await ensureWorkerWorktree(cwd, dir, teamName, worker, worktreeMode));
|
|
2567
|
+
} catch (error) {
|
|
2568
|
+
await rollbackCreatedWorktrees(workers);
|
|
2569
|
+
throw error;
|
|
2570
|
+
}
|
|
2571
|
+
const config: JwcTeamConfig = {
|
|
2572
|
+
team_name: teamName,
|
|
2573
|
+
display_name: displayName,
|
|
2574
|
+
requested_name: options.teamName ?? displayName,
|
|
2575
|
+
task: options.task,
|
|
2576
|
+
agent_type: options.agentType,
|
|
2577
|
+
worker_count: options.workerCount,
|
|
2578
|
+
max_workers: GJC_TEAM_MAX_WORKERS,
|
|
2579
|
+
state_root: stateRoot,
|
|
2580
|
+
worker_command: resolveJwcWorkerCommand(cwd, env),
|
|
2581
|
+
worker_cli_plan: workerCliPlan,
|
|
2582
|
+
tmux_command: tmuxCommand,
|
|
2583
|
+
tmux_session: tmuxContext.sessionName,
|
|
2584
|
+
tmux_session_name: tmuxContext.sessionName,
|
|
2585
|
+
tmux_target: tmuxContext.target,
|
|
2586
|
+
workspace_mode: worktreeMode.enabled ? "worktree" : "direct",
|
|
2587
|
+
dry_run: options.dryRun ?? false,
|
|
2588
|
+
leader: { session_id: env.GJC_SESSION_ID ?? env.CODEX_SESSION_ID ?? "", pane_id: tmuxContext.leaderPaneId, cwd },
|
|
2589
|
+
leader_cwd: cwd,
|
|
2590
|
+
team_state_root: stateRoot,
|
|
2591
|
+
workers,
|
|
2592
|
+
created_at: createdAt,
|
|
2593
|
+
updated_at: createdAt,
|
|
2594
|
+
};
|
|
2595
|
+
await initializeStateDirs(dir, config.workers);
|
|
2596
|
+
await writeJsonFile(path.join(dir, "config.json"), config);
|
|
2597
|
+
await writeJsonFile(path.join(dir, "manifest.v2.json"), {
|
|
2598
|
+
version: 2,
|
|
2599
|
+
team_name: config.team_name,
|
|
2600
|
+
display_name: config.display_name,
|
|
2601
|
+
requested_name: config.requested_name,
|
|
2602
|
+
tmux_session: config.tmux_session,
|
|
2603
|
+
tmux_session_name: config.tmux_session_name,
|
|
2604
|
+
tmux_target: config.tmux_target,
|
|
2605
|
+
worker_command: config.worker_command,
|
|
2606
|
+
worker_cli_plan: config.worker_cli_plan,
|
|
2607
|
+
tmux_command: config.tmux_command,
|
|
2608
|
+
leader: config.leader,
|
|
2609
|
+
workers: config.workers,
|
|
2610
|
+
workspace_mode: config.workspace_mode,
|
|
2611
|
+
dry_run: config.dry_run,
|
|
2612
|
+
created_at: createdAt,
|
|
2613
|
+
updated_at: createdAt,
|
|
2614
|
+
});
|
|
2615
|
+
await writePhase(dir, "starting");
|
|
2616
|
+
for (const task of initialTasks) await writeTask(dir, task);
|
|
2617
|
+
await appendEvent(dir, {
|
|
2618
|
+
type: "team_started",
|
|
2619
|
+
message: options.dryRun
|
|
2620
|
+
? "Created native gjc team dry-run state without starting tmux workers"
|
|
2621
|
+
: "Started native gjc team runtime",
|
|
2622
|
+
data: {
|
|
2623
|
+
worker_count: options.workerCount,
|
|
2624
|
+
agent_type: options.agentType,
|
|
2625
|
+
workspace_mode: config.workspace_mode,
|
|
2626
|
+
dry_run: config.dry_run,
|
|
2627
|
+
},
|
|
2628
|
+
});
|
|
2629
|
+
await appendTelemetry(dir, {
|
|
2630
|
+
type: "team_runtime",
|
|
2631
|
+
message: options.dryRun ? "Native gjc team dry-run state initialized" : "Native gjc team runtime initialized",
|
|
2632
|
+
data: {
|
|
2633
|
+
state_root: stateRoot,
|
|
2634
|
+
worker_command: config.worker_command,
|
|
2635
|
+
worker_cli_plan: workerCliPlan,
|
|
2636
|
+
workspace_mode: config.workspace_mode,
|
|
2637
|
+
dry_run: config.dry_run,
|
|
2638
|
+
},
|
|
2639
|
+
});
|
|
2640
|
+
let tmuxWorkers: JwcTeamWorker[];
|
|
2641
|
+
try {
|
|
2642
|
+
tmuxWorkers = await startTmuxSession(config, dir, options.dryRun ?? false, env);
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
await writePhase(dir, "failed");
|
|
2645
|
+
await appendEvent(dir, {
|
|
2646
|
+
type: "team_start_failed",
|
|
2647
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2648
|
+
});
|
|
2649
|
+
killWorkerPanes(config);
|
|
2650
|
+
await rollbackCreatedWorktrees(config.workers);
|
|
2651
|
+
throw error;
|
|
2652
|
+
}
|
|
2653
|
+
const runningConfig = {
|
|
2654
|
+
...config,
|
|
2655
|
+
workers: tmuxWorkers.map(worker => ({ ...worker, status: "idle" as const, last_heartbeat: now() })),
|
|
2656
|
+
updated_at: now(),
|
|
2657
|
+
};
|
|
2658
|
+
await writeJsonFile(path.join(dir, "config.json"), runningConfig);
|
|
2659
|
+
await writeWorkerLifecycleForConfig(dir, runningConfig, "starting", worker => ({
|
|
2660
|
+
pane_id: worker.pane_id,
|
|
2661
|
+
started_at: runningConfig.created_at,
|
|
2662
|
+
}));
|
|
2663
|
+
await writePhase(dir, "running");
|
|
2664
|
+
return readJwcTeamSnapshot(teamName, cwd, env);
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
export async function readJwcTeamSnapshot(
|
|
2668
|
+
teamName: string,
|
|
2669
|
+
cwd = process.cwd(),
|
|
2670
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
2671
|
+
options: JwcTeamSnapshotOptions = {},
|
|
2672
|
+
): Promise<JwcTeamSnapshot> {
|
|
2673
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
2674
|
+
const config = await readConfig(dir);
|
|
2675
|
+
const storedPhase = await readPhase(dir);
|
|
2676
|
+
const tasks = await readTasks(dir);
|
|
2677
|
+
const taskCounts: Record<JwcTeamTaskStatus, number> = {
|
|
2678
|
+
pending: 0,
|
|
2679
|
+
blocked: 0,
|
|
2680
|
+
in_progress: 0,
|
|
2681
|
+
completed: 0,
|
|
2682
|
+
failed: 0,
|
|
2683
|
+
};
|
|
2684
|
+
for (const task of tasks) taskCounts[task.status] += 1;
|
|
2685
|
+
const monitor = await readJsonFile<JwcTeamMonitorSnapshot>(monitorSnapshotPath(dir));
|
|
2686
|
+
const workerLifecycleById = await readWorkerLifecycleById(dir, config);
|
|
2687
|
+
const notificationSummary =
|
|
2688
|
+
options.reconcileNotifications === true
|
|
2689
|
+
? await reconcileTeamNotifications(dir, config)
|
|
2690
|
+
: summarizeNotifications(await listNotificationRecords(dir));
|
|
2691
|
+
const phase = await resolveJwcTeamSnapshotPhase(dir, config, storedPhase, tasks, monitor);
|
|
2692
|
+
return {
|
|
2693
|
+
team_name: config.team_name,
|
|
2694
|
+
display_name: config.display_name,
|
|
2695
|
+
phase,
|
|
2696
|
+
state_dir: dir,
|
|
2697
|
+
tmux_session: config.tmux_session,
|
|
2698
|
+
tmux_session_name: config.tmux_session_name,
|
|
2699
|
+
tmux_target: config.tmux_target,
|
|
2700
|
+
task_total: tasks.length,
|
|
2701
|
+
task_counts: taskCounts,
|
|
2702
|
+
workers: config.workers,
|
|
2703
|
+
integration_by_worker: monitor?.integration_by_worker,
|
|
2704
|
+
worker_lifecycle_by_id: workerLifecycleById,
|
|
2705
|
+
notification_summary: notificationSummary,
|
|
2706
|
+
updated_at: config.updated_at,
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
export async function monitorJwcTeamSnapshot(
|
|
2710
|
+
teamName: string,
|
|
2711
|
+
cwd = process.cwd(),
|
|
2712
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
2713
|
+
): Promise<JwcTeamSnapshot> {
|
|
2714
|
+
const snapshot = await monitorJwcTeam(teamName, cwd, env);
|
|
2715
|
+
return snapshot;
|
|
2716
|
+
}
|
|
2717
|
+
function workerIntegrationFingerprint(head: string | null, classification: JwcWorkerCheckpointClassification): string {
|
|
2718
|
+
return `${head ?? "no-head"}:${classification.kind}:${classification.files.join("\0")}`;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
export async function requestJwcWorkerIntegrationAttempt(
|
|
2722
|
+
cwd = process.cwd(),
|
|
2723
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
2724
|
+
): Promise<JwcWorkerIntegrationAttemptRequestResult> {
|
|
2725
|
+
const teamName = env.GJC_TEAM_NAME?.trim();
|
|
2726
|
+
const worker = env.GJC_TEAM_WORKER_ID?.trim() || env.GJC_TEAM_INTERNAL_WORKER?.split("/").pop()?.trim();
|
|
2727
|
+
if (!teamName || !worker) return { requested: false, reason: "not_worker" };
|
|
2728
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
2729
|
+
const config = await readConfig(dir);
|
|
2730
|
+
const configuredWorker = config.workers.find(candidate => candidate.id === worker);
|
|
2731
|
+
const worktreePath = env.GJC_TEAM_WORKTREE_PATH?.trim() || configuredWorker?.worktree_path;
|
|
2732
|
+
if (!worktreePath || !(await pathExists(worktreePath)))
|
|
2733
|
+
return { requested: false, reason: "missing_worktree", worker, team_name: teamName };
|
|
2734
|
+
const classification = classifyWorkerCheckpointStatus(worktreePath);
|
|
2735
|
+
const head = resolveHead(worktreePath);
|
|
2736
|
+
if (classification.kind === "git_error") {
|
|
2737
|
+
return { requested: false, reason: "git_error", worker, team_name: teamName, head, status: classification.kind };
|
|
2738
|
+
}
|
|
2739
|
+
if (classification.kind === "protected_only") {
|
|
2740
|
+
return { requested: false, reason: "no_changes", worker, team_name: teamName, head, status: classification.kind };
|
|
2741
|
+
}
|
|
2742
|
+
if (classification.kind === "clean" && configuredWorker?.worktree_base_ref === head) {
|
|
2743
|
+
return { requested: false, reason: "no_changes", worker, team_name: teamName, head, status: classification.kind };
|
|
2744
|
+
}
|
|
2745
|
+
const fingerprint = workerIntegrationFingerprint(head, classification);
|
|
2746
|
+
const dedupePath = workerIntegrationDedupePath(dir, worker);
|
|
2747
|
+
const dedupe = (await readJsonFile<JwcWorkerIntegrationDedupeState>(dedupePath)) ?? {};
|
|
2748
|
+
if (dedupe.last_requested_fingerprint === fingerprint) {
|
|
2749
|
+
return {
|
|
2750
|
+
requested: false,
|
|
2751
|
+
reason: "deduped",
|
|
2752
|
+
worker,
|
|
2753
|
+
team_name: teamName,
|
|
2754
|
+
fingerprint,
|
|
2755
|
+
head,
|
|
2756
|
+
status: classification.kind,
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
await writeJsonFile(dedupePath, {
|
|
2760
|
+
last_requested_fingerprint: fingerprint,
|
|
2761
|
+
last_requested_head: head,
|
|
2762
|
+
last_requested_status: classification.kind,
|
|
2763
|
+
last_requested_at: now(),
|
|
2764
|
+
} satisfies JwcWorkerIntegrationDedupeState);
|
|
2765
|
+
await appendEvent(dir, {
|
|
2766
|
+
type: "worker_integration_attempt_requested",
|
|
2767
|
+
worker,
|
|
2768
|
+
message: `Worker ${worker} requested leader integration attempt`,
|
|
2769
|
+
data: { worker_name: worker, worker_head: head, status: classification.kind, files: classification.files },
|
|
2770
|
+
});
|
|
2771
|
+
await sendJwcTeamMessage(
|
|
2772
|
+
teamName,
|
|
2773
|
+
worker,
|
|
2774
|
+
"leader-fixed",
|
|
2775
|
+
`INTEGRATION REQUESTED: ${worker} has ${classification.kind} git changes at ${head?.slice(0, 12) ?? "unknown-head"}.`,
|
|
2776
|
+
cwd,
|
|
2777
|
+
env,
|
|
2778
|
+
).catch(() => undefined);
|
|
2779
|
+
await appendCommitHygieneEntries(config, [
|
|
2780
|
+
{
|
|
2781
|
+
recorded_at: now(),
|
|
2782
|
+
operation: "leader_integration_attempt",
|
|
2783
|
+
worker_name: worker,
|
|
2784
|
+
task_id: configuredWorker?.assigned_tasks[0],
|
|
2785
|
+
status: "applied",
|
|
2786
|
+
source_commit: head ?? undefined,
|
|
2787
|
+
worker_head_after: head,
|
|
2788
|
+
worktree_path: worktreePath,
|
|
2789
|
+
detail: "Worker turn-end requested a leader integration attempt for semantic git changes.",
|
|
2790
|
+
},
|
|
2791
|
+
]);
|
|
2792
|
+
return {
|
|
2793
|
+
requested: true,
|
|
2794
|
+
reason: "requested",
|
|
2795
|
+
worker,
|
|
2796
|
+
team_name: teamName,
|
|
2797
|
+
fingerprint,
|
|
2798
|
+
head,
|
|
2799
|
+
status: classification.kind,
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
export async function buildTeamHudSummary(
|
|
2804
|
+
snapshot: JwcTeamSnapshot,
|
|
2805
|
+
latestEvent?: JwcTeamEvent,
|
|
2806
|
+
latestMessage?: JwcTeamMailboxMessage,
|
|
2807
|
+
): Promise<WorkflowHudSummary> {
|
|
2808
|
+
return buildWorkflowTeamHudSummary({
|
|
2809
|
+
phase: snapshot.phase,
|
|
2810
|
+
task_total: snapshot.task_total,
|
|
2811
|
+
task_counts: snapshot.task_counts,
|
|
2812
|
+
workers: snapshot.workers,
|
|
2813
|
+
updated_at: snapshot.updated_at,
|
|
2814
|
+
latestEvent,
|
|
2815
|
+
latestMessage,
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
export async function monitorJwcTeam(
|
|
2820
|
+
teamName: string,
|
|
2821
|
+
cwd = process.cwd(),
|
|
2822
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
2823
|
+
): Promise<JwcTeamSnapshot> {
|
|
2824
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
2825
|
+
const config = await readConfig(dir);
|
|
2826
|
+
const previous = await readJsonFile<JwcTeamMonitorSnapshot>(monitorSnapshotPath(dir));
|
|
2827
|
+
await reconcileJwcTeamStaleClaims(teamName, dir, config, env);
|
|
2828
|
+
const integrationByWorker = await integrateJwcWorkerCommits(config, dir, previous, cwd, env);
|
|
2829
|
+
await writeJsonFile(monitorSnapshotPath(dir), { integration_by_worker: integrationByWorker, updated_at: now() });
|
|
2830
|
+
await replayJwcTeamNotifications(teamName, cwd, env);
|
|
2831
|
+
await computeLifecycleNudges(config, dir, cwd, env);
|
|
2832
|
+
return readJwcTeamSnapshot(teamName, cwd, env);
|
|
2833
|
+
}
|
|
2834
|
+
export async function listJwcTeams(
|
|
2835
|
+
cwd = process.cwd(),
|
|
2836
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
2837
|
+
): Promise<JwcTeamSnapshot[]> {
|
|
2838
|
+
const root = resolveJwcTeamStateRoot(cwd, env);
|
|
2839
|
+
try {
|
|
2840
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
2841
|
+
const snapshots = await Promise.all(
|
|
2842
|
+
entries
|
|
2843
|
+
.filter(entry => entry.isDirectory())
|
|
2844
|
+
.map(entry => readJwcTeamSnapshot(entry.name, cwd, env).catch(() => null)),
|
|
2845
|
+
);
|
|
2846
|
+
return snapshots.filter((snapshot): snapshot is JwcTeamSnapshot => snapshot != null);
|
|
2847
|
+
} catch (error) {
|
|
2848
|
+
if (isEnoent(error)) return [];
|
|
2849
|
+
throw error;
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function parsePaneAttemptResult(value: string): JwcTeamPaneAttemptResult {
|
|
2854
|
+
if (value === "sent" || value === "queued" || value === "deferred" || value === "failed") return value;
|
|
2855
|
+
throw new Error(`invalid_pane_attempt_result:${value}`);
|
|
2856
|
+
}
|
|
2857
|
+
async function writeJwcWorkerStartupAck(
|
|
2858
|
+
teamName: string,
|
|
2859
|
+
worker: string,
|
|
2860
|
+
cwd: string,
|
|
2861
|
+
env: NodeJS.ProcessEnv,
|
|
2862
|
+
input: Record<string, unknown>,
|
|
2863
|
+
): Promise<Record<string, unknown>> {
|
|
2864
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
2865
|
+
const config = await readConfig(dir);
|
|
2866
|
+
const teamWorker = findKnownWorker(config, worker);
|
|
2867
|
+
const ack = {
|
|
2868
|
+
worker,
|
|
2869
|
+
pid: typeof input.pid === "number" ? input.pid : undefined,
|
|
2870
|
+
session: typeof input.session === "string" ? input.session : undefined,
|
|
2871
|
+
protocol_version: String(input.protocol_version ?? "1"),
|
|
2872
|
+
ack_at: now(),
|
|
2873
|
+
};
|
|
2874
|
+
await writeJsonFile(path.join(workerDir(dir, worker), "startup-ack.json"), ack);
|
|
2875
|
+
await writeWorkerLifecycleRecord(dir, teamWorker, "ready", {
|
|
2876
|
+
pane_id: teamWorker.pane_id,
|
|
2877
|
+
pid: typeof input.pid === "number" ? input.pid : undefined,
|
|
2878
|
+
started_at: ack.ack_at,
|
|
2879
|
+
});
|
|
2880
|
+
await appendEvent(dir, { type: "worker_startup_ack", worker, message: `Worker ${worker} acknowledged startup` });
|
|
2881
|
+
return ack;
|
|
2882
|
+
}
|
|
2883
|
+
function parseDurationEnv(env: NodeJS.ProcessEnv, name: string, fallbackMs: number): number {
|
|
2884
|
+
const raw = env[name]?.trim();
|
|
2885
|
+
if (!raw) return fallbackMs;
|
|
2886
|
+
const parsed = Number(raw);
|
|
2887
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallbackMs;
|
|
2888
|
+
}
|
|
2889
|
+
async function writeLifecycleNudge(
|
|
2890
|
+
dir: string,
|
|
2891
|
+
worker: string,
|
|
2892
|
+
condition: string,
|
|
2893
|
+
severity: "warning" | "error",
|
|
2894
|
+
suggestedAction: string,
|
|
2895
|
+
env: NodeJS.ProcessEnv,
|
|
2896
|
+
): Promise<void> {
|
|
2897
|
+
const fingerprint = `nudge-${stableHash([worker, condition].join(":"))}`;
|
|
2898
|
+
const nudgePath = path.join(workerDir(dir, worker), "nudges", `${fingerprint}.json`);
|
|
2899
|
+
const existing = await readJsonFile<Record<string, unknown>>(nudgePath);
|
|
2900
|
+
const nowMs = Date.now();
|
|
2901
|
+
const cooldownMs = parseDurationEnv(env, "GJC_TEAM_NUDGE_COOLDOWN_MS", 30_000);
|
|
2902
|
+
const cooldownUntil = typeof existing?.cooldown_until === "string" ? Date.parse(existing.cooldown_until) : 0;
|
|
2903
|
+
if (existing && Number.isFinite(cooldownUntil) && cooldownUntil > nowMs) return;
|
|
2904
|
+
const firstSeen = typeof existing?.first_seen_at === "string" ? existing.first_seen_at : now();
|
|
2905
|
+
const count = typeof existing?.count === "number" ? existing.count + 1 : 1;
|
|
2906
|
+
const record = {
|
|
2907
|
+
fingerprint,
|
|
2908
|
+
worker,
|
|
2909
|
+
condition,
|
|
2910
|
+
severity,
|
|
2911
|
+
first_seen_at: firstSeen,
|
|
2912
|
+
last_seen_at: now(),
|
|
2913
|
+
cooldown_until: new Date(nowMs + cooldownMs).toISOString(),
|
|
2914
|
+
count,
|
|
2915
|
+
suggested_action: suggestedAction,
|
|
2916
|
+
auto_action_taken: false,
|
|
2917
|
+
};
|
|
2918
|
+
await writeJsonFile(nudgePath, record);
|
|
2919
|
+
await appendEvent(dir, {
|
|
2920
|
+
type: "worker_lifecycle_nudge",
|
|
2921
|
+
worker,
|
|
2922
|
+
message: suggestedAction,
|
|
2923
|
+
data: { condition, severity, fingerprint, auto_action_taken: false },
|
|
2924
|
+
});
|
|
2925
|
+
await writeNotificationRecord(dir, {
|
|
2926
|
+
id: `ntf-${stableHash(["worker_lifecycle", worker, condition].join(":"))}`,
|
|
2927
|
+
kind: "worker_lifecycle",
|
|
2928
|
+
team_name: path.basename(dir),
|
|
2929
|
+
recipient: "leader-fixed",
|
|
2930
|
+
source: { type: "worker", id: worker },
|
|
2931
|
+
delivery_state: "pending",
|
|
2932
|
+
created_at: firstSeen,
|
|
2933
|
+
updated_at: now(),
|
|
2934
|
+
replay_count: 0,
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
async function computeLifecycleNudges(
|
|
2938
|
+
config: JwcTeamConfig,
|
|
2939
|
+
dir: string,
|
|
2940
|
+
_cwd: string,
|
|
2941
|
+
env: NodeJS.ProcessEnv,
|
|
2942
|
+
): Promise<void> {
|
|
2943
|
+
const startupGraceMs = parseDurationEnv(env, "GJC_TEAM_STARTUP_GRACE_MS", 30_000);
|
|
2944
|
+
const heartbeatStaleMs = parseDurationEnv(env, "GJC_TEAM_HEARTBEAT_STALE_MS", 120_000);
|
|
2945
|
+
const createdAt = Date.parse(config.created_at);
|
|
2946
|
+
const ageMs = Date.now() - (Number.isFinite(createdAt) ? createdAt : Date.now());
|
|
2947
|
+
for (const worker of config.workers) {
|
|
2948
|
+
const ack = await readJsonFile<Record<string, unknown>>(path.join(workerDir(dir, worker.id), "startup-ack.json"));
|
|
2949
|
+
if (!ack && ageMs >= startupGraceMs) {
|
|
2950
|
+
await writeLifecycleNudge(
|
|
2951
|
+
dir,
|
|
2952
|
+
worker.id,
|
|
2953
|
+
"missing_startup_ack",
|
|
2954
|
+
"warning",
|
|
2955
|
+
`Worker ${worker.id} has not sent startup ACK; leader may inspect or relaunch manually.`,
|
|
2956
|
+
env,
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
const heartbeat = await readJwcWorkerHeartbeat(config.team_name, worker.id, config.leader.cwd, {
|
|
2960
|
+
...env,
|
|
2961
|
+
GJC_TEAM_STATE_ROOT: config.state_root,
|
|
2962
|
+
});
|
|
2963
|
+
const heartbeatAt = Date.parse(heartbeat?.last_turn_at ?? worker.last_heartbeat);
|
|
2964
|
+
if (Number.isFinite(heartbeatAt) && Date.now() - heartbeatAt >= heartbeatStaleMs) {
|
|
2965
|
+
await writeLifecycleNudge(
|
|
2966
|
+
dir,
|
|
2967
|
+
worker.id,
|
|
2968
|
+
"stale_heartbeat",
|
|
2969
|
+
"warning",
|
|
2970
|
+
`Worker ${worker.id} heartbeat is stale; leader may inspect or relaunch manually.`,
|
|
2971
|
+
env,
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
if (worker.status === "stopped") {
|
|
2975
|
+
await writeLifecycleNudge(
|
|
2976
|
+
dir,
|
|
2977
|
+
worker.id,
|
|
2978
|
+
"worker_stopped",
|
|
2979
|
+
"error",
|
|
2980
|
+
`Worker ${worker.id} is stopped before team completion; leader action is required.`,
|
|
2981
|
+
env,
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
export async function shutdownJwcTeam(
|
|
2988
|
+
teamName: string,
|
|
2989
|
+
cwd = process.cwd(),
|
|
2990
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
2991
|
+
): Promise<JwcTeamSnapshot> {
|
|
2992
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
2993
|
+
const config = await readConfig(dir);
|
|
2994
|
+
const tasks = await readTasks(dir);
|
|
2995
|
+
const evidenceFailures = tasks
|
|
2996
|
+
.map(task => {
|
|
2997
|
+
const reason = task.status === "completed" ? getJwcTeamTaskCompletionEvidenceFailure(task) : null;
|
|
2998
|
+
return reason ? { task_id: task.id, reason } : null;
|
|
2999
|
+
})
|
|
3000
|
+
.filter((failure): failure is { task_id: string; reason: string } => failure != null);
|
|
3001
|
+
const shutdownRequestId = `shutdown-${stableHash([config.team_name, now(), randomUUID()].join(":"))}`;
|
|
3002
|
+
const shutdownRequestedAt = now();
|
|
3003
|
+
await Promise.all(
|
|
3004
|
+
config.workers.map(worker =>
|
|
3005
|
+
writeJwcShutdownRequest(
|
|
3006
|
+
teamName,
|
|
3007
|
+
worker.id,
|
|
3008
|
+
"leader-fixed",
|
|
3009
|
+
cwd,
|
|
3010
|
+
env,
|
|
3011
|
+
shutdownRequestId,
|
|
3012
|
+
"graceful",
|
|
3013
|
+
shutdownRequestedAt,
|
|
3014
|
+
),
|
|
3015
|
+
),
|
|
3016
|
+
);
|
|
3017
|
+
const monitor = await readJsonFile<JwcTeamMonitorSnapshot>(monitorSnapshotPath(dir));
|
|
3018
|
+
const completionVerified = tasks.length === 0 || tasks.every(isJwcTeamTaskCompletionVerified);
|
|
3019
|
+
const pendingIntegration = completionVerified ? await hasPendingJwcTeamIntegration(dir, config, monitor) : false;
|
|
3020
|
+
killWorkerPanes(config);
|
|
3021
|
+
await removeCleanCreatedWorktrees(config.workers);
|
|
3022
|
+
const stopped = {
|
|
3023
|
+
...config,
|
|
3024
|
+
workers: config.workers.map(worker => ({ ...worker, status: "stopped" as const, last_heartbeat: now() })),
|
|
3025
|
+
updated_at: now(),
|
|
3026
|
+
};
|
|
3027
|
+
await writeJsonFile(path.join(dir, "config.json"), stopped);
|
|
3028
|
+
await writeWorkerLifecycleForConfig(dir, stopped, "stopped", worker => ({
|
|
3029
|
+
pane_id: worker.pane_id,
|
|
3030
|
+
stopped_at: stopped.updated_at,
|
|
3031
|
+
stop_reason: "graceful_shutdown",
|
|
3032
|
+
shutdown_request_id: shutdownRequestId,
|
|
3033
|
+
shutdown_requested_at: shutdownRequestedAt,
|
|
3034
|
+
shutdown_mode: "graceful",
|
|
3035
|
+
}));
|
|
3036
|
+
const workerLifecycleById = await readWorkerLifecycleById(dir, stopped);
|
|
3037
|
+
const gracefulShutdownComplete = stopped.workers.every(worker => {
|
|
3038
|
+
const lifecycle = workerLifecycleById[worker.id];
|
|
3039
|
+
return (
|
|
3040
|
+
lifecycle?.lifecycle_state === "stopped" &&
|
|
3041
|
+
lifecycle.shutdown_request_id === shutdownRequestId &&
|
|
3042
|
+
lifecycle.shutdown_mode === "graceful"
|
|
3043
|
+
);
|
|
3044
|
+
});
|
|
3045
|
+
const shutdownPhase: JwcTeamPhase =
|
|
3046
|
+
completionVerified && gracefulShutdownComplete
|
|
3047
|
+
? pendingIntegration
|
|
3048
|
+
? "awaiting_integration"
|
|
3049
|
+
: "complete"
|
|
3050
|
+
: evidenceFailures.length > 0 || tasks.some(task => task.status === "failed" || task.status === "blocked")
|
|
3051
|
+
? "failed"
|
|
3052
|
+
: "cancelled";
|
|
3053
|
+
await writePhase(dir, shutdownPhase);
|
|
3054
|
+
const shutdownData: Record<string, unknown> = {
|
|
3055
|
+
phase: shutdownPhase,
|
|
3056
|
+
shutdown_request_id: shutdownRequestId,
|
|
3057
|
+
graceful_shutdown_complete: gracefulShutdownComplete,
|
|
3058
|
+
};
|
|
3059
|
+
if (evidenceFailures.length > 0) shutdownData.evidence_failures = evidenceFailures;
|
|
3060
|
+
await appendEvent(dir, {
|
|
3061
|
+
type: "team_shutdown",
|
|
3062
|
+
message:
|
|
3063
|
+
shutdownPhase === "complete"
|
|
3064
|
+
? "Shut down native gjc team runtime after completed tasks"
|
|
3065
|
+
: "Shut down native gjc team runtime with incomplete tasks",
|
|
3066
|
+
data: shutdownData,
|
|
3067
|
+
});
|
|
3068
|
+
await appendTelemetry(dir, {
|
|
3069
|
+
type: "team_shutdown",
|
|
3070
|
+
message: `Native gjc team runtime stopped with phase ${shutdownPhase}`,
|
|
3071
|
+
data: { shutdown_request_id: shutdownRequestId, graceful_shutdown_complete: gracefulShutdownComplete },
|
|
3072
|
+
});
|
|
3073
|
+
return readJwcTeamSnapshot(config.team_name, cwd, env);
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
export async function listJwcTeamTasks(
|
|
3077
|
+
teamName: string,
|
|
3078
|
+
cwd = process.cwd(),
|
|
3079
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3080
|
+
): Promise<JwcTeamTask[]> {
|
|
3081
|
+
return readTasks(await findTeamDir(teamName, cwd, env));
|
|
3082
|
+
}
|
|
3083
|
+
export async function readJwcTeamTask(
|
|
3084
|
+
teamName: string,
|
|
3085
|
+
taskId: string,
|
|
3086
|
+
cwd = process.cwd(),
|
|
3087
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3088
|
+
): Promise<JwcTeamTask> {
|
|
3089
|
+
const task = (await listJwcTeamTasks(teamName, cwd, env)).find(candidate => candidate.id === taskId);
|
|
3090
|
+
if (!task) throw new Error(`task_not_found:${taskId}`);
|
|
3091
|
+
return task;
|
|
3092
|
+
}
|
|
3093
|
+
export async function createJwcTeamTask(
|
|
3094
|
+
teamName: string,
|
|
3095
|
+
subject: string,
|
|
3096
|
+
description: string,
|
|
3097
|
+
cwd = process.cwd(),
|
|
3098
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3099
|
+
taskOptions: JwcTeamTaskMetadataInput = {},
|
|
3100
|
+
): Promise<JwcTeamTask> {
|
|
3101
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3102
|
+
const config = await readConfig(dir);
|
|
3103
|
+
if (taskOptions.owner) assertKnownWorker(config, taskOptions.owner);
|
|
3104
|
+
const tasks = await readTasks(dir);
|
|
3105
|
+
const next = tasks.length + 1;
|
|
3106
|
+
const task: JwcTeamTask = {
|
|
3107
|
+
id: `task-${next}`,
|
|
3108
|
+
subject,
|
|
3109
|
+
description,
|
|
3110
|
+
title: subject,
|
|
3111
|
+
objective: description,
|
|
3112
|
+
status: "pending",
|
|
3113
|
+
...(taskOptions.owner ? { owner: taskOptions.owner } : {}),
|
|
3114
|
+
...(taskOptions.lane ? { lane: taskOptions.lane } : {}),
|
|
3115
|
+
...(taskOptions.required_role ? { required_role: taskOptions.required_role } : {}),
|
|
3116
|
+
...(taskOptions.allowed_roles ? { allowed_roles: taskOptions.allowed_roles } : {}),
|
|
3117
|
+
...(taskOptions.depends_on ? { depends_on: taskOptions.depends_on } : {}),
|
|
3118
|
+
...(taskOptions.blocked_by ? { blocked_by: taskOptions.blocked_by } : {}),
|
|
3119
|
+
version: 1,
|
|
3120
|
+
created_at: now(),
|
|
3121
|
+
updated_at: now(),
|
|
3122
|
+
};
|
|
3123
|
+
await writeTask(dir, task);
|
|
3124
|
+
config.updated_at = now();
|
|
3125
|
+
await writeJsonFile(path.join(dir, "config.json"), config);
|
|
3126
|
+
await appendEvent(dir, { type: "task_created", task_id: task.id, message: subject });
|
|
3127
|
+
return task;
|
|
3128
|
+
}
|
|
3129
|
+
export async function updateJwcTeamTask(
|
|
3130
|
+
teamName: string,
|
|
3131
|
+
taskId: string,
|
|
3132
|
+
updates: Partial<
|
|
3133
|
+
Pick<
|
|
3134
|
+
JwcTeamTask,
|
|
3135
|
+
"subject" | "description" | "blocked_by" | "depends_on" | "lane" | "required_role" | "allowed_roles"
|
|
3136
|
+
>
|
|
3137
|
+
>,
|
|
3138
|
+
cwd = process.cwd(),
|
|
3139
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3140
|
+
): Promise<JwcTeamTask> {
|
|
3141
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3142
|
+
const task = await readJwcTeamTask(teamName, taskId, cwd, env);
|
|
3143
|
+
const updated = normalizeTask({
|
|
3144
|
+
...task,
|
|
3145
|
+
...updates,
|
|
3146
|
+
title: updates.subject ?? task.title,
|
|
3147
|
+
objective: updates.description ?? task.objective,
|
|
3148
|
+
version: task.version + 1,
|
|
3149
|
+
updated_at: now(),
|
|
3150
|
+
});
|
|
3151
|
+
await writeTask(dir, updated);
|
|
3152
|
+
await appendEvent(dir, { type: "task_updated", task_id: taskId, message: updated.subject });
|
|
3153
|
+
return updated;
|
|
3154
|
+
}
|
|
3155
|
+
export async function claimJwcTeamTask(
|
|
3156
|
+
teamName: string,
|
|
3157
|
+
workerId: string,
|
|
3158
|
+
cwd = process.cwd(),
|
|
3159
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3160
|
+
taskId?: string,
|
|
3161
|
+
): Promise<JwcTeamApiClaimResult> {
|
|
3162
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3163
|
+
const config = await readConfig(dir);
|
|
3164
|
+
const teamWorker = findKnownWorker(config, workerId);
|
|
3165
|
+
const livenessRecovery = await reconcileJwcTeamStaleClaims(teamName, dir, config, env);
|
|
3166
|
+
const staleWorkerReasons = livenessRecovery.stale_workers[workerId];
|
|
3167
|
+
if (staleWorkerReasons?.length)
|
|
3168
|
+
return { ok: false, reason: `worker_not_live:${workerId}:${staleWorkerReasons.join(",")}` };
|
|
3169
|
+
const tasks = await readTasks(dir);
|
|
3170
|
+
const task = taskId
|
|
3171
|
+
? tasks.find(candidate => candidate.id === taskId)
|
|
3172
|
+
: tasks.find(candidate => getJwcTeamTaskClaimEligibilityReason(candidate, teamWorker, tasks) == null);
|
|
3173
|
+
if (!task) return { ok: false, reason: taskId ? `task_not_found:${taskId}` : "no_pending_task" };
|
|
3174
|
+
const eligibilityReason = getJwcTeamTaskClaimEligibilityReason(task, teamWorker, tasks);
|
|
3175
|
+
if (eligibilityReason) return { ok: false, reason: eligibilityReason };
|
|
3176
|
+
const activeClaimReason = await getActiveClaimReason(dir, task);
|
|
3177
|
+
if (activeClaimReason) return { ok: false, reason: activeClaimReason };
|
|
3178
|
+
const token = randomUUID();
|
|
3179
|
+
const claim: JwcTeamTaskClaim = {
|
|
3180
|
+
owner: workerId,
|
|
3181
|
+
token,
|
|
3182
|
+
leased_until: new Date(Date.now() + 30 * 60_000).toISOString(),
|
|
3183
|
+
};
|
|
3184
|
+
const claimPath = path.join(dir, "claims", `${task.id}.json`);
|
|
3185
|
+
const created = await writeJsonFileNoClobber(claimPath, claim);
|
|
3186
|
+
if (!created) return { ok: false, reason: `task_already_claimed:${task.id}` };
|
|
3187
|
+
const current = await readJwcTeamTask(teamName, task.id, cwd, env);
|
|
3188
|
+
const currentEligibilityReason = getJwcTeamTaskClaimEligibilityReason(current, teamWorker, await readTasks(dir));
|
|
3189
|
+
if (currentEligibilityReason) {
|
|
3190
|
+
await fs.rm(claimPath, { force: true });
|
|
3191
|
+
return { ok: false, reason: currentEligibilityReason };
|
|
3192
|
+
}
|
|
3193
|
+
if (current.status !== "pending") {
|
|
3194
|
+
await deleteIfOwned(claimPath, {
|
|
3195
|
+
...stateWriterOptions(claimPath, "prune", "rollback"),
|
|
3196
|
+
predicate: current => (current as JwcTeamTaskClaim).token === token,
|
|
3197
|
+
});
|
|
3198
|
+
return { ok: false, reason: `task_not_pending:${task.id}` };
|
|
3199
|
+
}
|
|
3200
|
+
const updated: JwcTeamTask = {
|
|
3201
|
+
...current,
|
|
3202
|
+
status: "in_progress",
|
|
3203
|
+
assignee: workerId,
|
|
3204
|
+
owner: workerId,
|
|
3205
|
+
claim,
|
|
3206
|
+
version: current.version + 1,
|
|
3207
|
+
updated_at: now(),
|
|
3208
|
+
};
|
|
3209
|
+
try {
|
|
3210
|
+
await writeTask(dir, updated);
|
|
3211
|
+
} catch (error) {
|
|
3212
|
+
await deleteIfOwned(claimPath, {
|
|
3213
|
+
...stateWriterOptions(claimPath, "prune", "rollback"),
|
|
3214
|
+
predicate: current => (current as JwcTeamTaskClaim).token === token,
|
|
3215
|
+
});
|
|
3216
|
+
throw error;
|
|
3217
|
+
}
|
|
3218
|
+
await appendEvent(dir, {
|
|
3219
|
+
type: "task_claimed",
|
|
3220
|
+
task_id: updated.id,
|
|
3221
|
+
worker: workerId,
|
|
3222
|
+
message: "Worker claimed task",
|
|
3223
|
+
});
|
|
3224
|
+
return { ok: true, task: updated, worker_id: workerId, claim_token: token };
|
|
3225
|
+
}
|
|
3226
|
+
export async function transitionJwcTeamTaskStatus(
|
|
3227
|
+
teamName: string,
|
|
3228
|
+
taskId: string,
|
|
3229
|
+
status: JwcTeamTaskStatus,
|
|
3230
|
+
cwd = process.cwd(),
|
|
3231
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3232
|
+
claimToken?: string,
|
|
3233
|
+
workerId?: string,
|
|
3234
|
+
completionEvidenceInput?: unknown,
|
|
3235
|
+
): Promise<JwcTeamTask> {
|
|
3236
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3237
|
+
const config = await readConfig(dir);
|
|
3238
|
+
const task = await readJwcTeamTask(teamName, taskId, cwd, env);
|
|
3239
|
+
if (workerId) assertKnownWorker(config, workerId);
|
|
3240
|
+
if (status === "pending") throw new Error(`invalid_task_transition:${taskId}:pending_requires_release`);
|
|
3241
|
+
if (task.status === "completed" || task.status === "failed") throw new Error(`task_terminal:${taskId}`);
|
|
3242
|
+
if (!task.claim) throw new Error(`claim_token_required:${taskId}`);
|
|
3243
|
+
if (!claimToken) throw new Error(`claim_token_required:${taskId}`);
|
|
3244
|
+
if (task.claim.token !== claimToken) throw new Error(`claim_token_mismatch:${taskId}`);
|
|
3245
|
+
if (workerId && task.claim.owner !== workerId) throw new Error(`claim_owner_mismatch:${taskId}`);
|
|
3246
|
+
const terminal = status === "completed" || status === "failed";
|
|
3247
|
+
const transitionedAt = now();
|
|
3248
|
+
const completionEvidence =
|
|
3249
|
+
status === "completed"
|
|
3250
|
+
? normalizeJwcTeamTaskCompletionEvidence(taskId, task.claim.owner, completionEvidenceInput, transitionedAt)
|
|
3251
|
+
: undefined;
|
|
3252
|
+
const updated: JwcTeamTask = {
|
|
3253
|
+
...task,
|
|
3254
|
+
status,
|
|
3255
|
+
claim: terminal ? undefined : task.claim,
|
|
3256
|
+
version: task.version + 1,
|
|
3257
|
+
updated_at: transitionedAt,
|
|
3258
|
+
...(terminal ? { completed_at: transitionedAt } : {}),
|
|
3259
|
+
...(completionEvidence ? { completion_evidence: completionEvidence } : {}),
|
|
3260
|
+
};
|
|
3261
|
+
await writeTask(dir, updated);
|
|
3262
|
+
if (terminal) {
|
|
3263
|
+
const claimPath = path.join(dir, "claims", `${taskId}.json`);
|
|
3264
|
+
await removeFileAudited(claimPath, stateWriterOptions(claimPath, "prune", "terminal"));
|
|
3265
|
+
}
|
|
3266
|
+
const eventData: Record<string, unknown> = { status };
|
|
3267
|
+
if (completionEvidence) {
|
|
3268
|
+
eventData.completion_evidence = {
|
|
3269
|
+
recorded_by: completionEvidence.recorded_by,
|
|
3270
|
+
item_count: completionEvidence.items.length,
|
|
3271
|
+
verified_item_count: completionEvidence.items.filter(isJwcTeamCompletionEvidenceItemVerified).length,
|
|
3272
|
+
files_count: completionEvidence.files?.length ?? 0,
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
await appendEvent(dir, {
|
|
3276
|
+
type: "task_transitioned",
|
|
3277
|
+
task_id: taskId,
|
|
3278
|
+
message: "Task status changed",
|
|
3279
|
+
data: eventData,
|
|
3280
|
+
});
|
|
3281
|
+
return updated;
|
|
3282
|
+
}
|
|
3283
|
+
export async function transitionJwcTeamTask(
|
|
3284
|
+
teamName: string,
|
|
3285
|
+
taskId: string,
|
|
3286
|
+
status: JwcTeamTaskStatus | "complete",
|
|
3287
|
+
cwd = process.cwd(),
|
|
3288
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3289
|
+
claimToken?: string,
|
|
3290
|
+
completionEvidenceInput?: unknown,
|
|
3291
|
+
): Promise<JwcTeamTask> {
|
|
3292
|
+
return transitionJwcTeamTaskStatus(
|
|
3293
|
+
teamName,
|
|
3294
|
+
taskId,
|
|
3295
|
+
parseJwcTeamTaskStatus(status, true),
|
|
3296
|
+
cwd,
|
|
3297
|
+
env,
|
|
3298
|
+
claimToken,
|
|
3299
|
+
undefined,
|
|
3300
|
+
completionEvidenceInput,
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
export async function releaseJwcTeamTaskClaim(
|
|
3304
|
+
teamName: string,
|
|
3305
|
+
taskId: string,
|
|
3306
|
+
claimToken: string,
|
|
3307
|
+
workerId: string,
|
|
3308
|
+
cwd = process.cwd(),
|
|
3309
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3310
|
+
): Promise<JwcTeamTask> {
|
|
3311
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3312
|
+
const task = await readJwcTeamTask(teamName, taskId, cwd, env);
|
|
3313
|
+
if (!task.claim || task.claim.token !== claimToken || task.claim.owner !== workerId)
|
|
3314
|
+
throw new Error(`claim_token_mismatch:${taskId}`);
|
|
3315
|
+
const updated: JwcTeamTask = {
|
|
3316
|
+
...task,
|
|
3317
|
+
status: "pending",
|
|
3318
|
+
assignee: undefined,
|
|
3319
|
+
claim: undefined,
|
|
3320
|
+
version: task.version + 1,
|
|
3321
|
+
updated_at: now(),
|
|
3322
|
+
};
|
|
3323
|
+
await writeTask(dir, updated);
|
|
3324
|
+
const claimPath = path.join(dir, "claims", `${taskId}.json`);
|
|
3325
|
+
await deleteIfOwned(claimPath, {
|
|
3326
|
+
...stateWriterOptions(claimPath, "prune", "release"),
|
|
3327
|
+
predicate: current => (current as JwcTeamTaskClaim).token === claimToken,
|
|
3328
|
+
});
|
|
3329
|
+
await appendEvent(dir, {
|
|
3330
|
+
type: "task_claim_released",
|
|
3331
|
+
task_id: taskId,
|
|
3332
|
+
worker: workerId,
|
|
3333
|
+
message: "Task claim released",
|
|
3334
|
+
});
|
|
3335
|
+
return updated;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
function emptyNotificationSummary(): JwcTeamNotificationSummary {
|
|
3339
|
+
return {
|
|
3340
|
+
total: 0,
|
|
3341
|
+
replay_eligible: 0,
|
|
3342
|
+
by_state: {
|
|
3343
|
+
pending: 0,
|
|
3344
|
+
sent: 0,
|
|
3345
|
+
queued: 0,
|
|
3346
|
+
deferred: 0,
|
|
3347
|
+
failed: 0,
|
|
3348
|
+
delivered: 0,
|
|
3349
|
+
acknowledged: 0,
|
|
3350
|
+
},
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
function isReplayEligibleNotification(state: JwcTeamNotificationDeliveryState): boolean {
|
|
3354
|
+
return state === "pending" || state === "queued" || state === "deferred" || state === "failed";
|
|
3355
|
+
}
|
|
3356
|
+
function summarizeNotifications(notifications: JwcTeamNotification[]): JwcTeamNotificationSummary {
|
|
3357
|
+
const summary = emptyNotificationSummary();
|
|
3358
|
+
for (const notification of notifications) {
|
|
3359
|
+
summary.total += 1;
|
|
3360
|
+
summary.by_state[notification.delivery_state] += 1;
|
|
3361
|
+
if (isReplayEligibleNotification(notification.delivery_state)) summary.replay_eligible += 1;
|
|
3362
|
+
}
|
|
3363
|
+
return summary;
|
|
3364
|
+
}
|
|
3365
|
+
async function listNotificationRecords(dir: string): Promise<JwcTeamNotification[]> {
|
|
3366
|
+
const notificationsDir = path.join(dir, "notifications");
|
|
3367
|
+
try {
|
|
3368
|
+
const entries = await fs.readdir(notificationsDir, { withFileTypes: true });
|
|
3369
|
+
const records = await Promise.all(
|
|
3370
|
+
entries
|
|
3371
|
+
.filter(entry => entry.isFile() && entry.name.endsWith(".json"))
|
|
3372
|
+
.map(entry => readJsonFile<JwcTeamNotification>(path.join(notificationsDir, entry.name))),
|
|
3373
|
+
);
|
|
3374
|
+
return records
|
|
3375
|
+
.filter((record): record is JwcTeamNotification => record != null)
|
|
3376
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
3377
|
+
} catch (error) {
|
|
3378
|
+
if (isEnoent(error)) return [];
|
|
3379
|
+
throw error;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
async function readNotificationRecord(dir: string, notificationId: string): Promise<JwcTeamNotification> {
|
|
3383
|
+
assertSafeId("notification_id", notificationId);
|
|
3384
|
+
const notification = await readJsonFile<JwcTeamNotification>(notificationPath(dir, notificationId));
|
|
3385
|
+
if (!notification) throw new Error(`notification_not_found:${notificationId}`);
|
|
3386
|
+
return notification;
|
|
3387
|
+
}
|
|
3388
|
+
function mergeNotificationState(
|
|
3389
|
+
current: JwcTeamNotificationDeliveryState,
|
|
3390
|
+
next: JwcTeamNotificationDeliveryState,
|
|
3391
|
+
): JwcTeamNotificationDeliveryState {
|
|
3392
|
+
const rank: Record<JwcTeamNotificationDeliveryState, number> = {
|
|
3393
|
+
pending: 0,
|
|
3394
|
+
queued: 1,
|
|
3395
|
+
deferred: 1,
|
|
3396
|
+
failed: 1,
|
|
3397
|
+
sent: 2,
|
|
3398
|
+
delivered: 3,
|
|
3399
|
+
acknowledged: 4,
|
|
3400
|
+
};
|
|
3401
|
+
return rank[next] >= rank[current] ? next : current;
|
|
3402
|
+
}
|
|
3403
|
+
async function writeNotificationRecord(dir: string, notification: JwcTeamNotification): Promise<JwcTeamNotification> {
|
|
3404
|
+
const existing = await readJsonFile<JwcTeamNotification>(notificationPath(dir, notification.id));
|
|
3405
|
+
const merged: JwcTeamNotification = existing
|
|
3406
|
+
? {
|
|
3407
|
+
...existing,
|
|
3408
|
+
...notification,
|
|
3409
|
+
delivery_state: mergeNotificationState(existing.delivery_state, notification.delivery_state),
|
|
3410
|
+
created_at: existing.created_at,
|
|
3411
|
+
replay_count: Math.max(existing.replay_count ?? 0, notification.replay_count ?? 0),
|
|
3412
|
+
updated_at: now(),
|
|
3413
|
+
}
|
|
3414
|
+
: notification;
|
|
3415
|
+
await writeJsonFile(notificationPath(dir, merged.id), merged);
|
|
3416
|
+
return merged;
|
|
3417
|
+
}
|
|
3418
|
+
async function createMessageNotification(
|
|
3419
|
+
dir: string,
|
|
3420
|
+
teamName: string,
|
|
3421
|
+
message: JwcTeamMailboxMessage,
|
|
3422
|
+
state: JwcTeamNotificationDeliveryState = "pending",
|
|
3423
|
+
): Promise<JwcTeamNotification> {
|
|
3424
|
+
const id = messageNotificationId(teamName, message.to_worker, message.message_id);
|
|
3425
|
+
return writeNotificationRecord(dir, {
|
|
3426
|
+
id,
|
|
3427
|
+
kind: "mailbox_message",
|
|
3428
|
+
team_name: teamName,
|
|
3429
|
+
recipient: message.to_worker,
|
|
3430
|
+
source: { type: "message", id: message.message_id },
|
|
3431
|
+
idempotency_key: message.idempotency_key,
|
|
3432
|
+
delivery_state: state,
|
|
3433
|
+
created_at: message.created_at,
|
|
3434
|
+
updated_at: now(),
|
|
3435
|
+
replay_count: 0,
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
async function readLegacyMailbox(dir: string, worker: string): Promise<{ messages: JwcTeamMailboxMessage[] }> {
|
|
3439
|
+
return (await readJsonFile<{ messages: JwcTeamMailboxMessage[] }>(mailboxPath(dir, worker))) ?? { messages: [] };
|
|
3440
|
+
}
|
|
3441
|
+
async function readMailbox(dir: string, worker: string): Promise<{ messages: JwcTeamMailboxMessage[] }> {
|
|
3442
|
+
assertSafeId("worker_id", worker);
|
|
3443
|
+
const byId = new Map<string, JwcTeamMailboxMessage>();
|
|
3444
|
+
for (const message of (await readLegacyMailbox(dir, worker)).messages ?? []) byId.set(message.message_id, message);
|
|
3445
|
+
try {
|
|
3446
|
+
const entries = await fs.readdir(mailboxDirPath(dir, worker), { withFileTypes: true });
|
|
3447
|
+
const records = await Promise.all(
|
|
3448
|
+
entries
|
|
3449
|
+
.filter(entry => entry.isFile() && entry.name.endsWith(".json"))
|
|
3450
|
+
.map(entry => readJsonFile<JwcTeamMailboxMessage>(path.join(mailboxDirPath(dir, worker), entry.name))),
|
|
3451
|
+
);
|
|
3452
|
+
for (const message of records) if (message) byId.set(message.message_id, message);
|
|
3453
|
+
} catch (error) {
|
|
3454
|
+
if (!isEnoent(error)) throw error;
|
|
3455
|
+
}
|
|
3456
|
+
return { messages: [...byId.values()].sort((a, b) => a.created_at.localeCompare(b.created_at)) };
|
|
3457
|
+
}
|
|
3458
|
+
async function writeLegacyMailboxView(dir: string, worker: string): Promise<void> {
|
|
3459
|
+
const current = await readMailbox(dir, worker);
|
|
3460
|
+
await writeJsonFile(mailboxPath(dir, worker), current);
|
|
3461
|
+
}
|
|
3462
|
+
async function writeMailboxMessage(
|
|
3463
|
+
dir: string,
|
|
3464
|
+
worker: string,
|
|
3465
|
+
message: JwcTeamMailboxMessage,
|
|
3466
|
+
): Promise<JwcTeamMailboxMessage> {
|
|
3467
|
+
assertSafeId("message_id", message.message_id);
|
|
3468
|
+
const filePath = mailboxMessagePath(dir, worker, message.message_id);
|
|
3469
|
+
const existing = await readJsonFile<JwcTeamMailboxMessage>(filePath);
|
|
3470
|
+
if (existing) {
|
|
3471
|
+
if (
|
|
3472
|
+
existing.from_worker !== message.from_worker ||
|
|
3473
|
+
existing.to_worker !== message.to_worker ||
|
|
3474
|
+
existing.body !== message.body
|
|
3475
|
+
) {
|
|
3476
|
+
throw new Error(`message_id_conflict:${message.message_id}`);
|
|
3477
|
+
}
|
|
3478
|
+
const merged = {
|
|
3479
|
+
...existing,
|
|
3480
|
+
...message,
|
|
3481
|
+
notified_at: existing.notified_at ?? message.notified_at,
|
|
3482
|
+
delivered_at: existing.delivered_at ?? message.delivered_at,
|
|
3483
|
+
};
|
|
3484
|
+
await writeJsonFile(filePath, merged);
|
|
3485
|
+
await writeLegacyMailboxView(dir, worker);
|
|
3486
|
+
return merged;
|
|
3487
|
+
}
|
|
3488
|
+
const created = await writeJsonFileNoClobber(filePath, message);
|
|
3489
|
+
if (!created) return writeMailboxMessage(dir, worker, message);
|
|
3490
|
+
await writeLegacyMailboxView(dir, worker);
|
|
3491
|
+
return message;
|
|
3492
|
+
}
|
|
3493
|
+
async function reconcileTeamNotifications(dir: string, config: JwcTeamConfig): Promise<JwcTeamNotificationSummary> {
|
|
3494
|
+
for (const recipient of ["leader-fixed", ...config.workers.map(worker => worker.id)]) {
|
|
3495
|
+
const mailbox = await readMailbox(dir, recipient);
|
|
3496
|
+
for (const message of mailbox.messages) {
|
|
3497
|
+
const state = message.delivered_at ? "acknowledged" : message.notified_at ? "delivered" : "pending";
|
|
3498
|
+
await createMessageNotification(dir, config.team_name, message, state);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
return summarizeNotifications(await listNotificationRecords(dir));
|
|
3502
|
+
}
|
|
3503
|
+
async function attemptPaneNotification(
|
|
3504
|
+
dir: string,
|
|
3505
|
+
config: JwcTeamConfig,
|
|
3506
|
+
notification: JwcTeamNotification,
|
|
3507
|
+
env: NodeJS.ProcessEnv,
|
|
3508
|
+
): Promise<JwcTeamNotification> {
|
|
3509
|
+
const paneId =
|
|
3510
|
+
notification.recipient === "leader-fixed"
|
|
3511
|
+
? config.leader.pane_id
|
|
3512
|
+
: config.workers.find(worker => worker.id === notification.recipient)?.pane_id;
|
|
3513
|
+
let result: JwcTeamPaneAttemptResult = "deferred";
|
|
3514
|
+
let reason = "pane_missing";
|
|
3515
|
+
if (paneId) {
|
|
3516
|
+
if (config.tmux_session === "dry-run" || env.GJC_TEAM_FAKE_PANE_ATTEMPT === "sent") {
|
|
3517
|
+
result = "sent";
|
|
3518
|
+
reason = "dry_run_or_fake_tmux";
|
|
3519
|
+
} else {
|
|
3520
|
+
result = "queued";
|
|
3521
|
+
reason = "tmux_delivery_recorded_without_injection";
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
return writeNotificationRecord(dir, {
|
|
3525
|
+
...notification,
|
|
3526
|
+
delivery_state: result,
|
|
3527
|
+
pane_attempt_result: result,
|
|
3528
|
+
pane_attempt_reason: reason,
|
|
3529
|
+
pane_attempt_at: now(),
|
|
3530
|
+
updated_at: now(),
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
export async function replayJwcTeamNotifications(
|
|
3534
|
+
teamName: string,
|
|
3535
|
+
cwd = process.cwd(),
|
|
3536
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3537
|
+
): Promise<{ notifications: JwcTeamNotification[]; summary: JwcTeamNotificationSummary }> {
|
|
3538
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3539
|
+
const config = await readConfig(dir);
|
|
3540
|
+
await reconcileTeamNotifications(dir, config);
|
|
3541
|
+
const next: JwcTeamNotification[] = [];
|
|
3542
|
+
for (const notification of await listNotificationRecords(dir)) {
|
|
3543
|
+
if (!isReplayEligibleNotification(notification.delivery_state)) {
|
|
3544
|
+
next.push(notification);
|
|
3545
|
+
continue;
|
|
3546
|
+
}
|
|
3547
|
+
const attempted = await attemptPaneNotification(
|
|
3548
|
+
dir,
|
|
3549
|
+
config,
|
|
3550
|
+
{
|
|
3551
|
+
...notification,
|
|
3552
|
+
replay_count: (notification.replay_count ?? 0) + 1,
|
|
3553
|
+
},
|
|
3554
|
+
env,
|
|
3555
|
+
);
|
|
3556
|
+
next.push(attempted);
|
|
3557
|
+
}
|
|
3558
|
+
return { notifications: next, summary: summarizeNotifications(next) };
|
|
3559
|
+
}
|
|
3560
|
+
export async function sendJwcTeamMessage(
|
|
3561
|
+
teamName: string,
|
|
3562
|
+
fromWorker: string,
|
|
3563
|
+
toWorker: string,
|
|
3564
|
+
body: string,
|
|
3565
|
+
cwd = process.cwd(),
|
|
3566
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3567
|
+
idempotencyKey?: string,
|
|
3568
|
+
): Promise<JwcTeamMailboxMessage> {
|
|
3569
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3570
|
+
const config = await readConfig(dir);
|
|
3571
|
+
assertKnownParticipant(config, fromWorker);
|
|
3572
|
+
assertKnownParticipant(config, toWorker);
|
|
3573
|
+
const createdKey = idempotencyKey ?? randomUUID();
|
|
3574
|
+
const message: JwcTeamMailboxMessage = {
|
|
3575
|
+
message_id: messageIdFor({ teamName: config.team_name, fromWorker, toWorker, body, idempotencyKey, createdKey }),
|
|
3576
|
+
from_worker: fromWorker,
|
|
3577
|
+
to_worker: toWorker,
|
|
3578
|
+
body,
|
|
3579
|
+
created_at: now(),
|
|
3580
|
+
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
3581
|
+
};
|
|
3582
|
+
const written = await writeMailboxMessage(dir, toWorker, message);
|
|
3583
|
+
const notification = await createMessageNotification(dir, config.team_name, written);
|
|
3584
|
+
await attemptPaneNotification(dir, config, notification, env);
|
|
3585
|
+
await appendEvent(dir, {
|
|
3586
|
+
type: "message_sent",
|
|
3587
|
+
worker: fromWorker,
|
|
3588
|
+
message: body,
|
|
3589
|
+
data: { to_worker: toWorker, message_id: written.message_id },
|
|
3590
|
+
});
|
|
3591
|
+
return written;
|
|
3592
|
+
}
|
|
3593
|
+
export async function broadcastJwcTeamMessage(
|
|
3594
|
+
teamName: string,
|
|
3595
|
+
fromWorker: string,
|
|
3596
|
+
body: string,
|
|
3597
|
+
cwd = process.cwd(),
|
|
3598
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3599
|
+
idempotencyKey?: string,
|
|
3600
|
+
): Promise<JwcTeamMailboxMessage[]> {
|
|
3601
|
+
const config = await readConfig(await findTeamDir(teamName, cwd, env));
|
|
3602
|
+
return Promise.all(
|
|
3603
|
+
config.workers.map(worker =>
|
|
3604
|
+
sendJwcTeamMessage(
|
|
3605
|
+
teamName,
|
|
3606
|
+
fromWorker,
|
|
3607
|
+
worker.id,
|
|
3608
|
+
body,
|
|
3609
|
+
cwd,
|
|
3610
|
+
env,
|
|
3611
|
+
idempotencyKey ? `${idempotencyKey}:${worker.id}` : undefined,
|
|
3612
|
+
),
|
|
3613
|
+
),
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
export async function listJwcTeamMailbox(
|
|
3617
|
+
teamName: string,
|
|
3618
|
+
worker: string,
|
|
3619
|
+
cwd = process.cwd(),
|
|
3620
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3621
|
+
): Promise<JwcTeamMailboxMessage[]> {
|
|
3622
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3623
|
+
const config = await readConfig(dir);
|
|
3624
|
+
assertKnownParticipant(config, worker);
|
|
3625
|
+
return (await readMailbox(dir, worker)).messages;
|
|
3626
|
+
}
|
|
3627
|
+
export async function markJwcTeamMailboxMessage(
|
|
3628
|
+
teamName: string,
|
|
3629
|
+
worker: string,
|
|
3630
|
+
messageId: string,
|
|
3631
|
+
field: "delivered_at" | "notified_at",
|
|
3632
|
+
cwd = process.cwd(),
|
|
3633
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3634
|
+
): Promise<JwcTeamMailboxMessage> {
|
|
3635
|
+
assertSafeId("message_id", messageId);
|
|
3636
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3637
|
+
const config = await readConfig(dir);
|
|
3638
|
+
assertKnownParticipant(config, worker);
|
|
3639
|
+
const mailbox = await readMailbox(dir, worker);
|
|
3640
|
+
const message = mailbox.messages.find(candidate => candidate.message_id === messageId);
|
|
3641
|
+
if (!message) throw new Error(`message_not_found:${messageId}`);
|
|
3642
|
+
const updated = { ...message, [field]: message[field] ?? now() };
|
|
3643
|
+
const written = await writeMailboxMessage(dir, worker, updated);
|
|
3644
|
+
const notificationId = messageNotificationId(config.team_name, worker, messageId);
|
|
3645
|
+
const existing =
|
|
3646
|
+
(await readJsonFile<JwcTeamNotification>(notificationPath(dir, notificationId))) ??
|
|
3647
|
+
(await createMessageNotification(dir, config.team_name, written));
|
|
3648
|
+
const nextState: JwcTeamNotificationDeliveryState = field === "delivered_at" ? "acknowledged" : "delivered";
|
|
3649
|
+
const before = existing.delivery_state;
|
|
3650
|
+
await writeNotificationRecord(dir, { ...existing, delivery_state: nextState, updated_at: now() });
|
|
3651
|
+
if (mergeNotificationState(before, nextState) !== before)
|
|
3652
|
+
await appendEvent(dir, {
|
|
3653
|
+
type: `message_${field === "delivered_at" ? "acknowledged" : "notified"}`,
|
|
3654
|
+
worker,
|
|
3655
|
+
message: messageId,
|
|
3656
|
+
});
|
|
3657
|
+
return written;
|
|
3658
|
+
}
|
|
3659
|
+
export async function readJwcWorkerStatus(
|
|
3660
|
+
teamName: string,
|
|
3661
|
+
worker: string,
|
|
3662
|
+
cwd = process.cwd(),
|
|
3663
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3664
|
+
): Promise<WorkerStatusFile> {
|
|
3665
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3666
|
+
const config = await readConfig(dir);
|
|
3667
|
+
assertKnownWorker(config, worker);
|
|
3668
|
+
return readWorkerStatusFile(dir, worker);
|
|
3669
|
+
}
|
|
3670
|
+
export async function updateJwcWorkerStatus(
|
|
3671
|
+
teamName: string,
|
|
3672
|
+
worker: string,
|
|
3673
|
+
status: JwcWorkerStatusState,
|
|
3674
|
+
cwd = process.cwd(),
|
|
3675
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3676
|
+
currentTaskId?: string,
|
|
3677
|
+
reason?: string,
|
|
3678
|
+
): Promise<WorkerStatusFile> {
|
|
3679
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3680
|
+
const config = await readConfig(dir);
|
|
3681
|
+
const teamWorker = findKnownWorker(config, worker);
|
|
3682
|
+
if (currentTaskId) assertSafeId("task_id", currentTaskId);
|
|
3683
|
+
const trimmedReason = reason?.trim();
|
|
3684
|
+
const value: WorkerStatusFile = {
|
|
3685
|
+
state: status,
|
|
3686
|
+
...(currentTaskId ? { current_task_id: currentTaskId } : {}),
|
|
3687
|
+
...(trimmedReason ? { reason: trimmedReason } : {}),
|
|
3688
|
+
updated_at: now(),
|
|
3689
|
+
};
|
|
3690
|
+
await writeJsonFile(path.join(workerDir(dir, worker), "status.json"), value);
|
|
3691
|
+
const currentLifecycle = await readWorkerLifecycleRecord(dir, teamWorker);
|
|
3692
|
+
const lifecycleState =
|
|
3693
|
+
currentLifecycle.lifecycle_state === "stopped" ? "stopped" : lifecycleStateForWorkerStatus(status);
|
|
3694
|
+
await writeWorkerLifecycleRecord(dir, teamWorker, lifecycleState);
|
|
3695
|
+
await appendEvent(dir, {
|
|
3696
|
+
type: "worker_status_updated",
|
|
3697
|
+
worker,
|
|
3698
|
+
message: `Worker ${worker} reported ${status}`,
|
|
3699
|
+
data: {
|
|
3700
|
+
status,
|
|
3701
|
+
current_task_id: currentTaskId,
|
|
3702
|
+
},
|
|
3703
|
+
});
|
|
3704
|
+
return value;
|
|
3705
|
+
}
|
|
3706
|
+
export async function readJwcWorkerHeartbeat(
|
|
3707
|
+
teamName: string,
|
|
3708
|
+
worker: string,
|
|
3709
|
+
cwd = process.cwd(),
|
|
3710
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3711
|
+
): Promise<WorkerHeartbeatFile | null> {
|
|
3712
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3713
|
+
const config = await readConfig(dir);
|
|
3714
|
+
assertKnownWorker(config, worker);
|
|
3715
|
+
return readJsonFile<WorkerHeartbeatFile>(path.join(workerDir(dir, worker), "heartbeat.json"));
|
|
3716
|
+
}
|
|
3717
|
+
export async function updateJwcWorkerHeartbeat(
|
|
3718
|
+
teamName: string,
|
|
3719
|
+
worker: string,
|
|
3720
|
+
heartbeat: WorkerHeartbeatFile,
|
|
3721
|
+
cwd = process.cwd(),
|
|
3722
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3723
|
+
): Promise<WorkerHeartbeatFile> {
|
|
3724
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3725
|
+
const config = await readConfig(dir);
|
|
3726
|
+
assertKnownWorker(config, worker);
|
|
3727
|
+
const value = { ...heartbeat, last_turn_at: heartbeat.last_turn_at || now() };
|
|
3728
|
+
await writeJsonFile(path.join(workerDir(dir, worker), "heartbeat.json"), value);
|
|
3729
|
+
return value;
|
|
3730
|
+
}
|
|
3731
|
+
export async function writeJwcWorkerInbox(
|
|
3732
|
+
teamName: string,
|
|
3733
|
+
worker: string,
|
|
3734
|
+
content: string,
|
|
3735
|
+
cwd = process.cwd(),
|
|
3736
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3737
|
+
): Promise<{ path: string }> {
|
|
3738
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3739
|
+
const config = await readConfig(dir);
|
|
3740
|
+
assertKnownWorker(config, worker);
|
|
3741
|
+
const filePath = path.join(workerDir(dir, worker), "inbox.md");
|
|
3742
|
+
await writeReport(filePath, content, stateWriterOptions(filePath, "report", "write"));
|
|
3743
|
+
return { path: filePath };
|
|
3744
|
+
}
|
|
3745
|
+
export async function writeJwcWorkerIdentity(
|
|
3746
|
+
teamName: string,
|
|
3747
|
+
worker: JwcTeamWorker,
|
|
3748
|
+
cwd = process.cwd(),
|
|
3749
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3750
|
+
): Promise<JwcTeamWorker> {
|
|
3751
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3752
|
+
const config = await readConfig(dir);
|
|
3753
|
+
assertKnownWorker(config, worker.id);
|
|
3754
|
+
await writeJsonFile(path.join(workerDir(dir, worker.id), "identity.json"), worker);
|
|
3755
|
+
return worker;
|
|
3756
|
+
}
|
|
3757
|
+
export async function readJwcTeamEvents(
|
|
3758
|
+
teamName: string,
|
|
3759
|
+
cwd = process.cwd(),
|
|
3760
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3761
|
+
): Promise<JwcTeamEvent[]> {
|
|
3762
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3763
|
+
try {
|
|
3764
|
+
const text = await Bun.file(path.join(dir, "events.jsonl")).text();
|
|
3765
|
+
return text
|
|
3766
|
+
.split(/\r?\n/)
|
|
3767
|
+
.filter(Boolean)
|
|
3768
|
+
.map(line => JSON.parse(line) as JwcTeamEvent);
|
|
3769
|
+
} catch (error) {
|
|
3770
|
+
if (isEnoent(error)) return [];
|
|
3771
|
+
throw error;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
export async function readJwcTeamTraces(
|
|
3775
|
+
teamName: string,
|
|
3776
|
+
cwd = process.cwd(),
|
|
3777
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3778
|
+
): Promise<JwcTeamTraceEvent[]> {
|
|
3779
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3780
|
+
try {
|
|
3781
|
+
const text = await Bun.file(tracePath(dir)).text();
|
|
3782
|
+
return text
|
|
3783
|
+
.split(/\r?\n/)
|
|
3784
|
+
.filter(Boolean)
|
|
3785
|
+
.map(line => JSON.parse(line) as JwcTeamTraceEvent);
|
|
3786
|
+
} catch (error) {
|
|
3787
|
+
if (isEnoent(error)) return [];
|
|
3788
|
+
throw error;
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
export async function appendJwcTeamEvent(
|
|
3792
|
+
teamName: string,
|
|
3793
|
+
type: string,
|
|
3794
|
+
worker = "leader-fixed",
|
|
3795
|
+
cwd = process.cwd(),
|
|
3796
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3797
|
+
): Promise<JwcTeamEvent> {
|
|
3798
|
+
return appendEvent(await findTeamDir(teamName, cwd, env), { type, worker });
|
|
3799
|
+
}
|
|
3800
|
+
export async function awaitJwcTeamEvent(
|
|
3801
|
+
teamName: string,
|
|
3802
|
+
_timeoutMs = 0,
|
|
3803
|
+
cwd = process.cwd(),
|
|
3804
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3805
|
+
): Promise<{ status: "event" | "timeout"; event?: JwcTeamEvent }> {
|
|
3806
|
+
const events = await readJwcTeamEvents(teamName, cwd, env);
|
|
3807
|
+
const event = events.at(-1);
|
|
3808
|
+
return event ? { status: "event", event } : { status: "timeout" };
|
|
3809
|
+
}
|
|
3810
|
+
export async function writeJwcMonitorSnapshot(
|
|
3811
|
+
teamName: string,
|
|
3812
|
+
snapshot: unknown,
|
|
3813
|
+
cwd = process.cwd(),
|
|
3814
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3815
|
+
): Promise<unknown> {
|
|
3816
|
+
await writeJsonFile(monitorSnapshotPath(await findTeamDir(teamName, cwd, env)), snapshot);
|
|
3817
|
+
return snapshot;
|
|
3818
|
+
}
|
|
3819
|
+
export async function readJwcMonitorSnapshot(
|
|
3820
|
+
teamName: string,
|
|
3821
|
+
cwd = process.cwd(),
|
|
3822
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3823
|
+
): Promise<unknown> {
|
|
3824
|
+
return readJsonFile<unknown>(monitorSnapshotPath(await findTeamDir(teamName, cwd, env)));
|
|
3825
|
+
}
|
|
3826
|
+
export async function writeJwcTaskApproval(
|
|
3827
|
+
teamName: string,
|
|
3828
|
+
taskId: string,
|
|
3829
|
+
approval: Record<string, unknown>,
|
|
3830
|
+
cwd = process.cwd(),
|
|
3831
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3832
|
+
): Promise<Record<string, unknown>> {
|
|
3833
|
+
assertSafeId("task_id", taskId);
|
|
3834
|
+
await writeJsonFile(path.join(await findTeamDir(teamName, cwd, env), "approvals", `${taskId}.json`), approval);
|
|
3835
|
+
return approval;
|
|
3836
|
+
}
|
|
3837
|
+
export async function readJwcTaskApproval(
|
|
3838
|
+
teamName: string,
|
|
3839
|
+
taskId: string,
|
|
3840
|
+
cwd = process.cwd(),
|
|
3841
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3842
|
+
): Promise<Record<string, unknown> | null> {
|
|
3843
|
+
assertSafeId("task_id", taskId);
|
|
3844
|
+
return readJsonFile<Record<string, unknown>>(
|
|
3845
|
+
path.join(await findTeamDir(teamName, cwd, env), "approvals", `${taskId}.json`),
|
|
3846
|
+
);
|
|
3847
|
+
}
|
|
3848
|
+
export async function writeJwcShutdownRequest(
|
|
3849
|
+
teamName: string,
|
|
3850
|
+
worker: string,
|
|
3851
|
+
requestedBy: string,
|
|
3852
|
+
cwd = process.cwd(),
|
|
3853
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3854
|
+
requestId = `shutdown-${stableHash([teamName, worker, now(), randomUUID()].join(":"))}`,
|
|
3855
|
+
mode: JwcTeamShutdownMode = "graceful",
|
|
3856
|
+
requestedAt = now(),
|
|
3857
|
+
): Promise<Record<string, unknown>> {
|
|
3858
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3859
|
+
const config = await readConfig(dir);
|
|
3860
|
+
const teamWorker = findKnownWorker(config, worker);
|
|
3861
|
+
assertKnownParticipant(config, requestedBy);
|
|
3862
|
+
const value = { worker, requested_by: requestedBy, request_id: requestId, mode, requested_at: requestedAt };
|
|
3863
|
+
await writeJsonFile(path.join(workerDir(dir, worker), "shutdown-request.json"), value);
|
|
3864
|
+
await writeWorkerLifecycleRecord(dir, teamWorker, "draining", {
|
|
3865
|
+
shutdown_request_id: requestId,
|
|
3866
|
+
shutdown_requested_at: requestedAt,
|
|
3867
|
+
shutdown_mode: mode,
|
|
3868
|
+
});
|
|
3869
|
+
await appendEvent(dir, {
|
|
3870
|
+
type: "worker_shutdown_requested",
|
|
3871
|
+
worker,
|
|
3872
|
+
message: `Worker ${worker} shutdown requested`,
|
|
3873
|
+
data: { requested_by: requestedBy, request_id: requestId, mode },
|
|
3874
|
+
});
|
|
3875
|
+
return value;
|
|
3876
|
+
}
|
|
3877
|
+
export async function readJwcShutdownAck(
|
|
3878
|
+
teamName: string,
|
|
3879
|
+
worker: string,
|
|
3880
|
+
cwd = process.cwd(),
|
|
3881
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3882
|
+
): Promise<Record<string, unknown> | null> {
|
|
3883
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
3884
|
+
const config = await readConfig(dir);
|
|
3885
|
+
assertKnownWorker(config, worker);
|
|
3886
|
+
return readJsonFile<Record<string, unknown>>(path.join(workerDir(dir, worker), "shutdown-ack.json"));
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
export async function executeJwcTeamApiOperation(
|
|
3890
|
+
operation: string,
|
|
3891
|
+
input: Record<string, unknown>,
|
|
3892
|
+
cwd = process.cwd(),
|
|
3893
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
3894
|
+
): Promise<unknown> {
|
|
3895
|
+
const teamName = String(input.team_name ?? input.teamName ?? "").trim();
|
|
3896
|
+
if (!teamName) throw new Error("missing_team_name");
|
|
3897
|
+
const workerInput = input.worker ?? input.worker_id ?? input.workerId;
|
|
3898
|
+
const worker = String(workerInput ?? "worker-1");
|
|
3899
|
+
const explicitWorker = workerInput == null ? undefined : String(workerInput);
|
|
3900
|
+
switch (operation) {
|
|
3901
|
+
case "list-tasks":
|
|
3902
|
+
return { tasks: await listJwcTeamTasks(teamName, cwd, env) };
|
|
3903
|
+
case "read-task":
|
|
3904
|
+
return { task: await readJwcTeamTask(teamName, String(input.task_id ?? input.taskId), cwd, env) };
|
|
3905
|
+
case "create-task": {
|
|
3906
|
+
const task = await createJwcTeamTask(
|
|
3907
|
+
teamName,
|
|
3908
|
+
String(input.subject ?? "Task"),
|
|
3909
|
+
String(input.description ?? ""),
|
|
3910
|
+
cwd,
|
|
3911
|
+
env,
|
|
3912
|
+
taskMetadataFromInput(input, true),
|
|
3913
|
+
);
|
|
3914
|
+
return { ok: true, ...taskReceiptFields(teamName, task) };
|
|
3915
|
+
}
|
|
3916
|
+
case "update-task": {
|
|
3917
|
+
const task = await updateJwcTeamTask(
|
|
3918
|
+
teamName,
|
|
3919
|
+
String(input.task_id ?? input.taskId),
|
|
3920
|
+
{
|
|
3921
|
+
subject: typeof input.subject === "string" ? input.subject : undefined,
|
|
3922
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
3923
|
+
...taskMetadataFromInput(input),
|
|
3924
|
+
},
|
|
3925
|
+
cwd,
|
|
3926
|
+
env,
|
|
3927
|
+
);
|
|
3928
|
+
return { ok: true, ...taskReceiptFields(teamName, task) };
|
|
3929
|
+
}
|
|
3930
|
+
case "claim-task": {
|
|
3931
|
+
const requestedTaskId = input.task_id ?? input.taskId;
|
|
3932
|
+
const result = await claimJwcTeamTask(
|
|
3933
|
+
teamName,
|
|
3934
|
+
worker,
|
|
3935
|
+
cwd,
|
|
3936
|
+
env,
|
|
3937
|
+
typeof requestedTaskId === "string" ? requestedTaskId : undefined,
|
|
3938
|
+
);
|
|
3939
|
+
return {
|
|
3940
|
+
ok: result.ok,
|
|
3941
|
+
reason: result.reason,
|
|
3942
|
+
team_name: teamName,
|
|
3943
|
+
worker_id: result.worker_id ?? worker,
|
|
3944
|
+
...(result.task ? taskReceiptFields(teamName, result.task) : {}),
|
|
3945
|
+
claim_token: result.claim_token,
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
case "transition-task":
|
|
3949
|
+
case "transition-task-status": {
|
|
3950
|
+
const task = await transitionJwcTeamTaskStatus(
|
|
3951
|
+
teamName,
|
|
3952
|
+
String(input.task_id ?? input.taskId),
|
|
3953
|
+
parseJwcTeamTaskStatus(input.to ?? input.status),
|
|
3954
|
+
cwd,
|
|
3955
|
+
env,
|
|
3956
|
+
typeof input.claim_token === "string" ? input.claim_token : undefined,
|
|
3957
|
+
explicitWorker,
|
|
3958
|
+
input.completion_evidence ?? input.completionEvidence,
|
|
3959
|
+
);
|
|
3960
|
+
return {
|
|
3961
|
+
ok: true,
|
|
3962
|
+
...taskReceiptFields(teamName, task),
|
|
3963
|
+
worker_id: explicitWorker ?? task.owner ?? task.assignee,
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
case "release-task-claim": {
|
|
3967
|
+
const task = await releaseJwcTeamTaskClaim(
|
|
3968
|
+
teamName,
|
|
3969
|
+
String(input.task_id),
|
|
3970
|
+
String(input.claim_token),
|
|
3971
|
+
worker,
|
|
3972
|
+
cwd,
|
|
3973
|
+
env,
|
|
3974
|
+
);
|
|
3975
|
+
return { ok: true, ...taskReceiptFields(teamName, task), worker_id: worker };
|
|
3976
|
+
}
|
|
3977
|
+
case "send-message": {
|
|
3978
|
+
const message = await sendJwcTeamMessage(
|
|
3979
|
+
teamName,
|
|
3980
|
+
String(input.from_worker),
|
|
3981
|
+
String(input.to_worker),
|
|
3982
|
+
String(input.body),
|
|
3983
|
+
cwd,
|
|
3984
|
+
env,
|
|
3985
|
+
typeof input.idempotency_key === "string" ? input.idempotency_key : undefined,
|
|
3986
|
+
);
|
|
3987
|
+
return { ok: true, ...mailboxMessageReceiptFields(teamName, message) };
|
|
3988
|
+
}
|
|
3989
|
+
case "broadcast": {
|
|
3990
|
+
const messages = await broadcastJwcTeamMessage(
|
|
3991
|
+
teamName,
|
|
3992
|
+
String(input.from_worker),
|
|
3993
|
+
String(input.body),
|
|
3994
|
+
cwd,
|
|
3995
|
+
env,
|
|
3996
|
+
typeof input.idempotency_key === "string" ? input.idempotency_key : undefined,
|
|
3997
|
+
);
|
|
3998
|
+
return {
|
|
3999
|
+
ok: true,
|
|
4000
|
+
team_name: teamName,
|
|
4001
|
+
message_ids: messages.map(message => message.message_id),
|
|
4002
|
+
delivery_states: messages.map(message => ({
|
|
4003
|
+
message_id: message.message_id,
|
|
4004
|
+
to_worker: message.to_worker,
|
|
4005
|
+
delivered: Boolean(message.delivered_at),
|
|
4006
|
+
notified: Boolean(message.notified_at),
|
|
4007
|
+
})),
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
case "mailbox-list":
|
|
4011
|
+
return { messages: await listJwcTeamMailbox(teamName, worker, cwd, env) };
|
|
4012
|
+
case "mailbox-mark-delivered": {
|
|
4013
|
+
const message = await markJwcTeamMailboxMessage(
|
|
4014
|
+
teamName,
|
|
4015
|
+
worker,
|
|
4016
|
+
String(input.message_id),
|
|
4017
|
+
"delivered_at",
|
|
4018
|
+
cwd,
|
|
4019
|
+
env,
|
|
4020
|
+
);
|
|
4021
|
+
return { ok: true, ...mailboxMessageReceiptFields(teamName, message) };
|
|
4022
|
+
}
|
|
4023
|
+
case "mailbox-mark-notified": {
|
|
4024
|
+
const message = await markJwcTeamMailboxMessage(
|
|
4025
|
+
teamName,
|
|
4026
|
+
worker,
|
|
4027
|
+
String(input.message_id),
|
|
4028
|
+
"notified_at",
|
|
4029
|
+
cwd,
|
|
4030
|
+
env,
|
|
4031
|
+
);
|
|
4032
|
+
return { ok: true, ...mailboxMessageReceiptFields(teamName, message) };
|
|
4033
|
+
}
|
|
4034
|
+
case "notification-list": {
|
|
4035
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
4036
|
+
const config = await readConfig(dir);
|
|
4037
|
+
await reconcileTeamNotifications(dir, config);
|
|
4038
|
+
const notifications = await listNotificationRecords(dir);
|
|
4039
|
+
const result = { notifications, summary: summarizeNotifications(notifications) };
|
|
4040
|
+
return notificationSummaryReceipt(teamName, result);
|
|
4041
|
+
}
|
|
4042
|
+
case "notification-read":
|
|
4043
|
+
return {
|
|
4044
|
+
notification: await readNotificationRecord(
|
|
4045
|
+
await findTeamDir(teamName, cwd, env),
|
|
4046
|
+
String(input.notification_id),
|
|
4047
|
+
),
|
|
4048
|
+
};
|
|
4049
|
+
case "notification-replay":
|
|
4050
|
+
return notificationSummaryReceipt(teamName, await replayJwcTeamNotifications(teamName, cwd, env));
|
|
4051
|
+
case "notification-mark-pane-attempt": {
|
|
4052
|
+
const dir = await findTeamDir(teamName, cwd, env);
|
|
4053
|
+
const notification = await readNotificationRecord(dir, String(input.notification_id));
|
|
4054
|
+
const updated = await writeNotificationRecord(dir, {
|
|
4055
|
+
...notification,
|
|
4056
|
+
delivery_state: parsePaneAttemptResult(String(input.result ?? "failed")),
|
|
4057
|
+
pane_attempt_result: parsePaneAttemptResult(String(input.result ?? "failed")),
|
|
4058
|
+
pane_attempt_reason: String(input.reason ?? "manual_api"),
|
|
4059
|
+
pane_attempt_at: now(),
|
|
4060
|
+
updated_at: now(),
|
|
4061
|
+
});
|
|
4062
|
+
return { ok: true, ...notificationReceiptFields(updated) };
|
|
4063
|
+
}
|
|
4064
|
+
case "worker-startup-ack":
|
|
4065
|
+
return writeJwcWorkerStartupAck(teamName, worker, cwd, env, input);
|
|
4066
|
+
case "read-config":
|
|
4067
|
+
return await readConfig(await findTeamDir(teamName, cwd, env));
|
|
4068
|
+
case "read-manifest":
|
|
4069
|
+
return readJsonFile(path.join(await findTeamDir(teamName, cwd, env), "manifest.v2.json"));
|
|
4070
|
+
case "read-worker-status":
|
|
4071
|
+
return readJwcWorkerStatus(teamName, worker, cwd, env);
|
|
4072
|
+
case "update-worker-status": {
|
|
4073
|
+
const currentTaskIdInput = input.current_task_id ?? input.currentTaskId;
|
|
4074
|
+
return updateJwcWorkerStatus(
|
|
4075
|
+
teamName,
|
|
4076
|
+
worker,
|
|
4077
|
+
parseRequiredJwcWorkerStatusState(input.status ?? input.state),
|
|
4078
|
+
cwd,
|
|
4079
|
+
env,
|
|
4080
|
+
typeof currentTaskIdInput === "string" ? currentTaskIdInput : undefined,
|
|
4081
|
+
typeof input.reason === "string" ? input.reason : undefined,
|
|
4082
|
+
);
|
|
4083
|
+
}
|
|
4084
|
+
case "read-worker-heartbeat":
|
|
4085
|
+
return readJwcWorkerHeartbeat(teamName, worker, cwd, env);
|
|
4086
|
+
case "recover-stale-claims":
|
|
4087
|
+
return recoverJwcTeamStaleClaims(teamName, cwd, env);
|
|
4088
|
+
case "update-worker-heartbeat":
|
|
4089
|
+
return updateJwcWorkerHeartbeat(
|
|
4090
|
+
teamName,
|
|
4091
|
+
worker,
|
|
4092
|
+
{
|
|
4093
|
+
pid: Number(input.pid ?? 0),
|
|
4094
|
+
last_turn_at: now(),
|
|
4095
|
+
turn_count: Number(input.turn_count ?? 0),
|
|
4096
|
+
alive: Boolean(input.alive ?? true),
|
|
4097
|
+
},
|
|
4098
|
+
cwd,
|
|
4099
|
+
env,
|
|
4100
|
+
);
|
|
4101
|
+
case "write-worker-inbox":
|
|
4102
|
+
return writeJwcWorkerInbox(teamName, worker, String(input.content ?? ""), cwd, env);
|
|
4103
|
+
case "write-worker-identity":
|
|
4104
|
+
return writeJwcWorkerIdentity(
|
|
4105
|
+
teamName,
|
|
4106
|
+
{
|
|
4107
|
+
id: worker,
|
|
4108
|
+
name: worker,
|
|
4109
|
+
index: Number(input.index ?? 1),
|
|
4110
|
+
agent_type: String(input.role ?? "executor"),
|
|
4111
|
+
role: String(input.role ?? "executor"),
|
|
4112
|
+
status: "idle",
|
|
4113
|
+
last_heartbeat: now(),
|
|
4114
|
+
assigned_tasks: Array.isArray(input.assigned_tasks) ? input.assigned_tasks.map(String) : [],
|
|
4115
|
+
},
|
|
4116
|
+
cwd,
|
|
4117
|
+
env,
|
|
4118
|
+
);
|
|
4119
|
+
case "append-event":
|
|
4120
|
+
return appendJwcTeamEvent(teamName, String(input.type ?? "event"), worker, cwd, env);
|
|
4121
|
+
case "read-events":
|
|
4122
|
+
return { events: await readJwcTeamEvents(teamName, cwd, env) };
|
|
4123
|
+
case "read-traces":
|
|
4124
|
+
return { traces: await readJwcTeamTraces(teamName, cwd, env) };
|
|
4125
|
+
case "await-event":
|
|
4126
|
+
return awaitJwcTeamEvent(teamName, Number(input.timeout_ms ?? 0), cwd, env);
|
|
4127
|
+
case "write-monitor-snapshot":
|
|
4128
|
+
return writeJwcMonitorSnapshot(teamName, input.snapshot ?? {}, cwd, env);
|
|
4129
|
+
case "read-monitor-snapshot":
|
|
4130
|
+
return readJwcMonitorSnapshot(teamName, cwd, env);
|
|
4131
|
+
case "write-task-approval":
|
|
4132
|
+
return writeJwcTaskApproval(teamName, String(input.task_id), input, cwd, env);
|
|
4133
|
+
case "read-task-approval":
|
|
4134
|
+
return readJwcTaskApproval(teamName, String(input.task_id), cwd, env);
|
|
4135
|
+
case "write-shutdown-request": {
|
|
4136
|
+
const shutdownRequestIdInput = input.request_id ?? input.requestId;
|
|
4137
|
+
return writeJwcShutdownRequest(
|
|
4138
|
+
teamName,
|
|
4139
|
+
worker,
|
|
4140
|
+
String(input.requested_by ?? input.requestedBy ?? "leader-fixed"),
|
|
4141
|
+
cwd,
|
|
4142
|
+
env,
|
|
4143
|
+
typeof shutdownRequestIdInput === "string" ? shutdownRequestIdInput : undefined,
|
|
4144
|
+
parseJwcTeamShutdownMode(input.mode),
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
4147
|
+
case "read-shutdown-ack":
|
|
4148
|
+
return readJwcShutdownAck(teamName, worker, cwd, env);
|
|
4149
|
+
default:
|
|
4150
|
+
throw new Error(`unknown_team_api_operation:${operation}`);
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
export function parseTeamLaunchArgs(argv: string[]): JwcTeamStartOptions {
|
|
4155
|
+
const parsedWorktree = parseWorktreeMode(argv);
|
|
4156
|
+
const positionals = parsedWorktree.remainingArgs.filter(arg => !arg.startsWith("--"));
|
|
4157
|
+
const dryRun = argv.includes("--dry-run");
|
|
4158
|
+
let workerCount = GJC_TEAM_DEFAULT_WORKERS;
|
|
4159
|
+
let agentType = "executor";
|
|
4160
|
+
let taskStartIndex = 0;
|
|
4161
|
+
const first = positionals[0] ?? "";
|
|
4162
|
+
const countRole = first.match(/^(\d+):([a-zA-Z][a-zA-Z0-9_-]*)$/);
|
|
4163
|
+
const countOnly = first.match(/^(\d+)$/);
|
|
4164
|
+
const roleOnly = first.match(/^([a-zA-Z][a-zA-Z0-9_-]*)$/);
|
|
4165
|
+
if (countRole) {
|
|
4166
|
+
workerCount = Number.parseInt(countRole[1] ?? "", 10);
|
|
4167
|
+
agentType = countRole[2] ?? "executor";
|
|
4168
|
+
taskStartIndex = 1;
|
|
4169
|
+
} else if (countOnly) {
|
|
4170
|
+
workerCount = Number.parseInt(countOnly[1] ?? "", 10);
|
|
4171
|
+
taskStartIndex = 1;
|
|
4172
|
+
} else if (roleOnly && positionals.length > 1) {
|
|
4173
|
+
agentType = roleOnly[1] ?? "executor";
|
|
4174
|
+
taskStartIndex = 1;
|
|
4175
|
+
}
|
|
4176
|
+
const task = positionals.slice(taskStartIndex).join(" ").trim();
|
|
4177
|
+
if (!task) throw new Error("missing_team_task");
|
|
4178
|
+
if (!Number.isInteger(workerCount) || workerCount < 1 || workerCount > GJC_TEAM_MAX_WORKERS)
|
|
4179
|
+
throw new Error(`invalid_team_worker_count:${workerCount}:expected_1_${GJC_TEAM_MAX_WORKERS}`);
|
|
4180
|
+
return { workerCount, agentType, task, dryRun, worktreeMode: resolveDefaultWorktreeMode(parsedWorktree.mode) };
|
|
4181
|
+
}
|