@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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import {
|
|
2
4
|
classifyStateArgv,
|
|
3
5
|
STATE_ACTION_NAMES,
|
|
@@ -13,8 +15,20 @@ export interface BashAllowedPrefixesCheck {
|
|
|
13
15
|
|
|
14
16
|
export type BashRestrictionProfile = "workflow" | "read-only";
|
|
15
17
|
|
|
18
|
+
/** Session identities and workspace the restricted `gjc` command may name. */
|
|
19
|
+
export interface GjcSessionBinding {
|
|
20
|
+
sessionId?: string | null;
|
|
21
|
+
/** Owner identity already on the tool session. Role-agent ralplan writes use it. */
|
|
22
|
+
credentialSessionId?: string | null;
|
|
23
|
+
/** Session workspace. A `--worktree-root` must be this repo, not a foreign one. */
|
|
24
|
+
worktreeRoot: string;
|
|
25
|
+
/** Child cwd. Relative `--worktree-root` resolves against it, as ralplan does. */
|
|
26
|
+
invocationCwd: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
export interface BashRestrictionOptions {
|
|
17
30
|
profile?: BashRestrictionProfile;
|
|
31
|
+
gjcSessionBinding?: GjcSessionBinding;
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
const SHELL_CONTROL_CHARS = new Set([";", "|", "&", "<", ">", "(", ")"]);
|
|
@@ -231,6 +245,33 @@ function isLongOption(word: string, option: string): boolean {
|
|
|
231
245
|
return word === option || word.startsWith(`${option}=`);
|
|
232
246
|
}
|
|
233
247
|
|
|
248
|
+
function fileCompilesMagic(words: readonly string[]): boolean {
|
|
249
|
+
const valueOptions = ["--exclude", "--exclude-quiet", "--files-from", "--separator", "--magic-file", "--parameter"];
|
|
250
|
+
for (let index = 1; index < words.length; index += 1) {
|
|
251
|
+
const word = words[index]!;
|
|
252
|
+
if (word === "--") break;
|
|
253
|
+
if (word.startsWith("--")) {
|
|
254
|
+
const equalsAt = word.indexOf("=");
|
|
255
|
+
const name = equalsAt === -1 ? word : word.slice(0, equalsAt);
|
|
256
|
+
if (name.length > 2 && "--compile".startsWith(name)) return true;
|
|
257
|
+
if (equalsAt === -1 && valueOptions.some(option => name.length > 2 && option.startsWith(name))) {
|
|
258
|
+
index += 1;
|
|
259
|
+
}
|
|
260
|
+
} else if (word.startsWith("-")) {
|
|
261
|
+
for (let offset = 1; offset < word.length; offset += 1) {
|
|
262
|
+
const flag = word[offset]!;
|
|
263
|
+
if (flag === "C") return true;
|
|
264
|
+
// Required arguments consume the rest of the cluster, or the next word (even `--`).
|
|
265
|
+
if ("eFfmMP".includes(flag)) {
|
|
266
|
+
if (offset === word.length - 1) index += 1;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
234
275
|
function hasShortOption(word: string, option: string): boolean {
|
|
235
276
|
return word.startsWith("-") && !word.startsWith("--") && word.slice(1).includes(option);
|
|
236
277
|
}
|
|
@@ -239,7 +280,9 @@ function shellQuote(value: string): string {
|
|
|
239
280
|
}
|
|
240
281
|
|
|
241
282
|
function resolvedExternalCommand(command: string): string | undefined {
|
|
242
|
-
|
|
283
|
+
// Bun.which caches a command name until PATH is passed explicitly, so a test
|
|
284
|
+
// or a later session that changes PATH must be visible here.
|
|
285
|
+
return Bun.which(command, { PATH: process.env.PATH ?? "" }) ?? undefined;
|
|
243
286
|
}
|
|
244
287
|
|
|
245
288
|
function validateReadOnlyCommand(words: readonly string[]): BashAllowedPrefixesCheck {
|
|
@@ -254,6 +297,9 @@ function validateReadOnlyCommand(words: readonly string[]): BashAllowedPrefixesC
|
|
|
254
297
|
if (isLongOption(option, "--pre") || isLongOption(option, "--pre-glob")) {
|
|
255
298
|
return { allowed: false, reason: "read-only bash does not allow ripgrep preprocessors" };
|
|
256
299
|
}
|
|
300
|
+
if (isLongOption(option, "--hostname-bin")) {
|
|
301
|
+
return { allowed: false, reason: "read-only bash does not allow ripgrep hostname commands" };
|
|
302
|
+
}
|
|
257
303
|
if (isLongOption(option, "--search-zip") || hasShortOption(option, "z")) {
|
|
258
304
|
return { allowed: false, reason: "read-only bash does not allow ripgrep compressed-file subprocesses" };
|
|
259
305
|
}
|
|
@@ -268,23 +314,161 @@ function validateReadOnlyCommand(words: readonly string[]): BashAllowedPrefixesC
|
|
|
268
314
|
}
|
|
269
315
|
}
|
|
270
316
|
|
|
317
|
+
if (command === "file" && fileCompilesMagic(words)) {
|
|
318
|
+
return { allowed: false, reason: "read-only bash does not allow file magic compilation" };
|
|
319
|
+
}
|
|
320
|
+
|
|
271
321
|
const gitWrite = rejectGitHostAccess(words);
|
|
272
322
|
if (gitWrite) return gitWrite;
|
|
273
323
|
|
|
274
324
|
return { allowed: true };
|
|
275
325
|
}
|
|
276
326
|
|
|
327
|
+
const GIT_DIFF_FAMILY_SUBCOMMANDS = new Set(["blame", "diff", "log", "show"]);
|
|
328
|
+
|
|
329
|
+
function gitOptionName(word: string): string {
|
|
330
|
+
const equalsAt = word.indexOf("=");
|
|
331
|
+
return equalsAt === -1 ? word : word.slice(0, equalsAt);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Enabling forms only. `--no-ext-diff` and `--no-textconv` stay allowed. */
|
|
335
|
+
function gitEnablesExternalHelper(word: string): boolean {
|
|
336
|
+
if (!word.startsWith("--")) return false;
|
|
337
|
+
const name = gitOptionName(word);
|
|
338
|
+
return name === "--ext-diff" || name === "--textconv";
|
|
339
|
+
}
|
|
340
|
+
|
|
277
341
|
function rejectGitHostAccess(words: readonly string[]): BashAllowedPrefixesCheck | undefined {
|
|
278
342
|
if (words[0] !== "git") return undefined;
|
|
279
343
|
for (const option of optionWords(words)) {
|
|
280
344
|
if (isLongOption(option, "--output") || isLongOption(option, "--no-index")) {
|
|
281
345
|
return { allowed: false, reason: "restricted bash does not allow git --output or --no-index" };
|
|
282
346
|
}
|
|
347
|
+
if (gitEnablesExternalHelper(option)) {
|
|
348
|
+
return {
|
|
349
|
+
allowed: false,
|
|
350
|
+
reason: "restricted bash does not allow git external diff or textconv helpers",
|
|
351
|
+
};
|
|
352
|
+
}
|
|
283
353
|
}
|
|
284
354
|
return undefined;
|
|
285
355
|
}
|
|
286
356
|
|
|
287
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Diff-family git commands run `diff.external`, `GIT_EXTERNAL_DIFF`, and textconv
|
|
359
|
+
* filters unless these flags are present. Place them immediately after the
|
|
360
|
+
* subcommand so a later value-taking option cannot consume them and they stay
|
|
361
|
+
* before a `--` pathspec. The gate rejects a later `--ext-diff` or `--textconv`,
|
|
362
|
+
* which would otherwise win.
|
|
363
|
+
*/
|
|
364
|
+
function disableGitExternalHelpers(commandName: string, argv: readonly string[]): string[] {
|
|
365
|
+
if (commandName !== "git" || !GIT_DIFF_FAMILY_SUBCOMMANDS.has(argv[1] ?? "")) return [...argv];
|
|
366
|
+
const next = [...argv];
|
|
367
|
+
next.splice(2, 0, "--no-ext-diff", "--no-textconv");
|
|
368
|
+
return next;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const GJC_SESSION_BIND_REASON = "restricted role-agent bash binds gjc to the current session";
|
|
372
|
+
const GJC_WORKTREE_BIND_REASON = "restricted role-agent bash binds gjc --worktree-root to the current repository";
|
|
373
|
+
|
|
374
|
+
function separateFlagValues(words: readonly string[], flag: string): string[] {
|
|
375
|
+
const values: string[] = [];
|
|
376
|
+
for (let index = 0; index < words.length; index += 1) {
|
|
377
|
+
if (words[index] !== flag) continue;
|
|
378
|
+
values.push(words[index + 1] ?? "");
|
|
379
|
+
index += 1;
|
|
380
|
+
}
|
|
381
|
+
return values;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function boundSessionIds(binding: GjcSessionBinding): ReadonlySet<string> {
|
|
385
|
+
const ids = new Set<string>();
|
|
386
|
+
for (const value of [binding.sessionId, binding.credentialSessionId]) {
|
|
387
|
+
if (typeof value !== "string") continue;
|
|
388
|
+
const trimmed = value.trim();
|
|
389
|
+
if (trimmed) ids.add(trimmed);
|
|
390
|
+
}
|
|
391
|
+
return ids;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function inlineStateSessionIds(words: readonly string[]): string[] {
|
|
395
|
+
if (words[1] !== "state") return [];
|
|
396
|
+
const ids: string[] = [];
|
|
397
|
+
for (const input of classifyStateArgv(words.slice(2)).inputs) {
|
|
398
|
+
if (input.kind !== "inline") continue;
|
|
399
|
+
let payload: unknown;
|
|
400
|
+
try {
|
|
401
|
+
payload = JSON.parse(input.raw);
|
|
402
|
+
} catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) continue;
|
|
406
|
+
const sessionId = (payload as Record<string, unknown>).session_id;
|
|
407
|
+
if (typeof sessionId === "string") ids.push(sessionId);
|
|
408
|
+
}
|
|
409
|
+
return ids;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function gitCommonDir(directory: string): string | undefined {
|
|
413
|
+
const result = Bun.spawnSync(["git", "-C", directory, "rev-parse", "--git-common-dir"], {
|
|
414
|
+
stdout: "pipe",
|
|
415
|
+
stderr: "ignore",
|
|
416
|
+
});
|
|
417
|
+
if (result.exitCode !== 0) return undefined;
|
|
418
|
+
const text = result.stdout.toString().trim();
|
|
419
|
+
if (!text) return undefined;
|
|
420
|
+
const absolute = path.isAbsolute(text) ? text : path.resolve(directory, text);
|
|
421
|
+
try {
|
|
422
|
+
return fs.realpathSync(absolute);
|
|
423
|
+
} catch {
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Same directory, or another worktree of the same git common dir (isolated tasks). */
|
|
429
|
+
function sameRepository(workspace: string, target: string): boolean {
|
|
430
|
+
let workspaceReal: string;
|
|
431
|
+
let targetReal: string;
|
|
432
|
+
try {
|
|
433
|
+
workspaceReal = fs.realpathSync(workspace);
|
|
434
|
+
targetReal = fs.realpathSync(target);
|
|
435
|
+
} catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (workspaceReal === targetReal) return true;
|
|
439
|
+
const workspaceCommon = gitCommonDir(workspaceReal);
|
|
440
|
+
const targetCommon = gitCommonDir(targetReal);
|
|
441
|
+
return workspaceCommon !== undefined && workspaceCommon === targetCommon;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function worktreeSelectorAllowed(binding: GjcSessionBinding, rawTarget: string): boolean {
|
|
445
|
+
const trimmed = rawTarget.trim();
|
|
446
|
+
if (!trimmed) return false;
|
|
447
|
+
const resolved = path.isAbsolute(trimmed) ? trimmed : path.resolve(binding.invocationCwd, trimmed);
|
|
448
|
+
return sameRepository(binding.worktreeRoot, resolved);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function rejectUnboundGjcCommand(
|
|
452
|
+
words: readonly string[],
|
|
453
|
+
options: BashRestrictionOptions,
|
|
454
|
+
): BashAllowedPrefixesCheck | undefined {
|
|
455
|
+
const binding = options.gjcSessionBinding;
|
|
456
|
+
if (!binding) return undefined;
|
|
457
|
+
const allowed = boundSessionIds(binding);
|
|
458
|
+
const sessionSelectors = [...separateFlagValues(words, "--session-id"), ...inlineStateSessionIds(words)];
|
|
459
|
+
if (sessionSelectors.some(value => !allowed.has(value.trim()))) {
|
|
460
|
+
return { allowed: false, reason: GJC_SESSION_BIND_REASON };
|
|
461
|
+
}
|
|
462
|
+
if (separateFlagValues(words, "--worktree-root").some(value => !worktreeSelectorAllowed(binding, value))) {
|
|
463
|
+
return { allowed: false, reason: GJC_WORKTREE_BIND_REASON };
|
|
464
|
+
}
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function validateMatchedGjcCommand(
|
|
469
|
+
words: readonly string[],
|
|
470
|
+
options: BashRestrictionOptions,
|
|
471
|
+
): BashAllowedPrefixesCheck {
|
|
288
472
|
const gitWrite = rejectGitHostAccess(words);
|
|
289
473
|
if (gitWrite) return gitWrite;
|
|
290
474
|
if (words[0] !== "gjc") return { allowed: true };
|
|
@@ -293,7 +477,7 @@ function validateMatchedGjcCommand(words: readonly string[]): BashAllowedPrefixe
|
|
|
293
477
|
if (!words.includes("--write")) {
|
|
294
478
|
return { allowed: false, reason: "restricted role-agent bash only allows `gjc ralplan --write ...`" };
|
|
295
479
|
}
|
|
296
|
-
return { allowed: true };
|
|
480
|
+
return rejectUnboundGjcCommand(words, options) ?? { allowed: true };
|
|
297
481
|
}
|
|
298
482
|
|
|
299
483
|
if (words[1] === "state") {
|
|
@@ -311,10 +495,10 @@ function validateMatchedGjcCommand(words: readonly string[]): BashAllowedPrefixe
|
|
|
311
495
|
reason: `restricted role-agent bash does not allow \`gjc state ${classification.parsed.action}\``,
|
|
312
496
|
};
|
|
313
497
|
}
|
|
314
|
-
return { allowed: true };
|
|
498
|
+
return rejectUnboundGjcCommand(words, options) ?? { allowed: true };
|
|
315
499
|
}
|
|
316
500
|
|
|
317
|
-
return { allowed: true };
|
|
501
|
+
return rejectUnboundGjcCommand(words, options) ?? { allowed: true };
|
|
318
502
|
}
|
|
319
503
|
|
|
320
504
|
function commandAllowedPrefixesReason(normalizedPrefixes: readonly string[], options: BashRestrictionOptions): string {
|
|
@@ -324,6 +508,30 @@ function commandAllowedPrefixesReason(normalizedPrefixes: readonly string[], opt
|
|
|
324
508
|
: `restricted role-agent bash only allows commands starting with: ${prefixList}`;
|
|
325
509
|
}
|
|
326
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Workflow restricted bash keeps the lexical prefix check, then runs the resolved
|
|
513
|
+
* executable. Callers also skip the shell snapshot and shell prefix, because a
|
|
514
|
+
* snapshot function can be named for that absolute path.
|
|
515
|
+
*/
|
|
516
|
+
export function normalizeWorkflowBashCommand(
|
|
517
|
+
command: string,
|
|
518
|
+
allowedPrefixes: readonly string[] | undefined,
|
|
519
|
+
options: BashRestrictionOptions = {},
|
|
520
|
+
): string | undefined {
|
|
521
|
+
if (options.profile === "read-only") return undefined;
|
|
522
|
+
const gate = checkBashAllowedPrefixes(command, allowedPrefixes, { ...options, profile: "workflow" });
|
|
523
|
+
if (!gate.allowed) return undefined;
|
|
524
|
+
const parsed = parseShellWords(command.trim());
|
|
525
|
+
if (parsed.reason || parsed.words.length === 0) return undefined;
|
|
526
|
+
const [head, ...rest] = parsed.words;
|
|
527
|
+
if (!head) return undefined;
|
|
528
|
+
const resolvedHead = resolvedExternalCommand(head);
|
|
529
|
+
if (!resolvedHead) return undefined;
|
|
530
|
+
return disableGitExternalHelpers(head, [resolvedHead, ...rest])
|
|
531
|
+
.map(shellQuote)
|
|
532
|
+
.join(" ");
|
|
533
|
+
}
|
|
534
|
+
|
|
327
535
|
export function normalizeReadOnlyBashCommand(command: string): string | undefined {
|
|
328
536
|
const parsed = parseShellWords(command.trim());
|
|
329
537
|
if (parsed.reason || parsed.words.length === 0) return undefined;
|
|
@@ -360,5 +568,5 @@ export function checkBashAllowedPrefixes(
|
|
|
360
568
|
if (options.profile === "read-only") {
|
|
361
569
|
return validateReadOnlyCommand(parsed.words);
|
|
362
570
|
}
|
|
363
|
-
return validateMatchedGjcCommand(parsed.words);
|
|
571
|
+
return validateMatchedGjcCommand(parsed.words, options);
|
|
364
572
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
visibleWidth,
|
|
13
13
|
} from "@gajae-code/tui";
|
|
14
14
|
import { logger, sanitizeText } from "@gajae-code/utils";
|
|
15
|
+
import { projectDeclaredSpawnHookNames, projectSafeHomeOverride } from "@gajae-code/utils/shell-config";
|
|
15
16
|
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
|
16
17
|
import { Settings } from "../config/settings";
|
|
17
18
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
@@ -323,6 +324,14 @@ export interface InteractivePtyControls {
|
|
|
323
324
|
detachForegroundCancellation: () => void;
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
function ptyUnsetHookNames(explicit: Record<string, string> | undefined): string[] {
|
|
328
|
+
return projectDeclaredSpawnHookNames().filter(name => !Object.hasOwn(explicit ?? {}, name));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function ptyHomeOverride(explicit: Record<string, string> | undefined): Record<string, string> {
|
|
332
|
+
return projectSafeHomeOverride(explicit);
|
|
333
|
+
}
|
|
334
|
+
|
|
326
335
|
export async function runInteractiveBashPty(
|
|
327
336
|
ui: NonNullable<AgentToolContext["ui"]>,
|
|
328
337
|
options: {
|
|
@@ -422,8 +431,11 @@ export async function runInteractiveBashPty(
|
|
|
422
431
|
command: options.command,
|
|
423
432
|
cwd: options.cwd,
|
|
424
433
|
timeoutMs: options.timeoutMs,
|
|
425
|
-
|
|
426
|
-
|
|
434
|
+
// The PTY child inherits the process env; drop project hook variables
|
|
435
|
+
// unless this call sets them explicitly. A dropped HOME is replaced by
|
|
436
|
+
// the trusted account home so the shell and git still have one.
|
|
437
|
+
env: { ...ptyHomeOverride(options.env), ...NON_INTERACTIVE_ENV, ...options.env },
|
|
438
|
+
unsetEnv: [...(options.unsetEnv ?? []), ...ptyUnsetHookNames(options.env)],
|
|
427
439
|
signal: processAbortController.signal,
|
|
428
440
|
cols: initialPtySize.cols,
|
|
429
441
|
rows: initialPtySize.rows,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as fsTypes from "node:fs";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { isEnoent } from "@gajae-code/utils";
|
|
@@ -40,7 +41,7 @@ export interface InternalUrlExpansionOptions {
|
|
|
40
41
|
* Resolve a single skill:// URL to its absolute filesystem path.
|
|
41
42
|
* Does NOT read file content or verify existence.
|
|
42
43
|
*/
|
|
43
|
-
export function
|
|
44
|
+
export function resolveSkillUrl(url: string, skills: readonly Skill[]): { skill: Skill; lexicalPath: string } {
|
|
44
45
|
const parsed = /^skill:\/\/([^/?#]+)(\/[^?#]*)?(?:[?#].*)?$/.exec(url);
|
|
45
46
|
if (!parsed) {
|
|
46
47
|
throw new ToolError(`Invalid skill:// URL: ${url}`);
|
|
@@ -72,7 +73,7 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
|
|
|
72
73
|
const hasRelativePath = rawPath !== "" && rawPath !== "/";
|
|
73
74
|
|
|
74
75
|
if (!hasRelativePath) {
|
|
75
|
-
return path.resolve(skill.filePath);
|
|
76
|
+
return { skill, lexicalPath: path.resolve(skill.filePath) };
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
let relativePath: string;
|
|
@@ -95,7 +96,11 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
|
|
|
95
96
|
throw new ToolError("Path traversal is not allowed in skill:// URLs");
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
return resolvedPath;
|
|
99
|
+
return { skill, lexicalPath: resolvedPath };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): string {
|
|
103
|
+
return resolveSkillUrl(url, skills).lexicalPath;
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
@@ -150,7 +155,7 @@ async function canonicalLocalPath(localRoot: string, lexicalPath: string): Promi
|
|
|
150
155
|
const missing: string[] = [];
|
|
151
156
|
let cursor = lexicalPath;
|
|
152
157
|
for (;;) {
|
|
153
|
-
let stat:
|
|
158
|
+
let stat: fsTypes.Stats | undefined;
|
|
154
159
|
try {
|
|
155
160
|
stat = await fs.lstat(cursor);
|
|
156
161
|
} catch (error) {
|
|
@@ -177,6 +182,53 @@ async function canonicalLocalPath(localRoot: string, lexicalPath: string): Promi
|
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
|
|
185
|
+
const SKILL_ESCAPE = "Path traversal is not allowed in skill:// URLs";
|
|
186
|
+
|
|
187
|
+
/** Real path of `target`, or the real ancestor plus the missing suffix. */
|
|
188
|
+
async function canonicalPathOrAncestor(target: string, escapeMessage: string): Promise<string> {
|
|
189
|
+
try {
|
|
190
|
+
return await fs.realpath(target);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (!isEnoent(error)) throw error;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const missing: string[] = [];
|
|
196
|
+
let cursor = target;
|
|
197
|
+
for (;;) {
|
|
198
|
+
let stat: fsTypes.Stats | undefined;
|
|
199
|
+
try {
|
|
200
|
+
stat = await fs.lstat(cursor);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (!isEnoent(error)) throw error;
|
|
203
|
+
}
|
|
204
|
+
if (stat) {
|
|
205
|
+
try {
|
|
206
|
+
return path.join(await fs.realpath(cursor), ...missing);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (isEnoent(error)) throw new ToolError(escapeMessage);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const parent = path.dirname(cursor);
|
|
213
|
+
if (parent === cursor) throw new ToolError(escapeMessage);
|
|
214
|
+
missing.unshift(path.basename(cursor));
|
|
215
|
+
cursor = parent;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Keep the lexical skill path, then bind the shell to the real path inside the skill directory.
|
|
221
|
+
* A missing file under a real in-skill ancestor still expands.
|
|
222
|
+
*/
|
|
223
|
+
async function canonicalSkillExpansionPath(baseDir: string, lexicalPath: string): Promise<string> {
|
|
224
|
+
const realBase = await canonicalPathOrAncestor(baseDir, SKILL_ESCAPE);
|
|
225
|
+
const realTarget = await canonicalPathOrAncestor(lexicalPath, SKILL_ESCAPE);
|
|
226
|
+
if (localPathEscapes(realBase, realTarget)) {
|
|
227
|
+
throw new ToolError(SKILL_ESCAPE);
|
|
228
|
+
}
|
|
229
|
+
return realTarget;
|
|
230
|
+
}
|
|
231
|
+
|
|
180
232
|
function extractScheme(url: string): SupportedInternalScheme | undefined {
|
|
181
233
|
const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(url);
|
|
182
234
|
if (!match) return undefined;
|
|
@@ -211,7 +263,8 @@ async function resolveInternalUrlToPath(
|
|
|
211
263
|
}
|
|
212
264
|
|
|
213
265
|
if (scheme === "skill") {
|
|
214
|
-
|
|
266
|
+
const { skill, lexicalPath } = resolveSkillUrl(url, skills);
|
|
267
|
+
return canonicalSkillExpansionPath(skill.baseDir, lexicalPath);
|
|
215
268
|
}
|
|
216
269
|
|
|
217
270
|
// Managed artifact and agent resources carry session authority. Expanding
|
package/src/tools/bash.ts
CHANGED
|
@@ -64,7 +64,12 @@ import { CachedOutputBlock, getOutputBlockContentWidth } from "../tui/output-blo
|
|
|
64
64
|
import { truncateToWidth } from "../tui/utils";
|
|
65
65
|
import { getSixelLineMask } from "../utils/sixel";
|
|
66
66
|
import type { ToolSession } from ".";
|
|
67
|
-
import {
|
|
67
|
+
import { AwaitingInputDetector, type AwaitingInputSignal } from "./awaiting-input";
|
|
68
|
+
import {
|
|
69
|
+
checkBashAllowedPrefixes,
|
|
70
|
+
normalizeReadOnlyBashCommand,
|
|
71
|
+
normalizeWorkflowBashCommand,
|
|
72
|
+
} from "./bash-allowed-prefixes";
|
|
68
73
|
import { applyBashFixups } from "./bash-command-fixup";
|
|
69
74
|
import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
|
|
70
75
|
import { checkBashInterception } from "./bash-interceptor";
|
|
@@ -578,6 +583,12 @@ export interface BashToolDetails {
|
|
|
578
583
|
foldReason?: FoldReason;
|
|
579
584
|
/** The accepted model-declared activity declaration, when the call carried one. */
|
|
580
585
|
activity?: BashActivityDeclaration;
|
|
586
|
+
/**
|
|
587
|
+
* Set on a running (partial) update when the foreground command printed an
|
|
588
|
+
* interactive auth prompt and then went quiet. Carries only the prompt kind,
|
|
589
|
+
* never the URL or code.
|
|
590
|
+
*/
|
|
591
|
+
awaitingInput?: AwaitingInputSignal;
|
|
581
592
|
async?: {
|
|
582
593
|
state: "running" | "completed" | "failed";
|
|
583
594
|
jobId: string;
|
|
@@ -1125,6 +1136,17 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1125
1136
|
const spillThreshold = resolveBashOutputSinkTailBytes(this.session.settings);
|
|
1126
1137
|
const headBytes = resolveBashOutputSinkHeadBytes(this.session.settings);
|
|
1127
1138
|
const tailBuffer = new TailBuffer(spillThreshold);
|
|
1139
|
+
// Only a foreground wait blocks the turn; once folded or started in the
|
|
1140
|
+
// background the agent is free and this tool call has already settled.
|
|
1141
|
+
const awaitingInput = new AwaitingInputDetector({
|
|
1142
|
+
onDetect: signal => {
|
|
1143
|
+
if (backgrounded) return;
|
|
1144
|
+
void options.onUpdate?.({
|
|
1145
|
+
content: [{ type: "text", text: latestText }],
|
|
1146
|
+
details: { ...activityDetails, awaitingInput: signal },
|
|
1147
|
+
});
|
|
1148
|
+
},
|
|
1149
|
+
});
|
|
1128
1150
|
|
|
1129
1151
|
let executionResult: BashResult | BashInteractiveResult | undefined;
|
|
1130
1152
|
try {
|
|
@@ -1148,18 +1170,24 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1148
1170
|
spillThreshold,
|
|
1149
1171
|
headBytes,
|
|
1150
1172
|
oneShot: true,
|
|
1151
|
-
ignoreShellPrefix: this
|
|
1152
|
-
disableShellSnapshot: this
|
|
1173
|
+
ignoreShellPrefix: this.#isolateRestrictedShell(),
|
|
1174
|
+
disableShellSnapshot: this.#isolateRestrictedShell(),
|
|
1153
1175
|
onChunk: chunk => {
|
|
1154
1176
|
tailBuffer.append(chunk);
|
|
1155
1177
|
latestText = tailBuffer.text();
|
|
1156
1178
|
void reportProgress(latestText, runningDetails(jobId));
|
|
1157
1179
|
},
|
|
1158
|
-
|
|
1180
|
+
// onChunk is throttled and drops chunks inside the window; the
|
|
1181
|
+
// detector must see every chunk, so it reads the raw stream.
|
|
1182
|
+
onRawChunk: chunk => {
|
|
1183
|
+
manager.appendOutput(jobId, chunk);
|
|
1184
|
+
awaitingInput.push(chunk);
|
|
1185
|
+
},
|
|
1159
1186
|
onMinimizedSave: async originalText =>
|
|
1160
1187
|
saveBashOriginalArtifact(options.artifactPublication, originalText),
|
|
1161
1188
|
});
|
|
1162
1189
|
executionResult = result;
|
|
1190
|
+
awaitingInput.dispose();
|
|
1163
1191
|
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
1164
1192
|
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
1165
1193
|
notices: options.notices,
|
|
@@ -1173,6 +1201,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1173
1201
|
await reportProgress(finalText, completedDetails(jobId));
|
|
1174
1202
|
return finalText;
|
|
1175
1203
|
} catch (error) {
|
|
1204
|
+
awaitingInput.dispose();
|
|
1176
1205
|
const message = error instanceof Error ? error.message : String(error);
|
|
1177
1206
|
latestText = message;
|
|
1178
1207
|
options.onCompletion?.();
|
|
@@ -1323,6 +1352,11 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1323
1352
|
* helper after `AgentSession` has applied the public-tool permission gate, so
|
|
1324
1353
|
* Monitor inherits Bash's cwd / env / artifact / interceptor pipeline 1:1.
|
|
1325
1354
|
*/
|
|
1355
|
+
/** Read-only and workflow allowlists must not source a snapshot or a shell prefix. */
|
|
1356
|
+
#isolateRestrictedShell(): boolean {
|
|
1357
|
+
return this.session.bashRestrictionProfile === "read-only" || (this.session.bashAllowedPrefixes?.length ?? 0) > 0;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1326
1360
|
async #prepareBashExecution(
|
|
1327
1361
|
input: { command: string; env?: Record<string, string>; timeout?: number; cwd?: string },
|
|
1328
1362
|
ctx?: AgentToolContext,
|
|
@@ -1379,9 +1413,23 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1379
1413
|
}
|
|
1380
1414
|
if (allowedPrefixes && allowedPrefixes.length > 0) {
|
|
1381
1415
|
const commandsToCheck = rawCommand === command ? [command] : [rawCommand, command];
|
|
1416
|
+
const invocationCwd =
|
|
1417
|
+
cwd && !cwd.includes("://") && !cwd.includes("local:/")
|
|
1418
|
+
? resolveToCwd(cwd, this.session.cwd)
|
|
1419
|
+
: this.session.cwd;
|
|
1382
1420
|
for (const commandToCheck of commandsToCheck) {
|
|
1383
1421
|
const allowlist = checkBashAllowedPrefixes(commandToCheck, allowedPrefixes, {
|
|
1384
1422
|
profile: this.session.bashRestrictionProfile,
|
|
1423
|
+
...(this.session.bashRestrictionProfile === "read-only"
|
|
1424
|
+
? {}
|
|
1425
|
+
: {
|
|
1426
|
+
gjcSessionBinding: {
|
|
1427
|
+
sessionId: this.session.getSessionId?.() ?? null,
|
|
1428
|
+
credentialSessionId: this.session.getCredentialSessionId?.() ?? null,
|
|
1429
|
+
worktreeRoot: this.session.cwd,
|
|
1430
|
+
invocationCwd,
|
|
1431
|
+
},
|
|
1432
|
+
}),
|
|
1385
1433
|
});
|
|
1386
1434
|
if (!allowlist.allowed) {
|
|
1387
1435
|
throw new ToolError(allowlist.reason ?? "Command blocked by restricted role-agent bash allowlist.");
|
|
@@ -1433,19 +1481,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1433
1481
|
...internalUrlOptions,
|
|
1434
1482
|
ensureLocalParentDirs: this.session.bashRestrictionProfile !== "read-only",
|
|
1435
1483
|
});
|
|
1436
|
-
// Issue #5039: refuse tmux write verbs whose target resolves to this
|
|
1437
|
-
// agent's own tmux pane — such bytes forge a human `user` turn in the
|
|
1438
|
-
// transcript. This is after command expansion but before any spawn, so
|
|
1439
|
-
// blocked attempts inject no bytes and create no transcript turn.
|
|
1440
|
-
if (this.session.bashRestrictionProfile !== "read-only") {
|
|
1441
|
-
const injection = await checkTmuxSelfInjection(command, {
|
|
1442
|
-
env: process.env,
|
|
1443
|
-
cwd: this.session.cwd,
|
|
1444
|
-
});
|
|
1445
|
-
if (injection.block) {
|
|
1446
|
-
throw new ToolError(injection.reason ?? "Blocked: tmux self-pane injection.");
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
1484
|
const expandedEnv = env
|
|
1450
1485
|
? Object.fromEntries(
|
|
1451
1486
|
await Promise.all(
|
|
@@ -1527,6 +1562,45 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1527
1562
|
if (this.session.bashRestrictionProfile === "read-only" || (allowedPrefixes?.length ?? 0) > 0) {
|
|
1528
1563
|
await assertCwdInsideWorkspace(this.session.cwd, commandCwd);
|
|
1529
1564
|
}
|
|
1565
|
+
// Issue #5039: refuse tmux write verbs whose target resolves to this
|
|
1566
|
+
// agent's own tmux pane — such bytes forge a human `user` turn in the
|
|
1567
|
+
// transcript. This is after command/env/cwd preparation but before any
|
|
1568
|
+
// spawn, so guard discovery matches execution without trusting caller env
|
|
1569
|
+
// for the agent's tmux identity.
|
|
1570
|
+
if (this.session.bashRestrictionProfile !== "read-only") {
|
|
1571
|
+
const shellConfigEnv = this.session.settings.getShellConfig().env;
|
|
1572
|
+
const injection = await checkTmuxSelfInjection(command, {
|
|
1573
|
+
env: process.env,
|
|
1574
|
+
cwd: commandCwd,
|
|
1575
|
+
executionEnv: { ...process.env, ...shellConfigEnv, ...resolvedEnv },
|
|
1576
|
+
});
|
|
1577
|
+
if (injection.block) {
|
|
1578
|
+
throw new ToolError(injection.reason ?? "Blocked: tmux self-pane injection.");
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
// Direct master spawn parses the original command itself and does not source
|
|
1582
|
+
// a shell snapshot. Every other restricted workflow command is rebound here,
|
|
1583
|
+
// after the allowlist and interceptor have seen the caller's text.
|
|
1584
|
+
if (
|
|
1585
|
+
!directMasterSpawn &&
|
|
1586
|
+
this.session.bashRestrictionProfile !== "read-only" &&
|
|
1587
|
+
allowedPrefixes &&
|
|
1588
|
+
allowedPrefixes.length > 0
|
|
1589
|
+
) {
|
|
1590
|
+
const normalizedWorkflowCommand = normalizeWorkflowBashCommand(command, allowedPrefixes, {
|
|
1591
|
+
profile: "workflow",
|
|
1592
|
+
gjcSessionBinding: {
|
|
1593
|
+
sessionId: this.session.getSessionId?.() ?? null,
|
|
1594
|
+
credentialSessionId: this.session.getCredentialSessionId?.() ?? null,
|
|
1595
|
+
worktreeRoot: this.session.cwd,
|
|
1596
|
+
invocationCwd: commandCwd,
|
|
1597
|
+
},
|
|
1598
|
+
});
|
|
1599
|
+
if (!normalizedWorkflowCommand) {
|
|
1600
|
+
throw new ToolError("Restricted role-agent bash command could not be normalized safely.");
|
|
1601
|
+
}
|
|
1602
|
+
command = normalizedWorkflowCommand;
|
|
1603
|
+
}
|
|
1530
1604
|
|
|
1531
1605
|
const requestedTimeoutSec = input.timeout ?? 300;
|
|
1532
1606
|
const timeoutSec = clampTimeout("bash", requestedTimeoutSec);
|
|
@@ -1710,9 +1784,8 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1710
1784
|
spillThreshold,
|
|
1711
1785
|
headBytes,
|
|
1712
1786
|
oneShot: true,
|
|
1713
|
-
ignoreShellPrefix: this
|
|
1714
|
-
disableShellSnapshot:
|
|
1715
|
-
this.session.bashRestrictionProfile === "read-only" || prepared.directMasterSpawn,
|
|
1787
|
+
ignoreShellPrefix: this.#isolateRestrictedShell() || prepared.directMasterSpawn,
|
|
1788
|
+
disableShellSnapshot: this.#isolateRestrictedShell() || prepared.directMasterSpawn,
|
|
1716
1789
|
onChunk: chunk => {
|
|
1717
1790
|
tailBuffer.append(chunk);
|
|
1718
1791
|
void reportProgress(tailBuffer.text(), {
|
|
@@ -2637,6 +2710,14 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2637
2710
|
|
|
2638
2711
|
// Track output for streaming updates (tail only)
|
|
2639
2712
|
const tailBuffer = new TailBuffer(spillThreshold);
|
|
2713
|
+
const awaitingInput = new AwaitingInputDetector({
|
|
2714
|
+
onDetect: signal => {
|
|
2715
|
+
void onUpdate?.({
|
|
2716
|
+
content: [{ type: "text", text: tailBuffer.text() }],
|
|
2717
|
+
details: { ...(activity === undefined ? {} : { activity }), awaitingInput: signal },
|
|
2718
|
+
});
|
|
2719
|
+
},
|
|
2720
|
+
});
|
|
2640
2721
|
|
|
2641
2722
|
const artifactPublisher = createBashArtifactPublisher(artifactPublication);
|
|
2642
2723
|
|
|
@@ -2819,11 +2900,15 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2819
2900
|
onOutput: chunk => {
|
|
2820
2901
|
ptyOutputSeen = true;
|
|
2821
2902
|
if (ptyJobId) ownedManager?.appendOutput(ptyJobId, chunk);
|
|
2903
|
+
awaitingInput.push(chunk);
|
|
2822
2904
|
},
|
|
2823
|
-
}).catch(error => {
|
|
2824
|
-
if (ptyAdmission) ownedManager?.releaseCapacity(ptyAdmission);
|
|
2825
|
-
throw error;
|
|
2826
2905
|
})
|
|
2906
|
+
.catch(error => {
|
|
2907
|
+
if (ptyAdmission) ownedManager?.releaseCapacity(ptyAdmission);
|
|
2908
|
+
throw error;
|
|
2909
|
+
})
|
|
2910
|
+
// Completion, error, and a fold all settle this foreground wait.
|
|
2911
|
+
.finally(() => awaitingInput.dispose())
|
|
2827
2912
|
: await executeBash(command, {
|
|
2828
2913
|
cwd: commandCwd,
|
|
2829
2914
|
settings: this.session.settings,
|
|
@@ -2837,10 +2922,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
2837
2922
|
spillThreshold,
|
|
2838
2923
|
headBytes,
|
|
2839
2924
|
onChunk: streamTailUpdates(tailBuffer, onUpdate),
|
|
2925
|
+
// onChunk is throttled and drops chunks; the detector needs all of them.
|
|
2926
|
+
onRawChunk: chunk => awaitingInput.push(chunk),
|
|
2840
2927
|
onMinimizedSave: async originalText => saveBashOriginalArtifact(artifactPublication, originalText),
|
|
2841
|
-
ignoreShellPrefix: this
|
|
2842
|
-
disableShellSnapshot: this
|
|
2843
|
-
});
|
|
2928
|
+
ignoreShellPrefix: this.#isolateRestrictedShell() || directMasterSpawn,
|
|
2929
|
+
disableShellSnapshot: this.#isolateRestrictedShell() || directMasterSpawn,
|
|
2930
|
+
}).finally(() => awaitingInput.dispose());
|
|
2844
2931
|
ptyFoldUnregister?.();
|
|
2845
2932
|
// Folded: the foreground was settled by the fold, so return the background
|
|
2846
2933
|
// -start result verbatim instead of presenting a half-finished command.
|