@hasna/skills 0.8.10 → 0.8.11
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/README.md +52 -1
- package/bin/index.js +1304 -574
- package/bin/mcp.js +329 -82
- package/bin/migrate.js +6 -5
- package/bin/server.js +12312 -73626
- package/bin/worker.js +7635 -60520
- package/dist/index.d.ts +4 -1
- package/dist/index.js +817 -261
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/claude-marketplace-registry.d.ts +17 -0
- package/dist/lib/fleet-credentials.d.ts +18 -0
- package/dist/lib/hook-diagnostics.d.ts +7 -0
- package/dist/lib/recurring-recovery.d.ts +2 -2
- package/dist/lib/recurring-surface.d.ts +1 -1
- package/dist/lib/remote-client.d.ts +10 -3
- package/dist/lib/remote-credit-checkout.d.ts +22 -0
- package/dist/lib/remote-customer-operations.d.ts +2 -3
- package/dist/lib/selection-cache.d.ts +32 -0
- package/dist/lib/selection-resolver.d.ts +3 -1
- package/dist/lib/session-reconciliation.d.ts +100 -0
- package/dist/sdk/index.d.ts +3 -1
- package/dist/sdk/index.js +13212 -73972
- package/docs/plugin-admission.md +23 -0
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -9650,7 +9650,7 @@ import { homedir as homedir5 } from "os";
|
|
|
9650
9650
|
import {
|
|
9651
9651
|
cpSync as cpSync2,
|
|
9652
9652
|
existsSync as existsSync12,
|
|
9653
|
-
lstatSync as
|
|
9653
|
+
lstatSync as lstatSync18,
|
|
9654
9654
|
mkdirSync as mkdirSync8,
|
|
9655
9655
|
mkdtempSync as mkdtempSync2,
|
|
9656
9656
|
readFileSync as readFileSync15,
|
|
@@ -9661,12 +9661,12 @@ import {
|
|
|
9661
9661
|
writeFileSync as writeFileSync6
|
|
9662
9662
|
} from "fs";
|
|
9663
9663
|
import { homedir as homedir4 } from "os";
|
|
9664
|
-
import { basename as
|
|
9664
|
+
import { basename as basename7, dirname as dirname15, join as join26 } from "path";
|
|
9665
9665
|
|
|
9666
9666
|
// src/lib/agent-integration.ts
|
|
9667
|
-
import { createHash as
|
|
9668
|
-
import { existsSync as existsSync11, lstatSync as
|
|
9669
|
-
import { dirname as
|
|
9667
|
+
import { createHash as createHash13, randomUUID as randomUUID3 } from "crypto";
|
|
9668
|
+
import { existsSync as existsSync11, lstatSync as lstatSync17, statSync as statSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync14, readdirSync as readdirSync10, opendirSync as opendirSync5, readlinkSync as readlinkSync2, realpathSync as realpathSync5, renameSync as renameSync4, rmdirSync as rmdirSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, chmodSync, openSync as openSync10, closeSync as closeSync10, fsyncSync as fsyncSync3, fstatSync as fstatSync10, readSync as readSync8, constants as constants9, linkSync as linkSync2 } from "fs";
|
|
9669
|
+
import { dirname as dirname14, join as join25, resolve as resolve14, relative as relative5, isAbsolute as isAbsolute14, sep as sep6 } from "path";
|
|
9670
9670
|
import { homedir as homedir3 } from "os";
|
|
9671
9671
|
|
|
9672
9672
|
// src/lib/managed-policy.ts
|
|
@@ -9675,8 +9675,9 @@ import { join as join11 } from "path";
|
|
|
9675
9675
|
|
|
9676
9676
|
// src/lib/selection-cache.ts
|
|
9677
9677
|
import { createHash as createHash3, randomUUID } from "crypto";
|
|
9678
|
-
import { closeSync, constants, fstatSync, lstatSync as lstatSync3, mkdirSync as mkdirSync4, openSync, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
9678
|
+
import { closeSync, constants, fstatSync, fsyncSync, lstatSync as lstatSync3, mkdirSync as mkdirSync4, openSync, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
9679
9679
|
import { dirname as dirname4, join as join10, parse, resolve as resolve3 } from "path";
|
|
9680
|
+
import { isDeepStrictEqual } from "util";
|
|
9680
9681
|
|
|
9681
9682
|
// src/lib/skill-bundle.ts
|
|
9682
9683
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -10271,7 +10272,7 @@ function selectionSnapshotsEqual(left, right) {
|
|
|
10271
10272
|
var MAX_PROFILE_SELECTIONS = 4096;
|
|
10272
10273
|
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
10273
10274
|
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
10274
|
-
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS *
|
|
10275
|
+
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
10275
10276
|
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
10276
10277
|
var LEGACY_PROFILE_SELECTIONS = 256;
|
|
10277
10278
|
var LEGACY_PROFILE_DOCUMENT_BYTES = 1e6;
|
|
@@ -10420,12 +10421,21 @@ function writeSelectionJson(path, value) {
|
|
|
10420
10421
|
throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The Skills selection receipt exceeds its size limit.");
|
|
10421
10422
|
atomicWrite(path, bytes, 384);
|
|
10422
10423
|
}
|
|
10423
|
-
function atomicWrite(path, bytes, mode) {
|
|
10424
|
+
function atomicWrite(path, bytes, mode, durable = false) {
|
|
10424
10425
|
assertRegularPath(path, true);
|
|
10425
10426
|
const temporary = join10(dirname4(path), `.selection-${randomUUID()}.tmp`);
|
|
10426
10427
|
try {
|
|
10427
|
-
|
|
10428
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, mode);
|
|
10429
|
+
try {
|
|
10430
|
+
writeFileSync4(fd, bytes);
|
|
10431
|
+
if (durable)
|
|
10432
|
+
fsyncSync(fd);
|
|
10433
|
+
} finally {
|
|
10434
|
+
closeSync(fd);
|
|
10435
|
+
}
|
|
10428
10436
|
renameSync2(temporary, path);
|
|
10437
|
+
if (durable)
|
|
10438
|
+
syncDirectory(dirname4(path));
|
|
10429
10439
|
} finally {
|
|
10430
10440
|
try {
|
|
10431
10441
|
unlinkSync(temporary);
|
|
@@ -10544,19 +10554,194 @@ function sessionReceiptPath(sessionId, options = {}) {
|
|
|
10544
10554
|
throw new SkillSelectionError("INVALID_SESSION", `A Skills session id must contain 1\u2013${MAX_SKILL_SESSION_ID_CHARS} characters.`);
|
|
10545
10555
|
return join10(selectionCacheRoot(options), "sessions", `${hash(sessionId)}.json`);
|
|
10546
10556
|
}
|
|
10547
|
-
function
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10557
|
+
function skillSessionGeneration(receipt) {
|
|
10558
|
+
return receipt.generation ?? 0;
|
|
10559
|
+
}
|
|
10560
|
+
function validateSkillSessionBinding(binding) {
|
|
10561
|
+
if (!binding || typeof binding.sessionId !== "string" || !binding.sessionId.trim() || binding.sessionId.length > MAX_SKILL_SESSION_ID_CHARS || !Number.isSafeInteger(binding.generation) || binding.generation < 0 || !/^[a-f0-9]{64}$/.test(binding.receiptSha256)) {
|
|
10562
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt contains an invalid parent snapshot binding.");
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
function validateSkillSessionReceipt(receipt, sessionId) {
|
|
10566
|
+
validateSelectionReceipt(receipt);
|
|
10567
|
+
const keys = new Set(receipt.profile.selections.map(selectionKey));
|
|
10568
|
+
if (receipt.sessionId !== sessionId || !Array.isArray(receipt.loaded) || receipt.loaded.length > keys.size || new Set(receipt.loaded).size !== receipt.loaded.length || !receipt.loaded.every((key) => keys.has(key)) || receipt.generation !== undefined && (!Number.isSafeInteger(receipt.generation) || receipt.generation < 1)) {
|
|
10569
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is invalid.");
|
|
10570
|
+
}
|
|
10571
|
+
if (receipt.parent) {
|
|
10572
|
+
validateSkillSessionBinding(receipt.parent);
|
|
10573
|
+
if (receipt.parent.sessionId === sessionId)
|
|
10574
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
|
|
10575
|
+
}
|
|
10576
|
+
}
|
|
10577
|
+
function readSkillSessionSnapshotIfExists(sessionId, options = {}) {
|
|
10578
|
+
const path = sessionReceiptPath(sessionId, options), bytes = readRegularFile(path, MAX_PROFILE_DOCUMENT_BYTES);
|
|
10579
|
+
if (!bytes)
|
|
10580
|
+
return null;
|
|
10581
|
+
let receipt;
|
|
10582
|
+
try {
|
|
10583
|
+
receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
10584
|
+
} catch {
|
|
10585
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is unreadable.");
|
|
10586
|
+
}
|
|
10587
|
+
validateSkillSessionReceipt(receipt, sessionId);
|
|
10588
|
+
const sha256 = sha256Hex(bytes);
|
|
10589
|
+
return { path, bytes, receipt, sha256, sessionId, generation: skillSessionGeneration(receipt), receiptSha256: sha256 };
|
|
10590
|
+
}
|
|
10591
|
+
function readSkillSessionSnapshot(sessionId, options = {}) {
|
|
10592
|
+
const snapshot = readSkillSessionSnapshotIfExists(sessionId, options);
|
|
10593
|
+
if (!snapshot)
|
|
10594
|
+
throw new SkillSelectionError("SESSION_NOT_FOUND", "The requested Skills session receipt does not exist.");
|
|
10595
|
+
return snapshot;
|
|
10596
|
+
}
|
|
10597
|
+
function skillSessionSnapshotBinding(snapshot) {
|
|
10598
|
+
return { sessionId: snapshot.sessionId, generation: snapshot.generation, receiptSha256: snapshot.receiptSha256 };
|
|
10599
|
+
}
|
|
10600
|
+
function withSessionWriteLocks(sessionIds, options, action) {
|
|
10601
|
+
const operationId = randomUUID();
|
|
10602
|
+
const locks = [...new Set(sessionIds)].map((sessionId) => ({ sessionId, path: `${sessionReceiptPath(sessionId, options)}.write-lock` })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
10603
|
+
const owned = [];
|
|
10604
|
+
const assertOwned = () => {
|
|
10605
|
+
for (const lock of owned) {
|
|
10606
|
+
const current = lstatSync3(lock.path, { throwIfNoEntry: false });
|
|
10607
|
+
if (!current?.isFile() || current.dev !== lock.dev || current.ino !== lock.ino) {
|
|
10608
|
+
throw new SkillSelectionError("SESSION_WRITE_LOCK_CHANGED", "A session write lock changed during the operation; no replacement is permitted.");
|
|
10609
|
+
}
|
|
10610
|
+
}
|
|
10611
|
+
};
|
|
10612
|
+
try {
|
|
10613
|
+
for (const lock of locks) {
|
|
10614
|
+
assertRegularPath(lock.path, true);
|
|
10615
|
+
let fd;
|
|
10616
|
+
try {
|
|
10617
|
+
fd = openSync(lock.path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
10618
|
+
} catch (error) {
|
|
10619
|
+
if (error.code === "EEXIST")
|
|
10620
|
+
throw new SkillSelectionError("SESSION_WRITE_LOCKED", "Another Skills process owns a required session write lock; retry after it completes. A surviving lock requires explicit recovery review.");
|
|
10621
|
+
throw error;
|
|
10622
|
+
}
|
|
10623
|
+
const stat = fstatSync(fd);
|
|
10624
|
+
owned.push({ path: lock.path, fd, dev: stat.dev, ino: stat.ino });
|
|
10625
|
+
writeFileSync4(fd, `${JSON.stringify({ schemaVersion: 1, operationId, sessionId: lock.sessionId, pid: process.pid })}
|
|
10626
|
+
`);
|
|
10554
10627
|
}
|
|
10628
|
+
return action(assertOwned);
|
|
10629
|
+
} finally {
|
|
10630
|
+
for (const lock of owned.reverse()) {
|
|
10631
|
+
closeSync(lock.fd);
|
|
10632
|
+
const current = lstatSync3(lock.path, { throwIfNoEntry: false });
|
|
10633
|
+
if (current?.isFile() && current.dev === lock.dev && current.ino === lock.ino)
|
|
10634
|
+
unlinkSync(lock.path);
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
function withSessionWriteLock(sessionId, options, action) {
|
|
10639
|
+
return withSessionWriteLocks([sessionId], options, action);
|
|
10640
|
+
}
|
|
10641
|
+
function sameSnapshot(actual, expected) {
|
|
10642
|
+
return actual === null ? expected === null : expected !== null && actual.sessionId === expected.sessionId && actual.generation === expected.generation && actual.receiptSha256 === expected.receiptSha256;
|
|
10643
|
+
}
|
|
10644
|
+
function nextSkillSessionGeneration(generation) {
|
|
10645
|
+
if (!Number.isSafeInteger(generation) || generation < 0 || generation >= Number.MAX_SAFE_INTEGER) {
|
|
10646
|
+
throw new SkillSelectionError("SESSION_GENERATION_EXHAUSTED", "The Skills session generation cannot advance safely; preserve the receipt and review recovery before continuing.");
|
|
10647
|
+
}
|
|
10648
|
+
return generation + 1;
|
|
10649
|
+
}
|
|
10650
|
+
function parentChanged() {
|
|
10651
|
+
throw new SkillSelectionError("SESSION_PARENT_CHANGED", "The parent Skills session changed while the child was resolving; resolve the child again from the current parent pin.");
|
|
10652
|
+
}
|
|
10653
|
+
function writeSkillSession(receipt, expected, options = {}) {
|
|
10654
|
+
validateSkillSessionReceipt(receipt, receipt.sessionId);
|
|
10655
|
+
if (expected.parent)
|
|
10656
|
+
validateSkillSessionBinding(expected.parent);
|
|
10657
|
+
if (expected.parent?.sessionId === receipt.sessionId)
|
|
10658
|
+
throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
|
|
10659
|
+
const lockIds = expected.parent ? [receipt.sessionId, expected.parent.sessionId] : [receipt.sessionId];
|
|
10660
|
+
withSessionWriteLocks(lockIds, options, (assertOwned) => {
|
|
10661
|
+
const current = readSkillSessionSnapshotIfExists(receipt.sessionId, options);
|
|
10662
|
+
if (!sameSnapshot(current, expected.current) || current && !isDeepStrictEqual(current.receipt.profile, receipt.profile)) {
|
|
10663
|
+
throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The Skills session receipt changed while context was loading; resolve context again without replacing the current pin.");
|
|
10664
|
+
}
|
|
10665
|
+
let parent = null;
|
|
10666
|
+
if (expected.parent) {
|
|
10667
|
+
parent = readSkillSessionSnapshotIfExists(expected.parent.sessionId, options);
|
|
10668
|
+
if (!sameSnapshot(parent, expected.parent) || !parent || !isDeepStrictEqual(parent.receipt.profile, receipt.profile))
|
|
10669
|
+
parentChanged();
|
|
10670
|
+
}
|
|
10671
|
+
const generation = nextSkillSessionGeneration(current?.generation ?? 0);
|
|
10672
|
+
const next = current ? { ...current.receipt, generation, loaded: [...new Set([...current.receipt.loaded, ...receipt.loaded])] } : { ...receipt, generation, ...expected.parent ? { parent: expected.parent } : {} };
|
|
10673
|
+
assertOwned();
|
|
10674
|
+
if (expected.parent) {
|
|
10675
|
+
parent = readSkillSessionSnapshotIfExists(expected.parent.sessionId, options);
|
|
10676
|
+
if (!sameSnapshot(parent, expected.parent))
|
|
10677
|
+
parentChanged();
|
|
10678
|
+
}
|
|
10679
|
+
writeSelectionJson(sessionReceiptPath(receipt.sessionId, options), next);
|
|
10680
|
+
});
|
|
10681
|
+
}
|
|
10682
|
+
function syncDirectory(path) {
|
|
10683
|
+
assertRegularPath(path);
|
|
10684
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
10685
|
+
try {
|
|
10686
|
+
fsyncSync(fd);
|
|
10687
|
+
} finally {
|
|
10688
|
+
closeSync(fd);
|
|
10689
|
+
}
|
|
10690
|
+
}
|
|
10691
|
+
function archiveBytes(path, bytes) {
|
|
10692
|
+
assertRegularPath(path, true);
|
|
10693
|
+
const fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
10694
|
+
try {
|
|
10695
|
+
writeFileSync4(fd, bytes);
|
|
10696
|
+
fsyncSync(fd);
|
|
10697
|
+
} finally {
|
|
10698
|
+
closeSync(fd);
|
|
10555
10699
|
}
|
|
10556
|
-
|
|
10700
|
+
}
|
|
10701
|
+
function replaceSkillSession(expected, replacement, plan, options = {}, beforeCommit = () => {}) {
|
|
10702
|
+
validateSkillSessionReceipt(replacement, replacement.sessionId);
|
|
10703
|
+
const bytes = Buffer.from(`${JSON.stringify(replacement)}
|
|
10704
|
+
`);
|
|
10705
|
+
if (bytes.length > MAX_PROFILE_DOCUMENT_BYTES)
|
|
10706
|
+
throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The Skills selection receipt exceeds its size limit.");
|
|
10707
|
+
return withSessionWriteLock(replacement.sessionId, options, (assertOwned) => {
|
|
10708
|
+
const before = readSkillSessionSnapshot(replacement.sessionId, options);
|
|
10709
|
+
if (!sameSnapshot(before, expected))
|
|
10710
|
+
throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The Skills session receipt generation or bytes changed after review; inspect it and prepare a new plan.");
|
|
10711
|
+
if (replacement.generation !== nextSkillSessionGeneration(before.generation))
|
|
10712
|
+
throw new SkillSelectionError("SESSION_GENERATION_CHANGED", "The replacement Skills session must advance exactly one generation.");
|
|
10713
|
+
if (before.receipt.profile.authority !== replacement.profile.authority || before.receipt.profile.workspaceId !== replacement.profile.workspaceId) {
|
|
10714
|
+
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "A session reconciliation cannot change its Skills authority or workspace.");
|
|
10715
|
+
}
|
|
10716
|
+
const directory = join10(selectionCacheRoot(options), "session-reconciliations", randomUUID());
|
|
10717
|
+
const archivePath = join10(directory, "original.json"), replacementPath = join10(directory, "replacement.json"), receiptPath = join10(directory, "receipt.json");
|
|
10718
|
+
const afterSha256 = sha256Hex(bytes);
|
|
10719
|
+
archiveBytes(archivePath, before.bytes);
|
|
10720
|
+
archiveBytes(replacementPath, bytes);
|
|
10721
|
+
const receipt = { schemaVersion: 1, status: "prepared", sessionId: replacement.sessionId, beforeGeneration: before.generation, afterGeneration: replacement.generation, beforeSha256: before.sha256, afterSha256, archivePath, replacementPath, plan };
|
|
10722
|
+
archiveBytes(receiptPath, Buffer.from(`${JSON.stringify(receipt)}
|
|
10723
|
+
`));
|
|
10724
|
+
syncDirectory(directory);
|
|
10725
|
+
syncDirectory(dirname4(directory));
|
|
10726
|
+
syncDirectory(selectionCacheRoot(options));
|
|
10727
|
+
if (sha256Hex(readRegularFile(archivePath, MAX_PROFILE_DOCUMENT_BYTES)) !== before.sha256 || !sameSnapshot(readSkillSessionSnapshot(replacement.sessionId, options), expected)) {
|
|
10728
|
+
throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The session or its preservation archive changed before replacement; retained evidence must be reviewed.");
|
|
10729
|
+
}
|
|
10730
|
+
beforeCommit();
|
|
10731
|
+
assertOwned();
|
|
10732
|
+
try {
|
|
10733
|
+
atomicWrite(before.path, bytes, 384, true);
|
|
10734
|
+
atomicWrite(receiptPath, Buffer.from(`${JSON.stringify({ ...receipt, status: "applied" })}
|
|
10735
|
+
`), 384, true);
|
|
10736
|
+
} catch {
|
|
10737
|
+
throw new SkillSelectionError("SESSION_RECONCILIATION_INCOMPLETE", `Inspect sessions show and the preserved operation receipt at ${receiptPath} before any retry; replacement may already have committed.`);
|
|
10738
|
+
}
|
|
10739
|
+
return { archivePath, receiptPath, beforeSha256: before.sha256, afterSha256 };
|
|
10740
|
+
});
|
|
10557
10741
|
}
|
|
10558
10742
|
|
|
10559
10743
|
// src/lib/agent-policy-limits.ts
|
|
10744
|
+
import { basename as basename3, isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
10560
10745
|
var AGENT_POLICY_LIMITS = Object.freeze({ bytes: 1024 * 1024, agents: 16, discoverySources: 2048, discoveryRawSourceBytes: 64 * 1024 * 1024, discoveryRawTotalBytes: 256 * 1024 * 1024, discoveryPathLinks: 40, discoveryPathSteps: 256, discoveryPathSourceMetadataBytes: 64 * 1024, discoveryPathTotalMetadataBytes: 8 * 1024 * 1024, discoveryRoots: 512, discoveryDirectories: 64, discoveryDirectoryEntries: 20000, discoveryDirectoryBytes: 8 * 1024 * 1024, builtinNames: 2048, rootAliases: 2, fields: 64, pathCharacters: 4096 });
|
|
10561
10746
|
function object(value) {
|
|
10562
10747
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -10622,7 +10807,9 @@ function assertAgentPolicyCollections(policy) {
|
|
|
10622
10807
|
text(source.path);
|
|
10623
10808
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
10624
10809
|
if (source.hashMode !== undefined)
|
|
10625
|
-
requireBound(["bytes", "path-bytes", "claude-plugin-registry"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
10810
|
+
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
10811
|
+
if (source.hashMode === "claude-marketplace-registry")
|
|
10812
|
+
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && basename3(source.path) === "known_marketplaces.json");
|
|
10626
10813
|
if (source.hashMode === "claude-plugin-registry") {
|
|
10627
10814
|
requireBound(agent === "claude");
|
|
10628
10815
|
const managed = array(source.managedPlugins, 64);
|
|
@@ -10774,9 +10961,9 @@ function isOwnedCliBridge(path, expectedPaths) {
|
|
|
10774
10961
|
}
|
|
10775
10962
|
|
|
10776
10963
|
// src/lib/agent-discovery.ts
|
|
10777
|
-
import { createHash as
|
|
10778
|
-
import { lstatSync as
|
|
10779
|
-
import { dirname as
|
|
10964
|
+
import { createHash as createHash12 } from "crypto";
|
|
10965
|
+
import { lstatSync as lstatSync16, readFileSync as readFileSync13, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
|
|
10966
|
+
import { dirname as dirname13, isAbsolute as isAbsolute13, join as join23, resolve as resolve13, sep as sep5 } from "path";
|
|
10780
10967
|
|
|
10781
10968
|
// node_modules/yaml/dist/index.js
|
|
10782
10969
|
var composer = require_composer();
|
|
@@ -10825,7 +11012,7 @@ var $visit = visit.visit;
|
|
|
10825
11012
|
var $visitAsync = visit.visitAsync;
|
|
10826
11013
|
|
|
10827
11014
|
// src/lib/agent-hermes.ts
|
|
10828
|
-
import { dirname as dirname5, isAbsolute as
|
|
11015
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, resolve as resolve5 } from "path";
|
|
10829
11016
|
import { lstatSync as lstatSync6, readFileSync as readFileSync9, opendirSync } from "fs";
|
|
10830
11017
|
var HERMES_OPT_OUT = ".no-bundled-skills";
|
|
10831
11018
|
var EVENTS = ["pre_llm_call", "pre_tool_call"];
|
|
@@ -10863,7 +11050,7 @@ function quoted(value) {
|
|
|
10863
11050
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
10864
11051
|
}
|
|
10865
11052
|
function hermesHookDefinitions(supervisor) {
|
|
10866
|
-
if (!
|
|
11053
|
+
if (!isAbsolute4(supervisor.runtime) || !isAbsolute4(supervisor.path))
|
|
10867
11054
|
throw new Error("Expected absolute Hermes supervisor paths");
|
|
10868
11055
|
const entry = (event) => ({ command: `${quoted(supervisor.runtime)} ${quoted(supervisor.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
|
|
10869
11056
|
return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
|
|
@@ -10899,14 +11086,14 @@ function assertHermesEnvironment(home) {
|
|
|
10899
11086
|
throw new Error(`NATIVE_SKILL_DRIFT: Hermes ${variable} requires a dedicated discovery adapter; unset it before using this adapter`);
|
|
10900
11087
|
}
|
|
10901
11088
|
const selected = process.env.HERMES_HOME?.trim();
|
|
10902
|
-
if (selected && (!
|
|
11089
|
+
if (selected && (!isAbsolute4(selected) || resolve5(selected) !== join13(resolve5(home), ".hermes")))
|
|
10903
11090
|
throw new Error("NATIVE_SKILL_DRIFT: custom Hermes homes/profiles require their own reviewed bridge");
|
|
10904
11091
|
if (process.env.HERMES_ENABLE_PROJECT_PLUGINS?.trim() && !["0", "false", "no", "off"].includes(process.env.HERMES_ENABLE_PROJECT_PLUGINS.trim().toLowerCase()))
|
|
10905
11092
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes project plugins require a dedicated discovery review");
|
|
10906
11093
|
}
|
|
10907
11094
|
function assertNoHermesLegacyShadow(root) {
|
|
10908
11095
|
let entries = 0;
|
|
10909
|
-
for (let cursor =
|
|
11096
|
+
for (let cursor = resolve5(root);; cursor = dirname5(cursor)) {
|
|
10910
11097
|
if (lstatSync6(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
10911
11098
|
throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes legacy discovery root");
|
|
10912
11099
|
if (dirname5(cursor) === cursor)
|
|
@@ -10992,13 +11179,13 @@ if (failed) process.exitCode = 2;
|
|
|
10992
11179
|
// src/lib/agent-discovery-directories.ts
|
|
10993
11180
|
import { createHash as createHash5 } from "crypto";
|
|
10994
11181
|
import { lstatSync as lstatSync7, opendirSync as opendirSync2 } from "fs";
|
|
10995
|
-
import { dirname as dirname6, isAbsolute as
|
|
11182
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join14, resolve as resolve6 } from "path";
|
|
10996
11183
|
function need(value, message) {
|
|
10997
11184
|
if (!value)
|
|
10998
11185
|
throw new Error(message);
|
|
10999
11186
|
}
|
|
11000
11187
|
function safe(path) {
|
|
11001
|
-
need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters &&
|
|
11188
|
+
need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute5(path) && resolve6(path) === path && !path.includes("\x00"), "Expected a canonical absolute discovery directory path");
|
|
11002
11189
|
for (let cursor = path;; cursor = dirname6(cursor)) {
|
|
11003
11190
|
need(!lstatSync7(cursor, { throwIfNoEntry: false })?.isSymbolicLink(), "Refusing symlink native discovery directory");
|
|
11004
11191
|
if (dirname6(cursor) === cursor)
|
|
@@ -11063,12 +11250,12 @@ function verifyDiscoveryDirectories(directories) {
|
|
|
11063
11250
|
// src/lib/agent-discovery-bytes.ts
|
|
11064
11251
|
import { createHash as createHash6 } from "crypto";
|
|
11065
11252
|
import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync3, lstatSync as lstatSync8, openSync as openSync3, readSync as readSync3 } from "fs";
|
|
11066
|
-
import { dirname as dirname7, isAbsolute as
|
|
11253
|
+
import { dirname as dirname7, isAbsolute as isAbsolute6, resolve as resolve7 } from "path";
|
|
11067
11254
|
function discoveryByteBudget() {
|
|
11068
11255
|
return { remaining: AGENT_POLICY_LIMITS.discoveryRawTotalBytes };
|
|
11069
11256
|
}
|
|
11070
11257
|
function safe2(path) {
|
|
11071
|
-
if (typeof path !== "string" || !
|
|
11258
|
+
if (typeof path !== "string" || !isAbsolute6(path) || path !== resolve7(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00"))
|
|
11072
11259
|
throw new Error("Expected a canonical absolute raw discovery path");
|
|
11073
11260
|
for (let cursor = path;; cursor = dirname7(cursor)) {
|
|
11074
11261
|
if (lstatSync8(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
@@ -11131,7 +11318,7 @@ function hashRawDiscoveryFile(path, budget, changes) {
|
|
|
11131
11318
|
// src/lib/agent-discovery-path-bytes.ts
|
|
11132
11319
|
import { createHash as createHash7 } from "crypto";
|
|
11133
11320
|
import { lstatSync as lstatSync9, readlinkSync } from "fs";
|
|
11134
|
-
import { dirname as dirname8, isAbsolute as
|
|
11321
|
+
import { dirname as dirname8, isAbsolute as isAbsolute7, join as join15, parse as parse2, resolve as resolve8, sep as sep3 } from "path";
|
|
11135
11322
|
function identity2(stat, mutable = false) {
|
|
11136
11323
|
return {
|
|
11137
11324
|
dev: String(stat.dev),
|
|
@@ -11143,7 +11330,7 @@ function identity2(stat, mutable = false) {
|
|
|
11143
11330
|
};
|
|
11144
11331
|
}
|
|
11145
11332
|
function canonical(path) {
|
|
11146
|
-
if (typeof path !== "string" || !
|
|
11333
|
+
if (typeof path !== "string" || !isAbsolute7(path) || path !== resolve8(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00") || Buffer.from(path).toString("utf8") !== path)
|
|
11147
11334
|
throw new Error("Expected a canonical absolute discovery path");
|
|
11148
11335
|
}
|
|
11149
11336
|
function trace(path) {
|
|
@@ -11192,11 +11379,11 @@ function trace(path) {
|
|
|
11192
11379
|
if (!target || target.includes("\x00") || Buffer.byteLength(target) > AGENT_POLICY_LIMITS.pathCharacters || !after.isSymbolicLink() || JSON.stringify(identity2(stat, true)) !== JSON.stringify(identity2(after, true)))
|
|
11193
11380
|
throw new Error("Discovery link changed during read");
|
|
11194
11381
|
record2(candidate, { type: "link", target, ...identity2(stat, true) });
|
|
11195
|
-
if (
|
|
11382
|
+
if (isAbsolute7(target)) {
|
|
11196
11383
|
cursor = parse2(target).root;
|
|
11197
11384
|
root();
|
|
11198
11385
|
}
|
|
11199
|
-
const remainder =
|
|
11386
|
+
const remainder = isAbsolute7(target) ? target.slice(parse2(target).root.length) : target;
|
|
11200
11387
|
pending = [...remainder.split(sep3), ...pending];
|
|
11201
11388
|
if (pending.length + steps > AGENT_POLICY_LIMITS.discoveryPathSteps)
|
|
11202
11389
|
throw new Error("Discovery path step limit exceeded");
|
|
@@ -11244,15 +11431,15 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
11244
11431
|
|
|
11245
11432
|
// src/lib/plugin-discovery.ts
|
|
11246
11433
|
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync8, lstatSync as lstatSync14, openSync as openSync8, opendirSync as opendirSync4, readSync as readSync6 } from "fs";
|
|
11247
|
-
import { basename as
|
|
11434
|
+
import { basename as basename5, dirname as dirname11, isAbsolute as isAbsolute11, join as join21, resolve as resolve11 } from "path";
|
|
11248
11435
|
|
|
11249
11436
|
// src/lib/plugin-admission.ts
|
|
11250
11437
|
import { homedir as homedir2 } from "os";
|
|
11251
|
-
import { isAbsolute as
|
|
11438
|
+
import { isAbsolute as isAbsolute10, join as join20, resolve as resolve10 } from "path";
|
|
11252
11439
|
// package.json
|
|
11253
11440
|
var package_default = {
|
|
11254
11441
|
name: "@hasna/skills",
|
|
11255
|
-
version: "0.8.
|
|
11442
|
+
version: "0.8.11",
|
|
11256
11443
|
description: "Skills library for AI coding agents",
|
|
11257
11444
|
type: "module",
|
|
11258
11445
|
bin: {
|
|
@@ -11314,10 +11501,11 @@ var package_default = {
|
|
|
11314
11501
|
typecheck: "tsc --noEmit",
|
|
11315
11502
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
11316
11503
|
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
11317
|
-
|
|
11318
|
-
prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
|
|
11504
|
+
prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
|
|
11319
11505
|
prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
|
|
11320
|
-
"verify:producer": "bun run scripts/release-dependencies.ts"
|
|
11506
|
+
"verify:producer": "bun run scripts/release-dependencies.ts",
|
|
11507
|
+
"verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
|
|
11508
|
+
"verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
|
|
11321
11509
|
},
|
|
11322
11510
|
keywords: [
|
|
11323
11511
|
"skills",
|
|
@@ -11339,7 +11527,6 @@ var package_default = {
|
|
|
11339
11527
|
license: "Apache-2.0",
|
|
11340
11528
|
devDependencies: {
|
|
11341
11529
|
"@hasna/contracts": "1.1.0",
|
|
11342
|
-
"@hasna/secrets": "0.4.2",
|
|
11343
11530
|
"@types/bun": "1.3.14",
|
|
11344
11531
|
"@types/node": "25.2.3",
|
|
11345
11532
|
"@types/react": "^18.2.0",
|
|
@@ -11352,6 +11539,7 @@ var package_default = {
|
|
|
11352
11539
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
11353
11540
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
11354
11541
|
"@hasna/events": "0.1.18",
|
|
11542
|
+
"@hasna/secrets": "0.4.2",
|
|
11355
11543
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11356
11544
|
chalk: "^5.3.0",
|
|
11357
11545
|
commander: "^12.1.0",
|
|
@@ -11386,12 +11574,12 @@ import { spawnSync } from "child_process";
|
|
|
11386
11574
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
11387
11575
|
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
11388
11576
|
import { hostname as osHostname } from "os";
|
|
11389
|
-
import { isAbsolute as
|
|
11577
|
+
import { isAbsolute as isAbsolute8, join as join22 } from "path";
|
|
11390
11578
|
import { createRequire } from "module";
|
|
11391
11579
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11392
11580
|
import { closeSync as closeSync4, fstatSync as fstatSync4, lstatSync as lstatSync10, openSync as openSync4, readFileSync as readFileSync10, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
|
|
11393
11581
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
11394
|
-
import { basename as
|
|
11582
|
+
import { basename as basename4, dirname as dirname9, join as join16, relative as relative4, sep as sep4 } from "path";
|
|
11395
11583
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
11396
11584
|
function envToken(name) {
|
|
11397
11585
|
return name.toUpperCase().replace(/-/g, "_");
|
|
@@ -11499,7 +11687,7 @@ function entryPoint(directory) {
|
|
|
11499
11687
|
function resolveInstalledSecrets(parentUrl) {
|
|
11500
11688
|
let directory = dirname9(fileURLToPath(parentUrl));
|
|
11501
11689
|
for (;; ) {
|
|
11502
|
-
if (
|
|
11690
|
+
if (basename4(directory) !== "node_modules") {
|
|
11503
11691
|
const candidate = join16(directory, "node_modules", PACKAGE);
|
|
11504
11692
|
let absent = false;
|
|
11505
11693
|
try {
|
|
@@ -11559,7 +11747,7 @@ function homeDir(env) {
|
|
|
11559
11747
|
}
|
|
11560
11748
|
function absoluteOverride(env, key) {
|
|
11561
11749
|
const value = env[key]?.trim();
|
|
11562
|
-
return value &&
|
|
11750
|
+
return value && isAbsolute8(value) ? value : null;
|
|
11563
11751
|
}
|
|
11564
11752
|
function hasnaHomeDir(env) {
|
|
11565
11753
|
const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
|
|
@@ -12338,10 +12526,16 @@ function isCredentialResolutionError(error) {
|
|
|
12338
12526
|
function isSkillsFleetCredentialError(error) {
|
|
12339
12527
|
return error instanceof SkillsFleetCredentialError || typeof error === "object" && error !== null && error.name === "SkillsFleetCredentialError";
|
|
12340
12528
|
}
|
|
12341
|
-
|
|
12529
|
+
var REDACTED_VAULT_REFERENCE = "[redacted vault reference]";
|
|
12530
|
+
function redactVaultReference(message, vaultReference) {
|
|
12531
|
+
if (!vaultReference)
|
|
12532
|
+
return message;
|
|
12533
|
+
return message.split(vaultReference).join(REDACTED_VAULT_REFERENCE);
|
|
12534
|
+
}
|
|
12535
|
+
function asSkillsFleetCredentialError(error, vaultReference) {
|
|
12342
12536
|
if (!isCredentialResolutionError(error))
|
|
12343
12537
|
return null;
|
|
12344
|
-
return new SkillsFleetCredentialError(error.message, "MISSING_API_CREDENTIAL");
|
|
12538
|
+
return new SkillsFleetCredentialError(redactVaultReference(error.message, vaultReference), "MISSING_API_CREDENTIAL");
|
|
12345
12539
|
}
|
|
12346
12540
|
function normalizeSkillsApiOrigin(apiUrl) {
|
|
12347
12541
|
const url = new URL(apiUrl);
|
|
@@ -12526,7 +12720,7 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
12526
12720
|
try {
|
|
12527
12721
|
completed = await completePointerCredential(SKILLS_APP, pointer, env);
|
|
12528
12722
|
} catch (error) {
|
|
12529
|
-
const translated = asSkillsFleetCredentialError(error);
|
|
12723
|
+
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
12530
12724
|
if (translated)
|
|
12531
12725
|
throw translated;
|
|
12532
12726
|
throw error;
|
|
@@ -13407,11 +13601,18 @@ async function syncSelectionProfile(profileId, options = {}) {
|
|
|
13407
13601
|
return { profile, receipt, changed, downloaded, stationReported };
|
|
13408
13602
|
}
|
|
13409
13603
|
async function resolveSelectionContext(profileId, options = {}) {
|
|
13410
|
-
const
|
|
13411
|
-
const
|
|
13604
|
+
const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
|
|
13605
|
+
const session = sessionState?.receipt ?? null;
|
|
13606
|
+
const parentState = !session && options.parentSessionId ? readSkillSessionSnapshotIfExists(options.parentSessionId, options) : null;
|
|
13607
|
+
if (!session && options.parentSessionId && !parentState) {
|
|
13608
|
+
throw new SkillSelectionError("SESSION_PARENT_NOT_FOUND", "A child Skills session requires an existing parent receipt so its inherited pin can be bound exactly.");
|
|
13609
|
+
}
|
|
13610
|
+
const parentSession = parentState?.receipt ?? null;
|
|
13611
|
+
const sessionSnapshot = sessionState ? skillSessionSnapshotBinding(sessionState) : null;
|
|
13612
|
+
const parentSnapshot = parentState ? skillSessionSnapshotBinding(parentState) : undefined;
|
|
13412
13613
|
const project = options.projectDir ? readProjectSelection(options.projectDir) : null;
|
|
13413
13614
|
if (session && session.profile.profileId !== profileId || parentSession && parentSession.profile.profileId !== profileId || project && project.profile.profileId !== profileId) {
|
|
13414
|
-
throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select
|
|
13615
|
+
throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select the pinned profile, or review skills sessions reconcile for an intentional session change and explicit project sync for a project change.");
|
|
13415
13616
|
}
|
|
13416
13617
|
if (options.cached) {
|
|
13417
13618
|
const receipt2 = session ?? parentSession ?? project ?? readSelectionProfile(profileId, options);
|
|
@@ -13419,7 +13620,7 @@ async function resolveSelectionContext(profileId, options = {}) {
|
|
|
13419
13620
|
throw new SkillSelectionError("CACHED_PROFILE_MISSING", "No verified Skills profile is cached; authenticate and sync it first.");
|
|
13420
13621
|
assertFreshCachedProfile(receipt2, options);
|
|
13421
13622
|
assertBinding(receipt2.profile, options);
|
|
13422
|
-
return { receipt: receipt2, session, inheritedLoaded: parentSession?.loaded };
|
|
13623
|
+
return { receipt: receipt2, session, sessionSnapshot, parentSnapshot, inheritedLoaded: parentSession?.loaded };
|
|
13423
13624
|
}
|
|
13424
13625
|
const client = options.client ?? await createProfileClient();
|
|
13425
13626
|
const current = await client.resolveProfile(profileId);
|
|
@@ -13432,7 +13633,7 @@ async function resolveSelectionContext(profileId, options = {}) {
|
|
|
13432
13633
|
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The pinned selection belongs to another Skills authority or workspace.");
|
|
13433
13634
|
}
|
|
13434
13635
|
const receipt = locked ?? { schemaVersion: 1, verifiedAt: new Date((options.now ?? Date.now)()).toISOString(), profile: current };
|
|
13435
|
-
return { receipt, session, client, inheritedLoaded: parentSession?.loaded };
|
|
13636
|
+
return { receipt, session, sessionSnapshot, parentSnapshot, client, inheritedLoaded: parentSession?.loaded };
|
|
13436
13637
|
}
|
|
13437
13638
|
function assertBinding(profile, options) {
|
|
13438
13639
|
if (options.authority !== undefined && profile.authority !== options.authority || options.workspaceId !== undefined && profile.workspaceId !== options.workspaceId) {
|
|
@@ -13661,8 +13862,8 @@ function buildPluginProjection(entries) {
|
|
|
13661
13862
|
|
|
13662
13863
|
// src/lib/plugin-projection-store.ts
|
|
13663
13864
|
import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
|
|
13664
|
-
import { constants as constants6, closeSync as closeSync7, fchmodSync, fstatSync as fstatSync7, fsyncSync, linkSync, lstatSync as lstatSync13, mkdirSync as mkdirSync6, openSync as openSync7, opendirSync as opendirSync3, readSync as readSync5, renameSync as renameSync3, rmdirSync as rmdirSync2, rmSync as rmSync2, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
|
|
13665
|
-
import { dirname as dirname10, isAbsolute as
|
|
13865
|
+
import { constants as constants6, closeSync as closeSync7, fchmodSync, fstatSync as fstatSync7, fsyncSync as fsyncSync2, linkSync, lstatSync as lstatSync13, mkdirSync as mkdirSync6, openSync as openSync7, opendirSync as opendirSync3, readSync as readSync5, renameSync as renameSync3, rmdirSync as rmdirSync2, rmSync as rmSync2, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
|
|
13866
|
+
import { dirname as dirname10, isAbsolute as isAbsolute9, join as join19, parse as parse4, resolve as resolve9 } from "path";
|
|
13666
13867
|
var READ_FLAGS = constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK;
|
|
13667
13868
|
var WRITE_FLAGS = constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW | constants6.O_NONBLOCK;
|
|
13668
13869
|
var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
|
|
@@ -13677,7 +13878,7 @@ function stat(path) {
|
|
|
13677
13878
|
}
|
|
13678
13879
|
}
|
|
13679
13880
|
function canonical2(path) {
|
|
13680
|
-
pluginNeed(typeof path === "string" && path.length <= 8192 &&
|
|
13881
|
+
pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute9(path) && resolve9(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
|
|
13681
13882
|
}
|
|
13682
13883
|
function owner(stats, system = false) {
|
|
13683
13884
|
const current = uid();
|
|
@@ -13886,7 +14087,7 @@ function writeExclusive(path, bytes, mode) {
|
|
|
13886
14087
|
pluginNeed(count > 0, "Plugin file write did not progress");
|
|
13887
14088
|
offset += count;
|
|
13888
14089
|
}
|
|
13889
|
-
|
|
14090
|
+
fsyncSync2(fd);
|
|
13890
14091
|
} catch (error) {
|
|
13891
14092
|
if (created) {
|
|
13892
14093
|
same3(created, stat(path), true);
|
|
@@ -14060,7 +14261,7 @@ function validatePluginTarget(value) {
|
|
|
14060
14261
|
for (const registration of value.registrations) {
|
|
14061
14262
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
14062
14263
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
14063
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" &&
|
|
14264
|
+
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute10(registration.projectPath) && resolve10(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
|
|
14064
14265
|
const key = JSON.stringify([registration.scope, registration.projectPath]);
|
|
14065
14266
|
pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
|
|
14066
14267
|
scopes.add(key);
|
|
@@ -14068,7 +14269,7 @@ function validatePluginTarget(value) {
|
|
|
14068
14269
|
for (const key of ["native", "resolver"]) {
|
|
14069
14270
|
pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
14070
14271
|
pluginText(value[key].executable);
|
|
14071
|
-
pluginNeed(
|
|
14272
|
+
pluginNeed(isAbsolute10(value[key].executable) && resolve10(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
|
|
14072
14273
|
pluginDigest(value[key].digest);
|
|
14073
14274
|
}
|
|
14074
14275
|
pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
|
|
@@ -14220,7 +14421,7 @@ function pluginResolverCommand(binding) {
|
|
|
14220
14421
|
}
|
|
14221
14422
|
function root(options) {
|
|
14222
14423
|
const path = options.storeRoot ?? pluginAdmissionRoot();
|
|
14223
|
-
pluginNeed(
|
|
14424
|
+
pluginNeed(isAbsolute10(path) && resolve10(path) === path, "Invalid plugin admission store root");
|
|
14224
14425
|
return path;
|
|
14225
14426
|
}
|
|
14226
14427
|
function pluginBindingPath(storeRoot, id) {
|
|
@@ -14425,12 +14626,12 @@ function validateManagedPluginWitnesses(value) {
|
|
|
14425
14626
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
14426
14627
|
seen.add(item.bindingId);
|
|
14427
14628
|
pluginText(item.storeRoot);
|
|
14428
|
-
pluginNeed(
|
|
14629
|
+
pluginNeed(isAbsolute11(item.storeRoot) && resolve11(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
14429
14630
|
}
|
|
14430
14631
|
}
|
|
14431
14632
|
function safe3(path) {
|
|
14432
14633
|
pluginText(path);
|
|
14433
|
-
pluginNeed(
|
|
14634
|
+
pluginNeed(isAbsolute11(path) && resolve11(path) === path, "Invalid plugin registry path");
|
|
14434
14635
|
for (let at = path;; at = dirname11(at)) {
|
|
14435
14636
|
pluginNeed(!lstatSync14(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
14436
14637
|
if (at === dirname11(at))
|
|
@@ -14499,7 +14700,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
14499
14700
|
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join21(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
|
|
14500
14701
|
const approval = (producer) => {
|
|
14501
14702
|
pluginText(producer);
|
|
14502
|
-
const digest = `sha256:${
|
|
14703
|
+
const digest = `sha256:${basename5(producer)}`;
|
|
14503
14704
|
const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
|
|
14504
14705
|
pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
|
|
14505
14706
|
if (!verified.has(producer)) {
|
|
@@ -14560,8 +14761,178 @@ function captureManagedPluginRegistry(path, managedPlugins) {
|
|
|
14560
14761
|
return { path, hashMode: "claude-plugin-registry", managedPlugins, sha256: hashManagedPluginRegistry(path, managedPlugins) };
|
|
14561
14762
|
}
|
|
14562
14763
|
|
|
14764
|
+
// src/lib/claude-marketplace-registry.ts
|
|
14765
|
+
import { createHash as createHash11 } from "crypto";
|
|
14766
|
+
import { closeSync as closeSync9, constants as constants8, fstatSync as fstatSync9, lstatSync as lstatSync15, openSync as openSync9, readSync as readSync7 } from "fs";
|
|
14767
|
+
import { basename as basename6, dirname as dirname12, isAbsolute as isAbsolute12, resolve as resolve12 } from "path";
|
|
14768
|
+
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
14769
|
+
function need2(value, reason) {
|
|
14770
|
+
if (!value)
|
|
14771
|
+
throw new Error(`Claude marketplace registry ${reason}`);
|
|
14772
|
+
}
|
|
14773
|
+
function absolutePath(value) {
|
|
14774
|
+
need2(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute12(value) && resolve12(value) === value, "requires a normalized absolute path");
|
|
14775
|
+
}
|
|
14776
|
+
var sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
14777
|
+
function parents(path) {
|
|
14778
|
+
const result = [];
|
|
14779
|
+
for (let at = dirname12(path);; at = dirname12(at)) {
|
|
14780
|
+
const stat2 = lstatSync15(at, { bigint: true });
|
|
14781
|
+
need2(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
14782
|
+
result.push([at, stat2]);
|
|
14783
|
+
if (at === dirname12(at))
|
|
14784
|
+
return result;
|
|
14785
|
+
}
|
|
14786
|
+
}
|
|
14787
|
+
function readRegistry2(path, budget) {
|
|
14788
|
+
absolutePath(path);
|
|
14789
|
+
need2(basename6(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
14790
|
+
need2(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
14791
|
+
const ancestors2 = parents(path), initial = lstatSync15(path, { bigint: true });
|
|
14792
|
+
need2(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
14793
|
+
need2(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
14794
|
+
const fd = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
14795
|
+
try {
|
|
14796
|
+
const opened = fstatSync9(fd, { bigint: true });
|
|
14797
|
+
need2(opened.isFile() && sameFile(initial, opened), "changed during open");
|
|
14798
|
+
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
14799
|
+
let length = 0;
|
|
14800
|
+
while (length < bytes.length) {
|
|
14801
|
+
const count = readSync7(fd, bytes, length, bytes.length - length, null);
|
|
14802
|
+
if (!count)
|
|
14803
|
+
break;
|
|
14804
|
+
length += count;
|
|
14805
|
+
}
|
|
14806
|
+
const after = fstatSync9(fd, { bigint: true }), current = lstatSync15(path, { bigint: true });
|
|
14807
|
+
need2(current.isFile() && sameFile(opened, after) && sameFile(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
14808
|
+
for (const [ancestor, before] of ancestors2) {
|
|
14809
|
+
const now = lstatSync15(ancestor, { bigint: true });
|
|
14810
|
+
need2(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
14811
|
+
}
|
|
14812
|
+
budget.remaining -= length;
|
|
14813
|
+
try {
|
|
14814
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
|
|
14815
|
+
} catch {
|
|
14816
|
+
throw new Error("Claude marketplace registry is not strict UTF-8");
|
|
14817
|
+
}
|
|
14818
|
+
} finally {
|
|
14819
|
+
closeSync9(fd);
|
|
14820
|
+
}
|
|
14821
|
+
}
|
|
14822
|
+
function parse5(text2) {
|
|
14823
|
+
let at = 0, nodes = 0;
|
|
14824
|
+
const numberPattern = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
|
|
14825
|
+
const space = () => {
|
|
14826
|
+
while (at < text2.length && /[\x20\t\r\n]/.test(text2[at]))
|
|
14827
|
+
at++;
|
|
14828
|
+
};
|
|
14829
|
+
function string() {
|
|
14830
|
+
need2(text2[at] === '"', "contains invalid JSON");
|
|
14831
|
+
const start = at++;
|
|
14832
|
+
while (at < text2.length) {
|
|
14833
|
+
const character = text2[at++];
|
|
14834
|
+
if (character === '"') {
|
|
14835
|
+
let value2;
|
|
14836
|
+
try {
|
|
14837
|
+
value2 = JSON.parse(text2.slice(start, at));
|
|
14838
|
+
} catch {
|
|
14839
|
+
throw new Error("Claude marketplace registry contains invalid JSON string");
|
|
14840
|
+
}
|
|
14841
|
+
need2(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
|
|
14842
|
+
return value2;
|
|
14843
|
+
}
|
|
14844
|
+
if (character === "\\")
|
|
14845
|
+
at++;
|
|
14846
|
+
}
|
|
14847
|
+
throw new Error("Claude marketplace registry contains an unterminated JSON string");
|
|
14848
|
+
}
|
|
14849
|
+
function value(depth) {
|
|
14850
|
+
need2(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
14851
|
+
space();
|
|
14852
|
+
const character = text2[at];
|
|
14853
|
+
if (character === '"')
|
|
14854
|
+
return { kind: "string", value: string() };
|
|
14855
|
+
if (character === "{" || character === "[") {
|
|
14856
|
+
at++;
|
|
14857
|
+
space();
|
|
14858
|
+
const object4 = character === "{", end = object4 ? "}" : "]", entries = [], items = [], keys = new Set;
|
|
14859
|
+
if (text2[at] !== end)
|
|
14860
|
+
while (true) {
|
|
14861
|
+
if (object4) {
|
|
14862
|
+
space();
|
|
14863
|
+
const key = string();
|
|
14864
|
+
need2(!keys.has(key), "contains a duplicate JSON key");
|
|
14865
|
+
keys.add(key);
|
|
14866
|
+
space();
|
|
14867
|
+
need2(text2[at++] === ":", "contains invalid JSON");
|
|
14868
|
+
entries.push([key, value(depth + 1)]);
|
|
14869
|
+
} else
|
|
14870
|
+
items.push(value(depth + 1));
|
|
14871
|
+
space();
|
|
14872
|
+
if (text2[at] === end)
|
|
14873
|
+
break;
|
|
14874
|
+
need2(text2[at++] === ",", "contains invalid JSON");
|
|
14875
|
+
}
|
|
14876
|
+
at++;
|
|
14877
|
+
return object4 ? { kind: "object", entries } : { kind: "array", items };
|
|
14878
|
+
}
|
|
14879
|
+
for (const literal of ["true", "false", "null"])
|
|
14880
|
+
if (text2.startsWith(literal, at)) {
|
|
14881
|
+
at += literal.length;
|
|
14882
|
+
return { kind: "literal", value: literal };
|
|
14883
|
+
}
|
|
14884
|
+
numberPattern.lastIndex = at;
|
|
14885
|
+
const number = numberPattern.exec(text2);
|
|
14886
|
+
need2(number, "contains invalid JSON");
|
|
14887
|
+
at += number[0].length;
|
|
14888
|
+
return { kind: "number", value: number[0] };
|
|
14889
|
+
}
|
|
14890
|
+
const root2 = value(0);
|
|
14891
|
+
space();
|
|
14892
|
+
need2(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
14893
|
+
need2(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
|
|
14894
|
+
return root2;
|
|
14895
|
+
}
|
|
14896
|
+
function exactKeys(value, keys) {
|
|
14897
|
+
return value?.kind === "object" && value.entries.length === keys.length && value.entries.every(([key]) => keys.includes(key));
|
|
14898
|
+
}
|
|
14899
|
+
var field = (object4, key) => object4.entries.find(([name]) => name === key)?.[1];
|
|
14900
|
+
function textField(value) {
|
|
14901
|
+
need2(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
14902
|
+
return value.value;
|
|
14903
|
+
}
|
|
14904
|
+
function normalizeRow(row) {
|
|
14905
|
+
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
|
|
14906
|
+
return;
|
|
14907
|
+
const source = field(row, "source");
|
|
14908
|
+
if (source?.kind !== "object")
|
|
14909
|
+
return;
|
|
14910
|
+
const type = field(source, "source");
|
|
14911
|
+
if (type?.kind !== "string" || !["github", "directory"].includes(type.value))
|
|
14912
|
+
return;
|
|
14913
|
+
const location = type.value === "github" ? "repo" : "path";
|
|
14914
|
+
if (!exactKeys(source, ["source", location]))
|
|
14915
|
+
return;
|
|
14916
|
+
const sourceLocation = textField(field(source, location));
|
|
14917
|
+
if (type.value === "directory")
|
|
14918
|
+
absolutePath(sourceLocation);
|
|
14919
|
+
absolutePath(textField(field(row, "installLocation")));
|
|
14920
|
+
const updated = textField(field(row, "lastUpdated"));
|
|
14921
|
+
need2(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
|
|
14922
|
+
const millis = Date.parse(updated);
|
|
14923
|
+
need2(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
|
|
14924
|
+
row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
|
|
14925
|
+
}
|
|
14926
|
+
function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
14927
|
+
const value = parse5(readRegistry2(path, budget));
|
|
14928
|
+
for (const [, row] of value.entries)
|
|
14929
|
+
normalizeRow(row);
|
|
14930
|
+
const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
14931
|
+
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
14932
|
+
}
|
|
14933
|
+
|
|
14563
14934
|
// src/lib/agent-discovery.ts
|
|
14564
|
-
var digest = (text2) =>
|
|
14935
|
+
var digest = (text2) => createHash12("sha256").update(text2).digest("hex");
|
|
14565
14936
|
function parseConfig(text2, path, toml = false) {
|
|
14566
14937
|
try {
|
|
14567
14938
|
const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
|
|
@@ -14573,12 +14944,12 @@ function parseConfig(text2, path, toml = false) {
|
|
|
14573
14944
|
}
|
|
14574
14945
|
}
|
|
14575
14946
|
function safe4(path) {
|
|
14576
|
-
if (!
|
|
14947
|
+
if (!isAbsolute13(path) || path.includes("\x00"))
|
|
14577
14948
|
throw new Error("Expected an absolute native discovery path");
|
|
14578
|
-
for (let cursor = path;; cursor =
|
|
14579
|
-
if (
|
|
14949
|
+
for (let cursor = path;; cursor = dirname13(cursor)) {
|
|
14950
|
+
if (lstatSync16(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14580
14951
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
14581
|
-
if (
|
|
14952
|
+
if (dirname13(cursor) === cursor)
|
|
14582
14953
|
break;
|
|
14583
14954
|
}
|
|
14584
14955
|
}
|
|
@@ -14586,7 +14957,7 @@ function read(path, changes) {
|
|
|
14586
14957
|
safe4(path);
|
|
14587
14958
|
if (changes?.has(path))
|
|
14588
14959
|
return changes.get(path);
|
|
14589
|
-
const stat2 =
|
|
14960
|
+
const stat2 = lstatSync16(path, { throwIfNoEntry: false });
|
|
14590
14961
|
if (!stat2)
|
|
14591
14962
|
return null;
|
|
14592
14963
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
@@ -14594,6 +14965,14 @@ function read(path, changes) {
|
|
|
14594
14965
|
return readFileSync13(path, "utf8");
|
|
14595
14966
|
}
|
|
14596
14967
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
14968
|
+
if (source.hashMode === "claude-marketplace-registry") {
|
|
14969
|
+
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
|
|
14970
|
+
throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
|
|
14971
|
+
const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
|
|
14972
|
+
if (sha2562 !== source.sha256)
|
|
14973
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
14974
|
+
return sha2562;
|
|
14975
|
+
}
|
|
14597
14976
|
if (source.hashMode === "claude-plugin-registry") {
|
|
14598
14977
|
if (source.format !== undefined || source.fields !== undefined || !source.managedPlugins || changes?.has(source.path))
|
|
14599
14978
|
throw new Error("Managed plugin registry witnesses require verified native registrations");
|
|
@@ -14618,7 +14997,7 @@ function projected(source, changes, budget = discoveryByteBudget()) {
|
|
|
14618
14997
|
const object4 = source.format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, source.path, source.format === "toml");
|
|
14619
14998
|
if (!object4 || typeof object4 !== "object" || Array.isArray(object4))
|
|
14620
14999
|
throw new Error("Expected native discovery configuration object");
|
|
14621
|
-
return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((
|
|
15000
|
+
return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field2) => [field2, object4[field2] ?? null]))));
|
|
14622
15001
|
}
|
|
14623
15002
|
function verifyAgentDiscovery(binding) {
|
|
14624
15003
|
if (!binding || !Array.isArray(binding.sources) || !Array.isArray(binding.roots) || binding.sources.length > AGENT_POLICY_LIMITS.discoverySources || binding.roots.length > AGENT_POLICY_LIMITS.discoveryRoots)
|
|
@@ -14629,9 +15008,10 @@ function verifyAgentDiscovery(binding) {
|
|
|
14629
15008
|
verifyDiscoveryDirectories(binding.directories);
|
|
14630
15009
|
const budget = discoveryByteBudget();
|
|
14631
15010
|
for (const source of binding.sources) {
|
|
15011
|
+
assertMarketplaceBinding(binding, source);
|
|
14632
15012
|
if (source.hashMode === "claude-plugin-registry" && binding.agent !== "claude")
|
|
14633
15013
|
throw new Error("Managed Claude registry witnesses cannot apply to another agent");
|
|
14634
|
-
if (source.format !== undefined && (!["json", "toml", "yaml"].includes(source.format) || !Array.isArray(source.fields) || !source.fields.length || source.fields.length > 64 || source.fields.some((
|
|
15014
|
+
if (source.format !== undefined && (!["json", "toml", "yaml"].includes(source.format) || !Array.isArray(source.fields) || !source.fields.length || source.fields.length > 64 || source.fields.some((field2) => typeof field2 !== "string" || !field2)))
|
|
14635
15015
|
throw new Error("Invalid native discovery projection");
|
|
14636
15016
|
if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
14637
15017
|
throw new Error("Invalid native discovery digest");
|
|
@@ -14641,9 +15021,16 @@ function verifyAgentDiscovery(binding) {
|
|
|
14641
15021
|
for (const root2 of binding.roots)
|
|
14642
15022
|
safe4(root2);
|
|
14643
15023
|
}
|
|
15024
|
+
function assertMarketplaceBinding(binding, source) {
|
|
15025
|
+
if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
15026
|
+
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
15027
|
+
}
|
|
14644
15028
|
function rebindAgentDiscovery(binding, changes) {
|
|
14645
15029
|
const budget = discoveryByteBudget();
|
|
14646
|
-
return { ...binding, sources: binding.sources.map((source) =>
|
|
15030
|
+
return { ...binding, sources: binding.sources.map((source) => {
|
|
15031
|
+
assertMarketplaceBinding(binding, source);
|
|
15032
|
+
return { ...source, sha256: projected(source, changes, budget) };
|
|
15033
|
+
}) };
|
|
14647
15034
|
}
|
|
14648
15035
|
function captureDiscoveryByteSources(paths) {
|
|
14649
15036
|
if (!Array.isArray(paths) || paths.length > AGENT_POLICY_LIMITS.discoverySources || new Set(paths).size !== paths.length)
|
|
@@ -14658,7 +15045,7 @@ function captureDiscoveryPathSources(paths) {
|
|
|
14658
15045
|
return paths.map((path) => ({ path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget) }));
|
|
14659
15046
|
}
|
|
14660
15047
|
function resolveAgentDiscovery(options) {
|
|
14661
|
-
const canonical3 = options.canonical ??
|
|
15048
|
+
const canonical3 = options.canonical ?? resolve13, home = resolve13(options.home), agent = options.agent;
|
|
14662
15049
|
const sources = [], roots = new Set, builtinNames = [];
|
|
14663
15050
|
const witness = (path, format, fields) => {
|
|
14664
15051
|
const source = { path: canonical3(path), sha256: null, ...format ? { format, fields } : {} };
|
|
@@ -14689,7 +15076,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14689
15076
|
continue;
|
|
14690
15077
|
if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
|
|
14691
15078
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
14692
|
-
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join23(home, normalized.slice(2)) :
|
|
15079
|
+
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join23(home, normalized.slice(2)) : resolve13(home, ".hermes", normalized);
|
|
14693
15080
|
safe4(path);
|
|
14694
15081
|
roots.add(path);
|
|
14695
15082
|
}
|
|
@@ -14698,21 +15085,21 @@ function resolveAgentDiscovery(options) {
|
|
|
14698
15085
|
const executable = Bun.which("gemini");
|
|
14699
15086
|
if (executable) {
|
|
14700
15087
|
let packageRoot;
|
|
14701
|
-
for (let directory =
|
|
15088
|
+
for (let directory = dirname13(realpathSync4(executable)), depth = 0;depth < 8; directory = dirname13(directory), depth++) {
|
|
14702
15089
|
const path = join23(directory, "package.json"), raw = read(path);
|
|
14703
15090
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
14704
15091
|
packageRoot = directory;
|
|
14705
15092
|
witness(path);
|
|
14706
15093
|
break;
|
|
14707
15094
|
}
|
|
14708
|
-
if (
|
|
15095
|
+
if (dirname13(directory) === directory)
|
|
14709
15096
|
break;
|
|
14710
15097
|
}
|
|
14711
15098
|
if (!packageRoot)
|
|
14712
15099
|
unresolved("installed Gemini package layout is not recognized");
|
|
14713
15100
|
const builtinRoot = join23(packageRoot, "bundle/builtin");
|
|
14714
15101
|
safe4(builtinRoot);
|
|
14715
|
-
if (!
|
|
15102
|
+
if (!lstatSync16(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
14716
15103
|
unresolved("installed Gemini builtin root is missing");
|
|
14717
15104
|
for (const name of readdirSync9(builtinRoot).sort()) {
|
|
14718
15105
|
const text2 = witness(join23(builtinRoot, name, "SKILL.md"));
|
|
@@ -14750,7 +15137,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14750
15137
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
14751
15138
|
const commands = join23(root2, "commands");
|
|
14752
15139
|
safe4(commands);
|
|
14753
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
15140
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync16(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length))
|
|
14754
15141
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
14755
15142
|
const rootSkill = witness(join23(root2, "SKILL.md"));
|
|
14756
15143
|
if (rootSkill !== null)
|
|
@@ -14761,10 +15148,10 @@ function resolveAgentDiscovery(options) {
|
|
|
14761
15148
|
if (agent === "claude")
|
|
14762
15149
|
roots.add(join23(root2, "skills"));
|
|
14763
15150
|
for (const value of declared) {
|
|
14764
|
-
const path =
|
|
15151
|
+
const path = resolve13(root2, value);
|
|
14765
15152
|
if (path !== root2 && !path.startsWith(root2 + sep5))
|
|
14766
15153
|
unresolved("plugin skill path escapes its root");
|
|
14767
|
-
roots.add(path.endsWith(`${sep5}SKILL.md`) ?
|
|
15154
|
+
roots.add(path.endsWith(`${sep5}SKILL.md`) ? dirname13(path) : path);
|
|
14768
15155
|
}
|
|
14769
15156
|
}
|
|
14770
15157
|
if (agent === "claude") {
|
|
@@ -14772,7 +15159,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14772
15159
|
const installed = registrations === null ? {} : parseConfig(registrations, join23(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
14773
15160
|
const commands = canonical3(join23(home, ".claude/commands"));
|
|
14774
15161
|
safe4(commands);
|
|
14775
|
-
if (
|
|
15162
|
+
if (lstatSync16(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length)
|
|
14776
15163
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
14777
15164
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
14778
15165
|
if (enabled === false)
|
|
@@ -14792,7 +15179,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14792
15179
|
unresolved("unsupported plugin enablement");
|
|
14793
15180
|
const split = id.lastIndexOf("@"), name = id.slice(0, split), marketplace = id.slice(split + 1);
|
|
14794
15181
|
const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
|
|
14795
|
-
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !
|
|
15182
|
+
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute13(market.source))
|
|
14796
15183
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
14797
15184
|
const catalogText = witness(join23(market.source, ".agents/plugins/marketplace.json"));
|
|
14798
15185
|
if (catalogText === null)
|
|
@@ -14800,7 +15187,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14800
15187
|
const catalog = parseConfig(catalogText, join23(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
14801
15188
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
14802
15189
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
14803
|
-
plugin(
|
|
15190
|
+
plugin(resolve13(market.source, matches[0].source.path));
|
|
14804
15191
|
}
|
|
14805
15192
|
} else if (agent === "opencode") {
|
|
14806
15193
|
if (witness(join23(home, ".config/opencode/opencode.jsonc")) !== null || witness(join23(home, ".config/opencode/config.json")) !== null)
|
|
@@ -14808,18 +15195,18 @@ function resolveAgentDiscovery(options) {
|
|
|
14808
15195
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
14809
15196
|
unresolved("external plugin or remote skill source");
|
|
14810
15197
|
for (const path of config.skills?.paths ?? []) {
|
|
14811
|
-
if (typeof path !== "string" || !
|
|
15198
|
+
if (typeof path !== "string" || !isAbsolute13(path))
|
|
14812
15199
|
unresolved("relative or malformed added skill root");
|
|
14813
15200
|
roots.add(path);
|
|
14814
15201
|
}
|
|
14815
15202
|
const localPlugins = join23(home, ".config/opencode/plugins");
|
|
14816
15203
|
safe4(localPlugins);
|
|
14817
|
-
if (
|
|
15204
|
+
if (lstatSync16(localPlugins, { throwIfNoEntry: false }) && readdirSync9(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
14818
15205
|
unresolved("additional local plugin hooks require review");
|
|
14819
15206
|
} else if (agent === "hermes") {
|
|
14820
15207
|
for (const directory of [join23(home, ".hermes/plugins"), join23(home, ".hermes/hermes-agent")]) {
|
|
14821
15208
|
safe4(directory);
|
|
14822
|
-
if (
|
|
15209
|
+
if (lstatSync16(directory, { throwIfNoEntry: false }))
|
|
14823
15210
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
14824
15211
|
}
|
|
14825
15212
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -14829,11 +15216,11 @@ function resolveAgentDiscovery(options) {
|
|
|
14829
15216
|
unresolved("custom extension or skill paths");
|
|
14830
15217
|
const directory = join23(home, ".gemini/extensions");
|
|
14831
15218
|
safe4(directory);
|
|
14832
|
-
if (
|
|
15219
|
+
if (lstatSync16(directory, { throwIfNoEntry: false }))
|
|
14833
15220
|
for (const name of readdirSync9(directory)) {
|
|
14834
15221
|
const root2 = join23(directory, name);
|
|
14835
15222
|
safe4(root2);
|
|
14836
|
-
if (!
|
|
15223
|
+
if (!lstatSync16(root2)?.isDirectory())
|
|
14837
15224
|
continue;
|
|
14838
15225
|
const raw = witness(join23(root2, "gemini-extension.json"));
|
|
14839
15226
|
if (raw === null)
|
|
@@ -14914,7 +15301,7 @@ export default async ({ directory }) => {
|
|
|
14914
15301
|
}
|
|
14915
15302
|
|
|
14916
15303
|
// src/lib/agent-codex.ts
|
|
14917
|
-
import { isDeepStrictEqual } from "util";
|
|
15304
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
14918
15305
|
var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
|
|
14919
15306
|
function assertCodexPathConfigEditable(text2) {
|
|
14920
15307
|
const before = Bun.TOML.parse(text2);
|
|
@@ -14924,7 +15311,7 @@ function assertCodexPathConfigEditable(text2) {
|
|
|
14924
15311
|
delete expected.skills.config;
|
|
14925
15312
|
try {
|
|
14926
15313
|
const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
|
|
14927
|
-
if (
|
|
15314
|
+
if (isDeepStrictEqual2(withoutSections, expected))
|
|
14928
15315
|
return;
|
|
14929
15316
|
} catch {}
|
|
14930
15317
|
throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
|
|
@@ -14962,7 +15349,7 @@ function disableCodexBundledSkills(text2) {
|
|
|
14962
15349
|
const candidates = new Set;
|
|
14963
15350
|
const consider = (candidate2) => {
|
|
14964
15351
|
try {
|
|
14965
|
-
if (
|
|
15352
|
+
if (isDeepStrictEqual2(Bun.TOML.parse(candidate2), expected))
|
|
14966
15353
|
candidates.add(candidate2);
|
|
14967
15354
|
} catch {}
|
|
14968
15355
|
};
|
|
@@ -14988,7 +15375,7 @@ function disableCodexBundledSkills(text2) {
|
|
|
14988
15375
|
}
|
|
14989
15376
|
|
|
14990
15377
|
// src/lib/agent-integration.ts
|
|
14991
|
-
var sha = (data) =>
|
|
15378
|
+
var sha = (data) => createHash13("sha256").update(data).digest("hex");
|
|
14992
15379
|
var ROOTS = [
|
|
14993
15380
|
["claude", ".claude/skills"],
|
|
14994
15381
|
["codex", ".codex/skills"],
|
|
@@ -15007,11 +15394,11 @@ var ROOTS = [
|
|
|
15007
15394
|
["copilot", ".github/skills"]
|
|
15008
15395
|
];
|
|
15009
15396
|
function assertSafePath(path) {
|
|
15010
|
-
let cursor =
|
|
15397
|
+
let cursor = resolve14(path);
|
|
15011
15398
|
while (true) {
|
|
15012
|
-
if (
|
|
15399
|
+
if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15013
15400
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
15014
|
-
const parent =
|
|
15401
|
+
const parent = dirname14(cursor);
|
|
15015
15402
|
if (parent === cursor)
|
|
15016
15403
|
return;
|
|
15017
15404
|
cursor = parent;
|
|
@@ -15020,16 +15407,16 @@ function assertSafePath(path) {
|
|
|
15020
15407
|
function readRootAlias(home, agent) {
|
|
15021
15408
|
assertSafePath(home);
|
|
15022
15409
|
const realHome = realpathSync5(home), alias = join25(realHome, `.${agent}`);
|
|
15023
|
-
const stat2 =
|
|
15410
|
+
const stat2 = lstatSync17(alias, { throwIfNoEntry: false, bigint: true });
|
|
15024
15411
|
if (!stat2?.isSymbolicLink())
|
|
15025
15412
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
15026
|
-
const link = readlinkSync2(alias), targetPath =
|
|
15413
|
+
const link = readlinkSync2(alias), targetPath = resolve14(dirname14(alias), link);
|
|
15027
15414
|
assertSafePath(targetPath);
|
|
15028
|
-
const targetStat =
|
|
15415
|
+
const targetStat = lstatSync17(targetPath, { throwIfNoEntry: false });
|
|
15029
15416
|
if (!targetStat?.isDirectory())
|
|
15030
15417
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
15031
15418
|
const target = realpathSync5(targetPath), inside = relative5(realHome, target);
|
|
15032
|
-
if (!inside || inside === ".." || inside.startsWith(`..${sep6}`) ||
|
|
15419
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep6}`) || isAbsolute14(inside))
|
|
15033
15420
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
15034
15421
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
15035
15422
|
}
|
|
@@ -15038,14 +15425,14 @@ function rootAliases(home, allowed = false) {
|
|
|
15038
15425
|
return [];
|
|
15039
15426
|
const result = [];
|
|
15040
15427
|
for (const agent of ["claude", "codex"])
|
|
15041
|
-
if (
|
|
15428
|
+
if (lstatSync17(join25(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15042
15429
|
result.push(readRootAlias(home, agent));
|
|
15043
15430
|
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep6) || other.target.startsWith(item.target + sep6))))
|
|
15044
15431
|
throw new Error("Agent root alias targets must not overlap");
|
|
15045
15432
|
return result;
|
|
15046
15433
|
}
|
|
15047
15434
|
function canonicalAgentPath(path, aliases) {
|
|
15048
|
-
const absolute =
|
|
15435
|
+
const absolute = resolve14(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep6));
|
|
15049
15436
|
return binding ? join25(binding.target, relative5(binding.alias, absolute)) : absolute;
|
|
15050
15437
|
}
|
|
15051
15438
|
function recheckRootAliases(aliases) {
|
|
@@ -15059,35 +15446,35 @@ function recheckRootAliases(aliases) {
|
|
|
15059
15446
|
}
|
|
15060
15447
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
15061
15448
|
assertSafePath(path);
|
|
15062
|
-
const before =
|
|
15449
|
+
const before = lstatSync17(path);
|
|
15063
15450
|
if (!before.isFile() || before.size > maximum)
|
|
15064
15451
|
throw new Error("Unsupported or oversized native skill file");
|
|
15065
|
-
const descriptor =
|
|
15452
|
+
const descriptor = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
15066
15453
|
try {
|
|
15067
|
-
const opened =
|
|
15454
|
+
const opened = fstatSync10(descriptor);
|
|
15068
15455
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
|
|
15069
15456
|
throw new Error("Native skill file changed while reading");
|
|
15070
15457
|
const bytes = Buffer.allocUnsafe(before.size + 1);
|
|
15071
15458
|
let length = 0;
|
|
15072
15459
|
while (length < bytes.length) {
|
|
15073
|
-
const count =
|
|
15460
|
+
const count = readSync8(descriptor, bytes, length, bytes.length - length, null);
|
|
15074
15461
|
if (!count)
|
|
15075
15462
|
break;
|
|
15076
15463
|
length += count;
|
|
15077
15464
|
}
|
|
15078
|
-
const after =
|
|
15465
|
+
const after = fstatSync10(descriptor);
|
|
15079
15466
|
if (length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
15080
15467
|
throw new Error("Native skill file changed while reading");
|
|
15081
15468
|
return bytes.subarray(0, length);
|
|
15082
15469
|
} finally {
|
|
15083
|
-
|
|
15470
|
+
closeSync10(descriptor);
|
|
15084
15471
|
}
|
|
15085
15472
|
}
|
|
15086
15473
|
function treeHash(root2) {
|
|
15087
|
-
const hash2 =
|
|
15474
|
+
const hash2 = createHash13("sha256");
|
|
15088
15475
|
let bytes = 0, files = 0;
|
|
15089
15476
|
function visit2(path) {
|
|
15090
|
-
const stat2 =
|
|
15477
|
+
const stat2 = lstatSync17(path);
|
|
15091
15478
|
if (stat2.isSymbolicLink())
|
|
15092
15479
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
15093
15480
|
const rel = relative5(root2, path);
|
|
@@ -15113,11 +15500,11 @@ function treeHash(root2) {
|
|
|
15113
15500
|
function projectAncestorDirectories(projects) {
|
|
15114
15501
|
const directories = new Set;
|
|
15115
15502
|
for (const project of projects) {
|
|
15116
|
-
for (let path =
|
|
15503
|
+
for (let path = resolve14(project), depth = 0;; depth++) {
|
|
15117
15504
|
if (depth >= 100)
|
|
15118
15505
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
15119
15506
|
directories.add(path);
|
|
15120
|
-
const parent =
|
|
15507
|
+
const parent = dirname14(path);
|
|
15121
15508
|
if (parent === path)
|
|
15122
15509
|
break;
|
|
15123
15510
|
path = parent;
|
|
@@ -15144,10 +15531,10 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15144
15531
|
throw new Error("Native skill discovery metadata limit exceeded");
|
|
15145
15532
|
}
|
|
15146
15533
|
function emptySiblingCacheAlias(path, parent) {
|
|
15147
|
-
const link = readlinkSync2(path), target =
|
|
15148
|
-
if (
|
|
15534
|
+
const link = readlinkSync2(path), target = resolve14(parent, link);
|
|
15535
|
+
if (dirname14(target) !== parent || (isAbsolute14(link) ? link !== target : dirname14(link) !== "."))
|
|
15149
15536
|
return false;
|
|
15150
|
-
if (!
|
|
15537
|
+
if (!lstatSync17(target, { throwIfNoEntry: false })?.isDirectory())
|
|
15151
15538
|
return false;
|
|
15152
15539
|
const scan = cacheScans.get(target);
|
|
15153
15540
|
return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
|
|
@@ -15159,7 +15546,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15159
15546
|
assertSafePath(path);
|
|
15160
15547
|
if (!existsSync11(path))
|
|
15161
15548
|
return scan;
|
|
15162
|
-
const stat2 =
|
|
15549
|
+
const stat2 = lstatSync17(path);
|
|
15163
15550
|
if (stat2.isFile())
|
|
15164
15551
|
return scan;
|
|
15165
15552
|
if (!stat2.isDirectory())
|
|
@@ -15178,7 +15565,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15178
15565
|
}
|
|
15179
15566
|
const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
|
|
15180
15567
|
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep6));
|
|
15181
|
-
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join25(home, ".codex", "skills", ".system"), aliases) + sep6) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome:
|
|
15568
|
+
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join25(home, ".codex", "skills", ".system"), aliases) + sep6) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve14(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
15182
15569
|
return scan;
|
|
15183
15570
|
}
|
|
15184
15571
|
if (depth > (vendor ? 32 : 3))
|
|
@@ -15205,7 +15592,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15205
15592
|
if (isVendor && !options.includeVendor)
|
|
15206
15593
|
continue;
|
|
15207
15594
|
const child = join25(path, name);
|
|
15208
|
-
if (isVendor &&
|
|
15595
|
+
if (isVendor && lstatSync17(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
15209
15596
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
15210
15597
|
if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
|
|
15211
15598
|
scan.entries++;
|
|
@@ -15327,7 +15714,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
15327
15714
|
function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
15328
15715
|
const bundled = disableCodexBundledSkills(text2);
|
|
15329
15716
|
let result = bundled;
|
|
15330
|
-
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [
|
|
15717
|
+
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve14(bridgePath), resolve14(join25(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
|
|
15331
15718
|
const previousConfig = Bun.TOML.parse(bundled).skills?.config;
|
|
15332
15719
|
if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
|
|
15333
15720
|
throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
|
|
@@ -15339,7 +15726,7 @@ function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
|
15339
15726
|
result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
|
|
15340
15727
|
const parsed = Bun.TOML.parse(section);
|
|
15341
15728
|
const declared = parsed.skills?.config?.[0];
|
|
15342
|
-
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [
|
|
15729
|
+
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve14(path), resolve14(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
|
|
15343
15730
|
return section;
|
|
15344
15731
|
found = true;
|
|
15345
15732
|
return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
|
|
@@ -15369,9 +15756,9 @@ function planAgentIntegration(options) {
|
|
|
15369
15756
|
throw new Error("Invalid existing Skills bridge policy");
|
|
15370
15757
|
if (policy.bridge?.agents !== undefined && (!Array.isArray(policy.bridge.agents) || policy.bridge.agents.some((agent) => !INTEGRATION_AGENTS.includes(agent))))
|
|
15371
15758
|
throw new Error("Invalid existing bridge agent inventory");
|
|
15372
|
-
for (const
|
|
15373
|
-
if (policy.bridge?.[
|
|
15374
|
-
throw new Error(`Invalid existing bridge ${
|
|
15759
|
+
for (const field2 of ["commands", "profiles"])
|
|
15760
|
+
if (policy.bridge?.[field2] !== undefined && (!policy.bridge[field2] || typeof policy.bridge[field2] !== "object" || Array.isArray(policy.bridge[field2]) || Object.entries(policy.bridge[field2]).some(([key, value]) => !INTEGRATION_AGENTS.includes(key) || typeof value !== "string" || !value || value.includes("\x00"))))
|
|
15761
|
+
throw new Error(`Invalid existing bridge ${field2} binding`);
|
|
15375
15762
|
const priorAgents = policy.bridge?.agents ?? [];
|
|
15376
15763
|
const profileId = options.profileId ?? policy.profileId ?? "default";
|
|
15377
15764
|
const validateProfile = (value) => {
|
|
@@ -15457,7 +15844,7 @@ function planAgentIntegration(options) {
|
|
|
15457
15844
|
version: CLI_BRIDGE_VERSION,
|
|
15458
15845
|
digest: CLI_BRIDGE_DIGEST,
|
|
15459
15846
|
agents: [...new Set([...priorAgents, ...options.agents])].sort(),
|
|
15460
|
-
home:
|
|
15847
|
+
home: resolve14(home),
|
|
15461
15848
|
includeVendor: true,
|
|
15462
15849
|
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join25(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
|
|
15463
15850
|
commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
|
|
@@ -15474,7 +15861,7 @@ function planAgentIntegration(options) {
|
|
|
15474
15861
|
}
|
|
15475
15862
|
function atomicWrite2(path, content) {
|
|
15476
15863
|
assertSafePath(path);
|
|
15477
|
-
mkdirSync7(
|
|
15864
|
+
mkdirSync7(dirname14(path), { recursive: true, mode: 448 });
|
|
15478
15865
|
const temporary = `${path}.skills-${randomUUID3()}`;
|
|
15479
15866
|
try {
|
|
15480
15867
|
writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
|
|
@@ -15487,10 +15874,10 @@ function atomicWrite2(path, content) {
|
|
|
15487
15874
|
function applyAgentIntegration(plan) {
|
|
15488
15875
|
const policyPath = join25(plan.dataDir, "agent-policy.json");
|
|
15489
15876
|
assertSafePath(policyPath);
|
|
15490
|
-
const currentText = (path) =>
|
|
15877
|
+
const currentText = (path) => resolve14(path) === resolve14(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
15491
15878
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
15492
15879
|
for (const change of plan.changes)
|
|
15493
|
-
if (
|
|
15880
|
+
if (resolve14(change.path) === resolve14(policyPath))
|
|
15494
15881
|
parseManagedSkillPolicy(change.after);
|
|
15495
15882
|
const aliases = plan.rootAliases ?? [];
|
|
15496
15883
|
recheckRootAliases(aliases);
|
|
@@ -15520,7 +15907,7 @@ function applyAgentIntegration(plan) {
|
|
|
15520
15907
|
recheckRootAliases(aliases);
|
|
15521
15908
|
if (currentText(change.path) !== change.before)
|
|
15522
15909
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15523
|
-
for (let directory =
|
|
15910
|
+
for (let directory = dirname14(change.path);!existsSync11(directory); directory = dirname14(directory))
|
|
15524
15911
|
createdDirectories.add(directory);
|
|
15525
15912
|
const after = change.after;
|
|
15526
15913
|
atomicWrite2(change.path, after);
|
|
@@ -15560,11 +15947,11 @@ function applyAgentIntegration(plan) {
|
|
|
15560
15947
|
}
|
|
15561
15948
|
function syncArchiveDirectory(path) {
|
|
15562
15949
|
assertSafePath(path);
|
|
15563
|
-
const descriptor =
|
|
15950
|
+
const descriptor = openSync10(path, constants9.O_RDONLY | constants9.O_DIRECTORY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
15564
15951
|
try {
|
|
15565
|
-
|
|
15952
|
+
fsyncSync3(descriptor);
|
|
15566
15953
|
} finally {
|
|
15567
|
-
|
|
15954
|
+
closeSync10(descriptor);
|
|
15568
15955
|
}
|
|
15569
15956
|
}
|
|
15570
15957
|
function writeArchiveJournal(path, value) {
|
|
@@ -15572,18 +15959,18 @@ function writeArchiveJournal(path, value) {
|
|
|
15572
15959
|
const temporary = `${path}.skills-${randomUUID3()}`;
|
|
15573
15960
|
let descriptor;
|
|
15574
15961
|
try {
|
|
15575
|
-
descriptor =
|
|
15962
|
+
descriptor = openSync10(temporary, constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
|
|
15576
15963
|
writeFileSync5(descriptor, `${JSON.stringify(value)}
|
|
15577
15964
|
`);
|
|
15578
|
-
|
|
15579
|
-
|
|
15965
|
+
fsyncSync3(descriptor);
|
|
15966
|
+
closeSync10(descriptor);
|
|
15580
15967
|
descriptor = undefined;
|
|
15581
15968
|
renameSync4(temporary, path);
|
|
15582
|
-
syncArchiveDirectory(
|
|
15969
|
+
syncArchiveDirectory(dirname14(path));
|
|
15583
15970
|
} finally {
|
|
15584
15971
|
if (descriptor !== undefined)
|
|
15585
|
-
|
|
15586
|
-
if (
|
|
15972
|
+
closeSync10(descriptor);
|
|
15973
|
+
if (lstatSync17(temporary, { throwIfNoEntry: false }))
|
|
15587
15974
|
unlinkSync4(temporary);
|
|
15588
15975
|
}
|
|
15589
15976
|
}
|
|
@@ -15616,7 +16003,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15616
16003
|
const moved = [];
|
|
15617
16004
|
const verifyArchive = (entry) => {
|
|
15618
16005
|
assertSafePath(entry.archive);
|
|
15619
|
-
const stat2 =
|
|
16006
|
+
const stat2 = lstatSync17(entry.archive);
|
|
15620
16007
|
if (entry.discoveryOnly ? !stat2.isFile() : !stat2.isDirectory())
|
|
15621
16008
|
throw new Error("Archive type changed");
|
|
15622
16009
|
if ((entry.discoveryOnly ? sha(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -15624,14 +16011,14 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15624
16011
|
};
|
|
15625
16012
|
assertSafePath(archiveRoot);
|
|
15626
16013
|
const created = [];
|
|
15627
|
-
for (let path = archiveRoot;!existsSync11(path); path =
|
|
16014
|
+
for (let path = archiveRoot;!existsSync11(path); path = dirname14(path))
|
|
15628
16015
|
created.push(path);
|
|
15629
16016
|
mkdirSync7(archiveRoot, { recursive: true, mode: 448 });
|
|
15630
16017
|
try {
|
|
15631
16018
|
for (const directory of created)
|
|
15632
16019
|
syncArchiveDirectory(directory);
|
|
15633
16020
|
if (created.length)
|
|
15634
|
-
syncArchiveDirectory(
|
|
16021
|
+
syncArchiveDirectory(dirname14(created.at(-1)));
|
|
15635
16022
|
writeArchiveJournal(receiptPath, journal);
|
|
15636
16023
|
for (const [index, entry] of selected.entries()) {
|
|
15637
16024
|
recheckRootAliases(aliases);
|
|
@@ -15643,7 +16030,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15643
16030
|
writeArchiveJournal(receiptPath, journal);
|
|
15644
16031
|
renameSync4(move.source, move.archive);
|
|
15645
16032
|
moved.push(move);
|
|
15646
|
-
syncArchiveDirectory(
|
|
16033
|
+
syncArchiveDirectory(dirname14(move.source));
|
|
15647
16034
|
syncArchiveDirectory(archiveRoot);
|
|
15648
16035
|
verifyArchive(move);
|
|
15649
16036
|
move.status = "archived";
|
|
@@ -15664,7 +16051,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15664
16051
|
recheckRootAliases(aliases);
|
|
15665
16052
|
assertSafePath(entry.source);
|
|
15666
16053
|
assertSafePath(entry.archive);
|
|
15667
|
-
if (
|
|
16054
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
15668
16055
|
entry.status = "recovery-required";
|
|
15669
16056
|
entry.conflict = "source-occupied";
|
|
15670
16057
|
continue;
|
|
@@ -15678,10 +16065,10 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15678
16065
|
}
|
|
15679
16066
|
if (entry.discoveryOnly) {
|
|
15680
16067
|
linkSync2(entry.archive, entry.source);
|
|
15681
|
-
syncArchiveDirectory(
|
|
16068
|
+
syncArchiveDirectory(dirname14(entry.source));
|
|
15682
16069
|
unlinkSync4(entry.archive);
|
|
15683
16070
|
} else {
|
|
15684
|
-
if (
|
|
16071
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
15685
16072
|
entry.status = "recovery-required";
|
|
15686
16073
|
entry.conflict = "source-occupied";
|
|
15687
16074
|
continue;
|
|
@@ -15689,7 +16076,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15689
16076
|
renameSync4(entry.archive, entry.source);
|
|
15690
16077
|
}
|
|
15691
16078
|
entry.status = "restored";
|
|
15692
|
-
syncArchiveDirectory(
|
|
16079
|
+
syncArchiveDirectory(dirname14(entry.source));
|
|
15693
16080
|
syncArchiveDirectory(archiveRoot);
|
|
15694
16081
|
} catch {
|
|
15695
16082
|
entry.status = "recovery-required";
|
|
@@ -15728,7 +16115,7 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
15728
16115
|
function hasSkillsOwnershipMarker(dir) {
|
|
15729
16116
|
const path = join26(dir, SYNC_MARKER_FILE);
|
|
15730
16117
|
try {
|
|
15731
|
-
if (!
|
|
16118
|
+
if (!lstatSync18(path).isFile())
|
|
15732
16119
|
return false;
|
|
15733
16120
|
const marker = JSON.parse(readFileSync15(path, "utf8"));
|
|
15734
16121
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -15954,9 +16341,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
15954
16341
|
const action = dirExists ? "update" : "create";
|
|
15955
16342
|
if (options.dryRun)
|
|
15956
16343
|
return { action, path: skillMdPath };
|
|
15957
|
-
const parentDir =
|
|
16344
|
+
const parentDir = dirname15(dir);
|
|
15958
16345
|
mkdirSync8(parentDir, { recursive: true });
|
|
15959
|
-
const transactionDir = mkdtempSync2(join26(parentDir, `.hasna-skills-write-${
|
|
16346
|
+
const transactionDir = mkdtempSync2(join26(parentDir, `.hasna-skills-write-${basename7(dir)}-`));
|
|
15960
16347
|
const candidateDir = join26(transactionDir, "candidate");
|
|
15961
16348
|
const backupDir = join26(transactionDir, "backup");
|
|
15962
16349
|
const candidateSkillMdPath = join26(candidateDir, "SKILL.md");
|
|
@@ -16445,7 +16832,7 @@ function loadProjectConfigCompat(targetDir) {
|
|
|
16445
16832
|
return loadProjectConfig(targetDir);
|
|
16446
16833
|
}
|
|
16447
16834
|
// src/lib/run-state.ts
|
|
16448
|
-
import { createHash as
|
|
16835
|
+
import { createHash as createHash14, randomBytes } from "crypto";
|
|
16449
16836
|
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync18, readdirSync as readdirSync12, statSync as statSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
16450
16837
|
import { extname, join as join29, relative as relative6 } from "path";
|
|
16451
16838
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
@@ -16572,7 +16959,7 @@ function collectRunArtifacts(context) {
|
|
|
16572
16959
|
artifacts.push({
|
|
16573
16960
|
path: toProjectRelative(context.targetDir, path),
|
|
16574
16961
|
mime: mimeForPath(path),
|
|
16575
|
-
sha256:
|
|
16962
|
+
sha256: createHash14("sha256").update(bytes).digest("hex"),
|
|
16576
16963
|
sizeBytes: stat2.size
|
|
16577
16964
|
});
|
|
16578
16965
|
}
|
|
@@ -16700,7 +17087,7 @@ __export(exports_external, {
|
|
|
16700
17087
|
pipe: () => pipe,
|
|
16701
17088
|
partialRecord: () => partialRecord,
|
|
16702
17089
|
parseAsync: () => parseAsync2,
|
|
16703
|
-
parse: () =>
|
|
17090
|
+
parse: () => parse8,
|
|
16704
17091
|
overwrite: () => _overwrite,
|
|
16705
17092
|
output: () => output,
|
|
16706
17093
|
optional: () => optional,
|
|
@@ -16920,7 +17307,7 @@ __export(exports_core2, {
|
|
|
16920
17307
|
prettifyError: () => prettifyError,
|
|
16921
17308
|
parseURLObject: () => parseURLObject,
|
|
16922
17309
|
parseAsync: () => parseAsync,
|
|
16923
|
-
parse: () =>
|
|
17310
|
+
parse: () => parse6,
|
|
16924
17311
|
meta: () => meta,
|
|
16925
17312
|
mergeValues: () => mergeValues,
|
|
16926
17313
|
memoizer: () => memoizer,
|
|
@@ -18374,7 +18761,7 @@ var _parse = (_Err) => {
|
|
|
18374
18761
|
};
|
|
18375
18762
|
return fn;
|
|
18376
18763
|
};
|
|
18377
|
-
var
|
|
18764
|
+
var parse6 = /* @__PURE__ */ _parse($ZodRealError);
|
|
18378
18765
|
var _parseAsync = (_Err) => {
|
|
18379
18766
|
const fn = async (schema, value, _ctx, params) => {
|
|
18380
18767
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
@@ -18445,18 +18832,18 @@ var validateAsync = async (schema, value, _ctx) => {
|
|
|
18445
18832
|
return result.issues.length === 0;
|
|
18446
18833
|
};
|
|
18447
18834
|
var _encode = (_Err) => {
|
|
18448
|
-
const
|
|
18835
|
+
const parse7 = _parse(_Err);
|
|
18449
18836
|
const fn = (schema, value, _ctx, _params) => {
|
|
18450
18837
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
18451
|
-
return
|
|
18838
|
+
return parse7(schema, value, ctx, finalizeParams(fn, _params));
|
|
18452
18839
|
};
|
|
18453
18840
|
return fn;
|
|
18454
18841
|
};
|
|
18455
18842
|
var encode = /* @__PURE__ */ _encode($ZodRealError);
|
|
18456
18843
|
var _decode = (_Err) => {
|
|
18457
|
-
const
|
|
18844
|
+
const parse7 = _parse(_Err);
|
|
18458
18845
|
const fn = (schema, value, _ctx, _params) => {
|
|
18459
|
-
return
|
|
18846
|
+
return parse7(schema, value, _ctx, finalizeParams(fn, _params));
|
|
18460
18847
|
};
|
|
18461
18848
|
return fn;
|
|
18462
18849
|
};
|
|
@@ -20101,14 +20488,14 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
20101
20488
|
const shape = zod.def.shape;
|
|
20102
20489
|
const propValues = {};
|
|
20103
20490
|
for (const key in shape) {
|
|
20104
|
-
const
|
|
20105
|
-
if (
|
|
20491
|
+
const field2 = shape[key]._zod;
|
|
20492
|
+
if (field2.values) {
|
|
20106
20493
|
if (!Object.prototype.hasOwnProperty.call(propValues, key)) {
|
|
20107
20494
|
assignProp(propValues, key, new Set);
|
|
20108
20495
|
}
|
|
20109
|
-
for (const v of
|
|
20496
|
+
for (const v of field2.values)
|
|
20110
20497
|
propValues[key].add(v);
|
|
20111
|
-
if (
|
|
20498
|
+
if (field2.optin !== undefined)
|
|
20112
20499
|
propValues[key].add(undefined);
|
|
20113
20500
|
}
|
|
20114
20501
|
}
|
|
@@ -21379,10 +21766,10 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
|
|
|
21379
21766
|
throw new Error("implement() must be called with a function");
|
|
21380
21767
|
}
|
|
21381
21768
|
return Object.defineProperty(function(...args) {
|
|
21382
|
-
const parsedArgs = inst._def.input ?
|
|
21769
|
+
const parsedArgs = inst._def.input ? parse6(inst._def.input, args) : args;
|
|
21383
21770
|
const result = Reflect.apply(func, this, parsedArgs);
|
|
21384
21771
|
if (inst._def.output) {
|
|
21385
|
-
return
|
|
21772
|
+
return parse6(inst._def.output, result);
|
|
21386
21773
|
}
|
|
21387
21774
|
return result;
|
|
21388
21775
|
}, "_zod", { value: inst._zod, enumerable: false });
|
|
@@ -32301,11 +32688,11 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
32301
32688
|
}
|
|
32302
32689
|
const allKeys = new Set(Object.keys(shape));
|
|
32303
32690
|
const requiredKeys = new Set([...allKeys].filter((key) => {
|
|
32304
|
-
const
|
|
32691
|
+
const field2 = def.shape[key];
|
|
32305
32692
|
if (ctx.io === "input") {
|
|
32306
|
-
return inputOptin(
|
|
32693
|
+
return inputOptin(field2) === undefined;
|
|
32307
32694
|
} else {
|
|
32308
|
-
return
|
|
32695
|
+
return field2._zod.optout === undefined;
|
|
32309
32696
|
}
|
|
32310
32697
|
}));
|
|
32311
32698
|
if (requiredKeys.size > 0) {
|
|
@@ -33020,7 +33407,7 @@ var ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer2, undefi
|
|
|
33020
33407
|
});
|
|
33021
33408
|
|
|
33022
33409
|
// node_modules/zod/v4/classic/parse.js
|
|
33023
|
-
var
|
|
33410
|
+
var parse8 = /* @__PURE__ */ _parse(ZodRealError);
|
|
33024
33411
|
var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
33025
33412
|
var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
33026
33413
|
var safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -33156,7 +33543,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
33156
33543
|
exports_util.own(this, "~standard", value);
|
|
33157
33544
|
},
|
|
33158
33545
|
parse: function _parse2(data, params) {
|
|
33159
|
-
return
|
|
33546
|
+
return parse8(this, data, params, { callee: _parse2 });
|
|
33160
33547
|
},
|
|
33161
33548
|
parseAsync: async function _parseAsync2(data, params) {
|
|
33162
33549
|
return await parseAsync2(this, data, params, { callee: _parseAsync2 });
|
|
@@ -35987,7 +36374,7 @@ function clone2(value) {
|
|
|
35987
36374
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
35988
36375
|
}
|
|
35989
36376
|
// src/lib/canonical-json.ts
|
|
35990
|
-
import { createHash as
|
|
36377
|
+
import { createHash as createHash15 } from "crypto";
|
|
35991
36378
|
function canonicalJson2(value, maximumBytes = 2 * 1024 * 1024) {
|
|
35992
36379
|
return canonicalJsonAtDepth(value, maximumBytes, 64);
|
|
35993
36380
|
}
|
|
@@ -36031,7 +36418,7 @@ function canonicalJsonAtDepth(value, maximumBytes, maximumDepth) {
|
|
|
36031
36418
|
return visit3(value, 0);
|
|
36032
36419
|
}
|
|
36033
36420
|
function canonicalJsonSha256(value, maximumBytes) {
|
|
36034
|
-
return
|
|
36421
|
+
return createHash15("sha256").update(canonicalJson2(value, maximumBytes)).digest("hex");
|
|
36035
36422
|
}
|
|
36036
36423
|
|
|
36037
36424
|
// src/lib/remote-recurring-contract.ts
|
|
@@ -36913,15 +37300,41 @@ function object5(value) {
|
|
|
36913
37300
|
return value;
|
|
36914
37301
|
}
|
|
36915
37302
|
|
|
37303
|
+
// src/lib/remote-credit-checkout.ts
|
|
37304
|
+
var creditCheckoutKeyPattern = /^[A-Za-z0-9._:-]{8,255}$/;
|
|
37305
|
+
function creditCheckoutRequestKey(value) {
|
|
37306
|
+
if (value === undefined)
|
|
37307
|
+
return crypto.randomUUID();
|
|
37308
|
+
if (typeof value !== "string" || !creditCheckoutKeyPattern.test(value))
|
|
37309
|
+
throw new Error("Checkout idempotency key must be 8-255 URL-safe characters");
|
|
37310
|
+
return value;
|
|
37311
|
+
}
|
|
37312
|
+
var creditCheckoutMessages = {
|
|
37313
|
+
CREDIT_CHECKOUT_UNCONFIRMED: "Checkout creation could not be confirmed. Keep the request idempotency key, inspect billing, and use the same key for an explicit retry on the same server, account and pack. No automatic retry was made.",
|
|
37314
|
+
CREDIT_CHECKOUT_IN_PROGRESS: "This checkout is still being created. Wait before explicitly retrying the same server, account and pack with the same request idempotency key. No automatic retry was made.",
|
|
37315
|
+
CREDIT_CHECKOUT_EXPIRED: "This checkout has expired. Inspect billing before deliberately starting a new purchase with a new request idempotency key. Do not retry this checkout automatically.",
|
|
37316
|
+
CREDIT_CHECKOUT_FULFILLED: "This checkout is already fulfilled. Inspect the account balance; do not create another checkout to recover this purchase."
|
|
37317
|
+
};
|
|
37318
|
+
function creditCheckoutFailure(value, status, key) {
|
|
37319
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
37320
|
+
return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
|
|
37321
|
+
const row = value;
|
|
37322
|
+
if (row.requestIdempotencyKey !== undefined && row.requestIdempotencyKey !== key)
|
|
37323
|
+
return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
|
|
37324
|
+
const code = status === 409 && row.error === "credit checkout in_progress" ? "CREDIT_CHECKOUT_IN_PROGRESS" : status === 409 && row.error === "credit checkout expired" && row.requestIdempotencyKey === key ? "CREDIT_CHECKOUT_EXPIRED" : status === 409 && row.error === "credit checkout fulfilled" && row.requestIdempotencyKey === key ? "CREDIT_CHECKOUT_FULFILLED" : "CREDIT_CHECKOUT_UNCONFIRMED";
|
|
37325
|
+
const delay = row.retryAfterSeconds;
|
|
37326
|
+
return { code, ...code === "CREDIT_CHECKOUT_IN_PROGRESS" || status === 503 && row.error === "credit checkout creation unresolved" ? typeof delay === "number" && Number.isSafeInteger(delay) && delay > 0 && delay <= 3600 ? { retryAfterSeconds: delay } : {} : {} };
|
|
37327
|
+
}
|
|
37328
|
+
|
|
36916
37329
|
// src/lib/remote-profile.ts
|
|
36917
|
-
function customerNamePatch(input2,
|
|
36918
|
-
if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2,
|
|
37330
|
+
function customerNamePatch(input2, field2) {
|
|
37331
|
+
if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field2))
|
|
36919
37332
|
throw new Error("Provide only the requested name field.");
|
|
36920
|
-
const value = input2[
|
|
37333
|
+
const value = input2[field2];
|
|
36921
37334
|
if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
|
|
36922
37335
|
throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
|
|
36923
37336
|
}
|
|
36924
|
-
return { [
|
|
37337
|
+
return { [field2]: value.trim() };
|
|
36925
37338
|
}
|
|
36926
37339
|
function isRecord4(value) {
|
|
36927
37340
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -36966,10 +37379,10 @@ async function readQuoteUnavailableCode(response) {
|
|
|
36966
37379
|
let timer;
|
|
36967
37380
|
let deadlineExceeded = false;
|
|
36968
37381
|
const expired = Symbol("quote body deadline");
|
|
36969
|
-
const deadline = new Promise((
|
|
37382
|
+
const deadline = new Promise((resolve15) => {
|
|
36970
37383
|
timer = setTimeout(() => {
|
|
36971
37384
|
deadlineExceeded = true;
|
|
36972
|
-
|
|
37385
|
+
resolve15(expired);
|
|
36973
37386
|
reader.cancel().catch(() => {});
|
|
36974
37387
|
}, 1500);
|
|
36975
37388
|
});
|
|
@@ -37086,6 +37499,20 @@ class RemoteRequestError extends Error {
|
|
|
37086
37499
|
}
|
|
37087
37500
|
}
|
|
37088
37501
|
|
|
37502
|
+
class RemoteCreditCheckoutError extends RemoteRequestError {
|
|
37503
|
+
code;
|
|
37504
|
+
requestIdempotencyKey;
|
|
37505
|
+
retryAfterSeconds;
|
|
37506
|
+
constructor(code, status, requestIdempotencyKey, retryAfterSeconds) {
|
|
37507
|
+
super("/api/v1/billing/credits", status);
|
|
37508
|
+
this.code = code;
|
|
37509
|
+
this.requestIdempotencyKey = requestIdempotencyKey;
|
|
37510
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
37511
|
+
this.name = "RemoteCreditCheckoutError";
|
|
37512
|
+
this.message = creditCheckoutMessages[code];
|
|
37513
|
+
}
|
|
37514
|
+
}
|
|
37515
|
+
|
|
37089
37516
|
class RemoteQuoteUnavailableError extends RemoteRequestError {
|
|
37090
37517
|
code;
|
|
37091
37518
|
constructor(path, code) {
|
|
@@ -37574,14 +38001,43 @@ class RemoteSkillsClient {
|
|
|
37574
38001
|
async listCreditPacks() {
|
|
37575
38002
|
return parseRemoteCreditPacks(await (await this.requestNewRoute("/api/v1/billing/credits")).json());
|
|
37576
38003
|
}
|
|
37577
|
-
async createCreditCheckout(packId) {
|
|
38004
|
+
async createCreditCheckout(packId, options = {}) {
|
|
38005
|
+
const requestIdempotencyKey = creditCheckoutRequestKey(options.idempotencyKey);
|
|
37578
38006
|
const packs = await this.listCreditPacks();
|
|
37579
38007
|
if (!packs.some((pack) => pack.id === packId))
|
|
37580
38008
|
throw new Error("Choose a credit pack returned by skills credits packs");
|
|
37581
|
-
|
|
37582
|
-
|
|
37583
|
-
|
|
37584
|
-
|
|
38009
|
+
let response;
|
|
38010
|
+
try {
|
|
38011
|
+
response = await this.request("/api/v1/billing/credits", {
|
|
38012
|
+
method: "POST",
|
|
38013
|
+
body: JSON.stringify({ packId, idempotencyKey: requestIdempotencyKey })
|
|
38014
|
+
});
|
|
38015
|
+
} catch {
|
|
38016
|
+
throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", 0, requestIdempotencyKey);
|
|
38017
|
+
}
|
|
38018
|
+
if (response.status === 404 || response.status === 405) {
|
|
38019
|
+
response.body?.cancel().catch(() => {});
|
|
38020
|
+
throw new RemoteRouteUnsupportedError("/api/v1/billing/credits", response.status, this.apiUrl);
|
|
38021
|
+
}
|
|
38022
|
+
let value;
|
|
38023
|
+
try {
|
|
38024
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 4096)));
|
|
38025
|
+
} catch {
|
|
38026
|
+
throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
|
|
38027
|
+
}
|
|
38028
|
+
if (!response.ok) {
|
|
38029
|
+
const failure = creditCheckoutFailure(value, response.status, requestIdempotencyKey);
|
|
38030
|
+
throw new RemoteCreditCheckoutError(failure.code, response.status, requestIdempotencyKey, failure.retryAfterSeconds);
|
|
38031
|
+
}
|
|
38032
|
+
try {
|
|
38033
|
+
const checkout = parseRemoteCheckout(value);
|
|
38034
|
+
const echo = value.requestIdempotencyKey;
|
|
38035
|
+
if (echo !== undefined && echo !== requestIdempotencyKey)
|
|
38036
|
+
throw new Error("checkout request key changed");
|
|
38037
|
+
return { ...checkout, requestIdempotencyKey };
|
|
38038
|
+
} catch {
|
|
38039
|
+
throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
|
|
38040
|
+
}
|
|
37585
38041
|
}
|
|
37586
38042
|
async getUsage() {
|
|
37587
38043
|
return this.arrayResponse("/api/v1/billing/usage");
|
|
@@ -37814,13 +38270,13 @@ class RemoteSkillsClient {
|
|
|
37814
38270
|
return normalizeUpdatedSincePage(await response.json());
|
|
37815
38271
|
}
|
|
37816
38272
|
}
|
|
37817
|
-
function requireOptionalString(record7,
|
|
37818
|
-
if (record7[
|
|
38273
|
+
function requireOptionalString(record7, field2) {
|
|
38274
|
+
if (record7[field2] === undefined)
|
|
37819
38275
|
return;
|
|
37820
|
-
if (typeof record7[
|
|
37821
|
-
throw new Error(`Remote payload did not match the expected contract (${
|
|
38276
|
+
if (typeof record7[field2] !== "string") {
|
|
38277
|
+
throw new Error(`Remote payload did not match the expected contract (${field2} must be a string when present)`);
|
|
37822
38278
|
}
|
|
37823
|
-
return record7[
|
|
38279
|
+
return record7[field2];
|
|
37824
38280
|
}
|
|
37825
38281
|
var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
37826
38282
|
function isVersionRecord(value) {
|
|
@@ -38154,7 +38610,7 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
38154
38610
|
saveSchedules(data, targetDir);
|
|
38155
38611
|
}
|
|
38156
38612
|
// src/lib/revision.ts
|
|
38157
|
-
import { createHash as
|
|
38613
|
+
import { createHash as createHash16 } from "crypto";
|
|
38158
38614
|
var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
38159
38615
|
function revisionIdOf(content) {
|
|
38160
38616
|
const canonical3 = JSON.stringify({
|
|
@@ -38170,14 +38626,14 @@ function revisionIdOf(content) {
|
|
|
38170
38626
|
bundleSha256: content.bundleSha256 ?? null,
|
|
38171
38627
|
bundleByteSize: content.bundleByteSize ?? null
|
|
38172
38628
|
});
|
|
38173
|
-
return
|
|
38629
|
+
return createHash16("sha256").update(canonical3).digest("hex");
|
|
38174
38630
|
}
|
|
38175
38631
|
function revisionIdOfRecord(record7) {
|
|
38176
38632
|
return revisionIdOf(record7);
|
|
38177
38633
|
}
|
|
38178
38634
|
// src/lib/pull.ts
|
|
38179
38635
|
import { existsSync as existsSync17, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync20, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
38180
|
-
import { dirname as
|
|
38636
|
+
import { dirname as dirname16, join as join31 } from "path";
|
|
38181
38637
|
|
|
38182
38638
|
// src/lib/skill-bundles.ts
|
|
38183
38639
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -38519,7 +38975,7 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
38519
38975
|
try {
|
|
38520
38976
|
for (const entry of entries) {
|
|
38521
38977
|
const destination = join31(staging, entry.path);
|
|
38522
|
-
mkdirSync12(
|
|
38978
|
+
mkdirSync12(dirname16(destination), { recursive: true });
|
|
38523
38979
|
writeFileSync10(destination, entry.bytes, { mode: entry.mode });
|
|
38524
38980
|
}
|
|
38525
38981
|
writePullMarker(staging, {
|
|
@@ -38613,7 +39069,7 @@ function splitNameVersion(raw) {
|
|
|
38613
39069
|
return version2 ? { name, version: version2 } : { name };
|
|
38614
39070
|
}
|
|
38615
39071
|
// src/lib/recurring-surface.ts
|
|
38616
|
-
import { isAbsolute as
|
|
39072
|
+
import { isAbsolute as isAbsolute15 } from "path";
|
|
38617
39073
|
|
|
38618
39074
|
// src/lib/remote-invitation-recovery.ts
|
|
38619
39075
|
class InvitationEmailInputError extends Error {
|
|
@@ -38711,7 +39167,7 @@ async function requestInvitationEmail(origin, action, input2) {
|
|
|
38711
39167
|
}
|
|
38712
39168
|
|
|
38713
39169
|
// src/lib/remote-private-publications.ts
|
|
38714
|
-
import { createHash as
|
|
39170
|
+
import { createHash as createHash17 } from "crypto";
|
|
38715
39171
|
var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
|
|
38716
39172
|
var failures2 = {
|
|
38717
39173
|
INVALID_REQUEST: [400, "The publication request is invalid."],
|
|
@@ -38758,7 +39214,7 @@ var hash3 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
|
|
|
38758
39214
|
var record8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
38759
39215
|
var exact = (v, keys) => record8(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
|
|
38760
39216
|
var date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v));
|
|
38761
|
-
var publicationSha256 = (bytes) =>
|
|
39217
|
+
var publicationSha256 = (bytes) => createHash17("sha256").update(bytes).digest("hex");
|
|
38762
39218
|
function checkedPublicationDeclaration(value) {
|
|
38763
39219
|
if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash3(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
|
|
38764
39220
|
return bad();
|
|
@@ -39003,12 +39459,12 @@ class RemotePrivatePublicationsClient {
|
|
|
39003
39459
|
previous = view;
|
|
39004
39460
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
39005
39461
|
return view;
|
|
39006
|
-
await new Promise((
|
|
39462
|
+
await new Promise((resolve15) => {
|
|
39007
39463
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
39008
39464
|
function done() {
|
|
39009
39465
|
clearTimeout(timer);
|
|
39010
39466
|
options.signal?.removeEventListener("abort", done);
|
|
39011
|
-
|
|
39467
|
+
resolve15();
|
|
39012
39468
|
}
|
|
39013
39469
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
39014
39470
|
});
|
|
@@ -39272,7 +39728,7 @@ var stateSchema = exports_external.object({
|
|
|
39272
39728
|
var uuid13 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
|
|
39273
39729
|
var context = { userId: uuid13.optional(), membershipId: uuid13.optional() };
|
|
39274
39730
|
var page2 = { limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().min(1).max(2048).optional() };
|
|
39275
|
-
var directory = exports_external.string().max(4096).refine(
|
|
39731
|
+
var directory = exports_external.string().max(4096).refine(isAbsolute15, "Use an absolute recovery directory");
|
|
39276
39732
|
var email4 = exports_external.string().trim().toLowerCase().email().max(254);
|
|
39277
39733
|
var code = exports_external.string().regex(/^\d{6}$/);
|
|
39278
39734
|
var recurringSurfaceSchemas = {
|
|
@@ -39423,7 +39879,7 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
39423
39879
|
}
|
|
39424
39880
|
// src/lib/registry-sync.ts
|
|
39425
39881
|
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
39426
|
-
import { dirname as
|
|
39882
|
+
import { dirname as dirname17, relative as relative7 } from "path";
|
|
39427
39883
|
function createRegistrySyncArtifact(options = {}) {
|
|
39428
39884
|
const profile = options.profile ?? "all";
|
|
39429
39885
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -39480,7 +39936,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
39480
39936
|
};
|
|
39481
39937
|
}
|
|
39482
39938
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
39483
|
-
mkdirSync13(
|
|
39939
|
+
mkdirSync13(dirname17(path), { recursive: true });
|
|
39484
39940
|
writeFileSync11(path, `${JSON.stringify(artifact, null, 2)}
|
|
39485
39941
|
`);
|
|
39486
39942
|
}
|
|
@@ -39498,7 +39954,7 @@ var REMOTE_CUSTOMER_OPERATIONS = [
|
|
|
39498
39954
|
{ name: "list_remote_skills", title: "List Remote Skills", parameter: null, mutates: false, invoke: (client2) => client2.listSkills() },
|
|
39499
39955
|
{ name: "get_billing_status", title: "Get Billing Status", parameter: null, mutates: false, invoke: (client2) => client2.getBillingStatus() },
|
|
39500
39956
|
{ name: "list_credit_packs", title: "List Credit Packs", parameter: null, mutates: false, invoke: (client2) => client2.listCreditPacks() },
|
|
39501
|
-
{ name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client2, value) => client2.createCreditCheckout(value) },
|
|
39957
|
+
{ name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client2, value, options) => client2.createCreditCheckout(value, options) },
|
|
39502
39958
|
{ name: "get_billing_usage", title: "Get Billing Usage", parameter: null, mutates: false, invoke: (client2) => client2.getUsage() },
|
|
39503
39959
|
{ name: "list_invoices", title: "List Invoices", parameter: null, mutates: false, invoke: (client2) => client2.listInvoices() },
|
|
39504
39960
|
{ name: "create_billing_checkout", title: "Create Billing Checkout", parameter: null, mutates: true, invoke: (client2) => client2.createBillingCheckout() },
|
|
@@ -40320,11 +40776,11 @@ var remoteCustomerContracts = REMOTE_CUSTOMER_OPERATIONS.map((operation) => ({
|
|
|
40320
40776
|
name: operation.name,
|
|
40321
40777
|
title: operation.title,
|
|
40322
40778
|
description: `${operation.title} on the configured server; unavailable capabilities fail explicitly.`,
|
|
40323
|
-
params: operation.parameter ? [operation.parameter] : [],
|
|
40779
|
+
params: operation.parameter ? [operation.parameter, ...operation.name === "create_credit_checkout" ? ["idempotency_key?"] : []] : [],
|
|
40324
40780
|
category: "execution",
|
|
40325
40781
|
sideEffects: operation.mutates ? "local-process-or-remote-run" : "none",
|
|
40326
40782
|
stable: true,
|
|
40327
|
-
inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier.") } : {}, operation.parameter ? [operation.parameter] : []),
|
|
40783
|
+
inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier."), ...operation.name === "create_credit_checkout" ? { idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{8,255}$", description: "Caller-owned key retained before submission; reuse only for explicit recovery of the same server/account/pack." } } : {} } : {}, operation.parameter ? [operation.parameter] : []),
|
|
40328
40784
|
outputSchema: { oneOf: [objectSchema({}, [], "Server response.", true), { type: "array", items: objectSchema({}, [], "Server record.", true) }] }
|
|
40329
40785
|
}));
|
|
40330
40786
|
remoteCustomerContracts.push({
|
|
@@ -40565,14 +41021,14 @@ function clone3(value) {
|
|
|
40565
41021
|
}
|
|
40566
41022
|
// src/lib/feedback.ts
|
|
40567
41023
|
import { appendFileSync, existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
|
|
40568
|
-
import { dirname as
|
|
41024
|
+
import { dirname as dirname18, join as join32 } from "path";
|
|
40569
41025
|
import { Database } from "bun:sqlite";
|
|
40570
41026
|
function getFeedbackDbPath() {
|
|
40571
41027
|
return join32(getDataDir(), "skills.db");
|
|
40572
41028
|
}
|
|
40573
41029
|
function getFeedbackDb() {
|
|
40574
41030
|
const dbPath = getFeedbackDbPath();
|
|
40575
|
-
const dir =
|
|
41031
|
+
const dir = dirname18(dbPath);
|
|
40576
41032
|
if (!existsSync18(dir))
|
|
40577
41033
|
mkdirSync14(dir, { recursive: true });
|
|
40578
41034
|
const db = new Database(dbPath);
|
|
@@ -40601,7 +41057,7 @@ function saveFeedback(input2) {
|
|
|
40601
41057
|
const category = input2.category ?? "general";
|
|
40602
41058
|
if (isApiMode()) {
|
|
40603
41059
|
const path = join32(getDataDir(), "feedback.jsonl");
|
|
40604
|
-
const dir =
|
|
41060
|
+
const dir = dirname18(path);
|
|
40605
41061
|
if (!existsSync18(dir))
|
|
40606
41062
|
mkdirSync14(dir, { recursive: true });
|
|
40607
41063
|
appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
@@ -40624,7 +41080,7 @@ function isApiMode(env = process.env) {
|
|
|
40624
41080
|
}
|
|
40625
41081
|
}
|
|
40626
41082
|
// src/lib/native-storage.ts
|
|
40627
|
-
import { createHash as
|
|
41083
|
+
import { createHash as createHash18, createHmac as createHmac2 } from "crypto";
|
|
40628
41084
|
import {
|
|
40629
41085
|
existsSync as existsSync19,
|
|
40630
41086
|
mkdirSync as mkdirSync15,
|
|
@@ -40633,7 +41089,7 @@ import {
|
|
|
40633
41089
|
statSync as statSync11,
|
|
40634
41090
|
writeFileSync as writeFileSync12
|
|
40635
41091
|
} from "fs";
|
|
40636
|
-
import { dirname as
|
|
41092
|
+
import { dirname as dirname19, join as join33, normalize as normalize3, relative as relative8, sep as sep7 } from "path";
|
|
40637
41093
|
var SKILLS_STORAGE_TABLES = [
|
|
40638
41094
|
"skills_sync_records",
|
|
40639
41095
|
"skills_sync_cursors"
|
|
@@ -40800,7 +41256,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
40800
41256
|
files.push({
|
|
40801
41257
|
path: relativePath,
|
|
40802
41258
|
sizeBytes: bytes.byteLength,
|
|
40803
|
-
sha256:
|
|
41259
|
+
sha256: createHash18("sha256").update(bytes).digest("hex"),
|
|
40804
41260
|
...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
|
|
40805
41261
|
});
|
|
40806
41262
|
}
|
|
@@ -40819,18 +41275,18 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
|
|
|
40819
41275
|
skipped += 1;
|
|
40820
41276
|
continue;
|
|
40821
41277
|
}
|
|
40822
|
-
const
|
|
40823
|
-
if (existsSync19(
|
|
41278
|
+
const absolutePath2 = resolveSnapshotPath(targetDir, file2.path);
|
|
41279
|
+
if (existsSync19(absolutePath2) && !options.overwrite) {
|
|
40824
41280
|
skipped += 1;
|
|
40825
41281
|
continue;
|
|
40826
41282
|
}
|
|
40827
41283
|
const bytes = Buffer.from(file2.contentBase64, "base64");
|
|
40828
|
-
const hash4 =
|
|
41284
|
+
const hash4 = createHash18("sha256").update(bytes).digest("hex");
|
|
40829
41285
|
if (hash4 !== file2.sha256) {
|
|
40830
41286
|
throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
|
|
40831
41287
|
}
|
|
40832
|
-
mkdirSync15(
|
|
40833
|
-
writeFileSync12(
|
|
41288
|
+
mkdirSync15(dirname19(absolutePath2), { recursive: true });
|
|
41289
|
+
writeFileSync12(absolutePath2, bytes);
|
|
40834
41290
|
written += 1;
|
|
40835
41291
|
}
|
|
40836
41292
|
return { written, skipped };
|
|
@@ -41157,7 +41613,7 @@ function toIsoString(value) {
|
|
|
41157
41613
|
return Number.isNaN(date6.getTime()) ? value : date6.toISOString();
|
|
41158
41614
|
}
|
|
41159
41615
|
function sha256Hex2(value) {
|
|
41160
|
-
return
|
|
41616
|
+
return createHash18("sha256").update(value).digest("hex");
|
|
41161
41617
|
}
|
|
41162
41618
|
function normalizeHeaders(headers) {
|
|
41163
41619
|
const result = {};
|
|
@@ -41209,7 +41665,7 @@ function toArrayBuffer(bytes) {
|
|
|
41209
41665
|
return buffer;
|
|
41210
41666
|
}
|
|
41211
41667
|
// src/lib/station-snapshot.ts
|
|
41212
|
-
import { createHash as
|
|
41668
|
+
import { createHash as createHash19 } from "crypto";
|
|
41213
41669
|
import {
|
|
41214
41670
|
copyFileSync,
|
|
41215
41671
|
mkdirSync as mkdirSync16,
|
|
@@ -41217,7 +41673,7 @@ import {
|
|
|
41217
41673
|
statSync as statSync13,
|
|
41218
41674
|
writeFileSync as writeFileSync13
|
|
41219
41675
|
} from "fs";
|
|
41220
|
-
import { dirname as
|
|
41676
|
+
import { dirname as dirname20, isAbsolute as isAbsolute16, relative as relative9, resolve as resolve15, sep as sep9 } from "path";
|
|
41221
41677
|
|
|
41222
41678
|
// src/lib/portable-snapshot-filter.ts
|
|
41223
41679
|
import { readdirSync as readdirSync14, statSync as statSync12 } from "fs";
|
|
@@ -41384,7 +41840,7 @@ function validateStationId(stationId) {
|
|
|
41384
41840
|
}
|
|
41385
41841
|
}
|
|
41386
41842
|
function sha256File(filePath) {
|
|
41387
|
-
return
|
|
41843
|
+
return createHash19("sha256").update(readFileSync23(filePath)).digest("hex");
|
|
41388
41844
|
}
|
|
41389
41845
|
function scanHome(definition, homesRoot) {
|
|
41390
41846
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -41455,7 +41911,7 @@ function humanHomes(scanned) {
|
|
|
41455
41911
|
}));
|
|
41456
41912
|
}
|
|
41457
41913
|
function writeStationSnapshot(options) {
|
|
41458
|
-
const repoRoot =
|
|
41914
|
+
const repoRoot = resolve15(options.repoRoot ?? process.cwd());
|
|
41459
41915
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
41460
41916
|
const manifestFiles = plans.map((plan) => ({
|
|
41461
41917
|
relativePath: plan.source.relativePath,
|
|
@@ -41481,9 +41937,9 @@ function writeStationSnapshot(options) {
|
|
|
41481
41937
|
const conflicts = [];
|
|
41482
41938
|
const untouched = [];
|
|
41483
41939
|
for (const plan of plans) {
|
|
41484
|
-
const destination =
|
|
41940
|
+
const destination = resolve15(repoRoot, plan.destination);
|
|
41485
41941
|
const destinationRelative = relative9(repoRoot, destination);
|
|
41486
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep9) ||
|
|
41942
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep9) || isAbsolute16(destinationRelative)) {
|
|
41487
41943
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
41488
41944
|
}
|
|
41489
41945
|
let existingDigest = null;
|
|
@@ -41504,8 +41960,8 @@ function writeStationSnapshot(options) {
|
|
|
41504
41960
|
}
|
|
41505
41961
|
let written = 0;
|
|
41506
41962
|
for (const plan of untouched) {
|
|
41507
|
-
const destination =
|
|
41508
|
-
mkdirSync16(
|
|
41963
|
+
const destination = resolve15(repoRoot, plan.destination);
|
|
41964
|
+
mkdirSync16(dirname20(destination), { recursive: true });
|
|
41509
41965
|
copyFileSync(plan.source.fullPath, destination);
|
|
41510
41966
|
written += 1;
|
|
41511
41967
|
}
|
|
@@ -41523,8 +41979,8 @@ function writeStationSnapshot(options) {
|
|
|
41523
41979
|
},
|
|
41524
41980
|
files: manifestFiles
|
|
41525
41981
|
};
|
|
41526
|
-
const manifestPath =
|
|
41527
|
-
mkdirSync16(
|
|
41982
|
+
const manifestPath = resolve15(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
41983
|
+
mkdirSync16(dirname20(manifestPath), { recursive: true });
|
|
41528
41984
|
writeFileSync13(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
41529
41985
|
`);
|
|
41530
41986
|
return {
|
|
@@ -41535,7 +41991,7 @@ function writeStationSnapshot(options) {
|
|
|
41535
41991
|
};
|
|
41536
41992
|
}
|
|
41537
41993
|
// src/lib/station-hydrate.ts
|
|
41538
|
-
import { createHash as
|
|
41994
|
+
import { createHash as createHash20 } from "crypto";
|
|
41539
41995
|
import {
|
|
41540
41996
|
copyFileSync as copyFileSync2,
|
|
41541
41997
|
mkdirSync as mkdirSync17,
|
|
@@ -41544,7 +42000,7 @@ import {
|
|
|
41544
42000
|
statSync as statSync14,
|
|
41545
42001
|
writeFileSync as writeFileSync14
|
|
41546
42002
|
} from "fs";
|
|
41547
|
-
import { dirname as
|
|
42003
|
+
import { dirname as dirname21, join as join35, resolve as resolve16, sep as sep10 } from "path";
|
|
41548
42004
|
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
|
|
41549
42005
|
var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
41550
42006
|
var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
@@ -41741,12 +42197,12 @@ function skillSha256(skill) {
|
|
|
41741
42197
|
return sha256File(skill.files[0].winner.fullPath);
|
|
41742
42198
|
}
|
|
41743
42199
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
41744
|
-
return
|
|
42200
|
+
return createHash20("sha256").update(joined.sort().join(`
|
|
41745
42201
|
`)).digest("hex");
|
|
41746
42202
|
}
|
|
41747
42203
|
function writeStationHydration(options) {
|
|
41748
|
-
const repoRoot =
|
|
41749
|
-
const cacheRoot =
|
|
42204
|
+
const repoRoot = resolve16(options.repoRoot ?? process.cwd());
|
|
42205
|
+
const cacheRoot = resolve16(options.cacheRoot ?? resolveCorpusRoot());
|
|
41750
42206
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
41751
42207
|
const resultSkills = plan.winners.map((skill) => ({
|
|
41752
42208
|
ident: skill.ident,
|
|
@@ -41800,7 +42256,7 @@ function writeStationHydration(options) {
|
|
|
41800
42256
|
}
|
|
41801
42257
|
let written = 0;
|
|
41802
42258
|
for (const entry of toWrite) {
|
|
41803
|
-
mkdirSync17(
|
|
42259
|
+
mkdirSync17(dirname21(entry.destination), { recursive: true });
|
|
41804
42260
|
copyFileSync2(entry.fullPath, entry.destination);
|
|
41805
42261
|
written += 1;
|
|
41806
42262
|
}
|
|
@@ -41821,8 +42277,8 @@ function writeStationHydration(options) {
|
|
|
41821
42277
|
},
|
|
41822
42278
|
skills: resultSkills
|
|
41823
42279
|
};
|
|
41824
|
-
const hydrationManifestPath = join35(
|
|
41825
|
-
mkdirSync17(
|
|
42280
|
+
const hydrationManifestPath = join35(dirname21(cacheRoot), `hydration-${options.stationId}.json`);
|
|
42281
|
+
mkdirSync17(dirname21(hydrationManifestPath), { recursive: true });
|
|
41826
42282
|
writeFileSync14(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
41827
42283
|
`);
|
|
41828
42284
|
return {
|
|
@@ -41833,23 +42289,23 @@ function writeStationHydration(options) {
|
|
|
41833
42289
|
};
|
|
41834
42290
|
}
|
|
41835
42291
|
// src/lib/private-publication-recovery.ts
|
|
41836
|
-
import { constants as
|
|
41837
|
-
import { dirname as
|
|
42292
|
+
import { constants as constants10, closeSync as closeSync11, fsyncSync as fsyncSync4, fstatSync as fstatSync11, lstatSync as lstatSync19, mkdirSync as mkdirSync18, openSync as openSync11, readSync as readSync9, realpathSync as realpathSync6, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
|
|
42293
|
+
import { dirname as dirname22, isAbsolute as isAbsolute17, join as join36, resolve as resolve17 } from "path";
|
|
41838
42294
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
41839
42295
|
var fail2 = () => {
|
|
41840
42296
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
|
|
41841
42297
|
};
|
|
41842
42298
|
function safeDirectory(directory2) {
|
|
41843
|
-
if (!
|
|
42299
|
+
if (!isAbsolute17(directory2) || directory2 !== resolve17(directory2) || realpathSync6(directory2) !== directory2)
|
|
41844
42300
|
return fail2();
|
|
41845
|
-
for (let path = directory2;; path =
|
|
41846
|
-
const stat2 =
|
|
42301
|
+
for (let path = directory2;; path = dirname22(path)) {
|
|
42302
|
+
const stat2 = lstatSync19(path);
|
|
41847
42303
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
41848
42304
|
return fail2();
|
|
41849
|
-
if (path ===
|
|
42305
|
+
if (path === dirname22(path))
|
|
41850
42306
|
break;
|
|
41851
42307
|
}
|
|
41852
|
-
const own2 =
|
|
42308
|
+
const own2 = lstatSync19(directory2);
|
|
41853
42309
|
if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
|
|
41854
42310
|
return fail2();
|
|
41855
42311
|
return { dev: own2.dev, ino: own2.ino };
|
|
@@ -41861,35 +42317,35 @@ function unchangedDirectory(directory2, identity3) {
|
|
|
41861
42317
|
}
|
|
41862
42318
|
function readOwned(directory2, name, max) {
|
|
41863
42319
|
const identity3 = safeDirectory(directory2), file2 = join36(directory2, name);
|
|
41864
|
-
const fd =
|
|
42320
|
+
const fd = openSync11(file2, constants10.O_RDONLY | constants10.O_NOFOLLOW);
|
|
41865
42321
|
try {
|
|
41866
|
-
const stat2 =
|
|
42322
|
+
const stat2 = fstatSync11(fd);
|
|
41867
42323
|
if (!stat2.isFile() || stat2.nlink !== 1 || stat2.size > max || stat2.size < 1 || (stat2.mode & 63) !== 0 || process.getuid && stat2.uid !== process.getuid())
|
|
41868
42324
|
return fail2();
|
|
41869
42325
|
const buffer = Buffer.alloc(Math.min(max, stat2.size) + 1);
|
|
41870
42326
|
let length = 0;
|
|
41871
42327
|
while (length < buffer.length) {
|
|
41872
|
-
const count =
|
|
42328
|
+
const count = readSync9(fd, buffer, length, buffer.length - length, length);
|
|
41873
42329
|
if (count === 0)
|
|
41874
42330
|
break;
|
|
41875
42331
|
length += count;
|
|
41876
42332
|
}
|
|
41877
|
-
const bytes = buffer.subarray(0, length), after =
|
|
42333
|
+
const bytes = buffer.subarray(0, length), after = fstatSync11(fd);
|
|
41878
42334
|
unchangedDirectory(directory2, identity3);
|
|
41879
42335
|
if (bytes.length !== stat2.size || stat2.size !== after.size || stat2.mtimeMs !== after.mtimeMs || stat2.ctimeMs !== after.ctimeMs)
|
|
41880
42336
|
return fail2();
|
|
41881
42337
|
return bytes;
|
|
41882
42338
|
} finally {
|
|
41883
|
-
|
|
42339
|
+
closeSync11(fd);
|
|
41884
42340
|
}
|
|
41885
42341
|
}
|
|
41886
42342
|
function writeOwned(directory2, name, bytes) {
|
|
41887
|
-
const fd =
|
|
42343
|
+
const fd = openSync11(join36(directory2, name), constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW, 384);
|
|
41888
42344
|
try {
|
|
41889
42345
|
writeFileSync15(fd, bytes);
|
|
41890
|
-
|
|
42346
|
+
fsyncSync4(fd);
|
|
41891
42347
|
} finally {
|
|
41892
|
-
|
|
42348
|
+
closeSync11(fd);
|
|
41893
42349
|
}
|
|
41894
42350
|
}
|
|
41895
42351
|
function save(directory2, value) {
|
|
@@ -41898,11 +42354,11 @@ function save(directory2, value) {
|
|
|
41898
42354
|
`);
|
|
41899
42355
|
unchangedDirectory(directory2, identity3);
|
|
41900
42356
|
renameSync7(join36(directory2, temporary), join36(directory2, "receipt.json"));
|
|
41901
|
-
const fd =
|
|
42357
|
+
const fd = openSync11(directory2, constants10.O_RDONLY | constants10.O_NOFOLLOW);
|
|
41902
42358
|
try {
|
|
41903
|
-
|
|
42359
|
+
fsyncSync4(fd);
|
|
41904
42360
|
} finally {
|
|
41905
|
-
|
|
42361
|
+
closeSync11(fd);
|
|
41906
42362
|
}
|
|
41907
42363
|
}
|
|
41908
42364
|
function bind(client2, receipt) {
|
|
@@ -41913,20 +42369,20 @@ async function locked(directory2, action) {
|
|
|
41913
42369
|
const identity3 = safeDirectory(directory2), file2 = join36(directory2, "operation.lock");
|
|
41914
42370
|
let fd;
|
|
41915
42371
|
try {
|
|
41916
|
-
fd =
|
|
42372
|
+
fd = openSync11(file2, constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW, 384);
|
|
41917
42373
|
} catch {
|
|
41918
42374
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_BUSY", "Another operation holds this recovery directory. If it crashed, confirm that process has stopped before explicitly removing operation.lock and resuming.");
|
|
41919
42375
|
}
|
|
41920
|
-
const lock =
|
|
42376
|
+
const lock = fstatSync11(fd);
|
|
41921
42377
|
try {
|
|
41922
42378
|
writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
|
|
41923
42379
|
`);
|
|
41924
|
-
|
|
42380
|
+
fsyncSync4(fd);
|
|
41925
42381
|
return await action();
|
|
41926
42382
|
} finally {
|
|
41927
|
-
|
|
42383
|
+
closeSync11(fd);
|
|
41928
42384
|
unchangedDirectory(directory2, identity3);
|
|
41929
|
-
const now =
|
|
42385
|
+
const now = lstatSync19(file2);
|
|
41930
42386
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
41931
42387
|
fail2();
|
|
41932
42388
|
unlinkSync5(file2);
|
|
@@ -41978,15 +42434,15 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
|
|
|
41978
42434
|
phase: "prepared",
|
|
41979
42435
|
intent: null
|
|
41980
42436
|
};
|
|
41981
|
-
if (!
|
|
42437
|
+
if (!isAbsolute17(recoveryDirectory) || resolve17(recoveryDirectory) !== recoveryDirectory || realpathSync6(dirname22(recoveryDirectory)) !== dirname22(recoveryDirectory))
|
|
41982
42438
|
return fail2();
|
|
41983
42439
|
mkdirSync18(recoveryDirectory, { mode: 448 });
|
|
41984
42440
|
safeDirectory(recoveryDirectory);
|
|
41985
|
-
const parent =
|
|
42441
|
+
const parent = openSync11(dirname22(recoveryDirectory), constants10.O_RDONLY | constants10.O_NOFOLLOW);
|
|
41986
42442
|
try {
|
|
41987
|
-
|
|
42443
|
+
fsyncSync4(parent);
|
|
41988
42444
|
} finally {
|
|
41989
|
-
|
|
42445
|
+
closeSync11(parent);
|
|
41990
42446
|
}
|
|
41991
42447
|
writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
|
|
41992
42448
|
save(recoveryDirectory, receipt);
|
|
@@ -42069,8 +42525,8 @@ async function inspectLocked(client2, directory2, cancel) {
|
|
|
42069
42525
|
return privatePublicationResult(directory2, receipt);
|
|
42070
42526
|
}
|
|
42071
42527
|
// src/lib/skill-context.ts
|
|
42072
|
-
import { createHash as
|
|
42073
|
-
import { resolve as
|
|
42528
|
+
import { createHash as createHash21 } from "crypto";
|
|
42529
|
+
import { resolve as resolve18 } from "path";
|
|
42074
42530
|
function words(value) {
|
|
42075
42531
|
return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
42076
42532
|
}
|
|
@@ -42105,7 +42561,7 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
42105
42561
|
const promptWords = words(prompt);
|
|
42106
42562
|
const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
|
|
42107
42563
|
const loaded = new Set(resolved.session?.loaded ?? []);
|
|
42108
|
-
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd,
|
|
42564
|
+
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve18(input2.cwd)] : []];
|
|
42109
42565
|
const candidates = profile.selections.map((selection) => {
|
|
42110
42566
|
const key = selectionKey(selection);
|
|
42111
42567
|
if (input2.restore && loaded.has(key))
|
|
@@ -42158,7 +42614,7 @@ ${content}`;
|
|
|
42158
42614
|
`);
|
|
42159
42615
|
const receipt = {
|
|
42160
42616
|
schemaVersion: 1,
|
|
42161
|
-
id:
|
|
42617
|
+
id: createHash21("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
|
|
42162
42618
|
profileId: profile.profileId,
|
|
42163
42619
|
profileRevision: profile.profileRevision,
|
|
42164
42620
|
authority: profile.authority,
|
|
@@ -42170,9 +42626,105 @@ ${content}`;
|
|
|
42170
42626
|
selections
|
|
42171
42627
|
};
|
|
42172
42628
|
if (sessionId)
|
|
42173
|
-
|
|
42629
|
+
writeSkillSession({ ...resolved.receipt, sessionId, loaded: [...loaded] }, { current: resolved.sessionSnapshot, parent: resolved.parentSnapshot }, options);
|
|
42174
42630
|
return { context: context2, selections, omitted, receipt };
|
|
42175
42631
|
}
|
|
42632
|
+
// src/lib/session-reconciliation.ts
|
|
42633
|
+
import { createHash as createHash22 } from "crypto";
|
|
42634
|
+
var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
|
|
42635
|
+
var digest3 = (value) => createHash22("sha256").update(JSON.stringify(value)).digest("hex");
|
|
42636
|
+
function exactTimestamp(value) {
|
|
42637
|
+
const parsed = value === undefined ? Number.NaN : Date.parse(value);
|
|
42638
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
|
|
42639
|
+
throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "The reviewed plan must include its exact canonical issuedAt and expiresAt timestamps.");
|
|
42640
|
+
}
|
|
42641
|
+
return parsed;
|
|
42642
|
+
}
|
|
42643
|
+
function assertPlanWindow(issuedAt, expiresAt, now) {
|
|
42644
|
+
const issued = exactTimestamp(issuedAt), expires = exactTimestamp(expiresAt);
|
|
42645
|
+
if (expires - issued !== SESSION_RECONCILIATION_PLAN_TTL_MS) {
|
|
42646
|
+
throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "A session reconciliation approval is valid for exactly five minutes and cannot be extended by the caller.");
|
|
42647
|
+
}
|
|
42648
|
+
if (now < issued || now >= expires) {
|
|
42649
|
+
throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
|
|
42650
|
+
}
|
|
42651
|
+
}
|
|
42652
|
+
function inspectSkillSession(sessionId, options = {}) {
|
|
42653
|
+
const snapshot = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot;
|
|
42654
|
+
return {
|
|
42655
|
+
sessionId,
|
|
42656
|
+
path: snapshot.path,
|
|
42657
|
+
receiptSha256: snapshot.sha256,
|
|
42658
|
+
generation: snapshot.generation,
|
|
42659
|
+
verifiedAt: receipt.verifiedAt,
|
|
42660
|
+
authority: receipt.profile.authority,
|
|
42661
|
+
workspaceId: receipt.profile.workspaceId,
|
|
42662
|
+
profileId: receipt.profile.profileId,
|
|
42663
|
+
profileRevision: receipt.profile.profileRevision,
|
|
42664
|
+
selectionCount: receipt.profile.selections.length,
|
|
42665
|
+
loadedCount: receipt.loaded.length
|
|
42666
|
+
};
|
|
42667
|
+
}
|
|
42668
|
+
async function reconcileSkillSession(input2, options = {}) {
|
|
42669
|
+
if (![input2.fromProfile, input2.fromRevision, input2.selectionProfile, input2.profileRevision].every((value) => typeof value === "string" && value.trim()) || !/^[a-f0-9]{64}$/.test(input2.receiptSha256)) {
|
|
42670
|
+
throw new SkillSelectionError("INVALID_SESSION_RECONCILIATION", "Name the exact old receipt SHA256, old profile/revision and intended target profile/revision.");
|
|
42671
|
+
}
|
|
42672
|
+
if (input2.apply && (!input2.planDigest || !input2.planIssuedAt || !input2.planExpiresAt)) {
|
|
42673
|
+
throw new SkillSelectionError("SESSION_PLAN_REQUIRED", "Review the session reconciliation plan and provide its exact digest, issuedAt and expiresAt before applying it.");
|
|
42674
|
+
}
|
|
42675
|
+
if (!input2.apply && (input2.planDigest !== undefined || input2.planIssuedAt !== undefined || input2.planExpiresAt !== undefined)) {
|
|
42676
|
+
throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "Plan timestamps are minted by the planner and may only be replayed with --apply.");
|
|
42677
|
+
}
|
|
42678
|
+
const now = (options.now ?? Date.now)();
|
|
42679
|
+
const issuedAt = input2.apply ? input2.planIssuedAt : new Date(now).toISOString();
|
|
42680
|
+
const expiresAt = input2.apply ? input2.planExpiresAt : new Date(now + SESSION_RECONCILIATION_PLAN_TTL_MS).toISOString();
|
|
42681
|
+
if (input2.apply)
|
|
42682
|
+
assertPlanWindow(issuedAt, expiresAt, now);
|
|
42683
|
+
const snapshot = readSkillSessionSnapshot(input2.sessionId, options), old = snapshot.receipt;
|
|
42684
|
+
if (snapshot.sha256 !== input2.receiptSha256 || old.profile.profileId !== input2.fromProfile || old.profile.profileRevision !== input2.fromRevision) {
|
|
42685
|
+
throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The current session receipt does not match the reviewed old bytes, profile and revision.");
|
|
42686
|
+
}
|
|
42687
|
+
const client2 = options.client ?? await createProfileClient();
|
|
42688
|
+
const target = structuredClone(await client2.resolveProfile(input2.selectionProfile));
|
|
42689
|
+
validateResolvedProfile(target, client2.authority);
|
|
42690
|
+
if (target.profileId !== input2.selectionProfile || target.profileRevision !== input2.profileRevision) {
|
|
42691
|
+
throw new SkillSelectionError("SESSION_TARGET_CHANGED", "The API selection profile does not match the intended target revision; review the current selection before proceeding.");
|
|
42692
|
+
}
|
|
42693
|
+
if (old.profile.authority !== target.authority || old.profile.workspaceId !== target.workspaceId) {
|
|
42694
|
+
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "A session reconciliation cannot change its Skills authority or workspace.");
|
|
42695
|
+
}
|
|
42696
|
+
const targetKeys = new Set(target.selections.map(selectionKey));
|
|
42697
|
+
const loaded = old.loaded.filter((key) => targetKeys.has(key));
|
|
42698
|
+
const replacementGeneration = nextSkillSessionGeneration(snapshot.generation);
|
|
42699
|
+
const plan = {
|
|
42700
|
+
schemaVersion: 1,
|
|
42701
|
+
issuedAt,
|
|
42702
|
+
expiresAt,
|
|
42703
|
+
sessionId: input2.sessionId,
|
|
42704
|
+
sessionPath: snapshot.path,
|
|
42705
|
+
before: { receiptSha256: snapshot.sha256, generation: snapshot.generation, profileId: old.profile.profileId, profileRevision: old.profile.profileRevision, selectionCount: old.profile.selections.length },
|
|
42706
|
+
target: { authority: target.authority, workspaceId: target.workspaceId, profileId: target.profileId, profileRevision: target.profileRevision, profileSha256: digest3(target), selectionCount: target.selections.length },
|
|
42707
|
+
replacementGeneration,
|
|
42708
|
+
retainedLoadedCount: loaded.length,
|
|
42709
|
+
retiredLoadedCount: old.loaded.length - loaded.length,
|
|
42710
|
+
scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
|
|
42711
|
+
};
|
|
42712
|
+
const planDigest = digest3(plan);
|
|
42713
|
+
if (input2.planDigest !== undefined && input2.planDigest !== planDigest)
|
|
42714
|
+
throw new SkillSelectionError("SESSION_PLAN_CHANGED", "The session reconciliation plan differs from the reviewed plan.");
|
|
42715
|
+
if (!input2.apply)
|
|
42716
|
+
return { applied: false, plan, planDigest, archivePath: undefined, receiptPath: undefined, beforeSha256: snapshot.sha256, afterSha256: undefined };
|
|
42717
|
+
assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)());
|
|
42718
|
+
const replacement = {
|
|
42719
|
+
...old,
|
|
42720
|
+
generation: replacementGeneration,
|
|
42721
|
+
verifiedAt: new Date((options.now ?? Date.now)()).toISOString(),
|
|
42722
|
+
profile: target,
|
|
42723
|
+
loaded
|
|
42724
|
+
};
|
|
42725
|
+
const result = replaceSkillSession(skillSessionSnapshotBinding(snapshot), replacement, { ...plan, planDigest }, options, () => assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)()));
|
|
42726
|
+
return { applied: true, plan, planDigest, ...result };
|
|
42727
|
+
}
|
|
42176
42728
|
export {
|
|
42177
42729
|
writeStationSnapshot,
|
|
42178
42730
|
writeStationHydration,
|
|
@@ -42237,6 +42789,7 @@ export {
|
|
|
42237
42789
|
removeSchedule,
|
|
42238
42790
|
removeManagedAgentSkill,
|
|
42239
42791
|
recordScheduleRun,
|
|
42792
|
+
reconcileSkillSession,
|
|
42240
42793
|
readPrivatePublicationRecovery,
|
|
42241
42794
|
readPortableSkillManifest,
|
|
42242
42795
|
pullSkills,
|
|
@@ -42292,6 +42845,7 @@ export {
|
|
|
42292
42845
|
installSkillManifest,
|
|
42293
42846
|
installSkillForAgent,
|
|
42294
42847
|
installSkill,
|
|
42848
|
+
inspectSkillSession,
|
|
42295
42849
|
inspectPrivatePublication,
|
|
42296
42850
|
importSkillsLocalSnapshot,
|
|
42297
42851
|
homePathFor,
|
|
@@ -42367,6 +42921,7 @@ export {
|
|
|
42367
42921
|
captureDiscoveryPathSources,
|
|
42368
42922
|
captureDiscoveryDirectories,
|
|
42369
42923
|
captureDiscoveryByteSources,
|
|
42924
|
+
captureClaudeMarketplaceRegistry,
|
|
42370
42925
|
canonicalizeManifest,
|
|
42371
42926
|
buildSkillsS3ObjectUrl,
|
|
42372
42927
|
buildSkillsApiUrl,
|
|
@@ -42430,6 +42985,7 @@ export {
|
|
|
42430
42985
|
RemotePrivatePublicationsClient,
|
|
42431
42986
|
RemoteInvitationEmailUnconfirmedError,
|
|
42432
42987
|
RemoteInvitationEmailError,
|
|
42988
|
+
RemoteCreditCheckoutError,
|
|
42433
42989
|
RemoteCreditApprovalError,
|
|
42434
42990
|
RemoteCapabilityUnavailableError,
|
|
42435
42991
|
REMOTE_SKILL_RUN_CONTRACT_VERSION,
|