@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
|
@@ -11,7 +11,7 @@ import type { AgentMessage } from "@gajae-code/agent-core";
|
|
|
11
11
|
import type { ImageContent } from "@gajae-code/ai/core";
|
|
12
12
|
import type { glob as globFn } from "@gajae-code/natives";
|
|
13
13
|
import { fuzzyMatch } from "@gajae-code/tui";
|
|
14
|
-
import { formatAge, formatBytes, readImageMetadata } from "@gajae-code/utils";
|
|
14
|
+
import { formatAge, formatBytes, pathIsWithin, readImageMetadata, resolveEquivalentPath } from "@gajae-code/utils";
|
|
15
15
|
import { formatHashLines } from "../hashline/hash";
|
|
16
16
|
import type { FileMentionMessage } from "../session/messages";
|
|
17
17
|
import {
|
|
@@ -80,6 +80,7 @@ type MentionCandidate = {
|
|
|
80
80
|
path: string;
|
|
81
81
|
pathLower: string;
|
|
82
82
|
normalizedPath: string;
|
|
83
|
+
segments: string[];
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
function normalizeMentionQuery(query: string): string {
|
|
@@ -117,11 +118,37 @@ async function listMentionCandidates(cwd: string): Promise<MentionCandidate[]> {
|
|
|
117
118
|
if (normalizedPath.length === 0) {
|
|
118
119
|
continue;
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
+
const segments = pathLower.split(PATH_SEPARATOR_REGEX).filter(segment => segment.length > 0);
|
|
122
|
+
candidates.push({ path: entry, pathLower, normalizedPath, segments });
|
|
121
123
|
}
|
|
122
124
|
return candidates;
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
/**
|
|
128
|
+
* True when `query` can be spelled as prefixes of path segments taken in order
|
|
129
|
+
* (segments may be skipped), e.g. `httpserap` → `http_server_api_tests`.
|
|
130
|
+
* Plain subsequence matching would also accept letters scattered mid-word across
|
|
131
|
+
* any long path, so a stray `@handle` or `@word` would resolve to an unrelated file.
|
|
132
|
+
*/
|
|
133
|
+
function matchesSegmentPrefixes(query: string, segments: string[]): boolean {
|
|
134
|
+
const failed = new Set<number>();
|
|
135
|
+
const visit = (segmentIndex: number, queryIndex: number): boolean => {
|
|
136
|
+
if (queryIndex === query.length) return true;
|
|
137
|
+
if (segmentIndex === segments.length) return false;
|
|
138
|
+
const key = segmentIndex * (query.length + 1) + queryIndex;
|
|
139
|
+
if (failed.has(key)) return false;
|
|
140
|
+
const segment = segments[segmentIndex]!;
|
|
141
|
+
for (let taken = 1; taken <= segment.length && queryIndex + taken <= query.length; taken++) {
|
|
142
|
+
if (segment[taken - 1] !== query[queryIndex + taken - 1]) break;
|
|
143
|
+
if (visit(segmentIndex + 1, queryIndex + taken)) return true;
|
|
144
|
+
}
|
|
145
|
+
if (visit(segmentIndex + 1, queryIndex)) return true;
|
|
146
|
+
failed.add(key);
|
|
147
|
+
return false;
|
|
148
|
+
};
|
|
149
|
+
return visit(0, 0);
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
async function resolveMentionPath(
|
|
126
153
|
filePath: string,
|
|
127
154
|
cwd: string,
|
|
@@ -148,6 +175,7 @@ async function resolveMentionPath(
|
|
|
148
175
|
}
|
|
149
176
|
|
|
150
177
|
const scored = candidates
|
|
178
|
+
.filter(candidate => matchesSegmentPrefixes(normalizedQuery, candidate.segments))
|
|
151
179
|
.map(candidate => ({ candidate, match: fuzzyMatch(normalizedQuery, candidate.normalizedPath) }))
|
|
152
180
|
.filter(entry => entry.match.matches)
|
|
153
181
|
.sort((a, b) => {
|
|
@@ -285,6 +313,26 @@ export function extractFileMentions(text: string): string[] {
|
|
|
285
313
|
return [...new Set(mentions)];
|
|
286
314
|
}
|
|
287
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Compare canonical Windows paths without folding directory names: individual
|
|
318
|
+
* directories can opt into case-sensitive names.
|
|
319
|
+
*/
|
|
320
|
+
export function isCanonicalWindowsPathWithin(root: string, candidate: string): boolean {
|
|
321
|
+
if (!path.win32.isAbsolute(root) || !path.win32.isAbsolute(candidate)) return false;
|
|
322
|
+
|
|
323
|
+
const canonicalRoot = path.win32.resolve(root);
|
|
324
|
+
const canonicalCandidate = path.win32.resolve(candidate);
|
|
325
|
+
const rootParts = path.win32.parse(canonicalRoot);
|
|
326
|
+
const candidateParts = path.win32.parse(canonicalCandidate);
|
|
327
|
+
if (rootParts.root.toLowerCase() !== candidateParts.root.toLowerCase()) return false;
|
|
328
|
+
|
|
329
|
+
const rootSegments = canonicalRoot.slice(rootParts.root.length).split(path.win32.sep).filter(Boolean);
|
|
330
|
+
const candidateSegments = canonicalCandidate.slice(candidateParts.root.length).split(path.win32.sep).filter(Boolean);
|
|
331
|
+
if (candidateSegments.length < rootSegments.length) return false;
|
|
332
|
+
|
|
333
|
+
return rootSegments.every((segment, index) => segment === candidateSegments[index]);
|
|
334
|
+
}
|
|
335
|
+
|
|
288
336
|
/**
|
|
289
337
|
* Generate a FileMentionMessage containing the contents of mentioned files.
|
|
290
338
|
* Returns empty array if no files could be read.
|
|
@@ -308,6 +356,8 @@ export async function generateFileMentionMessages(
|
|
|
308
356
|
if (filePaths.length === 0) return [];
|
|
309
357
|
|
|
310
358
|
const autoResizeImages = options?.autoResizeImages ?? true;
|
|
359
|
+
const canonicalWindowsCwd = process.platform === "win32" ? await fs.realpath(cwd).catch(() => undefined) : undefined;
|
|
360
|
+
if (process.platform === "win32" && canonicalWindowsCwd === undefined) return [];
|
|
311
361
|
|
|
312
362
|
const files: FileMentionMessage["files"] = [];
|
|
313
363
|
let mentionCandidatesPromise: Promise<MentionCandidate[]> | null = null;
|
|
@@ -323,6 +373,24 @@ export async function generateFileMentionMessages(
|
|
|
323
373
|
continue;
|
|
324
374
|
}
|
|
325
375
|
const absolutePath = resolveReadPath(resolvedPath, cwd);
|
|
376
|
+
// Skip `..`, `~`, and outside absolute paths. On Windows, realpath both
|
|
377
|
+
// paths and compare exact components because directories can be case-sensitive.
|
|
378
|
+
// Fail closed if either real path is unavailable. Read the canonical path so
|
|
379
|
+
// replacing the original mention symlink does not retarget this read.
|
|
380
|
+
const canonicalPath =
|
|
381
|
+
process.platform === "win32"
|
|
382
|
+
? await fs.realpath(absolutePath).catch(() => undefined)
|
|
383
|
+
: resolveEquivalentPath(absolutePath);
|
|
384
|
+
if (!canonicalPath) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const staysWithinSession =
|
|
388
|
+
process.platform === "win32"
|
|
389
|
+
? canonicalWindowsCwd !== undefined && isCanonicalWindowsPathWithin(canonicalWindowsCwd, canonicalPath)
|
|
390
|
+
: pathIsWithin(cwd, canonicalPath);
|
|
391
|
+
if (!staysWithinSession) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
326
394
|
// Duplicate suppression, keyed by absolute path: same path shown earlier this
|
|
327
395
|
// batch or recently in the conversation → compact note, no re-read (the path
|
|
328
396
|
// stays referenced so the model can re-fetch via the read tool).
|
|
@@ -336,14 +404,14 @@ export async function generateFileMentionMessages(
|
|
|
336
404
|
}
|
|
337
405
|
shownInBatch.add(absolutePath);
|
|
338
406
|
try {
|
|
339
|
-
const stat = await Bun.file(
|
|
407
|
+
const stat = await Bun.file(canonicalPath).stat();
|
|
340
408
|
if (stat.isDirectory()) {
|
|
341
|
-
const { output, lineCount } = await buildDirectoryListing(
|
|
409
|
+
const { output, lineCount } = await buildDirectoryListing(canonicalPath);
|
|
342
410
|
files.push({ path: resolvedPath, content: output, lineCount });
|
|
343
411
|
continue;
|
|
344
412
|
}
|
|
345
413
|
|
|
346
|
-
const imageMetadata = await readImageMetadata(
|
|
414
|
+
const imageMetadata = await readImageMetadata(canonicalPath);
|
|
347
415
|
const mimeType = imageMetadata?.mimeType;
|
|
348
416
|
if (mimeType) {
|
|
349
417
|
if (stat.size > MAX_AUTO_READ_IMAGE_BYTES) {
|
|
@@ -355,7 +423,7 @@ export async function generateFileMentionMessages(
|
|
|
355
423
|
});
|
|
356
424
|
continue;
|
|
357
425
|
}
|
|
358
|
-
const buffer = await fs.readFile(
|
|
426
|
+
const buffer = await fs.readFile(canonicalPath);
|
|
359
427
|
if (buffer.length === 0) {
|
|
360
428
|
continue;
|
|
361
429
|
}
|
|
@@ -392,7 +460,7 @@ export async function generateFileMentionMessages(
|
|
|
392
460
|
continue;
|
|
393
461
|
}
|
|
394
462
|
|
|
395
|
-
const content = await Bun.file(
|
|
463
|
+
const content = await Bun.file(canonicalPath).text();
|
|
396
464
|
let { output, lineCount } = buildTextOutput(content, options?.maxInlineBytes);
|
|
397
465
|
if (options?.useHashLines) {
|
|
398
466
|
output = formatHashLines(output);
|
package/src/utils/git.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { $which, hasFsCode, isEnoent
|
|
4
|
+
import { $which, hasFsCode, isEnoent } from "@gajae-code/utils";
|
|
5
|
+
import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
|
|
5
6
|
import {
|
|
6
7
|
parseDiffHunks as parseCommitDiffHunks,
|
|
7
8
|
parseFileDiffs,
|
|
@@ -198,7 +199,10 @@ async function runCommand(
|
|
|
198
199
|
const commandArgs = withShortLivedGitConfig(options.readOnly ? withNoOptionalLocks(args) : [...args]);
|
|
199
200
|
const child = Bun.spawn(["git", ...commandArgs], {
|
|
200
201
|
cwd,
|
|
201
|
-
|
|
202
|
+
// The inherited environment holds the autoloaded project `.env`; without
|
|
203
|
+
// filtering, a repository could set e.g. `GIT_CONFIG_*` (`core.hooksPath`)
|
|
204
|
+
// or `GIT_SSH_COMMAND` and have GJC's own commit/fetch/push run its code.
|
|
205
|
+
env: options.env ? { ...projectSafeSpawnEnv(), GIT_OPTIONAL_LOCKS: "0", ...options.env } : projectSafeSpawnEnv(),
|
|
202
206
|
signal: options.signal,
|
|
203
207
|
stdin: normalizeStdin(options.stdin),
|
|
204
208
|
stdout: "pipe",
|
|
@@ -365,9 +369,24 @@ function buildApplyArgs(patchPath: string, options: PatchOptions): string[] {
|
|
|
365
369
|
}
|
|
366
370
|
|
|
367
371
|
async function writeTempPatch(content: string): Promise<string> {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
372
|
+
// Random directory plus an exclusive create: a preexisting symlink under the
|
|
373
|
+
// shared temp directory is not opened, and the patch is not left world-readable.
|
|
374
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "gjc-git-patch-"));
|
|
375
|
+
try {
|
|
376
|
+
if (process.platform !== "win32") await fs.promises.chmod(tempDir, 0o700);
|
|
377
|
+
const tempPath = path.join(tempDir, "patch");
|
|
378
|
+
const handle = await fs.promises.open(tempPath, "wx", 0o600);
|
|
379
|
+
try {
|
|
380
|
+
await handle.writeFile(content);
|
|
381
|
+
} finally {
|
|
382
|
+
await handle.close();
|
|
383
|
+
}
|
|
384
|
+
if (process.platform !== "win32") await fs.promises.chmod(tempPath, 0o600);
|
|
385
|
+
return tempPath;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
371
390
|
}
|
|
372
391
|
|
|
373
392
|
// ════════════════════════════════════════════════════════════════════════════
|
|
@@ -1232,7 +1251,7 @@ export const patch = {
|
|
|
1232
1251
|
try {
|
|
1233
1252
|
await patch.apply(cwd, tempPath, options);
|
|
1234
1253
|
} finally {
|
|
1235
|
-
await fs.promises.rm(tempPath, { force: true });
|
|
1254
|
+
await fs.promises.rm(path.dirname(tempPath), { recursive: true, force: true });
|
|
1236
1255
|
}
|
|
1237
1256
|
},
|
|
1238
1257
|
|
|
@@ -1253,7 +1272,7 @@ export const patch = {
|
|
|
1253
1272
|
try {
|
|
1254
1273
|
return await patch.canApply(cwd, tempPath, options);
|
|
1255
1274
|
} finally {
|
|
1256
|
-
await fs.promises.rm(tempPath, { force: true });
|
|
1275
|
+
await fs.promises.rm(path.dirname(tempPath), { recursive: true, force: true });
|
|
1257
1276
|
}
|
|
1258
1277
|
},
|
|
1259
1278
|
|
|
@@ -1594,6 +1613,14 @@ function formatGhFailure(args: readonly string[], stdout: string, stderr: string
|
|
|
1594
1613
|
return `GitHub CLI command failed: gh ${args.join(" ")}`;
|
|
1595
1614
|
}
|
|
1596
1615
|
|
|
1616
|
+
/** Captured `gh` output never wants a pager or forced-terminal behavior. */
|
|
1617
|
+
function ghSpawnEnv(): Record<string, string> {
|
|
1618
|
+
const env = projectSafeSpawnEnv();
|
|
1619
|
+
delete env.GH_FORCE_TTY;
|
|
1620
|
+
env.GH_PAGER = "cat";
|
|
1621
|
+
return env;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1597
1624
|
export const github = {
|
|
1598
1625
|
/** Check if `gh` CLI is installed. */
|
|
1599
1626
|
available(): boolean {
|
|
@@ -1609,6 +1636,10 @@ export const github = {
|
|
|
1609
1636
|
try {
|
|
1610
1637
|
const child = Bun.spawn(["gh", ...args], {
|
|
1611
1638
|
cwd,
|
|
1639
|
+
// Same project-dotenv filtering as git: `gh` runs git for checkout
|
|
1640
|
+
// and push. Output is captured, so gh must never page or act as if
|
|
1641
|
+
// on a terminal; pin both so a project `.env` cannot select a pager.
|
|
1642
|
+
env: ghSpawnEnv(),
|
|
1612
1643
|
stdin: "ignore",
|
|
1613
1644
|
stdout: "pipe",
|
|
1614
1645
|
stderr: "pipe",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Shared input policy must remain independent of file loading, tools and sessions.
|
|
2
|
+
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
3
|
+
export const MAX_STAGED_PROMPT_IMAGES = 16;
|
|
4
|
+
export const MAX_PASTED_IMAGE_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
export const MAX_PASTED_IMAGE_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
6
|
+
export const MAX_PASTED_IMAGE_DIMENSION = 20_000;
|
|
7
|
+
export const MAX_PASTED_IMAGE_PIXELS = 40_000_000;
|
|
8
|
+
export const MAX_PASTED_IMAGE_DECODED_BYTES = 160 * 1024 * 1024;
|
|
@@ -2,9 +2,9 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import type { ImageContent } from "@gajae-code/ai/core";
|
|
3
3
|
import { formatBytes, readImageMetadata, SUPPORTED_IMAGE_MIME_TYPES } from "@gajae-code/utils";
|
|
4
4
|
import { resolveReadPath } from "../tools/path-utils";
|
|
5
|
+
import { MAX_IMAGE_INPUT_BYTES } from "./image-limits";
|
|
5
6
|
import { formatDimensionNote, resizeImageBuffer } from "./image-resize";
|
|
6
7
|
|
|
7
|
-
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
8
8
|
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
|
|
9
9
|
|
|
10
10
|
const SVG_MIME_TYPE = "image/svg+xml";
|
package/src/utils/open.ts
CHANGED
|
@@ -1,15 +1,84 @@
|
|
|
1
|
-
/** Open
|
|
1
|
+
/** Open an http(s) URL or local path. Best-effort, never throws. */
|
|
2
|
+
const ALLOWED_OPEN_PROTOCOLS = new Set(["http:", "https:"]);
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Zero-width and non-ASCII spaces. ASCII controls are rejected, not stripped:
|
|
6
|
+
* the URL parser drops some of them and would turn a hostile string into a clean href.
|
|
7
|
+
*/
|
|
8
|
+
const OUTER_FORMAT = /[\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF\u200B\u2060]/;
|
|
9
|
+
|
|
10
|
+
function hasAsciiControl(value: string): boolean {
|
|
11
|
+
for (let i = 0; i < value.length; i++) {
|
|
12
|
+
const code = value.charCodeAt(i);
|
|
13
|
+
if (code <= 0x1f || code === 0x7f) return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isOuterPadding(ch: string): boolean {
|
|
19
|
+
return ch.trim() === "" || OUTER_FORMAT.test(ch);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Drop every outer space or format character. Classification only; local paths stay raw. */
|
|
23
|
+
function peelPadding(value: string): string {
|
|
24
|
+
let start = 0;
|
|
25
|
+
let end = value.length;
|
|
26
|
+
while (start < end && isOuterPadding(value.charAt(start))) start++;
|
|
27
|
+
while (end > start && isOuterPadding(value.charAt(end - 1))) end--;
|
|
28
|
+
return start === 0 && end === value.length ? value : value.slice(start, end);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Drive paths parse as one-letter schemes (`c:`). Recognize them before that check. */
|
|
32
|
+
function isWindowsDrivePath(value: string): boolean {
|
|
33
|
+
return /^[A-Za-z]:[\\/]/.test(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SCHEME_PREFIX = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
37
|
+
|
|
38
|
+
/** http(s) href, "reject" for any other URL, or undefined when `value` is not a URL. */
|
|
39
|
+
function classifyUrl(value: string): string | "reject" | undefined {
|
|
40
|
+
if (isWindowsDrivePath(value) || isWindowsDrivePath(value.trim())) return undefined;
|
|
41
|
+
try {
|
|
42
|
+
const parsed = new URL(value);
|
|
43
|
+
if (!ALLOWED_OPEN_PROTOCOLS.has(parsed.protocol) || hasAsciiControl(parsed.href)) return "reject";
|
|
44
|
+
return parsed.href;
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveOpenTarget(raw: string): string | undefined {
|
|
51
|
+
if (hasAsciiControl(raw) || raw.length === 0) return undefined;
|
|
52
|
+
// Local paths, including surrounding spaces, are spawned unchanged.
|
|
53
|
+
if (isWindowsDrivePath(raw)) return raw;
|
|
54
|
+
const direct = classifyUrl(raw);
|
|
55
|
+
if (direct === "reject") return undefined;
|
|
56
|
+
if (direct !== undefined) return direct;
|
|
57
|
+
|
|
58
|
+
const peeled = peelPadding(raw);
|
|
59
|
+
if (peeled.length === 0) return undefined;
|
|
60
|
+
// Padding can hide a scheme or a flag. Refuse that instead of opening a different string.
|
|
61
|
+
if (peeled !== raw && !isWindowsDrivePath(peeled)) {
|
|
62
|
+
const hidden = classifyUrl(peeled);
|
|
63
|
+
if (hidden !== undefined || peeled.startsWith("-") || SCHEME_PREFIX.test(peeled)) return undefined;
|
|
64
|
+
}
|
|
65
|
+
if (raw.startsWith("-") || SCHEME_PREFIX.test(raw)) return undefined;
|
|
66
|
+
return raw;
|
|
67
|
+
}
|
|
68
|
+
|
|
2
69
|
export function openPath(urlOrPath: string): void {
|
|
70
|
+
const target = resolveOpenTarget(urlOrPath);
|
|
71
|
+
if (target === undefined) return;
|
|
3
72
|
let cmd: string[];
|
|
4
73
|
switch (process.platform) {
|
|
5
74
|
case "darwin":
|
|
6
|
-
cmd = ["open",
|
|
75
|
+
cmd = ["open", target];
|
|
7
76
|
break;
|
|
8
77
|
case "win32":
|
|
9
|
-
cmd = ["rundll32", "url.dll,FileProtocolHandler",
|
|
78
|
+
cmd = ["rundll32", "url.dll,FileProtocolHandler", target];
|
|
10
79
|
break;
|
|
11
80
|
default:
|
|
12
|
-
cmd = ["xdg-open",
|
|
81
|
+
cmd = ["xdg-open", target];
|
|
13
82
|
break;
|
|
14
83
|
}
|
|
15
84
|
try {
|
|
@@ -4,10 +4,17 @@ import * as os from "node:os";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { ImageContent } from "@gajae-code/ai/core";
|
|
6
6
|
import { formatBytes, parseImageMetadata } from "@gajae-code/utils";
|
|
7
|
+
import {
|
|
8
|
+
MAX_IMAGE_INPUT_BYTES,
|
|
9
|
+
MAX_PASTED_IMAGE_DECODED_BYTES,
|
|
10
|
+
MAX_PASTED_IMAGE_DIMENSION,
|
|
11
|
+
MAX_PASTED_IMAGE_OUTPUT_BYTES,
|
|
12
|
+
MAX_PASTED_IMAGE_PIXELS,
|
|
13
|
+
MAX_PASTED_IMAGE_SOURCE_BYTES,
|
|
14
|
+
} from "./image-limits";
|
|
7
15
|
import {
|
|
8
16
|
ImageInputTooLargeError,
|
|
9
17
|
type LoadedImageInput,
|
|
10
|
-
MAX_IMAGE_INPUT_BYTES,
|
|
11
18
|
materializeImageInput,
|
|
12
19
|
type TransformedImageInput,
|
|
13
20
|
transformImageInputBytes,
|
|
@@ -15,12 +22,6 @@ import {
|
|
|
15
22
|
import { DEFAULT_IMAGE_RESIZE_MAX_BYTES } from "./image-resize";
|
|
16
23
|
import { MAX_PASTED_IMAGE_COUNT } from "./pasted-image-path";
|
|
17
24
|
|
|
18
|
-
export const MAX_PASTED_IMAGE_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
19
|
-
export const MAX_PASTED_IMAGE_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
20
|
-
export const MAX_PASTED_IMAGE_DIMENSION = 20_000;
|
|
21
|
-
export const MAX_PASTED_IMAGE_PIXELS = 40_000_000;
|
|
22
|
-
export const MAX_PASTED_IMAGE_DECODED_BYTES = 160 * 1024 * 1024;
|
|
23
|
-
|
|
24
25
|
export type PastedImageBatchErrorCode =
|
|
25
26
|
| "too-many"
|
|
26
27
|
| "unsafe-path"
|
|
@@ -319,7 +320,7 @@ function hasWebpAnimation(bytes: Buffer): boolean {
|
|
|
319
320
|
return false;
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
function isAnimatedImage(bytes: Buffer, mimeType: string): boolean | undefined {
|
|
323
|
+
export function isAnimatedImage(bytes: Buffer, mimeType: string): boolean | undefined {
|
|
323
324
|
if (mimeType === "image/gif") {
|
|
324
325
|
const frames = gifFrameCount(bytes);
|
|
325
326
|
return frames === undefined ? undefined : frames > 1;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Predict the user's likely next prompt after an agent turn
|
|
3
|
-
*
|
|
2
|
+
* Predict the user's likely next prompt after an agent turn with the `default`
|
|
3
|
+
* model role. Native GJC port of the ghost-text prompt-suggestion behavior
|
|
4
4
|
* from Claude Code: the prediction renders as dim ghost text in the empty
|
|
5
5
|
* composer and Tab accepts it.
|
|
6
6
|
*/
|
|
@@ -169,8 +169,9 @@ function getSuggestionModel(
|
|
|
169
169
|
registry: ModelRegistry,
|
|
170
170
|
settings: Settings,
|
|
171
171
|
currentModel?: Model<Api>,
|
|
172
|
+
credentialSessionId?: string,
|
|
172
173
|
): Model<Api> | undefined {
|
|
173
|
-
const availableModels = registry.getAvailable();
|
|
174
|
+
const availableModels = registry.getAvailable(credentialSessionId);
|
|
174
175
|
if (availableModels.length === 0) return undefined;
|
|
175
176
|
const model = resolveRoleSelection(["default"], settings, availableModels, registry)?.model;
|
|
176
177
|
if (model) return model;
|
|
@@ -203,7 +204,7 @@ export async function generatePromptSuggestion(
|
|
|
203
204
|
const context = buildPromptSuggestionContext(messages);
|
|
204
205
|
if (!context) return null;
|
|
205
206
|
|
|
206
|
-
const model = getSuggestionModel(registry, settings, currentModel);
|
|
207
|
+
const model = getSuggestionModel(registry, settings, currentModel, sessionId);
|
|
207
208
|
if (!model) {
|
|
208
209
|
logger.debug("prompt-suggestion: no model available");
|
|
209
210
|
return null;
|
|
@@ -31,6 +31,11 @@ function getShellConfigFile(shell: string, home: string): string {
|
|
|
31
31
|
return path.join(home, ".profile");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** One POSIX shell word. Double quotes would still run `$()` and backticks. */
|
|
35
|
+
export function quotePosixShell(value: string): string {
|
|
36
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
/**
|
|
35
40
|
* Generate the snapshot creation script.
|
|
36
41
|
* This script sources the user's rc file and extracts functions, aliases, and options.
|
|
@@ -43,6 +48,9 @@ function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: str
|
|
|
43
48
|
|
|
44
49
|
// Escape the snapshot path for shell
|
|
45
50
|
const escapedPath = snapshotPath.replace(/'/g, "'\\''");
|
|
51
|
+
const rcSource = hasRcFile
|
|
52
|
+
? `source ${quotePosixShell(rcFile)} < /dev/null 2>/dev/null`
|
|
53
|
+
: "# No user config file to source";
|
|
46
54
|
|
|
47
55
|
// Function extraction differs between bash and zsh
|
|
48
56
|
const functionScript = isZsh
|
|
@@ -83,7 +91,7 @@ umask 077
|
|
|
83
91
|
SNAPSHOT_FILE='${escapedPath}'
|
|
84
92
|
|
|
85
93
|
# Source user's rc file if it exists
|
|
86
|
-
${
|
|
94
|
+
${rcSource}
|
|
87
95
|
|
|
88
96
|
# Create/clear the snapshot file
|
|
89
97
|
echo "# Shell snapshot - generated by gjc agent" >| "$SNAPSHOT_FILE"
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate session titles
|
|
2
|
+
* Generate session titles with the `default` model role.
|
|
3
3
|
*/
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
|
|
6
6
|
import type { AgentMessage } from "@gajae-code/agent-core";
|
|
7
7
|
import { type Api, type AssistantMessage, completeSimple, type Model, type Tool } from "@gajae-code/ai/core";
|
|
8
|
-
import { logger, prompt } from "@gajae-code/utils";
|
|
8
|
+
import { logger, prompt, redactCrashSecrets } from "@gajae-code/utils";
|
|
9
9
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
10
|
import { resolveRoleSelection } from "../config/model-resolver";
|
|
11
11
|
import type { Settings } from "../config/settings";
|
|
12
12
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
13
|
+
import { collectEnvSecrets, createSecretObfuscator, loadSecrets } from "../secrets";
|
|
13
14
|
import { syncCmuxWorkspaceTitle } from "./cmux-workspace";
|
|
14
15
|
import { syncHerdrPaneTitle } from "./herdr-pane";
|
|
15
16
|
|
|
@@ -37,8 +38,13 @@ const MAX_TITLE_WORDS = 12;
|
|
|
37
38
|
* Build a bounded conversation digest for title regeneration. Regeneration
|
|
38
39
|
* reads several messages so a throwaway first prompt can be corrected, while
|
|
39
40
|
* the automatic first-message path intentionally uses only its initial input.
|
|
41
|
+
* `obfuscateText`, when set, runs on each kept message's full text before the
|
|
42
|
+
* per-message character cut so a secret cannot be split by that cut.
|
|
40
43
|
*/
|
|
41
|
-
export function buildConversationTitleInput(
|
|
44
|
+
export function buildConversationTitleInput(
|
|
45
|
+
messages: readonly AgentMessage[],
|
|
46
|
+
obfuscateText?: (text: string) => string,
|
|
47
|
+
): string | undefined {
|
|
42
48
|
const userMessages: string[] = [];
|
|
43
49
|
for (const message of messages) {
|
|
44
50
|
if (message.role !== "user") continue;
|
|
@@ -51,7 +57,10 @@ export function buildConversationTitleInput(messages: readonly AgentMessage[]):
|
|
|
51
57
|
.map(block => block.text)
|
|
52
58
|
.join("\n");
|
|
53
59
|
const trimmed = text.trim();
|
|
54
|
-
if (trimmed)
|
|
60
|
+
if (!trimmed) continue;
|
|
61
|
+
// Emptiness uses the trimmed text. With an obfuscator, keep the original
|
|
62
|
+
// so a secret that includes edge whitespace still matches, then trim.
|
|
63
|
+
userMessages.push(obfuscateText ? text : trimmed);
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
if (userMessages.length === 0) return undefined;
|
|
@@ -60,13 +69,19 @@ export function buildConversationTitleInput(messages: readonly AgentMessage[]):
|
|
|
60
69
|
userMessages.length > MAX_TITLE_CONVERSATION_MESSAGES
|
|
61
70
|
? [userMessages[0]!, ...userMessages.slice(-(MAX_TITLE_CONVERSATION_MESSAGES - 1))]
|
|
62
71
|
: userMessages;
|
|
72
|
+
// Obfuscate the full kept text first. The 400-character cut below, and the
|
|
73
|
+
// later 2,000-character cap, then keep their existing limits.
|
|
74
|
+
const preparedMessages = obfuscateText
|
|
75
|
+
? keptMessages.map(message => obfuscateText(message).trim()).filter(message => message.length > 0)
|
|
76
|
+
: keptMessages;
|
|
77
|
+
if (preparedMessages.length === 0) return undefined;
|
|
63
78
|
// Reserve separator and ellipsis room so the digest is already within the
|
|
64
79
|
// generator's MAX_INPUT_CHARS bound; its existing truncation is a no-op here.
|
|
65
80
|
const maxMessageChars = Math.min(
|
|
66
81
|
MAX_TITLE_MESSAGE_CHARS,
|
|
67
|
-
Math.floor((MAX_INPUT_CHARS - (
|
|
82
|
+
Math.floor((MAX_INPUT_CHARS - (preparedMessages.length - 1) - preparedMessages.length) / preparedMessages.length),
|
|
68
83
|
);
|
|
69
|
-
return
|
|
84
|
+
return preparedMessages
|
|
70
85
|
.map(message => (message.length > maxMessageChars ? `${message.slice(0, maxMessageChars)}…` : message))
|
|
71
86
|
.join("\n");
|
|
72
87
|
}
|
|
@@ -87,8 +102,13 @@ const setTitleTool: Tool = {
|
|
|
87
102
|
},
|
|
88
103
|
};
|
|
89
104
|
|
|
90
|
-
function getTitleModel(
|
|
91
|
-
|
|
105
|
+
function getTitleModel(
|
|
106
|
+
registry: ModelRegistry,
|
|
107
|
+
settings: Settings,
|
|
108
|
+
currentModel?: Model<Api>,
|
|
109
|
+
credentialSessionId?: string,
|
|
110
|
+
): Model<Api> | undefined {
|
|
111
|
+
const availableModels = registry.getAvailable(credentialSessionId);
|
|
92
112
|
if (availableModels.length === 0) return undefined;
|
|
93
113
|
|
|
94
114
|
const titleModel = resolveRoleSelection(["default"], settings, availableModels, registry)?.model;
|
|
@@ -99,6 +119,32 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
|
|
|
99
119
|
return undefined;
|
|
100
120
|
}
|
|
101
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Obfuscate the full title input, then truncate, then wrap.
|
|
124
|
+
* A secret that crosses the character cap is replaced before the cut, and the
|
|
125
|
+
* `<user-message>` fence stays outside the truncated body.
|
|
126
|
+
*/
|
|
127
|
+
export function prepareTitleOutboundText(firstMessage: string, obfuscateText?: (text: string) => string): string {
|
|
128
|
+
const source = obfuscateText ? obfuscateText(firstMessage) : firstMessage;
|
|
129
|
+
const truncated = source.length > MAX_INPUT_CHARS ? `${source.slice(0, MAX_INPUT_CHARS)}…` : source;
|
|
130
|
+
return `<user-message>\n${truncated}\n</user-message>`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function titleInputObfuscator(
|
|
134
|
+
settings: Settings,
|
|
135
|
+
activeCwd?: string,
|
|
136
|
+
): Promise<((text: string) => string) | undefined> {
|
|
137
|
+
const reader = settings as Partial<Pick<Settings, "get" | "getCwd" | "getAgentDir">>;
|
|
138
|
+
if (typeof reader.get !== "function" || reader.get("secrets.enabled") !== true) return undefined;
|
|
139
|
+
const cwd = activeCwd ?? (typeof reader.getCwd === "function" ? reader.getCwd() : process.cwd());
|
|
140
|
+
const agentDir = typeof reader.getAgentDir === "function" ? reader.getAgentDir() : cwd;
|
|
141
|
+
const entries = [...collectEnvSecrets(), ...(await loadSecrets(cwd, agentDir))];
|
|
142
|
+
if (entries.length === 0) return undefined;
|
|
143
|
+
const obfuscator = createSecretObfuscator(entries);
|
|
144
|
+
if (!obfuscator.hasSecrets()) return undefined;
|
|
145
|
+
return text => obfuscator.obfuscate(text);
|
|
146
|
+
}
|
|
147
|
+
|
|
102
148
|
/**
|
|
103
149
|
* Generate a title for a session based on the provided user-message input.
|
|
104
150
|
* Automatic titles pass the first user message; regeneration passes a bounded
|
|
@@ -106,13 +152,14 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
|
|
|
106
152
|
*
|
|
107
153
|
* @param firstMessage The first user message or bounded conversation digest
|
|
108
154
|
* @param registry Model registry
|
|
109
|
-
* @param settings Settings used to resolve the
|
|
155
|
+
* @param settings Settings used to resolve the `default` model role
|
|
110
156
|
* @param sessionId Optional session id for sticky API key selection
|
|
111
157
|
* @param currentModel Current model (used to derive title model)
|
|
112
158
|
* @param metadataResolver Optional resolver evaluated after credential selection
|
|
113
159
|
* to produce request metadata (e.g. user_id for session attribution). Using a
|
|
114
160
|
* resolver instead of a pre-evaluated value ensures the metadata's account_uuid
|
|
115
161
|
* reflects the credential actually selected for this request.
|
|
162
|
+
* @param activeCwd Active session cwd for project-scoped secret lookup.
|
|
116
163
|
*/
|
|
117
164
|
export async function generateSessionTitle(
|
|
118
165
|
firstMessage: string,
|
|
@@ -121,23 +168,17 @@ export async function generateSessionTitle(
|
|
|
121
168
|
sessionId?: string,
|
|
122
169
|
currentModel?: Model<Api>,
|
|
123
170
|
metadataResolver?: (provider: string) => Record<string, unknown> | undefined,
|
|
171
|
+
activeCwd?: string,
|
|
124
172
|
): Promise<string | null> {
|
|
125
|
-
const model = getTitleModel(registry, settings, currentModel);
|
|
173
|
+
const model = getTitleModel(registry, settings, currentModel, sessionId);
|
|
126
174
|
if (!model) {
|
|
127
175
|
logger.debug("title-generator: no title model found");
|
|
128
176
|
return null;
|
|
129
177
|
}
|
|
130
178
|
|
|
131
|
-
// Truncate message if too long
|
|
132
|
-
const truncatedMessage =
|
|
133
|
-
firstMessage.length > MAX_INPUT_CHARS ? `${firstMessage.slice(0, MAX_INPUT_CHARS)}…` : firstMessage;
|
|
134
|
-
const userMessage = `<user-message>
|
|
135
|
-
${truncatedMessage}
|
|
136
|
-
</user-message>`;
|
|
137
|
-
|
|
138
179
|
const apiKey = await registry.getApiKey(model, sessionId);
|
|
139
180
|
if (!apiKey) {
|
|
140
|
-
logger.debug("title-generator: no API key for
|
|
181
|
+
logger.debug("title-generator: no API key for title model", {
|
|
141
182
|
provider: model.provider,
|
|
142
183
|
id: model.id,
|
|
143
184
|
});
|
|
@@ -147,6 +188,7 @@ ${truncatedMessage}
|
|
|
147
188
|
// request is already recorded; metadataResolver can then return the correct
|
|
148
189
|
// account_uuid rather than the snapshot-at-call-site value.
|
|
149
190
|
const metadata = metadataResolver?.(model.provider);
|
|
191
|
+
const userMessage = prepareTitleOutboundText(firstMessage, await titleInputObfuscator(settings, activeCwd));
|
|
150
192
|
|
|
151
193
|
// Title generation is a 3-6 word task, but some reasoning backends ignore
|
|
152
194
|
// disableReasoning. Keep the normal cheap budget for non-reasoning models
|
|
@@ -159,7 +201,12 @@ ${truncatedMessage}
|
|
|
159
201
|
userMessage,
|
|
160
202
|
maxTokens,
|
|
161
203
|
};
|
|
162
|
-
logger.debug("title-generator: request",
|
|
204
|
+
logger.debug("title-generator: request", {
|
|
205
|
+
model: request.model,
|
|
206
|
+
systemPrompt: request.systemPrompt,
|
|
207
|
+
userMessage: redactCrashSecrets(request.userMessage),
|
|
208
|
+
maxTokens: request.maxTokens,
|
|
209
|
+
});
|
|
163
210
|
|
|
164
211
|
try {
|
|
165
212
|
const response = await completeSimple(
|
|
@@ -182,7 +229,10 @@ ${truncatedMessage}
|
|
|
182
229
|
logger.debug("title-generator: response error", {
|
|
183
230
|
model: request.model,
|
|
184
231
|
stopReason: response.stopReason,
|
|
185
|
-
errorMessage:
|
|
232
|
+
errorMessage:
|
|
233
|
+
typeof response.errorMessage === "string"
|
|
234
|
+
? redactCrashSecrets(response.errorMessage)
|
|
235
|
+
: response.errorMessage,
|
|
186
236
|
});
|
|
187
237
|
return null;
|
|
188
238
|
}
|
|
@@ -191,7 +241,7 @@ ${truncatedMessage}
|
|
|
191
241
|
|
|
192
242
|
logger.debug("title-generator: response", {
|
|
193
243
|
model: request.model,
|
|
194
|
-
title,
|
|
244
|
+
title: redactCrashSecrets(title),
|
|
195
245
|
usage: response.usage,
|
|
196
246
|
stopReason: response.stopReason,
|
|
197
247
|
});
|
|
@@ -204,7 +254,7 @@ ${truncatedMessage}
|
|
|
204
254
|
} catch (err) {
|
|
205
255
|
logger.debug("title-generator: error", {
|
|
206
256
|
model: request.model,
|
|
207
|
-
error: err instanceof Error ? err.message : String(err),
|
|
257
|
+
error: redactCrashSecrets(err instanceof Error ? err.message : String(err)),
|
|
208
258
|
});
|
|
209
259
|
return null;
|
|
210
260
|
}
|