@gajae-code/coding-agent 0.18.7 → 0.18.9
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 +377 -1
- package/dist/types/async/endpoint-id.d.ts +16 -0
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/async/support.d.ts +0 -14
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +2 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/mcp-serve.d.ts +4 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +6 -1
- package/dist/types/discovery/mcp-json.d.ts +1 -0
- package/dist/types/eval/py/kernel.d.ts +18 -0
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/html-export-destination.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +4 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/config.d.ts +8 -5
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -1
- package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +24 -2
- package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -1
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +31 -7
- package/dist/types/session/session-storage.d.ts +69 -2
- package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/isolation-dir.d.ts +26 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/browser/cdp-url.d.ts +2 -0
- package/dist/types/tools/github-side-effect.d.ts +22 -0
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/monitor-mutation.d.ts +9 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/endpoint-id.ts +26 -0
- package/src/async/job-manager.ts +247 -132
- package/src/async/support.ts +0 -25
- package/src/capability/index.ts +15 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +2 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/doctor/repairs.ts +4 -1
- package/src/cli/doctor-supervisor.ts +3 -0
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/public-command-registry.ts +28 -6
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/mcp-serve.ts +7 -0
- package/src/commands/sdk.ts +455 -397
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +316 -79
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/coordinator-mcp/server.ts +2 -0
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +4 -13
- package/src/dap/config.ts +2 -2
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +13 -1
- package/src/discovery/helpers.ts +171 -29
- package/src/discovery/mcp-json.ts +26 -12
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +796 -154
- package/src/eval/py/kernel.ts +79 -13
- package/src/eval/py/tool-bridge.ts +4 -0
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/html-export-destination.ts +16 -0
- package/src/export/html/index.ts +7 -10
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
- package/src/extensibility/extensions/runner.ts +15 -1
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +9 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -9
- package/src/gjc-runtime/launch-worktree.ts +70 -9
- package/src/gjc-runtime/psmux-detect.ts +28 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/session-state-sidecar.ts +3 -2
- package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +105 -16
- package/src/gjc-runtime/tmux-common.ts +8 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/internal-urls/docs-index.generated.ts +20 -19
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/config.ts +12 -63
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/memories/index.ts +88 -7
- package/src/modes/acp/acp-agent.ts +88 -4
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +76 -31
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
- package/src/modes/controllers/selector-controller.ts +24 -15
- package/src/modes/controllers/todo-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -1
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +4 -2
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/json-rpc.ts +2 -1
- package/src/runtime-mcp/manager.ts +29 -11
- package/src/runtime-mcp/mcp-redirect.ts +73 -0
- package/src/runtime-mcp/oauth-flow.ts +14 -2
- package/src/runtime-mcp/oauth-public-url.ts +14 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/pool.ts +6 -0
- package/src/runtime-mcp/project-host-values.ts +46 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +4 -3
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +95 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +81 -6
- package/src/sdk/broker/doctor-restart.ts +4 -0
- package/src/sdk/broker/ensure.ts +31 -2
- package/src/sdk/broker/lifecycle.ts +711 -66
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +238 -43
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2705 -303
- package/src/sdk/host/session-runtime.ts +490 -143
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +82 -18
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +69 -45
- package/src/session/agent-session.ts +679 -238
- package/src/session/artifacts.ts +176 -71
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3779 -462
- package/src/session/internal/managed-session-storage.ts +501 -78
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +19 -3
- package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
- package/src/session/session-manager.ts +951 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +447 -15
- package/src/session/task-artifact-owner-codec.ts +71 -8
- package/src/session/task-artifact-owner-retirement.ts +21 -19
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +145 -2
- package/src/skill-state/workflow-mutation-guard.ts +102 -10
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/mcp.ts +8 -4
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/task/isolation-dir.ts +143 -0
- package/src/task/worktree.ts +16 -7
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit-write-path.ts +68 -0
- package/src/tools/ast-edit.ts +10 -1
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/browser/cdp-url.ts +18 -0
- package/src/tools/browser.ts +8 -1
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/gh.ts +10 -0
- package/src/tools/github-side-effect.ts +112 -0
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/monitor-mutation.ts +27 -0
- package/src/tools/monitor.ts +7 -0
- package/src/tools/python.ts +188 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/external-editor.ts +3 -2
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/insane/url-guard.ts +2 -1
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generated bundled GJC workflow skill catalog.
|
|
3
3
|
*
|
|
4
|
-
* Keep this module metadata-only: skill bodies are
|
|
5
|
-
*
|
|
4
|
+
* Keep this module metadata-only: skill bodies are read from their embedded
|
|
5
|
+
* `type: "file"` paths only when a caller asks for their content. Do not also
|
|
6
|
+
* import a body with `type: "text"`: Bun caches a module per specifier, so the
|
|
7
|
+
* second import of the same file would return the first one's value.
|
|
6
8
|
*/
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import autoresearchAutoCriticPath from "./gjc/skills/autoresearch/auto-critic.md" with { type: "file" };
|
|
11
|
+
import autoresearchAutoIteratePath from "./gjc/skills/autoresearch/auto-iterate.md" with { type: "file" };
|
|
12
|
+
import autoresearchSkillPath from "./gjc/skills/autoresearch/SKILL.md" with { type: "file" };
|
|
13
|
+
import deepInterviewAutoAnswerUncertainPath from "./gjc/skills/deep-interview/auto-answer-uncertain.md" with {
|
|
14
|
+
type: "file",
|
|
15
|
+
};
|
|
16
|
+
import deepInterviewLateralReviewPanelPath from "./gjc/skills/deep-interview/lateral-review-panel.md" with {
|
|
17
|
+
type: "file",
|
|
18
|
+
};
|
|
19
|
+
import deepInterviewSkillPath from "./gjc/skills/deep-interview/SKILL.md" with { type: "file" };
|
|
20
|
+
import ralplanSkillPath from "./gjc/skills/ralplan/SKILL.md" with { type: "file" };
|
|
21
|
+
import ultragoalAiSlopCleanerPath from "./gjc/skills/ultragoal/ai-slop-cleaner.md" with { type: "file" };
|
|
22
|
+
import ultragoalBoundaryCohortGatePath from "./gjc/skills/ultragoal/boundary-cohort-gate.md" with { type: "file" };
|
|
23
|
+
import ultragoalCrossRepositorySuccessionPath from "./gjc/skills/ultragoal/cross-repository-succession.md" with {
|
|
24
|
+
type: "file",
|
|
25
|
+
};
|
|
26
|
+
import ultragoalSkillPath from "./gjc/skills/ultragoal/SKILL.md" with { type: "file" };
|
|
27
|
+
import ultragoalTerminalCriticGatePath from "./gjc/skills/ultragoal/terminal-critic-gate.md" with { type: "file" };
|
|
28
|
+
import ultragoalValidationBatchContractsPath from "./gjc/skills/ultragoal/validation-batch-contracts.md" with {
|
|
29
|
+
type: "file",
|
|
30
|
+
};
|
|
31
|
+
|
|
7
32
|
export type BundledGjcSkillName = "autoresearch" | "deep-interview" | "ralplan" | "ultragoal";
|
|
8
33
|
|
|
9
34
|
export interface BundledGjcSkillCatalogEntry {
|
|
@@ -12,49 +37,34 @@ export interface BundledGjcSkillCatalogEntry {
|
|
|
12
37
|
readonly parentSkillName?: BundledGjcSkillName;
|
|
13
38
|
readonly relativePath: string;
|
|
14
39
|
readonly description?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Path of the bundled body. A `type: "file"` import, so `bun build --compile`
|
|
42
|
+
* embeds the file and this resolves inside `/$bunfs` in compiled binaries.
|
|
43
|
+
*/
|
|
44
|
+
readonly sourcePath?: string;
|
|
15
45
|
readonly loadContent: () => Promise<string>;
|
|
16
46
|
}
|
|
17
47
|
|
|
18
|
-
const deepInterview = () =>
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
import("./gjc/skills/deep-interview/lateral-review-panel.md", { with: { type: "text" } }).then(
|
|
32
|
-
module => module.default,
|
|
33
|
-
);
|
|
34
|
-
const aiSlopCleaner = () =>
|
|
35
|
-
import("./gjc/skills/ultragoal/ai-slop-cleaner.md", { with: { type: "text" } }).then(module => module.default);
|
|
36
|
-
const validationBatchContracts = () =>
|
|
37
|
-
import("./gjc/skills/ultragoal/validation-batch-contracts.md", { with: { type: "text" } }).then(
|
|
38
|
-
module => module.default,
|
|
39
|
-
);
|
|
40
|
-
const boundaryCohortGate = () =>
|
|
41
|
-
import("./gjc/skills/ultragoal/boundary-cohort-gate.md", { with: { type: "text" } }).then(module => module.default);
|
|
42
|
-
const terminalCriticGate = () =>
|
|
43
|
-
import("./gjc/skills/ultragoal/terminal-critic-gate.md", { with: { type: "text" } }).then(module => module.default);
|
|
44
|
-
const crossRepositorySuccession = () =>
|
|
45
|
-
import("./gjc/skills/ultragoal/cross-repository-succession.md", { with: { type: "text" } }).then(
|
|
46
|
-
module => module.default,
|
|
47
|
-
);
|
|
48
|
-
const autoresearchIterate = () =>
|
|
49
|
-
import("./gjc/skills/autoresearch/auto-iterate.md", { with: { type: "text" } }).then(module => module.default);
|
|
50
|
-
const autoresearchCritic = () =>
|
|
51
|
-
import("./gjc/skills/autoresearch/auto-critic.md", { with: { type: "text" } }).then(module => module.default);
|
|
48
|
+
const deepInterview = () => readFile(deepInterviewSkillPath, "utf8");
|
|
49
|
+
const ralplan = () => readFile(ralplanSkillPath, "utf8");
|
|
50
|
+
const autoresearch = () => readFile(autoresearchSkillPath, "utf8");
|
|
51
|
+
const ultragoal = () => readFile(ultragoalSkillPath, "utf8");
|
|
52
|
+
const autoAnswerUncertain = () => readFile(deepInterviewAutoAnswerUncertainPath, "utf8");
|
|
53
|
+
const lateralReviewPanel = () => readFile(deepInterviewLateralReviewPanelPath, "utf8");
|
|
54
|
+
const aiSlopCleaner = () => readFile(ultragoalAiSlopCleanerPath, "utf8");
|
|
55
|
+
const validationBatchContracts = () => readFile(ultragoalValidationBatchContractsPath, "utf8");
|
|
56
|
+
const boundaryCohortGate = () => readFile(ultragoalBoundaryCohortGatePath, "utf8");
|
|
57
|
+
const terminalCriticGate = () => readFile(ultragoalTerminalCriticGatePath, "utf8");
|
|
58
|
+
const crossRepositorySuccession = () => readFile(ultragoalCrossRepositorySuccessionPath, "utf8");
|
|
59
|
+
const autoresearchIterate = () => readFile(autoresearchAutoIteratePath, "utf8");
|
|
60
|
+
const autoresearchCritic = () => readFile(autoresearchAutoCriticPath, "utf8");
|
|
52
61
|
|
|
53
62
|
export const BUNDLED_GJC_SKILL_CATALOG: readonly BundledGjcSkillCatalogEntry[] = [
|
|
54
63
|
{
|
|
55
64
|
kind: "skill",
|
|
56
65
|
name: "deep-interview",
|
|
57
66
|
relativePath: "skills/deep-interview/SKILL.md",
|
|
67
|
+
sourcePath: deepInterviewSkillPath,
|
|
58
68
|
description: "Socratic deep interview with mathematical ambiguity gating before explicit execution approval",
|
|
59
69
|
loadContent: deepInterview,
|
|
60
70
|
},
|
|
@@ -62,6 +72,7 @@ export const BUNDLED_GJC_SKILL_CATALOG: readonly BundledGjcSkillCatalogEntry[] =
|
|
|
62
72
|
kind: "skill",
|
|
63
73
|
name: "ralplan",
|
|
64
74
|
relativePath: "skills/ralplan/SKILL.md",
|
|
75
|
+
sourcePath: ralplanSkillPath,
|
|
65
76
|
description: "Consensus planning entrypoint that auto-gates vague ultragoal requests before execution",
|
|
66
77
|
loadContent: ralplan,
|
|
67
78
|
},
|
|
@@ -69,6 +80,7 @@ export const BUNDLED_GJC_SKILL_CATALOG: readonly BundledGjcSkillCatalogEntry[] =
|
|
|
69
80
|
kind: "skill",
|
|
70
81
|
name: "autoresearch",
|
|
71
82
|
relativePath: "skills/autoresearch/SKILL.md",
|
|
83
|
+
sourcePath: autoresearchSkillPath,
|
|
72
84
|
description: "Goal-directed research missions interleaving web and data evidence into a structured verdict",
|
|
73
85
|
loadContent: autoresearch,
|
|
74
86
|
},
|
|
@@ -76,6 +88,7 @@ export const BUNDLED_GJC_SKILL_CATALOG: readonly BundledGjcSkillCatalogEntry[] =
|
|
|
76
88
|
kind: "skill",
|
|
77
89
|
name: "ultragoal",
|
|
78
90
|
relativePath: "skills/ultragoal/SKILL.md",
|
|
91
|
+
sourcePath: ultragoalSkillPath,
|
|
79
92
|
description: "Create and execute durable repo-native multi-goal plans over GJC goal mode artifacts.",
|
|
80
93
|
loadContent: ultragoal,
|
|
81
94
|
},
|
|
@@ -83,54 +96,63 @@ export const BUNDLED_GJC_SKILL_CATALOG: readonly BundledGjcSkillCatalogEntry[] =
|
|
|
83
96
|
kind: "skill-fragment",
|
|
84
97
|
parentSkillName: "deep-interview",
|
|
85
98
|
relativePath: "skill-fragments/deep-interview/auto-answer-uncertain.md",
|
|
99
|
+
sourcePath: deepInterviewAutoAnswerUncertainPath,
|
|
86
100
|
loadContent: autoAnswerUncertain,
|
|
87
101
|
},
|
|
88
102
|
{
|
|
89
103
|
kind: "skill-fragment",
|
|
90
104
|
parentSkillName: "deep-interview",
|
|
91
105
|
relativePath: "skill-fragments/deep-interview/lateral-review-panel.md",
|
|
106
|
+
sourcePath: deepInterviewLateralReviewPanelPath,
|
|
92
107
|
loadContent: lateralReviewPanel,
|
|
93
108
|
},
|
|
94
109
|
{
|
|
95
110
|
kind: "skill-fragment",
|
|
96
111
|
parentSkillName: "ultragoal",
|
|
97
112
|
relativePath: "skill-fragments/ultragoal/ai-slop-cleaner.md",
|
|
113
|
+
sourcePath: ultragoalAiSlopCleanerPath,
|
|
98
114
|
loadContent: aiSlopCleaner,
|
|
99
115
|
},
|
|
100
116
|
{
|
|
101
117
|
kind: "skill-fragment",
|
|
102
118
|
parentSkillName: "ultragoal",
|
|
103
119
|
relativePath: "skill-fragments/ultragoal/validation-batch-contracts.md",
|
|
120
|
+
sourcePath: ultragoalValidationBatchContractsPath,
|
|
104
121
|
loadContent: validationBatchContracts,
|
|
105
122
|
},
|
|
106
123
|
{
|
|
107
124
|
kind: "skill-fragment",
|
|
108
125
|
parentSkillName: "ultragoal",
|
|
109
126
|
relativePath: "skill-fragments/ultragoal/boundary-cohort-gate.md",
|
|
127
|
+
sourcePath: ultragoalBoundaryCohortGatePath,
|
|
110
128
|
loadContent: boundaryCohortGate,
|
|
111
129
|
},
|
|
112
130
|
{
|
|
113
131
|
kind: "skill-fragment",
|
|
114
132
|
parentSkillName: "ultragoal",
|
|
115
133
|
relativePath: "skill-fragments/ultragoal/terminal-critic-gate.md",
|
|
134
|
+
sourcePath: ultragoalTerminalCriticGatePath,
|
|
116
135
|
loadContent: terminalCriticGate,
|
|
117
136
|
},
|
|
118
137
|
{
|
|
119
138
|
kind: "skill-fragment",
|
|
120
139
|
parentSkillName: "ultragoal",
|
|
121
140
|
relativePath: "skill-fragments/ultragoal/cross-repository-succession.md",
|
|
141
|
+
sourcePath: ultragoalCrossRepositorySuccessionPath,
|
|
122
142
|
loadContent: crossRepositorySuccession,
|
|
123
143
|
},
|
|
124
144
|
{
|
|
125
145
|
kind: "skill-fragment",
|
|
126
146
|
parentSkillName: "autoresearch",
|
|
127
147
|
relativePath: "skill-fragments/autoresearch/auto-iterate.md",
|
|
148
|
+
sourcePath: autoresearchAutoIteratePath,
|
|
128
149
|
loadContent: autoresearchIterate,
|
|
129
150
|
},
|
|
130
151
|
{
|
|
131
152
|
kind: "skill-fragment",
|
|
132
153
|
parentSkillName: "autoresearch",
|
|
133
154
|
relativePath: "skill-fragments/autoresearch/auto-critic.md",
|
|
155
|
+
sourcePath: autoresearchAutoCriticPath,
|
|
134
156
|
loadContent: autoresearchCritic,
|
|
135
157
|
},
|
|
136
158
|
];
|
package/src/discovery/agents.ts
CHANGED
|
@@ -79,7 +79,12 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
|
79
79
|
scanSkillsFromDir(ctx, { dir, providerId: PROVIDER_ID, level: "project" }),
|
|
80
80
|
);
|
|
81
81
|
const userScans = getUserPathCandidates(ctx, "skills").map(dir =>
|
|
82
|
-
scanSkillsFromDir(ctx, {
|
|
82
|
+
scanSkillsFromDir(ctx, {
|
|
83
|
+
dir,
|
|
84
|
+
providerId: PROVIDER_ID,
|
|
85
|
+
level: "user",
|
|
86
|
+
allowExternalUserSkillSymlinks: ctx.allowExternalUserSkillSymlinks,
|
|
87
|
+
}),
|
|
83
88
|
);
|
|
84
89
|
|
|
85
90
|
const results = await Promise.all([...projectScans, ...userScans]);
|
package/src/discovery/builtin.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { type SlashCommand, slashCommandCapability } from "../capability/slash-c
|
|
|
22
22
|
import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
|
|
23
23
|
import { type CustomTool, toolCapability } from "../capability/tool";
|
|
24
24
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
25
|
+
import { resolveGlobalUserSkillLinkTrust } from "../config/skill-settings-defaults";
|
|
25
26
|
import { expandTilde } from "../tools/path-utils";
|
|
26
27
|
import {
|
|
27
28
|
buildRuleFromMarkdown,
|
|
@@ -137,7 +138,9 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
137
138
|
const data = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
|
|
138
139
|
if (!data?.mcpServers) return result;
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
// Project mcp.json is repo content. Skip secret-named ${VAR} the same way
|
|
142
|
+
// project ssh.json does; user scope still expands those names.
|
|
143
|
+
const expanded = expandEnvVarsDeep(data.mcpServers, undefined, level === "project");
|
|
141
144
|
for (const [serverName, config] of Object.entries(expanded)) {
|
|
142
145
|
const serverConfig = config as Record<string, unknown>;
|
|
143
146
|
|
|
@@ -220,6 +223,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
220
223
|
cwd: serverConfig.cwd as string | undefined,
|
|
221
224
|
url: serverConfig.url as string | undefined,
|
|
222
225
|
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
226
|
+
publicNetwork: serverConfig.publicNetwork === true ? true : undefined,
|
|
223
227
|
auth: serverConfig.auth as
|
|
224
228
|
| {
|
|
225
229
|
type: "oauth" | "apikey";
|
|
@@ -359,6 +363,14 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
|
359
363
|
level: "user",
|
|
360
364
|
scope: "native",
|
|
361
365
|
requireDescription: true,
|
|
366
|
+
allowExternalUserSkillSymlinks:
|
|
367
|
+
ctx.allowExternalUserSkillSymlinks ??
|
|
368
|
+
(typeof ctx.settings?.getGlobal === "function"
|
|
369
|
+
? resolveGlobalUserSkillLinkTrust({
|
|
370
|
+
trustUserSkills: ctx.settings.getGlobal("skills.trustUserSkills"),
|
|
371
|
+
enablePiUser: ctx.settings.getGlobal("skills.enablePiUser"),
|
|
372
|
+
})
|
|
373
|
+
: true),
|
|
362
374
|
}),
|
|
363
375
|
);
|
|
364
376
|
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -479,6 +479,8 @@ export interface ScanSkillsFromDirOptions {
|
|
|
479
479
|
linkContainmentRoot?: string;
|
|
480
480
|
/** Filesystem authority for explicit-home reads. */
|
|
481
481
|
scope?: ReadScope;
|
|
482
|
+
/** User-owned permission to follow symlinks outside a user-scope scan root. */
|
|
483
|
+
allowExternalUserSkillSymlinks?: boolean;
|
|
482
484
|
}
|
|
483
485
|
|
|
484
486
|
// Stable ordering used for skill lists in prompts: name (case-insensitive), then name, then path.
|
|
@@ -498,6 +500,8 @@ export const SKILL_FRONTMATTER_SCAN_TOTAL_BYTES = 64 * 1024;
|
|
|
498
500
|
|
|
499
501
|
export const SkillDiscoveryTestHooks: {
|
|
500
502
|
afterCandidateValidated?: (candidatePath: string) => void | Promise<void>;
|
|
503
|
+
afterSkillLinksCaptured?: (candidatePath: string) => void | Promise<void>;
|
|
504
|
+
afterSkillPathResolved?: (candidatePath: string) => void | Promise<void>;
|
|
501
505
|
afterAuthorityRootValidated?: (root: string) => void | Promise<void>;
|
|
502
506
|
afterAuthorityRootMissing?: (root: string) => void | Promise<void>;
|
|
503
507
|
afterContainedRootValidated?: (root: string) => void | Promise<void>;
|
|
@@ -517,6 +521,25 @@ interface ScanRootLinkIdentity {
|
|
|
517
521
|
target: DirectoryIdentity;
|
|
518
522
|
}
|
|
519
523
|
|
|
524
|
+
interface SkillFileIdentity {
|
|
525
|
+
dev: bigint;
|
|
526
|
+
ino: bigint;
|
|
527
|
+
realPath: string;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
interface SkillLinkIdentity {
|
|
531
|
+
path: string;
|
|
532
|
+
dev: bigint;
|
|
533
|
+
ino: bigint;
|
|
534
|
+
linkText: string;
|
|
535
|
+
target: {
|
|
536
|
+
dev: bigint;
|
|
537
|
+
ino: bigint;
|
|
538
|
+
realPath: string;
|
|
539
|
+
kind: "directory" | "file";
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
520
543
|
async function captureScanRootLinkIdentity(root: string): Promise<ScanRootLinkIdentity | null> {
|
|
521
544
|
const observed = await fs.promises.lstat(root, { bigint: true });
|
|
522
545
|
if (!observed.isSymbolicLink()) return null;
|
|
@@ -577,6 +600,38 @@ async function directoryIdentityIsCurrent(
|
|
|
577
600
|
}
|
|
578
601
|
}
|
|
579
602
|
|
|
603
|
+
async function captureSkillLinkIdentity(linkPath: string): Promise<SkillLinkIdentity | null> {
|
|
604
|
+
const observed = await fs.promises.lstat(linkPath, { bigint: true });
|
|
605
|
+
if (!observed.isSymbolicLink()) return null;
|
|
606
|
+
const linkText = await fs.promises.readlink(linkPath);
|
|
607
|
+
const realPath = await fs.promises.realpath(linkPath);
|
|
608
|
+
const target = await fs.promises.stat(realPath, { bigint: true });
|
|
609
|
+
const kind = target.isDirectory() ? "directory" : target.isFile() ? "file" : undefined;
|
|
610
|
+
if (!kind) return null;
|
|
611
|
+
return {
|
|
612
|
+
path: linkPath,
|
|
613
|
+
dev: observed.dev,
|
|
614
|
+
ino: observed.ino,
|
|
615
|
+
linkText,
|
|
616
|
+
target: { dev: target.dev, ino: target.ino, realPath, kind },
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function skillLinkIdentityIsCurrent(expected: SkillLinkIdentity): Promise<boolean> {
|
|
621
|
+
try {
|
|
622
|
+
const observed = await fs.promises.lstat(expected.path, { bigint: true });
|
|
623
|
+
if (!observed.isSymbolicLink() || observed.dev !== expected.dev || observed.ino !== expected.ino) return false;
|
|
624
|
+
if ((await fs.promises.readlink(expected.path)) !== expected.linkText) return false;
|
|
625
|
+
const realPath = await fs.promises.realpath(expected.path);
|
|
626
|
+
if (normalizePathForComparison(realPath) !== normalizePathForComparison(expected.target.realPath)) return false;
|
|
627
|
+
const target = await fs.promises.stat(realPath, { bigint: true });
|
|
628
|
+
const kind = target.isDirectory() ? "directory" : target.isFile() ? "file" : undefined;
|
|
629
|
+
return kind === expected.target.kind && target.dev === expected.target.dev && target.ino === expected.target.ino;
|
|
630
|
+
} catch {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
580
635
|
export async function scanSkillsFromDir(
|
|
581
636
|
_ctx: LoadContext,
|
|
582
637
|
options: ScanSkillsFromDirOptions,
|
|
@@ -693,7 +748,36 @@ export async function scanSkillsFromDir(
|
|
|
693
748
|
const relative = path.relative(realRoot, candidate);
|
|
694
749
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
695
750
|
};
|
|
696
|
-
const
|
|
751
|
+
const skillPathAndLinksAreCurrent = async (
|
|
752
|
+
skillPath: string,
|
|
753
|
+
expectedFile: SkillFileIdentity | undefined,
|
|
754
|
+
linkIdentities: readonly SkillLinkIdentity[],
|
|
755
|
+
): Promise<boolean> => {
|
|
756
|
+
try {
|
|
757
|
+
const [realPath, currentFile, currentLinks] = await Promise.all([
|
|
758
|
+
fs.promises.realpath(skillPath),
|
|
759
|
+
expectedFile ? fs.promises.stat(expectedFile.realPath, { bigint: true }) : Promise.resolve(undefined),
|
|
760
|
+
Promise.all(linkIdentities.map(skillLinkIdentityIsCurrent)),
|
|
761
|
+
]);
|
|
762
|
+
if (!expectedFile) return isWithinRoot(realPath) && currentLinks.every(Boolean);
|
|
763
|
+
if (!currentFile) return false;
|
|
764
|
+
return (
|
|
765
|
+
normalizePathForComparison(realPath) === normalizePathForComparison(expectedFile.realPath) &&
|
|
766
|
+
currentFile.isFile() &&
|
|
767
|
+
currentFile.nlink === 1n &&
|
|
768
|
+
currentFile.dev === expectedFile.dev &&
|
|
769
|
+
currentFile.ino === expectedFile.ino &&
|
|
770
|
+
currentLinks.every(Boolean)
|
|
771
|
+
);
|
|
772
|
+
} catch {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
const openSkillFileSafely = async (
|
|
777
|
+
skillPath: string,
|
|
778
|
+
expectedFile: SkillFileIdentity | undefined,
|
|
779
|
+
linkIdentities: readonly SkillLinkIdentity[],
|
|
780
|
+
): Promise<FileHandle> => {
|
|
697
781
|
if (
|
|
698
782
|
!(await scanRootIsCurrent()) ||
|
|
699
783
|
(options.authorityRoot &&
|
|
@@ -707,14 +791,14 @@ export async function scanSkillsFromDir(
|
|
|
707
791
|
(process.platform === "win32" ? 0 : (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
|
|
708
792
|
const handle = await fs.promises.open(skillPath, flags);
|
|
709
793
|
try {
|
|
710
|
-
const [opened, observed,
|
|
794
|
+
const [opened, observed, currentScanRoot, currentAuthorityRoot, currentSkillPath] = await Promise.all([
|
|
711
795
|
handle.stat({ bigint: true }),
|
|
712
796
|
fs.promises.lstat(skillPath, { bigint: true }),
|
|
713
|
-
fs.promises.realpath(skillPath),
|
|
714
797
|
scanRootIsCurrent(),
|
|
715
798
|
options.authorityRoot && authorityIdentity
|
|
716
799
|
? directoryIdentityIsCurrent(options.authorityRoot, authorityIdentity)
|
|
717
800
|
: true,
|
|
801
|
+
skillPathAndLinksAreCurrent(skillPath, expectedFile, linkIdentities),
|
|
718
802
|
]);
|
|
719
803
|
if (
|
|
720
804
|
!opened.isFile() ||
|
|
@@ -723,9 +807,10 @@ export async function scanSkillsFromDir(
|
|
|
723
807
|
!observed.isFile() ||
|
|
724
808
|
opened.dev !== observed.dev ||
|
|
725
809
|
opened.ino !== observed.ino ||
|
|
726
|
-
|
|
810
|
+
(expectedFile !== undefined && (opened.dev !== expectedFile.dev || opened.ino !== expectedFile.ino)) ||
|
|
727
811
|
!currentScanRoot ||
|
|
728
|
-
!currentAuthorityRoot
|
|
812
|
+
!currentAuthorityRoot ||
|
|
813
|
+
!currentSkillPath
|
|
729
814
|
) {
|
|
730
815
|
throw new Error(`Unsafe skill file: ${skillPath}`);
|
|
731
816
|
}
|
|
@@ -735,15 +820,20 @@ export async function scanSkillsFromDir(
|
|
|
735
820
|
throw error;
|
|
736
821
|
}
|
|
737
822
|
};
|
|
738
|
-
const readSkillContentSafely = async (
|
|
739
|
-
|
|
823
|
+
const readSkillContentSafely = async (
|
|
824
|
+
skillPath: string,
|
|
825
|
+
expectedFile: SkillFileIdentity | undefined,
|
|
826
|
+
linkIdentities: readonly SkillLinkIdentity[],
|
|
827
|
+
): Promise<string> => {
|
|
828
|
+
const handle = await openSkillFileSafely(skillPath, expectedFile, linkIdentities);
|
|
740
829
|
try {
|
|
741
830
|
const content = await handle.readFile({ encoding: "utf8" });
|
|
742
831
|
if (
|
|
743
832
|
!(await scanRootIsCurrent()) ||
|
|
744
833
|
(options.authorityRoot &&
|
|
745
834
|
authorityIdentity &&
|
|
746
|
-
!(await directoryIdentityIsCurrent(options.authorityRoot, authorityIdentity)))
|
|
835
|
+
!(await directoryIdentityIsCurrent(options.authorityRoot, authorityIdentity))) ||
|
|
836
|
+
!(await skillPathAndLinksAreCurrent(skillPath, expectedFile, linkIdentities))
|
|
747
837
|
) {
|
|
748
838
|
throw new Error(`Unsafe skill authority root for: ${skillPath}`);
|
|
749
839
|
}
|
|
@@ -754,8 +844,10 @@ export async function scanSkillsFromDir(
|
|
|
754
844
|
};
|
|
755
845
|
const readSkillFrontmatterSafely = async (
|
|
756
846
|
skillPath: string,
|
|
847
|
+
expectedFile: SkillFileIdentity | undefined,
|
|
848
|
+
linkIdentities: readonly SkillLinkIdentity[],
|
|
757
849
|
): Promise<{ frontmatter: SkillFrontmatter | null; size: number }> => {
|
|
758
|
-
const handle = await openSkillFileSafely(skillPath);
|
|
850
|
+
const handle = await openSkillFileSafely(skillPath, expectedFile, linkIdentities);
|
|
759
851
|
try {
|
|
760
852
|
const size = Number((await handle.stat({ bigint: true })).size);
|
|
761
853
|
const scanLimit = Math.min(size, SKILL_FRONTMATTER_SCAN_TOTAL_BYTES);
|
|
@@ -786,20 +878,64 @@ export async function scanSkillsFromDir(
|
|
|
786
878
|
await handle.close();
|
|
787
879
|
}
|
|
788
880
|
};
|
|
789
|
-
const loadSkill = async (candidatePath: string) => {
|
|
881
|
+
const loadSkill = async (candidatePath: string, expectedCanonicalPath?: string) => {
|
|
790
882
|
try {
|
|
883
|
+
const linkIdentities = (
|
|
884
|
+
await Promise.all([
|
|
885
|
+
captureSkillLinkIdentity(path.dirname(candidatePath)),
|
|
886
|
+
captureSkillLinkIdentity(candidatePath),
|
|
887
|
+
])
|
|
888
|
+
).filter((identity): identity is SkillLinkIdentity => identity !== null);
|
|
889
|
+
await SkillDiscoveryTestHooks.afterSkillLinksCaptured?.(candidatePath);
|
|
791
890
|
const skillPath = await fs.promises.realpath(candidatePath);
|
|
792
|
-
|
|
793
|
-
|
|
891
|
+
await SkillDiscoveryTestHooks.afterSkillPathResolved?.(candidatePath);
|
|
892
|
+
if (
|
|
893
|
+
expectedCanonicalPath &&
|
|
894
|
+
normalizePathForComparison(skillPath) !== normalizePathForComparison(expectedCanonicalPath)
|
|
895
|
+
) {
|
|
896
|
+
warnings.push(`Refusing changed skill path during isolated-home scan: ${candidatePath}`);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
const allowOutsideRoot = options.allowExternalUserSkillSymlinks && level === "user";
|
|
900
|
+
if (!isWithinRoot(skillPath) && !allowOutsideRoot) {
|
|
901
|
+
const remedy =
|
|
902
|
+
level === "user"
|
|
903
|
+
? `add ${path.dirname(path.dirname(skillPath))} to skills.customDirectories to load it, or enable skills.trustUserSkills`
|
|
904
|
+
: "keep project skill targets inside the skills scan root";
|
|
905
|
+
warnings.push(
|
|
906
|
+
`Refusing skill path outside scan root: ${candidatePath} (resolves to ${skillPath}; ${remedy})`,
|
|
907
|
+
);
|
|
794
908
|
return;
|
|
795
909
|
}
|
|
796
|
-
const stat = await fs.promises.stat(skillPath);
|
|
797
|
-
if (!stat.isFile() || stat.nlink !==
|
|
910
|
+
const stat = await fs.promises.stat(skillPath, { bigint: true });
|
|
911
|
+
if (!stat.isFile() || stat.nlink !== 1n) {
|
|
798
912
|
warnings.push(`Skill path is not a regular file: ${candidatePath}`);
|
|
799
913
|
return;
|
|
800
914
|
}
|
|
915
|
+
const pinExternalTarget = !isWithinRoot(skillPath) && allowOutsideRoot;
|
|
916
|
+
const fileLink = linkIdentities.find(identity => identity.path === candidatePath);
|
|
917
|
+
const parentLink = linkIdentities.find(identity => identity.path === path.dirname(candidatePath));
|
|
918
|
+
let capturedLinkTargetPath: string | undefined;
|
|
919
|
+
if (fileLink?.target.kind === "file") {
|
|
920
|
+
capturedLinkTargetPath = fileLink.target.realPath;
|
|
921
|
+
} else if (parentLink?.target.kind === "directory") {
|
|
922
|
+
capturedLinkTargetPath = path.join(parentLink.target.realPath, path.basename(candidatePath));
|
|
923
|
+
}
|
|
924
|
+
if (
|
|
925
|
+
pinExternalTarget &&
|
|
926
|
+
(!capturedLinkTargetPath ||
|
|
927
|
+
normalizePathForComparison(skillPath) !== normalizePathForComparison(capturedLinkTargetPath))
|
|
928
|
+
) {
|
|
929
|
+
warnings.push(`Refusing skill path not bound to its captured external link target: ${candidatePath}`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const fileIdentity = pinExternalTarget ? { dev: stat.dev, ino: stat.ino, realPath: skillPath } : undefined;
|
|
933
|
+
const pinnedLinkIdentities = pinExternalTarget ? linkIdentities : [];
|
|
801
934
|
await SkillDiscoveryTestHooks.afterCandidateValidated?.(candidatePath);
|
|
802
|
-
const { frontmatter, size } = await readSkillFrontmatterSafely(skillPath);
|
|
935
|
+
const { frontmatter, size } = await readSkillFrontmatterSafely(skillPath, fileIdentity, pinnedLinkIdentities);
|
|
936
|
+
if (!(await skillPathAndLinksAreCurrent(skillPath, fileIdentity, pinnedLinkIdentities))) {
|
|
937
|
+
throw new Error(`Unsafe skill link for: ${candidatePath}`);
|
|
938
|
+
}
|
|
803
939
|
if (!frontmatter) {
|
|
804
940
|
if (size > SKILL_FRONTMATTER_SCAN_TOTAL_BYTES) {
|
|
805
941
|
warnings.push(
|
|
@@ -823,8 +959,9 @@ export async function scanSkillsFromDir(
|
|
|
823
959
|
items.push({
|
|
824
960
|
name,
|
|
825
961
|
path: skillPath,
|
|
962
|
+
discoveryPath: candidatePath,
|
|
826
963
|
loadContent: async () => {
|
|
827
|
-
const content = await readSkillContentSafely(skillPath);
|
|
964
|
+
const content = await readSkillContentSafely(skillPath, fileIdentity, pinnedLinkIdentities);
|
|
828
965
|
return parseFrontmatter(content, { source: skillPath }).body;
|
|
829
966
|
},
|
|
830
967
|
frontmatter: frontmatter as SkillFrontmatter,
|
|
@@ -841,14 +978,10 @@ export async function scanSkillsFromDir(
|
|
|
841
978
|
for (const entry of entries) {
|
|
842
979
|
if (entry.name.startsWith(".")) continue;
|
|
843
980
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
scope,
|
|
849
|
-
);
|
|
850
|
-
if (!skillPath) continue;
|
|
851
|
-
work.push(loadSkill(skillPath));
|
|
981
|
+
const candidatePath = path.join(scanDir, entry.name, "SKILL.md");
|
|
982
|
+
const canonicalSkillPath = await canonicalizePathWithinHome(_ctx, candidatePath, containmentRoot, scope);
|
|
983
|
+
if (!canonicalSkillPath) continue;
|
|
984
|
+
work.push(loadSkill(candidatePath, _ctx.isolatedHome ? canonicalSkillPath : undefined));
|
|
852
985
|
}
|
|
853
986
|
await Promise.all(work);
|
|
854
987
|
|
|
@@ -923,8 +1056,17 @@ export async function readContainedFile(
|
|
|
923
1056
|
* Expand environment variables in a string.
|
|
924
1057
|
* Supports ${VAR} and ${VAR:-default} syntax.
|
|
925
1058
|
*/
|
|
926
|
-
|
|
1059
|
+
const SENSITIVE_ENV_NAME = /key|secret|token|pass|auth|credential|cookie|dsn|url$/i;
|
|
1060
|
+
|
|
1061
|
+
export function isSensitiveEnvName(name: string): boolean {
|
|
1062
|
+
return SENSITIVE_ENV_NAME.test(name);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function expandEnvVars(value: string, extraEnv?: Record<string, string>, skipSensitiveNames = false): string {
|
|
927
1066
|
return value.replace(/\$\{([^}:]+)(?::-([^}]*))?\}/g, (_, varName: string, defaultValue?: string) => {
|
|
1067
|
+
if (skipSensitiveNames && isSensitiveEnvName(varName)) {
|
|
1068
|
+
return defaultValue !== undefined ? defaultValue : `\${${varName}}`;
|
|
1069
|
+
}
|
|
928
1070
|
const envValue = extraEnv?.[varName] ?? Bun.env[varName];
|
|
929
1071
|
if (envValue !== undefined) return envValue;
|
|
930
1072
|
if (defaultValue !== undefined) return defaultValue;
|
|
@@ -935,17 +1077,17 @@ function expandEnvVars(value: string, extraEnv?: Record<string, string>): string
|
|
|
935
1077
|
/**
|
|
936
1078
|
* Recursively expand environment variables in an object.
|
|
937
1079
|
*/
|
|
938
|
-
export function expandEnvVarsDeep<T>(obj: T, extraEnv?: Record<string, string
|
|
1080
|
+
export function expandEnvVarsDeep<T>(obj: T, extraEnv?: Record<string, string>, skipSensitiveNames = false): T {
|
|
939
1081
|
if (typeof obj === "string") {
|
|
940
|
-
return expandEnvVars(obj, extraEnv) as T;
|
|
1082
|
+
return expandEnvVars(obj, extraEnv, skipSensitiveNames) as T;
|
|
941
1083
|
}
|
|
942
1084
|
if (Array.isArray(obj)) {
|
|
943
|
-
return obj.map(item => expandEnvVarsDeep(item, extraEnv)) as T;
|
|
1085
|
+
return obj.map(item => expandEnvVarsDeep(item, extraEnv, skipSensitiveNames)) as T;
|
|
944
1086
|
}
|
|
945
1087
|
if (obj !== null && typeof obj === "object") {
|
|
946
1088
|
const result: Record<string, unknown> = {};
|
|
947
1089
|
for (const [key, value] of Object.entries(obj)) {
|
|
948
|
-
result[key] = expandEnvVarsDeep(value, extraEnv);
|
|
1090
|
+
result[key] = expandEnvVarsDeep(value, extraEnv, skipSensitiveNames);
|
|
949
1091
|
}
|
|
950
1092
|
return result as T;
|
|
951
1093
|
}
|
|
@@ -154,7 +154,12 @@ function validateExactMCPConfig(config: unknown): {
|
|
|
154
154
|
/**
|
|
155
155
|
* Transform raw MCP config to canonical MCPServer format.
|
|
156
156
|
*/
|
|
157
|
-
function transformMCPConfig(
|
|
157
|
+
function transformMCPConfig(
|
|
158
|
+
config: MCPConfigFile,
|
|
159
|
+
source: SourceMeta,
|
|
160
|
+
quiet = false,
|
|
161
|
+
skipSensitiveEnvNames = false,
|
|
162
|
+
): MCPServer[] {
|
|
158
163
|
const servers: MCPServer[] = [];
|
|
159
164
|
|
|
160
165
|
if (config.mcpServers) {
|
|
@@ -217,21 +222,29 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta, quiet = f
|
|
|
217
222
|
cwd: serverConfig.cwd,
|
|
218
223
|
url: serverConfig.url,
|
|
219
224
|
headers: serverConfig.headers,
|
|
225
|
+
publicNetwork:
|
|
226
|
+
serverConfig.type === "http" &&
|
|
227
|
+
"publicNetwork" in serverConfig &&
|
|
228
|
+
(serverConfig as { publicNetwork?: boolean }).publicNetwork === true
|
|
229
|
+
? true
|
|
230
|
+
: undefined,
|
|
220
231
|
auth: serverConfig.auth,
|
|
221
232
|
oauth: serverConfig.oauth,
|
|
222
233
|
transport: serverConfig.type,
|
|
223
234
|
_source: source,
|
|
224
235
|
};
|
|
225
236
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (server.
|
|
230
|
-
if (server.
|
|
231
|
-
if (server.
|
|
232
|
-
if (server.
|
|
233
|
-
if (server.
|
|
234
|
-
if (server.
|
|
237
|
+
// Repo-root mcp.json is project content. Exact `--mcp-config` passes
|
|
238
|
+
// skipSensitiveEnvNames false so a trusted file can still reference secrets.
|
|
239
|
+
const expand = <T>(value: T): T => expandEnvVarsDeep(value, undefined, skipSensitiveEnvNames);
|
|
240
|
+
if (server.command) server.command = expand(server.command);
|
|
241
|
+
if (server.args) server.args = expand(server.args);
|
|
242
|
+
if (server.env) server.env = expand(server.env);
|
|
243
|
+
if (server.cwd) server.cwd = expand(server.cwd);
|
|
244
|
+
if (server.url) server.url = expand(server.url);
|
|
245
|
+
if (server.headers) server.headers = expand(server.headers);
|
|
246
|
+
if (server.auth) server.auth = expand(server.auth);
|
|
247
|
+
if (server.oauth) server.oauth = expand(server.oauth);
|
|
235
248
|
servers.push(server);
|
|
236
249
|
}
|
|
237
250
|
}
|
|
@@ -440,7 +453,7 @@ async function readExactMCPConfigFile(filePath: string): Promise<string | null>
|
|
|
440
453
|
export async function loadMCPJsonFile(
|
|
441
454
|
filePath: string,
|
|
442
455
|
level: "user" | "project",
|
|
443
|
-
options?: { quiet?: boolean; useCache?: boolean; readOptions?: ReadFileOptions },
|
|
456
|
+
options?: { quiet?: boolean; useCache?: boolean; readOptions?: ReadFileOptions; skipSensitiveEnvNames?: boolean },
|
|
444
457
|
): Promise<MCPJsonLoadResult> {
|
|
445
458
|
const warnings: string[] = [];
|
|
446
459
|
const items: MCPServer[] = [];
|
|
@@ -497,7 +510,7 @@ export async function loadMCPJsonFile(
|
|
|
497
510
|
validConfig = config as MCPConfigFile;
|
|
498
511
|
}
|
|
499
512
|
const source = createSourceMeta(PROVIDER_ID, sourcePath, level);
|
|
500
|
-
items.push(...transformMCPConfig(validConfig, source, options?.quiet));
|
|
513
|
+
items.push(...transformMCPConfig(validConfig, source, options?.quiet, options?.skipSensitiveEnvNames === true));
|
|
501
514
|
|
|
502
515
|
return {
|
|
503
516
|
items,
|
|
@@ -515,6 +528,7 @@ async function load(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
|
515
528
|
filenames.map(filename =>
|
|
516
529
|
loadMCPJsonFile(path.join(ctx.cwd, filename), "project", {
|
|
517
530
|
readOptions: getReadOptions(ctx, "project"),
|
|
531
|
+
skipSensitiveEnvNames: true,
|
|
518
532
|
}),
|
|
519
533
|
),
|
|
520
534
|
);
|
package/src/discovery/ssh.ts
CHANGED
|
@@ -119,7 +119,7 @@ async function loadSshJsonFile(
|
|
|
119
119
|
warnings.push(`Failed to parse JSON in ${canonicalPath}`);
|
|
120
120
|
return { items, warnings };
|
|
121
121
|
}
|
|
122
|
-
const config = expandEnvVarsDeep(parsed);
|
|
122
|
+
const config = expandEnvVarsDeep(parsed, undefined, level === "project");
|
|
123
123
|
if (!config.hosts || typeof config.hosts !== "object") {
|
|
124
124
|
warnings.push(`Missing hosts in ${canonicalPath}`);
|
|
125
125
|
return { items, warnings };
|