@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
|
@@ -6,13 +6,17 @@
|
|
|
6
6
|
* that decides add/update/skip/fail, destinations, and warnings, guaranteeing
|
|
7
7
|
* dry-run/live parity.
|
|
8
8
|
*/
|
|
9
|
+
|
|
9
10
|
import * as fs from "node:fs/promises";
|
|
10
11
|
import * as path from "node:path";
|
|
12
|
+
import { FileType, glob } from "@gajae-code/natives";
|
|
11
13
|
import { isEnoent, parseFrontmatter } from "@gajae-code/utils";
|
|
12
14
|
import { readMCPConfigFile, validateServerName } from "../runtime-mcp/config-writer";
|
|
15
|
+
import { PROTECTED_AGENT_NAMES } from "./adapters";
|
|
16
|
+
import { isSupportedHookEvent, renderHookModule } from "./hook-module";
|
|
13
17
|
import { mapMcpEntry } from "./mcp-mapper";
|
|
14
18
|
import { slugify } from "./skill-normalizer";
|
|
15
|
-
import type { AdapterResult, MigrateAction, MigrateDestinations, MigrateWarning } from "./types";
|
|
19
|
+
import type { AdapterResult, ManualAction, MigrateAction, MigrateDestinations, MigrateWarning } from "./types";
|
|
16
20
|
|
|
17
21
|
export interface PlanInput {
|
|
18
22
|
results: AdapterResult[];
|
|
@@ -23,6 +27,7 @@ export interface PlanInput {
|
|
|
23
27
|
export interface PlanOutput {
|
|
24
28
|
actions: MigrateAction[];
|
|
25
29
|
warnings: MigrateWarning[];
|
|
30
|
+
manualActions: ManualAction[];
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
interface DestSkillIndex {
|
|
@@ -77,9 +82,153 @@ async function indexDestinationSkills(skillsDir: string): Promise<DestSkillIndex
|
|
|
77
82
|
return index;
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
interface CommandDestinationInspection {
|
|
86
|
+
destination: string;
|
|
87
|
+
exists: boolean;
|
|
88
|
+
unsafeReason?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface PlannedCommandDestination {
|
|
92
|
+
name: string;
|
|
93
|
+
destination: string;
|
|
94
|
+
segments: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isStrictPathPrefix(prefix: string[], candidate: string[]): boolean {
|
|
98
|
+
return prefix.length < candidate.length && prefix.every((segment, index) => segment === candidate[index]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function commandNameFromDestination(commandsDir: string, destination: string): string {
|
|
102
|
+
return path.relative(commandsDir, destination).replace(/\.md$/, "").split(path.sep).join(":");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ExistingCommandDestination {
|
|
106
|
+
name: string;
|
|
107
|
+
destination: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type ExistingCommandNameIndex = Map<string, ExistingCommandDestination[]>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Imported commands are always namespaced under their source. Built-in,
|
|
114
|
+
* extension, and MCP/custom commands are all resolved before file commands, so
|
|
115
|
+
* a bare imported name can be shadowed by any registration the user later
|
|
116
|
+
* enables. The prefix keeps every imported command addressable.
|
|
117
|
+
*/
|
|
118
|
+
function importedSlashCommandName(source: string, name: string): string {
|
|
119
|
+
return `${source}:${name}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function indexExistingCommandNames(commandsDir: string): Promise<ExistingCommandNameIndex> {
|
|
123
|
+
const index: ExistingCommandNameIndex = new Map();
|
|
124
|
+
const root = path.resolve(commandsDir);
|
|
125
|
+
try {
|
|
126
|
+
const rootStat = await fs.lstat(root);
|
|
127
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) return index;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (isEnoent(error)) return index;
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result = await glob({
|
|
134
|
+
pattern: "**/*.md",
|
|
135
|
+
path: root,
|
|
136
|
+
fileType: FileType.File,
|
|
137
|
+
hidden: false,
|
|
138
|
+
gitignore: true,
|
|
139
|
+
});
|
|
140
|
+
for (const match of result.matches) {
|
|
141
|
+
const matchSegments = match.path.split("/");
|
|
142
|
+
const destination = path.join(root, ...matchSegments);
|
|
143
|
+
const relative = path.relative(root, destination);
|
|
144
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) continue;
|
|
145
|
+
|
|
146
|
+
let isRegularFile = false;
|
|
147
|
+
try {
|
|
148
|
+
const stat = await fs.lstat(destination);
|
|
149
|
+
isRegularFile = stat.isFile() && !stat.isSymbolicLink();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (isEnoent(error)) continue;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
if (!isRegularFile) continue;
|
|
155
|
+
|
|
156
|
+
const name = commandNameFromDestination(root, destination);
|
|
157
|
+
const key = name.toLowerCase();
|
|
158
|
+
const destinations = index.get(key) ?? [];
|
|
159
|
+
destinations.push({ name, destination });
|
|
160
|
+
index.set(key, destinations);
|
|
161
|
+
}
|
|
162
|
+
return index;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Resolve case-equivalent existing paths so command collisions stay portable. */
|
|
166
|
+
async function inspectCommandDestination(
|
|
167
|
+
commandsDir: string,
|
|
168
|
+
nameParts: string[],
|
|
169
|
+
): Promise<CommandDestinationInspection> {
|
|
170
|
+
const segments = [...nameParts.slice(0, -1), `${nameParts[nameParts.length - 1]}.md`];
|
|
171
|
+
let directory = commandsDir;
|
|
172
|
+
|
|
173
|
+
for (let index = 0; index < segments.length; index++) {
|
|
174
|
+
const segment = segments[index]!;
|
|
175
|
+
const entries = await fs.readdir(directory, { withFileTypes: true }).catch((error: unknown) => {
|
|
176
|
+
if (!isEnoent(error)) throw error;
|
|
177
|
+
return null;
|
|
178
|
+
});
|
|
179
|
+
if (!entries) {
|
|
180
|
+
return { destination: path.join(directory, ...segments.slice(index)), exists: false };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const matches = entries.filter(entry => entry.name.toLowerCase() === segment.toLowerCase());
|
|
184
|
+
if (matches.length > 1) {
|
|
185
|
+
return {
|
|
186
|
+
destination: path.join(directory, segment),
|
|
187
|
+
exists: false,
|
|
188
|
+
unsafeReason: `multiple case-equivalent destination entries exist for ${segment} in ${directory}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const entry = matches[0];
|
|
192
|
+
if (!entry) return { destination: path.join(directory, ...segments.slice(index)), exists: false };
|
|
193
|
+
|
|
194
|
+
const entryPath = path.join(directory, entry.name);
|
|
195
|
+
if (index < segments.length - 1) {
|
|
196
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
197
|
+
return {
|
|
198
|
+
destination: entryPath,
|
|
199
|
+
exists: false,
|
|
200
|
+
unsafeReason: `command destination ancestor is not a real directory: ${entryPath}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
directory = entryPath;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
208
|
+
return {
|
|
209
|
+
destination: entryPath,
|
|
210
|
+
exists: false,
|
|
211
|
+
unsafeReason: "destination is not a regular file; refusing to replace it",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (!entry.name.endsWith(".md")) {
|
|
215
|
+
return {
|
|
216
|
+
destination: entryPath,
|
|
217
|
+
exists: false,
|
|
218
|
+
unsafeReason: "destination filename must end in lowercase .md for native slash-command discovery",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return { destination: entryPath, exists: true };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
throw new Error("slash command name has no destination segment");
|
|
225
|
+
}
|
|
226
|
+
|
|
80
227
|
export async function planMigration(input: PlanInput): Promise<PlanOutput> {
|
|
81
228
|
const actions: MigrateAction[] = [];
|
|
82
229
|
const warnings: MigrateWarning[] = [];
|
|
230
|
+
// Settings that cannot be expressed in gjc are reported, never guessed at.
|
|
231
|
+
const manualActions = input.results.flatMap(result => result.manualActions);
|
|
83
232
|
|
|
84
233
|
// 1. Source-level diagnostics become `source`-typed actions.
|
|
85
234
|
for (const result of input.results) {
|
|
@@ -94,6 +243,67 @@ export async function planMigration(input: PlanInput): Promise<PlanOutput> {
|
|
|
94
243
|
}
|
|
95
244
|
}
|
|
96
245
|
|
|
246
|
+
// 1b. Hook commands: one generated module per source that has importable events.
|
|
247
|
+
for (const result of input.results) {
|
|
248
|
+
const importable = result.hookCandidates.filter(candidate => isSupportedHookEvent(candidate.event));
|
|
249
|
+
const unsupported = result.hookCandidates.filter(candidate => !isSupportedHookEvent(candidate.event));
|
|
250
|
+
for (const candidate of unsupported) {
|
|
251
|
+
actions.push({
|
|
252
|
+
source: candidate.source,
|
|
253
|
+
type: "hook",
|
|
254
|
+
name: `${candidate.event}`,
|
|
255
|
+
operation: "skip",
|
|
256
|
+
status: "skipped_unmappable",
|
|
257
|
+
reason: `${candidate.event} has no faithful gjc hook event; port this command by hand`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const source = importable[0]?.source;
|
|
261
|
+
if (importable.length === 0 || !source) continue;
|
|
262
|
+
const destination = path.join(input.destinations.hooksDir, `migrated-${source}.ts`);
|
|
263
|
+
const content = renderHookModule(source, importable);
|
|
264
|
+
let exists = false;
|
|
265
|
+
let unsafe = false;
|
|
266
|
+
try {
|
|
267
|
+
const stat = await fs.lstat(destination);
|
|
268
|
+
exists = true;
|
|
269
|
+
unsafe = !stat.isFile() || stat.isSymbolicLink();
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (!isEnoent(error)) {
|
|
272
|
+
actions.push({
|
|
273
|
+
source,
|
|
274
|
+
type: "hook",
|
|
275
|
+
name: path.basename(destination),
|
|
276
|
+
destination,
|
|
277
|
+
operation: "fail",
|
|
278
|
+
status: "failed_io",
|
|
279
|
+
reason: `failed to inspect destination: ${(error as Error).message}`,
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (unsafe) {
|
|
285
|
+
actions.push({
|
|
286
|
+
source,
|
|
287
|
+
type: "hook",
|
|
288
|
+
name: path.basename(destination),
|
|
289
|
+
destination,
|
|
290
|
+
operation: "fail",
|
|
291
|
+
status: "failed_invalid_destination",
|
|
292
|
+
reason: "destination is not a regular file; refusing to replace it",
|
|
293
|
+
});
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
actions.push({
|
|
297
|
+
source,
|
|
298
|
+
type: "hook",
|
|
299
|
+
name: path.basename(destination),
|
|
300
|
+
destination,
|
|
301
|
+
operation: exists && !input.force ? "skip" : exists ? "update" : "create",
|
|
302
|
+
status: exists && !input.force ? "skipped_exists" : exists ? "updated" : "imported",
|
|
303
|
+
hook: { content },
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
97
307
|
// 2. MCP actions — read the destination config once.
|
|
98
308
|
const existingConfig = await readMCPConfigFile(input.destinations.mcpConfigPath).catch(() => null);
|
|
99
309
|
const mcpInvalid = existingConfig === null;
|
|
@@ -314,5 +524,367 @@ export async function planMigration(input: PlanInput): Promise<PlanOutput> {
|
|
|
314
524
|
}
|
|
315
525
|
}
|
|
316
526
|
|
|
317
|
-
|
|
527
|
+
// 4. Instructions — all selected sources target the same scoped AGENTS.md.
|
|
528
|
+
const plannedInstructions = new Set<string>();
|
|
529
|
+
for (const result of input.results) {
|
|
530
|
+
for (const candidate of result.instructionCandidates) {
|
|
531
|
+
const destination = input.destinations.instructionsPath;
|
|
532
|
+
const base = { source: candidate.source, type: "instruction" as const, name: candidate.name, destination };
|
|
533
|
+
if (plannedInstructions.has(destination)) {
|
|
534
|
+
actions.push({
|
|
535
|
+
...base,
|
|
536
|
+
operation: "skip",
|
|
537
|
+
status: "skipped_exists",
|
|
538
|
+
reason: "another selected source already targets this file; import one source at a time",
|
|
539
|
+
});
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let exists = false;
|
|
544
|
+
try {
|
|
545
|
+
const stat = await fs.lstat(destination);
|
|
546
|
+
exists = true;
|
|
547
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
548
|
+
actions.push({
|
|
549
|
+
...base,
|
|
550
|
+
operation: input.force ? "fail" : "skip",
|
|
551
|
+
status: input.force ? "failed_invalid_destination" : "skipped_exists",
|
|
552
|
+
reason: "destination is not a regular file; refusing to replace it",
|
|
553
|
+
});
|
|
554
|
+
plannedInstructions.add(destination);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (!isEnoent(error)) {
|
|
559
|
+
actions.push({
|
|
560
|
+
...base,
|
|
561
|
+
operation: "fail",
|
|
562
|
+
status: "failed_io",
|
|
563
|
+
reason: `failed to inspect destination: ${(error as Error).message}`,
|
|
564
|
+
});
|
|
565
|
+
plannedInstructions.add(destination);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (exists && !input.force) {
|
|
571
|
+
actions.push({ ...base, operation: "skip", status: "skipped_exists" });
|
|
572
|
+
} else {
|
|
573
|
+
actions.push({
|
|
574
|
+
...base,
|
|
575
|
+
operation: exists ? "update" : "create",
|
|
576
|
+
status: exists ? "updated" : "imported",
|
|
577
|
+
instruction: { content: candidate.content },
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
plannedInstructions.add(destination);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// 5. Slash commands — command names map to the scoped commands directory.
|
|
585
|
+
let existingCommandNames: ExistingCommandNameIndex = new Map();
|
|
586
|
+
let existingCommandNameIndexError: unknown;
|
|
587
|
+
if (input.results.some(result => result.slashCommandCandidates.length > 0)) {
|
|
588
|
+
try {
|
|
589
|
+
existingCommandNames = await indexExistingCommandNames(input.destinations.commandsDir);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
existingCommandNameIndexError = error;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const plannedCommands = new Map<string, PlannedCommandDestination>();
|
|
595
|
+
const validCommandNameSegment = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
596
|
+
for (const result of input.results) {
|
|
597
|
+
for (const candidate of result.slashCommandCandidates) {
|
|
598
|
+
const name = importedSlashCommandName(candidate.source, candidate.name);
|
|
599
|
+
const nameParts = name.split(":");
|
|
600
|
+
if (!nameParts.every(part => validCommandNameSegment.test(part))) {
|
|
601
|
+
actions.push({
|
|
602
|
+
source: candidate.source,
|
|
603
|
+
type: "command",
|
|
604
|
+
name,
|
|
605
|
+
operation: "fail",
|
|
606
|
+
status: "failed_invalid_source",
|
|
607
|
+
reason:
|
|
608
|
+
"slash command name must use letters, numbers, dots, underscores, or hyphens separated by colons",
|
|
609
|
+
});
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const requestedDestination = path.join(
|
|
614
|
+
input.destinations.commandsDir,
|
|
615
|
+
...nameParts.slice(0, -1),
|
|
616
|
+
`${nameParts[nameParts.length - 1]}.md`,
|
|
617
|
+
);
|
|
618
|
+
const collisionKey = path.resolve(requestedDestination).toLowerCase();
|
|
619
|
+
const destinationSegments = [...nameParts.slice(0, -1), `${nameParts[nameParts.length - 1]}.md`].map(segment =>
|
|
620
|
+
segment.toLowerCase(),
|
|
621
|
+
);
|
|
622
|
+
const plannedCommand = plannedCommands.get(collisionKey);
|
|
623
|
+
let destination = plannedCommand?.destination ?? requestedDestination;
|
|
624
|
+
let base = { source: candidate.source, type: "command" as const, name, destination };
|
|
625
|
+
if (existingCommandNameIndexError) {
|
|
626
|
+
actions.push({
|
|
627
|
+
...base,
|
|
628
|
+
operation: "fail",
|
|
629
|
+
status: "failed_io",
|
|
630
|
+
reason: `failed to index existing slash commands: ${(existingCommandNameIndexError as Error).message}`,
|
|
631
|
+
});
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (plannedCommand) {
|
|
635
|
+
const sameName = plannedCommand.name === name;
|
|
636
|
+
const collisionReason = sameName
|
|
637
|
+
? "duplicate within this run"
|
|
638
|
+
: `case-insensitive destination collision with "${plannedCommand.name}"`;
|
|
639
|
+
if (input.force) {
|
|
640
|
+
actions.push({
|
|
641
|
+
...base,
|
|
642
|
+
name: plannedCommand.name,
|
|
643
|
+
operation: "update",
|
|
644
|
+
status: "updated",
|
|
645
|
+
reason: `${collisionReason} (overwritten under --force)`,
|
|
646
|
+
command: { content: candidate.content },
|
|
647
|
+
});
|
|
648
|
+
} else {
|
|
649
|
+
actions.push({
|
|
650
|
+
...base,
|
|
651
|
+
operation: "skip",
|
|
652
|
+
status: "skipped_exists",
|
|
653
|
+
reason: collisionReason,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const matchingExistingNames = existingCommandNames.get(name.toLowerCase()) ?? [];
|
|
660
|
+
if (matchingExistingNames.length > 1) {
|
|
661
|
+
const paths = matchingExistingNames.map(existing => existing.destination).join(", ");
|
|
662
|
+
actions.push({
|
|
663
|
+
...base,
|
|
664
|
+
operation: input.force ? "fail" : "skip",
|
|
665
|
+
status: input.force ? "failed_invalid_destination" : "skipped_exists",
|
|
666
|
+
reason: `multiple existing files resolve to /${name}: ${paths}`,
|
|
667
|
+
});
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const matchingExistingName = matchingExistingNames[0];
|
|
671
|
+
if (matchingExistingName) {
|
|
672
|
+
const effectiveBase = {
|
|
673
|
+
...base,
|
|
674
|
+
name: matchingExistingName.name,
|
|
675
|
+
destination: matchingExistingName.destination,
|
|
676
|
+
};
|
|
677
|
+
if (input.force) {
|
|
678
|
+
actions.push({
|
|
679
|
+
...effectiveBase,
|
|
680
|
+
operation: "update",
|
|
681
|
+
status: "updated",
|
|
682
|
+
command: { content: candidate.content },
|
|
683
|
+
});
|
|
684
|
+
} else {
|
|
685
|
+
actions.push({
|
|
686
|
+
...effectiveBase,
|
|
687
|
+
operation: "skip",
|
|
688
|
+
status: "skipped_exists",
|
|
689
|
+
reason: `command name already resolves from ${matchingExistingName.destination}`,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
plannedCommands.set(collisionKey, {
|
|
693
|
+
name: matchingExistingName.name,
|
|
694
|
+
destination: matchingExistingName.destination,
|
|
695
|
+
segments: destinationSegments,
|
|
696
|
+
});
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const structuralCollision = [...plannedCommands.values()].find(
|
|
701
|
+
planned =>
|
|
702
|
+
isStrictPathPrefix(planned.segments, destinationSegments) ||
|
|
703
|
+
isStrictPathPrefix(destinationSegments, planned.segments),
|
|
704
|
+
);
|
|
705
|
+
if (structuralCollision) {
|
|
706
|
+
actions.push({
|
|
707
|
+
...base,
|
|
708
|
+
operation: input.force ? "fail" : "skip",
|
|
709
|
+
status: input.force ? "failed_invalid_destination" : "skipped_exists",
|
|
710
|
+
reason: `structural destination collision with "${structuralCollision.name}"; a command file cannot also be a directory`,
|
|
711
|
+
});
|
|
712
|
+
plannedCommands.set(collisionKey, { name: candidate.name, destination, segments: destinationSegments });
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const destinationDirectories = [path.dirname(input.destinations.commandsDir), input.destinations.commandsDir];
|
|
717
|
+
let commandDirectory = input.destinations.commandsDir;
|
|
718
|
+
for (const segment of nameParts.slice(0, -1)) {
|
|
719
|
+
commandDirectory = path.join(commandDirectory, segment);
|
|
720
|
+
destinationDirectories.push(commandDirectory);
|
|
721
|
+
}
|
|
722
|
+
let unsafeDirectory: string | undefined;
|
|
723
|
+
let directoryReadError: unknown;
|
|
724
|
+
for (const directory of destinationDirectories) {
|
|
725
|
+
try {
|
|
726
|
+
const stat = await fs.lstat(directory);
|
|
727
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
728
|
+
unsafeDirectory = directory;
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
} catch (error) {
|
|
732
|
+
if (!isEnoent(error)) {
|
|
733
|
+
directoryReadError = error;
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (directoryReadError) {
|
|
739
|
+
actions.push({
|
|
740
|
+
...base,
|
|
741
|
+
operation: "fail",
|
|
742
|
+
status: "failed_io",
|
|
743
|
+
reason: `failed to inspect command destination directory: ${(directoryReadError as Error).message}`,
|
|
744
|
+
});
|
|
745
|
+
plannedCommands.set(collisionKey, { name: candidate.name, destination, segments: destinationSegments });
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (unsafeDirectory) {
|
|
749
|
+
actions.push({
|
|
750
|
+
...base,
|
|
751
|
+
operation: input.force ? "fail" : "skip",
|
|
752
|
+
status: input.force ? "failed_invalid_destination" : "skipped_exists",
|
|
753
|
+
reason: `command destination ancestor is not a real directory: ${unsafeDirectory}`,
|
|
754
|
+
});
|
|
755
|
+
plannedCommands.set(collisionKey, { name: candidate.name, destination, segments: destinationSegments });
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
let inspection: CommandDestinationInspection;
|
|
760
|
+
try {
|
|
761
|
+
inspection = await inspectCommandDestination(input.destinations.commandsDir, nameParts);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
actions.push({
|
|
764
|
+
...base,
|
|
765
|
+
operation: "fail",
|
|
766
|
+
status: "failed_io",
|
|
767
|
+
reason: `failed to inspect command destination: ${(error as Error).message}`,
|
|
768
|
+
});
|
|
769
|
+
plannedCommands.set(collisionKey, { name: candidate.name, destination, segments: destinationSegments });
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
destination = inspection.destination;
|
|
773
|
+
base = { ...base, destination };
|
|
774
|
+
if (inspection.unsafeReason) {
|
|
775
|
+
actions.push({
|
|
776
|
+
...base,
|
|
777
|
+
operation: input.force ? "fail" : "skip",
|
|
778
|
+
status: input.force ? "failed_invalid_destination" : "skipped_exists",
|
|
779
|
+
reason: inspection.unsafeReason,
|
|
780
|
+
});
|
|
781
|
+
plannedCommands.set(collisionKey, { name: candidate.name, destination, segments: destinationSegments });
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
const effectiveName = commandNameFromDestination(input.destinations.commandsDir, destination);
|
|
785
|
+
base = { ...base, name: effectiveName };
|
|
786
|
+
|
|
787
|
+
if (inspection.exists && !input.force) {
|
|
788
|
+
actions.push({ ...base, operation: "skip", status: "skipped_exists" });
|
|
789
|
+
} else {
|
|
790
|
+
actions.push({
|
|
791
|
+
...base,
|
|
792
|
+
operation: inspection.exists ? "update" : "create",
|
|
793
|
+
status: inspection.exists ? "updated" : "imported",
|
|
794
|
+
command: { content: candidate.content },
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
plannedCommands.set(collisionKey, { name: effectiveName, destination, segments: destinationSegments });
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// 6. Subagents — one flat `<name>.md` per agent. An imported agent must never
|
|
802
|
+
// shadow a bundled role, so a colliding name is namespaced under its source.
|
|
803
|
+
const plannedAgents = new Map<string, { name: string; destination: string }>();
|
|
804
|
+
const validAgentName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
805
|
+
for (const result of input.results) {
|
|
806
|
+
for (const candidate of result.agentCandidates) {
|
|
807
|
+
const warnings = [...candidate.warnings];
|
|
808
|
+
let name = candidate.name;
|
|
809
|
+
if (PROTECTED_AGENT_NAMES.has(name.toLowerCase())) {
|
|
810
|
+
name = `${candidate.source}-${name}`;
|
|
811
|
+
warnings.push(
|
|
812
|
+
`renamed agent "${candidate.name}" to "${name}" so the bundled ${candidate.name} role stays authoritative`,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
if (!validAgentName.test(name)) {
|
|
816
|
+
actions.push({
|
|
817
|
+
source: candidate.source,
|
|
818
|
+
type: "agent",
|
|
819
|
+
name,
|
|
820
|
+
operation: "fail",
|
|
821
|
+
status: "failed_invalid_source",
|
|
822
|
+
reason: "agent name must use letters, numbers, dots, underscores, or hyphens",
|
|
823
|
+
});
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const destination = path.join(input.destinations.agentsDir, `${name}.md`);
|
|
828
|
+
const collisionKey = path.resolve(destination).toLowerCase();
|
|
829
|
+
const base = {
|
|
830
|
+
source: candidate.source,
|
|
831
|
+
type: "agent" as const,
|
|
832
|
+
name,
|
|
833
|
+
destination,
|
|
834
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
835
|
+
};
|
|
836
|
+
const planned = plannedAgents.get(collisionKey);
|
|
837
|
+
if (planned) {
|
|
838
|
+
actions.push({
|
|
839
|
+
...base,
|
|
840
|
+
operation: input.force ? "update" : "skip",
|
|
841
|
+
status: input.force ? "updated" : "skipped_exists",
|
|
842
|
+
reason: `duplicate agent destination with "${planned.name}"`,
|
|
843
|
+
...(input.force ? { agent: { content: candidate.content } } : {}),
|
|
844
|
+
});
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
let exists = false;
|
|
849
|
+
try {
|
|
850
|
+
const stat = await fs.lstat(destination);
|
|
851
|
+
exists = true;
|
|
852
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
853
|
+
actions.push({
|
|
854
|
+
...base,
|
|
855
|
+
operation: input.force ? "fail" : "skip",
|
|
856
|
+
status: input.force ? "failed_invalid_destination" : "skipped_exists",
|
|
857
|
+
reason: "destination is not a regular file; refusing to replace it",
|
|
858
|
+
});
|
|
859
|
+
plannedAgents.set(collisionKey, { name, destination });
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
} catch (error) {
|
|
863
|
+
if (!isEnoent(error)) {
|
|
864
|
+
actions.push({
|
|
865
|
+
...base,
|
|
866
|
+
operation: "fail",
|
|
867
|
+
status: "failed_io",
|
|
868
|
+
reason: `failed to inspect destination: ${(error as Error).message}`,
|
|
869
|
+
});
|
|
870
|
+
plannedAgents.set(collisionKey, { name, destination });
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (exists && !input.force) {
|
|
876
|
+
actions.push({ ...base, operation: "skip", status: "skipped_exists" });
|
|
877
|
+
} else {
|
|
878
|
+
actions.push({
|
|
879
|
+
...base,
|
|
880
|
+
operation: exists ? "update" : "create",
|
|
881
|
+
status: exists ? "updated" : "imported",
|
|
882
|
+
agent: { content: candidate.content },
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
plannedAgents.set(collisionKey, { name, destination });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return { actions, warnings, manualActions };
|
|
318
890
|
}
|