@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
package/src/tools/ask.ts
CHANGED
|
@@ -953,6 +953,7 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
|
|
|
953
953
|
this.session.cwd,
|
|
954
954
|
{ sessionId: this.session.getSessionId?.() ?? null },
|
|
955
955
|
this.session.getSessionAgentDir?.() ?? this.session.settings.getAgentDir(),
|
|
956
|
+
params,
|
|
956
957
|
);
|
|
957
958
|
assertDeepInterviewStructuredResponseWithinLimit(params);
|
|
958
959
|
// Recheck at execution time as well: direct SDK callers and a phase change
|
|
@@ -66,3 +66,67 @@ export async function resolveAstEditPreviewWritePaths(
|
|
|
66
66
|
}
|
|
67
67
|
return resolved;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/** Refusal prefix when a single-file real path leaves its reference root. */
|
|
71
|
+
export const AST_EDIT_OUTSIDE_ROOT_MESSAGE = "AST edit target resolves outside its root";
|
|
72
|
+
|
|
73
|
+
function isEnoent(error: unknown): boolean {
|
|
74
|
+
return (error as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function staysInside(root: string, target: string): boolean {
|
|
78
|
+
const relative = path.relative(root, target);
|
|
79
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function realOrLexical(target: string): Promise<string> {
|
|
83
|
+
try {
|
|
84
|
+
return await fs.realpath(target);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (isEnoent(error)) return path.resolve(target);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Native astEdit canonicalizes one file and writes that real path. Directory
|
|
93
|
+
* scans do not follow links. A lexical path inside cwd uses the real cwd as
|
|
94
|
+
* the reference root; an explicit path outside cwd uses its real parent, so a
|
|
95
|
+
* direct file there still applies. Returns the lexical path that would leave
|
|
96
|
+
* that root, if any.
|
|
97
|
+
*/
|
|
98
|
+
export async function astEditSingleFileOutsideReferenceRoot(
|
|
99
|
+
cwd: string,
|
|
100
|
+
candidatePaths: readonly string[],
|
|
101
|
+
signal?: AbortSignal,
|
|
102
|
+
): Promise<string | undefined> {
|
|
103
|
+
signal?.throwIfAborted();
|
|
104
|
+
const resolvedCwd = path.resolve(cwd);
|
|
105
|
+
const realCwd = await realOrLexical(resolvedCwd);
|
|
106
|
+
for (const candidate of candidatePaths) {
|
|
107
|
+
signal?.throwIfAborted();
|
|
108
|
+
const lexical = path.resolve(resolvedCwd, candidate);
|
|
109
|
+
let followed: string;
|
|
110
|
+
try {
|
|
111
|
+
followed = await fs.realpath(lexical);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (isEnoent(error)) continue;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
let stat: Awaited<ReturnType<typeof fs.stat>>;
|
|
117
|
+
try {
|
|
118
|
+
stat = await fs.stat(followed);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (isEnoent(error)) continue;
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
if (!stat.isFile()) continue;
|
|
124
|
+
const root =
|
|
125
|
+
staysInside(resolvedCwd, lexical) || staysInside(realCwd, lexical)
|
|
126
|
+
? realCwd
|
|
127
|
+
: await realOrLexical(path.dirname(lexical));
|
|
128
|
+
signal?.throwIfAborted();
|
|
129
|
+
if (!staysInside(root, followed)) return lexical;
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -13,7 +13,11 @@ import { assertWorkflowMutationRawPathsAllowed } from "../skill-state/workflow-m
|
|
|
13
13
|
import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
14
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
AST_EDIT_OUTSIDE_ROOT_MESSAGE,
|
|
18
|
+
astEditSingleFileOutsideReferenceRoot,
|
|
19
|
+
resolveAstEditPreviewWritePaths,
|
|
20
|
+
} from "./ast-edit-write-path";
|
|
17
21
|
import { assertEditableFile } from "./auto-generated-guard";
|
|
18
22
|
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
19
23
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
@@ -220,6 +224,18 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
220
224
|
});
|
|
221
225
|
const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
|
|
222
226
|
|
|
227
|
+
const assertSingleFileReferenceRoot = async (flowSignal?: AbortSignal): Promise<void> => {
|
|
228
|
+
const nativeWritePaths = multiTargets ? multiTargets.map(target => target.basePath) : [resolvedSearchPath];
|
|
229
|
+
const outsideRoot = await astEditSingleFileOutsideReferenceRoot(
|
|
230
|
+
this.session.cwd,
|
|
231
|
+
nativeWritePaths,
|
|
232
|
+
flowSignal,
|
|
233
|
+
);
|
|
234
|
+
if (outsideRoot) {
|
|
235
|
+
throw new ToolError(`${AST_EDIT_OUTSIDE_ROOT_MESSAGE}: ${outsideRoot}`);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
223
239
|
const result = await runAstEditOnce(multiTargets, resolvedSearchPath, globFilter, {
|
|
224
240
|
rewrites: normalizedRewrites,
|
|
225
241
|
dryRun: true,
|
|
@@ -351,6 +367,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
351
367
|
for (const filePath of previewedFiles) {
|
|
352
368
|
await assertEditableFile(resolvePlanPath(this.session, filePath), filePath, this.session.settings);
|
|
353
369
|
}
|
|
370
|
+
await assertSingleFileReferenceRoot();
|
|
354
371
|
const applyResult = await runAstEditOnce(multiTargets, resolvedSearchPath, globFilter, {
|
|
355
372
|
rewrites: normalizedRewrites,
|
|
356
373
|
dryRun: false,
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -11,6 +11,7 @@ import astGrepDescription from "../prompts/tools/ast-grep.md" with { type: "text
|
|
|
11
11
|
import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
12
12
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
13
13
|
import type { ToolSession } from ".";
|
|
14
|
+
import { wrapUntrustedContent } from "./fetch";
|
|
14
15
|
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
15
16
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
16
17
|
import { formatMatchLine } from "./match-line-format";
|
|
@@ -221,7 +222,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
221
222
|
const parseMessage = cappedParseErrors.length
|
|
222
223
|
? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
|
|
223
224
|
: "";
|
|
224
|
-
return toolResult(baseDetails)
|
|
225
|
+
return toolResult(baseDetails)
|
|
226
|
+
.text(wrapUntrustedContent(`${noMatchMessage}${parseMessage}`))
|
|
227
|
+
.done();
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
|
|
@@ -291,7 +294,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
291
294
|
outputLines.push("", ...formatParseErrors(cappedParseErrors, parseErrorsTotal));
|
|
292
295
|
}
|
|
293
296
|
|
|
294
|
-
return toolResult(details)
|
|
297
|
+
return toolResult(details)
|
|
298
|
+
.text(wrapUntrustedContent(outputLines.join("\n")))
|
|
299
|
+
.done();
|
|
295
300
|
});
|
|
296
301
|
}
|
|
297
302
|
}
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Destination symlinks are followed: the referent is replaced, the link stays.
|
|
27
27
|
* The referent is re-resolved immediately before publication so a retargeted
|
|
28
|
-
* link cannot silently repoint the write
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* link cannot silently repoint the write. When the lexical destination sits
|
|
29
|
+
* inside the active explicit artifacts local root (`<artifacts>/local`) or a
|
|
30
|
+
* session-scoped `gjc-local` root, the resolved referent and its parent must
|
|
31
|
+
* remain inside that root (a link there must not redirect the write out of
|
|
32
|
+
* the trust boundary).
|
|
32
33
|
*
|
|
33
34
|
* Staging uses exclusive create (`wx`) so a colliding leftover temp is never
|
|
34
35
|
* truncated or unlinked; only the temp this call created is cleaned on failure.
|
|
@@ -44,6 +45,7 @@ import * as fs from "node:fs/promises";
|
|
|
44
45
|
import * as os from "node:os";
|
|
45
46
|
import * as path from "node:path";
|
|
46
47
|
import { hasFsCode, isEacces, isEisdir, isEnoent, isFsError } from "@gajae-code/utils";
|
|
48
|
+
import { LocalProtocolHandler } from "../internal-urls/local-protocol";
|
|
47
49
|
|
|
48
50
|
const TEMP_CREATE_ATTEMPTS = 8;
|
|
49
51
|
const DEFAULT_FILE_MODE = 0o666;
|
|
@@ -111,9 +113,10 @@ export function formatFileWriteError(error: unknown, dest: string, options: { de
|
|
|
111
113
|
export interface WriteFileAtomicallyOptions {
|
|
112
114
|
/**
|
|
113
115
|
* Trusted root that a resolved symlink referent and its parent must not
|
|
114
|
-
* leave. When omitted,
|
|
115
|
-
*
|
|
116
|
-
*
|
|
116
|
+
* leave. When omitted, a destination inside the active explicit artifacts
|
|
117
|
+
* local root (`<artifacts>/local`) uses that root. Otherwise the helper
|
|
118
|
+
* still enforces the session-scoped `gjc-local` boundary implied by a
|
|
119
|
+
* lexical destination under `<tmpdir>/gjc-local/<session-id>`.
|
|
117
120
|
*/
|
|
118
121
|
trustBoundary?: string;
|
|
119
122
|
/** Platform override used by deterministic retry tests. */
|
|
@@ -170,11 +173,20 @@ function pathIsWithin(target: string, root: string): boolean {
|
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
async function realpathOrSelf(p: string): Promise<string> {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const resolved = path.resolve(p);
|
|
177
|
+
const missing: string[] = [];
|
|
178
|
+
let cursor = resolved;
|
|
179
|
+
for (;;) {
|
|
180
|
+
try {
|
|
181
|
+
const real = await fs.realpath(cursor);
|
|
182
|
+
return missing.length === 0 ? real : path.join(real, ...missing.reverse());
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (!isEnoent(error)) throw error;
|
|
185
|
+
const parent = path.dirname(cursor);
|
|
186
|
+
if (parent === cursor) return resolved;
|
|
187
|
+
missing.push(path.basename(cursor));
|
|
188
|
+
cursor = parent;
|
|
189
|
+
}
|
|
178
190
|
}
|
|
179
191
|
}
|
|
180
192
|
|
|
@@ -193,6 +205,25 @@ function sessionLocalRootFor(lexicalDest: string): string | undefined {
|
|
|
193
205
|
return path.join(localParent, sessionSegment);
|
|
194
206
|
}
|
|
195
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Active explicit artifacts root (`<artifacts>/local`) when `lexicalDest` is
|
|
210
|
+
* inside it. That root is not under `<tmpdir>/gjc-local`, so the tmp helper
|
|
211
|
+
* above would leave the write unbounded. Managed sessions keep the tmp root.
|
|
212
|
+
*/
|
|
213
|
+
function explicitLocalRootFor(lexicalDest: string): string | undefined {
|
|
214
|
+
const options = LocalProtocolHandler.resolveOptions();
|
|
215
|
+
if (!options || options.isManagedDestination?.()) return undefined;
|
|
216
|
+
const artifactsDir = options.getArtifactsDir?.();
|
|
217
|
+
if (!artifactsDir) return undefined;
|
|
218
|
+
const root = path.resolve(artifactsDir, "local");
|
|
219
|
+
if (!pathIsWithin(path.resolve(lexicalDest), root)) return undefined;
|
|
220
|
+
return root;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function trustBoundaryForDestination(lexicalDest: string): string | undefined {
|
|
224
|
+
return explicitLocalRootFor(lexicalDest) ?? sessionLocalRootFor(lexicalDest);
|
|
225
|
+
}
|
|
226
|
+
|
|
196
227
|
/**
|
|
197
228
|
* Reject publication when the resolved referent's real parent (and therefore
|
|
198
229
|
* the referent itself) leaves the trust boundary. The boundary root is
|
|
@@ -201,8 +232,8 @@ function sessionLocalRootFor(lexicalDest: string): string | undefined {
|
|
|
201
232
|
* This must run before any directory is created. A dangling symlink inside a
|
|
202
233
|
* trusted root resolves to a path outside it, so creating parents first would
|
|
203
234
|
* let an attacker-selected directory tree be materialized outside the sandbox
|
|
204
|
-
* before publication is refused.
|
|
205
|
-
*
|
|
235
|
+
* before publication is refused. A missing path is resolved through the
|
|
236
|
+
* nearest existing ancestor's realpath, then the missing suffix is appended.
|
|
206
237
|
*/
|
|
207
238
|
async function assertWithinTrustBoundary(publishPath: string, trustBoundary: string): Promise<void> {
|
|
208
239
|
const boundary = path.resolve(trustBoundary);
|
|
@@ -502,7 +533,7 @@ export async function writeFileAtomically(
|
|
|
502
533
|
try {
|
|
503
534
|
const platform = options.platform ?? process.platform;
|
|
504
535
|
const sleep = options.sleep ?? (async (delayMs: number): Promise<void> => await Bun.sleep(delayMs));
|
|
505
|
-
const trustBoundary = options.trustBoundary ??
|
|
536
|
+
const trustBoundary = options.trustBoundary ?? trustBoundaryForDestination(dest);
|
|
506
537
|
const resolved = await resolvePublishPath(dest);
|
|
507
538
|
publishPath = resolved.publishPath;
|
|
508
539
|
// Boundary first, then create parents. A dangling symlink inside a trusted
|
|
@@ -36,6 +36,8 @@ const AUTO_GENERATED_HEADER_MARKERS: readonly RegExp[] = [
|
|
|
36
36
|
/\bcode\s+generated\s+by\s+[a-z0-9_.-]+/i,
|
|
37
37
|
/\bthis\s+file\s+was\s+automatically\s+generated\b/i,
|
|
38
38
|
new RegExp(`\\bgenerated\\s+by\\s+${KNOWN_GENERATOR_PATTERN}\\b`, "i"),
|
|
39
|
+
/\bgenerated\s+by\s+scripts\/[a-z0-9_./-]+/i,
|
|
40
|
+
/\bauto-generated\s+by\s+[a-z0-9_./-]+/i,
|
|
39
41
|
];
|
|
40
42
|
|
|
41
43
|
type CommentStyle = "slash" | "hash" | "sql" | "html";
|
|
@@ -100,6 +102,7 @@ const AUTO_GENERATED_FILENAME_PATTERNS = [
|
|
|
100
102
|
/_pb2\.py$/,
|
|
101
103
|
/_pb2_grpc\.py$/,
|
|
102
104
|
/\.gen\.(go|ts|js|py)$/,
|
|
105
|
+
/\.generated\.ts$/,
|
|
103
106
|
/^generated\.(go|ts|js|py)$/,
|
|
104
107
|
/\.swagger\.json$/,
|
|
105
108
|
/\.openapi\.json$/,
|
|
@@ -209,8 +212,14 @@ function extractLeadingHeaderCommentText(content: string, commentStyles: readonl
|
|
|
209
212
|
* Check if a file name indicates it might be auto-generated.
|
|
210
213
|
* This is a quick pre-check before reading file content.
|
|
211
214
|
*/
|
|
215
|
+
function guardFileName(filePath: string): string {
|
|
216
|
+
// resolvePlanPath keeps the platform separator. Anchored patterns such as
|
|
217
|
+
// zz_generated.* and generated.ts must see that basename on Windows too.
|
|
218
|
+
return path.win32.basename(filePath);
|
|
219
|
+
}
|
|
220
|
+
|
|
212
221
|
function isAutoGeneratedFileName(filePath: string): boolean {
|
|
213
|
-
const fileName = filePath
|
|
222
|
+
const fileName = guardFileName(filePath);
|
|
214
223
|
return AUTO_GENERATED_FILENAME_PATTERNS.some(pattern => pattern.test(fileName));
|
|
215
224
|
}
|
|
216
225
|
|
|
@@ -251,11 +260,14 @@ const autoGeneratedMap = new LRUCache<string, { marker: string | undefined }>({
|
|
|
251
260
|
|
|
252
261
|
async function getAutoGeneratedMarker(filePath: string): Promise<string | undefined> {
|
|
253
262
|
if (isAutoGeneratedFileName(filePath)) {
|
|
254
|
-
return filePath
|
|
263
|
+
return guardFileName(filePath);
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
const cached = autoGeneratedMap.get(filePath);
|
|
258
|
-
|
|
267
|
+
// A cached miss only means the file was handwritten at the last read.
|
|
268
|
+
// An earlier patch entry can write a generated header before a later
|
|
269
|
+
// rename checks the same destination, so do not trust that miss.
|
|
270
|
+
if (cached && cached.marker !== undefined) return cached.marker;
|
|
259
271
|
|
|
260
272
|
let marker: string | undefined;
|
|
261
273
|
try {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects a running command that printed an interactive auth prompt and is now
|
|
3
|
+
* waiting for a human (browser sign-in URL or device code).
|
|
4
|
+
*
|
|
5
|
+
* The agent cannot see this on its own: the tool call is simply still running.
|
|
6
|
+
* The detector watches the streamed output tail and fires once per command when
|
|
7
|
+
* all of these hold:
|
|
8
|
+
* - a known auth-prompt signature is present in the recent output tail;
|
|
9
|
+
* - the command is still running (the caller stops feeding it on exit);
|
|
10
|
+
* - no new output arrived for `quietMs` after the match.
|
|
11
|
+
*
|
|
12
|
+
* Requiring the quiet window keeps `cat README.md` or `grep` over docs that
|
|
13
|
+
* merely mention "enter the code" from firing: those commands exit or keep
|
|
14
|
+
* printing. The signal carries only the prompt kind, never the URL or code,
|
|
15
|
+
* because both are short-lived credential-equivalent values.
|
|
16
|
+
*/
|
|
17
|
+
import { stripVTControlCharacters } from "node:util";
|
|
18
|
+
|
|
19
|
+
export type AwaitingInputKind = "browser_auth" | "device_code";
|
|
20
|
+
|
|
21
|
+
export interface AwaitingInputSignal {
|
|
22
|
+
kind: AwaitingInputKind;
|
|
23
|
+
/** Epoch milliseconds when the quiet window elapsed after the match. */
|
|
24
|
+
detectedAt: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Cancels a scheduled callback. */
|
|
28
|
+
export type CancelScheduled = () => void;
|
|
29
|
+
|
|
30
|
+
export interface AwaitingInputDetectorOptions {
|
|
31
|
+
onDetect: (signal: AwaitingInputSignal) => void;
|
|
32
|
+
/** Quiet window after a match before the signal fires. */
|
|
33
|
+
quietMs?: number;
|
|
34
|
+
/** Injectable scheduler; defaults to `setTimeout`. */
|
|
35
|
+
schedule?: (callback: () => void, ms: number) => CancelScheduled;
|
|
36
|
+
/** Injectable clock; defaults to `Date.now`. */
|
|
37
|
+
now?: () => number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const AWAITING_INPUT_QUIET_MS = 3_000;
|
|
41
|
+
|
|
42
|
+
/** Only the most recent output can be the prompt the process is blocked on. */
|
|
43
|
+
const TAIL_CHARS = 2_048;
|
|
44
|
+
|
|
45
|
+
const URL_PATTERN = /\bhttps?:\/\/\S+/i;
|
|
46
|
+
|
|
47
|
+
const DEVICE_CODE_PATTERNS: readonly RegExp[] = [
|
|
48
|
+
/\bone-time code\b/i,
|
|
49
|
+
/\benter the code\b/i,
|
|
50
|
+
/\benter code\b/i,
|
|
51
|
+
/\buser_code=/i,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const BROWSER_AUTH_PATTERNS: readonly RegExp[] = [
|
|
55
|
+
/\bto authenticate,? visit\b/i,
|
|
56
|
+
/\blogin\.tailscale\.com\/a\//i,
|
|
57
|
+
/\bgo to the following link in your browser\b/i,
|
|
58
|
+
/\bopen the following url\b/i,
|
|
59
|
+
/\bvisit this url to authori[sz]e\b/i,
|
|
60
|
+
/\bopen (?:this|the) (?:url|link) in (?:a|your) browser\b/i,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** Classify an output tail; `undefined` when it does not look like an auth prompt. */
|
|
64
|
+
export function classifyAwaitingInput(tail: string): AwaitingInputKind | undefined {
|
|
65
|
+
const text = stripVTControlCharacters(tail);
|
|
66
|
+
if (DEVICE_CODE_PATTERNS.some(pattern => pattern.test(text))) return "device_code";
|
|
67
|
+
if (URL_PATTERN.test(text) && BROWSER_AUTH_PATTERNS.some(pattern => pattern.test(text))) return "browser_auth";
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function defaultSchedule(callback: () => void, ms: number): CancelScheduled {
|
|
72
|
+
const timer = setTimeout(callback, ms);
|
|
73
|
+
return () => clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class AwaitingInputDetector {
|
|
77
|
+
readonly #onDetect: (signal: AwaitingInputSignal) => void;
|
|
78
|
+
readonly #quietMs: number;
|
|
79
|
+
readonly #schedule: (callback: () => void, ms: number) => CancelScheduled;
|
|
80
|
+
readonly #now: () => number;
|
|
81
|
+
#tail = "";
|
|
82
|
+
#cancel: CancelScheduled | undefined;
|
|
83
|
+
#done = false;
|
|
84
|
+
|
|
85
|
+
constructor(options: AwaitingInputDetectorOptions) {
|
|
86
|
+
this.#onDetect = options.onDetect;
|
|
87
|
+
this.#quietMs = options.quietMs ?? AWAITING_INPUT_QUIET_MS;
|
|
88
|
+
this.#schedule = options.schedule ?? defaultSchedule;
|
|
89
|
+
this.#now = options.now ?? Date.now;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Feed one streamed output chunk. Any new output restarts the quiet window. */
|
|
93
|
+
push(chunk: string): void {
|
|
94
|
+
if (this.#done || chunk.length === 0) return;
|
|
95
|
+
this.#cancel?.();
|
|
96
|
+
this.#cancel = undefined;
|
|
97
|
+
this.#tail = (this.#tail + chunk).slice(-TAIL_CHARS);
|
|
98
|
+
const kind = classifyAwaitingInput(this.#tail);
|
|
99
|
+
if (!kind) return;
|
|
100
|
+
this.#cancel = this.#schedule(() => {
|
|
101
|
+
this.#cancel = undefined;
|
|
102
|
+
if (this.#done) return;
|
|
103
|
+
this.#done = true;
|
|
104
|
+
this.#onDetect({ kind, detectedAt: this.#now() });
|
|
105
|
+
}, this.#quietMs);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Stop watching; the command exited or was folded. Never fires afterwards. */
|
|
109
|
+
dispose(): void {
|
|
110
|
+
this.#done = true;
|
|
111
|
+
this.#cancel?.();
|
|
112
|
+
this.#cancel = undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|