@hasna/skills 0.8.10 → 0.8.12
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 +62 -1
- package/bin/index.js +1558 -591
- package/bin/mcp.js +389 -82
- package/bin/migrate.js +6 -5
- package/bin/server.js +12312 -73626
- package/bin/worker.js +7635 -60520
- package/dist/index.d.ts +5 -1
- package/dist/index.js +1058 -267
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/claude-marketplace-registry.d.ts +17 -0
- package/dist/lib/claude-settings-witness.d.ts +19 -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 +13419 -73945
- package/docs/plugin-admission.md +90 -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 lstatSync19,
|
|
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 basename9, dirname as dirname16, 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 createHash14, randomUUID as randomUUID3 } from "crypto";
|
|
9668
|
+
import { existsSync as existsSync11, lstatSync as lstatSync18, 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 openSync11, closeSync as closeSync11, fsyncSync as fsyncSync3, fstatSync as fstatSync11, readSync as readSync9, constants as constants10, linkSync as linkSync2 } from "fs";
|
|
9669
|
+
import { dirname as dirname15, join as join25, resolve as resolve15, relative as relative5, isAbsolute as isAbsolute15, 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,11 @@ 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", "claude-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
10811
|
+
if (source.hashMode === "claude-settings-v1")
|
|
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) === "settings.json");
|
|
10813
|
+
if (source.hashMode === "claude-marketplace-registry")
|
|
10814
|
+
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
10815
|
if (source.hashMode === "claude-plugin-registry") {
|
|
10627
10816
|
requireBound(agent === "claude");
|
|
10628
10817
|
const managed = array(source.managedPlugins, 64);
|
|
@@ -10774,9 +10963,9 @@ function isOwnedCliBridge(path, expectedPaths) {
|
|
|
10774
10963
|
}
|
|
10775
10964
|
|
|
10776
10965
|
// src/lib/agent-discovery.ts
|
|
10777
|
-
import { createHash as
|
|
10778
|
-
import { lstatSync as
|
|
10779
|
-
import { dirname as
|
|
10966
|
+
import { createHash as createHash13 } from "crypto";
|
|
10967
|
+
import { lstatSync as lstatSync17, readFileSync as readFileSync13, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
|
|
10968
|
+
import { basename as basename8, dirname as dirname14, isAbsolute as isAbsolute14, join as join23, resolve as resolve14, sep as sep5 } from "path";
|
|
10780
10969
|
|
|
10781
10970
|
// node_modules/yaml/dist/index.js
|
|
10782
10971
|
var composer = require_composer();
|
|
@@ -10825,7 +11014,7 @@ var $visit = visit.visit;
|
|
|
10825
11014
|
var $visitAsync = visit.visitAsync;
|
|
10826
11015
|
|
|
10827
11016
|
// src/lib/agent-hermes.ts
|
|
10828
|
-
import { dirname as dirname5, isAbsolute as
|
|
11017
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, resolve as resolve5 } from "path";
|
|
10829
11018
|
import { lstatSync as lstatSync6, readFileSync as readFileSync9, opendirSync } from "fs";
|
|
10830
11019
|
var HERMES_OPT_OUT = ".no-bundled-skills";
|
|
10831
11020
|
var EVENTS = ["pre_llm_call", "pre_tool_call"];
|
|
@@ -10863,7 +11052,7 @@ function quoted(value) {
|
|
|
10863
11052
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
10864
11053
|
}
|
|
10865
11054
|
function hermesHookDefinitions(supervisor) {
|
|
10866
|
-
if (!
|
|
11055
|
+
if (!isAbsolute4(supervisor.runtime) || !isAbsolute4(supervisor.path))
|
|
10867
11056
|
throw new Error("Expected absolute Hermes supervisor paths");
|
|
10868
11057
|
const entry = (event) => ({ command: `${quoted(supervisor.runtime)} ${quoted(supervisor.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
|
|
10869
11058
|
return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
|
|
@@ -10899,14 +11088,14 @@ function assertHermesEnvironment(home) {
|
|
|
10899
11088
|
throw new Error(`NATIVE_SKILL_DRIFT: Hermes ${variable} requires a dedicated discovery adapter; unset it before using this adapter`);
|
|
10900
11089
|
}
|
|
10901
11090
|
const selected = process.env.HERMES_HOME?.trim();
|
|
10902
|
-
if (selected && (!
|
|
11091
|
+
if (selected && (!isAbsolute4(selected) || resolve5(selected) !== join13(resolve5(home), ".hermes")))
|
|
10903
11092
|
throw new Error("NATIVE_SKILL_DRIFT: custom Hermes homes/profiles require their own reviewed bridge");
|
|
10904
11093
|
if (process.env.HERMES_ENABLE_PROJECT_PLUGINS?.trim() && !["0", "false", "no", "off"].includes(process.env.HERMES_ENABLE_PROJECT_PLUGINS.trim().toLowerCase()))
|
|
10905
11094
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes project plugins require a dedicated discovery review");
|
|
10906
11095
|
}
|
|
10907
11096
|
function assertNoHermesLegacyShadow(root) {
|
|
10908
11097
|
let entries = 0;
|
|
10909
|
-
for (let cursor =
|
|
11098
|
+
for (let cursor = resolve5(root);; cursor = dirname5(cursor)) {
|
|
10910
11099
|
if (lstatSync6(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
10911
11100
|
throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes legacy discovery root");
|
|
10912
11101
|
if (dirname5(cursor) === cursor)
|
|
@@ -10992,13 +11181,13 @@ if (failed) process.exitCode = 2;
|
|
|
10992
11181
|
// src/lib/agent-discovery-directories.ts
|
|
10993
11182
|
import { createHash as createHash5 } from "crypto";
|
|
10994
11183
|
import { lstatSync as lstatSync7, opendirSync as opendirSync2 } from "fs";
|
|
10995
|
-
import { dirname as dirname6, isAbsolute as
|
|
11184
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join14, resolve as resolve6 } from "path";
|
|
10996
11185
|
function need(value, message) {
|
|
10997
11186
|
if (!value)
|
|
10998
11187
|
throw new Error(message);
|
|
10999
11188
|
}
|
|
11000
11189
|
function safe(path) {
|
|
11001
|
-
need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters &&
|
|
11190
|
+
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
11191
|
for (let cursor = path;; cursor = dirname6(cursor)) {
|
|
11003
11192
|
need(!lstatSync7(cursor, { throwIfNoEntry: false })?.isSymbolicLink(), "Refusing symlink native discovery directory");
|
|
11004
11193
|
if (dirname6(cursor) === cursor)
|
|
@@ -11063,12 +11252,12 @@ function verifyDiscoveryDirectories(directories) {
|
|
|
11063
11252
|
// src/lib/agent-discovery-bytes.ts
|
|
11064
11253
|
import { createHash as createHash6 } from "crypto";
|
|
11065
11254
|
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
|
|
11255
|
+
import { dirname as dirname7, isAbsolute as isAbsolute6, resolve as resolve7 } from "path";
|
|
11067
11256
|
function discoveryByteBudget() {
|
|
11068
11257
|
return { remaining: AGENT_POLICY_LIMITS.discoveryRawTotalBytes };
|
|
11069
11258
|
}
|
|
11070
11259
|
function safe2(path) {
|
|
11071
|
-
if (typeof path !== "string" || !
|
|
11260
|
+
if (typeof path !== "string" || !isAbsolute6(path) || path !== resolve7(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00"))
|
|
11072
11261
|
throw new Error("Expected a canonical absolute raw discovery path");
|
|
11073
11262
|
for (let cursor = path;; cursor = dirname7(cursor)) {
|
|
11074
11263
|
if (lstatSync8(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
@@ -11131,7 +11320,7 @@ function hashRawDiscoveryFile(path, budget, changes) {
|
|
|
11131
11320
|
// src/lib/agent-discovery-path-bytes.ts
|
|
11132
11321
|
import { createHash as createHash7 } from "crypto";
|
|
11133
11322
|
import { lstatSync as lstatSync9, readlinkSync } from "fs";
|
|
11134
|
-
import { dirname as dirname8, isAbsolute as
|
|
11323
|
+
import { dirname as dirname8, isAbsolute as isAbsolute7, join as join15, parse as parse2, resolve as resolve8, sep as sep3 } from "path";
|
|
11135
11324
|
function identity2(stat, mutable = false) {
|
|
11136
11325
|
return {
|
|
11137
11326
|
dev: String(stat.dev),
|
|
@@ -11143,7 +11332,7 @@ function identity2(stat, mutable = false) {
|
|
|
11143
11332
|
};
|
|
11144
11333
|
}
|
|
11145
11334
|
function canonical(path) {
|
|
11146
|
-
if (typeof path !== "string" || !
|
|
11335
|
+
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
11336
|
throw new Error("Expected a canonical absolute discovery path");
|
|
11148
11337
|
}
|
|
11149
11338
|
function trace(path) {
|
|
@@ -11192,11 +11381,11 @@ function trace(path) {
|
|
|
11192
11381
|
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
11382
|
throw new Error("Discovery link changed during read");
|
|
11194
11383
|
record2(candidate, { type: "link", target, ...identity2(stat, true) });
|
|
11195
|
-
if (
|
|
11384
|
+
if (isAbsolute7(target)) {
|
|
11196
11385
|
cursor = parse2(target).root;
|
|
11197
11386
|
root();
|
|
11198
11387
|
}
|
|
11199
|
-
const remainder =
|
|
11388
|
+
const remainder = isAbsolute7(target) ? target.slice(parse2(target).root.length) : target;
|
|
11200
11389
|
pending = [...remainder.split(sep3), ...pending];
|
|
11201
11390
|
if (pending.length + steps > AGENT_POLICY_LIMITS.discoveryPathSteps)
|
|
11202
11391
|
throw new Error("Discovery path step limit exceeded");
|
|
@@ -11244,15 +11433,15 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
11244
11433
|
|
|
11245
11434
|
// src/lib/plugin-discovery.ts
|
|
11246
11435
|
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
|
|
11436
|
+
import { basename as basename5, dirname as dirname11, isAbsolute as isAbsolute11, join as join21, resolve as resolve11 } from "path";
|
|
11248
11437
|
|
|
11249
11438
|
// src/lib/plugin-admission.ts
|
|
11250
11439
|
import { homedir as homedir2 } from "os";
|
|
11251
|
-
import { isAbsolute as
|
|
11440
|
+
import { isAbsolute as isAbsolute10, join as join20, resolve as resolve10 } from "path";
|
|
11252
11441
|
// package.json
|
|
11253
11442
|
var package_default = {
|
|
11254
11443
|
name: "@hasna/skills",
|
|
11255
|
-
version: "0.8.
|
|
11444
|
+
version: "0.8.12",
|
|
11256
11445
|
description: "Skills library for AI coding agents",
|
|
11257
11446
|
type: "module",
|
|
11258
11447
|
bin: {
|
|
@@ -11314,10 +11503,11 @@ var package_default = {
|
|
|
11314
11503
|
typecheck: "tsc --noEmit",
|
|
11315
11504
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
11316
11505
|
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
11317
|
-
|
|
11318
|
-
prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
|
|
11506
|
+
prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
|
|
11319
11507
|
prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
|
|
11320
|
-
"verify:producer": "bun run scripts/release-dependencies.ts"
|
|
11508
|
+
"verify:producer": "bun run scripts/release-dependencies.ts",
|
|
11509
|
+
"verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
|
|
11510
|
+
"verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
|
|
11321
11511
|
},
|
|
11322
11512
|
keywords: [
|
|
11323
11513
|
"skills",
|
|
@@ -11339,7 +11529,6 @@ var package_default = {
|
|
|
11339
11529
|
license: "Apache-2.0",
|
|
11340
11530
|
devDependencies: {
|
|
11341
11531
|
"@hasna/contracts": "1.1.0",
|
|
11342
|
-
"@hasna/secrets": "0.4.2",
|
|
11343
11532
|
"@types/bun": "1.3.14",
|
|
11344
11533
|
"@types/node": "25.2.3",
|
|
11345
11534
|
"@types/react": "^18.2.0",
|
|
@@ -11352,6 +11541,7 @@ var package_default = {
|
|
|
11352
11541
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
11353
11542
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
11354
11543
|
"@hasna/events": "0.1.18",
|
|
11544
|
+
"@hasna/secrets": "0.4.2",
|
|
11355
11545
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11356
11546
|
chalk: "^5.3.0",
|
|
11357
11547
|
commander: "^12.1.0",
|
|
@@ -11386,12 +11576,12 @@ import { spawnSync } from "child_process";
|
|
|
11386
11576
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
11387
11577
|
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
11388
11578
|
import { hostname as osHostname } from "os";
|
|
11389
|
-
import { isAbsolute as
|
|
11579
|
+
import { isAbsolute as isAbsolute8, join as join22 } from "path";
|
|
11390
11580
|
import { createRequire } from "module";
|
|
11391
11581
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11392
11582
|
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
11583
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
11394
|
-
import { basename as
|
|
11584
|
+
import { basename as basename4, dirname as dirname9, join as join16, relative as relative4, sep as sep4 } from "path";
|
|
11395
11585
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
11396
11586
|
function envToken(name) {
|
|
11397
11587
|
return name.toUpperCase().replace(/-/g, "_");
|
|
@@ -11499,7 +11689,7 @@ function entryPoint(directory) {
|
|
|
11499
11689
|
function resolveInstalledSecrets(parentUrl) {
|
|
11500
11690
|
let directory = dirname9(fileURLToPath(parentUrl));
|
|
11501
11691
|
for (;; ) {
|
|
11502
|
-
if (
|
|
11692
|
+
if (basename4(directory) !== "node_modules") {
|
|
11503
11693
|
const candidate = join16(directory, "node_modules", PACKAGE);
|
|
11504
11694
|
let absent = false;
|
|
11505
11695
|
try {
|
|
@@ -11559,7 +11749,7 @@ function homeDir(env) {
|
|
|
11559
11749
|
}
|
|
11560
11750
|
function absoluteOverride(env, key) {
|
|
11561
11751
|
const value = env[key]?.trim();
|
|
11562
|
-
return value &&
|
|
11752
|
+
return value && isAbsolute8(value) ? value : null;
|
|
11563
11753
|
}
|
|
11564
11754
|
function hasnaHomeDir(env) {
|
|
11565
11755
|
const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
|
|
@@ -12338,10 +12528,16 @@ function isCredentialResolutionError(error) {
|
|
|
12338
12528
|
function isSkillsFleetCredentialError(error) {
|
|
12339
12529
|
return error instanceof SkillsFleetCredentialError || typeof error === "object" && error !== null && error.name === "SkillsFleetCredentialError";
|
|
12340
12530
|
}
|
|
12341
|
-
|
|
12531
|
+
var REDACTED_VAULT_REFERENCE = "[redacted vault reference]";
|
|
12532
|
+
function redactVaultReference(message, vaultReference) {
|
|
12533
|
+
if (!vaultReference)
|
|
12534
|
+
return message;
|
|
12535
|
+
return message.split(vaultReference).join(REDACTED_VAULT_REFERENCE);
|
|
12536
|
+
}
|
|
12537
|
+
function asSkillsFleetCredentialError(error, vaultReference) {
|
|
12342
12538
|
if (!isCredentialResolutionError(error))
|
|
12343
12539
|
return null;
|
|
12344
|
-
return new SkillsFleetCredentialError(error.message, "MISSING_API_CREDENTIAL");
|
|
12540
|
+
return new SkillsFleetCredentialError(redactVaultReference(error.message, vaultReference), "MISSING_API_CREDENTIAL");
|
|
12345
12541
|
}
|
|
12346
12542
|
function normalizeSkillsApiOrigin(apiUrl) {
|
|
12347
12543
|
const url = new URL(apiUrl);
|
|
@@ -12526,7 +12722,7 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
12526
12722
|
try {
|
|
12527
12723
|
completed = await completePointerCredential(SKILLS_APP, pointer, env);
|
|
12528
12724
|
} catch (error) {
|
|
12529
|
-
const translated = asSkillsFleetCredentialError(error);
|
|
12725
|
+
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
12530
12726
|
if (translated)
|
|
12531
12727
|
throw translated;
|
|
12532
12728
|
throw error;
|
|
@@ -13407,11 +13603,18 @@ async function syncSelectionProfile(profileId, options = {}) {
|
|
|
13407
13603
|
return { profile, receipt, changed, downloaded, stationReported };
|
|
13408
13604
|
}
|
|
13409
13605
|
async function resolveSelectionContext(profileId, options = {}) {
|
|
13410
|
-
const
|
|
13411
|
-
const
|
|
13606
|
+
const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
|
|
13607
|
+
const session = sessionState?.receipt ?? null;
|
|
13608
|
+
const parentState = !session && options.parentSessionId ? readSkillSessionSnapshotIfExists(options.parentSessionId, options) : null;
|
|
13609
|
+
if (!session && options.parentSessionId && !parentState) {
|
|
13610
|
+
throw new SkillSelectionError("SESSION_PARENT_NOT_FOUND", "A child Skills session requires an existing parent receipt so its inherited pin can be bound exactly.");
|
|
13611
|
+
}
|
|
13612
|
+
const parentSession = parentState?.receipt ?? null;
|
|
13613
|
+
const sessionSnapshot = sessionState ? skillSessionSnapshotBinding(sessionState) : null;
|
|
13614
|
+
const parentSnapshot = parentState ? skillSessionSnapshotBinding(parentState) : undefined;
|
|
13412
13615
|
const project = options.projectDir ? readProjectSelection(options.projectDir) : null;
|
|
13413
13616
|
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
|
|
13617
|
+
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
13618
|
}
|
|
13416
13619
|
if (options.cached) {
|
|
13417
13620
|
const receipt2 = session ?? parentSession ?? project ?? readSelectionProfile(profileId, options);
|
|
@@ -13419,7 +13622,7 @@ async function resolveSelectionContext(profileId, options = {}) {
|
|
|
13419
13622
|
throw new SkillSelectionError("CACHED_PROFILE_MISSING", "No verified Skills profile is cached; authenticate and sync it first.");
|
|
13420
13623
|
assertFreshCachedProfile(receipt2, options);
|
|
13421
13624
|
assertBinding(receipt2.profile, options);
|
|
13422
|
-
return { receipt: receipt2, session, inheritedLoaded: parentSession?.loaded };
|
|
13625
|
+
return { receipt: receipt2, session, sessionSnapshot, parentSnapshot, inheritedLoaded: parentSession?.loaded };
|
|
13423
13626
|
}
|
|
13424
13627
|
const client = options.client ?? await createProfileClient();
|
|
13425
13628
|
const current = await client.resolveProfile(profileId);
|
|
@@ -13432,7 +13635,7 @@ async function resolveSelectionContext(profileId, options = {}) {
|
|
|
13432
13635
|
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The pinned selection belongs to another Skills authority or workspace.");
|
|
13433
13636
|
}
|
|
13434
13637
|
const receipt = locked ?? { schemaVersion: 1, verifiedAt: new Date((options.now ?? Date.now)()).toISOString(), profile: current };
|
|
13435
|
-
return { receipt, session, client, inheritedLoaded: parentSession?.loaded };
|
|
13638
|
+
return { receipt, session, sessionSnapshot, parentSnapshot, client, inheritedLoaded: parentSession?.loaded };
|
|
13436
13639
|
}
|
|
13437
13640
|
function assertBinding(profile, options) {
|
|
13438
13641
|
if (options.authority !== undefined && profile.authority !== options.authority || options.workspaceId !== undefined && profile.workspaceId !== options.workspaceId) {
|
|
@@ -13661,8 +13864,8 @@ function buildPluginProjection(entries) {
|
|
|
13661
13864
|
|
|
13662
13865
|
// src/lib/plugin-projection-store.ts
|
|
13663
13866
|
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
|
|
13867
|
+
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";
|
|
13868
|
+
import { dirname as dirname10, isAbsolute as isAbsolute9, join as join19, parse as parse4, resolve as resolve9 } from "path";
|
|
13666
13869
|
var READ_FLAGS = constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK;
|
|
13667
13870
|
var WRITE_FLAGS = constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW | constants6.O_NONBLOCK;
|
|
13668
13871
|
var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
|
|
@@ -13677,7 +13880,7 @@ function stat(path) {
|
|
|
13677
13880
|
}
|
|
13678
13881
|
}
|
|
13679
13882
|
function canonical2(path) {
|
|
13680
|
-
pluginNeed(typeof path === "string" && path.length <= 8192 &&
|
|
13883
|
+
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
13884
|
}
|
|
13682
13885
|
function owner(stats, system = false) {
|
|
13683
13886
|
const current = uid();
|
|
@@ -13886,7 +14089,7 @@ function writeExclusive(path, bytes, mode) {
|
|
|
13886
14089
|
pluginNeed(count > 0, "Plugin file write did not progress");
|
|
13887
14090
|
offset += count;
|
|
13888
14091
|
}
|
|
13889
|
-
|
|
14092
|
+
fsyncSync2(fd);
|
|
13890
14093
|
} catch (error) {
|
|
13891
14094
|
if (created) {
|
|
13892
14095
|
same3(created, stat(path), true);
|
|
@@ -14060,7 +14263,7 @@ function validatePluginTarget(value) {
|
|
|
14060
14263
|
for (const registration of value.registrations) {
|
|
14061
14264
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
14062
14265
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
14063
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" &&
|
|
14266
|
+
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
14267
|
const key = JSON.stringify([registration.scope, registration.projectPath]);
|
|
14065
14268
|
pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
|
|
14066
14269
|
scopes.add(key);
|
|
@@ -14068,7 +14271,7 @@ function validatePluginTarget(value) {
|
|
|
14068
14271
|
for (const key of ["native", "resolver"]) {
|
|
14069
14272
|
pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
14070
14273
|
pluginText(value[key].executable);
|
|
14071
|
-
pluginNeed(
|
|
14274
|
+
pluginNeed(isAbsolute10(value[key].executable) && resolve10(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
|
|
14072
14275
|
pluginDigest(value[key].digest);
|
|
14073
14276
|
}
|
|
14074
14277
|
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 +14423,7 @@ function pluginResolverCommand(binding) {
|
|
|
14220
14423
|
}
|
|
14221
14424
|
function root(options) {
|
|
14222
14425
|
const path = options.storeRoot ?? pluginAdmissionRoot();
|
|
14223
|
-
pluginNeed(
|
|
14426
|
+
pluginNeed(isAbsolute10(path) && resolve10(path) === path, "Invalid plugin admission store root");
|
|
14224
14427
|
return path;
|
|
14225
14428
|
}
|
|
14226
14429
|
function pluginBindingPath(storeRoot, id) {
|
|
@@ -14425,12 +14628,12 @@ function validateManagedPluginWitnesses(value) {
|
|
|
14425
14628
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
14426
14629
|
seen.add(item.bindingId);
|
|
14427
14630
|
pluginText(item.storeRoot);
|
|
14428
|
-
pluginNeed(
|
|
14631
|
+
pluginNeed(isAbsolute11(item.storeRoot) && resolve11(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
14429
14632
|
}
|
|
14430
14633
|
}
|
|
14431
14634
|
function safe3(path) {
|
|
14432
14635
|
pluginText(path);
|
|
14433
|
-
pluginNeed(
|
|
14636
|
+
pluginNeed(isAbsolute11(path) && resolve11(path) === path, "Invalid plugin registry path");
|
|
14434
14637
|
for (let at = path;; at = dirname11(at)) {
|
|
14435
14638
|
pluginNeed(!lstatSync14(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
14436
14639
|
if (at === dirname11(at))
|
|
@@ -14499,7 +14702,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
14499
14702
|
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
14703
|
const approval = (producer) => {
|
|
14501
14704
|
pluginText(producer);
|
|
14502
|
-
const digest = `sha256:${
|
|
14705
|
+
const digest = `sha256:${basename5(producer)}`;
|
|
14503
14706
|
const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
|
|
14504
14707
|
pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
|
|
14505
14708
|
if (!verified.has(producer)) {
|
|
@@ -14560,8 +14763,398 @@ function captureManagedPluginRegistry(path, managedPlugins) {
|
|
|
14560
14763
|
return { path, hashMode: "claude-plugin-registry", managedPlugins, sha256: hashManagedPluginRegistry(path, managedPlugins) };
|
|
14561
14764
|
}
|
|
14562
14765
|
|
|
14766
|
+
// src/lib/claude-marketplace-registry.ts
|
|
14767
|
+
import { createHash as createHash11 } from "crypto";
|
|
14768
|
+
import { closeSync as closeSync9, constants as constants8, fstatSync as fstatSync9, lstatSync as lstatSync15, openSync as openSync9, readSync as readSync7 } from "fs";
|
|
14769
|
+
import { basename as basename6, dirname as dirname12, isAbsolute as isAbsolute12, resolve as resolve12 } from "path";
|
|
14770
|
+
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
14771
|
+
function need2(value, reason) {
|
|
14772
|
+
if (!value)
|
|
14773
|
+
throw new Error(`Claude marketplace registry ${reason}`);
|
|
14774
|
+
}
|
|
14775
|
+
function absolutePath(value) {
|
|
14776
|
+
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");
|
|
14777
|
+
}
|
|
14778
|
+
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;
|
|
14779
|
+
function parents(path) {
|
|
14780
|
+
const result = [];
|
|
14781
|
+
for (let at = dirname12(path);; at = dirname12(at)) {
|
|
14782
|
+
const stat2 = lstatSync15(at, { bigint: true });
|
|
14783
|
+
need2(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
14784
|
+
result.push([at, stat2]);
|
|
14785
|
+
if (at === dirname12(at))
|
|
14786
|
+
return result;
|
|
14787
|
+
}
|
|
14788
|
+
}
|
|
14789
|
+
function readRegistry2(path, budget) {
|
|
14790
|
+
absolutePath(path);
|
|
14791
|
+
need2(basename6(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
14792
|
+
need2(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
14793
|
+
const ancestors2 = parents(path), initial = lstatSync15(path, { bigint: true });
|
|
14794
|
+
need2(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
14795
|
+
need2(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
14796
|
+
const fd = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
14797
|
+
try {
|
|
14798
|
+
const opened = fstatSync9(fd, { bigint: true });
|
|
14799
|
+
need2(opened.isFile() && sameFile(initial, opened), "changed during open");
|
|
14800
|
+
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
14801
|
+
let length = 0;
|
|
14802
|
+
while (length < bytes.length) {
|
|
14803
|
+
const count = readSync7(fd, bytes, length, bytes.length - length, null);
|
|
14804
|
+
if (!count)
|
|
14805
|
+
break;
|
|
14806
|
+
length += count;
|
|
14807
|
+
}
|
|
14808
|
+
const after = fstatSync9(fd, { bigint: true }), current = lstatSync15(path, { bigint: true });
|
|
14809
|
+
need2(current.isFile() && sameFile(opened, after) && sameFile(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
14810
|
+
for (const [ancestor, before] of ancestors2) {
|
|
14811
|
+
const now = lstatSync15(ancestor, { bigint: true });
|
|
14812
|
+
need2(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
14813
|
+
}
|
|
14814
|
+
budget.remaining -= length;
|
|
14815
|
+
try {
|
|
14816
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
|
|
14817
|
+
} catch {
|
|
14818
|
+
throw new Error("Claude marketplace registry is not strict UTF-8");
|
|
14819
|
+
}
|
|
14820
|
+
} finally {
|
|
14821
|
+
closeSync9(fd);
|
|
14822
|
+
}
|
|
14823
|
+
}
|
|
14824
|
+
function parse5(text2) {
|
|
14825
|
+
let at = 0, nodes = 0;
|
|
14826
|
+
const numberPattern = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
|
|
14827
|
+
const space = () => {
|
|
14828
|
+
while (at < text2.length && /[\x20\t\r\n]/.test(text2[at]))
|
|
14829
|
+
at++;
|
|
14830
|
+
};
|
|
14831
|
+
function string() {
|
|
14832
|
+
need2(text2[at] === '"', "contains invalid JSON");
|
|
14833
|
+
const start = at++;
|
|
14834
|
+
while (at < text2.length) {
|
|
14835
|
+
const character = text2[at++];
|
|
14836
|
+
if (character === '"') {
|
|
14837
|
+
let value2;
|
|
14838
|
+
try {
|
|
14839
|
+
value2 = JSON.parse(text2.slice(start, at));
|
|
14840
|
+
} catch {
|
|
14841
|
+
throw new Error("Claude marketplace registry contains invalid JSON string");
|
|
14842
|
+
}
|
|
14843
|
+
need2(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
|
|
14844
|
+
return value2;
|
|
14845
|
+
}
|
|
14846
|
+
if (character === "\\")
|
|
14847
|
+
at++;
|
|
14848
|
+
}
|
|
14849
|
+
throw new Error("Claude marketplace registry contains an unterminated JSON string");
|
|
14850
|
+
}
|
|
14851
|
+
function value(depth) {
|
|
14852
|
+
need2(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
14853
|
+
space();
|
|
14854
|
+
const character = text2[at];
|
|
14855
|
+
if (character === '"')
|
|
14856
|
+
return { kind: "string", value: string() };
|
|
14857
|
+
if (character === "{" || character === "[") {
|
|
14858
|
+
at++;
|
|
14859
|
+
space();
|
|
14860
|
+
const object4 = character === "{", end = object4 ? "}" : "]", entries = [], items = [], keys = new Set;
|
|
14861
|
+
if (text2[at] !== end)
|
|
14862
|
+
while (true) {
|
|
14863
|
+
if (object4) {
|
|
14864
|
+
space();
|
|
14865
|
+
const key = string();
|
|
14866
|
+
need2(!keys.has(key), "contains a duplicate JSON key");
|
|
14867
|
+
keys.add(key);
|
|
14868
|
+
space();
|
|
14869
|
+
need2(text2[at++] === ":", "contains invalid JSON");
|
|
14870
|
+
entries.push([key, value(depth + 1)]);
|
|
14871
|
+
} else
|
|
14872
|
+
items.push(value(depth + 1));
|
|
14873
|
+
space();
|
|
14874
|
+
if (text2[at] === end)
|
|
14875
|
+
break;
|
|
14876
|
+
need2(text2[at++] === ",", "contains invalid JSON");
|
|
14877
|
+
}
|
|
14878
|
+
at++;
|
|
14879
|
+
return object4 ? { kind: "object", entries } : { kind: "array", items };
|
|
14880
|
+
}
|
|
14881
|
+
for (const literal of ["true", "false", "null"])
|
|
14882
|
+
if (text2.startsWith(literal, at)) {
|
|
14883
|
+
at += literal.length;
|
|
14884
|
+
return { kind: "literal", value: literal };
|
|
14885
|
+
}
|
|
14886
|
+
numberPattern.lastIndex = at;
|
|
14887
|
+
const number = numberPattern.exec(text2);
|
|
14888
|
+
need2(number, "contains invalid JSON");
|
|
14889
|
+
at += number[0].length;
|
|
14890
|
+
return { kind: "number", value: number[0] };
|
|
14891
|
+
}
|
|
14892
|
+
const root2 = value(0);
|
|
14893
|
+
space();
|
|
14894
|
+
need2(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
14895
|
+
need2(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
|
|
14896
|
+
return root2;
|
|
14897
|
+
}
|
|
14898
|
+
function exactKeys(value, keys) {
|
|
14899
|
+
return value?.kind === "object" && value.entries.length === keys.length && value.entries.every(([key]) => keys.includes(key));
|
|
14900
|
+
}
|
|
14901
|
+
var field = (object4, key) => object4.entries.find(([name]) => name === key)?.[1];
|
|
14902
|
+
function textField(value) {
|
|
14903
|
+
need2(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
14904
|
+
return value.value;
|
|
14905
|
+
}
|
|
14906
|
+
function normalizeRow(row) {
|
|
14907
|
+
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
|
|
14908
|
+
return;
|
|
14909
|
+
const source = field(row, "source");
|
|
14910
|
+
if (source?.kind !== "object")
|
|
14911
|
+
return;
|
|
14912
|
+
const type = field(source, "source");
|
|
14913
|
+
if (type?.kind !== "string" || !["github", "directory"].includes(type.value))
|
|
14914
|
+
return;
|
|
14915
|
+
const location = type.value === "github" ? "repo" : "path";
|
|
14916
|
+
if (!exactKeys(source, ["source", location]))
|
|
14917
|
+
return;
|
|
14918
|
+
const sourceLocation = textField(field(source, location));
|
|
14919
|
+
if (type.value === "directory")
|
|
14920
|
+
absolutePath(sourceLocation);
|
|
14921
|
+
absolutePath(textField(field(row, "installLocation")));
|
|
14922
|
+
const updated = textField(field(row, "lastUpdated"));
|
|
14923
|
+
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");
|
|
14924
|
+
const millis = Date.parse(updated);
|
|
14925
|
+
need2(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
|
|
14926
|
+
row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
|
|
14927
|
+
}
|
|
14928
|
+
function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
14929
|
+
const value = parse5(readRegistry2(path, budget));
|
|
14930
|
+
for (const [, row] of value.entries)
|
|
14931
|
+
normalizeRow(row);
|
|
14932
|
+
const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
14933
|
+
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
14934
|
+
}
|
|
14935
|
+
|
|
14936
|
+
// src/lib/claude-settings-witness.ts
|
|
14937
|
+
import { createHash as createHash12 } from "crypto";
|
|
14938
|
+
import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync16, openSync as openSync10, readSync as readSync8 } from "fs";
|
|
14939
|
+
import { basename as basename7, dirname as dirname13, isAbsolute as isAbsolute13, resolve as resolve13 } from "path";
|
|
14940
|
+
var CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
14941
|
+
function need3(value, reason) {
|
|
14942
|
+
if (!value)
|
|
14943
|
+
throw new Error(`Claude settings witness ${reason}`);
|
|
14944
|
+
}
|
|
14945
|
+
function absolutePath2(value) {
|
|
14946
|
+
need3(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_SETTINGS_WITNESS_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute13(value) && resolve13(value) === value, "requires a normalized absolute path");
|
|
14947
|
+
}
|
|
14948
|
+
var sameFile2 = (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;
|
|
14949
|
+
function parents2(path) {
|
|
14950
|
+
const result = [];
|
|
14951
|
+
for (let at = dirname13(path);; at = dirname13(at)) {
|
|
14952
|
+
const stat2 = lstatSync16(at, { bigint: true });
|
|
14953
|
+
need3(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
14954
|
+
result.push([at, stat2]);
|
|
14955
|
+
if (at === dirname13(at))
|
|
14956
|
+
return result;
|
|
14957
|
+
}
|
|
14958
|
+
}
|
|
14959
|
+
function readSettings(path, budget) {
|
|
14960
|
+
absolutePath2(path);
|
|
14961
|
+
need3(basename7(path) === "settings.json", "requires settings.json");
|
|
14962
|
+
need3(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
14963
|
+
const ancestors2 = parents2(path), initial = lstatSync16(path, { bigint: true });
|
|
14964
|
+
need3(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_SETTINGS_WITNESS_LIMITS.bytes), "is not a bounded regular file");
|
|
14965
|
+
need3(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
14966
|
+
const fd = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
14967
|
+
try {
|
|
14968
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
14969
|
+
need3(opened.isFile() && sameFile2(initial, opened), "changed during open");
|
|
14970
|
+
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
14971
|
+
let length = 0;
|
|
14972
|
+
while (length < bytes.length) {
|
|
14973
|
+
const count = readSync8(fd, bytes, length, bytes.length - length, null);
|
|
14974
|
+
if (!count)
|
|
14975
|
+
break;
|
|
14976
|
+
length += count;
|
|
14977
|
+
}
|
|
14978
|
+
const after = fstatSync10(fd, { bigint: true }), current = lstatSync16(path, { bigint: true });
|
|
14979
|
+
need3(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
14980
|
+
for (const [ancestor, before] of ancestors2) {
|
|
14981
|
+
const now = lstatSync16(ancestor, { bigint: true });
|
|
14982
|
+
need3(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
14983
|
+
}
|
|
14984
|
+
budget.remaining -= length;
|
|
14985
|
+
try {
|
|
14986
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
|
|
14987
|
+
} catch {
|
|
14988
|
+
throw new Error("Claude settings witness is not strict UTF-8");
|
|
14989
|
+
}
|
|
14990
|
+
} finally {
|
|
14991
|
+
closeSync10(fd);
|
|
14992
|
+
}
|
|
14993
|
+
}
|
|
14994
|
+
function parse6(text2) {
|
|
14995
|
+
let at = 0, nodes = 0;
|
|
14996
|
+
const numberPattern = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
|
|
14997
|
+
const space = () => {
|
|
14998
|
+
while (at < text2.length && /[\x20\t\r\n]/.test(text2[at]))
|
|
14999
|
+
at++;
|
|
15000
|
+
};
|
|
15001
|
+
function string() {
|
|
15002
|
+
need3(text2[at] === '"', "contains invalid JSON");
|
|
15003
|
+
const start = at++;
|
|
15004
|
+
while (at < text2.length) {
|
|
15005
|
+
const character = text2[at++];
|
|
15006
|
+
if (character === '"') {
|
|
15007
|
+
let value2;
|
|
15008
|
+
try {
|
|
15009
|
+
value2 = JSON.parse(text2.slice(start, at));
|
|
15010
|
+
} catch {
|
|
15011
|
+
throw new Error("Claude settings witness contains invalid JSON string");
|
|
15012
|
+
}
|
|
15013
|
+
need3(value2.length <= CLAUDE_SETTINGS_WITNESS_LIMITS.stringCharacters, "exceeds its string limit");
|
|
15014
|
+
return value2;
|
|
15015
|
+
}
|
|
15016
|
+
if (character === "\\")
|
|
15017
|
+
at++;
|
|
15018
|
+
}
|
|
15019
|
+
throw new Error("Claude settings witness contains an unterminated JSON string");
|
|
15020
|
+
}
|
|
15021
|
+
function value(depth) {
|
|
15022
|
+
need3(depth <= CLAUDE_SETTINGS_WITNESS_LIMITS.depth && ++nodes <= CLAUDE_SETTINGS_WITNESS_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
15023
|
+
space();
|
|
15024
|
+
const character = text2[at];
|
|
15025
|
+
if (character === '"')
|
|
15026
|
+
return { kind: "string", value: string() };
|
|
15027
|
+
if (character === "{" || character === "[") {
|
|
15028
|
+
at++;
|
|
15029
|
+
space();
|
|
15030
|
+
const object4 = character === "{", end = object4 ? "}" : "]", entries = [], items = [], keys = new Set;
|
|
15031
|
+
if (text2[at] !== end)
|
|
15032
|
+
while (true) {
|
|
15033
|
+
if (object4) {
|
|
15034
|
+
space();
|
|
15035
|
+
const key = string();
|
|
15036
|
+
need3(!keys.has(key), "contains a duplicate JSON key");
|
|
15037
|
+
keys.add(key);
|
|
15038
|
+
space();
|
|
15039
|
+
need3(text2[at++] === ":", "contains invalid JSON");
|
|
15040
|
+
entries.push([key, value(depth + 1)]);
|
|
15041
|
+
} else
|
|
15042
|
+
items.push(value(depth + 1));
|
|
15043
|
+
space();
|
|
15044
|
+
if (text2[at] === end)
|
|
15045
|
+
break;
|
|
15046
|
+
need3(text2[at++] === ",", "contains invalid JSON");
|
|
15047
|
+
}
|
|
15048
|
+
at++;
|
|
15049
|
+
return object4 ? { kind: "object", entries } : { kind: "array", items };
|
|
15050
|
+
}
|
|
15051
|
+
for (const literal of ["true", "false", "null"])
|
|
15052
|
+
if (text2.startsWith(literal, at)) {
|
|
15053
|
+
at += literal.length;
|
|
15054
|
+
return { kind: "literal", value: literal };
|
|
15055
|
+
}
|
|
15056
|
+
numberPattern.lastIndex = at;
|
|
15057
|
+
const number = numberPattern.exec(text2);
|
|
15058
|
+
need3(number, "contains invalid JSON");
|
|
15059
|
+
at += number[0].length;
|
|
15060
|
+
return { kind: "number", value: number[0] };
|
|
15061
|
+
}
|
|
15062
|
+
const root2 = value(0);
|
|
15063
|
+
space();
|
|
15064
|
+
need3(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
15065
|
+
need3(root2.entries.length <= CLAUDE_SETTINGS_WITNESS_LIMITS.rows, "exceeds its settings field limit");
|
|
15066
|
+
return root2;
|
|
15067
|
+
}
|
|
15068
|
+
var BOOLEAN_PREFERENCES = new Set([
|
|
15069
|
+
"autoScrollEnabled",
|
|
15070
|
+
"axScreenReader",
|
|
15071
|
+
"emojiCompletionEnabled",
|
|
15072
|
+
"prefersReducedMotion",
|
|
15073
|
+
"showTurnDuration",
|
|
15074
|
+
"spinnerTipsEnabled",
|
|
15075
|
+
"syntaxHighlightingDisabled",
|
|
15076
|
+
"terminalProgressBarEnabled",
|
|
15077
|
+
"terminalTitleFromRename",
|
|
15078
|
+
"verbose",
|
|
15079
|
+
"wheelScrollAccelerationEnabled"
|
|
15080
|
+
]);
|
|
15081
|
+
var ENUM_PREFERENCES = Object.freeze({
|
|
15082
|
+
editorMode: ["normal", "vim"],
|
|
15083
|
+
tui: ["default", "fullscreen"],
|
|
15084
|
+
viewMode: ["default", "verbose", "focus"]
|
|
15085
|
+
});
|
|
15086
|
+
var BUILTIN_MODEL_SELECTIONS = new Set([
|
|
15087
|
+
"default",
|
|
15088
|
+
"best",
|
|
15089
|
+
"fable",
|
|
15090
|
+
"fable[1m]",
|
|
15091
|
+
"sonnet",
|
|
15092
|
+
"sonnet[1m]",
|
|
15093
|
+
"opus",
|
|
15094
|
+
"opus[1m]",
|
|
15095
|
+
"haiku",
|
|
15096
|
+
"opusplan",
|
|
15097
|
+
"claude-fable-5-1",
|
|
15098
|
+
"claude-fable-5",
|
|
15099
|
+
"claude-fable-5[1m]",
|
|
15100
|
+
"claude-opus-5",
|
|
15101
|
+
"claude-sonnet-5",
|
|
15102
|
+
"claude-haiku-4-5-20251001",
|
|
15103
|
+
"claude-opus-4-6",
|
|
15104
|
+
"claude-sonnet-4-5",
|
|
15105
|
+
"claude-sonnet-4-5-20250929",
|
|
15106
|
+
"claude-opus-4-8",
|
|
15107
|
+
"claude-opus-4-8[1m]",
|
|
15108
|
+
"claude-opus-4-7",
|
|
15109
|
+
"claude-sonnet-4-6",
|
|
15110
|
+
"claude-opus-4-5-20251101",
|
|
15111
|
+
"claude-opus-4-5",
|
|
15112
|
+
"claude-haiku-4-5",
|
|
15113
|
+
"claude-fable-5-1[1m]",
|
|
15114
|
+
"claude-opus-5[1m]",
|
|
15115
|
+
"claude-opus-4-7[1m]",
|
|
15116
|
+
"claude-opus-4-6[1m]",
|
|
15117
|
+
"claude-sonnet-4-6[1m]"
|
|
15118
|
+
]);
|
|
15119
|
+
function canonical3(value) {
|
|
15120
|
+
if (value.kind === "object")
|
|
15121
|
+
return { kind: "object", entries: value.entries.map(([key, child]) => [key, canonical3(child)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0) };
|
|
15122
|
+
if (value.kind === "array")
|
|
15123
|
+
return { kind: "array", items: value.items.map(canonical3) };
|
|
15124
|
+
return value;
|
|
15125
|
+
}
|
|
15126
|
+
function settingsDigest(text2) {
|
|
15127
|
+
const value = parse6(text2);
|
|
15128
|
+
value.entries = value.entries.filter(([key, child]) => {
|
|
15129
|
+
if (key === "model") {
|
|
15130
|
+
need3(child.kind === "string", "contains an invalid model selection");
|
|
15131
|
+
return !BUILTIN_MODEL_SELECTIONS.has(child.value);
|
|
15132
|
+
}
|
|
15133
|
+
if (BOOLEAN_PREFERENCES.has(key)) {
|
|
15134
|
+
need3(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid display preference");
|
|
15135
|
+
return false;
|
|
15136
|
+
}
|
|
15137
|
+
if (Object.hasOwn(ENUM_PREFERENCES, key)) {
|
|
15138
|
+
need3(child.kind === "string" && ENUM_PREFERENCES[key].includes(child.value), "contains an invalid display preference");
|
|
15139
|
+
return false;
|
|
15140
|
+
}
|
|
15141
|
+
return true;
|
|
15142
|
+
});
|
|
15143
|
+
return createHash12("sha256").update("hasna.skills.claude-settings.v1\x00").update(JSON.stringify(canonical3(value))).digest("hex");
|
|
15144
|
+
}
|
|
15145
|
+
function hashClaudeSettingsReplacement(text2, budget) {
|
|
15146
|
+
need3(typeof text2 === "string", "requires settings text");
|
|
15147
|
+
const bytes = Buffer.byteLength(text2);
|
|
15148
|
+
need3(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
15149
|
+
budget.remaining -= bytes;
|
|
15150
|
+
return settingsDigest(text2);
|
|
15151
|
+
}
|
|
15152
|
+
function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
15153
|
+
return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
|
|
15154
|
+
}
|
|
15155
|
+
|
|
14563
15156
|
// src/lib/agent-discovery.ts
|
|
14564
|
-
var digest = (text2) =>
|
|
15157
|
+
var digest = (text2) => createHash13("sha256").update(text2).digest("hex");
|
|
14565
15158
|
function parseConfig(text2, path, toml = false) {
|
|
14566
15159
|
try {
|
|
14567
15160
|
const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
|
|
@@ -14573,12 +15166,12 @@ function parseConfig(text2, path, toml = false) {
|
|
|
14573
15166
|
}
|
|
14574
15167
|
}
|
|
14575
15168
|
function safe4(path) {
|
|
14576
|
-
if (!
|
|
15169
|
+
if (!isAbsolute14(path) || path.includes("\x00"))
|
|
14577
15170
|
throw new Error("Expected an absolute native discovery path");
|
|
14578
|
-
for (let cursor = path;; cursor =
|
|
14579
|
-
if (
|
|
15171
|
+
for (let cursor = path;; cursor = dirname14(cursor)) {
|
|
15172
|
+
if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14580
15173
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
14581
|
-
if (
|
|
15174
|
+
if (dirname14(cursor) === cursor)
|
|
14582
15175
|
break;
|
|
14583
15176
|
}
|
|
14584
15177
|
}
|
|
@@ -14586,7 +15179,7 @@ function read(path, changes) {
|
|
|
14586
15179
|
safe4(path);
|
|
14587
15180
|
if (changes?.has(path))
|
|
14588
15181
|
return changes.get(path);
|
|
14589
|
-
const stat2 =
|
|
15182
|
+
const stat2 = lstatSync17(path, { throwIfNoEntry: false });
|
|
14590
15183
|
if (!stat2)
|
|
14591
15184
|
return null;
|
|
14592
15185
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
@@ -14594,6 +15187,22 @@ function read(path, changes) {
|
|
|
14594
15187
|
return readFileSync13(path, "utf8");
|
|
14595
15188
|
}
|
|
14596
15189
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
15190
|
+
if (source.hashMode === "claude-settings-v1") {
|
|
15191
|
+
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
15192
|
+
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
15193
|
+
const current = captureClaudeSettings(source.path, budget).sha256;
|
|
15194
|
+
if (current !== source.sha256)
|
|
15195
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
15196
|
+
return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
|
|
15197
|
+
}
|
|
15198
|
+
if (source.hashMode === "claude-marketplace-registry") {
|
|
15199
|
+
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))
|
|
15200
|
+
throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
|
|
15201
|
+
const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
|
|
15202
|
+
if (sha2562 !== source.sha256)
|
|
15203
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
15204
|
+
return sha2562;
|
|
15205
|
+
}
|
|
14597
15206
|
if (source.hashMode === "claude-plugin-registry") {
|
|
14598
15207
|
if (source.format !== undefined || source.fields !== undefined || !source.managedPlugins || changes?.has(source.path))
|
|
14599
15208
|
throw new Error("Managed plugin registry witnesses require verified native registrations");
|
|
@@ -14618,7 +15227,7 @@ function projected(source, changes, budget = discoveryByteBudget()) {
|
|
|
14618
15227
|
const object4 = source.format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, source.path, source.format === "toml");
|
|
14619
15228
|
if (!object4 || typeof object4 !== "object" || Array.isArray(object4))
|
|
14620
15229
|
throw new Error("Expected native discovery configuration object");
|
|
14621
|
-
return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((
|
|
15230
|
+
return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field2) => [field2, object4[field2] ?? null]))));
|
|
14622
15231
|
}
|
|
14623
15232
|
function verifyAgentDiscovery(binding) {
|
|
14624
15233
|
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 +15238,10 @@ function verifyAgentDiscovery(binding) {
|
|
|
14629
15238
|
verifyDiscoveryDirectories(binding.directories);
|
|
14630
15239
|
const budget = discoveryByteBudget();
|
|
14631
15240
|
for (const source of binding.sources) {
|
|
15241
|
+
assertMarketplaceBinding(binding, source);
|
|
14632
15242
|
if (source.hashMode === "claude-plugin-registry" && binding.agent !== "claude")
|
|
14633
15243
|
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((
|
|
15244
|
+
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
15245
|
throw new Error("Invalid native discovery projection");
|
|
14636
15246
|
if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
14637
15247
|
throw new Error("Invalid native discovery digest");
|
|
@@ -14641,9 +15251,18 @@ function verifyAgentDiscovery(binding) {
|
|
|
14641
15251
|
for (const root2 of binding.roots)
|
|
14642
15252
|
safe4(root2);
|
|
14643
15253
|
}
|
|
15254
|
+
function assertMarketplaceBinding(binding, source) {
|
|
15255
|
+
if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
15256
|
+
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
15257
|
+
if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
15258
|
+
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
15259
|
+
}
|
|
14644
15260
|
function rebindAgentDiscovery(binding, changes) {
|
|
14645
15261
|
const budget = discoveryByteBudget();
|
|
14646
|
-
return { ...binding, sources: binding.sources.map((source) =>
|
|
15262
|
+
return { ...binding, sources: binding.sources.map((source) => {
|
|
15263
|
+
assertMarketplaceBinding(binding, source);
|
|
15264
|
+
return { ...source, sha256: projected(source, changes, budget) };
|
|
15265
|
+
}) };
|
|
14647
15266
|
}
|
|
14648
15267
|
function captureDiscoveryByteSources(paths) {
|
|
14649
15268
|
if (!Array.isArray(paths) || paths.length > AGENT_POLICY_LIMITS.discoverySources || new Set(paths).size !== paths.length)
|
|
@@ -14658,10 +15277,10 @@ function captureDiscoveryPathSources(paths) {
|
|
|
14658
15277
|
return paths.map((path) => ({ path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget) }));
|
|
14659
15278
|
}
|
|
14660
15279
|
function resolveAgentDiscovery(options) {
|
|
14661
|
-
const
|
|
15280
|
+
const canonical4 = options.canonical ?? resolve14, home = resolve14(options.home), agent = options.agent;
|
|
14662
15281
|
const sources = [], roots = new Set, builtinNames = [];
|
|
14663
15282
|
const witness = (path, format, fields) => {
|
|
14664
|
-
const source = { path:
|
|
15283
|
+
const source = { path: canonical4(path), sha256: null, ...format ? { format, fields } : {} };
|
|
14665
15284
|
source.sha256 = projected(source);
|
|
14666
15285
|
sources.push(source);
|
|
14667
15286
|
return read(source.path);
|
|
@@ -14689,7 +15308,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14689
15308
|
continue;
|
|
14690
15309
|
if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
|
|
14691
15310
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
14692
|
-
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join23(home, normalized.slice(2)) :
|
|
15311
|
+
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join23(home, normalized.slice(2)) : resolve14(home, ".hermes", normalized);
|
|
14693
15312
|
safe4(path);
|
|
14694
15313
|
roots.add(path);
|
|
14695
15314
|
}
|
|
@@ -14698,21 +15317,21 @@ function resolveAgentDiscovery(options) {
|
|
|
14698
15317
|
const executable = Bun.which("gemini");
|
|
14699
15318
|
if (executable) {
|
|
14700
15319
|
let packageRoot;
|
|
14701
|
-
for (let directory =
|
|
15320
|
+
for (let directory = dirname14(realpathSync4(executable)), depth = 0;depth < 8; directory = dirname14(directory), depth++) {
|
|
14702
15321
|
const path = join23(directory, "package.json"), raw = read(path);
|
|
14703
15322
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
14704
15323
|
packageRoot = directory;
|
|
14705
15324
|
witness(path);
|
|
14706
15325
|
break;
|
|
14707
15326
|
}
|
|
14708
|
-
if (
|
|
15327
|
+
if (dirname14(directory) === directory)
|
|
14709
15328
|
break;
|
|
14710
15329
|
}
|
|
14711
15330
|
if (!packageRoot)
|
|
14712
15331
|
unresolved("installed Gemini package layout is not recognized");
|
|
14713
15332
|
const builtinRoot = join23(packageRoot, "bundle/builtin");
|
|
14714
15333
|
safe4(builtinRoot);
|
|
14715
|
-
if (!
|
|
15334
|
+
if (!lstatSync17(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
14716
15335
|
unresolved("installed Gemini builtin root is missing");
|
|
14717
15336
|
for (const name of readdirSync9(builtinRoot).sort()) {
|
|
14718
15337
|
const text2 = witness(join23(builtinRoot, name, "SKILL.md"));
|
|
@@ -14733,12 +15352,14 @@ function resolveAgentDiscovery(options) {
|
|
|
14733
15352
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
14734
15353
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
14735
15354
|
verifyAgentDiscovery(supplied);
|
|
14736
|
-
if (
|
|
15355
|
+
if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
|
|
15356
|
+
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
15357
|
+
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
14737
15358
|
throw new Error("Discovery review must include the agent configuration source");
|
|
14738
15359
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: [...sources, ...review.sources], ...agent === "gemini" ? { builtinNames } : {} };
|
|
14739
15360
|
}
|
|
14740
15361
|
function plugin(root2) {
|
|
14741
|
-
root2 =
|
|
15362
|
+
root2 = canonical4(root2);
|
|
14742
15363
|
safe4(root2);
|
|
14743
15364
|
const manifestPath = join23(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
14744
15365
|
const raw = witness(manifestPath);
|
|
@@ -14750,7 +15371,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14750
15371
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
14751
15372
|
const commands = join23(root2, "commands");
|
|
14752
15373
|
safe4(commands);
|
|
14753
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
15374
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync17(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length))
|
|
14754
15375
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
14755
15376
|
const rootSkill = witness(join23(root2, "SKILL.md"));
|
|
14756
15377
|
if (rootSkill !== null)
|
|
@@ -14761,18 +15382,18 @@ function resolveAgentDiscovery(options) {
|
|
|
14761
15382
|
if (agent === "claude")
|
|
14762
15383
|
roots.add(join23(root2, "skills"));
|
|
14763
15384
|
for (const value of declared) {
|
|
14764
|
-
const path =
|
|
15385
|
+
const path = resolve14(root2, value);
|
|
14765
15386
|
if (path !== root2 && !path.startsWith(root2 + sep5))
|
|
14766
15387
|
unresolved("plugin skill path escapes its root");
|
|
14767
|
-
roots.add(path.endsWith(`${sep5}SKILL.md`) ?
|
|
15388
|
+
roots.add(path.endsWith(`${sep5}SKILL.md`) ? dirname14(path) : path);
|
|
14768
15389
|
}
|
|
14769
15390
|
}
|
|
14770
15391
|
if (agent === "claude") {
|
|
14771
15392
|
const registrations = witness(join23(home, ".claude/plugins/installed_plugins.json"));
|
|
14772
15393
|
const installed = registrations === null ? {} : parseConfig(registrations, join23(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
14773
|
-
const commands =
|
|
15394
|
+
const commands = canonical4(join23(home, ".claude/commands"));
|
|
14774
15395
|
safe4(commands);
|
|
14775
|
-
if (
|
|
15396
|
+
if (lstatSync17(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length)
|
|
14776
15397
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
14777
15398
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
14778
15399
|
if (enabled === false)
|
|
@@ -14792,7 +15413,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14792
15413
|
unresolved("unsupported plugin enablement");
|
|
14793
15414
|
const split = id.lastIndexOf("@"), name = id.slice(0, split), marketplace = id.slice(split + 1);
|
|
14794
15415
|
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" || !
|
|
15416
|
+
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute14(market.source))
|
|
14796
15417
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
14797
15418
|
const catalogText = witness(join23(market.source, ".agents/plugins/marketplace.json"));
|
|
14798
15419
|
if (catalogText === null)
|
|
@@ -14800,7 +15421,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14800
15421
|
const catalog = parseConfig(catalogText, join23(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
14801
15422
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
14802
15423
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
14803
|
-
plugin(
|
|
15424
|
+
plugin(resolve14(market.source, matches[0].source.path));
|
|
14804
15425
|
}
|
|
14805
15426
|
} else if (agent === "opencode") {
|
|
14806
15427
|
if (witness(join23(home, ".config/opencode/opencode.jsonc")) !== null || witness(join23(home, ".config/opencode/config.json")) !== null)
|
|
@@ -14808,18 +15429,18 @@ function resolveAgentDiscovery(options) {
|
|
|
14808
15429
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
14809
15430
|
unresolved("external plugin or remote skill source");
|
|
14810
15431
|
for (const path of config.skills?.paths ?? []) {
|
|
14811
|
-
if (typeof path !== "string" || !
|
|
15432
|
+
if (typeof path !== "string" || !isAbsolute14(path))
|
|
14812
15433
|
unresolved("relative or malformed added skill root");
|
|
14813
15434
|
roots.add(path);
|
|
14814
15435
|
}
|
|
14815
15436
|
const localPlugins = join23(home, ".config/opencode/plugins");
|
|
14816
15437
|
safe4(localPlugins);
|
|
14817
|
-
if (
|
|
15438
|
+
if (lstatSync17(localPlugins, { throwIfNoEntry: false }) && readdirSync9(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
14818
15439
|
unresolved("additional local plugin hooks require review");
|
|
14819
15440
|
} else if (agent === "hermes") {
|
|
14820
15441
|
for (const directory of [join23(home, ".hermes/plugins"), join23(home, ".hermes/hermes-agent")]) {
|
|
14821
15442
|
safe4(directory);
|
|
14822
|
-
if (
|
|
15443
|
+
if (lstatSync17(directory, { throwIfNoEntry: false }))
|
|
14823
15444
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
14824
15445
|
}
|
|
14825
15446
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -14829,11 +15450,11 @@ function resolveAgentDiscovery(options) {
|
|
|
14829
15450
|
unresolved("custom extension or skill paths");
|
|
14830
15451
|
const directory = join23(home, ".gemini/extensions");
|
|
14831
15452
|
safe4(directory);
|
|
14832
|
-
if (
|
|
15453
|
+
if (lstatSync17(directory, { throwIfNoEntry: false }))
|
|
14833
15454
|
for (const name of readdirSync9(directory)) {
|
|
14834
15455
|
const root2 = join23(directory, name);
|
|
14835
15456
|
safe4(root2);
|
|
14836
|
-
if (!
|
|
15457
|
+
if (!lstatSync17(root2)?.isDirectory())
|
|
14837
15458
|
continue;
|
|
14838
15459
|
const raw = witness(join23(root2, "gemini-extension.json"));
|
|
14839
15460
|
if (raw === null)
|
|
@@ -14844,7 +15465,7 @@ function resolveAgentDiscovery(options) {
|
|
|
14844
15465
|
roots.add(join23(root2, "skills"));
|
|
14845
15466
|
}
|
|
14846
15467
|
}
|
|
14847
|
-
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join23(home, ".hermes/plugins"), join23(home, ".hermes/hermes-agent")].map((path) =>
|
|
15468
|
+
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join23(home, ".hermes/plugins"), join23(home, ".hermes/hermes-agent")].map((path) => canonical4(path))) } : {} };
|
|
14848
15469
|
}
|
|
14849
15470
|
|
|
14850
15471
|
// src/lib/agent-adapters.ts
|
|
@@ -14914,7 +15535,7 @@ export default async ({ directory }) => {
|
|
|
14914
15535
|
}
|
|
14915
15536
|
|
|
14916
15537
|
// src/lib/agent-codex.ts
|
|
14917
|
-
import { isDeepStrictEqual } from "util";
|
|
15538
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
14918
15539
|
var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
|
|
14919
15540
|
function assertCodexPathConfigEditable(text2) {
|
|
14920
15541
|
const before = Bun.TOML.parse(text2);
|
|
@@ -14924,7 +15545,7 @@ function assertCodexPathConfigEditable(text2) {
|
|
|
14924
15545
|
delete expected.skills.config;
|
|
14925
15546
|
try {
|
|
14926
15547
|
const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
|
|
14927
|
-
if (
|
|
15548
|
+
if (isDeepStrictEqual2(withoutSections, expected))
|
|
14928
15549
|
return;
|
|
14929
15550
|
} catch {}
|
|
14930
15551
|
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 +15583,7 @@ function disableCodexBundledSkills(text2) {
|
|
|
14962
15583
|
const candidates = new Set;
|
|
14963
15584
|
const consider = (candidate2) => {
|
|
14964
15585
|
try {
|
|
14965
|
-
if (
|
|
15586
|
+
if (isDeepStrictEqual2(Bun.TOML.parse(candidate2), expected))
|
|
14966
15587
|
candidates.add(candidate2);
|
|
14967
15588
|
} catch {}
|
|
14968
15589
|
};
|
|
@@ -14988,7 +15609,7 @@ function disableCodexBundledSkills(text2) {
|
|
|
14988
15609
|
}
|
|
14989
15610
|
|
|
14990
15611
|
// src/lib/agent-integration.ts
|
|
14991
|
-
var sha = (data) =>
|
|
15612
|
+
var sha = (data) => createHash14("sha256").update(data).digest("hex");
|
|
14992
15613
|
var ROOTS = [
|
|
14993
15614
|
["claude", ".claude/skills"],
|
|
14994
15615
|
["codex", ".codex/skills"],
|
|
@@ -15007,11 +15628,11 @@ var ROOTS = [
|
|
|
15007
15628
|
["copilot", ".github/skills"]
|
|
15008
15629
|
];
|
|
15009
15630
|
function assertSafePath(path) {
|
|
15010
|
-
let cursor =
|
|
15631
|
+
let cursor = resolve15(path);
|
|
15011
15632
|
while (true) {
|
|
15012
|
-
if (
|
|
15633
|
+
if (lstatSync18(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15013
15634
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
15014
|
-
const parent =
|
|
15635
|
+
const parent = dirname15(cursor);
|
|
15015
15636
|
if (parent === cursor)
|
|
15016
15637
|
return;
|
|
15017
15638
|
cursor = parent;
|
|
@@ -15020,16 +15641,16 @@ function assertSafePath(path) {
|
|
|
15020
15641
|
function readRootAlias(home, agent) {
|
|
15021
15642
|
assertSafePath(home);
|
|
15022
15643
|
const realHome = realpathSync5(home), alias = join25(realHome, `.${agent}`);
|
|
15023
|
-
const stat2 =
|
|
15644
|
+
const stat2 = lstatSync18(alias, { throwIfNoEntry: false, bigint: true });
|
|
15024
15645
|
if (!stat2?.isSymbolicLink())
|
|
15025
15646
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
15026
|
-
const link = readlinkSync2(alias), targetPath =
|
|
15647
|
+
const link = readlinkSync2(alias), targetPath = resolve15(dirname15(alias), link);
|
|
15027
15648
|
assertSafePath(targetPath);
|
|
15028
|
-
const targetStat =
|
|
15649
|
+
const targetStat = lstatSync18(targetPath, { throwIfNoEntry: false });
|
|
15029
15650
|
if (!targetStat?.isDirectory())
|
|
15030
15651
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
15031
15652
|
const target = realpathSync5(targetPath), inside = relative5(realHome, target);
|
|
15032
|
-
if (!inside || inside === ".." || inside.startsWith(`..${sep6}`) ||
|
|
15653
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep6}`) || isAbsolute15(inside))
|
|
15033
15654
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
15034
15655
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
15035
15656
|
}
|
|
@@ -15038,14 +15659,14 @@ function rootAliases(home, allowed = false) {
|
|
|
15038
15659
|
return [];
|
|
15039
15660
|
const result = [];
|
|
15040
15661
|
for (const agent of ["claude", "codex"])
|
|
15041
|
-
if (
|
|
15662
|
+
if (lstatSync18(join25(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15042
15663
|
result.push(readRootAlias(home, agent));
|
|
15043
15664
|
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
15665
|
throw new Error("Agent root alias targets must not overlap");
|
|
15045
15666
|
return result;
|
|
15046
15667
|
}
|
|
15047
15668
|
function canonicalAgentPath(path, aliases) {
|
|
15048
|
-
const absolute =
|
|
15669
|
+
const absolute = resolve15(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep6));
|
|
15049
15670
|
return binding ? join25(binding.target, relative5(binding.alias, absolute)) : absolute;
|
|
15050
15671
|
}
|
|
15051
15672
|
function recheckRootAliases(aliases) {
|
|
@@ -15059,35 +15680,35 @@ function recheckRootAliases(aliases) {
|
|
|
15059
15680
|
}
|
|
15060
15681
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
15061
15682
|
assertSafePath(path);
|
|
15062
|
-
const before =
|
|
15683
|
+
const before = lstatSync18(path);
|
|
15063
15684
|
if (!before.isFile() || before.size > maximum)
|
|
15064
15685
|
throw new Error("Unsupported or oversized native skill file");
|
|
15065
|
-
const descriptor =
|
|
15686
|
+
const descriptor = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
15066
15687
|
try {
|
|
15067
|
-
const opened =
|
|
15688
|
+
const opened = fstatSync11(descriptor);
|
|
15068
15689
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
|
|
15069
15690
|
throw new Error("Native skill file changed while reading");
|
|
15070
15691
|
const bytes = Buffer.allocUnsafe(before.size + 1);
|
|
15071
15692
|
let length = 0;
|
|
15072
15693
|
while (length < bytes.length) {
|
|
15073
|
-
const count =
|
|
15694
|
+
const count = readSync9(descriptor, bytes, length, bytes.length - length, null);
|
|
15074
15695
|
if (!count)
|
|
15075
15696
|
break;
|
|
15076
15697
|
length += count;
|
|
15077
15698
|
}
|
|
15078
|
-
const after =
|
|
15699
|
+
const after = fstatSync11(descriptor);
|
|
15079
15700
|
if (length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
15080
15701
|
throw new Error("Native skill file changed while reading");
|
|
15081
15702
|
return bytes.subarray(0, length);
|
|
15082
15703
|
} finally {
|
|
15083
|
-
|
|
15704
|
+
closeSync11(descriptor);
|
|
15084
15705
|
}
|
|
15085
15706
|
}
|
|
15086
15707
|
function treeHash(root2) {
|
|
15087
|
-
const hash2 =
|
|
15708
|
+
const hash2 = createHash14("sha256");
|
|
15088
15709
|
let bytes = 0, files = 0;
|
|
15089
15710
|
function visit2(path) {
|
|
15090
|
-
const stat2 =
|
|
15711
|
+
const stat2 = lstatSync18(path);
|
|
15091
15712
|
if (stat2.isSymbolicLink())
|
|
15092
15713
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
15093
15714
|
const rel = relative5(root2, path);
|
|
@@ -15113,11 +15734,11 @@ function treeHash(root2) {
|
|
|
15113
15734
|
function projectAncestorDirectories(projects) {
|
|
15114
15735
|
const directories = new Set;
|
|
15115
15736
|
for (const project of projects) {
|
|
15116
|
-
for (let path =
|
|
15737
|
+
for (let path = resolve15(project), depth = 0;; depth++) {
|
|
15117
15738
|
if (depth >= 100)
|
|
15118
15739
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
15119
15740
|
directories.add(path);
|
|
15120
|
-
const parent =
|
|
15741
|
+
const parent = dirname15(path);
|
|
15121
15742
|
if (parent === path)
|
|
15122
15743
|
break;
|
|
15123
15744
|
path = parent;
|
|
@@ -15144,10 +15765,10 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15144
15765
|
throw new Error("Native skill discovery metadata limit exceeded");
|
|
15145
15766
|
}
|
|
15146
15767
|
function emptySiblingCacheAlias(path, parent) {
|
|
15147
|
-
const link = readlinkSync2(path), target =
|
|
15148
|
-
if (
|
|
15768
|
+
const link = readlinkSync2(path), target = resolve15(parent, link);
|
|
15769
|
+
if (dirname15(target) !== parent || (isAbsolute15(link) ? link !== target : dirname15(link) !== "."))
|
|
15149
15770
|
return false;
|
|
15150
|
-
if (!
|
|
15771
|
+
if (!lstatSync18(target, { throwIfNoEntry: false })?.isDirectory())
|
|
15151
15772
|
return false;
|
|
15152
15773
|
const scan = cacheScans.get(target);
|
|
15153
15774
|
return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
|
|
@@ -15159,7 +15780,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15159
15780
|
assertSafePath(path);
|
|
15160
15781
|
if (!existsSync11(path))
|
|
15161
15782
|
return scan;
|
|
15162
|
-
const stat2 =
|
|
15783
|
+
const stat2 = lstatSync18(path);
|
|
15163
15784
|
if (stat2.isFile())
|
|
15164
15785
|
return scan;
|
|
15165
15786
|
if (!stat2.isDirectory())
|
|
@@ -15178,7 +15799,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15178
15799
|
}
|
|
15179
15800
|
const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
|
|
15180
15801
|
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:
|
|
15802
|
+
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: resolve15(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
15182
15803
|
return scan;
|
|
15183
15804
|
}
|
|
15184
15805
|
if (depth > (vendor ? 32 : 3))
|
|
@@ -15205,7 +15826,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
15205
15826
|
if (isVendor && !options.includeVendor)
|
|
15206
15827
|
continue;
|
|
15207
15828
|
const child = join25(path, name);
|
|
15208
|
-
if (isVendor &&
|
|
15829
|
+
if (isVendor && lstatSync18(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
15209
15830
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
15210
15831
|
if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
|
|
15211
15832
|
scan.entries++;
|
|
@@ -15327,7 +15948,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
15327
15948
|
function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
15328
15949
|
const bundled = disableCodexBundledSkills(text2);
|
|
15329
15950
|
let result = bundled;
|
|
15330
|
-
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [
|
|
15951
|
+
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve15(bridgePath), resolve15(join25(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
|
|
15331
15952
|
const previousConfig = Bun.TOML.parse(bundled).skills?.config;
|
|
15332
15953
|
if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
|
|
15333
15954
|
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 +15960,7 @@ function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
|
15339
15960
|
result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
|
|
15340
15961
|
const parsed = Bun.TOML.parse(section);
|
|
15341
15962
|
const declared = parsed.skills?.config?.[0];
|
|
15342
|
-
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [
|
|
15963
|
+
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve15(path), resolve15(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
|
|
15343
15964
|
return section;
|
|
15344
15965
|
found = true;
|
|
15345
15966
|
return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
|
|
@@ -15369,9 +15990,9 @@ function planAgentIntegration(options) {
|
|
|
15369
15990
|
throw new Error("Invalid existing Skills bridge policy");
|
|
15370
15991
|
if (policy.bridge?.agents !== undefined && (!Array.isArray(policy.bridge.agents) || policy.bridge.agents.some((agent) => !INTEGRATION_AGENTS.includes(agent))))
|
|
15371
15992
|
throw new Error("Invalid existing bridge agent inventory");
|
|
15372
|
-
for (const
|
|
15373
|
-
if (policy.bridge?.[
|
|
15374
|
-
throw new Error(`Invalid existing bridge ${
|
|
15993
|
+
for (const field2 of ["commands", "profiles"])
|
|
15994
|
+
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"))))
|
|
15995
|
+
throw new Error(`Invalid existing bridge ${field2} binding`);
|
|
15375
15996
|
const priorAgents = policy.bridge?.agents ?? [];
|
|
15376
15997
|
const profileId = options.profileId ?? policy.profileId ?? "default";
|
|
15377
15998
|
const validateProfile = (value) => {
|
|
@@ -15457,7 +16078,7 @@ function planAgentIntegration(options) {
|
|
|
15457
16078
|
version: CLI_BRIDGE_VERSION,
|
|
15458
16079
|
digest: CLI_BRIDGE_DIGEST,
|
|
15459
16080
|
agents: [...new Set([...priorAgents, ...options.agents])].sort(),
|
|
15460
|
-
home:
|
|
16081
|
+
home: resolve15(home),
|
|
15461
16082
|
includeVendor: true,
|
|
15462
16083
|
...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
16084
|
commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
|
|
@@ -15474,7 +16095,7 @@ function planAgentIntegration(options) {
|
|
|
15474
16095
|
}
|
|
15475
16096
|
function atomicWrite2(path, content) {
|
|
15476
16097
|
assertSafePath(path);
|
|
15477
|
-
mkdirSync7(
|
|
16098
|
+
mkdirSync7(dirname15(path), { recursive: true, mode: 448 });
|
|
15478
16099
|
const temporary = `${path}.skills-${randomUUID3()}`;
|
|
15479
16100
|
try {
|
|
15480
16101
|
writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
|
|
@@ -15487,10 +16108,10 @@ function atomicWrite2(path, content) {
|
|
|
15487
16108
|
function applyAgentIntegration(plan) {
|
|
15488
16109
|
const policyPath = join25(plan.dataDir, "agent-policy.json");
|
|
15489
16110
|
assertSafePath(policyPath);
|
|
15490
|
-
const currentText = (path) =>
|
|
16111
|
+
const currentText = (path) => resolve15(path) === resolve15(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
15491
16112
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
15492
16113
|
for (const change of plan.changes)
|
|
15493
|
-
if (
|
|
16114
|
+
if (resolve15(change.path) === resolve15(policyPath))
|
|
15494
16115
|
parseManagedSkillPolicy(change.after);
|
|
15495
16116
|
const aliases = plan.rootAliases ?? [];
|
|
15496
16117
|
recheckRootAliases(aliases);
|
|
@@ -15520,7 +16141,7 @@ function applyAgentIntegration(plan) {
|
|
|
15520
16141
|
recheckRootAliases(aliases);
|
|
15521
16142
|
if (currentText(change.path) !== change.before)
|
|
15522
16143
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15523
|
-
for (let directory =
|
|
16144
|
+
for (let directory = dirname15(change.path);!existsSync11(directory); directory = dirname15(directory))
|
|
15524
16145
|
createdDirectories.add(directory);
|
|
15525
16146
|
const after = change.after;
|
|
15526
16147
|
atomicWrite2(change.path, after);
|
|
@@ -15560,11 +16181,11 @@ function applyAgentIntegration(plan) {
|
|
|
15560
16181
|
}
|
|
15561
16182
|
function syncArchiveDirectory(path) {
|
|
15562
16183
|
assertSafePath(path);
|
|
15563
|
-
const descriptor =
|
|
16184
|
+
const descriptor = openSync11(path, constants10.O_RDONLY | constants10.O_DIRECTORY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
15564
16185
|
try {
|
|
15565
|
-
|
|
16186
|
+
fsyncSync3(descriptor);
|
|
15566
16187
|
} finally {
|
|
15567
|
-
|
|
16188
|
+
closeSync11(descriptor);
|
|
15568
16189
|
}
|
|
15569
16190
|
}
|
|
15570
16191
|
function writeArchiveJournal(path, value) {
|
|
@@ -15572,18 +16193,18 @@ function writeArchiveJournal(path, value) {
|
|
|
15572
16193
|
const temporary = `${path}.skills-${randomUUID3()}`;
|
|
15573
16194
|
let descriptor;
|
|
15574
16195
|
try {
|
|
15575
|
-
descriptor =
|
|
16196
|
+
descriptor = openSync11(temporary, constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW, 384);
|
|
15576
16197
|
writeFileSync5(descriptor, `${JSON.stringify(value)}
|
|
15577
16198
|
`);
|
|
15578
|
-
|
|
15579
|
-
|
|
16199
|
+
fsyncSync3(descriptor);
|
|
16200
|
+
closeSync11(descriptor);
|
|
15580
16201
|
descriptor = undefined;
|
|
15581
16202
|
renameSync4(temporary, path);
|
|
15582
|
-
syncArchiveDirectory(
|
|
16203
|
+
syncArchiveDirectory(dirname15(path));
|
|
15583
16204
|
} finally {
|
|
15584
16205
|
if (descriptor !== undefined)
|
|
15585
|
-
|
|
15586
|
-
if (
|
|
16206
|
+
closeSync11(descriptor);
|
|
16207
|
+
if (lstatSync18(temporary, { throwIfNoEntry: false }))
|
|
15587
16208
|
unlinkSync4(temporary);
|
|
15588
16209
|
}
|
|
15589
16210
|
}
|
|
@@ -15616,7 +16237,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15616
16237
|
const moved = [];
|
|
15617
16238
|
const verifyArchive = (entry) => {
|
|
15618
16239
|
assertSafePath(entry.archive);
|
|
15619
|
-
const stat2 =
|
|
16240
|
+
const stat2 = lstatSync18(entry.archive);
|
|
15620
16241
|
if (entry.discoveryOnly ? !stat2.isFile() : !stat2.isDirectory())
|
|
15621
16242
|
throw new Error("Archive type changed");
|
|
15622
16243
|
if ((entry.discoveryOnly ? sha(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -15624,14 +16245,14 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15624
16245
|
};
|
|
15625
16246
|
assertSafePath(archiveRoot);
|
|
15626
16247
|
const created = [];
|
|
15627
|
-
for (let path = archiveRoot;!existsSync11(path); path =
|
|
16248
|
+
for (let path = archiveRoot;!existsSync11(path); path = dirname15(path))
|
|
15628
16249
|
created.push(path);
|
|
15629
16250
|
mkdirSync7(archiveRoot, { recursive: true, mode: 448 });
|
|
15630
16251
|
try {
|
|
15631
16252
|
for (const directory of created)
|
|
15632
16253
|
syncArchiveDirectory(directory);
|
|
15633
16254
|
if (created.length)
|
|
15634
|
-
syncArchiveDirectory(
|
|
16255
|
+
syncArchiveDirectory(dirname15(created.at(-1)));
|
|
15635
16256
|
writeArchiveJournal(receiptPath, journal);
|
|
15636
16257
|
for (const [index, entry] of selected.entries()) {
|
|
15637
16258
|
recheckRootAliases(aliases);
|
|
@@ -15643,7 +16264,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15643
16264
|
writeArchiveJournal(receiptPath, journal);
|
|
15644
16265
|
renameSync4(move.source, move.archive);
|
|
15645
16266
|
moved.push(move);
|
|
15646
|
-
syncArchiveDirectory(
|
|
16267
|
+
syncArchiveDirectory(dirname15(move.source));
|
|
15647
16268
|
syncArchiveDirectory(archiveRoot);
|
|
15648
16269
|
verifyArchive(move);
|
|
15649
16270
|
move.status = "archived";
|
|
@@ -15664,7 +16285,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15664
16285
|
recheckRootAliases(aliases);
|
|
15665
16286
|
assertSafePath(entry.source);
|
|
15666
16287
|
assertSafePath(entry.archive);
|
|
15667
|
-
if (
|
|
16288
|
+
if (lstatSync18(entry.source, { throwIfNoEntry: false })) {
|
|
15668
16289
|
entry.status = "recovery-required";
|
|
15669
16290
|
entry.conflict = "source-occupied";
|
|
15670
16291
|
continue;
|
|
@@ -15678,10 +16299,10 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15678
16299
|
}
|
|
15679
16300
|
if (entry.discoveryOnly) {
|
|
15680
16301
|
linkSync2(entry.archive, entry.source);
|
|
15681
|
-
syncArchiveDirectory(
|
|
16302
|
+
syncArchiveDirectory(dirname15(entry.source));
|
|
15682
16303
|
unlinkSync4(entry.archive);
|
|
15683
16304
|
} else {
|
|
15684
|
-
if (
|
|
16305
|
+
if (lstatSync18(entry.source, { throwIfNoEntry: false })) {
|
|
15685
16306
|
entry.status = "recovery-required";
|
|
15686
16307
|
entry.conflict = "source-occupied";
|
|
15687
16308
|
continue;
|
|
@@ -15689,7 +16310,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15689
16310
|
renameSync4(entry.archive, entry.source);
|
|
15690
16311
|
}
|
|
15691
16312
|
entry.status = "restored";
|
|
15692
|
-
syncArchiveDirectory(
|
|
16313
|
+
syncArchiveDirectory(dirname15(entry.source));
|
|
15693
16314
|
syncArchiveDirectory(archiveRoot);
|
|
15694
16315
|
} catch {
|
|
15695
16316
|
entry.status = "recovery-required";
|
|
@@ -15728,7 +16349,7 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
15728
16349
|
function hasSkillsOwnershipMarker(dir) {
|
|
15729
16350
|
const path = join26(dir, SYNC_MARKER_FILE);
|
|
15730
16351
|
try {
|
|
15731
|
-
if (!
|
|
16352
|
+
if (!lstatSync19(path).isFile())
|
|
15732
16353
|
return false;
|
|
15733
16354
|
const marker = JSON.parse(readFileSync15(path, "utf8"));
|
|
15734
16355
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -15954,9 +16575,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
15954
16575
|
const action = dirExists ? "update" : "create";
|
|
15955
16576
|
if (options.dryRun)
|
|
15956
16577
|
return { action, path: skillMdPath };
|
|
15957
|
-
const parentDir =
|
|
16578
|
+
const parentDir = dirname16(dir);
|
|
15958
16579
|
mkdirSync8(parentDir, { recursive: true });
|
|
15959
|
-
const transactionDir = mkdtempSync2(join26(parentDir, `.hasna-skills-write-${
|
|
16580
|
+
const transactionDir = mkdtempSync2(join26(parentDir, `.hasna-skills-write-${basename9(dir)}-`));
|
|
15960
16581
|
const candidateDir = join26(transactionDir, "candidate");
|
|
15961
16582
|
const backupDir = join26(transactionDir, "backup");
|
|
15962
16583
|
const candidateSkillMdPath = join26(candidateDir, "SKILL.md");
|
|
@@ -16445,7 +17066,7 @@ function loadProjectConfigCompat(targetDir) {
|
|
|
16445
17066
|
return loadProjectConfig(targetDir);
|
|
16446
17067
|
}
|
|
16447
17068
|
// src/lib/run-state.ts
|
|
16448
|
-
import { createHash as
|
|
17069
|
+
import { createHash as createHash15, randomBytes } from "crypto";
|
|
16449
17070
|
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync18, readdirSync as readdirSync12, statSync as statSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
16450
17071
|
import { extname, join as join29, relative as relative6 } from "path";
|
|
16451
17072
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
@@ -16572,7 +17193,7 @@ function collectRunArtifacts(context) {
|
|
|
16572
17193
|
artifacts.push({
|
|
16573
17194
|
path: toProjectRelative(context.targetDir, path),
|
|
16574
17195
|
mime: mimeForPath(path),
|
|
16575
|
-
sha256:
|
|
17196
|
+
sha256: createHash15("sha256").update(bytes).digest("hex"),
|
|
16576
17197
|
sizeBytes: stat2.size
|
|
16577
17198
|
});
|
|
16578
17199
|
}
|
|
@@ -16700,7 +17321,7 @@ __export(exports_external, {
|
|
|
16700
17321
|
pipe: () => pipe,
|
|
16701
17322
|
partialRecord: () => partialRecord,
|
|
16702
17323
|
parseAsync: () => parseAsync2,
|
|
16703
|
-
parse: () =>
|
|
17324
|
+
parse: () => parse9,
|
|
16704
17325
|
overwrite: () => _overwrite,
|
|
16705
17326
|
output: () => output,
|
|
16706
17327
|
optional: () => optional,
|
|
@@ -16920,7 +17541,7 @@ __export(exports_core2, {
|
|
|
16920
17541
|
prettifyError: () => prettifyError,
|
|
16921
17542
|
parseURLObject: () => parseURLObject,
|
|
16922
17543
|
parseAsync: () => parseAsync,
|
|
16923
|
-
parse: () =>
|
|
17544
|
+
parse: () => parse7,
|
|
16924
17545
|
meta: () => meta,
|
|
16925
17546
|
mergeValues: () => mergeValues,
|
|
16926
17547
|
memoizer: () => memoizer,
|
|
@@ -18374,7 +18995,7 @@ var _parse = (_Err) => {
|
|
|
18374
18995
|
};
|
|
18375
18996
|
return fn;
|
|
18376
18997
|
};
|
|
18377
|
-
var
|
|
18998
|
+
var parse7 = /* @__PURE__ */ _parse($ZodRealError);
|
|
18378
18999
|
var _parseAsync = (_Err) => {
|
|
18379
19000
|
const fn = async (schema, value, _ctx, params) => {
|
|
18380
19001
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
@@ -18445,18 +19066,18 @@ var validateAsync = async (schema, value, _ctx) => {
|
|
|
18445
19066
|
return result.issues.length === 0;
|
|
18446
19067
|
};
|
|
18447
19068
|
var _encode = (_Err) => {
|
|
18448
|
-
const
|
|
19069
|
+
const parse8 = _parse(_Err);
|
|
18449
19070
|
const fn = (schema, value, _ctx, _params) => {
|
|
18450
19071
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
18451
|
-
return
|
|
19072
|
+
return parse8(schema, value, ctx, finalizeParams(fn, _params));
|
|
18452
19073
|
};
|
|
18453
19074
|
return fn;
|
|
18454
19075
|
};
|
|
18455
19076
|
var encode = /* @__PURE__ */ _encode($ZodRealError);
|
|
18456
19077
|
var _decode = (_Err) => {
|
|
18457
|
-
const
|
|
19078
|
+
const parse8 = _parse(_Err);
|
|
18458
19079
|
const fn = (schema, value, _ctx, _params) => {
|
|
18459
|
-
return
|
|
19080
|
+
return parse8(schema, value, _ctx, finalizeParams(fn, _params));
|
|
18460
19081
|
};
|
|
18461
19082
|
return fn;
|
|
18462
19083
|
};
|
|
@@ -20101,14 +20722,14 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
20101
20722
|
const shape = zod.def.shape;
|
|
20102
20723
|
const propValues = {};
|
|
20103
20724
|
for (const key in shape) {
|
|
20104
|
-
const
|
|
20105
|
-
if (
|
|
20725
|
+
const field2 = shape[key]._zod;
|
|
20726
|
+
if (field2.values) {
|
|
20106
20727
|
if (!Object.prototype.hasOwnProperty.call(propValues, key)) {
|
|
20107
20728
|
assignProp(propValues, key, new Set);
|
|
20108
20729
|
}
|
|
20109
|
-
for (const v of
|
|
20730
|
+
for (const v of field2.values)
|
|
20110
20731
|
propValues[key].add(v);
|
|
20111
|
-
if (
|
|
20732
|
+
if (field2.optin !== undefined)
|
|
20112
20733
|
propValues[key].add(undefined);
|
|
20113
20734
|
}
|
|
20114
20735
|
}
|
|
@@ -21379,10 +22000,10 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
|
|
|
21379
22000
|
throw new Error("implement() must be called with a function");
|
|
21380
22001
|
}
|
|
21381
22002
|
return Object.defineProperty(function(...args) {
|
|
21382
|
-
const parsedArgs = inst._def.input ?
|
|
22003
|
+
const parsedArgs = inst._def.input ? parse7(inst._def.input, args) : args;
|
|
21383
22004
|
const result = Reflect.apply(func, this, parsedArgs);
|
|
21384
22005
|
if (inst._def.output) {
|
|
21385
|
-
return
|
|
22006
|
+
return parse7(inst._def.output, result);
|
|
21386
22007
|
}
|
|
21387
22008
|
return result;
|
|
21388
22009
|
}, "_zod", { value: inst._zod, enumerable: false });
|
|
@@ -32301,11 +32922,11 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
32301
32922
|
}
|
|
32302
32923
|
const allKeys = new Set(Object.keys(shape));
|
|
32303
32924
|
const requiredKeys = new Set([...allKeys].filter((key) => {
|
|
32304
|
-
const
|
|
32925
|
+
const field2 = def.shape[key];
|
|
32305
32926
|
if (ctx.io === "input") {
|
|
32306
|
-
return inputOptin(
|
|
32927
|
+
return inputOptin(field2) === undefined;
|
|
32307
32928
|
} else {
|
|
32308
|
-
return
|
|
32929
|
+
return field2._zod.optout === undefined;
|
|
32309
32930
|
}
|
|
32310
32931
|
}));
|
|
32311
32932
|
if (requiredKeys.size > 0) {
|
|
@@ -33020,7 +33641,7 @@ var ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer2, undefi
|
|
|
33020
33641
|
});
|
|
33021
33642
|
|
|
33022
33643
|
// node_modules/zod/v4/classic/parse.js
|
|
33023
|
-
var
|
|
33644
|
+
var parse9 = /* @__PURE__ */ _parse(ZodRealError);
|
|
33024
33645
|
var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
33025
33646
|
var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
33026
33647
|
var safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -33156,7 +33777,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
33156
33777
|
exports_util.own(this, "~standard", value);
|
|
33157
33778
|
},
|
|
33158
33779
|
parse: function _parse2(data, params) {
|
|
33159
|
-
return
|
|
33780
|
+
return parse9(this, data, params, { callee: _parse2 });
|
|
33160
33781
|
},
|
|
33161
33782
|
parseAsync: async function _parseAsync2(data, params) {
|
|
33162
33783
|
return await parseAsync2(this, data, params, { callee: _parseAsync2 });
|
|
@@ -35987,7 +36608,7 @@ function clone2(value) {
|
|
|
35987
36608
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
35988
36609
|
}
|
|
35989
36610
|
// src/lib/canonical-json.ts
|
|
35990
|
-
import { createHash as
|
|
36611
|
+
import { createHash as createHash16 } from "crypto";
|
|
35991
36612
|
function canonicalJson2(value, maximumBytes = 2 * 1024 * 1024) {
|
|
35992
36613
|
return canonicalJsonAtDepth(value, maximumBytes, 64);
|
|
35993
36614
|
}
|
|
@@ -36031,7 +36652,7 @@ function canonicalJsonAtDepth(value, maximumBytes, maximumDepth) {
|
|
|
36031
36652
|
return visit3(value, 0);
|
|
36032
36653
|
}
|
|
36033
36654
|
function canonicalJsonSha256(value, maximumBytes) {
|
|
36034
|
-
return
|
|
36655
|
+
return createHash16("sha256").update(canonicalJson2(value, maximumBytes)).digest("hex");
|
|
36035
36656
|
}
|
|
36036
36657
|
|
|
36037
36658
|
// src/lib/remote-recurring-contract.ts
|
|
@@ -36913,15 +37534,41 @@ function object5(value) {
|
|
|
36913
37534
|
return value;
|
|
36914
37535
|
}
|
|
36915
37536
|
|
|
37537
|
+
// src/lib/remote-credit-checkout.ts
|
|
37538
|
+
var creditCheckoutKeyPattern = /^[A-Za-z0-9._:-]{8,255}$/;
|
|
37539
|
+
function creditCheckoutRequestKey(value) {
|
|
37540
|
+
if (value === undefined)
|
|
37541
|
+
return crypto.randomUUID();
|
|
37542
|
+
if (typeof value !== "string" || !creditCheckoutKeyPattern.test(value))
|
|
37543
|
+
throw new Error("Checkout idempotency key must be 8-255 URL-safe characters");
|
|
37544
|
+
return value;
|
|
37545
|
+
}
|
|
37546
|
+
var creditCheckoutMessages = {
|
|
37547
|
+
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.",
|
|
37548
|
+
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.",
|
|
37549
|
+
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.",
|
|
37550
|
+
CREDIT_CHECKOUT_FULFILLED: "This checkout is already fulfilled. Inspect the account balance; do not create another checkout to recover this purchase."
|
|
37551
|
+
};
|
|
37552
|
+
function creditCheckoutFailure(value, status, key) {
|
|
37553
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
37554
|
+
return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
|
|
37555
|
+
const row = value;
|
|
37556
|
+
if (row.requestIdempotencyKey !== undefined && row.requestIdempotencyKey !== key)
|
|
37557
|
+
return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
|
|
37558
|
+
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";
|
|
37559
|
+
const delay = row.retryAfterSeconds;
|
|
37560
|
+
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 } : {} : {} };
|
|
37561
|
+
}
|
|
37562
|
+
|
|
36916
37563
|
// src/lib/remote-profile.ts
|
|
36917
|
-
function customerNamePatch(input2,
|
|
36918
|
-
if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2,
|
|
37564
|
+
function customerNamePatch(input2, field2) {
|
|
37565
|
+
if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field2))
|
|
36919
37566
|
throw new Error("Provide only the requested name field.");
|
|
36920
|
-
const value = input2[
|
|
37567
|
+
const value = input2[field2];
|
|
36921
37568
|
if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
|
|
36922
37569
|
throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
|
|
36923
37570
|
}
|
|
36924
|
-
return { [
|
|
37571
|
+
return { [field2]: value.trim() };
|
|
36925
37572
|
}
|
|
36926
37573
|
function isRecord4(value) {
|
|
36927
37574
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -36966,10 +37613,10 @@ async function readQuoteUnavailableCode(response) {
|
|
|
36966
37613
|
let timer;
|
|
36967
37614
|
let deadlineExceeded = false;
|
|
36968
37615
|
const expired = Symbol("quote body deadline");
|
|
36969
|
-
const deadline = new Promise((
|
|
37616
|
+
const deadline = new Promise((resolve16) => {
|
|
36970
37617
|
timer = setTimeout(() => {
|
|
36971
37618
|
deadlineExceeded = true;
|
|
36972
|
-
|
|
37619
|
+
resolve16(expired);
|
|
36973
37620
|
reader.cancel().catch(() => {});
|
|
36974
37621
|
}, 1500);
|
|
36975
37622
|
});
|
|
@@ -37086,6 +37733,20 @@ class RemoteRequestError extends Error {
|
|
|
37086
37733
|
}
|
|
37087
37734
|
}
|
|
37088
37735
|
|
|
37736
|
+
class RemoteCreditCheckoutError extends RemoteRequestError {
|
|
37737
|
+
code;
|
|
37738
|
+
requestIdempotencyKey;
|
|
37739
|
+
retryAfterSeconds;
|
|
37740
|
+
constructor(code, status, requestIdempotencyKey, retryAfterSeconds) {
|
|
37741
|
+
super("/api/v1/billing/credits", status);
|
|
37742
|
+
this.code = code;
|
|
37743
|
+
this.requestIdempotencyKey = requestIdempotencyKey;
|
|
37744
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
37745
|
+
this.name = "RemoteCreditCheckoutError";
|
|
37746
|
+
this.message = creditCheckoutMessages[code];
|
|
37747
|
+
}
|
|
37748
|
+
}
|
|
37749
|
+
|
|
37089
37750
|
class RemoteQuoteUnavailableError extends RemoteRequestError {
|
|
37090
37751
|
code;
|
|
37091
37752
|
constructor(path, code) {
|
|
@@ -37574,14 +38235,43 @@ class RemoteSkillsClient {
|
|
|
37574
38235
|
async listCreditPacks() {
|
|
37575
38236
|
return parseRemoteCreditPacks(await (await this.requestNewRoute("/api/v1/billing/credits")).json());
|
|
37576
38237
|
}
|
|
37577
|
-
async createCreditCheckout(packId) {
|
|
38238
|
+
async createCreditCheckout(packId, options = {}) {
|
|
38239
|
+
const requestIdempotencyKey = creditCheckoutRequestKey(options.idempotencyKey);
|
|
37578
38240
|
const packs = await this.listCreditPacks();
|
|
37579
38241
|
if (!packs.some((pack) => pack.id === packId))
|
|
37580
38242
|
throw new Error("Choose a credit pack returned by skills credits packs");
|
|
37581
|
-
|
|
37582
|
-
|
|
37583
|
-
|
|
37584
|
-
|
|
38243
|
+
let response;
|
|
38244
|
+
try {
|
|
38245
|
+
response = await this.request("/api/v1/billing/credits", {
|
|
38246
|
+
method: "POST",
|
|
38247
|
+
body: JSON.stringify({ packId, idempotencyKey: requestIdempotencyKey })
|
|
38248
|
+
});
|
|
38249
|
+
} catch {
|
|
38250
|
+
throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", 0, requestIdempotencyKey);
|
|
38251
|
+
}
|
|
38252
|
+
if (response.status === 404 || response.status === 405) {
|
|
38253
|
+
response.body?.cancel().catch(() => {});
|
|
38254
|
+
throw new RemoteRouteUnsupportedError("/api/v1/billing/credits", response.status, this.apiUrl);
|
|
38255
|
+
}
|
|
38256
|
+
let value;
|
|
38257
|
+
try {
|
|
38258
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 4096)));
|
|
38259
|
+
} catch {
|
|
38260
|
+
throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
|
|
38261
|
+
}
|
|
38262
|
+
if (!response.ok) {
|
|
38263
|
+
const failure = creditCheckoutFailure(value, response.status, requestIdempotencyKey);
|
|
38264
|
+
throw new RemoteCreditCheckoutError(failure.code, response.status, requestIdempotencyKey, failure.retryAfterSeconds);
|
|
38265
|
+
}
|
|
38266
|
+
try {
|
|
38267
|
+
const checkout = parseRemoteCheckout(value);
|
|
38268
|
+
const echo = value.requestIdempotencyKey;
|
|
38269
|
+
if (echo !== undefined && echo !== requestIdempotencyKey)
|
|
38270
|
+
throw new Error("checkout request key changed");
|
|
38271
|
+
return { ...checkout, requestIdempotencyKey };
|
|
38272
|
+
} catch {
|
|
38273
|
+
throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
|
|
38274
|
+
}
|
|
37585
38275
|
}
|
|
37586
38276
|
async getUsage() {
|
|
37587
38277
|
return this.arrayResponse("/api/v1/billing/usage");
|
|
@@ -37814,13 +38504,13 @@ class RemoteSkillsClient {
|
|
|
37814
38504
|
return normalizeUpdatedSincePage(await response.json());
|
|
37815
38505
|
}
|
|
37816
38506
|
}
|
|
37817
|
-
function requireOptionalString(record7,
|
|
37818
|
-
if (record7[
|
|
38507
|
+
function requireOptionalString(record7, field2) {
|
|
38508
|
+
if (record7[field2] === undefined)
|
|
37819
38509
|
return;
|
|
37820
|
-
if (typeof record7[
|
|
37821
|
-
throw new Error(`Remote payload did not match the expected contract (${
|
|
38510
|
+
if (typeof record7[field2] !== "string") {
|
|
38511
|
+
throw new Error(`Remote payload did not match the expected contract (${field2} must be a string when present)`);
|
|
37822
38512
|
}
|
|
37823
|
-
return record7[
|
|
38513
|
+
return record7[field2];
|
|
37824
38514
|
}
|
|
37825
38515
|
var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
37826
38516
|
function isVersionRecord(value) {
|
|
@@ -38154,10 +38844,10 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
38154
38844
|
saveSchedules(data, targetDir);
|
|
38155
38845
|
}
|
|
38156
38846
|
// src/lib/revision.ts
|
|
38157
|
-
import { createHash as
|
|
38847
|
+
import { createHash as createHash17 } from "crypto";
|
|
38158
38848
|
var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
38159
38849
|
function revisionIdOf(content) {
|
|
38160
|
-
const
|
|
38850
|
+
const canonical4 = JSON.stringify({
|
|
38161
38851
|
slug: content.slug,
|
|
38162
38852
|
displayName: content.displayName,
|
|
38163
38853
|
description: content.description,
|
|
@@ -38170,14 +38860,14 @@ function revisionIdOf(content) {
|
|
|
38170
38860
|
bundleSha256: content.bundleSha256 ?? null,
|
|
38171
38861
|
bundleByteSize: content.bundleByteSize ?? null
|
|
38172
38862
|
});
|
|
38173
|
-
return
|
|
38863
|
+
return createHash17("sha256").update(canonical4).digest("hex");
|
|
38174
38864
|
}
|
|
38175
38865
|
function revisionIdOfRecord(record7) {
|
|
38176
38866
|
return revisionIdOf(record7);
|
|
38177
38867
|
}
|
|
38178
38868
|
// src/lib/pull.ts
|
|
38179
38869
|
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
|
|
38870
|
+
import { dirname as dirname17, join as join31 } from "path";
|
|
38181
38871
|
|
|
38182
38872
|
// src/lib/skill-bundles.ts
|
|
38183
38873
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -38519,7 +39209,7 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
38519
39209
|
try {
|
|
38520
39210
|
for (const entry of entries) {
|
|
38521
39211
|
const destination = join31(staging, entry.path);
|
|
38522
|
-
mkdirSync12(
|
|
39212
|
+
mkdirSync12(dirname17(destination), { recursive: true });
|
|
38523
39213
|
writeFileSync10(destination, entry.bytes, { mode: entry.mode });
|
|
38524
39214
|
}
|
|
38525
39215
|
writePullMarker(staging, {
|
|
@@ -38613,7 +39303,7 @@ function splitNameVersion(raw) {
|
|
|
38613
39303
|
return version2 ? { name, version: version2 } : { name };
|
|
38614
39304
|
}
|
|
38615
39305
|
// src/lib/recurring-surface.ts
|
|
38616
|
-
import { isAbsolute as
|
|
39306
|
+
import { isAbsolute as isAbsolute16 } from "path";
|
|
38617
39307
|
|
|
38618
39308
|
// src/lib/remote-invitation-recovery.ts
|
|
38619
39309
|
class InvitationEmailInputError extends Error {
|
|
@@ -38711,7 +39401,7 @@ async function requestInvitationEmail(origin, action, input2) {
|
|
|
38711
39401
|
}
|
|
38712
39402
|
|
|
38713
39403
|
// src/lib/remote-private-publications.ts
|
|
38714
|
-
import { createHash as
|
|
39404
|
+
import { createHash as createHash18 } from "crypto";
|
|
38715
39405
|
var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
|
|
38716
39406
|
var failures2 = {
|
|
38717
39407
|
INVALID_REQUEST: [400, "The publication request is invalid."],
|
|
@@ -38758,7 +39448,7 @@ var hash3 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
|
|
|
38758
39448
|
var record8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
38759
39449
|
var exact = (v, keys) => record8(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
|
|
38760
39450
|
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) =>
|
|
39451
|
+
var publicationSha256 = (bytes) => createHash18("sha256").update(bytes).digest("hex");
|
|
38762
39452
|
function checkedPublicationDeclaration(value) {
|
|
38763
39453
|
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
39454
|
return bad();
|
|
@@ -39003,12 +39693,12 @@ class RemotePrivatePublicationsClient {
|
|
|
39003
39693
|
previous = view;
|
|
39004
39694
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
39005
39695
|
return view;
|
|
39006
|
-
await new Promise((
|
|
39696
|
+
await new Promise((resolve16) => {
|
|
39007
39697
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
39008
39698
|
function done() {
|
|
39009
39699
|
clearTimeout(timer);
|
|
39010
39700
|
options.signal?.removeEventListener("abort", done);
|
|
39011
|
-
|
|
39701
|
+
resolve16();
|
|
39012
39702
|
}
|
|
39013
39703
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
39014
39704
|
});
|
|
@@ -39272,7 +39962,7 @@ var stateSchema = exports_external.object({
|
|
|
39272
39962
|
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
39963
|
var context = { userId: uuid13.optional(), membershipId: uuid13.optional() };
|
|
39274
39964
|
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(
|
|
39965
|
+
var directory = exports_external.string().max(4096).refine(isAbsolute16, "Use an absolute recovery directory");
|
|
39276
39966
|
var email4 = exports_external.string().trim().toLowerCase().email().max(254);
|
|
39277
39967
|
var code = exports_external.string().regex(/^\d{6}$/);
|
|
39278
39968
|
var recurringSurfaceSchemas = {
|
|
@@ -39423,7 +40113,7 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
39423
40113
|
}
|
|
39424
40114
|
// src/lib/registry-sync.ts
|
|
39425
40115
|
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
39426
|
-
import { dirname as
|
|
40116
|
+
import { dirname as dirname18, relative as relative7 } from "path";
|
|
39427
40117
|
function createRegistrySyncArtifact(options = {}) {
|
|
39428
40118
|
const profile = options.profile ?? "all";
|
|
39429
40119
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -39480,7 +40170,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
39480
40170
|
};
|
|
39481
40171
|
}
|
|
39482
40172
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
39483
|
-
mkdirSync13(
|
|
40173
|
+
mkdirSync13(dirname18(path), { recursive: true });
|
|
39484
40174
|
writeFileSync11(path, `${JSON.stringify(artifact, null, 2)}
|
|
39485
40175
|
`);
|
|
39486
40176
|
}
|
|
@@ -39498,7 +40188,7 @@ var REMOTE_CUSTOMER_OPERATIONS = [
|
|
|
39498
40188
|
{ name: "list_remote_skills", title: "List Remote Skills", parameter: null, mutates: false, invoke: (client2) => client2.listSkills() },
|
|
39499
40189
|
{ name: "get_billing_status", title: "Get Billing Status", parameter: null, mutates: false, invoke: (client2) => client2.getBillingStatus() },
|
|
39500
40190
|
{ 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) },
|
|
40191
|
+
{ name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client2, value, options) => client2.createCreditCheckout(value, options) },
|
|
39502
40192
|
{ name: "get_billing_usage", title: "Get Billing Usage", parameter: null, mutates: false, invoke: (client2) => client2.getUsage() },
|
|
39503
40193
|
{ name: "list_invoices", title: "List Invoices", parameter: null, mutates: false, invoke: (client2) => client2.listInvoices() },
|
|
39504
40194
|
{ name: "create_billing_checkout", title: "Create Billing Checkout", parameter: null, mutates: true, invoke: (client2) => client2.createBillingCheckout() },
|
|
@@ -40320,11 +41010,11 @@ var remoteCustomerContracts = REMOTE_CUSTOMER_OPERATIONS.map((operation) => ({
|
|
|
40320
41010
|
name: operation.name,
|
|
40321
41011
|
title: operation.title,
|
|
40322
41012
|
description: `${operation.title} on the configured server; unavailable capabilities fail explicitly.`,
|
|
40323
|
-
params: operation.parameter ? [operation.parameter] : [],
|
|
41013
|
+
params: operation.parameter ? [operation.parameter, ...operation.name === "create_credit_checkout" ? ["idempotency_key?"] : []] : [],
|
|
40324
41014
|
category: "execution",
|
|
40325
41015
|
sideEffects: operation.mutates ? "local-process-or-remote-run" : "none",
|
|
40326
41016
|
stable: true,
|
|
40327
|
-
inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier.") } : {}, operation.parameter ? [operation.parameter] : []),
|
|
41017
|
+
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
41018
|
outputSchema: { oneOf: [objectSchema({}, [], "Server response.", true), { type: "array", items: objectSchema({}, [], "Server record.", true) }] }
|
|
40329
41019
|
}));
|
|
40330
41020
|
remoteCustomerContracts.push({
|
|
@@ -40565,14 +41255,14 @@ function clone3(value) {
|
|
|
40565
41255
|
}
|
|
40566
41256
|
// src/lib/feedback.ts
|
|
40567
41257
|
import { appendFileSync, existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
|
|
40568
|
-
import { dirname as
|
|
41258
|
+
import { dirname as dirname19, join as join32 } from "path";
|
|
40569
41259
|
import { Database } from "bun:sqlite";
|
|
40570
41260
|
function getFeedbackDbPath() {
|
|
40571
41261
|
return join32(getDataDir(), "skills.db");
|
|
40572
41262
|
}
|
|
40573
41263
|
function getFeedbackDb() {
|
|
40574
41264
|
const dbPath = getFeedbackDbPath();
|
|
40575
|
-
const dir =
|
|
41265
|
+
const dir = dirname19(dbPath);
|
|
40576
41266
|
if (!existsSync18(dir))
|
|
40577
41267
|
mkdirSync14(dir, { recursive: true });
|
|
40578
41268
|
const db = new Database(dbPath);
|
|
@@ -40601,7 +41291,7 @@ function saveFeedback(input2) {
|
|
|
40601
41291
|
const category = input2.category ?? "general";
|
|
40602
41292
|
if (isApiMode()) {
|
|
40603
41293
|
const path = join32(getDataDir(), "feedback.jsonl");
|
|
40604
|
-
const dir =
|
|
41294
|
+
const dir = dirname19(path);
|
|
40605
41295
|
if (!existsSync18(dir))
|
|
40606
41296
|
mkdirSync14(dir, { recursive: true });
|
|
40607
41297
|
appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
@@ -40624,7 +41314,7 @@ function isApiMode(env = process.env) {
|
|
|
40624
41314
|
}
|
|
40625
41315
|
}
|
|
40626
41316
|
// src/lib/native-storage.ts
|
|
40627
|
-
import { createHash as
|
|
41317
|
+
import { createHash as createHash19, createHmac as createHmac2 } from "crypto";
|
|
40628
41318
|
import {
|
|
40629
41319
|
existsSync as existsSync19,
|
|
40630
41320
|
mkdirSync as mkdirSync15,
|
|
@@ -40633,7 +41323,7 @@ import {
|
|
|
40633
41323
|
statSync as statSync11,
|
|
40634
41324
|
writeFileSync as writeFileSync12
|
|
40635
41325
|
} from "fs";
|
|
40636
|
-
import { dirname as
|
|
41326
|
+
import { dirname as dirname20, join as join33, normalize as normalize3, relative as relative8, sep as sep7 } from "path";
|
|
40637
41327
|
var SKILLS_STORAGE_TABLES = [
|
|
40638
41328
|
"skills_sync_records",
|
|
40639
41329
|
"skills_sync_cursors"
|
|
@@ -40800,7 +41490,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
40800
41490
|
files.push({
|
|
40801
41491
|
path: relativePath,
|
|
40802
41492
|
sizeBytes: bytes.byteLength,
|
|
40803
|
-
sha256:
|
|
41493
|
+
sha256: createHash19("sha256").update(bytes).digest("hex"),
|
|
40804
41494
|
...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
|
|
40805
41495
|
});
|
|
40806
41496
|
}
|
|
@@ -40819,18 +41509,18 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
|
|
|
40819
41509
|
skipped += 1;
|
|
40820
41510
|
continue;
|
|
40821
41511
|
}
|
|
40822
|
-
const
|
|
40823
|
-
if (existsSync19(
|
|
41512
|
+
const absolutePath3 = resolveSnapshotPath(targetDir, file2.path);
|
|
41513
|
+
if (existsSync19(absolutePath3) && !options.overwrite) {
|
|
40824
41514
|
skipped += 1;
|
|
40825
41515
|
continue;
|
|
40826
41516
|
}
|
|
40827
41517
|
const bytes = Buffer.from(file2.contentBase64, "base64");
|
|
40828
|
-
const hash4 =
|
|
41518
|
+
const hash4 = createHash19("sha256").update(bytes).digest("hex");
|
|
40829
41519
|
if (hash4 !== file2.sha256) {
|
|
40830
41520
|
throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
|
|
40831
41521
|
}
|
|
40832
|
-
mkdirSync15(
|
|
40833
|
-
writeFileSync12(
|
|
41522
|
+
mkdirSync15(dirname20(absolutePath3), { recursive: true });
|
|
41523
|
+
writeFileSync12(absolutePath3, bytes);
|
|
40834
41524
|
written += 1;
|
|
40835
41525
|
}
|
|
40836
41526
|
return { written, skipped };
|
|
@@ -41157,7 +41847,7 @@ function toIsoString(value) {
|
|
|
41157
41847
|
return Number.isNaN(date6.getTime()) ? value : date6.toISOString();
|
|
41158
41848
|
}
|
|
41159
41849
|
function sha256Hex2(value) {
|
|
41160
|
-
return
|
|
41850
|
+
return createHash19("sha256").update(value).digest("hex");
|
|
41161
41851
|
}
|
|
41162
41852
|
function normalizeHeaders(headers) {
|
|
41163
41853
|
const result = {};
|
|
@@ -41209,7 +41899,7 @@ function toArrayBuffer(bytes) {
|
|
|
41209
41899
|
return buffer;
|
|
41210
41900
|
}
|
|
41211
41901
|
// src/lib/station-snapshot.ts
|
|
41212
|
-
import { createHash as
|
|
41902
|
+
import { createHash as createHash20 } from "crypto";
|
|
41213
41903
|
import {
|
|
41214
41904
|
copyFileSync,
|
|
41215
41905
|
mkdirSync as mkdirSync16,
|
|
@@ -41217,7 +41907,7 @@ import {
|
|
|
41217
41907
|
statSync as statSync13,
|
|
41218
41908
|
writeFileSync as writeFileSync13
|
|
41219
41909
|
} from "fs";
|
|
41220
|
-
import { dirname as
|
|
41910
|
+
import { dirname as dirname21, isAbsolute as isAbsolute17, relative as relative9, resolve as resolve16, sep as sep9 } from "path";
|
|
41221
41911
|
|
|
41222
41912
|
// src/lib/portable-snapshot-filter.ts
|
|
41223
41913
|
import { readdirSync as readdirSync14, statSync as statSync12 } from "fs";
|
|
@@ -41384,7 +42074,7 @@ function validateStationId(stationId) {
|
|
|
41384
42074
|
}
|
|
41385
42075
|
}
|
|
41386
42076
|
function sha256File(filePath) {
|
|
41387
|
-
return
|
|
42077
|
+
return createHash20("sha256").update(readFileSync23(filePath)).digest("hex");
|
|
41388
42078
|
}
|
|
41389
42079
|
function scanHome(definition, homesRoot) {
|
|
41390
42080
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -41455,7 +42145,7 @@ function humanHomes(scanned) {
|
|
|
41455
42145
|
}));
|
|
41456
42146
|
}
|
|
41457
42147
|
function writeStationSnapshot(options) {
|
|
41458
|
-
const repoRoot =
|
|
42148
|
+
const repoRoot = resolve16(options.repoRoot ?? process.cwd());
|
|
41459
42149
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
41460
42150
|
const manifestFiles = plans.map((plan) => ({
|
|
41461
42151
|
relativePath: plan.source.relativePath,
|
|
@@ -41481,9 +42171,9 @@ function writeStationSnapshot(options) {
|
|
|
41481
42171
|
const conflicts = [];
|
|
41482
42172
|
const untouched = [];
|
|
41483
42173
|
for (const plan of plans) {
|
|
41484
|
-
const destination =
|
|
42174
|
+
const destination = resolve16(repoRoot, plan.destination);
|
|
41485
42175
|
const destinationRelative = relative9(repoRoot, destination);
|
|
41486
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep9) ||
|
|
42176
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep9) || isAbsolute17(destinationRelative)) {
|
|
41487
42177
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
41488
42178
|
}
|
|
41489
42179
|
let existingDigest = null;
|
|
@@ -41504,8 +42194,8 @@ function writeStationSnapshot(options) {
|
|
|
41504
42194
|
}
|
|
41505
42195
|
let written = 0;
|
|
41506
42196
|
for (const plan of untouched) {
|
|
41507
|
-
const destination =
|
|
41508
|
-
mkdirSync16(
|
|
42197
|
+
const destination = resolve16(repoRoot, plan.destination);
|
|
42198
|
+
mkdirSync16(dirname21(destination), { recursive: true });
|
|
41509
42199
|
copyFileSync(plan.source.fullPath, destination);
|
|
41510
42200
|
written += 1;
|
|
41511
42201
|
}
|
|
@@ -41523,8 +42213,8 @@ function writeStationSnapshot(options) {
|
|
|
41523
42213
|
},
|
|
41524
42214
|
files: manifestFiles
|
|
41525
42215
|
};
|
|
41526
|
-
const manifestPath =
|
|
41527
|
-
mkdirSync16(
|
|
42216
|
+
const manifestPath = resolve16(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
42217
|
+
mkdirSync16(dirname21(manifestPath), { recursive: true });
|
|
41528
42218
|
writeFileSync13(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
41529
42219
|
`);
|
|
41530
42220
|
return {
|
|
@@ -41535,7 +42225,7 @@ function writeStationSnapshot(options) {
|
|
|
41535
42225
|
};
|
|
41536
42226
|
}
|
|
41537
42227
|
// src/lib/station-hydrate.ts
|
|
41538
|
-
import { createHash as
|
|
42228
|
+
import { createHash as createHash21 } from "crypto";
|
|
41539
42229
|
import {
|
|
41540
42230
|
copyFileSync as copyFileSync2,
|
|
41541
42231
|
mkdirSync as mkdirSync17,
|
|
@@ -41544,7 +42234,7 @@ import {
|
|
|
41544
42234
|
statSync as statSync14,
|
|
41545
42235
|
writeFileSync as writeFileSync14
|
|
41546
42236
|
} from "fs";
|
|
41547
|
-
import { dirname as
|
|
42237
|
+
import { dirname as dirname22, join as join35, resolve as resolve17, sep as sep10 } from "path";
|
|
41548
42238
|
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
|
|
41549
42239
|
var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
41550
42240
|
var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
@@ -41741,12 +42431,12 @@ function skillSha256(skill) {
|
|
|
41741
42431
|
return sha256File(skill.files[0].winner.fullPath);
|
|
41742
42432
|
}
|
|
41743
42433
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
41744
|
-
return
|
|
42434
|
+
return createHash21("sha256").update(joined.sort().join(`
|
|
41745
42435
|
`)).digest("hex");
|
|
41746
42436
|
}
|
|
41747
42437
|
function writeStationHydration(options) {
|
|
41748
|
-
const repoRoot =
|
|
41749
|
-
const cacheRoot =
|
|
42438
|
+
const repoRoot = resolve17(options.repoRoot ?? process.cwd());
|
|
42439
|
+
const cacheRoot = resolve17(options.cacheRoot ?? resolveCorpusRoot());
|
|
41750
42440
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
41751
42441
|
const resultSkills = plan.winners.map((skill) => ({
|
|
41752
42442
|
ident: skill.ident,
|
|
@@ -41800,7 +42490,7 @@ function writeStationHydration(options) {
|
|
|
41800
42490
|
}
|
|
41801
42491
|
let written = 0;
|
|
41802
42492
|
for (const entry of toWrite) {
|
|
41803
|
-
mkdirSync17(
|
|
42493
|
+
mkdirSync17(dirname22(entry.destination), { recursive: true });
|
|
41804
42494
|
copyFileSync2(entry.fullPath, entry.destination);
|
|
41805
42495
|
written += 1;
|
|
41806
42496
|
}
|
|
@@ -41821,8 +42511,8 @@ function writeStationHydration(options) {
|
|
|
41821
42511
|
},
|
|
41822
42512
|
skills: resultSkills
|
|
41823
42513
|
};
|
|
41824
|
-
const hydrationManifestPath = join35(
|
|
41825
|
-
mkdirSync17(
|
|
42514
|
+
const hydrationManifestPath = join35(dirname22(cacheRoot), `hydration-${options.stationId}.json`);
|
|
42515
|
+
mkdirSync17(dirname22(hydrationManifestPath), { recursive: true });
|
|
41826
42516
|
writeFileSync14(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
41827
42517
|
`);
|
|
41828
42518
|
return {
|
|
@@ -41833,23 +42523,23 @@ function writeStationHydration(options) {
|
|
|
41833
42523
|
};
|
|
41834
42524
|
}
|
|
41835
42525
|
// src/lib/private-publication-recovery.ts
|
|
41836
|
-
import { constants as
|
|
41837
|
-
import { dirname as
|
|
42526
|
+
import { constants as constants11, closeSync as closeSync12, fsyncSync as fsyncSync4, fstatSync as fstatSync12, lstatSync as lstatSync20, mkdirSync as mkdirSync18, openSync as openSync12, readSync as readSync10, realpathSync as realpathSync6, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
|
|
42527
|
+
import { dirname as dirname23, isAbsolute as isAbsolute18, join as join36, resolve as resolve18 } from "path";
|
|
41838
42528
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
41839
42529
|
var fail2 = () => {
|
|
41840
42530
|
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
42531
|
};
|
|
41842
42532
|
function safeDirectory(directory2) {
|
|
41843
|
-
if (!
|
|
42533
|
+
if (!isAbsolute18(directory2) || directory2 !== resolve18(directory2) || realpathSync6(directory2) !== directory2)
|
|
41844
42534
|
return fail2();
|
|
41845
|
-
for (let path = directory2;; path =
|
|
41846
|
-
const stat2 =
|
|
42535
|
+
for (let path = directory2;; path = dirname23(path)) {
|
|
42536
|
+
const stat2 = lstatSync20(path);
|
|
41847
42537
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
41848
42538
|
return fail2();
|
|
41849
|
-
if (path ===
|
|
42539
|
+
if (path === dirname23(path))
|
|
41850
42540
|
break;
|
|
41851
42541
|
}
|
|
41852
|
-
const own2 =
|
|
42542
|
+
const own2 = lstatSync20(directory2);
|
|
41853
42543
|
if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
|
|
41854
42544
|
return fail2();
|
|
41855
42545
|
return { dev: own2.dev, ino: own2.ino };
|
|
@@ -41861,35 +42551,35 @@ function unchangedDirectory(directory2, identity3) {
|
|
|
41861
42551
|
}
|
|
41862
42552
|
function readOwned(directory2, name, max) {
|
|
41863
42553
|
const identity3 = safeDirectory(directory2), file2 = join36(directory2, name);
|
|
41864
|
-
const fd =
|
|
42554
|
+
const fd = openSync12(file2, constants11.O_RDONLY | constants11.O_NOFOLLOW);
|
|
41865
42555
|
try {
|
|
41866
|
-
const stat2 =
|
|
42556
|
+
const stat2 = fstatSync12(fd);
|
|
41867
42557
|
if (!stat2.isFile() || stat2.nlink !== 1 || stat2.size > max || stat2.size < 1 || (stat2.mode & 63) !== 0 || process.getuid && stat2.uid !== process.getuid())
|
|
41868
42558
|
return fail2();
|
|
41869
42559
|
const buffer = Buffer.alloc(Math.min(max, stat2.size) + 1);
|
|
41870
42560
|
let length = 0;
|
|
41871
42561
|
while (length < buffer.length) {
|
|
41872
|
-
const count =
|
|
42562
|
+
const count = readSync10(fd, buffer, length, buffer.length - length, length);
|
|
41873
42563
|
if (count === 0)
|
|
41874
42564
|
break;
|
|
41875
42565
|
length += count;
|
|
41876
42566
|
}
|
|
41877
|
-
const bytes = buffer.subarray(0, length), after =
|
|
42567
|
+
const bytes = buffer.subarray(0, length), after = fstatSync12(fd);
|
|
41878
42568
|
unchangedDirectory(directory2, identity3);
|
|
41879
42569
|
if (bytes.length !== stat2.size || stat2.size !== after.size || stat2.mtimeMs !== after.mtimeMs || stat2.ctimeMs !== after.ctimeMs)
|
|
41880
42570
|
return fail2();
|
|
41881
42571
|
return bytes;
|
|
41882
42572
|
} finally {
|
|
41883
|
-
|
|
42573
|
+
closeSync12(fd);
|
|
41884
42574
|
}
|
|
41885
42575
|
}
|
|
41886
42576
|
function writeOwned(directory2, name, bytes) {
|
|
41887
|
-
const fd =
|
|
42577
|
+
const fd = openSync12(join36(directory2, name), constants11.O_WRONLY | constants11.O_CREAT | constants11.O_EXCL | constants11.O_NOFOLLOW, 384);
|
|
41888
42578
|
try {
|
|
41889
42579
|
writeFileSync15(fd, bytes);
|
|
41890
|
-
|
|
42580
|
+
fsyncSync4(fd);
|
|
41891
42581
|
} finally {
|
|
41892
|
-
|
|
42582
|
+
closeSync12(fd);
|
|
41893
42583
|
}
|
|
41894
42584
|
}
|
|
41895
42585
|
function save(directory2, value) {
|
|
@@ -41898,11 +42588,11 @@ function save(directory2, value) {
|
|
|
41898
42588
|
`);
|
|
41899
42589
|
unchangedDirectory(directory2, identity3);
|
|
41900
42590
|
renameSync7(join36(directory2, temporary), join36(directory2, "receipt.json"));
|
|
41901
|
-
const fd =
|
|
42591
|
+
const fd = openSync12(directory2, constants11.O_RDONLY | constants11.O_NOFOLLOW);
|
|
41902
42592
|
try {
|
|
41903
|
-
|
|
42593
|
+
fsyncSync4(fd);
|
|
41904
42594
|
} finally {
|
|
41905
|
-
|
|
42595
|
+
closeSync12(fd);
|
|
41906
42596
|
}
|
|
41907
42597
|
}
|
|
41908
42598
|
function bind(client2, receipt) {
|
|
@@ -41913,20 +42603,20 @@ async function locked(directory2, action) {
|
|
|
41913
42603
|
const identity3 = safeDirectory(directory2), file2 = join36(directory2, "operation.lock");
|
|
41914
42604
|
let fd;
|
|
41915
42605
|
try {
|
|
41916
|
-
fd =
|
|
42606
|
+
fd = openSync12(file2, constants11.O_WRONLY | constants11.O_CREAT | constants11.O_EXCL | constants11.O_NOFOLLOW, 384);
|
|
41917
42607
|
} catch {
|
|
41918
42608
|
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
42609
|
}
|
|
41920
|
-
const lock =
|
|
42610
|
+
const lock = fstatSync12(fd);
|
|
41921
42611
|
try {
|
|
41922
42612
|
writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
|
|
41923
42613
|
`);
|
|
41924
|
-
|
|
42614
|
+
fsyncSync4(fd);
|
|
41925
42615
|
return await action();
|
|
41926
42616
|
} finally {
|
|
41927
|
-
|
|
42617
|
+
closeSync12(fd);
|
|
41928
42618
|
unchangedDirectory(directory2, identity3);
|
|
41929
|
-
const now =
|
|
42619
|
+
const now = lstatSync20(file2);
|
|
41930
42620
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
41931
42621
|
fail2();
|
|
41932
42622
|
unlinkSync5(file2);
|
|
@@ -41978,15 +42668,15 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
|
|
|
41978
42668
|
phase: "prepared",
|
|
41979
42669
|
intent: null
|
|
41980
42670
|
};
|
|
41981
|
-
if (!
|
|
42671
|
+
if (!isAbsolute18(recoveryDirectory) || resolve18(recoveryDirectory) !== recoveryDirectory || realpathSync6(dirname23(recoveryDirectory)) !== dirname23(recoveryDirectory))
|
|
41982
42672
|
return fail2();
|
|
41983
42673
|
mkdirSync18(recoveryDirectory, { mode: 448 });
|
|
41984
42674
|
safeDirectory(recoveryDirectory);
|
|
41985
|
-
const parent =
|
|
42675
|
+
const parent = openSync12(dirname23(recoveryDirectory), constants11.O_RDONLY | constants11.O_NOFOLLOW);
|
|
41986
42676
|
try {
|
|
41987
|
-
|
|
42677
|
+
fsyncSync4(parent);
|
|
41988
42678
|
} finally {
|
|
41989
|
-
|
|
42679
|
+
closeSync12(parent);
|
|
41990
42680
|
}
|
|
41991
42681
|
writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
|
|
41992
42682
|
save(recoveryDirectory, receipt);
|
|
@@ -42069,8 +42759,8 @@ async function inspectLocked(client2, directory2, cancel) {
|
|
|
42069
42759
|
return privatePublicationResult(directory2, receipt);
|
|
42070
42760
|
}
|
|
42071
42761
|
// src/lib/skill-context.ts
|
|
42072
|
-
import { createHash as
|
|
42073
|
-
import { resolve as
|
|
42762
|
+
import { createHash as createHash22 } from "crypto";
|
|
42763
|
+
import { resolve as resolve19 } from "path";
|
|
42074
42764
|
function words(value) {
|
|
42075
42765
|
return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
42076
42766
|
}
|
|
@@ -42105,7 +42795,7 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
42105
42795
|
const promptWords = words(prompt);
|
|
42106
42796
|
const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
|
|
42107
42797
|
const loaded = new Set(resolved.session?.loaded ?? []);
|
|
42108
|
-
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd,
|
|
42798
|
+
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve19(input2.cwd)] : []];
|
|
42109
42799
|
const candidates = profile.selections.map((selection) => {
|
|
42110
42800
|
const key = selectionKey(selection);
|
|
42111
42801
|
if (input2.restore && loaded.has(key))
|
|
@@ -42158,7 +42848,7 @@ ${content}`;
|
|
|
42158
42848
|
`);
|
|
42159
42849
|
const receipt = {
|
|
42160
42850
|
schemaVersion: 1,
|
|
42161
|
-
id:
|
|
42851
|
+
id: createHash22("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
|
|
42162
42852
|
profileId: profile.profileId,
|
|
42163
42853
|
profileRevision: profile.profileRevision,
|
|
42164
42854
|
authority: profile.authority,
|
|
@@ -42170,9 +42860,105 @@ ${content}`;
|
|
|
42170
42860
|
selections
|
|
42171
42861
|
};
|
|
42172
42862
|
if (sessionId)
|
|
42173
|
-
|
|
42863
|
+
writeSkillSession({ ...resolved.receipt, sessionId, loaded: [...loaded] }, { current: resolved.sessionSnapshot, parent: resolved.parentSnapshot }, options);
|
|
42174
42864
|
return { context: context2, selections, omitted, receipt };
|
|
42175
42865
|
}
|
|
42866
|
+
// src/lib/session-reconciliation.ts
|
|
42867
|
+
import { createHash as createHash23 } from "crypto";
|
|
42868
|
+
var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
|
|
42869
|
+
var digest3 = (value) => createHash23("sha256").update(JSON.stringify(value)).digest("hex");
|
|
42870
|
+
function exactTimestamp(value) {
|
|
42871
|
+
const parsed = value === undefined ? Number.NaN : Date.parse(value);
|
|
42872
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
|
|
42873
|
+
throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "The reviewed plan must include its exact canonical issuedAt and expiresAt timestamps.");
|
|
42874
|
+
}
|
|
42875
|
+
return parsed;
|
|
42876
|
+
}
|
|
42877
|
+
function assertPlanWindow(issuedAt, expiresAt, now) {
|
|
42878
|
+
const issued = exactTimestamp(issuedAt), expires = exactTimestamp(expiresAt);
|
|
42879
|
+
if (expires - issued !== SESSION_RECONCILIATION_PLAN_TTL_MS) {
|
|
42880
|
+
throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "A session reconciliation approval is valid for exactly five minutes and cannot be extended by the caller.");
|
|
42881
|
+
}
|
|
42882
|
+
if (now < issued || now >= expires) {
|
|
42883
|
+
throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
|
|
42884
|
+
}
|
|
42885
|
+
}
|
|
42886
|
+
function inspectSkillSession(sessionId, options = {}) {
|
|
42887
|
+
const snapshot = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot;
|
|
42888
|
+
return {
|
|
42889
|
+
sessionId,
|
|
42890
|
+
path: snapshot.path,
|
|
42891
|
+
receiptSha256: snapshot.sha256,
|
|
42892
|
+
generation: snapshot.generation,
|
|
42893
|
+
verifiedAt: receipt.verifiedAt,
|
|
42894
|
+
authority: receipt.profile.authority,
|
|
42895
|
+
workspaceId: receipt.profile.workspaceId,
|
|
42896
|
+
profileId: receipt.profile.profileId,
|
|
42897
|
+
profileRevision: receipt.profile.profileRevision,
|
|
42898
|
+
selectionCount: receipt.profile.selections.length,
|
|
42899
|
+
loadedCount: receipt.loaded.length
|
|
42900
|
+
};
|
|
42901
|
+
}
|
|
42902
|
+
async function reconcileSkillSession(input2, options = {}) {
|
|
42903
|
+
if (![input2.fromProfile, input2.fromRevision, input2.selectionProfile, input2.profileRevision].every((value) => typeof value === "string" && value.trim()) || !/^[a-f0-9]{64}$/.test(input2.receiptSha256)) {
|
|
42904
|
+
throw new SkillSelectionError("INVALID_SESSION_RECONCILIATION", "Name the exact old receipt SHA256, old profile/revision and intended target profile/revision.");
|
|
42905
|
+
}
|
|
42906
|
+
if (input2.apply && (!input2.planDigest || !input2.planIssuedAt || !input2.planExpiresAt)) {
|
|
42907
|
+
throw new SkillSelectionError("SESSION_PLAN_REQUIRED", "Review the session reconciliation plan and provide its exact digest, issuedAt and expiresAt before applying it.");
|
|
42908
|
+
}
|
|
42909
|
+
if (!input2.apply && (input2.planDigest !== undefined || input2.planIssuedAt !== undefined || input2.planExpiresAt !== undefined)) {
|
|
42910
|
+
throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "Plan timestamps are minted by the planner and may only be replayed with --apply.");
|
|
42911
|
+
}
|
|
42912
|
+
const now = (options.now ?? Date.now)();
|
|
42913
|
+
const issuedAt = input2.apply ? input2.planIssuedAt : new Date(now).toISOString();
|
|
42914
|
+
const expiresAt = input2.apply ? input2.planExpiresAt : new Date(now + SESSION_RECONCILIATION_PLAN_TTL_MS).toISOString();
|
|
42915
|
+
if (input2.apply)
|
|
42916
|
+
assertPlanWindow(issuedAt, expiresAt, now);
|
|
42917
|
+
const snapshot = readSkillSessionSnapshot(input2.sessionId, options), old = snapshot.receipt;
|
|
42918
|
+
if (snapshot.sha256 !== input2.receiptSha256 || old.profile.profileId !== input2.fromProfile || old.profile.profileRevision !== input2.fromRevision) {
|
|
42919
|
+
throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The current session receipt does not match the reviewed old bytes, profile and revision.");
|
|
42920
|
+
}
|
|
42921
|
+
const client2 = options.client ?? await createProfileClient();
|
|
42922
|
+
const target = structuredClone(await client2.resolveProfile(input2.selectionProfile));
|
|
42923
|
+
validateResolvedProfile(target, client2.authority);
|
|
42924
|
+
if (target.profileId !== input2.selectionProfile || target.profileRevision !== input2.profileRevision) {
|
|
42925
|
+
throw new SkillSelectionError("SESSION_TARGET_CHANGED", "The API selection profile does not match the intended target revision; review the current selection before proceeding.");
|
|
42926
|
+
}
|
|
42927
|
+
if (old.profile.authority !== target.authority || old.profile.workspaceId !== target.workspaceId) {
|
|
42928
|
+
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "A session reconciliation cannot change its Skills authority or workspace.");
|
|
42929
|
+
}
|
|
42930
|
+
const targetKeys = new Set(target.selections.map(selectionKey));
|
|
42931
|
+
const loaded = old.loaded.filter((key) => targetKeys.has(key));
|
|
42932
|
+
const replacementGeneration = nextSkillSessionGeneration(snapshot.generation);
|
|
42933
|
+
const plan = {
|
|
42934
|
+
schemaVersion: 1,
|
|
42935
|
+
issuedAt,
|
|
42936
|
+
expiresAt,
|
|
42937
|
+
sessionId: input2.sessionId,
|
|
42938
|
+
sessionPath: snapshot.path,
|
|
42939
|
+
before: { receiptSha256: snapshot.sha256, generation: snapshot.generation, profileId: old.profile.profileId, profileRevision: old.profile.profileRevision, selectionCount: old.profile.selections.length },
|
|
42940
|
+
target: { authority: target.authority, workspaceId: target.workspaceId, profileId: target.profileId, profileRevision: target.profileRevision, profileSha256: digest3(target), selectionCount: target.selections.length },
|
|
42941
|
+
replacementGeneration,
|
|
42942
|
+
retainedLoadedCount: loaded.length,
|
|
42943
|
+
retiredLoadedCount: old.loaded.length - loaded.length,
|
|
42944
|
+
scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
|
|
42945
|
+
};
|
|
42946
|
+
const planDigest = digest3(plan);
|
|
42947
|
+
if (input2.planDigest !== undefined && input2.planDigest !== planDigest)
|
|
42948
|
+
throw new SkillSelectionError("SESSION_PLAN_CHANGED", "The session reconciliation plan differs from the reviewed plan.");
|
|
42949
|
+
if (!input2.apply)
|
|
42950
|
+
return { applied: false, plan, planDigest, archivePath: undefined, receiptPath: undefined, beforeSha256: snapshot.sha256, afterSha256: undefined };
|
|
42951
|
+
assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)());
|
|
42952
|
+
const replacement = {
|
|
42953
|
+
...old,
|
|
42954
|
+
generation: replacementGeneration,
|
|
42955
|
+
verifiedAt: new Date((options.now ?? Date.now)()).toISOString(),
|
|
42956
|
+
profile: target,
|
|
42957
|
+
loaded
|
|
42958
|
+
};
|
|
42959
|
+
const result = replaceSkillSession(skillSessionSnapshotBinding(snapshot), replacement, { ...plan, planDigest }, options, () => assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)()));
|
|
42960
|
+
return { applied: true, plan, planDigest, ...result };
|
|
42961
|
+
}
|
|
42176
42962
|
export {
|
|
42177
42963
|
writeStationSnapshot,
|
|
42178
42964
|
writeStationHydration,
|
|
@@ -42237,6 +43023,7 @@ export {
|
|
|
42237
43023
|
removeSchedule,
|
|
42238
43024
|
removeManagedAgentSkill,
|
|
42239
43025
|
recordScheduleRun,
|
|
43026
|
+
reconcileSkillSession,
|
|
42240
43027
|
readPrivatePublicationRecovery,
|
|
42241
43028
|
readPortableSkillManifest,
|
|
42242
43029
|
pullSkills,
|
|
@@ -42292,6 +43079,7 @@ export {
|
|
|
42292
43079
|
installSkillManifest,
|
|
42293
43080
|
installSkillForAgent,
|
|
42294
43081
|
installSkill,
|
|
43082
|
+
inspectSkillSession,
|
|
42295
43083
|
inspectPrivatePublication,
|
|
42296
43084
|
importSkillsLocalSnapshot,
|
|
42297
43085
|
homePathFor,
|
|
@@ -42367,6 +43155,8 @@ export {
|
|
|
42367
43155
|
captureDiscoveryPathSources,
|
|
42368
43156
|
captureDiscoveryDirectories,
|
|
42369
43157
|
captureDiscoveryByteSources,
|
|
43158
|
+
captureClaudeSettings,
|
|
43159
|
+
captureClaudeMarketplaceRegistry,
|
|
42370
43160
|
canonicalizeManifest,
|
|
42371
43161
|
buildSkillsS3ObjectUrl,
|
|
42372
43162
|
buildSkillsApiUrl,
|
|
@@ -42430,6 +43220,7 @@ export {
|
|
|
42430
43220
|
RemotePrivatePublicationsClient,
|
|
42431
43221
|
RemoteInvitationEmailUnconfirmedError,
|
|
42432
43222
|
RemoteInvitationEmailError,
|
|
43223
|
+
RemoteCreditCheckoutError,
|
|
42433
43224
|
RemoteCreditApprovalError,
|
|
42434
43225
|
RemoteCapabilityUnavailableError,
|
|
42435
43226
|
REMOTE_SKILL_RUN_CONTRACT_VERSION,
|