@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
|
@@ -4,6 +4,30 @@ import { type FunctionHookRegistration, validateFunctionHookTarget } from "./fun
|
|
|
4
4
|
|
|
5
5
|
export type TaggedFunctionHookHandler = (...args: unknown[]) => Promise<unknown>;
|
|
6
6
|
|
|
7
|
+
// Host SDK lifecycle observers publish the accepted run's bookkeeping. They do
|
|
8
|
+
// not execute user work and must not consume provider-attempt replay authority.
|
|
9
|
+
const sdkLifecycleObservers = new WeakSet<(...args: never[]) => unknown>();
|
|
10
|
+
const providerRequestHandlers = new WeakMap<(...args: never[]) => unknown, string>();
|
|
11
|
+
|
|
12
|
+
/** Host-owned provider adapters must not consume replay authority for other providers. */
|
|
13
|
+
export function tagProviderRequestHandler<T extends (...args: never[]) => unknown>(provider: string, handler: T): T {
|
|
14
|
+
providerRequestHandlers.set(handler, provider);
|
|
15
|
+
return handler;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getProviderRequestHandlerProvider(handler: (...args: never[]) => unknown): string | undefined {
|
|
19
|
+
return providerRequestHandlers.get(handler);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function tagSdkLifecycleObserver<T extends (...args: never[]) => unknown>(handler: T): T {
|
|
23
|
+
sdkLifecycleObservers.add(handler);
|
|
24
|
+
return handler;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isSdkLifecycleObserver(handler: (...args: never[]) => unknown): boolean {
|
|
28
|
+
return sdkLifecycleObservers.has(handler);
|
|
29
|
+
}
|
|
30
|
+
|
|
7
31
|
const registrations = new WeakMap<TaggedFunctionHookHandler, FunctionHookRegistration>();
|
|
8
32
|
const registrationOrders = new WeakMap<(...args: never[]) => unknown, number>();
|
|
9
33
|
|
|
@@ -21,6 +45,9 @@ export function wrapExtensionHandlerRegistration<T extends (...args: never[]) =>
|
|
|
21
45
|
): T {
|
|
22
46
|
const registered = ((...args: never[]) => handler(...args)) as T;
|
|
23
47
|
registrationOrders.set(registered, registrationOrder);
|
|
48
|
+
if (sdkLifecycleObservers.has(handler)) sdkLifecycleObservers.add(registered);
|
|
49
|
+
const provider = providerRequestHandlers.get(handler);
|
|
50
|
+
if (provider !== undefined) providerRequestHandlers.set(registered, provider);
|
|
24
51
|
return registered;
|
|
25
52
|
}
|
|
26
53
|
|
|
@@ -51,6 +51,8 @@ import {
|
|
|
51
51
|
import {
|
|
52
52
|
getExtensionHandlerRegistrationOrder,
|
|
53
53
|
getFunctionHookRegistration,
|
|
54
|
+
getProviderRequestHandlerProvider,
|
|
55
|
+
isSdkLifecycleObserver,
|
|
54
56
|
readConstrainedFunctionHookFile,
|
|
55
57
|
} from "./function-hooks-internal";
|
|
56
58
|
import type {
|
|
@@ -325,6 +327,7 @@ export class ExtensionRunner {
|
|
|
325
327
|
#getArtifactRangeFn: ExtensionContextActions["getArtifactRange"] = undefined;
|
|
326
328
|
|
|
327
329
|
#getJobsFn: ExtensionContextActions["getJobs"] = undefined;
|
|
330
|
+
#getProjectProgressFn: ExtensionContextActions["getProjectProgress"] = undefined;
|
|
328
331
|
#onJobFoldFn: ExtensionContextActions["onJobFold"] = undefined;
|
|
329
332
|
#onSessionEventFn: ExtensionContextActions["onSessionEvent"] = undefined;
|
|
330
333
|
#sdkControlFn: ExtensionContextActions["sdkControl"] = undefined;
|
|
@@ -482,6 +485,7 @@ export class ExtensionRunner {
|
|
|
482
485
|
this.#getArtifactRangeFn = contextActions.getArtifactRange;
|
|
483
486
|
|
|
484
487
|
this.#getJobsFn = contextActions.getJobs;
|
|
488
|
+
this.#getProjectProgressFn = contextActions.getProjectProgress;
|
|
485
489
|
this.#onJobFoldFn = contextActions.onJobFold;
|
|
486
490
|
this.#onSessionEventFn = contextActions.onSessionEvent;
|
|
487
491
|
this.#sdkControlFn = contextActions.sdkControl;
|
|
@@ -848,6 +852,7 @@ export class ExtensionRunner {
|
|
|
848
852
|
getArtifactRange: (id, offset, length) => this.#getArtifactRangeFn?.(id, offset, length),
|
|
849
853
|
|
|
850
854
|
getJobs: () => this.#getJobsFn?.(),
|
|
855
|
+
...(this.#getProjectProgressFn ? { getProjectProgress: this.#getProjectProgressFn } : {}),
|
|
851
856
|
onJobFold: listener => this.#onJobFoldFn?.(listener) ?? (() => {}),
|
|
852
857
|
onSessionEvent: listener => this.#onSessionEventFn?.(listener) ?? (() => {}),
|
|
853
858
|
sdkControl: (operation, input) => this.#sdkControlFn?.(operation, input),
|
|
@@ -867,6 +872,7 @@ export class ExtensionRunner {
|
|
|
867
872
|
...(this.#getExtensionsFn ? ["getExtensions"] : []),
|
|
868
873
|
...(this.#getArtifactRangeFn ? ["getArtifactRange"] : []),
|
|
869
874
|
...(this.#getJobsFn ? ["getJobs"] : []),
|
|
875
|
+
...(this.#getProjectProgressFn ? ["getProjectProgress"] : []),
|
|
870
876
|
...(this.#onJobFoldFn ? ["onJobFold"] : []),
|
|
871
877
|
...(this.#sdkControlFn ? ["sdkControl"] : []),
|
|
872
878
|
...(this.#invokeSkillFn ? ["invokeSkill"] : []),
|
|
@@ -914,6 +920,13 @@ export class ExtensionRunner {
|
|
|
914
920
|
const wildcard = this.#handlersByEvent.get("*") ?? [];
|
|
915
921
|
const matches: IndexedHandler[] = [];
|
|
916
922
|
for (const indexed of [...exact, ...wildcard]) {
|
|
923
|
+
const provider = getProviderRequestHandlerProvider(indexed.handler);
|
|
924
|
+
if (
|
|
925
|
+
event.type === "before_provider_request" &&
|
|
926
|
+
provider !== undefined &&
|
|
927
|
+
this.createContext().model?.provider !== provider
|
|
928
|
+
)
|
|
929
|
+
continue;
|
|
917
930
|
const registration = getFunctionHookRegistration(indexed.handler);
|
|
918
931
|
if (!registration) {
|
|
919
932
|
if (includeLegacyToolHandlers && indexed.registrationOrder >= 0 && exact.includes(indexed))
|
|
@@ -1072,7 +1085,8 @@ export class ExtensionRunner {
|
|
|
1072
1085
|
if (handlers.length === 0) return { action: "continue", event };
|
|
1073
1086
|
const includesLegacy = handlers.some(indexed => getFunctionHookRegistration(indexed.handler) === undefined);
|
|
1074
1087
|
if (includesLegacy) this.#requireScopeOrFailClosed(options.scope, event.type);
|
|
1075
|
-
|
|
1088
|
+
if (handlers.some(indexed => event.type !== "agent_start" || !isSdkLifecycleObserver(indexed.handler)))
|
|
1089
|
+
this.#markAttemptExecuted(options.scope);
|
|
1076
1090
|
const functionHookDepth = this.#functionHookDepth.getStore() ?? 0;
|
|
1077
1091
|
if (functionHookDepth >= 16) {
|
|
1078
1092
|
const reason = "Function hook re-entry depth exceeded";
|
|
@@ -49,6 +49,7 @@ import type { ExecOptions, ExecResult } from "../../exec/exec";
|
|
|
49
49
|
import type { CustomEditor } from "../../modes/components/custom-editor";
|
|
50
50
|
import type { WorkflowGateEmitter } from "../../modes/shared/agent-wire/workflow-gate-broker";
|
|
51
51
|
import type { Theme } from "../../modes/theme/theme";
|
|
52
|
+
import type { ProjectProgressSnapshot } from "../../progress/progress-contract";
|
|
52
53
|
import type { AgentSessionEventListener } from "../../session/agent-session";
|
|
53
54
|
import type {
|
|
54
55
|
ClientBridge,
|
|
@@ -538,6 +539,8 @@ export interface ExtensionContext {
|
|
|
538
539
|
| Promise<{ bytes: Uint8Array; totalBytes: number } | undefined>;
|
|
539
540
|
|
|
540
541
|
getJobs(): unknown;
|
|
542
|
+
/** Read-only project progress snapshot (`session.progress`), derived from durable session state. */
|
|
543
|
+
getProjectProgress?(): Promise<ProjectProgressSnapshot>;
|
|
541
544
|
/** Subscribe to foreground-wait folds (`bash_folded`). Returns an unsubscribe function. */
|
|
542
545
|
onJobFold?(listener: (event: JobFoldEvent) => void): () => void;
|
|
543
546
|
/** Observe session events synchronously in producer order. Returns an unsubscribe function. */
|
|
@@ -764,6 +767,8 @@ export interface AgentStartEvent extends SharedAgentStartEvent {
|
|
|
764
767
|
sdkRunToken?: string;
|
|
765
768
|
/** Complete set of SDK queue owners consumed together by this run. */
|
|
766
769
|
sdkRunTokens?: string[];
|
|
770
|
+
/** Internal public lifecycle boundary; accepted retries with a suppressed predecessor end retain it. */
|
|
771
|
+
lifecycleScope?: AttemptScope;
|
|
767
772
|
}
|
|
768
773
|
|
|
769
774
|
/** Fired when an agent run fails before emitting agent_end. The error is the
|
|
@@ -1723,6 +1728,8 @@ export interface ExtensionContextActions {
|
|
|
1723
1728
|
| undefined
|
|
1724
1729
|
| Promise<{ bytes: Uint8Array; totalBytes: number } | undefined>;
|
|
1725
1730
|
getJobs?: () => unknown;
|
|
1731
|
+
/** Read-only project progress snapshot backing the `session.progress` SDK query. */
|
|
1732
|
+
getProjectProgress?: () => Promise<ProjectProgressSnapshot>;
|
|
1726
1733
|
/** Subscribe to foreground-wait folds so the SDK host can publish `bash_folded`. Returns an unsubscribe. */
|
|
1727
1734
|
onJobFold?: (listener: (event: JobFoldEvent) => void) => () => void;
|
|
1728
1735
|
/** Ordered, synchronous AgentSession subscription, independent of async extension hooks. */
|
|
@@ -6,7 +6,7 @@ import { GjcPluginLoadError, type GjcPluginMcpManifestEntry } from "./types";
|
|
|
6
6
|
/**
|
|
7
7
|
* Shared MCP security policy applied at BOTH install validation and runtime
|
|
8
8
|
* connect for third-party plugin-bundle MCP servers. Defaults are deny-first:
|
|
9
|
-
* HTTPS only, no private/loopback/link-local/metadata endpoints, stdio confined
|
|
9
|
+
* HTTPS only, no private/loopback/link-local/CGNAT/metadata endpoints, stdio confined
|
|
10
10
|
* to the plugin root.
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -34,6 +34,7 @@ export function isDeniedIpv4(host: string): boolean {
|
|
|
34
34
|
if (a === 172 && b >= 16 && b <= 31) return true; // private 172.16.0.0/12
|
|
35
35
|
if (a === 192 && b === 168) return true; // private 192.168.0.0/16
|
|
36
36
|
if (a === 169 && b === 254) return true; // link-local 169.254.0.0/16 (incl 169.254.169.254 metadata)
|
|
37
|
+
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT 100.64.0.0/10
|
|
37
38
|
if (a === 0) return true; // 0.0.0.0/8 unspecified/this-network
|
|
38
39
|
if (a >= 224) return true; // multicast/reserved 224.0.0.0/4 and 240.0.0.0/4
|
|
39
40
|
return false;
|
|
@@ -149,22 +149,82 @@ const initialTemporaryRoots = initialProcessEnvironment.then(async environment =
|
|
|
149
149
|
}
|
|
150
150
|
return [...roots];
|
|
151
151
|
});
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
|
|
153
|
+
/** Startup identity of a file: the object `hashStableFile` must still be reading. */
|
|
154
|
+
export interface StableFileIdentity {
|
|
155
|
+
dev: number;
|
|
156
|
+
ino: number;
|
|
157
|
+
size: number;
|
|
158
|
+
mtimeMs: number;
|
|
159
|
+
ctimeMs: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Startup identity of every canonical `node` on PATH, keyed by realpath. Only
|
|
164
|
+
* `stat` runs at startup; the digest is computed on first use from a descriptor
|
|
165
|
+
* whose identity must still match this snapshot, so a later replacement cannot
|
|
166
|
+
* become launch authority.
|
|
167
|
+
*/
|
|
168
|
+
const initialNodeIdentities = initialProcessEnvironment.then(async environment => {
|
|
169
|
+
const identities = new Map<string, StableFileIdentity>();
|
|
154
170
|
const temporaryRoots = await initialTemporaryRoots;
|
|
155
171
|
for (const pathEntry of (environment.get("PATH") ?? "").split(path.delimiter).filter(path.isAbsolute)) {
|
|
156
172
|
const lexical = path.join(pathEntry, process.platform === "win32" ? "node.exe" : "node");
|
|
157
173
|
try {
|
|
158
174
|
const real = await fs.realpath(lexical);
|
|
175
|
+
// A temporary root is writable by other local users and by checkouts the user
|
|
176
|
+
// never vetted, so a `node` resolved there never becomes launch authority.
|
|
159
177
|
if (temporaryRoots.some(root => isWithin(root, real))) continue;
|
|
160
|
-
|
|
178
|
+
if (identities.has(real)) continue;
|
|
179
|
+
const stat = await fs.stat(real);
|
|
180
|
+
identities.set(real, {
|
|
181
|
+
dev: stat.dev,
|
|
182
|
+
ino: stat.ino,
|
|
183
|
+
size: stat.size,
|
|
184
|
+
mtimeMs: stat.mtimeMs,
|
|
185
|
+
ctimeMs: stat.ctimeMs,
|
|
186
|
+
});
|
|
161
187
|
} catch {
|
|
162
|
-
// Missing
|
|
188
|
+
// Missing startup candidates do not become authority.
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
|
-
return
|
|
191
|
+
return identities;
|
|
166
192
|
});
|
|
167
193
|
|
|
194
|
+
/**
|
|
195
|
+
* First-use digests of startup Node candidates, keyed by realpath. Successful and
|
|
196
|
+
* in-flight digests stay memoized; a failed attempt is evicted so a transient error
|
|
197
|
+
* (e.g. EMFILE) can be retried against the same startup identity.
|
|
198
|
+
*/
|
|
199
|
+
const initialNodeHashes = new Map<string, Promise<string | undefined>>();
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Digest of a `node` that was on PATH at startup, or `undefined` when the path
|
|
203
|
+
* was not a startup candidate or no longer is the startup file object.
|
|
204
|
+
*/
|
|
205
|
+
export function getInitialNodeHash(realPath: string): Promise<string | undefined> {
|
|
206
|
+
const cached = initialNodeHashes.get(realPath);
|
|
207
|
+
if (cached) return cached;
|
|
208
|
+
|
|
209
|
+
const attempt = initialNodeIdentities.then(async identities => {
|
|
210
|
+
const identity = identities.get(realPath);
|
|
211
|
+
if (!identity) return undefined;
|
|
212
|
+
try {
|
|
213
|
+
return await hashStableFile(realPath, "Initial Node executable", MCP_LAUNCHER_MAX_BYTES, identity);
|
|
214
|
+
} catch {
|
|
215
|
+
// Do not memoize failed attempts to allow retries on transient errors
|
|
216
|
+
// (e.g., EMFILE on file open/read). Remove the pending entry so the next
|
|
217
|
+
// call can retry the operation with the same startup identity.
|
|
218
|
+
initialNodeHashes.delete(realPath);
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
// Memoize the in-flight attempt so only one concurrent operation runs, but
|
|
223
|
+
// the entry may be deleted if the operation fails (to allow retry).
|
|
224
|
+
initialNodeHashes.set(realPath, attempt);
|
|
225
|
+
return attempt;
|
|
226
|
+
}
|
|
227
|
+
|
|
168
228
|
async function snapshotExistingFile(filePath: string): Promise<FileSnapshot | null> {
|
|
169
229
|
try {
|
|
170
230
|
const stat = await fs.stat(filePath);
|
|
@@ -416,11 +476,28 @@ const STABLE_HASH_CHUNK_BYTES = 1024 * 1024;
|
|
|
416
476
|
* on PATH; buffering each interpreter (~120 MiB) held its full bytes in the
|
|
417
477
|
* session process until exit.
|
|
418
478
|
*/
|
|
419
|
-
export async function hashStableFile(
|
|
479
|
+
export async function hashStableFile(
|
|
480
|
+
filePath: string,
|
|
481
|
+
label: string,
|
|
482
|
+
maxBytes: number,
|
|
483
|
+
expected?: StableFileIdentity,
|
|
484
|
+
): Promise<string> {
|
|
420
485
|
const handle = await fs.open(filePath, fs.constants.O_RDONLY);
|
|
421
486
|
try {
|
|
422
487
|
const before = await handle.stat();
|
|
423
488
|
if (!before.isFile()) throw new Error(`${label} is not a regular file`);
|
|
489
|
+
// Checked on the opened descriptor, so the digest belongs to the expected
|
|
490
|
+
// file object even if the path is swapped around the open.
|
|
491
|
+
if (
|
|
492
|
+
expected &&
|
|
493
|
+
(before.dev !== expected.dev ||
|
|
494
|
+
before.ino !== expected.ino ||
|
|
495
|
+
before.size !== expected.size ||
|
|
496
|
+
before.mtimeMs !== expected.mtimeMs ||
|
|
497
|
+
before.ctimeMs !== expected.ctimeMs)
|
|
498
|
+
) {
|
|
499
|
+
throw new Error(`${label} is not the expected file`);
|
|
500
|
+
}
|
|
424
501
|
if (before.size > maxBytes) throw new Error(`${label} exceeds its byte limit`);
|
|
425
502
|
const hash = createHash("sha256");
|
|
426
503
|
const chunk = Buffer.allocUnsafe(STABLE_HASH_CHUNK_BYTES);
|
|
@@ -515,7 +592,7 @@ async function isInitialManagedNodeLauncherPath(
|
|
|
515
592
|
const real = await fs.realpath(executablePath);
|
|
516
593
|
if (untrustedRoots.some(root => isWithin(root, real))) return false;
|
|
517
594
|
if ((await initialTemporaryRoots).some(root => isWithin(root, real))) return false;
|
|
518
|
-
const expected =
|
|
595
|
+
const expected = await getInitialNodeHash(real);
|
|
519
596
|
if (!expected) return false;
|
|
520
597
|
return (await hashStableFile(real, "Initial Node executable", MCP_LAUNCHER_MAX_BYTES)) === expected;
|
|
521
598
|
}
|
|
@@ -671,7 +748,7 @@ async function prepareVerifiedStdioLaunch(input: {
|
|
|
671
748
|
true,
|
|
672
749
|
);
|
|
673
750
|
const launcherReal = await fs.realpath(input.launcherPath);
|
|
674
|
-
const expectedLauncherHash =
|
|
751
|
+
const expectedLauncherHash = await getInitialNodeHash(launcherReal);
|
|
675
752
|
if (!expectedLauncherHash || sha256(launcherBytes) !== expectedLauncherHash) {
|
|
676
753
|
throw new Error("Plugin MCP Node interpreter drifted from startup authority");
|
|
677
754
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@gajae-code/utils";
|
|
13
13
|
import { withFileLock } from "../../config/file-lock";
|
|
14
14
|
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
15
|
+
import { resolvePluginLinkPath } from "./plugin-link-path";
|
|
15
16
|
import type {
|
|
16
17
|
DoctorCheck,
|
|
17
18
|
DoctorOptions,
|
|
@@ -535,11 +536,11 @@ export class PluginManager {
|
|
|
535
536
|
|
|
536
537
|
await this.#ensurePluginsDir();
|
|
537
538
|
|
|
538
|
-
const linkPath =
|
|
539
|
+
const linkPath = resolvePluginLinkPath(getPluginsNodeModules(), pkg.name);
|
|
539
540
|
|
|
540
541
|
// Handle scoped packages
|
|
541
542
|
if (pkg.name.startsWith("@")) {
|
|
542
|
-
const scopeDir = path.
|
|
543
|
+
const scopeDir = path.dirname(linkPath);
|
|
543
544
|
await fs.promises.mkdir(scopeDir, { recursive: true });
|
|
544
545
|
}
|
|
545
546
|
|
|
@@ -87,6 +87,21 @@ export async function cachePlugin(
|
|
|
87
87
|
return targetPath;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** Write a regular file. A symlink at the destination is removed, not followed. */
|
|
91
|
+
export async function writeCachedRegularFile(targetPath: string, content: string): Promise<void> {
|
|
92
|
+
try {
|
|
93
|
+
const stat = await fs.lstat(targetPath);
|
|
94
|
+
if (stat.isSymbolicLink()) {
|
|
95
|
+
await fs.unlink(targetPath);
|
|
96
|
+
} else if (!stat.isFile()) {
|
|
97
|
+
throw new Error("unsafe cache artifact");
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (!isEnoent(error)) throw error;
|
|
101
|
+
}
|
|
102
|
+
await Bun.write(targetPath, content);
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
export async function stageCachedPlugin(sourcePath: string, stagingPath: string): Promise<string> {
|
|
91
106
|
await fs.rm(stagingPath, { recursive: true, force: true });
|
|
92
107
|
await fs.cp(sourcePath, stagingPath, { recursive: true, errorOnExist: true, verbatimSymlinks: true });
|
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
} from "@gajae-code/natives";
|
|
20
20
|
import { isEnoent, logger, pathIsWithin } from "@gajae-code/utils";
|
|
21
21
|
import { withFileLock } from "../../../config/file-lock";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
cachePlugin,
|
|
24
|
+
getCachedPluginPath,
|
|
25
|
+
inspectCachedPlugin,
|
|
26
|
+
stageCachedPlugin,
|
|
27
|
+
writeCachedRegularFile,
|
|
28
|
+
} from "./cache";
|
|
23
29
|
import { classifySource, fetchMarketplace, parseMarketplaceCatalog, promoteCloneToCache } from "./fetcher";
|
|
24
30
|
import {
|
|
25
31
|
addInstalledPlugin,
|
|
@@ -1430,12 +1436,14 @@ export class MarketplaceManager {
|
|
|
1430
1436
|
if (!pathIsWithin(cachePath, sourcePath)) {
|
|
1431
1437
|
throw new Error(`Plugin "${entry.name}" lspServers path escapes the plugin directory`);
|
|
1432
1438
|
}
|
|
1439
|
+
const sourceStat = await fs.lstat(sourcePath);
|
|
1440
|
+
if (sourceStat.isSymbolicLink()) throw new Error(`Plugin "${entry.name}" lspServers path is a symlink`);
|
|
1433
1441
|
const content = await Bun.file(sourcePath).text();
|
|
1434
|
-
await
|
|
1442
|
+
await writeCachedRegularFile(targetPath, content);
|
|
1435
1443
|
return;
|
|
1436
1444
|
}
|
|
1437
1445
|
|
|
1438
|
-
await
|
|
1446
|
+
await writeCachedRegularFile(targetPath, `${JSON.stringify({ servers: lspServers }, null, 2)}\n`);
|
|
1439
1447
|
}
|
|
1440
1448
|
|
|
1441
1449
|
/**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
/** Resolve a package.json name under the plugins node_modules root, or throw. */
|
|
4
|
+
export function resolvePluginLinkPath(root: string, name: string): string {
|
|
5
|
+
const base = path.resolve(root);
|
|
6
|
+
const destination = path.resolve(base, name);
|
|
7
|
+
const relative = path.relative(base, destination);
|
|
8
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
9
|
+
throw new Error(`package.json name is not a safe plugin link path: ${name}`);
|
|
10
|
+
}
|
|
11
|
+
return destination;
|
|
12
|
+
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { findRepoRoot } from "../capability/fs";
|
|
10
10
|
import type { Skill as CapabilitySkill } from "../capability/skill";
|
|
11
11
|
import type { SkillsSettings } from "../config/settings-schema";
|
|
12
|
-
import { resolveSkillScopeTrust } from "../config/skill-settings-defaults";
|
|
12
|
+
import { resolveGlobalUserSkillLinkTrust, resolveSkillScopeTrust } from "../config/skill-settings-defaults";
|
|
13
13
|
import { BUNDLED_GJC_SKILL_CATALOG } from "../defaults/gjc-skills.generated";
|
|
14
14
|
import { scanClaudeProjectSkills, scanClaudeUserSkills } from "../discovery/claude";
|
|
15
15
|
import { loadMarketplaceSkills } from "../discovery/claude-plugins";
|
|
@@ -79,6 +79,8 @@ export interface DiscoverRuntimeSkillsOptions {
|
|
|
79
79
|
offset?: number;
|
|
80
80
|
source?: RuntimeSkillDiscoveryScope | "all";
|
|
81
81
|
policy?: SkillsSettings;
|
|
82
|
+
/** User-owned permission to read external user skill symlinks; defaults to the direct policy value. */
|
|
83
|
+
allowExternalUserSkillSymlinks?: boolean;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
function getRuntimeHome(): string {
|
|
@@ -296,6 +298,7 @@ async function scanProjectOrUserDir(
|
|
|
296
298
|
source: RuntimeSkillDiscoveryScope,
|
|
297
299
|
providerPriority: number,
|
|
298
300
|
authorityRoot?: string,
|
|
301
|
+
allowExternalUserSkillSymlinks?: boolean,
|
|
299
302
|
): Promise<ScanJobResult> {
|
|
300
303
|
const result = await scanSkillsFromDir(ctx, {
|
|
301
304
|
dir,
|
|
@@ -304,6 +307,7 @@ async function scanProjectOrUserDir(
|
|
|
304
307
|
providerId: "runtime",
|
|
305
308
|
level,
|
|
306
309
|
requireDescription: true,
|
|
310
|
+
allowExternalUserSkillSymlinks,
|
|
307
311
|
});
|
|
308
312
|
return {
|
|
309
313
|
items: result.items.map(skill => ({ skill, source, providerPriority })),
|
|
@@ -449,6 +453,8 @@ export async function discoverRuntimeSkills(
|
|
|
449
453
|
const home = options.home ?? getRuntimeHome();
|
|
450
454
|
const source = options.source ?? "all";
|
|
451
455
|
const policy = options.policy;
|
|
456
|
+
const allowExternalUserSkillSymlinks =
|
|
457
|
+
options.allowExternalUserSkillSymlinks ?? resolveGlobalUserSkillLinkTrust(policy ?? {});
|
|
452
458
|
const diagnostics: string[] = [];
|
|
453
459
|
const agentDir = resolveRuntimeAgentDir(home, options.agentDir, hasExplicitHome);
|
|
454
460
|
const profileAuthority =
|
|
@@ -478,6 +484,7 @@ export async function discoverRuntimeSkills(
|
|
|
478
484
|
"user",
|
|
479
485
|
100,
|
|
480
486
|
path.resolve(dir) === path.resolve(agentDir, "skills") ? agentDir : undefined,
|
|
487
|
+
allowExternalUserSkillSymlinks,
|
|
481
488
|
),
|
|
482
489
|
);
|
|
483
490
|
}
|
|
@@ -485,7 +492,16 @@ export async function discoverRuntimeSkills(
|
|
|
485
492
|
if ((source === "all" || source === "user") && policy?.enabled === true) {
|
|
486
493
|
for (const dir of getCustomSkillDirs(policy, home)) {
|
|
487
494
|
scanJobs.push(
|
|
488
|
-
scanProjectOrUserDir(
|
|
495
|
+
scanProjectOrUserDir(
|
|
496
|
+
{ cwd: options.cwd, home, repoRoot: home },
|
|
497
|
+
dir,
|
|
498
|
+
"user",
|
|
499
|
+
`custom ${dir}`,
|
|
500
|
+
"user",
|
|
501
|
+
0,
|
|
502
|
+
undefined,
|
|
503
|
+
allowExternalUserSkillSymlinks,
|
|
504
|
+
),
|
|
489
505
|
);
|
|
490
506
|
}
|
|
491
507
|
}
|
|
@@ -630,6 +646,7 @@ export async function findRuntimeSkillByName(
|
|
|
630
646
|
home?: string,
|
|
631
647
|
agentDir?: string,
|
|
632
648
|
profileAuthority?: "default" | "custom",
|
|
649
|
+
allowExternalUserSkillSymlinks?: boolean,
|
|
633
650
|
): Promise<Skill | undefined> {
|
|
634
651
|
const normalized = name.trim();
|
|
635
652
|
if (!normalized) return undefined;
|
|
@@ -650,6 +667,7 @@ export async function findRuntimeSkillByName(
|
|
|
650
667
|
home: resolvedHome,
|
|
651
668
|
agentDir: resolvedAgentDir,
|
|
652
669
|
profileAuthority: resolvedProfileAuthority,
|
|
670
|
+
allowExternalUserSkillSymlinks: allowExternalUserSkillSymlinks ?? resolveGlobalUserSkillLinkTrust(policy ?? {}),
|
|
653
671
|
});
|
|
654
672
|
return loaded.skills.find(skill => skill.name === normalized);
|
|
655
673
|
}
|
|
@@ -56,6 +56,7 @@ export type SkillDisabledReason = "protected" | "scope-trust" | "ignored" | "inc
|
|
|
56
56
|
export interface ManagedSkillRecord {
|
|
57
57
|
name: string;
|
|
58
58
|
description: string;
|
|
59
|
+
importedFrom?: string;
|
|
59
60
|
path: string;
|
|
60
61
|
scope: SkillScope;
|
|
61
62
|
/** Canonical source label, e.g. "project .gjc/skills" or "user ~/.gjc/agent/skills". */
|
|
@@ -299,8 +300,10 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
299
300
|
cwd: string;
|
|
300
301
|
home?: string;
|
|
301
302
|
agentDir?: string;
|
|
302
|
-
/** Resolver-owned profile classification; unlike path comparison, this survives HOME refreshes. */
|
|
303
|
+
/** Resolver-owned profile classification; unlike path comparison, this survives HOME/config refreshes. */
|
|
303
304
|
profileAuthority?: "default" | "custom";
|
|
305
|
+
/** User-owned permission to follow symlinks outside user skill scan roots. */
|
|
306
|
+
allowExternalUserSkillSymlinks?: boolean;
|
|
304
307
|
policy?: SkillManagementPolicy;
|
|
305
308
|
}): Promise<ManagedSkillRecord[]> {
|
|
306
309
|
const homeWasInjected = options.home !== undefined;
|
|
@@ -321,6 +324,7 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
321
324
|
const policy = options.policy;
|
|
322
325
|
const projectTrusted = resolveSkillScopeTrust(policy ?? {}, "project");
|
|
323
326
|
const userTrusted = resolveSkillScopeTrust(policy ?? {}, "user");
|
|
327
|
+
const allowExternalUserSkillSymlinks = options.allowExternalUserSkillSymlinks ?? userTrusted;
|
|
324
328
|
|
|
325
329
|
const scanJobs: Array<Promise<{ dir: string; scope: SkillScope; items: CapabilitySkill[] }>> = [];
|
|
326
330
|
const projectDirs = await getProjectSkillDirs(options.cwd, home);
|
|
@@ -351,6 +355,7 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
351
355
|
providerId: "runtime",
|
|
352
356
|
level: "user",
|
|
353
357
|
requireDescription: true,
|
|
358
|
+
allowExternalUserSkillSymlinks,
|
|
354
359
|
},
|
|
355
360
|
).then(result => ({ dir, scope: "user" as const, items: result.items })),
|
|
356
361
|
);
|
|
@@ -388,10 +393,12 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
388
393
|
|
|
389
394
|
if (!seenNames.has(skill.name)) {
|
|
390
395
|
seenNames.add(skill.name);
|
|
396
|
+
const importedFrom = skill.frontmatter?.xGjcImportedFrom ?? skill.frontmatter?.["x-gjc-imported-from"];
|
|
391
397
|
records.push({
|
|
392
398
|
name: skill.name,
|
|
393
399
|
description: typeof skill.frontmatter?.description === "string" ? skill.frontmatter.description : "",
|
|
394
|
-
|
|
400
|
+
...(typeof importedFrom === "string" ? { importedFrom } : {}),
|
|
401
|
+
path: skill.discoveryPath ?? skill.path,
|
|
395
402
|
scope,
|
|
396
403
|
source,
|
|
397
404
|
hidden: skill.frontmatter?.hide === true,
|
|
@@ -125,6 +125,8 @@ export interface LoadSkillsOptions extends SkillsSettings {
|
|
|
125
125
|
agentDir?: string;
|
|
126
126
|
/** Resolver-owned classification for the selected agent directory. */
|
|
127
127
|
profileAuthority?: "default" | "custom";
|
|
128
|
+
/** User-owned permission to follow symlinks outside user skill scan roots. */
|
|
129
|
+
allowExternalUserSkillSymlinks?: boolean;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
/**
|
|
@@ -167,6 +169,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
167
169
|
|
|
168
170
|
const projectTrusted = resolveSkillScopeTrust(options, "project");
|
|
169
171
|
const userTrusted = resolveSkillScopeTrust(options, "user");
|
|
172
|
+
const allowExternalUserSkillSymlinks = options.allowExternalUserSkillSymlinks ?? userTrusted;
|
|
170
173
|
|
|
171
174
|
// Skill scope trust decides which canonical locations are loaded: project
|
|
172
175
|
// scope covers `.gjc/skills` (walk-up), user scope covers `~/.gjc/agent/skills`
|
|
@@ -197,6 +200,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
197
200
|
userAgentDir: agentDir,
|
|
198
201
|
profileAuthority,
|
|
199
202
|
repoRoot: await findRepoRoot(cwd),
|
|
203
|
+
allowExternalUserSkillSymlinks,
|
|
200
204
|
};
|
|
201
205
|
const providerResults = await Promise.all(
|
|
202
206
|
providers.map(async provider => {
|
|
@@ -311,12 +315,13 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
311
315
|
customDirectories.map(async dir => {
|
|
312
316
|
const expandedDir = expandTilde(dir, home);
|
|
313
317
|
const scanResult = await scanSkillsFromDir(
|
|
314
|
-
{ cwd, home, repoRoot: null },
|
|
318
|
+
{ cwd, home, repoRoot: null, allowExternalUserSkillSymlinks },
|
|
315
319
|
{
|
|
316
320
|
dir: expandedDir,
|
|
317
321
|
providerId: "custom",
|
|
318
322
|
level: "user",
|
|
319
323
|
requireDescription: true,
|
|
324
|
+
allowExternalUserSkillSymlinks,
|
|
320
325
|
},
|
|
321
326
|
);
|
|
322
327
|
return { expandedDir, scanResult };
|