@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
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as childProcess from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as process from "node:process";
|
|
5
|
+
import type * as stream from "node:stream";
|
|
6
|
+
import type { Process } from "@gajae-code/natives";
|
|
7
|
+
import { nativeProcessBindings } from "@gajae-code/utils/native-process";
|
|
8
|
+
import type { BrokerHopMessage } from "./ensure";
|
|
9
|
+
|
|
10
|
+
/** Handoff protocol frames written to the trampoline's stdin. */
|
|
11
|
+
export const BROKER_HANDOFF_ACKNOWLEDGEMENT = "GJC_BROKER_HANDOFF_ACK\n";
|
|
12
|
+
export const BROKER_HANDOFF_COMMIT = "GJC_BROKER_HANDOFF_COMMIT\n";
|
|
13
|
+
export const BROKER_HANDOFF_ABORT = "GJC_BROKER_HANDOFF_ABORT\n";
|
|
14
|
+
/**
|
|
15
|
+
* Backstop for a retained trampoline whose parent never sends a decision. It must
|
|
16
|
+
* outlive the parent's own broker discovery deadline (which is derived from the
|
|
17
|
+
* publication budget and can double across a guarded retry), otherwise the
|
|
18
|
+
* trampoline reaps the broker first and the parent misreports its own discovery
|
|
19
|
+
* timeout as an "exited before discovery" startup failure.
|
|
20
|
+
*/
|
|
21
|
+
export const BROKER_HANDOFF_DECISION_TIMEOUT_MS = 120_000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Windows broker hop: spawns the real broker with detached:true and reports its pid.
|
|
25
|
+
*
|
|
26
|
+
* The hop is needed because a broker spawned with detached:true IS still killed when the
|
|
27
|
+
* parent process tree is terminated (e.g., by taskkill /T /F on Windows). Using an
|
|
28
|
+
* intermediate hop process that exits after spawning the real broker allows the broker
|
|
29
|
+
* to survive, because the hop's parent (the initiating client) can be killed without
|
|
30
|
+
* affecting the broker (the hop's pid is no longer valid).
|
|
31
|
+
*
|
|
32
|
+
* Invoked by gjc internals only. Usage:
|
|
33
|
+
* gjc internal broker-hop <json-encoded-args>
|
|
34
|
+
*
|
|
35
|
+
* Exits with code 0 after writing the broker pid/incarnation to stdout and
|
|
36
|
+
* receiving the parent's acknowledgement. If stdin closes first, it terminates
|
|
37
|
+
* the pinned broker and exits with code 1.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export async function runBrokerHopFromArgv(argv: string[]): Promise<void> {
|
|
41
|
+
if (argv.length !== 1) fail("broker hop requires exactly one argument");
|
|
42
|
+
let message: BrokerHopMessage;
|
|
43
|
+
try {
|
|
44
|
+
message = JSON.parse(argv[0]) as BrokerHopMessage;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
fail(`broker hop JSON parse error: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
if (!message.command?.file || !Array.isArray(message.command.args)) fail("broker hop message missing command");
|
|
49
|
+
try {
|
|
50
|
+
// stderr is opened here by path: a parent fd number is meaningless in this process.
|
|
51
|
+
const stderr = message.stderrLogPath ? fs.openSync(message.stderrLogPath, "a") : "ignore";
|
|
52
|
+
// The broker inherits this process's environment, which the parent set to the
|
|
53
|
+
// broker environment; it is never carried on the command line.
|
|
54
|
+
const child = childProcess.spawn(message.command.file, message.command.args, {
|
|
55
|
+
detached: true,
|
|
56
|
+
windowsHide: true,
|
|
57
|
+
stdio: ["ignore", "ignore", stderr],
|
|
58
|
+
env: process.env,
|
|
59
|
+
...(message.cwd ? { cwd: message.cwd } : {}),
|
|
60
|
+
});
|
|
61
|
+
if (typeof stderr === "number") fs.closeSync(stderr);
|
|
62
|
+
const spawned = Promise.withResolvers<void>();
|
|
63
|
+
child.once("spawn", spawned.resolve);
|
|
64
|
+
child.once("error", spawned.reject);
|
|
65
|
+
await spawned.promise;
|
|
66
|
+
const pid = child.pid;
|
|
67
|
+
if (pid === undefined) fail("broker hop spawn succeeded but child pid unavailable");
|
|
68
|
+
const childReference = getSpawnedChildReference(pid);
|
|
69
|
+
if (!childReference) {
|
|
70
|
+
const cleanup = await terminateUnverifiableWindowsChild(child);
|
|
71
|
+
fail(`broker process identity could not be bound to the spawned child; ${cleanup}`);
|
|
72
|
+
}
|
|
73
|
+
const handoff = await writeBrokerHopReply(child, childReference, pid, writeBrokerHopStdout, () =>
|
|
74
|
+
waitForBrokerHandoffAcknowledgement(process.stdin),
|
|
75
|
+
);
|
|
76
|
+
if (handoff.kind === "failed") fail(handoff.reason);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
fail(`broker hop spawn failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type BrokerHopWriteCallback = (error?: Error | null) => void;
|
|
84
|
+
type BrokerHopWriter = (chunk: string, callback: BrokerHopWriteCallback) => void;
|
|
85
|
+
type BrokerHopAcknowledger = () => Promise<boolean>;
|
|
86
|
+
|
|
87
|
+
async function writeBrokerHopReply(
|
|
88
|
+
child: childProcess.ChildProcess,
|
|
89
|
+
reference: Process,
|
|
90
|
+
pid: number,
|
|
91
|
+
write: BrokerHopWriter,
|
|
92
|
+
waitForAcknowledgement: BrokerHopAcknowledger,
|
|
93
|
+
): Promise<{ kind: "written" } | { kind: "failed"; reason: string }> {
|
|
94
|
+
const completion = Promise.withResolvers<void>();
|
|
95
|
+
const acknowledgementPromise = waitForAcknowledgement();
|
|
96
|
+
let settled = false;
|
|
97
|
+
const settle = (error?: Error | null): void => {
|
|
98
|
+
if (settled) return;
|
|
99
|
+
settled = true;
|
|
100
|
+
if (error) completion.reject(error);
|
|
101
|
+
else completion.resolve();
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
write(`${JSON.stringify({ pid, incarnation: reference.incarnation })}\n`, settle);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
settle(error instanceof Error ? error : new Error(String(error)));
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await completion.promise;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const writeError = error instanceof Error ? error : new Error(String(error));
|
|
112
|
+
const cleanup = await terminateBrokerAfterFailedHandoff(reference);
|
|
113
|
+
return {
|
|
114
|
+
kind: "failed",
|
|
115
|
+
reason: `broker hop handoff failed for pid ${pid}: ${cleanup}; ${writeError.message}`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
let acknowledged = false;
|
|
119
|
+
let acknowledgementError: string | undefined;
|
|
120
|
+
try {
|
|
121
|
+
acknowledged = await acknowledgementPromise;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
acknowledgementError = error instanceof Error ? error.message : String(error);
|
|
124
|
+
}
|
|
125
|
+
if (!acknowledged) {
|
|
126
|
+
const cleanup = await terminateBrokerAfterFailedHandoff(reference);
|
|
127
|
+
return {
|
|
128
|
+
kind: "failed",
|
|
129
|
+
reason: `broker hop handoff was not acknowledged for pid ${pid}: ${cleanup}${acknowledgementError ? `; ${acknowledgementError}` : ""}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
child.unref();
|
|
133
|
+
return { kind: "written" };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function writeBrokerHopReplyForTest(
|
|
137
|
+
child: childProcess.ChildProcess,
|
|
138
|
+
reference: Process,
|
|
139
|
+
pid: number,
|
|
140
|
+
write: BrokerHopWriter,
|
|
141
|
+
waitForAcknowledgement: BrokerHopAcknowledger = async () => true,
|
|
142
|
+
): Promise<{ kind: "written" } | { kind: "failed"; reason: string }> {
|
|
143
|
+
return writeBrokerHopReply(child, reference, pid, write, waitForAcknowledgement);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function waitForBrokerHandoffAcknowledgement(input: stream.Readable): Promise<boolean> {
|
|
147
|
+
const acknowledgment = Promise.withResolvers<boolean>();
|
|
148
|
+
let received = "";
|
|
149
|
+
let settled = false;
|
|
150
|
+
const finish = (accepted: boolean): void => {
|
|
151
|
+
if (settled) return;
|
|
152
|
+
settled = true;
|
|
153
|
+
input.removeListener("data", onData);
|
|
154
|
+
input.removeListener("end", onEnd);
|
|
155
|
+
input.removeListener("close", onEnd);
|
|
156
|
+
input.removeListener("error", onEnd);
|
|
157
|
+
acknowledgment.resolve(accepted);
|
|
158
|
+
};
|
|
159
|
+
const onData = (chunk: Buffer | string): void => {
|
|
160
|
+
received += chunk.toString();
|
|
161
|
+
if (received.length > BROKER_HANDOFF_ACKNOWLEDGEMENT.length) {
|
|
162
|
+
finish(false);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (received.includes("\n")) finish(received === BROKER_HANDOFF_ACKNOWLEDGEMENT);
|
|
166
|
+
};
|
|
167
|
+
const onEnd = (): void => finish(false);
|
|
168
|
+
input.on("data", onData);
|
|
169
|
+
input.once("end", onEnd);
|
|
170
|
+
input.once("close", onEnd);
|
|
171
|
+
input.once("error", onEnd);
|
|
172
|
+
input.resume();
|
|
173
|
+
return acknowledgment.promise;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Wait for a validated identity ACK followed by the parent's publication decision. */
|
|
177
|
+
export function waitForBrokerHandoffDecision(
|
|
178
|
+
input: stream.Readable,
|
|
179
|
+
timeoutMs = BROKER_HANDOFF_DECISION_TIMEOUT_MS,
|
|
180
|
+
): Promise<"commit" | "abort"> {
|
|
181
|
+
const decision = Promise.withResolvers<"commit" | "abort">();
|
|
182
|
+
let received = "";
|
|
183
|
+
let settled = false;
|
|
184
|
+
const finish = (value: "commit" | "abort"): void => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
clearTimeout(timer);
|
|
188
|
+
input.removeListener("data", onData);
|
|
189
|
+
input.removeListener("end", onEnd);
|
|
190
|
+
input.removeListener("close", onEnd);
|
|
191
|
+
input.removeListener("error", onEnd);
|
|
192
|
+
decision.resolve(value);
|
|
193
|
+
};
|
|
194
|
+
const onData = (chunk: Buffer | string): void => {
|
|
195
|
+
received += chunk.toString();
|
|
196
|
+
if (received.length > BROKER_HANDOFF_ACKNOWLEDGEMENT.length + BROKER_HANDOFF_COMMIT.length) {
|
|
197
|
+
finish("abort");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (received.length < BROKER_HANDOFF_ACKNOWLEDGEMENT.length) {
|
|
201
|
+
if (!BROKER_HANDOFF_ACKNOWLEDGEMENT.startsWith(received)) finish("abort");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!received.startsWith(BROKER_HANDOFF_ACKNOWLEDGEMENT)) {
|
|
205
|
+
finish("abort");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const action = received.slice(BROKER_HANDOFF_ACKNOWLEDGEMENT.length);
|
|
209
|
+
if (action === BROKER_HANDOFF_COMMIT) finish("commit");
|
|
210
|
+
else if (action === BROKER_HANDOFF_ABORT) finish("abort");
|
|
211
|
+
else if (!BROKER_HANDOFF_COMMIT.startsWith(action) && !BROKER_HANDOFF_ABORT.startsWith(action)) finish("abort");
|
|
212
|
+
};
|
|
213
|
+
const onEnd = (): void => finish("abort");
|
|
214
|
+
const timer = setTimeout(() => finish("abort"), timeoutMs);
|
|
215
|
+
input.on("data", onData);
|
|
216
|
+
input.once("end", onEnd);
|
|
217
|
+
input.once("close", onEnd);
|
|
218
|
+
input.once("error", onEnd);
|
|
219
|
+
input.resume();
|
|
220
|
+
return decision.promise;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const writeBrokerHopStdout: BrokerHopWriter = (chunk, callback): void => {
|
|
224
|
+
const onError = (error: Error): void => {
|
|
225
|
+
process.stdout.removeListener("error", onError);
|
|
226
|
+
callback(error);
|
|
227
|
+
};
|
|
228
|
+
process.stdout.once("error", onError);
|
|
229
|
+
try {
|
|
230
|
+
process.stdout.write(chunk, error => {
|
|
231
|
+
process.stdout.removeListener("error", onError);
|
|
232
|
+
callback(error);
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
process.stdout.removeListener("error", onError);
|
|
236
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/** Pin only the still-running direct child so a reused PID cannot be adopted. */
|
|
241
|
+
function getSpawnedChildReference(pid: number): Process | undefined {
|
|
242
|
+
try {
|
|
243
|
+
const reference = nativeProcessBindings().Process.fromPid(pid);
|
|
244
|
+
if (!reference || reference.pid !== pid || reference.ppid !== process.pid || reference.status() !== "running")
|
|
245
|
+
return undefined;
|
|
246
|
+
return reference;
|
|
247
|
+
} catch {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function terminateUnverifiableWindowsChild(child: childProcess.ChildProcess): Promise<string> {
|
|
253
|
+
if (process.platform !== "win32") return "no signal was sent without a verified process identity";
|
|
254
|
+
if (child.exitCode !== null || child.signalCode !== null) return "the spawned child already exited";
|
|
255
|
+
const exited = Promise.withResolvers<void>();
|
|
256
|
+
child.once("exit", exited.resolve);
|
|
257
|
+
child.once("close", exited.resolve);
|
|
258
|
+
child.on("error", () => {});
|
|
259
|
+
try {
|
|
260
|
+
child.kill("SIGKILL");
|
|
261
|
+
} catch {
|
|
262
|
+
// The process handle remains exact; inspect its exit state below.
|
|
263
|
+
}
|
|
264
|
+
await Promise.race([exited.promise, Bun.sleep(500)]);
|
|
265
|
+
return child.exitCode !== null || child.signalCode !== null
|
|
266
|
+
? "the exact spawned child was terminated"
|
|
267
|
+
: "termination of the exact spawned child was not confirmed";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function terminateBrokerAfterFailedHandoff(reference: Process): Promise<string> {
|
|
271
|
+
let cleanup: string;
|
|
272
|
+
try {
|
|
273
|
+
if (reference.status() === "running") reference.signalRoot(os.constants.signals.SIGKILL);
|
|
274
|
+
const exited = await reference.waitForExit({ timeoutMs: 500 });
|
|
275
|
+
cleanup =
|
|
276
|
+
exited || reference.status() !== "running" ? "broker terminated" : "broker termination was not confirmed";
|
|
277
|
+
} catch (error) {
|
|
278
|
+
cleanup = `broker termination failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
279
|
+
}
|
|
280
|
+
return cleanup;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function fail(message: string): never {
|
|
284
|
+
process.stderr.write(`gjc: ${message}\n`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
@@ -130,6 +130,10 @@ type BoundedSourceRead =
|
|
|
130
130
|
|
|
131
131
|
const terminal = (s: LifecycleState) => s === "terminal_ok" || s === "terminal_error";
|
|
132
132
|
const final = (s: LifecycleState) => terminal(s) || s === "terminal_uncertain";
|
|
133
|
+
const recordedSessionIds = (entry: LifecycleLedgerEntry): string[] =>
|
|
134
|
+
[entry.intendedSessionId, entry.resultSessionId].filter(
|
|
135
|
+
(sessionId): sessionId is string => typeof sessionId === "string" && sessionId.length > 0,
|
|
136
|
+
);
|
|
133
137
|
export interface LifecycleLedgerLimits {
|
|
134
138
|
maxBytes?: number;
|
|
135
139
|
maxLineBytes?: number;
|
|
@@ -795,27 +799,47 @@ export class LifecycleLedger {
|
|
|
795
799
|
return [...this.#byIdentity.values()].findLast(entry => entry.operationKey === operationKey);
|
|
796
800
|
}
|
|
797
801
|
/**
|
|
798
|
-
* Live
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
802
|
+
* Live legacy rows predate the operation/key index. Their opaque identities
|
|
803
|
+
* cannot establish that a different target is safe, so callers must reject
|
|
804
|
+
* rather than create a second admission. A fresh session.create may ignore
|
|
805
|
+
* final legacy rows: exact legacy identities are still resolved before this
|
|
806
|
+
* check, and uncertain rows retain their session-scoped fences.
|
|
807
|
+
*
|
|
808
|
+
* A session-targeted request passes `sessionTarget` to ignore terminal legacy
|
|
809
|
+
* rows that record only other sessions, unless the row is the caller's key
|
|
810
|
+
* reused across targets (`keyReuseIdentities`). Terminal rows recording the
|
|
811
|
+
* target or no session, uncertain rows, and live rows keep the fence because
|
|
812
|
+
* they cannot prove the requested target is unrelated.
|
|
807
813
|
*/
|
|
808
814
|
hasLegacyIdentity(
|
|
809
815
|
excludedIdentities?: ReadonlySet<string>,
|
|
810
|
-
options: {
|
|
816
|
+
options: {
|
|
817
|
+
ignoreFinalRows?: boolean;
|
|
818
|
+
sessionTarget?: { sessionId: string; keyReuseIdentities: ReadonlySet<string> };
|
|
819
|
+
} = {},
|
|
811
820
|
): boolean {
|
|
821
|
+
const target = options.sessionTarget;
|
|
822
|
+
const unrelatedTerminal = (entry: LifecycleLedgerEntry): boolean => {
|
|
823
|
+
if (!target || !terminal(entry.state) || target.keyReuseIdentities.has(entry.identity)) return false;
|
|
824
|
+
const sessionIds = recordedSessionIds(entry);
|
|
825
|
+
return sessionIds.length > 0 && !sessionIds.includes(target.sessionId);
|
|
826
|
+
};
|
|
812
827
|
return [...this.#byIdentity.values()].some(
|
|
813
828
|
entry =>
|
|
814
829
|
entry.operationKey === undefined &&
|
|
815
|
-
!(options.
|
|
830
|
+
!(options.ignoreFinalRows && final(entry.state)) &&
|
|
831
|
+
!unrelatedTerminal(entry) &&
|
|
816
832
|
!excludedIdentities?.has(entry.identity),
|
|
817
833
|
);
|
|
818
834
|
}
|
|
835
|
+
/** Sessions recorded by terminal legacy rows, for deriving key-reuse identities. */
|
|
836
|
+
terminalLegacySessionIds(): Set<string> {
|
|
837
|
+
const sessionIds = new Set<string>();
|
|
838
|
+
for (const entry of this.#byIdentity.values())
|
|
839
|
+
if (entry.operationKey === undefined && terminal(entry.state))
|
|
840
|
+
for (const sessionId of recordedSessionIds(entry)) sessionIds.add(sessionId);
|
|
841
|
+
return sessionIds;
|
|
842
|
+
}
|
|
819
843
|
async migrateIdentity(
|
|
820
844
|
from: string,
|
|
821
845
|
to: string,
|
|
@@ -944,6 +968,9 @@ export class LifecycleLedger {
|
|
|
944
968
|
return this.#mutate(async () => {
|
|
945
969
|
const previous = this.#byIdentity.get(identity);
|
|
946
970
|
if (!previous) throw new Error("Unknown lifecycle identity");
|
|
971
|
+
// Match replay validation: proven terminal records admit no successor.
|
|
972
|
+
// A late failure must not append a row that will be quarantined on reopen.
|
|
973
|
+
if (terminal(previous.state)) return previous;
|
|
947
974
|
const next = {
|
|
948
975
|
...previous,
|
|
949
976
|
...fields,
|
|
@@ -951,6 +951,14 @@ type SessionLaunch = {
|
|
|
951
951
|
modelId?: string;
|
|
952
952
|
modelPreset?: string;
|
|
953
953
|
mcpServers?: SessionLifecycleMcpServer[];
|
|
954
|
+
/**
|
|
955
|
+
* Per-session environment for the hosted session (#6735). Applied to the
|
|
956
|
+
* session host's spawn environment (and therefore inherited by its Bash
|
|
957
|
+
* executor) on top of the broker's own environment, after `unsetEnv` has
|
|
958
|
+
* removed inherited names. Broker-reserved `GJC_*` names are rejected.
|
|
959
|
+
*/
|
|
960
|
+
env?: Record<string, string>;
|
|
961
|
+
unsetEnv?: string[];
|
|
954
962
|
/** Coordinator namespace dir; broker computes the state file path from launch.id (#2549). */
|
|
955
963
|
coordinatorStateDir?: string;
|
|
956
964
|
coordinatorSessionId?: string;
|
|
@@ -963,6 +971,85 @@ type SessionLaunch = {
|
|
|
963
971
|
};
|
|
964
972
|
|
|
965
973
|
const CREDENTIAL_NAME_PATTERN = /(?:token|secret|password|credential|api[_-]?key|auth|cookie)/iu;
|
|
974
|
+
const SESSION_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
975
|
+
const SESSION_ENV_MAX_ENTRIES = 256;
|
|
976
|
+
const SESSION_ENV_MAX_VALUE_LENGTH = 131_072;
|
|
977
|
+
|
|
978
|
+
function isSessionEnvName(name: unknown): name is string {
|
|
979
|
+
return (
|
|
980
|
+
typeof name === "string" &&
|
|
981
|
+
name.length <= 256 &&
|
|
982
|
+
SESSION_ENV_NAME_PATTERN.test(name) &&
|
|
983
|
+
// GJC_* carries broker-issued launch authority (request, effect marker,
|
|
984
|
+
// state root, coordinator correlation); a caller must not shadow it.
|
|
985
|
+
!name.toUpperCase().startsWith("GJC_")
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const sessionEnvKey = (name: string): string => (process.platform === "win32" ? name.toUpperCase() : name);
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Validates the optional per-session `env`/`unsetEnv` pair (#6735). Returns
|
|
993
|
+
* null when either is malformed so the caller fails the launch closed.
|
|
994
|
+
*/
|
|
995
|
+
export function lifecycleSessionEnv(input: {
|
|
996
|
+
env?: unknown;
|
|
997
|
+
unsetEnv?: unknown;
|
|
998
|
+
}): { env?: Record<string, string>; unsetEnv?: string[] } | null {
|
|
999
|
+
let env: Record<string, string> | undefined;
|
|
1000
|
+
if (input.env !== undefined) {
|
|
1001
|
+
if (input.env === null || typeof input.env !== "object" || Array.isArray(input.env)) return null;
|
|
1002
|
+
const entries = Object.entries(input.env as Record<string, unknown>);
|
|
1003
|
+
if (entries.length > SESSION_ENV_MAX_ENTRIES) return null;
|
|
1004
|
+
const seen = new Set<string>();
|
|
1005
|
+
for (const [name, value] of entries) {
|
|
1006
|
+
if (
|
|
1007
|
+
!isSessionEnvName(name) ||
|
|
1008
|
+
typeof value !== "string" ||
|
|
1009
|
+
value.length > SESSION_ENV_MAX_VALUE_LENGTH ||
|
|
1010
|
+
value.includes("\0") ||
|
|
1011
|
+
seen.has(sessionEnvKey(name))
|
|
1012
|
+
)
|
|
1013
|
+
return null;
|
|
1014
|
+
seen.add(sessionEnvKey(name));
|
|
1015
|
+
}
|
|
1016
|
+
env = Object.fromEntries(entries) as Record<string, string>;
|
|
1017
|
+
}
|
|
1018
|
+
let unsetEnv: string[] | undefined;
|
|
1019
|
+
if (input.unsetEnv !== undefined) {
|
|
1020
|
+
if (!Array.isArray(input.unsetEnv) || input.unsetEnv.length > SESSION_ENV_MAX_ENTRIES) return null;
|
|
1021
|
+
const keys = new Set<string>();
|
|
1022
|
+
for (const name of input.unsetEnv) {
|
|
1023
|
+
if (!isSessionEnvName(name) || keys.has(sessionEnvKey(name))) return null;
|
|
1024
|
+
keys.add(sessionEnvKey(name));
|
|
1025
|
+
}
|
|
1026
|
+
// Setting and unsetting the same name is contradictory, not a precedence question.
|
|
1027
|
+
if (env && Object.keys(env).some(name => keys.has(sessionEnvKey(name)))) return null;
|
|
1028
|
+
unsetEnv = [...(input.unsetEnv as string[])];
|
|
1029
|
+
}
|
|
1030
|
+
return { ...(env ? { env } : {}), ...(unsetEnv ? { unsetEnv } : {}) };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Composes the inherited part of a lifecycle session host's environment:
|
|
1035
|
+
* drops the process-local master capability, every `unsetEnv` name, and any
|
|
1036
|
+
* inherited name an explicit per-session `env` entry replaces (case-folded on
|
|
1037
|
+
* Windows so `Path` and `PATH` never both reach the child), then layers the
|
|
1038
|
+
* per-session `env` on top. Broker-issued `GJC_*` keys are applied after this.
|
|
1039
|
+
*/
|
|
1040
|
+
export function lifecycleSessionHostBaseEnv(
|
|
1041
|
+
inherited: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>,
|
|
1042
|
+
launch: { env?: Readonly<Record<string, string>>; unsetEnv?: readonly string[] },
|
|
1043
|
+
): Record<string, string> {
|
|
1044
|
+
const removed = new Set([...(launch.unsetEnv ?? []), ...Object.keys(launch.env ?? {})].map(sessionEnvKey));
|
|
1045
|
+
// Valid names like `__proto__` must remain own environment entries.
|
|
1046
|
+
const base = Object.create(null) as Record<string, string>;
|
|
1047
|
+
for (const [name, value] of Object.entries(inherited)) {
|
|
1048
|
+
if (name === "GJC_MASTER_CAPABILITY" || typeof value !== "string" || removed.has(sessionEnvKey(name))) continue;
|
|
1049
|
+
base[name] = value;
|
|
1050
|
+
}
|
|
1051
|
+
return { ...base, ...launch.env };
|
|
1052
|
+
}
|
|
966
1053
|
|
|
967
1054
|
function lifecycleLaunchKnownSecrets(launch: SessionLaunch): string[] {
|
|
968
1055
|
const secrets = [
|
|
@@ -985,6 +1072,9 @@ function lifecycleLaunchKnownSecrets(launch: SessionLaunch): string[] {
|
|
|
985
1072
|
.filter(([name, value]) => value && CREDENTIAL_NAME_PATTERN.test(name))
|
|
986
1073
|
.map(([, value]) => value);
|
|
987
1074
|
}),
|
|
1075
|
+
...Object.entries(launch.env ?? {})
|
|
1076
|
+
.filter(([name, value]) => value && CREDENTIAL_NAME_PATTERN.test(name))
|
|
1077
|
+
.map(([, value]) => value),
|
|
988
1078
|
];
|
|
989
1079
|
return [...new Set(secrets.filter(secret => typeof secret === "string" && secret.length > 0))];
|
|
990
1080
|
}
|
|
@@ -5998,6 +6088,12 @@ async function launchInput(
|
|
|
5998
6088
|
if (input.mcpServers !== undefined && !isSessionLifecycleMcpServers(input.mcpServers))
|
|
5999
6089
|
return fail("invalid_input", "mcpServers must contain unique valid stdio, HTTP, or SSE server definitions.");
|
|
6000
6090
|
const mcpServers = input.mcpServers as SessionLifecycleMcpServer[] | undefined;
|
|
6091
|
+
const sessionEnv = lifecycleSessionEnv(input);
|
|
6092
|
+
if (!sessionEnv)
|
|
6093
|
+
return fail(
|
|
6094
|
+
"invalid_input",
|
|
6095
|
+
"env must map valid non-GJC_ variable names to strings, and unsetEnv must list distinct valid names not also set in env.",
|
|
6096
|
+
);
|
|
6001
6097
|
|
|
6002
6098
|
/**
|
|
6003
6099
|
* Prepared readiness is an explicit creation-only intent. Only the two exact
|
|
@@ -6047,6 +6143,7 @@ async function launchInput(
|
|
|
6047
6143
|
modelId,
|
|
6048
6144
|
modelPreset,
|
|
6049
6145
|
mcpServers,
|
|
6146
|
+
...sessionEnv,
|
|
6050
6147
|
worktree,
|
|
6051
6148
|
worktreePlan,
|
|
6052
6149
|
...(readiness ? { readiness } : {}),
|
|
@@ -6070,6 +6167,7 @@ async function launchInput(
|
|
|
6070
6167
|
sessionIdentity: saved.identity,
|
|
6071
6168
|
modelPreset,
|
|
6072
6169
|
mcpServers,
|
|
6170
|
+
...sessionEnv,
|
|
6073
6171
|
worktree,
|
|
6074
6172
|
worktreePlan,
|
|
6075
6173
|
...(coordinatorStateDir ? { coordinatorStateDir } : {}),
|
|
@@ -6104,6 +6202,7 @@ async function launchInput(
|
|
|
6104
6202
|
modelId,
|
|
6105
6203
|
modelPreset,
|
|
6106
6204
|
mcpServers,
|
|
6205
|
+
...sessionEnv,
|
|
6107
6206
|
worktree,
|
|
6108
6207
|
worktreePlan,
|
|
6109
6208
|
...(coordinatorStateDir ? { coordinatorStateDir } : {}),
|
|
@@ -7160,12 +7259,9 @@ async function executeLifecycleResponse(
|
|
|
7160
7259
|
stdio: ["ignore", "ignore", childStderrSink?.drainer.stdin ?? "ignore"],
|
|
7161
7260
|
env: {
|
|
7162
7261
|
// Master capability is process-local to direct Bash children and must
|
|
7163
|
-
// never cross a broker lifecycle launch boundary.
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
([key]) => key !== "GJC_MASTER_CAPABILITY",
|
|
7167
|
-
),
|
|
7168
|
-
),
|
|
7262
|
+
// never cross a broker lifecycle launch boundary. Per-session env
|
|
7263
|
+
// (#6735) is layered here, below the broker-issued GJC_* keys.
|
|
7264
|
+
...lifecycleSessionHostBaseEnv("kind" in cmd ? cmd.env : process.env, launch),
|
|
7169
7265
|
GJC_AGENT_DIR: broker.settings.agentDir,
|
|
7170
7266
|
GJC_CODING_AGENT_DIR: broker.settings.agentDir,
|
|
7171
7267
|
GJC_SESSION_ID: launch.id,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadInstallationHostId, loadLegacyInstallationHostId } from "../../config/machine-identity";
|
|
3
|
+
|
|
4
|
+
export interface BrokerLockHostIdentity {
|
|
5
|
+
ownerHostId: string;
|
|
6
|
+
previousOwnerHostIds: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const brokerLockHostIdentityPromises = new Map<string, Promise<BrokerLockHostIdentity>>();
|
|
10
|
+
|
|
11
|
+
export async function brokerLockHostIdentity(agentDir: string): Promise<BrokerLockHostIdentity> {
|
|
12
|
+
const coordinationRoot = path.resolve(agentDir, "sdk");
|
|
13
|
+
const pending =
|
|
14
|
+
brokerLockHostIdentityPromises.get(coordinationRoot) ??
|
|
15
|
+
Promise.all([loadInstallationHostId({ configRootDir: coordinationRoot }), loadLegacyInstallationHostId()]).then(
|
|
16
|
+
([ownerHostId, legacyHostId]) => ({
|
|
17
|
+
ownerHostId,
|
|
18
|
+
previousOwnerHostIds: legacyHostId === ownerHostId ? [] : [legacyHostId],
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
brokerLockHostIdentityPromises.set(coordinationRoot, pending);
|
|
22
|
+
try {
|
|
23
|
+
return await pending;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (brokerLockHostIdentityPromises.get(coordinationRoot) === pending)
|
|
26
|
+
brokerLockHostIdentityPromises.delete(coordinationRoot);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|