@gajae-code/coding-agent 0.18.7 → 0.18.8
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 +279 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- 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/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/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- 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 +3 -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/ralplan-runtime.d.ts +0 -5
- 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 +11 -1
- 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/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/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -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 +18 -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 +62 -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/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -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/job-manager.ts +228 -122
- package/src/capability/index.ts +17 -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/public-command-errors.ts +21 -1
- 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/sdk.ts +28 -8
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- 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/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 +3 -0
- 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/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 +10 -0
- package/src/discovery/helpers.ts +159 -20
- package/src/discovery/mcp-json.ts +6 -0
- 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 +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -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 +6 -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 +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- 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 +83 -8
- 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 +18 -17
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- 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/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 +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- 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/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +75 -6
- package/src/sdk/broker/ensure.ts +2 -0
- 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 +2647 -294
- package/src/sdk/host/session-runtime.ts +426 -109
- 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 +75 -12
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +67 -38
- package/src/session/agent-session.ts +623 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +376 -61
- 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 +939 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +417 -14
- 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 +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- 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/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- 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/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -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/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
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);
|
|
@@ -788,18 +880,55 @@ export async function scanSkillsFromDir(
|
|
|
788
880
|
};
|
|
789
881
|
const loadSkill = async (candidatePath: 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
|
+
const allowOutsideRoot = options.allowExternalUserSkillSymlinks && level === "user";
|
|
893
|
+
if (!isWithinRoot(skillPath) && !allowOutsideRoot) {
|
|
894
|
+
const remedy =
|
|
895
|
+
level === "user"
|
|
896
|
+
? `add ${path.dirname(path.dirname(skillPath))} to skills.customDirectories to load it, or enable skills.trustUserSkills`
|
|
897
|
+
: "keep project skill targets inside the skills scan root";
|
|
898
|
+
warnings.push(
|
|
899
|
+
`Refusing skill path outside scan root: ${candidatePath} (resolves to ${skillPath}; ${remedy})`,
|
|
900
|
+
);
|
|
794
901
|
return;
|
|
795
902
|
}
|
|
796
|
-
const stat = await fs.promises.stat(skillPath);
|
|
797
|
-
if (!stat.isFile() || stat.nlink !==
|
|
903
|
+
const stat = await fs.promises.stat(skillPath, { bigint: true });
|
|
904
|
+
if (!stat.isFile() || stat.nlink !== 1n) {
|
|
798
905
|
warnings.push(`Skill path is not a regular file: ${candidatePath}`);
|
|
799
906
|
return;
|
|
800
907
|
}
|
|
908
|
+
const pinExternalTarget = !isWithinRoot(skillPath) && allowOutsideRoot;
|
|
909
|
+
const fileLink = linkIdentities.find(identity => identity.path === candidatePath);
|
|
910
|
+
const parentLink = linkIdentities.find(identity => identity.path === path.dirname(candidatePath));
|
|
911
|
+
let capturedLinkTargetPath: string | undefined;
|
|
912
|
+
if (fileLink?.target.kind === "file") {
|
|
913
|
+
capturedLinkTargetPath = fileLink.target.realPath;
|
|
914
|
+
} else if (parentLink?.target.kind === "directory") {
|
|
915
|
+
capturedLinkTargetPath = path.join(parentLink.target.realPath, path.basename(candidatePath));
|
|
916
|
+
}
|
|
917
|
+
if (
|
|
918
|
+
pinExternalTarget &&
|
|
919
|
+
(!capturedLinkTargetPath ||
|
|
920
|
+
normalizePathForComparison(skillPath) !== normalizePathForComparison(capturedLinkTargetPath))
|
|
921
|
+
) {
|
|
922
|
+
warnings.push(`Refusing skill path not bound to its captured external link target: ${candidatePath}`);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const fileIdentity = pinExternalTarget ? { dev: stat.dev, ino: stat.ino, realPath: skillPath } : undefined;
|
|
926
|
+
const pinnedLinkIdentities = pinExternalTarget ? linkIdentities : [];
|
|
801
927
|
await SkillDiscoveryTestHooks.afterCandidateValidated?.(candidatePath);
|
|
802
|
-
const { frontmatter, size } = await readSkillFrontmatterSafely(skillPath);
|
|
928
|
+
const { frontmatter, size } = await readSkillFrontmatterSafely(skillPath, fileIdentity, pinnedLinkIdentities);
|
|
929
|
+
if (!(await skillPathAndLinksAreCurrent(skillPath, fileIdentity, pinnedLinkIdentities))) {
|
|
930
|
+
throw new Error(`Unsafe skill link for: ${candidatePath}`);
|
|
931
|
+
}
|
|
803
932
|
if (!frontmatter) {
|
|
804
933
|
if (size > SKILL_FRONTMATTER_SCAN_TOTAL_BYTES) {
|
|
805
934
|
warnings.push(
|
|
@@ -823,8 +952,9 @@ export async function scanSkillsFromDir(
|
|
|
823
952
|
items.push({
|
|
824
953
|
name,
|
|
825
954
|
path: skillPath,
|
|
955
|
+
discoveryPath: candidatePath,
|
|
826
956
|
loadContent: async () => {
|
|
827
|
-
const content = await readSkillContentSafely(skillPath);
|
|
957
|
+
const content = await readSkillContentSafely(skillPath, fileIdentity, pinnedLinkIdentities);
|
|
828
958
|
return parseFrontmatter(content, { source: skillPath }).body;
|
|
829
959
|
},
|
|
830
960
|
frontmatter: frontmatter as SkillFrontmatter,
|
|
@@ -923,8 +1053,17 @@ export async function readContainedFile(
|
|
|
923
1053
|
* Expand environment variables in a string.
|
|
924
1054
|
* Supports ${VAR} and ${VAR:-default} syntax.
|
|
925
1055
|
*/
|
|
926
|
-
|
|
1056
|
+
const SENSITIVE_ENV_NAME = /key|secret|token|pass|auth|credential|cookie|dsn|url$/i;
|
|
1057
|
+
|
|
1058
|
+
export function isSensitiveEnvName(name: string): boolean {
|
|
1059
|
+
return SENSITIVE_ENV_NAME.test(name);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function expandEnvVars(value: string, extraEnv?: Record<string, string>, skipSensitiveNames = false): string {
|
|
927
1063
|
return value.replace(/\$\{([^}:]+)(?::-([^}]*))?\}/g, (_, varName: string, defaultValue?: string) => {
|
|
1064
|
+
if (skipSensitiveNames && isSensitiveEnvName(varName)) {
|
|
1065
|
+
return defaultValue !== undefined ? defaultValue : `\${${varName}}`;
|
|
1066
|
+
}
|
|
928
1067
|
const envValue = extraEnv?.[varName] ?? Bun.env[varName];
|
|
929
1068
|
if (envValue !== undefined) return envValue;
|
|
930
1069
|
if (defaultValue !== undefined) return defaultValue;
|
|
@@ -935,17 +1074,17 @@ function expandEnvVars(value: string, extraEnv?: Record<string, string>): string
|
|
|
935
1074
|
/**
|
|
936
1075
|
* Recursively expand environment variables in an object.
|
|
937
1076
|
*/
|
|
938
|
-
export function expandEnvVarsDeep<T>(obj: T, extraEnv?: Record<string, string
|
|
1077
|
+
export function expandEnvVarsDeep<T>(obj: T, extraEnv?: Record<string, string>, skipSensitiveNames = false): T {
|
|
939
1078
|
if (typeof obj === "string") {
|
|
940
|
-
return expandEnvVars(obj, extraEnv) as T;
|
|
1079
|
+
return expandEnvVars(obj, extraEnv, skipSensitiveNames) as T;
|
|
941
1080
|
}
|
|
942
1081
|
if (Array.isArray(obj)) {
|
|
943
|
-
return obj.map(item => expandEnvVarsDeep(item, extraEnv)) as T;
|
|
1082
|
+
return obj.map(item => expandEnvVarsDeep(item, extraEnv, skipSensitiveNames)) as T;
|
|
944
1083
|
}
|
|
945
1084
|
if (obj !== null && typeof obj === "object") {
|
|
946
1085
|
const result: Record<string, unknown> = {};
|
|
947
1086
|
for (const [key, value] of Object.entries(obj)) {
|
|
948
|
-
result[key] = expandEnvVarsDeep(value, extraEnv);
|
|
1087
|
+
result[key] = expandEnvVarsDeep(value, extraEnv, skipSensitiveNames);
|
|
949
1088
|
}
|
|
950
1089
|
return result as T;
|
|
951
1090
|
}
|
|
@@ -217,6 +217,12 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta, quiet = f
|
|
|
217
217
|
cwd: serverConfig.cwd,
|
|
218
218
|
url: serverConfig.url,
|
|
219
219
|
headers: serverConfig.headers,
|
|
220
|
+
publicNetwork:
|
|
221
|
+
serverConfig.type === "http" &&
|
|
222
|
+
"publicNetwork" in serverConfig &&
|
|
223
|
+
(serverConfig as { publicNetwork?: boolean }).publicNetwork === true
|
|
224
|
+
? true
|
|
225
|
+
: undefined,
|
|
220
226
|
auth: serverConfig.auth,
|
|
221
227
|
oauth: serverConfig.oauth,
|
|
222
228
|
transport: serverConfig.type,
|
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 };
|
|
@@ -8,7 +8,9 @@ const GJC_VAR = "$" + "{GJC_PLUGIN_ROOT}";
|
|
|
8
8
|
|
|
9
9
|
export function substitutePluginRoot<T>(value: T, rootPath: string): T {
|
|
10
10
|
if (typeof value === "string") {
|
|
11
|
-
|
|
11
|
+
// Function replacers: a string replacement would expand `$&`, `$$`, `` $` ``
|
|
12
|
+
// and `$'` inside the install path instead of inserting it verbatim.
|
|
13
|
+
return value.replaceAll(CLAUDE_VAR, () => rootPath).replaceAll(GJC_VAR, () => rootPath) as T;
|
|
12
14
|
}
|
|
13
15
|
if (Array.isArray(value)) {
|
|
14
16
|
return value.map(v => substitutePluginRoot(v, rootPath)) as T;
|
|
@@ -27,7 +27,7 @@ import type { ToolSession } from "../tools";
|
|
|
27
27
|
|
|
28
28
|
const MAX_PATHS_PER_SESSION = 30;
|
|
29
29
|
/** Snapshot generations retained per path (newest first). */
|
|
30
|
-
const MAX_GENERATIONS_PER_PATH =
|
|
30
|
+
const MAX_GENERATIONS_PER_PATH = 8;
|
|
31
31
|
|
|
32
32
|
export interface FileReadSnapshot {
|
|
33
33
|
/** 1-indexed line number → exact line content as observed by `read`/`search`. */
|