@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
|
@@ -2157,19 +2157,54 @@ const eventWebhookTails = new Map<string, Promise<void>>();
|
|
|
2157
2157
|
const eventWebhookStartupTails = new Map<string, Promise<void>>();
|
|
2158
2158
|
const EVENT_WEBHOOK_ERROR_CAP = 240;
|
|
2159
2159
|
|
|
2160
|
+
const EVENT_WEBHOOK_DIAGNOSTIC_CAP = 512;
|
|
2161
|
+
|
|
2162
|
+
/** Printable ASCII only, so a journal id cannot inject another diagnostic line. */
|
|
2163
|
+
function webhookEventDiagnosticToken(eventId: string, maxLength: number): string {
|
|
2164
|
+
if (maxLength <= 0) return "";
|
|
2165
|
+
let token = "";
|
|
2166
|
+
for (let index = 0; index < eventId.length && token.length < maxLength; index += 1) {
|
|
2167
|
+
const code = eventId.charCodeAt(index);
|
|
2168
|
+
if (code >= 0x20 && code <= 0x7e) token += eventId[index];
|
|
2169
|
+
}
|
|
2170
|
+
return token;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/** Test-only access for generated normalization and truncation properties. */
|
|
2174
|
+
export function webhookEventDiagnosticTokenForTest(eventId: string, maxLength: number): string {
|
|
2175
|
+
return webhookEventDiagnosticToken(eventId, maxLength);
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2160
2178
|
async function appendEventWebhookDiagnostic(namespaceDir: string, eventId: string, error: unknown): Promise<void> {
|
|
2161
2179
|
const code =
|
|
2162
2180
|
error instanceof Error && /^[a-z0-9_]+$/.test(error.message)
|
|
2163
2181
|
? error.message.slice(0, EVENT_WEBHOOK_ERROR_CAP)
|
|
2164
2182
|
: "event_webhook_delivery_failed";
|
|
2165
|
-
const
|
|
2183
|
+
const timestamp = new Date().toISOString();
|
|
2184
|
+
const prefix = `${timestamp} event=`;
|
|
2185
|
+
const suffix = ` error=${code}\n`;
|
|
2186
|
+
const token = webhookEventDiagnosticToken(eventId, EVENT_WEBHOOK_DIAGNOSTIC_CAP - prefix.length - suffix.length);
|
|
2187
|
+
const line = `${prefix}${token}${suffix}`;
|
|
2166
2188
|
try {
|
|
2167
|
-
await fs.appendFile(
|
|
2189
|
+
await fs.appendFile(
|
|
2190
|
+
path.join(namespaceDir, "event-webhook-errors.log"),
|
|
2191
|
+
line.slice(0, EVENT_WEBHOOK_DIAGNOSTIC_CAP),
|
|
2192
|
+
{ mode: 0o600 },
|
|
2193
|
+
);
|
|
2168
2194
|
} catch {
|
|
2169
2195
|
// Diagnostics are best-effort; sink problems must never propagate upward.
|
|
2170
2196
|
}
|
|
2171
2197
|
}
|
|
2172
2198
|
|
|
2199
|
+
/** Test-only access for diagnostic record boundary coverage. */
|
|
2200
|
+
export async function appendEventWebhookDiagnosticForTest(
|
|
2201
|
+
namespaceDir: string,
|
|
2202
|
+
eventId: string,
|
|
2203
|
+
error: unknown,
|
|
2204
|
+
): Promise<void> {
|
|
2205
|
+
await appendEventWebhookDiagnostic(namespaceDir, eventId, error);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2173
2208
|
function enqueueEventWebhook(namespaceDir: string, event: CoordinatorEvent): void {
|
|
2174
2209
|
const config = eventWebhookConfigs.get(namespaceDir);
|
|
2175
2210
|
if (config === null || config === undefined) return;
|
package/src/crash/sanitize.ts
CHANGED
|
@@ -49,6 +49,10 @@ const RESIDUAL_PATTERNS: readonly RegExp[] = [
|
|
|
49
49
|
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\./,
|
|
50
50
|
/\bxox[baprs]-[A-Za-z0-9-]{8,}/,
|
|
51
51
|
/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/,
|
|
52
|
+
// Same shapes `redactCrashSecrets` rewrites. A survivor means this scanner
|
|
53
|
+
// is not certain, so the submission is refused rather than sent raw.
|
|
54
|
+
/(?<![A-Za-z0-9/+])(?=[A-Za-z0-9/+]{0,39}[/+])[A-Za-z0-9/+]{40}(?![A-Za-z0-9/+])/,
|
|
55
|
+
/(?<![A-Za-z0-9_%])session(?:id|[_-]id)%3[Dd][A-Za-z0-9._~%-]{8,}/i,
|
|
52
56
|
// Opaque URIs the URL rule cannot parse into an origin, and inline payloads.
|
|
53
57
|
/\bdata:[^\s]{0,64};base64,/i,
|
|
54
58
|
/:\/\//,
|
|
@@ -95,8 +99,10 @@ export function sanitizeExternalCrashV1(text: string, maxBytes: number = CRASH_F
|
|
|
95
99
|
let value = text.replace(/\r\n?/g, "\n");
|
|
96
100
|
value = value.replace(OSC_PATTERN, " ").replace(CSI_PATTERN, "");
|
|
97
101
|
value = value.replace(CONTROL_PATTERN, "").replace(INVISIBLE_PATTERN, "");
|
|
102
|
+
// Redact each URL separately so an unquoted query value cannot absorb prose
|
|
103
|
+
// after the URL; then sanitize the remaining crash text normally.
|
|
104
|
+
value = value.replace(URL_PATTERN, match => sanitizeUrl(redactCrashSecrets(match)));
|
|
98
105
|
value = redactCrashSecrets(value);
|
|
99
|
-
value = value.replace(URL_PATTERN, match => sanitizeUrl(match));
|
|
100
106
|
value = replaceAbsolutePaths(value);
|
|
101
107
|
value = value.replace(UUID_PATTERN, "<uuid>");
|
|
102
108
|
value = value.replace(HEX_RUN_PATTERN, match => (/[a-fA-F]/.test(match) ? "<hex>" : "<num>"));
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* - Hooks: Claude `.claude/hooks/<pre|post>/<file>` and Codex flat
|
|
9
9
|
* `.codex/hooks/<phase>-<tool>.ts|js` conventions, normalized through the
|
|
10
10
|
* canonical hook IR (`normalizeDirectoryHook`, #4286/#4289) and written to
|
|
11
|
-
* the
|
|
11
|
+
* the canonical `<root>/hooks/<pre|post>/` layout. Project-scope hooks are
|
|
12
|
+
* stored-only; ordinary sessions do not load repository hook modules.
|
|
12
13
|
* - MCPs: `normalizeClaudeMcpJson` / `normalizeCodexMcpToml` plus
|
|
13
14
|
* `validateMCPCompatServer` (#4284 bounded compatibility adapters), merged
|
|
14
15
|
* through the canonical atomic config writer.
|
|
@@ -17,12 +18,16 @@
|
|
|
17
18
|
* secret values live only in the separate `ImportPlan`. `applyImport` runs as
|
|
18
19
|
* a validated transaction: every destination path is containment- and
|
|
19
20
|
* symlink-checked before any write, destinations are revalidated against the
|
|
20
|
-
* preview decisions, files are published
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* preview decisions, and skill/hook files are published through a no-follow
|
|
22
|
+
* directory descriptor with same-directory temp+rename. Any failure restores
|
|
23
|
+
* exactly the files this transaction wrote (pre-existing symlinks are never
|
|
24
|
+
* touched). Verification failures roll back too.
|
|
23
25
|
*/
|
|
26
|
+
import { dlopen, FFIType, read } from "bun:ffi";
|
|
24
27
|
import type { Dirent } from "node:fs";
|
|
28
|
+
import * as fsSync from "node:fs";
|
|
25
29
|
import * as fs from "node:fs/promises";
|
|
30
|
+
import * as os from "node:os";
|
|
26
31
|
import * as path from "node:path";
|
|
27
32
|
import { parseFrontmatter, tryParseJson } from "@gajae-code/utils";
|
|
28
33
|
import { YAML } from "bun";
|
|
@@ -351,6 +356,11 @@ export async function buildImportPreview(options: BuildImportPreviewOptions): Pr
|
|
|
351
356
|
const entries: ImportPreviewEntry[] = [];
|
|
352
357
|
const payloads: (NormalizedPayload | undefined)[] = [];
|
|
353
358
|
const warnings: string[] = [];
|
|
359
|
+
if (surfaces.includes("hooks") && options.destinationScope === "project") {
|
|
360
|
+
warnings.push(
|
|
361
|
+
"Project-scope hooks are stored-only: ordinary sessions do not load repository hook modules. Import hooks into Global .gjc or configure an explicit hook path to activate them.",
|
|
362
|
+
);
|
|
363
|
+
}
|
|
354
364
|
const productName = productLabel(options.product);
|
|
355
365
|
const sourceLabel = `${productName} ${sourceScopeLabel(options.sourceScope)}`;
|
|
356
366
|
|
|
@@ -538,7 +548,9 @@ export async function buildImportPreview(options: BuildImportPreviewOptions): Pr
|
|
|
538
548
|
read.kind === "value"
|
|
539
549
|
? options.product === "claude-code"
|
|
540
550
|
? normalizeClaudeMcpJson(read.content, sourcePath, options.sourceScope)
|
|
541
|
-
: normalizeCodexMcpToml(read.content, sourcePath, options.sourceScope
|
|
551
|
+
: normalizeCodexMcpToml(read.content, sourcePath, options.sourceScope, {
|
|
552
|
+
omitEnvReferences: options.destinationScope === "project",
|
|
553
|
+
})
|
|
542
554
|
: { items: [], warnings: [] as string[] };
|
|
543
555
|
for (const warning of normalized.warnings) warnings.push(`${sourceLabel} MCPs: ${warning}`);
|
|
544
556
|
const unsupportedNestedAuth =
|
|
@@ -634,6 +646,9 @@ interface FileSnapshot {
|
|
|
634
646
|
path: string;
|
|
635
647
|
/** Previous content, or null when the file did not exist. */
|
|
636
648
|
previous: string | null;
|
|
649
|
+
/** Set when publication was bound to this directory descriptor. */
|
|
650
|
+
parentFd?: number;
|
|
651
|
+
name?: string;
|
|
637
652
|
}
|
|
638
653
|
|
|
639
654
|
interface PlannedFileWrite {
|
|
@@ -673,6 +688,13 @@ async function findSymlinkedAncestor(root: string, target: string): Promise<stri
|
|
|
673
688
|
|
|
674
689
|
/** Remove stale staging temp files from an interrupted earlier apply. */
|
|
675
690
|
async function sweepStagingTemps(dir: string): Promise<void> {
|
|
691
|
+
try {
|
|
692
|
+
const stat = await fs.lstat(dir);
|
|
693
|
+
// readdir follows a swapped directory symlink; never clean through one.
|
|
694
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) return;
|
|
695
|
+
} catch {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
676
698
|
let names: string[];
|
|
677
699
|
try {
|
|
678
700
|
names = await fs.readdir(dir);
|
|
@@ -685,6 +707,209 @@ async function sweepStagingTemps(dir: string): Promise<void> {
|
|
|
685
707
|
}
|
|
686
708
|
}
|
|
687
709
|
|
|
710
|
+
const { ENOENT, EEXIST, ELOOP, ENOTDIR } = os.constants.errno;
|
|
711
|
+
const CLOEXEC = (fsSync.constants as { O_CLOEXEC?: number }).O_CLOEXEC ?? 0;
|
|
712
|
+
const NOFOLLOW = fsSync.constants.O_NOFOLLOW ?? 0;
|
|
713
|
+
const DIR_FLAGS = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0) | NOFOLLOW | CLOEXEC;
|
|
714
|
+
const READ_FLAGS = fsSync.constants.O_RDONLY | (fsSync.constants.O_NONBLOCK ?? 0) | NOFOLLOW | CLOEXEC;
|
|
715
|
+
const WRITE_FLAGS = fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL | NOFOLLOW | CLOEXEC;
|
|
716
|
+
const MAX_PINNED_IMPORT_PARENTS = 128;
|
|
717
|
+
|
|
718
|
+
interface PinnedDestination {
|
|
719
|
+
fds: number[];
|
|
720
|
+
parentFd: number;
|
|
721
|
+
name: string;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
type DirectoryOps = {
|
|
725
|
+
mkdirat: (dirfd: number, name: string, mode: number) => number;
|
|
726
|
+
openat: (dirfd: number, name: string, flags: number, mode: number) => number;
|
|
727
|
+
renameat: (fromfd: number, from: string, tofd: number, to: string) => number;
|
|
728
|
+
unlinkat: (dirfd: number, name: string, flags: number) => number;
|
|
729
|
+
errno: () => number;
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
let directoryOpsCache: DirectoryOps | undefined;
|
|
733
|
+
|
|
734
|
+
/** POSIX only. Windows has no O_NOFOLLOW, so publication keeps the lexical recheck. */
|
|
735
|
+
function pinnedPublicationAvailable(): boolean {
|
|
736
|
+
return (
|
|
737
|
+
process.platform !== "win32" &&
|
|
738
|
+
fsSync.constants.O_NOFOLLOW !== undefined &&
|
|
739
|
+
fsSync.constants.O_DIRECTORY !== undefined
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function directoryOps(): DirectoryOps {
|
|
744
|
+
if (directoryOpsCache) return directoryOpsCache;
|
|
745
|
+
const darwin = process.platform === "darwin";
|
|
746
|
+
const lib = dlopen(darwin ? "/usr/lib/libSystem.B.dylib" : "libc.so.6", {
|
|
747
|
+
mkdirat: { args: [FFIType.i32, FFIType.cstring, FFIType.u32], returns: FFIType.i32 },
|
|
748
|
+
openat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32, FFIType.u32], returns: FFIType.i32 },
|
|
749
|
+
renameat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32, FFIType.cstring], returns: FFIType.i32 },
|
|
750
|
+
unlinkat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32], returns: FFIType.i32 },
|
|
751
|
+
...(darwin
|
|
752
|
+
? { __error: { args: [], returns: FFIType.ptr } }
|
|
753
|
+
: { __errno_location: { args: [], returns: FFIType.ptr } }),
|
|
754
|
+
});
|
|
755
|
+
const symbols = lib.symbols as unknown as DirectoryOps & {
|
|
756
|
+
__error?: () => Parameters<typeof read.i32>[0];
|
|
757
|
+
__errno_location?: () => Parameters<typeof read.i32>[0];
|
|
758
|
+
};
|
|
759
|
+
directoryOpsCache = {
|
|
760
|
+
mkdirat: symbols.mkdirat,
|
|
761
|
+
openat: symbols.openat,
|
|
762
|
+
renameat: symbols.renameat,
|
|
763
|
+
unlinkat: symbols.unlinkat,
|
|
764
|
+
errno: () => read.i32(darwin ? symbols.__error!() : symbols.__errno_location!()),
|
|
765
|
+
};
|
|
766
|
+
return directoryOpsCache;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function closeRawFds(fds: number[]): void {
|
|
770
|
+
for (const fd of fds.splice(0).reverse()) fsSync.closeSync(fd);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function symlinkRefusal(location: string): Error {
|
|
774
|
+
return new Error(`refusing to write through symlinked ancestor: ${location}`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** Lexical shape of an import destination. Symlink checks are separate. */
|
|
778
|
+
export function importDestinationShapeAllowed(root: string, target: string): boolean {
|
|
779
|
+
const relative = path.relative(root, path.dirname(target));
|
|
780
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) return false;
|
|
781
|
+
const segments = relative === "" ? [] : relative.split(path.sep);
|
|
782
|
+
return [...segments, path.basename(target)].every(isSafeName);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** Walk from `root` with openat/mkdirat and retain only the final parent descriptor. */
|
|
786
|
+
function pinImportParent(root: string, target: string): PinnedDestination {
|
|
787
|
+
if (!importDestinationShapeAllowed(root, target)) throw new Error(`refusing unsafe import destination: ${target}`);
|
|
788
|
+
const relative = path.relative(root, path.dirname(target));
|
|
789
|
+
const segments = relative === "" ? [] : relative.split(path.sep);
|
|
790
|
+
const name = path.basename(target);
|
|
791
|
+
const ops = directoryOps();
|
|
792
|
+
const fds: number[] = [];
|
|
793
|
+
try {
|
|
794
|
+
let parentFd: number;
|
|
795
|
+
try {
|
|
796
|
+
parentFd = fsSync.openSync(root, DIR_FLAGS);
|
|
797
|
+
} catch (error) {
|
|
798
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
799
|
+
if (code === "ELOOP" || code === "ENOTDIR") throw symlinkRefusal(root);
|
|
800
|
+
throw error;
|
|
801
|
+
}
|
|
802
|
+
fds.push(parentFd);
|
|
803
|
+
let cursor = root;
|
|
804
|
+
for (const segment of segments) {
|
|
805
|
+
cursor = path.join(cursor, segment);
|
|
806
|
+
let fd = ops.openat(parentFd, segment, DIR_FLAGS, 0);
|
|
807
|
+
if (fd < 0 && ops.errno() === ENOENT) {
|
|
808
|
+
if (ops.mkdirat(parentFd, segment, 0o700) < 0 && ops.errno() !== EEXIST) {
|
|
809
|
+
throw new Error(`cannot create import directory ${cursor}: errno ${ops.errno()}`);
|
|
810
|
+
}
|
|
811
|
+
fd = ops.openat(parentFd, segment, DIR_FLAGS, 0);
|
|
812
|
+
}
|
|
813
|
+
if (fd < 0) {
|
|
814
|
+
const err = ops.errno();
|
|
815
|
+
if (err === ELOOP || err === ENOTDIR) throw symlinkRefusal(cursor);
|
|
816
|
+
throw new Error(`cannot open import directory ${cursor}: errno ${err}`);
|
|
817
|
+
}
|
|
818
|
+
let isDirectory: boolean;
|
|
819
|
+
try {
|
|
820
|
+
isDirectory = fsSync.fstatSync(fd).isDirectory();
|
|
821
|
+
} catch (error) {
|
|
822
|
+
fsSync.closeSync(fd);
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
if (!isDirectory) {
|
|
826
|
+
fsSync.closeSync(fd);
|
|
827
|
+
throw symlinkRefusal(cursor);
|
|
828
|
+
}
|
|
829
|
+
closeRawFds(fds);
|
|
830
|
+
fds.push(fd);
|
|
831
|
+
parentFd = fd;
|
|
832
|
+
}
|
|
833
|
+
const named = fsSync.lstatSync(path.dirname(target));
|
|
834
|
+
const opened = fsSync.fstatSync(parentFd);
|
|
835
|
+
if (named.isSymbolicLink() || !named.isDirectory() || named.dev !== opened.dev || named.ino !== opened.ino) {
|
|
836
|
+
throw symlinkRefusal(path.dirname(target));
|
|
837
|
+
}
|
|
838
|
+
return { fds, parentFd, name };
|
|
839
|
+
} catch (error) {
|
|
840
|
+
closeRawFds(fds);
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function readRegularAt(parentFd: number, name: string, lexicalPath: string): string | null {
|
|
846
|
+
const ops = directoryOps();
|
|
847
|
+
const fd = ops.openat(parentFd, name, READ_FLAGS, 0);
|
|
848
|
+
if (fd < 0) {
|
|
849
|
+
const err = ops.errno();
|
|
850
|
+
if (err === ENOENT) return null;
|
|
851
|
+
if (err === ELOOP || err === ENOTDIR) throw new Error(`refusing to write through symlink: ${lexicalPath}`);
|
|
852
|
+
throw new Error(`cannot read ${lexicalPath}: errno ${err}`);
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
if (!fsSync.fstatSync(fd).isFile()) throw new Error(`not a regular file: ${lexicalPath}`);
|
|
856
|
+
return fsSync.readFileSync(fd, "utf-8");
|
|
857
|
+
} finally {
|
|
858
|
+
fsSync.closeSync(fd);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function writeRegularAt(parentFd: number, name: string, content: string, index: number, lexicalPath: string): void {
|
|
863
|
+
const ops = directoryOps();
|
|
864
|
+
const tmp = `.gjc-import-${process.pid}-${index}-${name}.tmp`;
|
|
865
|
+
const fd = ops.openat(parentFd, tmp, WRITE_FLAGS, 0o600);
|
|
866
|
+
if (fd < 0) throw new Error(`cannot stage import file ${lexicalPath}: errno ${ops.errno()}`);
|
|
867
|
+
try {
|
|
868
|
+
fsSync.writeFileSync(fd, content, "utf-8");
|
|
869
|
+
fsSync.fchmodSync(fd, 0o600);
|
|
870
|
+
} finally {
|
|
871
|
+
fsSync.closeSync(fd);
|
|
872
|
+
}
|
|
873
|
+
if (ops.renameat(parentFd, tmp, parentFd, name) < 0) {
|
|
874
|
+
const err = ops.errno();
|
|
875
|
+
ops.unlinkat(parentFd, tmp, 0);
|
|
876
|
+
throw new Error(`cannot publish import file ${lexicalPath}: errno ${err}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** The lexical parent must still be the directory the descriptor published into. */
|
|
881
|
+
function assertPinnedParentStillAt(parentFd: number, lexicalParent: string, lexicalPath: string): void {
|
|
882
|
+
let named: fsSync.Stats;
|
|
883
|
+
try {
|
|
884
|
+
named = fsSync.lstatSync(lexicalParent);
|
|
885
|
+
} catch {
|
|
886
|
+
throw new Error(`post-import verification failed: ${lexicalPath} was not persisted`);
|
|
887
|
+
}
|
|
888
|
+
const opened = fsSync.fstatSync(parentFd);
|
|
889
|
+
if (named.isSymbolicLink() || !named.isDirectory() || named.dev !== opened.dev || named.ino !== opened.ino) {
|
|
890
|
+
throw new Error(`post-import verification failed: ${lexicalPath} was not persisted`);
|
|
891
|
+
}
|
|
892
|
+
try {
|
|
893
|
+
const leaf = fsSync.lstatSync(lexicalPath);
|
|
894
|
+
if (leaf.isSymbolicLink() || !leaf.isFile()) {
|
|
895
|
+
throw new Error(`post-import verification failed: ${lexicalPath} was not persisted`);
|
|
896
|
+
}
|
|
897
|
+
} catch (error) {
|
|
898
|
+
if (error instanceof Error && error.message.startsWith("post-import verification failed:")) throw error;
|
|
899
|
+
throw new Error(`post-import verification failed: ${lexicalPath} was not persisted`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function restoreRegularAt(parentFd: number, name: string, previous: string | null, lexicalPath: string): void {
|
|
904
|
+
if (previous === null) {
|
|
905
|
+
if (directoryOps().unlinkat(parentFd, name, 0) < 0 && directoryOps().errno() !== ENOENT) {
|
|
906
|
+
throw new Error(`cannot remove ${lexicalPath}: errno ${directoryOps().errno()}`);
|
|
907
|
+
}
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
writeRegularAt(parentFd, name, previous, 1_000_000 + process.pid, lexicalPath);
|
|
911
|
+
}
|
|
912
|
+
|
|
688
913
|
/**
|
|
689
914
|
* Apply a confirmed plan. The transaction contract:
|
|
690
915
|
*
|
|
@@ -848,12 +1073,44 @@ export async function applyImport(plan: ImportPlan, options: { cwd: string }): P
|
|
|
848
1073
|
|
|
849
1074
|
// --- Phase 2: publication ------------------------------------------------
|
|
850
1075
|
const snapshots: FileSnapshot[] = [];
|
|
1076
|
+
const pins: PinnedDestination[] = [];
|
|
1077
|
+
const pinsByParent = new Map<string, PinnedDestination>();
|
|
851
1078
|
for (const dir of [destination.skillsDir, destination.hooksDir]) {
|
|
852
1079
|
await sweepStagingTemps(dir).catch(() => {});
|
|
853
1080
|
}
|
|
854
1081
|
|
|
855
1082
|
try {
|
|
1083
|
+
if (fileWrites.length > 0 && pinnedPublicationAvailable()) {
|
|
1084
|
+
// Scope root only. Children are created with mkdirat on the pinned fd.
|
|
1085
|
+
await fs.mkdir(destination.root, { recursive: true, mode: 0o700 });
|
|
1086
|
+
for (const write of fileWrites) {
|
|
1087
|
+
const parentPath = path.dirname(write.path);
|
|
1088
|
+
if (pinsByParent.has(parentPath)) continue;
|
|
1089
|
+
if (pinsByParent.size >= MAX_PINNED_IMPORT_PARENTS) {
|
|
1090
|
+
throw new Error(
|
|
1091
|
+
`import batch exceeds the limit of ${MAX_PINNED_IMPORT_PARENTS} distinct destination directories`,
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
const pin = pinImportParent(destination.root, write.path);
|
|
1095
|
+
pins.push(pin);
|
|
1096
|
+
pinsByParent.set(parentPath, pin);
|
|
1097
|
+
}
|
|
1098
|
+
// Fail before publishing anything if pinned directories leave no
|
|
1099
|
+
// descriptor available for staging or rollback.
|
|
1100
|
+
const descriptorProbe = fsSync.openSync(os.devNull, fsSync.constants.O_RDONLY | CLOEXEC);
|
|
1101
|
+
fsSync.closeSync(descriptorProbe);
|
|
1102
|
+
}
|
|
856
1103
|
for (const [index, write] of fileWrites.entries()) {
|
|
1104
|
+
if (pinnedPublicationAvailable()) {
|
|
1105
|
+
const pin = pinsByParent.get(path.dirname(write.path));
|
|
1106
|
+
if (!pin) throw new Error(`cannot pin import destination directory: ${path.dirname(write.path)}`);
|
|
1107
|
+
const previous = readRegularAt(pin.parentFd, pin.name, write.path);
|
|
1108
|
+
writeRegularAt(pin.parentFd, pin.name, write.content, index, write.path);
|
|
1109
|
+
snapshots.push({ path: write.path, previous, parentFd: pin.parentFd, name: pin.name });
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
const symlinked = await findSymlinkedAncestor(destination.root, write.path);
|
|
1113
|
+
if (symlinked) throw new Error(`refusing to write through symlinked ancestor: ${symlinked}`);
|
|
857
1114
|
await fs.mkdir(path.dirname(write.path), { recursive: true, mode: 0o700 });
|
|
858
1115
|
const stat = await fs.lstat(write.path).catch(() => null);
|
|
859
1116
|
if (stat?.isSymbolicLink()) throw new Error(`refusing to write through symlink: ${write.path}`);
|
|
@@ -889,8 +1146,18 @@ export async function applyImport(plan: ImportPlan, options: { cwd: string }): P
|
|
|
889
1146
|
|
|
890
1147
|
// --- Phase 3: verification (inside the transaction) ------------------
|
|
891
1148
|
for (const write of fileWrites) {
|
|
892
|
-
const
|
|
893
|
-
|
|
1149
|
+
const pinned = snapshots.find(snapshot => snapshot.path === write.path && snapshot.parentFd !== undefined);
|
|
1150
|
+
let persisted: string | null;
|
|
1151
|
+
if (pinned?.parentFd !== undefined && pinned.name) {
|
|
1152
|
+
assertPinnedParentStillAt(pinned.parentFd, path.dirname(write.path), write.path);
|
|
1153
|
+
persisted = readRegularAt(pinned.parentFd, pinned.name, write.path);
|
|
1154
|
+
assertPinnedParentStillAt(pinned.parentFd, path.dirname(write.path), write.path);
|
|
1155
|
+
} else {
|
|
1156
|
+
persisted = await readStructured(write.path).then(result =>
|
|
1157
|
+
result.kind === "value" ? result.content : null,
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
if (persisted !== write.content) {
|
|
894
1161
|
throw new Error(`post-import verification failed: ${write.path} was not persisted`);
|
|
895
1162
|
}
|
|
896
1163
|
}
|
|
@@ -908,7 +1175,9 @@ export async function applyImport(plan: ImportPlan, options: { cwd: string }): P
|
|
|
908
1175
|
const rollbackErrors: string[] = [];
|
|
909
1176
|
for (const snapshot of snapshots.reverse()) {
|
|
910
1177
|
try {
|
|
911
|
-
if (snapshot.
|
|
1178
|
+
if (snapshot.parentFd !== undefined && snapshot.name) {
|
|
1179
|
+
restoreRegularAt(snapshot.parentFd, snapshot.name, snapshot.previous, snapshot.path);
|
|
1180
|
+
} else if (snapshot.previous === null) {
|
|
912
1181
|
await fs.rm(snapshot.path, { force: true });
|
|
913
1182
|
} else {
|
|
914
1183
|
const staged = `${snapshot.path}.gjc-rollback-${process.pid}.tmp`;
|
|
@@ -926,6 +1195,8 @@ export async function applyImport(plan: ImportPlan, options: { cwd: string }): P
|
|
|
926
1195
|
`import failed and was rolled back: ${reason}` +
|
|
927
1196
|
(rollbackErrors.length > 0 ? `; ROLLBACK ERRORS (manual repair needed): ${rollbackErrors.join("; ")}` : ""),
|
|
928
1197
|
);
|
|
1198
|
+
} finally {
|
|
1199
|
+
for (const pin of pins) closeRawFds(pin.fds);
|
|
929
1200
|
}
|
|
930
1201
|
|
|
931
1202
|
for (const { entry } of writable) {
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* discovery/policy model (trust, include/ignore, disabledExtensions,
|
|
7
7
|
* bundled-name protection). A narrow supplemental scan flags SKILL.md files
|
|
8
8
|
* the management loader rejects as `invalid` rows.
|
|
9
|
-
* - Hooks: canonical `<root>/hooks/<pre|post>/` layout
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - Hooks: canonical `<root>/hooks/<pre|post>/` layout. Project hooks are
|
|
10
|
+
* stored-only and do not shadow user hooks because sessions never load
|
|
11
|
+
* repository hook modules. User-scope collisions follow the runtime's
|
|
12
|
+
* first-wins dedupe key (`<type>:<tool>:<name>`); losers are `shadowed`.
|
|
12
13
|
* - MCPs: canonical config files for both scopes with the runtime's
|
|
13
14
|
* project-first precedence and the union of disabled markers; malformed
|
|
14
15
|
* files surface as `invalid` rows without leaking raw JSON.
|
|
@@ -173,7 +174,8 @@ const HOOK_PHASES = ["pre", "post"] as const;
|
|
|
173
174
|
|
|
174
175
|
async function loadHookRows(options: LoadCustomizationInventoryOptions, _warnings: string[]): Promise<InventoryRow[]> {
|
|
175
176
|
const rows: InventoryRow[] = [];
|
|
176
|
-
//
|
|
177
|
+
// Repository hooks are deliberately excluded by session startup. They stay
|
|
178
|
+
// visible for management, but are stored-only and cannot shadow user hooks.
|
|
177
179
|
const seen = new Set<string>();
|
|
178
180
|
for (const scope of ["project", "global"] as const) {
|
|
179
181
|
const hooksDir = resolveScopePaths(scope, options.cwd).hooksDir;
|
|
@@ -190,19 +192,33 @@ async function loadHookRows(options: LoadCustomizationInventoryOptions, _warning
|
|
|
190
192
|
const hookPath = path.join(phaseDir, entry.name);
|
|
191
193
|
const baseName = entry.name.includes(".") ? entry.name.slice(0, entry.name.lastIndexOf(".")) : entry.name;
|
|
192
194
|
const key = `${phase}:${baseName}:${entry.name}`;
|
|
193
|
-
const
|
|
194
|
-
seen.
|
|
195
|
+
const storedOnly = scope === "project";
|
|
196
|
+
const shadowed = !storedOnly && seen.has(key);
|
|
197
|
+
if (!storedOnly) seen.add(key);
|
|
198
|
+
let status: InventoryStatus;
|
|
199
|
+
let diagnostics: string[] | undefined;
|
|
200
|
+
if (storedOnly) {
|
|
201
|
+
status = "stored-only";
|
|
202
|
+
diagnostics = [
|
|
203
|
+
"project hooks are stored only; sessions do not load repository hook modules automatically",
|
|
204
|
+
];
|
|
205
|
+
} else if (shadowed) {
|
|
206
|
+
status = "shadowed";
|
|
207
|
+
diagnostics = ["shadowed by the same hook at higher-precedence scope"];
|
|
208
|
+
} else {
|
|
209
|
+
status = "enabled";
|
|
210
|
+
}
|
|
195
211
|
rows.push({
|
|
196
212
|
id: inventoryRowId("hooks", scope, hookPath),
|
|
197
213
|
surface: "hooks",
|
|
198
214
|
name: entry.name,
|
|
199
215
|
displayName: `${phase}/${entry.name}`,
|
|
200
|
-
status
|
|
216
|
+
status,
|
|
201
217
|
provenance: `${scopeLabel(scope)} hooks/${phase}`,
|
|
202
218
|
path: hookPath,
|
|
203
219
|
scope,
|
|
204
220
|
description: `${phase}-tool hook for ${baseName}`,
|
|
205
|
-
diagnostics
|
|
221
|
+
diagnostics,
|
|
206
222
|
raw: { name: entry.name, type: phase, tool: baseName },
|
|
207
223
|
});
|
|
208
224
|
}
|
package/src/dap/client.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import { logger } from "@gajae-code/utils";
|
|
4
|
+
import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
|
|
4
5
|
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
5
6
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
6
7
|
import { type OwnedProcess, spawnOwnedProcess } from "../runtime/process-lifecycle";
|
|
@@ -134,7 +135,7 @@ export class DapClient {
|
|
|
134
135
|
// debuggee children can reach /dev/tty and trigger SIGTTIN, suspending
|
|
135
136
|
// the parent harness under shell job control.
|
|
136
137
|
const env = {
|
|
137
|
-
...
|
|
138
|
+
...projectSafeSpawnEnv(),
|
|
138
139
|
...NON_INTERACTIVE_ENV,
|
|
139
140
|
};
|
|
140
141
|
const owner = spawnOwnedProcess([adapter.resolvedCommand, ...adapter.args], {
|
|
@@ -159,7 +160,7 @@ export class DapClient {
|
|
|
159
160
|
*/
|
|
160
161
|
static async #spawnSocket({ adapter, cwd }: DapSpawnOptions): Promise<DapClient> {
|
|
161
162
|
const env = {
|
|
162
|
-
...
|
|
163
|
+
...projectSafeSpawnEnv(),
|
|
163
164
|
...NON_INTERACTIVE_ENV,
|
|
164
165
|
};
|
|
165
166
|
const isLinux = process.platform === "linux";
|
package/src/dap/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { isRecord } from "@gajae-code/utils";
|
|
3
3
|
import { hasRootMarkers, resolveTrustedCommand } from "../lsp/config";
|
|
4
|
+
import { isProjectControlledPath } from "../lsp/path-trust";
|
|
4
5
|
import DEFAULTS from "./defaults.json" with { type: "json" };
|
|
5
6
|
import type { DapAdapterConfig, DapResolvedAdapter } from "./types";
|
|
6
7
|
|
|
@@ -48,11 +49,20 @@ export function getAdapterConfigs(): Record<string, DapAdapterConfig> {
|
|
|
48
49
|
return { ...DEFAULT_ADAPTERS };
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
/**
|
|
53
|
+
* `trustCwds` are extra trust roots, such as the session workspace when the command
|
|
54
|
+
* cwd is a different directory. A command controlled by any of them is refused.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveAdapter(
|
|
57
|
+
adapterName: string,
|
|
58
|
+
cwd: string,
|
|
59
|
+
trustCwds?: readonly string[],
|
|
60
|
+
): DapResolvedAdapter | null {
|
|
52
61
|
const config = DEFAULT_ADAPTERS[adapterName];
|
|
53
62
|
if (!config) return null;
|
|
54
63
|
const resolvedCommand = resolveTrustedCommand(config.command, cwd);
|
|
55
64
|
if (!resolvedCommand) return null;
|
|
65
|
+
if (trustCwds?.some(root => isProjectControlledPath(resolvedCommand, root))) return null;
|
|
56
66
|
return {
|
|
57
67
|
name: adapterName,
|
|
58
68
|
command: config.command,
|
|
@@ -67,15 +77,15 @@ export function resolveAdapter(adapterName: string, cwd: string): DapResolvedAda
|
|
|
67
77
|
};
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
export function getAvailableAdapters(cwd: string): DapResolvedAdapter[] {
|
|
80
|
+
export function getAvailableAdapters(cwd: string, trustCwds?: readonly string[]): DapResolvedAdapter[] {
|
|
71
81
|
return Object.keys(DEFAULT_ADAPTERS)
|
|
72
|
-
.map(name => resolveAdapter(name, cwd))
|
|
82
|
+
.map(name => resolveAdapter(name, cwd, trustCwds))
|
|
73
83
|
.filter((adapter): adapter is DapResolvedAdapter => adapter !== null);
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
function getMatchingAdapters(program: string, cwd: string): DapResolvedAdapter[] {
|
|
86
|
+
function getMatchingAdapters(program: string, cwd: string, trustCwds?: readonly string[]): DapResolvedAdapter[] {
|
|
77
87
|
const extension = path.extname(program).toLowerCase();
|
|
78
|
-
const available = getAvailableAdapters(cwd);
|
|
88
|
+
const available = getAvailableAdapters(cwd, trustCwds);
|
|
79
89
|
if (!extension) {
|
|
80
90
|
// For extensionless binaries, only consider native debuggers (gdb, lldb-dap)
|
|
81
91
|
// or adapters that match by root markers. Don't silently fall back to
|
|
@@ -124,20 +134,30 @@ function sortAdaptersForLaunch(program: string, cwd: string, adapters: DapResolv
|
|
|
124
134
|
return rootAware.map(entry => entry.adapter);
|
|
125
135
|
}
|
|
126
136
|
|
|
127
|
-
export function selectLaunchAdapter(
|
|
137
|
+
export function selectLaunchAdapter(
|
|
138
|
+
program: string,
|
|
139
|
+
cwd: string,
|
|
140
|
+
adapterName?: string,
|
|
141
|
+
trustCwds?: readonly string[],
|
|
142
|
+
): DapResolvedAdapter | null {
|
|
128
143
|
if (adapterName) {
|
|
129
|
-
return resolveAdapter(adapterName, cwd);
|
|
144
|
+
return resolveAdapter(adapterName, cwd, trustCwds);
|
|
130
145
|
}
|
|
131
|
-
const matches = getMatchingAdapters(program, cwd);
|
|
146
|
+
const matches = getMatchingAdapters(program, cwd, trustCwds);
|
|
132
147
|
const sorted = sortAdaptersForLaunch(program, cwd, matches);
|
|
133
148
|
return sorted[0] ?? null;
|
|
134
149
|
}
|
|
135
150
|
|
|
136
|
-
export function selectAttachAdapter(
|
|
151
|
+
export function selectAttachAdapter(
|
|
152
|
+
cwd: string,
|
|
153
|
+
adapterName?: string,
|
|
154
|
+
port?: number,
|
|
155
|
+
trustCwds?: readonly string[],
|
|
156
|
+
): DapResolvedAdapter | null {
|
|
137
157
|
if (adapterName) {
|
|
138
|
-
return resolveAdapter(adapterName, cwd);
|
|
158
|
+
return resolveAdapter(adapterName, cwd, trustCwds);
|
|
139
159
|
}
|
|
140
|
-
const available = getAvailableAdapters(cwd);
|
|
160
|
+
const available = getAvailableAdapters(cwd, trustCwds);
|
|
141
161
|
if (port !== undefined) {
|
|
142
162
|
const debugpy = available.find(adapter => adapter.name === "debugpy");
|
|
143
163
|
if (debugpy) return debugpy;
|
package/src/dap/defaults.json
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"debugpy": {
|
|
46
46
|
"command": "python",
|
|
47
|
-
"args": ["-m", "debugpy.adapter"],
|
|
47
|
+
"args": ["-I", "-m", "debugpy.adapter"],
|
|
48
48
|
"languages": ["python"],
|
|
49
49
|
"fileTypes": [".py"],
|
|
50
50
|
"rootMarkers": ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"],
|