@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
|
@@ -1896,10 +1896,10 @@ const EXECUTION_APPROVAL_TRANSCRIPT_MAX_BYTES = 128 * 1024 * 1024;
|
|
|
1896
1896
|
const DEEP_INTERVIEW_EXECUTION_APPROVAL_RECORD_MAX_BYTES = 16 * 1024;
|
|
1897
1897
|
const DEEP_INTERVIEW_EXECUTION_APPROVAL_ID_MAX_LENGTH = 256;
|
|
1898
1898
|
|
|
1899
|
+
// The boundary binds approval to transcript content (prefix length/sha256, leaf, Ask call), not to
|
|
1900
|
+
// the file's inode: SessionManager persists full rewrites by atomic rename, which replaces the inode.
|
|
1899
1901
|
export interface ExecutionApprovalTranscriptBoundary {
|
|
1900
1902
|
byte_length: number;
|
|
1901
|
-
device: string;
|
|
1902
|
-
inode: string;
|
|
1903
1903
|
leaf_id: string | null;
|
|
1904
1904
|
approval_tool_call_id: string;
|
|
1905
1905
|
}
|
|
@@ -1909,10 +1909,6 @@ function isExecutionApprovalTranscriptBoundary(value: unknown): value is Executi
|
|
|
1909
1909
|
isPlainObject(value) &&
|
|
1910
1910
|
Number.isSafeInteger(value.byte_length) &&
|
|
1911
1911
|
(value.byte_length as number) > 0 &&
|
|
1912
|
-
typeof value.device === "string" &&
|
|
1913
|
-
/^\d+$/.test(value.device) &&
|
|
1914
|
-
typeof value.inode === "string" &&
|
|
1915
|
-
/^\d+$/.test(value.inode) &&
|
|
1916
1912
|
(value.leaf_id === null || isExecutionApprovalId(value.leaf_id)) &&
|
|
1917
1913
|
isExecutionApprovalId(value.approval_tool_call_id)
|
|
1918
1914
|
);
|
|
@@ -2037,8 +2033,6 @@ export async function captureExecutionApprovalTranscriptBoundary(
|
|
|
2037
2033
|
throw new StateCommandError(2, "execution approval transcript boundary lacks the current Ask call");
|
|
2038
2034
|
return {
|
|
2039
2035
|
byte_length: Buffer.byteLength(text),
|
|
2040
|
-
device: after.dev.toString(),
|
|
2041
|
-
inode: after.ino.toString(),
|
|
2042
2036
|
leaf_id: prefix.leaf,
|
|
2043
2037
|
approval_tool_call_id: approvalToolCallId,
|
|
2044
2038
|
};
|
|
@@ -2062,13 +2056,8 @@ export async function assertExecutionApprovalTranscriptBoundary(
|
|
|
2062
2056
|
const after = await fs.lstat(transcriptPath, { bigint: true });
|
|
2063
2057
|
if ((await fs.realpath(transcriptPath)) !== path.resolve(transcriptPath))
|
|
2064
2058
|
throw new StateCommandError(2, "execution approval transcript path is not canonical");
|
|
2065
|
-
if (
|
|
2066
|
-
|
|
2067
|
-
after.dev.toString() !== boundary.device ||
|
|
2068
|
-
after.ino.toString() !== boundary.inode ||
|
|
2069
|
-
text === undefined
|
|
2070
|
-
)
|
|
2071
|
-
throw new StateCommandError(2, "execution approval transcript identity changed");
|
|
2059
|
+
if (!sameBoundedFileIdentity(before, after) || text === undefined)
|
|
2060
|
+
throw new StateCommandError(2, "execution approval transcript changed while it was read");
|
|
2072
2061
|
const bytes = Buffer.from(text);
|
|
2073
2062
|
const prefix = bytes.subarray(0, boundary.byte_length);
|
|
2074
2063
|
if (prefix.length !== boundary.byte_length || createHash("sha256").update(prefix).digest("hex") !== transcriptSha256)
|
|
@@ -3222,6 +3211,21 @@ async function assertSanctionedExecutionApprovalAudit(
|
|
|
3222
3211
|
)
|
|
3223
3212
|
throw new StateCommandError(2, "deep-interview execution approval lacks sanctioned transition provenance");
|
|
3224
3213
|
|
|
3214
|
+
// The approval index is written only after the append-only ledger entry.
|
|
3215
|
+
// A pending journal means that append did not finish, so the index must
|
|
3216
|
+
// not authorize execution handoff.
|
|
3217
|
+
let approvalJournal: WorkflowTransactionJournal | undefined;
|
|
3218
|
+
try {
|
|
3219
|
+
approvalJournal = await readWorkflowTransactionJournal(cwd, sessionId, mutationId);
|
|
3220
|
+
} catch (error) {
|
|
3221
|
+
throw new StateCommandError(
|
|
3222
|
+
2,
|
|
3223
|
+
`failed to read execution approval journal: ${error instanceof Error ? error.message : String(error)}`,
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
if (approvalJournal?.status === "pending")
|
|
3227
|
+
throw new StateCommandError(2, "deep-interview execution approval journal is incomplete");
|
|
3228
|
+
|
|
3225
3229
|
const indexedApprovalPath = path.join(sessionStateDir(cwd, sessionId), "deep-interview-approval-audit.json");
|
|
3226
3230
|
let indexedRaw = "";
|
|
3227
3231
|
try {
|
|
@@ -5067,8 +5071,6 @@ async function appendExecutionApprovalAudit(
|
|
|
5067
5071
|
} = {},
|
|
5068
5072
|
): Promise<void> {
|
|
5069
5073
|
const entry = buildExecutionApprovalAuditEntry(options);
|
|
5070
|
-
await writeExecutionApprovalIndex(options, entry);
|
|
5071
|
-
await hooks.afterIndex?.();
|
|
5072
5074
|
const filePath = auditPath(options.cwd, options.sessionId);
|
|
5073
5075
|
await withWorkflowStateLock(
|
|
5074
5076
|
filePath,
|
|
@@ -5089,6 +5091,8 @@ async function appendExecutionApprovalAudit(
|
|
|
5089
5091
|
},
|
|
5090
5092
|
{ cwd: options.cwd },
|
|
5091
5093
|
);
|
|
5094
|
+
await writeExecutionApprovalIndex(options, entry);
|
|
5095
|
+
await hooks.afterIndex?.();
|
|
5092
5096
|
await hooks.afterAudit?.();
|
|
5093
5097
|
}
|
|
5094
5098
|
|
|
@@ -5296,8 +5300,6 @@ async function handleApproveExecutionRecordLocked(
|
|
|
5296
5300
|
indexedMatches = isPlainObject(parsed) && JSON.stringify(parsed) === JSON.stringify(expectedApprovalEntry);
|
|
5297
5301
|
} catch {}
|
|
5298
5302
|
}
|
|
5299
|
-
if (!indexedMatches) await writeExecutionApprovalIndex(approvalOptions, expectedApprovalEntry);
|
|
5300
|
-
recoverySteps.add("approval-index");
|
|
5301
5303
|
let approvalAuditOffset = pendingJournal.approval_audit_offset;
|
|
5302
5304
|
if (!recoverySteps.has("approval-audit")) {
|
|
5303
5305
|
approvalAuditOffset = await appendExecutionApprovalAuditIdempotent(
|
|
@@ -5312,6 +5314,8 @@ async function handleApproveExecutionRecordLocked(
|
|
|
5312
5314
|
);
|
|
5313
5315
|
recoverySteps.add("approval-audit");
|
|
5314
5316
|
}
|
|
5317
|
+
if (!indexedMatches) await writeExecutionApprovalIndex(approvalOptions, expectedApprovalEntry);
|
|
5318
|
+
recoverySteps.add("approval-index");
|
|
5315
5319
|
await updateWorkflowTransactionJournal(cwd, selectors.gjcSessionId, existingReceipt.mutation_id, {
|
|
5316
5320
|
steps: [...recoverySteps],
|
|
5317
5321
|
approval_audit_offset: approvalAuditOffset,
|
|
@@ -1158,10 +1158,113 @@ export async function updateJsonAtomic<T = unknown>(
|
|
|
1158
1158
|
);
|
|
1159
1159
|
}
|
|
1160
1160
|
|
|
1161
|
+
async function assertAppendRegularFile(filePath: string): Promise<void> {
|
|
1162
|
+
try {
|
|
1163
|
+
const stat = await fs.lstat(filePath);
|
|
1164
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("state append path is not a regular file");
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
if (!isErrno(error, "ENOENT")) throw error;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Append bytes the same way `appendAuditEntry` opens its log: mode 0600, `O_NOFOLLOW`
|
|
1172
|
+
* when the path already exists, and `O_EXCL` when it does not. An existing mode 0666
|
|
1173
|
+
* file, and any other group or other permission bits, is tightened to 0600 before the write.
|
|
1174
|
+
*/
|
|
1175
|
+
async function tightenReadOnlyAppendMode(filePath: string): Promise<void> {
|
|
1176
|
+
if (process.platform === "win32") return;
|
|
1177
|
+
const noFollow = fsConstants.O_NOFOLLOW ?? 0;
|
|
1178
|
+
const initialStat = await fs.lstat(filePath, { bigint: true });
|
|
1179
|
+
if (initialStat.isSymbolicLink() || !initialStat.isFile())
|
|
1180
|
+
throw new Error("state append path is not a regular file");
|
|
1181
|
+
// A duplicate idempotent append must not require a writable file. Owner-only
|
|
1182
|
+
// modes such as 0400 stay as they are; only group or other bits are tightened.
|
|
1183
|
+
const handle = await fs.open(filePath, fsConstants.O_RDONLY | noFollow);
|
|
1184
|
+
try {
|
|
1185
|
+
const openedStat = await handle.stat({ bigint: true });
|
|
1186
|
+
const pathStat = await fs.lstat(filePath, { bigint: true });
|
|
1187
|
+
const sameObject = (left: nodeFs.BigIntStats, right: nodeFs.BigIntStats) =>
|
|
1188
|
+
left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink;
|
|
1189
|
+
if (
|
|
1190
|
+
openedStat.isSymbolicLink() ||
|
|
1191
|
+
!openedStat.isFile() ||
|
|
1192
|
+
pathStat.isSymbolicLink() ||
|
|
1193
|
+
!pathStat.isFile() ||
|
|
1194
|
+
!sameObject(initialStat, openedStat) ||
|
|
1195
|
+
!sameObject(openedStat, pathStat)
|
|
1196
|
+
)
|
|
1197
|
+
throw new Error("state append path identity changed before append");
|
|
1198
|
+
if ((Number(openedStat.mode) & 0o077) === 0) return;
|
|
1199
|
+
await handle.chmod(0o600);
|
|
1200
|
+
const tightened = await handle.stat({ bigint: true });
|
|
1201
|
+
if ((Number(tightened.mode) & 0o777) !== 0o600)
|
|
1202
|
+
throw new Error("state append path could not be tightened to mode 0600");
|
|
1203
|
+
} finally {
|
|
1204
|
+
await handle.close();
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
async function appendOwnerOnlyText(filePath: string, text: string): Promise<void> {
|
|
1209
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1210
|
+
await assertAppendRegularFile(filePath);
|
|
1211
|
+
const noFollow = process.platform === "win32" ? 0 : (fsConstants.O_NOFOLLOW ?? 0);
|
|
1212
|
+
let handle: fs.FileHandle | undefined;
|
|
1213
|
+
let initialStat: nodeFs.BigIntStats | undefined;
|
|
1214
|
+
// O_EXCL matches appendAuditEntry for a missing path. Concurrent appends can
|
|
1215
|
+
// create the same file together, so an EEXIST loser retries the existing-file
|
|
1216
|
+
// open, which carries O_NOFOLLOW and will not follow a symlink.
|
|
1217
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1218
|
+
initialStat = undefined;
|
|
1219
|
+
try {
|
|
1220
|
+
initialStat = await fs.lstat(filePath, { bigint: true });
|
|
1221
|
+
if (initialStat.isSymbolicLink() || !initialStat.isFile())
|
|
1222
|
+
throw new Error("state append path is not a regular file");
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
if (!isErrno(error, "ENOENT")) throw error;
|
|
1225
|
+
}
|
|
1226
|
+
const flags = initialStat
|
|
1227
|
+
? fsConstants.O_WRONLY | fsConstants.O_APPEND | noFollow
|
|
1228
|
+
: fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | fsConstants.O_EXCL;
|
|
1229
|
+
try {
|
|
1230
|
+
handle = await fs.open(filePath, flags, 0o600);
|
|
1231
|
+
break;
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
if (isErrno(error, "ELOOP")) throw new Error("state append path is not a regular file");
|
|
1234
|
+
if (initialStat === undefined && isErrno(error, "EEXIST") && attempt === 0) continue;
|
|
1235
|
+
throw error;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (!handle) throw new Error("state append path identity changed before append");
|
|
1239
|
+
try {
|
|
1240
|
+
const openedStat = await handle.stat({ bigint: true });
|
|
1241
|
+
const pathStat = await fs.lstat(filePath, { bigint: true });
|
|
1242
|
+
const sameObject = (left: nodeFs.BigIntStats, right: nodeFs.BigIntStats) =>
|
|
1243
|
+
left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink;
|
|
1244
|
+
if (
|
|
1245
|
+
openedStat.isSymbolicLink() ||
|
|
1246
|
+
!openedStat.isFile() ||
|
|
1247
|
+
pathStat.isSymbolicLink() ||
|
|
1248
|
+
!pathStat.isFile() ||
|
|
1249
|
+
(initialStat !== undefined && !sameObject(initialStat, openedStat)) ||
|
|
1250
|
+
!sameObject(openedStat, pathStat)
|
|
1251
|
+
)
|
|
1252
|
+
throw new Error("state append path identity changed before append");
|
|
1253
|
+
if (process.platform !== "win32" && (Number(openedStat.mode) & 0o077) !== 0) {
|
|
1254
|
+
await handle.chmod(0o600);
|
|
1255
|
+
const tightened = await handle.stat({ bigint: true });
|
|
1256
|
+
if ((Number(tightened.mode) & 0o777) !== 0o600)
|
|
1257
|
+
throw new Error("state append path could not be tightened to mode 0600");
|
|
1258
|
+
}
|
|
1259
|
+
await handle.writeFile(text, "utf-8");
|
|
1260
|
+
} finally {
|
|
1261
|
+
await handle?.close();
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1161
1265
|
export async function appendJsonl(targetPath: string, entry: unknown, options?: StateWriterOptions): Promise<string> {
|
|
1162
1266
|
const filePath = resolveGjcTarget(targetPath, cwdForOptions(options));
|
|
1163
|
-
await
|
|
1164
|
-
await fs.appendFile(filePath, `${JSON.stringify(entry)}\n`, "utf-8");
|
|
1267
|
+
await appendOwnerOnlyText(filePath, `${JSON.stringify(entry)}\n`);
|
|
1165
1268
|
await maybeAudit(filePath, options);
|
|
1166
1269
|
return filePath;
|
|
1167
1270
|
}
|
|
@@ -1259,12 +1362,14 @@ export async function appendJsonlIdempotent(
|
|
|
1259
1362
|
return lockResolvedWorkflowTarget(
|
|
1260
1363
|
filePath,
|
|
1261
1364
|
async () => {
|
|
1365
|
+
await assertAppendRegularFile(filePath);
|
|
1262
1366
|
const existing = await readJsonlEntries(filePath);
|
|
1263
1367
|
const duplicate = findJsonlDuplicate(existing, entry, options);
|
|
1264
1368
|
if (duplicate !== undefined) {
|
|
1369
|
+
await tightenReadOnlyAppendMode(filePath);
|
|
1265
1370
|
return { path: filePath, appended: false, duplicate };
|
|
1266
1371
|
}
|
|
1267
|
-
await
|
|
1372
|
+
await appendOwnerOnlyText(filePath, `${JSON.stringify(entry)}\n`);
|
|
1268
1373
|
await maybeAudit(filePath, options);
|
|
1269
1374
|
return { path: filePath, appended: true };
|
|
1270
1375
|
},
|
|
@@ -1274,8 +1379,7 @@ export async function appendJsonlIdempotent(
|
|
|
1274
1379
|
|
|
1275
1380
|
export async function appendText(targetPath: string, text: string, options?: StateWriterOptions): Promise<string> {
|
|
1276
1381
|
const filePath = resolveGjcTarget(targetPath, cwdForOptions(options));
|
|
1277
|
-
await
|
|
1278
|
-
await fs.appendFile(filePath, text, "utf-8");
|
|
1382
|
+
await appendOwnerOnlyText(filePath, text);
|
|
1279
1383
|
await maybeAudit(filePath, options);
|
|
1280
1384
|
return filePath;
|
|
1281
1385
|
}
|
|
@@ -4,6 +4,8 @@ import * as fsSync from "node:fs";
|
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import type { Process } from "@gajae-code/natives";
|
|
7
|
+
import { startupProjectEnvSnapshot } from "@gajae-code/utils/env";
|
|
8
|
+
import { canonicalEnvKey, type ProjectEnvSnapshot, projectEnvSnapshot } from "@gajae-code/utils/env-file";
|
|
7
9
|
import { nativeProcessBindings } from "@gajae-code/utils/native-process";
|
|
8
10
|
import { managedSecurityFailureClassification } from "../session/internal/managed-session-storage";
|
|
9
11
|
import { readLinuxProcStartTime, readLinuxProcStartTimeSync } from "./linux-proc";
|
|
@@ -23,6 +25,7 @@ import {
|
|
|
23
25
|
GJC_TMUX_OWNER_SERVER_KEY_ENV,
|
|
24
26
|
GJC_TMUX_OWNER_STATE_DIR_ENV,
|
|
25
27
|
} from "./session-state-sidecar";
|
|
28
|
+
import { trustedCoordinatorEnv } from "./sidecar-bootstrap-env";
|
|
26
29
|
import {
|
|
27
30
|
assertGjcTmuxMutationAuthoritySync,
|
|
28
31
|
bindGjcTmuxProviderAuthority,
|
|
@@ -247,6 +250,81 @@ export function __setMutationServerProofForTests(
|
|
|
247
250
|
mutationServerProofTestDependency = dependency;
|
|
248
251
|
}
|
|
249
252
|
|
|
253
|
+
const TMUX_LAUNCH_IDENTITY_ENV_NAMES = [
|
|
254
|
+
"GJC_SESSION_ID",
|
|
255
|
+
GJC_COORDINATOR_SESSION_ID_ENV,
|
|
256
|
+
GJC_COORDINATOR_SESSION_STATE_FILE_ENV,
|
|
257
|
+
"GJC_TMUX_SESSION",
|
|
258
|
+
] as const;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Drop project-dotenv copies of the tmux session name, coordinator id, and
|
|
262
|
+
* state-file path. A pin is kept only when every snapshot accepts it, so a
|
|
263
|
+
* declaration Bun loaded before `chdir` is refused even if the launch cwd no
|
|
264
|
+
* longer contains that file. Acceptance uses the shared helper, but the
|
|
265
|
+
* original string stays: callers trim at use, and a later check must see the
|
|
266
|
+
* same value that was accepted.
|
|
267
|
+
*/
|
|
268
|
+
export function trustedTmuxLaunchIdentityEnv(
|
|
269
|
+
env: NodeJS.ProcessEnv,
|
|
270
|
+
snapshots: readonly ProjectEnvSnapshot[],
|
|
271
|
+
): NodeJS.ProcessEnv {
|
|
272
|
+
const denied: string[] = [];
|
|
273
|
+
for (const name of TMUX_LAUNCH_IDENTITY_ENV_NAMES) {
|
|
274
|
+
if (!env[name]) continue;
|
|
275
|
+
for (const snapshot of snapshots) {
|
|
276
|
+
if (trustedCoordinatorEnv(name, env, snapshot) === undefined) {
|
|
277
|
+
denied.push(name);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (denied.length === 0) return env;
|
|
283
|
+
const next: NodeJS.ProcessEnv = { ...env };
|
|
284
|
+
for (const name of denied) delete next[name];
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function tmuxLaunchIdentityEnv(env: NodeJS.ProcessEnv, cwd: string): NodeJS.ProcessEnv {
|
|
289
|
+
return trustedTmuxLaunchIdentityEnv(env, [projectEnvSnapshot(cwd), startupProjectEnvSnapshot()]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Session id the launched child must see. A trusted pin is passed through.
|
|
294
|
+
* A rejected project pin is replaced by the same fallback the launch plan uses
|
|
295
|
+
* for the session root, which also stops Bun from reloading that dotenv value
|
|
296
|
+
* (`GJC_TMUX_LAUNCHED=1` skips the planner filter). An omitted id stays omitted.
|
|
297
|
+
*/
|
|
298
|
+
export function resolvedChildGjcSessionId(input: {
|
|
299
|
+
trusted: string | undefined;
|
|
300
|
+
inherited: string | undefined;
|
|
301
|
+
projectDeclared: boolean;
|
|
302
|
+
fallback: string;
|
|
303
|
+
}): string | undefined {
|
|
304
|
+
const trusted = input.trusted?.trim();
|
|
305
|
+
if (trusted) return trusted;
|
|
306
|
+
if (!input.inherited?.trim() && !input.projectDeclared) return undefined;
|
|
307
|
+
const fallback = input.fallback.trim();
|
|
308
|
+
return fallback.length > 0 ? fallback : undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function childGjcSessionIdAssignment(
|
|
312
|
+
env: NodeJS.ProcessEnv,
|
|
313
|
+
identityEnv: NodeJS.ProcessEnv,
|
|
314
|
+
cwd: string,
|
|
315
|
+
fallbackSessionId: string,
|
|
316
|
+
): Record<string, string> {
|
|
317
|
+
const snapshot = projectEnvSnapshot(cwd);
|
|
318
|
+
const key = canonicalEnvKey("GJC_SESSION_ID");
|
|
319
|
+
const value = resolvedChildGjcSessionId({
|
|
320
|
+
trusted: identityEnv.GJC_SESSION_ID,
|
|
321
|
+
inherited: env.GJC_SESSION_ID,
|
|
322
|
+
projectDeclared: snapshot.values[key] !== undefined || snapshot.dynamic.has(key),
|
|
323
|
+
fallback: fallbackSessionId,
|
|
324
|
+
});
|
|
325
|
+
return value === undefined ? {} : { GJC_SESSION_ID: value };
|
|
326
|
+
}
|
|
327
|
+
|
|
250
328
|
function psmuxAuthorityFromEnv(env: NodeJS.ProcessEnv): ProviderAuthority | null {
|
|
251
329
|
const stateDir = env[GJC_TMUX_OWNER_STATE_DIR_ENV]?.trim();
|
|
252
330
|
const sessionId = env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim();
|
|
@@ -515,11 +593,14 @@ function canonicalProviderStateDirs(cwd: string): string[] {
|
|
|
515
593
|
}
|
|
516
594
|
|
|
517
595
|
function psmuxAuthorityEnvironments(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv[] {
|
|
518
|
-
const
|
|
596
|
+
const identityEnv = tmuxLaunchIdentityEnv(env, process.cwd());
|
|
597
|
+
const explicitAuthority = psmuxAuthorityFromEnv(identityEnv);
|
|
519
598
|
if (explicitAuthority) return [environmentForProviderAuthority(env, explicitAuthority)];
|
|
520
599
|
const explicitStateDir =
|
|
521
|
-
|
|
522
|
-
(
|
|
600
|
+
identityEnv[GJC_TMUX_OWNER_STATE_DIR_ENV]?.trim() ??
|
|
601
|
+
(identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]
|
|
602
|
+
? path.dirname(identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV])
|
|
603
|
+
: "");
|
|
523
604
|
const ambient = resolveGjcTmuxBinary({ env });
|
|
524
605
|
if (gjcTmuxAuthorityPlatform() === "win32") {
|
|
525
606
|
const ambientAvailable = path.isAbsolute(ambient.command)
|
|
@@ -669,17 +750,15 @@ export function createGjcTmuxSession(
|
|
|
669
750
|
if (launch && !validManagedLaunchSpec(launch, platform)) throw new Error("gjc_tmux_managed_launch_spec_invalid");
|
|
670
751
|
const provider = resolveGjcTmuxProviderContext({ env, platform });
|
|
671
752
|
const tmuxCommand = provider.command;
|
|
672
|
-
const launchEnvironment = launch ? { ...env, GJC_TMUX_SESSION: launch.childSessionId } : env;
|
|
673
|
-
const sessionName = buildGjcTmuxSessionName(launchEnvironment);
|
|
674
753
|
const cwd = launch?.cwd ?? process.cwd();
|
|
675
|
-
const
|
|
754
|
+
const identityEnv = tmuxLaunchIdentityEnv(env, cwd);
|
|
755
|
+
const launchEnvironment = launch ? { ...identityEnv, GJC_TMUX_SESSION: launch.childSessionId } : identityEnv;
|
|
756
|
+
const sessionName = buildGjcTmuxSessionName(launchEnvironment);
|
|
757
|
+
const sessionId = launch?.childSessionId ?? (identityEnv[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || sessionName);
|
|
758
|
+
const filteredGjcSessionId = identityEnv.GJC_SESSION_ID?.trim() || sessionId;
|
|
676
759
|
const stateFile =
|
|
677
|
-
(!launch ?
|
|
678
|
-
tmuxRuntimeSessionPath(
|
|
679
|
-
cwd,
|
|
680
|
-
launch?.childSessionId ?? (env.GJC_SESSION_ID?.trim() || sessionId),
|
|
681
|
-
buildGjcTmuxSessionSlug(sessionName),
|
|
682
|
-
);
|
|
760
|
+
(!launch ? identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim() : undefined) ||
|
|
761
|
+
tmuxRuntimeSessionPath(cwd, launch?.childSessionId ?? filteredGjcSessionId, buildGjcTmuxSessionSlug(sessionName));
|
|
683
762
|
const stateDir = (platform === "win32" ? path.win32 : path).dirname(stateFile);
|
|
684
763
|
const generation = crypto.randomUUID();
|
|
685
764
|
const authority = bindGjcTmuxProviderAuthority(provider, { stateDir, sessionId, generation });
|
|
@@ -690,6 +769,7 @@ export function createGjcTmuxSession(
|
|
|
690
769
|
[GJC_TMUX_OWNER_SERVER_KEY_ENV]: tmuxCommand,
|
|
691
770
|
[GJC_COORDINATOR_SESSION_ID_ENV]: sessionId,
|
|
692
771
|
[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: stateFile,
|
|
772
|
+
...childGjcSessionIdAssignment(env, identityEnv, cwd, filteredGjcSessionId),
|
|
693
773
|
...(launch
|
|
694
774
|
? {
|
|
695
775
|
[MANAGED_OWNER_RUN_ID_ENV]: crypto.randomUUID(),
|
|
@@ -699,7 +779,7 @@ export function createGjcTmuxSession(
|
|
|
699
779
|
: {}),
|
|
700
780
|
};
|
|
701
781
|
const childEnvironment: Record<string, string> = { ...(launch?.env ?? {}), ...managedEnvironment };
|
|
702
|
-
const executionEnv = { ...
|
|
782
|
+
const executionEnv = { ...identityEnv, ...managedEnvironment };
|
|
703
783
|
const shellQuote = (value: string): string => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
704
784
|
const command =
|
|
705
785
|
platform === "win32"
|
|
@@ -128,15 +128,30 @@ async function hashAddedFiles(cwd: string, paths: readonly UltragoalChangeSetPat
|
|
|
128
128
|
|
|
129
129
|
export async function spawnText(
|
|
130
130
|
command: string[],
|
|
131
|
-
options: { cwd: string; timeoutMs?: number },
|
|
131
|
+
options: { cwd: string; timeoutMs?: number; stdin?: string },
|
|
132
132
|
): Promise<{ ok: boolean; stdout: string; stderr: string }> {
|
|
133
133
|
try {
|
|
134
|
-
const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
|
|
134
|
+
const proc = Bun.spawn(command, { cwd: options.cwd, stdin: "pipe", stdout: "pipe", stderr: "pipe" });
|
|
135
135
|
const timeout = setTimeout(() => proc.kill(), options.timeoutMs ?? 5000);
|
|
136
|
+
// Drain both pipes while the stdin write runs: a child that never reads
|
|
137
|
+
// stdin blocks the write, and a child that fills stdout before draining
|
|
138
|
+
// stdin blocks the read.
|
|
139
|
+
const stdinWrite = (async () => {
|
|
140
|
+
const stdin = proc.stdin;
|
|
141
|
+
if (!stdin || options.stdin === undefined) return;
|
|
142
|
+
try {
|
|
143
|
+
stdin.write(options.stdin);
|
|
144
|
+
await stdin.end();
|
|
145
|
+
} catch {
|
|
146
|
+
// The child closed stdin or the timer killed it; the exit code
|
|
147
|
+
// already reports the failure.
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
136
150
|
const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
|
|
137
151
|
new Response(proc.stdout).arrayBuffer(),
|
|
138
152
|
new Response(proc.stderr).arrayBuffer(),
|
|
139
153
|
proc.exited,
|
|
154
|
+
stdinWrite,
|
|
140
155
|
]);
|
|
141
156
|
clearTimeout(timeout);
|
|
142
157
|
let stdout: string;
|
|
@@ -152,6 +167,37 @@ export async function spawnText(
|
|
|
152
167
|
}
|
|
153
168
|
}
|
|
154
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Derive the empty tree object ID for the given repository, which depends on the
|
|
172
|
+
* repository's object format (SHA-1 vs SHA-256). A repository with no commits has
|
|
173
|
+
* no refs and no merge base, so the empty tree is the only authoritative baseline:
|
|
174
|
+
* every tracked, staged, and untracked file is an addition on top of it.
|
|
175
|
+
*
|
|
176
|
+
* @param cwd The repository root directory
|
|
177
|
+
* @returns The empty tree object ID (format-specific), or the SHA-1 ID if derivation fails
|
|
178
|
+
*/
|
|
179
|
+
async function getEmptyTreeId(cwd: string): Promise<string> {
|
|
180
|
+
// Compute the empty tree object ID by hashing an empty tree.
|
|
181
|
+
// This respects the repository's object format (SHA-1 or SHA-256).
|
|
182
|
+
const result = await spawnText(["git", "hash-object", "-t", "tree", "--stdin"], {
|
|
183
|
+
cwd,
|
|
184
|
+
timeoutMs: 3000,
|
|
185
|
+
stdin: "", // Empty input for empty tree
|
|
186
|
+
});
|
|
187
|
+
if (result.ok && result.stdout.trim()) {
|
|
188
|
+
return result.stdout.trim();
|
|
189
|
+
}
|
|
190
|
+
// Fallback to SHA-1 empty tree ID if derivation fails
|
|
191
|
+
// (e.g., git command not available, permission denied, etc.)
|
|
192
|
+
return "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The canonical empty tree object for SHA-1 repositories. Use getEmptyTreeId()
|
|
197
|
+
* to derive the format-specific ID for a given repository.
|
|
198
|
+
*/
|
|
199
|
+
export const GIT_EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
200
|
+
|
|
155
201
|
export async function resolveGitBase(cwd: string, branch?: string): Promise<string> {
|
|
156
202
|
if (branch) {
|
|
157
203
|
const exists = await spawnText(["git", "rev-parse", "--verify", branch], { cwd, timeoutMs: 3000 });
|
|
@@ -163,11 +209,31 @@ export async function resolveGitBase(cwd: string, branch?: string): Promise<stri
|
|
|
163
209
|
// and mis-attributes other people's changes to this story (e.g. falsely
|
|
164
210
|
// tripping change-scoped gates). Among existing candidates, pick the one
|
|
165
211
|
// whose merge-base with HEAD is closest to HEAD (fewest commits ahead).
|
|
166
|
-
const candidates = [
|
|
212
|
+
const candidates = [
|
|
213
|
+
"origin/dev",
|
|
214
|
+
"dev",
|
|
215
|
+
"origin/develop",
|
|
216
|
+
"develop",
|
|
217
|
+
"origin/main",
|
|
218
|
+
"origin/master",
|
|
219
|
+
"main",
|
|
220
|
+
"master",
|
|
221
|
+
];
|
|
222
|
+
const currentBranch = await spawnText(["git", "symbolic-ref", "--quiet", "HEAD"], { cwd, timeoutMs: 3000 });
|
|
223
|
+
const currentRef = currentBranch.ok ? currentBranch.stdout.trim() : undefined;
|
|
167
224
|
let best: { ref: string; ahead: number } | undefined;
|
|
225
|
+
|
|
168
226
|
for (const candidate of candidates) {
|
|
169
|
-
const exists = await spawnText(["git", "rev-parse", "--verify", candidate], {
|
|
227
|
+
const exists = await spawnText(["git", "rev-parse", "--symbolic-full-name", "--verify", candidate], {
|
|
228
|
+
cwd,
|
|
229
|
+
timeoutMs: 3000,
|
|
230
|
+
});
|
|
170
231
|
if (!exists.ok) continue;
|
|
232
|
+
// Exclude only the checked-out local ref, not distinct refs at the same
|
|
233
|
+
// commit or remote-tracking refs with the same branch name. Comparing
|
|
234
|
+
// the checked-out ref to HEAD hides committed work as a complete empty
|
|
235
|
+
// capture; without an independent baseline, resolution must fail closed.
|
|
236
|
+
if (currentRef && exists.stdout.trim() === currentRef) continue;
|
|
171
237
|
const mergeBase = await spawnText(["git", "merge-base", "HEAD", candidate], { cwd, timeoutMs: 3000 });
|
|
172
238
|
if (!mergeBase.ok || !mergeBase.stdout.trim()) continue;
|
|
173
239
|
const count = await spawnText(["git", "rev-list", "--count", `${mergeBase.stdout.trim()}..HEAD`], {
|
|
@@ -285,16 +351,26 @@ export async function computeCheckpointChangeSet(cwd: string): Promise<Ultragoal
|
|
|
285
351
|
}
|
|
286
352
|
const captureWitness = await repositoryStateWitness(cwd);
|
|
287
353
|
await repositoryStateWitnessTestHook?.("after-initial", cwd);
|
|
288
|
-
|
|
354
|
+
// A freshly initialized repository has no commits, so `HEAD`, every branch
|
|
355
|
+
// candidate, and any merge base are unresolvable. The empty tree is the only
|
|
356
|
+
// authoritative baseline there; refusing outright would strand a run whose
|
|
357
|
+
// implementation is entirely untracked. Everything on disk then counts as an
|
|
358
|
+
// addition, which is hashed like any other added file and never committed.
|
|
359
|
+
const headPresent = await spawnText(["git", "rev-parse", "--verify", "HEAD"], { cwd, timeoutMs: 3000 });
|
|
360
|
+
const unborn = !headPresent.ok;
|
|
361
|
+
const baseRef = unborn ? await getEmptyTreeId(cwd) : await resolveGitBase(cwd);
|
|
289
362
|
const base = baseRef;
|
|
290
|
-
const mergeBase =
|
|
363
|
+
const mergeBase = unborn
|
|
364
|
+
? { ok: false, stdout: "", stderr: "repository has no commits" }
|
|
365
|
+
: await spawnText(["git", "merge-base", "HEAD", baseRef], { cwd, timeoutMs: 3000 });
|
|
366
|
+
const diffRange = unborn ? baseRef : `${base}...HEAD`;
|
|
291
367
|
const [committed, unstaged, staged, untracked, stat, committedDiff, unstagedDiff, stagedDiff] = await Promise.all([
|
|
292
|
-
spawnText(["git", "diff", "--name-status", "-z",
|
|
368
|
+
spawnText(["git", "diff", "--name-status", "-z", unborn ? baseRef : diffRange], { cwd, timeoutMs: 5000 }),
|
|
293
369
|
spawnText(["git", "diff", "--name-status", "-z"], { cwd, timeoutMs: 5000 }),
|
|
294
370
|
spawnText(["git", "diff", "--cached", "--name-status", "-z"], { cwd, timeoutMs: 5000 }),
|
|
295
371
|
spawnText(["git", "ls-files", "--others", "--exclude-standard", "-z"], { cwd, timeoutMs: 5000 }),
|
|
296
|
-
spawnText(["git", "diff", "--stat",
|
|
297
|
-
spawnText(["git", "diff",
|
|
372
|
+
spawnText(["git", "diff", "--stat", diffRange], { cwd, timeoutMs: 5000 }),
|
|
373
|
+
spawnText(["git", "diff", diffRange], { cwd, timeoutMs: 5000 }),
|
|
298
374
|
spawnText(["git", "diff"], { cwd, timeoutMs: 5000 }),
|
|
299
375
|
spawnText(["git", "diff", "--cached"], { cwd, timeoutMs: 5000 }),
|
|
300
376
|
]);
|
|
@@ -382,7 +458,10 @@ async function repositoryStateWitness(cwd: string): Promise<string | undefined>
|
|
|
382
458
|
spawnText(["git", "ls-files", "-z"], { cwd, timeoutMs: 5000 }),
|
|
383
459
|
spawnText(["git", "ls-files", "--others", "--exclude-standard", "-z"], { cwd, timeoutMs: 5000 }),
|
|
384
460
|
]);
|
|
385
|
-
|
|
461
|
+
// An unborn repository has no HEAD yet; the remaining reads still describe its
|
|
462
|
+
// full state, so witness it as an empty head rather than failing the capture.
|
|
463
|
+
if (!status.ok || !tracked.ok || !untracked.ok) return undefined;
|
|
464
|
+
const headToken = head.ok ? head.stdout.trim() : "unborn";
|
|
386
465
|
const paths = [
|
|
387
466
|
...new Set([
|
|
388
467
|
...parseGitUntrackedPaths(tracked.stdout).map(row => row.path),
|
|
@@ -417,7 +496,7 @@ async function repositoryStateWitness(cwd: string): Promise<string | undefined>
|
|
|
417
496
|
} catch {
|
|
418
497
|
return undefined;
|
|
419
498
|
}
|
|
420
|
-
return `${
|
|
499
|
+
return `${headToken}\u0000${status.stdout}\u0000sha256:${content.digest("hex")}`;
|
|
421
500
|
}
|
|
422
501
|
|
|
423
502
|
export function parseUnifiedDiffPaths(diff: string): UltragoalChangeSetPath[] {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type UltragoalPlan,
|
|
26
26
|
type UltragoalReceiptKind,
|
|
27
27
|
} from "./ultragoal-runtime";
|
|
28
|
+
import { readUltragoalSuccessionFence } from "./ultragoal-succession";
|
|
28
29
|
|
|
29
30
|
export type UltragoalGuardState =
|
|
30
31
|
| "inactive"
|
|
@@ -823,7 +824,14 @@ async function consumeUltragoalNudge(input: {
|
|
|
823
824
|
if (!sessionId) return { nudged: false };
|
|
824
825
|
const plan = await readUltragoalPlan(input.cwd, sessionId);
|
|
825
826
|
if (!plan) return { nudged: false };
|
|
826
|
-
const
|
|
827
|
+
const fence = await readUltragoalSuccessionFence(input.cwd, sessionId);
|
|
828
|
+
// A goal handed off to a successor run is not this run's resolvable work. Skip
|
|
829
|
+
// fenced goals while selecting, so a retained source-owned goal can still receive
|
|
830
|
+
// the nudge (and keep aggregate drop guarded) without spending budget on the handoff.
|
|
831
|
+
const target = selectUltragoalNudgeTarget(plan, {
|
|
832
|
+
currentGoalObjective: input.currentGoal?.objective,
|
|
833
|
+
excludedGoalIds: fence?.selectedGoalIds,
|
|
834
|
+
});
|
|
827
835
|
if (!target) return { nudged: false };
|
|
828
836
|
const { budget } = await resolveUltragoalNudgeBudget(input.cwd, input.agentDir);
|
|
829
837
|
const outcome = await recordUltragoalNudgeIfBudgetRemaining({
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
import {
|
|
83
83
|
assertUltragoalAdoptionPublished,
|
|
84
84
|
assertUltragoalGoalNotFenced,
|
|
85
|
+
readUltragoalSuccessionFence,
|
|
85
86
|
runUltragoalSuccessionCommand,
|
|
86
87
|
} from "./ultragoal-succession";
|
|
87
88
|
import {
|
|
@@ -472,22 +473,27 @@ export async function resolveUltragoalNudgeBudget(
|
|
|
472
473
|
*/
|
|
473
474
|
export function selectUltragoalNudgeTarget(
|
|
474
475
|
plan: UltragoalPlan,
|
|
475
|
-
options: { currentGoalObjective?: string; retryFailed?: boolean } = {},
|
|
476
|
+
options: { currentGoalObjective?: string; retryFailed?: boolean; excludedGoalIds?: readonly string[] } = {},
|
|
476
477
|
): UltragoalNudgeTarget | null {
|
|
477
478
|
const objective = options.currentGoalObjective?.trim();
|
|
479
|
+
const isExcluded = (goalId: string): boolean => options.excludedGoalIds?.includes(goalId) ?? false;
|
|
478
480
|
if (objective) {
|
|
479
481
|
const matched = plan.goals.find(
|
|
480
|
-
goal => goal.objective.trim() === objective && SCHEDULABLE_STATUSES.has(goal.status),
|
|
482
|
+
goal => goal.objective.trim() === objective && SCHEDULABLE_STATUSES.has(goal.status) && !isExcluded(goal.id),
|
|
481
483
|
);
|
|
482
484
|
if (matched) return { goalId: matched.id, targetKind: "story" };
|
|
483
485
|
}
|
|
484
|
-
const
|
|
486
|
+
const schedulableGoals = options.excludedGoalIds?.length
|
|
487
|
+
? plan.goals.filter(goal => !isExcluded(goal.id))
|
|
488
|
+
: plan.goals;
|
|
489
|
+
const next = selectNextSchedulableGoal(schedulableGoals, options.retryFailed === true);
|
|
485
490
|
if (next) return { goalId: next.id, targetKind: "story" };
|
|
486
491
|
const completion = getUltragoalRunCompletionState(plan, { retryFailed: options.retryFailed });
|
|
487
492
|
if (completion.needsFinalAggregateReceipt) {
|
|
488
493
|
const required = requiredUltragoalGoals(plan);
|
|
489
494
|
const finalGoal = required.at(-1);
|
|
490
|
-
if (finalGoal
|
|
495
|
+
if (finalGoal && !isExcluded(finalGoal.id))
|
|
496
|
+
return { goalId: finalGoal.id, targetKind: "final_aggregate_receipt" };
|
|
491
497
|
}
|
|
492
498
|
return null;
|
|
493
499
|
}
|
|
@@ -1125,7 +1131,11 @@ export async function getUltragoalStatus(
|
|
|
1125
1131
|
else if (counts.active > 0) status = "active";
|
|
1126
1132
|
else if (counts.failed > 0) status = "failed";
|
|
1127
1133
|
else if (counts.blocked > 0 || counts.review_blocked > 0) status = "blocked";
|
|
1128
|
-
const
|
|
1134
|
+
const fence = await readUltragoalSuccessionFence(cwd, resolvedSessionId);
|
|
1135
|
+
const nudgeTarget = selectUltragoalNudgeTarget(plan, {
|
|
1136
|
+
currentGoalObjective: currentGoal?.objective,
|
|
1137
|
+
excludedGoalIds: fence?.selectedGoalIds,
|
|
1138
|
+
});
|
|
1129
1139
|
let nudgeFields: Partial<UltragoalStatusSummary> = {};
|
|
1130
1140
|
if (nudgeTarget) {
|
|
1131
1141
|
const { budget } = await resolveUltragoalNudgeBudget(cwd, agentDir);
|
|
@@ -14,6 +14,7 @@ import { randomBytes } from "node:crypto";
|
|
|
14
14
|
import { readdirSync, readFileSync } from "node:fs";
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
16
16
|
import { type LinuxProcPidProbeResult, probeLinuxProcPidSync } from "../gjc-runtime/linux-proc";
|
|
17
|
+
import { checkTmuxSelfInjection } from "../tools/tmux-self-injection-guard";
|
|
17
18
|
import {
|
|
18
19
|
buildReceipt,
|
|
19
20
|
type CompletionEvidence,
|
|
@@ -558,6 +559,16 @@ export function defaultFinalizeChecks(
|
|
|
558
559
|
return {
|
|
559
560
|
async runValidation(spec, signal) {
|
|
560
561
|
if (signal?.aborted) throw new ValidationObservationUncertainError(spec.command, workspace, signal.reason);
|
|
562
|
+
// Same preflight as BashTool: refuse before spawn so a plan command cannot type into this pane.
|
|
563
|
+
const injection = await checkTmuxSelfInjection(spec.command, {
|
|
564
|
+
env: process.env,
|
|
565
|
+
cwd: workspace,
|
|
566
|
+
executionEnv: process.env,
|
|
567
|
+
});
|
|
568
|
+
if (signal?.aborted) throw new ValidationObservationUncertainError(spec.command, workspace, signal.reason);
|
|
569
|
+
if (injection.block) {
|
|
570
|
+
return { exactCommand: spec.command, cwd: workspace, exitStatus: 1, pass: false };
|
|
571
|
+
}
|
|
561
572
|
let proc: Bun.Subprocess<"ignore", "pipe", "pipe">;
|
|
562
573
|
try {
|
|
563
574
|
proc = Bun.spawn(["bash", "-lc", spec.command], {
|