@gajae-code/coding-agent 0.18.11 → 0.19.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 +760 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +21 -3
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +13 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +53 -1
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +44 -0
- package/dist/types/config/file-lock.d.ts +52 -3
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +79 -9
- package/dist/types/config/model-registry.d.ts +72 -8
- package/dist/types/config/model-resolver.d.ts +7 -4
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +57 -4
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +14 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +42 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +82 -3
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
- package/dist/types/sdk/broker/broker.d.ts +29 -2
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +141 -9
- package/dist/types/sdk/broker/hop.d.ts +45 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +6 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +44 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +40 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +107 -9
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +29 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +15 -2
- package/dist/types/tools/fetch.d.ts +7 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +10 -9
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +38 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +58 -22
- package/src/capability/index.ts +2 -2
- package/src/capability/rule.ts +11 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -3
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +19 -6
- package/src/cli/public-command-errors.ts +6 -0
- package/src/cli/root-flags.ts +2 -4
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +471 -13
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +277 -50
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/message.ts +10 -3
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +50 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +742 -0
- package/src/config/file-lock.ts +1615 -328
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +889 -148
- package/src/config/model-registry.ts +1706 -205
- package/src/config/model-resolver.ts +52 -22
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +73 -4
- package/src/config/settings.ts +460 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +7 -1
- package/src/customization/import.ts +279 -8
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +89 -6
- package/src/debug/report-bundle.ts +90 -2
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +25 -16
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +204 -22
- package/src/edit/modes/patch.ts +214 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +86 -6
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +10 -1
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +37 -7
- package/src/gjc-runtime/launch-tmux.ts +30 -15
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +17 -12
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +23 -22
- package/src/internal-urls/local-protocol.ts +167 -8
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +574 -2
- package/src/migrate/adapters/claude-code.ts +162 -5
- package/src/migrate/adapters/codex.ts +77 -7
- package/src/migrate/adapters/index.ts +258 -5
- package/src/migrate/adapters/opencode.ts +10 -1
- package/src/migrate/executor.ts +222 -7
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +63 -2
- package/src/migrate/types.ts +84 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +300 -9
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +66 -11
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +76 -7
- package/src/modes/interactive-mode.ts +123 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/theme/theme.ts +15 -0
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +40 -3
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +194 -21
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +10 -5
- package/src/sdk/broker/broker.ts +484 -85
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1214 -268
- package/src/sdk/broker/hop.ts +286 -0
- package/src/sdk/broker/lifecycle-ledger.ts +38 -11
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +91 -16
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +460 -35
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +23 -6
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1737 -129
- package/src/sdk/host/session-runtime.ts +684 -83
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +15 -1
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +81 -12
- package/src/sdk/protocol/operation-inventory.generated.json +261 -2
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +237 -97
- package/src/session/agent-session.ts +2726 -479
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +173 -57
- package/src/session/internal/managed-session-storage.ts +85 -12
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +326 -19
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/redact.ts +53 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +2145 -206
- package/src/slash-commands/builtin-registry.ts +21 -6
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +115 -20
- package/src/task/index.ts +389 -175
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +15 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +214 -6
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +113 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +16 -8
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +50 -6
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +93 -4
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/index.ts +2 -0
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +74 -5
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +26 -37
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +118 -5
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +32 -4
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +42 -3
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +12 -4
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +75 -7
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +72 -22
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
|
|
14
14
|
MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
|
|
15
15
|
MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
|
|
16
|
+
trustedManagedOwnerLaunchSessionFile,
|
|
16
17
|
} from "./managed-owner-admission";
|
|
17
18
|
import {
|
|
18
19
|
MANAGED_OWNER_INCARNATION_ENV,
|
|
@@ -70,11 +71,13 @@ import {
|
|
|
70
71
|
} from "./tmux-owner-isolation";
|
|
71
72
|
import { assertGjcTmuxStagedMutationAuthoritySync } from "./tmux-provider-context";
|
|
72
73
|
import {
|
|
74
|
+
childGjcSessionIdAssignment,
|
|
73
75
|
findGjcTmuxSessionByName,
|
|
74
76
|
findGjcTmuxSessionByScope,
|
|
75
77
|
type GjcTmuxSessionStatus,
|
|
76
78
|
type ProvenTmuxSessionIdentity,
|
|
77
79
|
proveGjcTmuxSessionMutationTarget,
|
|
80
|
+
tmuxLaunchIdentityEnv,
|
|
78
81
|
} from "./tmux-sessions";
|
|
79
82
|
import {
|
|
80
83
|
buildWindowsPowerShellInnerCommand,
|
|
@@ -432,6 +435,8 @@ function stripRootTmuxFlag(rawArgs: string[]): string[] {
|
|
|
432
435
|
|
|
433
436
|
export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProfileResult {
|
|
434
437
|
const env = context.env ?? process.env;
|
|
438
|
+
const cwd = context.cwd ?? process.cwd();
|
|
439
|
+
const identityEnv = tmuxLaunchIdentityEnv(env, cwd);
|
|
435
440
|
const branchSlug = context.branch ? buildGjcTmuxSessionSlug(context.branch) : (context.branchSlug ?? null);
|
|
436
441
|
// The psmux UX filter (mouse / set-clipboard / mode-style /
|
|
437
442
|
// set-window-option) now lives in buildGjcTmuxProfileCommands so every
|
|
@@ -447,8 +452,8 @@ export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProf
|
|
|
447
452
|
branch: context.branch ?? null,
|
|
448
453
|
branchSlug,
|
|
449
454
|
project: context.project ?? null,
|
|
450
|
-
sessionId: context.sessionId ??
|
|
451
|
-
sessionStateFile: context.sessionStateFile ??
|
|
455
|
+
sessionId: context.sessionId ?? identityEnv[GJC_COORDINATOR_SESSION_ID_ENV] ?? null,
|
|
456
|
+
sessionStateFile: context.sessionStateFile ?? identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV] ?? null,
|
|
452
457
|
ownerGeneration: context.ownerGeneration ?? null,
|
|
453
458
|
ownerServerKey: context.ownerServerKey ?? null,
|
|
454
459
|
version: context.version ?? null,
|
|
@@ -462,7 +467,6 @@ export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProf
|
|
|
462
467
|
});
|
|
463
468
|
if (commands.length === 0) return { skipped: true, commands: [], failures: [] };
|
|
464
469
|
const spawnSync = context.spawnSync ?? defaultSpawnSync;
|
|
465
|
-
const cwd = context.cwd ?? process.cwd();
|
|
466
470
|
const options: TmuxSpawnOptions = {
|
|
467
471
|
cwd,
|
|
468
472
|
env,
|
|
@@ -1105,22 +1109,24 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
1105
1109
|
if (policy === "tmux" && !isInteractiveRootLaunch(context.parsed, tty)) return undefined;
|
|
1106
1110
|
|
|
1107
1111
|
const cwd = context.cwd ?? process.cwd();
|
|
1112
|
+
const identityEnv = tmuxLaunchIdentityEnv(env, cwd);
|
|
1108
1113
|
const branch = context.worktreeBranch ?? context.currentBranch ?? readCurrentBranch(cwd);
|
|
1109
1114
|
const project = context.project ?? cwd;
|
|
1110
|
-
const sessionName = buildGjcTmuxSessionName(
|
|
1115
|
+
const sessionName = buildGjcTmuxSessionName(identityEnv, { branch });
|
|
1111
1116
|
// Pick the most appropriate tmux binary for this platform. On native Windows
|
|
1112
1117
|
// the resolver walks psmux / pmux / tmux and uses the first one present on
|
|
1113
1118
|
// PATH, so the default `gjc --tmux` flow lands on a real multiplexer even
|
|
1114
1119
|
// without an explicit GJC_TMUX_COMMAND override.
|
|
1115
1120
|
const resolvedBinary = resolveGjcTmuxBinary({ platform, env });
|
|
1116
1121
|
const tmuxCommand = resolvedBinary.command;
|
|
1117
|
-
const sessionId =
|
|
1122
|
+
const sessionId = identityEnv[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || sessionName;
|
|
1118
1123
|
// The session ROOT is keyed by the active GJC session (GJC_SESSION_ID), NOT the
|
|
1119
1124
|
// coordinator/tmux identity. Fall back to the coordinator id only for standalone
|
|
1120
|
-
// tmux launches with no GJC session context.
|
|
1121
|
-
|
|
1125
|
+
// tmux launches with no GJC session context. Project dotenv pins are already
|
|
1126
|
+
// removed from identityEnv.
|
|
1127
|
+
const gjcSessionId = identityEnv.GJC_SESSION_ID?.trim() || sessionId;
|
|
1122
1128
|
const sessionStateFile =
|
|
1123
|
-
|
|
1129
|
+
identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim() ||
|
|
1124
1130
|
tmuxRuntimeSessionPath(cwd, gjcSessionId, buildGjcTmuxSessionSlug(sessionName));
|
|
1125
1131
|
const signingEnv = coordinatorSidecarSigningEnv(env);
|
|
1126
1132
|
// Stage only the one-shot channel descriptor (path + key id). The PKCS#8 key
|
|
@@ -1135,13 +1141,13 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
1135
1141
|
(context.diagnosticWriter ?? safeStderrWrite)(formatTmuxUnavailableDiagnostic(platform));
|
|
1136
1142
|
return undefined;
|
|
1137
1143
|
}
|
|
1138
|
-
const existingSessionName = allowsExistingTmuxAttach(context.parsed,
|
|
1144
|
+
const existingSessionName = allowsExistingTmuxAttach(context.parsed, identityEnv)
|
|
1139
1145
|
? "existingBranchSessionName" in context
|
|
1140
1146
|
? (context.existingBranchSessionName ?? undefined)
|
|
1141
1147
|
: resolvedBinary.isPsmux && platform === "win32"
|
|
1142
|
-
? explicitTmuxSessionName(
|
|
1148
|
+
? explicitTmuxSessionName(identityEnv)
|
|
1143
1149
|
: findExistingSessionForLaunch({
|
|
1144
|
-
env,
|
|
1150
|
+
env: identityEnv,
|
|
1145
1151
|
project,
|
|
1146
1152
|
branch,
|
|
1147
1153
|
})
|
|
@@ -1153,6 +1159,7 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
1153
1159
|
execPath: context.execPath ?? process.execPath,
|
|
1154
1160
|
extraEnv: {
|
|
1155
1161
|
...childSigningEnv,
|
|
1162
|
+
...childGjcSessionIdAssignment(env, identityEnv, cwd, gjcSessionId),
|
|
1156
1163
|
[GJC_COORDINATOR_SESSION_ID_ENV]: sessionId,
|
|
1157
1164
|
[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: sessionStateFile,
|
|
1158
1165
|
// Carry the GJC-managed session name into the child so tmux-backed
|
|
@@ -1209,6 +1216,9 @@ function trustedReplacementAuthority(
|
|
|
1209
1216
|
function prepareManagedOwnerLifecycle(plan: TmuxLaunchPlan, context: TmuxLaunchContext): void {
|
|
1210
1217
|
if (plan.ownerGeneration) return;
|
|
1211
1218
|
const sessionId = plan.sessionId ?? plan.sessionName;
|
|
1219
|
+
const launchEnv = context.env ?? process.env;
|
|
1220
|
+
const identityEnv = tmuxLaunchIdentityEnv(launchEnv, plan.cwd);
|
|
1221
|
+
const gjcSessionId = identityEnv.GJC_SESSION_ID?.trim() || sessionId;
|
|
1212
1222
|
const stateDir = path.dirname(plan.sessionStateFile ?? path.join(plan.cwd, ".gjc", "runtime"));
|
|
1213
1223
|
const baseline = captureOwnerGenerationBaselineSync(stateDir, sessionId);
|
|
1214
1224
|
const replacement = trustedReplacementAuthority(stateDir, sessionId, baseline);
|
|
@@ -1233,6 +1243,7 @@ function prepareManagedOwnerLifecycle(plan: TmuxLaunchPlan, context: TmuxLaunchC
|
|
|
1233
1243
|
[GJC_COORDINATOR_SIDECAR_KEY_ID_ENV]: plan.coordinatorSidecarBootstrap.keyId,
|
|
1234
1244
|
}
|
|
1235
1245
|
: {}),
|
|
1246
|
+
...childGjcSessionIdAssignment(launchEnv, identityEnv, plan.cwd, gjcSessionId),
|
|
1236
1247
|
[GJC_COORDINATOR_SESSION_ID_ENV]: sessionId,
|
|
1237
1248
|
[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: plan.sessionStateFile ?? "",
|
|
1238
1249
|
// The tmux server inherits the bootstrap secret as child environment;
|
|
@@ -1249,8 +1260,12 @@ function prepareManagedOwnerLifecycle(plan: TmuxLaunchPlan, context: TmuxLaunchC
|
|
|
1249
1260
|
[MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]: replacement.generation,
|
|
1250
1261
|
[MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]: replacement.runId,
|
|
1251
1262
|
[MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]: replacement.incarnation,
|
|
1252
|
-
[MANAGED_OWNER_TRANSCRIPT_PATH_ENV]:
|
|
1253
|
-
|
|
1263
|
+
[MANAGED_OWNER_TRANSCRIPT_PATH_ENV]: trustedManagedOwnerLaunchSessionFile(
|
|
1264
|
+
{
|
|
1265
|
+
GJC_SESSION_FILE: context.env?.GJC_SESSION_FILE ?? process.env.GJC_SESSION_FILE,
|
|
1266
|
+
},
|
|
1267
|
+
plan.cwd,
|
|
1268
|
+
),
|
|
1254
1269
|
}
|
|
1255
1270
|
: {}),
|
|
1256
1271
|
},
|
|
@@ -1522,7 +1537,7 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
1522
1537
|
try {
|
|
1523
1538
|
if (plan.attachSessionName) {
|
|
1524
1539
|
const stateDir = env[GJC_TMUX_OWNER_STATE_DIR_ENV]?.trim();
|
|
1525
|
-
const sessionId = env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim();
|
|
1540
|
+
const sessionId = tmuxLaunchIdentityEnv(env, plan.cwd)[GJC_COORDINATOR_SESSION_ID_ENV]?.trim();
|
|
1526
1541
|
const generation = env[GJC_TMUX_OWNER_GENERATION_ENV]?.trim();
|
|
1527
1542
|
if (!stateDir || !sessionId || !generation) throw new Error("gjc_tmux_provider_authority_unavailable");
|
|
1528
1543
|
plan.authority = readGjcTmuxProviderAuthoritySync({ stateDir, sessionId, generation });
|
|
@@ -1591,7 +1606,7 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
1591
1606
|
}
|
|
1592
1607
|
};
|
|
1593
1608
|
const creationSpawn = plan.authority ? spawnSync : rawSpawnSync;
|
|
1594
|
-
const spawnEnv = { ...env };
|
|
1609
|
+
const spawnEnv = { ...tmuxLaunchIdentityEnv(env, plan.cwd) };
|
|
1595
1610
|
delete spawnEnv[GJC_COORDINATOR_SIDECAR_SIGNING_KEY_ENV];
|
|
1596
1611
|
const signingEnv = coordinatorSidecarSigningEnv(env);
|
|
1597
1612
|
Object.assign(spawnEnv, signingEnv);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ensureLaunchWorktree,
|
|
10
10
|
evacuatePreWorktreeTarget,
|
|
11
11
|
isReplaceableWorktreeTarget,
|
|
12
|
+
removeOwnedLaunchWorktree,
|
|
12
13
|
restorePreWorktreeGjc,
|
|
13
14
|
} from "./launch-worktree";
|
|
14
15
|
|
|
@@ -246,3 +247,150 @@ describe("acquireTargetLock", () => {
|
|
|
246
247
|
expect(fs.existsSync(path.join(worktreePath, ".git"))).toBe(true);
|
|
247
248
|
});
|
|
248
249
|
});
|
|
250
|
+
|
|
251
|
+
function ownedReceipt(): { created: true; reused: false; createdBranch: false } {
|
|
252
|
+
return { created: true, reused: false, createdBranch: false };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function detachedPlan(repoRoot: string, worktreePath: string): GjcLaunchWorktreePlan {
|
|
256
|
+
return {
|
|
257
|
+
enabled: true,
|
|
258
|
+
repoRoot,
|
|
259
|
+
worktreePath,
|
|
260
|
+
detached: true,
|
|
261
|
+
baseRef: "HEAD",
|
|
262
|
+
branchName: null,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Replace a real worktree directory with a symlink to a directory that copies its `.git` file. */
|
|
267
|
+
function swapWorktreeForForgedGitdir(worktreePath: string, forgeDir: string): void {
|
|
268
|
+
const gitFile = fs.readFileSync(path.join(worktreePath, ".git"), "utf8");
|
|
269
|
+
fs.mkdirSync(forgeDir, { recursive: true });
|
|
270
|
+
fs.writeFileSync(path.join(forgeDir, ".git"), gitFile.endsWith("\n") ? gitFile : `${gitFile}\n`);
|
|
271
|
+
fs.writeFileSync(path.join(forgeDir, "needle.txt"), "KEEP-NEEDLE\n");
|
|
272
|
+
fs.rmSync(worktreePath, { recursive: true, force: true });
|
|
273
|
+
fs.symlinkSync(forgeDir, worktreePath, "dir");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
describe("removeOwnedLaunchWorktree", () => {
|
|
277
|
+
test("still removes a real directory worktree", () => {
|
|
278
|
+
const repo = initRepo("gjc-rm-real-repo-");
|
|
279
|
+
const bucket = makeTempDir("gjc-rm-real-bucket-");
|
|
280
|
+
const worktreePath = path.join(bucket, "wt");
|
|
281
|
+
git(repo, ["worktree", "add", "-q", "--detach", worktreePath, "HEAD"]);
|
|
282
|
+
fs.writeFileSync(path.join(worktreePath, "needle.txt"), "inside\n");
|
|
283
|
+
|
|
284
|
+
removeOwnedLaunchWorktree(detachedPlan(repo, worktreePath), ownedReceipt());
|
|
285
|
+
|
|
286
|
+
expect(fs.existsSync(worktreePath)).toBe(false);
|
|
287
|
+
expect(countRegisteredWorktrees(repo)).toBe(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("still removes a worktree reached through an in-repo directory symlink", () => {
|
|
291
|
+
const repo = initRepo("gjc-rm-inside-link-repo-");
|
|
292
|
+
const inside = path.join(repo, "inside");
|
|
293
|
+
fs.mkdirSync(inside);
|
|
294
|
+
fs.symlinkSync(inside, path.join(repo, ".worktrees"), "dir");
|
|
295
|
+
const worktreePath = path.join(repo, ".worktrees", "wt");
|
|
296
|
+
git(repo, ["worktree", "add", "-q", "--detach", worktreePath, "HEAD"]);
|
|
297
|
+
|
|
298
|
+
removeOwnedLaunchWorktree(detachedPlan(repo, worktreePath), ownedReceipt());
|
|
299
|
+
|
|
300
|
+
expect(fs.existsSync(path.join(inside, "wt"))).toBe(false);
|
|
301
|
+
expect(fs.lstatSync(path.join(repo, ".worktrees")).isSymbolicLink()).toBe(true);
|
|
302
|
+
expect(countRegisteredWorktrees(repo)).toBe(1);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("does not delete the referent when the worktree path is a symlink to a forged gitdir", () => {
|
|
306
|
+
const repo = initRepo("gjc-rm-forge-repo-");
|
|
307
|
+
const bucket = makeTempDir("gjc-rm-forge-bucket-");
|
|
308
|
+
const worktreePath = path.join(bucket, "wt");
|
|
309
|
+
git(repo, ["worktree", "add", "-q", "--detach", worktreePath, "HEAD"]);
|
|
310
|
+
const forge = makeTempDir("gjc-rm-forge-target-");
|
|
311
|
+
swapWorktreeForForgedGitdir(worktreePath, forge);
|
|
312
|
+
|
|
313
|
+
removeOwnedLaunchWorktree(detachedPlan(repo, worktreePath), ownedReceipt());
|
|
314
|
+
|
|
315
|
+
expect(fs.readFileSync(path.join(forge, "needle.txt"), "utf8")).toBe("KEEP-NEEDLE\n");
|
|
316
|
+
expect(fs.lstatSync(path.join(forge, ".git")).isFile()).toBe(true);
|
|
317
|
+
expect(fs.existsSync(worktreePath)).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("still leaves an outside symlink when an in-repo bucket is retargeted", () => {
|
|
321
|
+
const repo = initRepo("gjc-rm-outlink-repo-");
|
|
322
|
+
const bucket = path.join(repo, ".worktrees");
|
|
323
|
+
fs.mkdirSync(bucket);
|
|
324
|
+
const worktreePath = path.join(bucket, "wt");
|
|
325
|
+
git(repo, ["worktree", "add", "-q", "--detach", worktreePath, "HEAD"]);
|
|
326
|
+
fs.rmSync(bucket, { recursive: true, force: true });
|
|
327
|
+
const outside = makeTempDir("gjc-rm-outlink-outside-");
|
|
328
|
+
const vault = makeTempDir("gjc-rm-outlink-vault-");
|
|
329
|
+
fs.writeFileSync(path.join(vault, "needle.txt"), "KEEP-NEEDLE\n");
|
|
330
|
+
const outsideLink = path.join(outside, "wt");
|
|
331
|
+
fs.symlinkSync(vault, outsideLink, "dir");
|
|
332
|
+
fs.symlinkSync(outside, bucket, "dir");
|
|
333
|
+
|
|
334
|
+
removeOwnedLaunchWorktree(detachedPlan(repo, worktreePath), ownedReceipt());
|
|
335
|
+
|
|
336
|
+
expect(fs.lstatSync(outsideLink).isSymbolicLink()).toBe(true);
|
|
337
|
+
expect(fs.readlinkSync(outsideLink)).toBe(vault);
|
|
338
|
+
expect(fs.readFileSync(path.join(vault, "needle.txt"), "utf8")).toBe("KEEP-NEEDLE\n");
|
|
339
|
+
expect(fs.lstatSync(bucket).isSymbolicLink()).toBe(true);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("does not delete a directory outside the repo when an in-repo bucket symlink is retargeted", () => {
|
|
343
|
+
const repo = initRepo("gjc-rm-retarget-repo-");
|
|
344
|
+
const bucket = path.join(repo, ".worktrees");
|
|
345
|
+
fs.mkdirSync(bucket);
|
|
346
|
+
const worktreePath = path.join(bucket, "wt");
|
|
347
|
+
git(repo, ["worktree", "add", "-q", "--detach", worktreePath, "HEAD"]);
|
|
348
|
+
const gitFile = fs.readFileSync(path.join(worktreePath, ".git"), "utf8");
|
|
349
|
+
fs.rmSync(bucket, { recursive: true, force: true });
|
|
350
|
+
const outside = makeTempDir("gjc-rm-retarget-outside-");
|
|
351
|
+
const outsideWt = path.join(outside, "wt");
|
|
352
|
+
fs.mkdirSync(outsideWt);
|
|
353
|
+
fs.writeFileSync(path.join(outsideWt, ".git"), gitFile.endsWith("\n") ? gitFile : `${gitFile}\n`);
|
|
354
|
+
fs.writeFileSync(path.join(outsideWt, "needle.txt"), "KEEP-NEEDLE\n");
|
|
355
|
+
fs.symlinkSync(outside, bucket, "dir");
|
|
356
|
+
|
|
357
|
+
removeOwnedLaunchWorktree(detachedPlan(repo, worktreePath), ownedReceipt());
|
|
358
|
+
|
|
359
|
+
expect(fs.readFileSync(path.join(outsideWt, "needle.txt"), "utf8")).toBe("KEEP-NEEDLE\n");
|
|
360
|
+
expect(fs.lstatSync(bucket).isSymbolicLink()).toBe(true);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe("commitPreWorktreeRestore symlink rollback", () => {
|
|
365
|
+
test("rolls back a symlinked worktree without deleting the forged referent", () => {
|
|
366
|
+
const repo = initRepo("gjc-rb-forge-repo-");
|
|
367
|
+
const bucket = makeTempDir("gjc-rb-forge-bucket-");
|
|
368
|
+
const worktreePath = path.join(bucket, "wt");
|
|
369
|
+
git(repo, ["worktree", "add", "-q", "--detach", worktreePath, "HEAD"]);
|
|
370
|
+
const forge = makeTempDir("gjc-rb-forge-target-");
|
|
371
|
+
swapWorktreeForForgedGitdir(worktreePath, forge);
|
|
372
|
+
const stashPath = path.join(bucket, ".gjc-pre-wt-wt");
|
|
373
|
+
fs.mkdirSync(stashPath, { recursive: true });
|
|
374
|
+
fs.writeFileSync(path.join(stashPath, "launcher.txt"), "config");
|
|
375
|
+
|
|
376
|
+
let message = "";
|
|
377
|
+
try {
|
|
378
|
+
commitPreWorktreeRestore(
|
|
379
|
+
{
|
|
380
|
+
restore: () => {
|
|
381
|
+
throw new Error("overlay exploded");
|
|
382
|
+
},
|
|
383
|
+
stashPath,
|
|
384
|
+
},
|
|
385
|
+
detachedPlan(repo, worktreePath),
|
|
386
|
+
);
|
|
387
|
+
throw new Error("expected commitPreWorktreeRestore to throw");
|
|
388
|
+
} catch (error) {
|
|
389
|
+
message = error instanceof Error ? error.message : String(error);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
expect(message).toMatch(/worktree_gjc_restore_failed/);
|
|
393
|
+
expect(fs.readFileSync(path.join(forge, "needle.txt"), "utf8")).toBe("KEEP-NEEDLE\n");
|
|
394
|
+
expect(fs.existsSync(path.join(stashPath, "launcher.txt"))).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
@@ -244,9 +244,34 @@ function resolveWorktreeBucket(repoRoot: string): string {
|
|
|
244
244
|
return resolveWorktreeBucketForPath(repoRoot, trustedWorktreeBucketEnv(process.env), os.homedir(), path);
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
function throwIfBucketRealpathEscapesRepository(repoRoot: string, bucketPath: string): void {
|
|
248
|
+
let realBucket: string;
|
|
249
|
+
try {
|
|
250
|
+
realBucket = fs.realpathSync(bucketPath);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
// A missing bucket is created later. A dangling link is classified by inspectBucketDir.
|
|
253
|
+
if (fileSystemErrorCode(error) === "ENOENT") return;
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
const relativeReal = path.relative(fs.realpathSync(repoRoot), realBucket);
|
|
257
|
+
if (relativeReal.startsWith(`..${path.sep}`) || relativeReal === ".." || path.isAbsolute(relativeReal)) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
[
|
|
260
|
+
"worktree_bucket_escapes_repository",
|
|
261
|
+
"The GJC launch worktree bucket resolves outside the repository.",
|
|
262
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
263
|
+
"Safe remediation: replace the path with a real directory inside the repository, or set GJC_WORKTREE_DIR to a directory outside the repository. GJC did not create a worktree.",
|
|
264
|
+
].join("\n"),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
247
269
|
function ensureRepositoryBucketIgnored(repoRoot: string, bucketPath: string): void {
|
|
248
270
|
const relativeBucket = path.relative(repoRoot, bucketPath);
|
|
271
|
+
// Lexical paths outside the repo are the operator override and stay allowed.
|
|
249
272
|
if (relativeBucket.startsWith(`..${path.sep}`) || relativeBucket === ".." || path.isAbsolute(relativeBucket)) return;
|
|
273
|
+
// git check-ignore accepts a gitignored symlink without looking at its target.
|
|
274
|
+
throwIfBucketRealpathEscapesRepository(repoRoot, bucketPath);
|
|
250
275
|
const ignoreProbe = path.join(relativeBucket, ".gjc-worktree-probe");
|
|
251
276
|
for (const candidate of [relativeBucket, ignoreProbe]) {
|
|
252
277
|
const result = Bun.spawnSync(["git", "check-ignore", "--quiet", "--", candidate], {
|
|
@@ -702,17 +727,87 @@ function tryRestore(restore: () => void): void {
|
|
|
702
727
|
}
|
|
703
728
|
}
|
|
704
729
|
|
|
705
|
-
|
|
706
|
-
|
|
730
|
+
function lexicalPathInsideRepository(repoRoot: string, targetPath: string): boolean {
|
|
731
|
+
const relative = path.relative(path.resolve(repoRoot), path.resolve(targetPath));
|
|
732
|
+
return !(relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function realpathEscapesRepository(repoRoot: string, targetPath: string): boolean {
|
|
736
|
+
let realTarget: string;
|
|
737
|
+
try {
|
|
738
|
+
realTarget = fs.realpathSync(targetPath);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
// A missing path is not an escape. Anything else is not safe to hand to remove --force.
|
|
741
|
+
if (fileSystemErrorCode(error) === "ENOENT") return false;
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
const relativeReal = path.relative(fs.realpathSync(repoRoot), realTarget);
|
|
745
|
+
return relativeReal.startsWith(`..${path.sep}`) || relativeReal === ".." || path.isAbsolute(relativeReal);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function pruneWorktreeRegistration(repoRoot: string): void {
|
|
707
749
|
try {
|
|
708
|
-
runGit(
|
|
750
|
+
runGit(repoRoot, ["worktree", "prune"]);
|
|
709
751
|
} catch {
|
|
752
|
+
// Best-effort. Callers report their own failure.
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Remove a launch worktree this process just created.
|
|
758
|
+
*
|
|
759
|
+
* `git worktree remove --force` follows a symlink and deletes the referent when a
|
|
760
|
+
* copied `.git` gitdir file passes validation. Unlink that link only when its
|
|
761
|
+
* parent stays inside the repository, or when the path is an explicit external
|
|
762
|
+
* worktree. An in-repo path whose parent resolves outside the repository is
|
|
763
|
+
* left untouched. A real directory is still removed with `--force`.
|
|
764
|
+
*/
|
|
765
|
+
function removeCreatedWorktree(repoRoot: string, worktreePath: string): void {
|
|
766
|
+
let stat: fs.Stats | undefined;
|
|
767
|
+
try {
|
|
768
|
+
stat = fs.lstatSync(worktreePath);
|
|
769
|
+
} catch (error) {
|
|
770
|
+
if (fileSystemErrorCode(error) !== "ENOENT") {
|
|
771
|
+
pruneWorktreeRegistration(repoRoot);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// A retargeted in-repo bucket can place this path's parent outside the repository.
|
|
776
|
+
// Do not unlink that outside entry. An explicit external worktree is not this case.
|
|
777
|
+
if (
|
|
778
|
+
lexicalPathInsideRepository(repoRoot, worktreePath) &&
|
|
779
|
+
realpathEscapesRepository(repoRoot, path.dirname(worktreePath))
|
|
780
|
+
) {
|
|
781
|
+
pruneWorktreeRegistration(repoRoot);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (stat?.isSymbolicLink()) {
|
|
710
785
|
try {
|
|
711
|
-
|
|
786
|
+
fs.unlinkSync(worktreePath);
|
|
712
787
|
} catch {
|
|
713
|
-
//
|
|
788
|
+
// The link may already be gone.
|
|
714
789
|
}
|
|
790
|
+
pruneWorktreeRegistration(repoRoot);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (
|
|
794
|
+
stat?.isDirectory() &&
|
|
795
|
+
lexicalPathInsideRepository(repoRoot, worktreePath) &&
|
|
796
|
+
realpathEscapesRepository(repoRoot, worktreePath)
|
|
797
|
+
) {
|
|
798
|
+
pruneWorktreeRegistration(repoRoot);
|
|
799
|
+
return;
|
|
715
800
|
}
|
|
801
|
+
try {
|
|
802
|
+
runGit(repoRoot, ["worktree", "remove", "--force", worktreePath]);
|
|
803
|
+
} catch {
|
|
804
|
+
pruneWorktreeRegistration(repoRoot);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** Best-effort removal of a worktree GJC just created, used when a later step must roll it back. */
|
|
809
|
+
function rollbackCreatedWorktree(plan: GjcLaunchWorktreePlan): void {
|
|
810
|
+
removeCreatedWorktree(plan.repoRoot, plan.worktreePath);
|
|
716
811
|
}
|
|
717
812
|
|
|
718
813
|
/**
|
|
@@ -1205,15 +1300,7 @@ export function removeOwnedLaunchWorktree(
|
|
|
1205
1300
|
receipt: { created: boolean; reused: boolean; createdBranch: boolean },
|
|
1206
1301
|
): void {
|
|
1207
1302
|
if (!receipt.created || receipt.reused) return;
|
|
1208
|
-
|
|
1209
|
-
runGit(plan.repoRoot, ["worktree", "remove", "--force", plan.worktreePath]);
|
|
1210
|
-
} catch {
|
|
1211
|
-
try {
|
|
1212
|
-
runGit(plan.repoRoot, ["worktree", "prune"]);
|
|
1213
|
-
} catch {
|
|
1214
|
-
// Proof stays on the ledger; leftover classify is best-effort after abort.
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1303
|
+
removeCreatedWorktree(plan.repoRoot, plan.worktreePath);
|
|
1217
1304
|
if (receipt.createdBranch && plan.branchName) {
|
|
1218
1305
|
try {
|
|
1219
1306
|
runGit(plan.repoRoot, ["branch", "-D", plan.branchName]);
|
|
@@ -2,8 +2,10 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import * as crypto from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { canonicalEnvKey, type ProjectEnvSnapshot, projectEnvSnapshot } from "@gajae-code/utils/env-file";
|
|
5
6
|
import type { ManagedOwnerBinding, ManagedOwnerSigabrtReceipt } from "./managed-owner-supervisor";
|
|
6
7
|
import { assertSafePathComponent } from "./session-layout";
|
|
8
|
+
import { trustedCoordinatorEnv } from "./sidecar-bootstrap-env";
|
|
7
9
|
import { lifecyclePaths } from "./tmux-owner-isolation";
|
|
8
10
|
import {
|
|
9
11
|
persistUltragoalRecoveryDecision,
|
|
@@ -23,6 +25,48 @@ export const MANAGED_OWNER_PREDECESSOR_GENERATION_ENV = "GJC_MANAGED_OWNER_PREDE
|
|
|
23
25
|
export const MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV = "GJC_MANAGED_OWNER_PREDECESSOR_RUN_ID";
|
|
24
26
|
export const MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV = "GJC_MANAGED_OWNER_PREDECESSOR_INCARNATION";
|
|
25
27
|
export const MANAGED_OWNER_TRANSCRIPT_PATH_ENV = "GJC_MANAGED_OWNER_TRANSCRIPT_PATH";
|
|
28
|
+
const MANAGED_OWNER_SESSION_FILE_ENV = "GJC_SESSION_FILE";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Operator environment only. A project dotenv value, including a dynamic
|
|
32
|
+
* declaration, must not choose the recovery transcript. A static
|
|
33
|
+
* `GJC_SESSION_FILE` declaration is the same source: the launcher copies it
|
|
34
|
+
* into the transcript variable. The operator bytes are kept, including
|
|
35
|
+
* surrounding whitespace; provenance is not path normalization.
|
|
36
|
+
*/
|
|
37
|
+
function operatorOwnedEnvValue(name: string, raw: string | undefined, snapshot: ProjectEnvSnapshot): string {
|
|
38
|
+
if (!raw) return "";
|
|
39
|
+
const key = canonicalEnvKey(name);
|
|
40
|
+
const declared = snapshot.values[key];
|
|
41
|
+
if (declared !== undefined && (snapshot.dynamic.has(key) || declared === raw)) return "";
|
|
42
|
+
const verdict = trustedCoordinatorEnv(name, { [name]: raw }, snapshot);
|
|
43
|
+
if (verdict === undefined && raw.trim().length > 0) return "";
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function trustedManagedOwnerTranscriptPath(env: NodeJS.ProcessEnv = process.env, cwd = process.cwd()): string {
|
|
48
|
+
const snapshot = projectEnvSnapshot(cwd);
|
|
49
|
+
const raw = operatorOwnedEnvValue(
|
|
50
|
+
MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
|
|
51
|
+
env[MANAGED_OWNER_TRANSCRIPT_PATH_ENV],
|
|
52
|
+
snapshot,
|
|
53
|
+
);
|
|
54
|
+
if (!raw) return "";
|
|
55
|
+
const sessionKey = canonicalEnvKey(MANAGED_OWNER_SESSION_FILE_ENV);
|
|
56
|
+
if (snapshot.dynamic.has(sessionKey)) return raw;
|
|
57
|
+
const declared = snapshot.values[sessionKey];
|
|
58
|
+
if (declared !== undefined && raw === declared) return "";
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Session file forwarded to a replacement child. Same provenance rule. */
|
|
63
|
+
export function trustedManagedOwnerLaunchSessionFile(env: NodeJS.ProcessEnv, cwd: string): string {
|
|
64
|
+
return operatorOwnedEnvValue(
|
|
65
|
+
MANAGED_OWNER_SESSION_FILE_ENV,
|
|
66
|
+
env[MANAGED_OWNER_SESSION_FILE_ENV],
|
|
67
|
+
projectEnvSnapshot(cwd),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
26
70
|
|
|
27
71
|
export interface ManagedOwnerRecoveryContext {
|
|
28
72
|
root: string;
|
|
@@ -295,7 +339,7 @@ export async function admitManagedOwnerBeforeCli(): Promise<ManagedOwnerAdmissio
|
|
|
295
339
|
},
|
|
296
340
|
receipt,
|
|
297
341
|
admission,
|
|
298
|
-
transcriptPath:
|
|
342
|
+
transcriptPath: trustedManagedOwnerTranscriptPath(),
|
|
299
343
|
});
|
|
300
344
|
await persistUltragoalRecoveryDecision({
|
|
301
345
|
cwd: process.cwd(),
|
|
@@ -330,7 +374,7 @@ export async function completeManagedOwnerRecovery(
|
|
|
330
374
|
binding: recoveryBinding,
|
|
331
375
|
receipt: context.receipt,
|
|
332
376
|
admission: context.admission,
|
|
333
|
-
transcriptPath:
|
|
377
|
+
transcriptPath: trustedManagedOwnerTranscriptPath(),
|
|
334
378
|
protectedPaths: context.decision.snapshot?.protectedPaths,
|
|
335
379
|
sanctionedDeltas: context.decision.snapshot?.sanctionedDeltas,
|
|
336
380
|
absentArtifacts: context.decision.snapshot?.absentArtifacts,
|