@notis_ai/cli 0.2.0-beta.165.1 → 0.2.0-beta.167.1
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/dist/agent-hooks/notis-agent-hook.mjs +74 -18
- package/dist/skill-sync/index.js +74 -18
- package/dist/skill-sync/index.js.map +2 -2
- package/dist/skill-sync-worker.mjs +69 -16
- package/package.json +1 -1
- package/src/runtime/skill-sync/index.ts +42 -8
- package/src/runtime/skill-sync/local-scanner.ts +66 -9
- package/src/runtime/skill-sync/types.ts +11 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
|
@@ -3669,10 +3669,34 @@ async function extractZipToDirectory(zipPath, destinationDir) {
|
|
|
3669
3669
|
await fs2.rm(destinationDir, { recursive: true, force: true });
|
|
3670
3670
|
await fs2.mkdir(path2.dirname(destinationDir), { recursive: true });
|
|
3671
3671
|
await fs2.cp(sourceDir, destinationDir, { recursive: true });
|
|
3672
|
+
return await listRelativeFiles(destinationDir);
|
|
3672
3673
|
} finally {
|
|
3673
3674
|
await fs2.rm(extractRoot, { recursive: true, force: true });
|
|
3674
3675
|
}
|
|
3675
3676
|
}
|
|
3677
|
+
async function listRelativeFiles(dirPath) {
|
|
3678
|
+
try {
|
|
3679
|
+
const files = await listFilesRecursive2(dirPath);
|
|
3680
|
+
return files.map((filePath) => toPosixRelativePath(dirPath, filePath)).sort();
|
|
3681
|
+
} catch (error) {
|
|
3682
|
+
if (error?.code === "ENOENT") return [];
|
|
3683
|
+
throw error;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
function toPosixRelativePath(rootDir, filePath) {
|
|
3687
|
+
return path2.relative(rootDir, filePath).split(path2.sep).join("/");
|
|
3688
|
+
}
|
|
3689
|
+
async function preserveUnmanagedFiles(existingDir, stagingDir, previouslyApplied) {
|
|
3690
|
+
const managed = previouslyApplied ? new Set(previouslyApplied) : null;
|
|
3691
|
+
for (const existingFile of await listFilesRecursive2(existingDir)) {
|
|
3692
|
+
const relativePath = toPosixRelativePath(existingDir, existingFile);
|
|
3693
|
+
if (managed?.has(relativePath)) continue;
|
|
3694
|
+
const stagedPath = path2.join(stagingDir, ...relativePath.split("/"));
|
|
3695
|
+
if (await pathExists(stagedPath)) continue;
|
|
3696
|
+
await fs2.mkdir(path2.dirname(stagedPath), { recursive: true });
|
|
3697
|
+
await fs2.copyFile(existingFile, stagedPath);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3676
3700
|
async function pathExists(targetPath) {
|
|
3677
3701
|
try {
|
|
3678
3702
|
await fs2.access(targetPath);
|
|
@@ -3681,7 +3705,7 @@ async function pathExists(targetPath) {
|
|
|
3681
3705
|
return false;
|
|
3682
3706
|
}
|
|
3683
3707
|
}
|
|
3684
|
-
async function replaceSkillDirectoryAtomically(skillDir, populateDir) {
|
|
3708
|
+
async function replaceSkillDirectoryAtomically(skillDir, populateDir, preserve) {
|
|
3685
3709
|
const parentDir = path2.dirname(skillDir);
|
|
3686
3710
|
const skillName = path2.basename(skillDir);
|
|
3687
3711
|
await fs2.mkdir(parentDir, { recursive: true });
|
|
@@ -3697,6 +3721,9 @@ async function replaceSkillDirectoryAtomically(skillDir, populateDir) {
|
|
|
3697
3721
|
let cleanupError = null;
|
|
3698
3722
|
try {
|
|
3699
3723
|
await populateDir(stagingDir);
|
|
3724
|
+
if (preserve && await pathExists(skillDir)) {
|
|
3725
|
+
await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied);
|
|
3726
|
+
}
|
|
3700
3727
|
if (await pathExists(skillDir)) {
|
|
3701
3728
|
await fs2.rename(skillDir, backupDir);
|
|
3702
3729
|
movedExisting = true;
|
|
@@ -3744,11 +3771,12 @@ async function createSkillBundleBase64(skill) {
|
|
|
3744
3771
|
await fs2.rm(zipPath, { force: true });
|
|
3745
3772
|
}
|
|
3746
3773
|
}
|
|
3747
|
-
async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PATHS) {
|
|
3774
|
+
async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PATHS, options = {}) {
|
|
3748
3775
|
const skillDir = path2.join(
|
|
3749
3776
|
paths.skillsDir,
|
|
3750
3777
|
safeName(skill.name, paths.skillsDir)
|
|
3751
3778
|
);
|
|
3779
|
+
const preserve = { previouslyApplied: options.previouslyApplied };
|
|
3752
3780
|
if (bundleBytes?.length) {
|
|
3753
3781
|
const bundlePath2 = path2.join(
|
|
3754
3782
|
os.tmpdir(),
|
|
@@ -3756,8 +3784,11 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
3756
3784
|
);
|
|
3757
3785
|
try {
|
|
3758
3786
|
await fs2.writeFile(bundlePath2, bundleBytes);
|
|
3759
|
-
|
|
3760
|
-
|
|
3787
|
+
let appliedFiles = [];
|
|
3788
|
+
await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {
|
|
3789
|
+
appliedFiles = await extractZipToDirectory(bundlePath2, stagingDir);
|
|
3790
|
+
}, preserve);
|
|
3791
|
+
return { written: true, appliedFiles };
|
|
3761
3792
|
} finally {
|
|
3762
3793
|
await fs2.rm(bundlePath2, { force: true });
|
|
3763
3794
|
}
|
|
@@ -3771,6 +3802,7 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
3771
3802
|
`Synced bundle for "${skill.name}" is missing SKILL.md or SKILLS.md`
|
|
3772
3803
|
);
|
|
3773
3804
|
}
|
|
3805
|
+
const appliedFiles = [];
|
|
3774
3806
|
await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {
|
|
3775
3807
|
for (const bundleFile of skill.bundle_files || []) {
|
|
3776
3808
|
const filePath = resolveSkillBundleFilePath(
|
|
@@ -3782,9 +3814,10 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
3782
3814
|
filePath,
|
|
3783
3815
|
Buffer.from(bundleFile.content_b64, "base64")
|
|
3784
3816
|
);
|
|
3817
|
+
appliedFiles.push(toPosixRelativePath(stagingDir, filePath));
|
|
3785
3818
|
}
|
|
3786
|
-
});
|
|
3787
|
-
return true;
|
|
3819
|
+
}, preserve);
|
|
3820
|
+
return { written: true, appliedFiles: appliedFiles.sort() };
|
|
3788
3821
|
}
|
|
3789
3822
|
await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {
|
|
3790
3823
|
await fs2.writeFile(
|
|
@@ -3792,8 +3825,8 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
3792
3825
|
skill.skill_md ?? "",
|
|
3793
3826
|
"utf8"
|
|
3794
3827
|
);
|
|
3795
|
-
});
|
|
3796
|
-
return true;
|
|
3828
|
+
}, preserve);
|
|
3829
|
+
return { written: true, appliedFiles: ["SKILL.md"] };
|
|
3797
3830
|
}
|
|
3798
3831
|
async function requestJson(url, jwt, options = {}) {
|
|
3799
3832
|
const response = await fetch(url, {
|
|
@@ -4174,7 +4207,7 @@ async function writeCloudSkillWithBundleFallback(skill, dependencies) {
|
|
|
4174
4207
|
const bundleBytes = await dependencies.downloadSkillBundle(skill.skill_source_url);
|
|
4175
4208
|
const wroteBundleToDisk = await dependencies.writeCloudSkillToDisk(skill, bundleBytes);
|
|
4176
4209
|
if (wroteBundleToDisk) {
|
|
4177
|
-
return
|
|
4210
|
+
return wroteBundleToDisk;
|
|
4178
4211
|
}
|
|
4179
4212
|
dependencies.onWarning?.(
|
|
4180
4213
|
`Bundle sync for "${skill.name}" produced no local changes, falling back to SKILL.md payload.`,
|
|
@@ -4267,12 +4300,20 @@ function collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillN
|
|
|
4267
4300
|
}
|
|
4268
4301
|
return applied;
|
|
4269
4302
|
}
|
|
4270
|
-
function
|
|
4303
|
+
function collectAppliedFiles(pullResponse, previousState, appliedFilesByName) {
|
|
4304
|
+
const applied = {};
|
|
4305
|
+
for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
4306
|
+
applied[skill.name] = appliedFilesByName[skill.name] ?? previousState.skills[skill.name]?.appliedFiles;
|
|
4307
|
+
}
|
|
4308
|
+
return applied;
|
|
4309
|
+
}
|
|
4310
|
+
function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}, appliedFiles = {}) {
|
|
4271
4311
|
const localSkillMap = toSkillMap(localSkills);
|
|
4272
4312
|
const skills = Object.fromEntries(
|
|
4273
4313
|
selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
|
|
4274
4314
|
const localSkill = localSkillMap.get(skill.name);
|
|
4275
4315
|
const appliedCloudFolderHash = appliedRevisions[skill.name];
|
|
4316
|
+
const appliedFileList = appliedFiles[skill.name];
|
|
4276
4317
|
return [
|
|
4277
4318
|
skill.name,
|
|
4278
4319
|
{
|
|
@@ -4283,6 +4324,7 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
|
|
|
4283
4324
|
cloudUpdatedAt: skill.updated_at,
|
|
4284
4325
|
...!failedContentNames.has(skill.name) && !skill.skill_source_url ? { cloudContentHash: cloudContentHash(skill) } : {},
|
|
4285
4326
|
...appliedCloudFolderHash !== void 0 ? { appliedCloudFolderHash } : {},
|
|
4327
|
+
...appliedFileList !== void 0 ? { appliedFiles: appliedFileList } : {},
|
|
4286
4328
|
syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
4287
4329
|
}
|
|
4288
4330
|
];
|
|
@@ -4338,7 +4380,7 @@ function applyLegacyFirstRunState(localSkills, scopedState, legacyState) {
|
|
|
4338
4380
|
skills: migratedSkills
|
|
4339
4381
|
};
|
|
4340
4382
|
}
|
|
4341
|
-
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set()) {
|
|
4383
|
+
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set(), appliedFilesByName = {}) {
|
|
4342
4384
|
const localSkillMap = toSkillMap(localSkills);
|
|
4343
4385
|
const warnSkillSync = (message, error) => {
|
|
4344
4386
|
console.warn(`[Notis] ${message}`, error);
|
|
@@ -4348,13 +4390,21 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
4348
4390
|
if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
|
|
4349
4391
|
continue;
|
|
4350
4392
|
}
|
|
4351
|
-
|
|
4393
|
+
const outcome = await writeCloudSkillWithBundleFallback(cloudSkill, {
|
|
4352
4394
|
downloadSkillBundle: deps.downloadSkillBundle,
|
|
4353
|
-
writeCloudSkillToDisk: (skill, bundleBytes) => deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths
|
|
4395
|
+
writeCloudSkillToDisk: (skill, bundleBytes) => deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths, {
|
|
4396
|
+
// What the last write put there. Everything else in the folder was
|
|
4397
|
+
// produced locally and survives this one.
|
|
4398
|
+
previouslyApplied: previousState.skills[skill.name]?.appliedFiles
|
|
4399
|
+
}),
|
|
4354
4400
|
onWarning: warnSkillSync
|
|
4355
|
-
})
|
|
4401
|
+
});
|
|
4402
|
+
if (outcome) {
|
|
4356
4403
|
downloaded += 1;
|
|
4357
4404
|
writtenSkillNames.add(cloudSkill.name);
|
|
4405
|
+
if (typeof outcome === "object" && Array.isArray(outcome.appliedFiles)) {
|
|
4406
|
+
appliedFilesByName[cloudSkill.name] = outcome.appliedFiles;
|
|
4407
|
+
}
|
|
4358
4408
|
} else {
|
|
4359
4409
|
failures.push({ name: cloudSkill.name, error: "Skill content could not be downloaded or written; sync will retry" });
|
|
4360
4410
|
}
|
|
@@ -4386,6 +4436,7 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
4386
4436
|
const localSkills = await deps.scanLocalSkills(syncPaths);
|
|
4387
4437
|
const failedDownloads = [];
|
|
4388
4438
|
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
4439
|
+
const appliedFilesByName = {};
|
|
4389
4440
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
4390
4441
|
pullResponse,
|
|
4391
4442
|
localSkills,
|
|
@@ -4393,7 +4444,8 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
4393
4444
|
syncPaths,
|
|
4394
4445
|
deps,
|
|
4395
4446
|
failedDownloads,
|
|
4396
|
-
writtenSkillNames
|
|
4447
|
+
writtenSkillNames,
|
|
4448
|
+
appliedFilesByName
|
|
4397
4449
|
);
|
|
4398
4450
|
const finalLocalSkills = await deps.scanLocalSkills(syncPaths);
|
|
4399
4451
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4426,7 +4478,8 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
4426
4478
|
lastSyncedAt,
|
|
4427
4479
|
verifiedLinks,
|
|
4428
4480
|
new Set(failedDownloads.map((item) => item.name)),
|
|
4429
|
-
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
4481
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
|
|
4482
|
+
collectAppliedFiles(pullResponse, previousState, appliedFilesByName)
|
|
4430
4483
|
);
|
|
4431
4484
|
for (const [name, entry] of Object.entries(materializedState.skills)) {
|
|
4432
4485
|
if (writtenSkillNames.has(name)) continue;
|
|
@@ -4607,6 +4660,7 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
4607
4660
|
}
|
|
4608
4661
|
const failedDownloads = [];
|
|
4609
4662
|
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
4663
|
+
const appliedFilesByName = {};
|
|
4610
4664
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
4611
4665
|
pullResponse,
|
|
4612
4666
|
localSkills,
|
|
@@ -4614,7 +4668,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
4614
4668
|
syncPaths,
|
|
4615
4669
|
deps,
|
|
4616
4670
|
failedDownloads,
|
|
4617
|
-
writtenSkillNames
|
|
4671
|
+
writtenSkillNames,
|
|
4672
|
+
appliedFilesByName
|
|
4618
4673
|
);
|
|
4619
4674
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES2.has(skill.name));
|
|
4620
4675
|
const symlinkResult = await deps.syncSymlinks(
|
|
@@ -4631,7 +4686,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
4631
4686
|
lastSyncedAt,
|
|
4632
4687
|
verifiedLinks,
|
|
4633
4688
|
new Set(failedDownloads.map((item) => item.name)),
|
|
4634
|
-
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
4689
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
|
|
4690
|
+
collectAppliedFiles(pullResponse, previousState, appliedFilesByName)
|
|
4635
4691
|
),
|
|
4636
4692
|
syncPaths
|
|
4637
4693
|
);
|
package/dist/skill-sync/index.js
CHANGED
|
@@ -625,10 +625,34 @@ async function extractZipToDirectory(zipPath, destinationDir) {
|
|
|
625
625
|
await fs.rm(destinationDir, { recursive: true, force: true });
|
|
626
626
|
await fs.mkdir(path.dirname(destinationDir), { recursive: true });
|
|
627
627
|
await fs.cp(sourceDir, destinationDir, { recursive: true });
|
|
628
|
+
return await listRelativeFiles(destinationDir);
|
|
628
629
|
} finally {
|
|
629
630
|
await fs.rm(extractRoot, { recursive: true, force: true });
|
|
630
631
|
}
|
|
631
632
|
}
|
|
633
|
+
async function listRelativeFiles(dirPath) {
|
|
634
|
+
try {
|
|
635
|
+
const files = await listFilesRecursive(dirPath);
|
|
636
|
+
return files.map((filePath) => toPosixRelativePath(dirPath, filePath)).sort();
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (error?.code === "ENOENT") return [];
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function toPosixRelativePath(rootDir, filePath) {
|
|
643
|
+
return path.relative(rootDir, filePath).split(path.sep).join("/");
|
|
644
|
+
}
|
|
645
|
+
async function preserveUnmanagedFiles(existingDir, stagingDir, previouslyApplied) {
|
|
646
|
+
const managed = previouslyApplied ? new Set(previouslyApplied) : null;
|
|
647
|
+
for (const existingFile of await listFilesRecursive(existingDir)) {
|
|
648
|
+
const relativePath = toPosixRelativePath(existingDir, existingFile);
|
|
649
|
+
if (managed?.has(relativePath)) continue;
|
|
650
|
+
const stagedPath = path.join(stagingDir, ...relativePath.split("/"));
|
|
651
|
+
if (await pathExists(stagedPath)) continue;
|
|
652
|
+
await fs.mkdir(path.dirname(stagedPath), { recursive: true });
|
|
653
|
+
await fs.copyFile(existingFile, stagedPath);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
632
656
|
async function pathExists(targetPath) {
|
|
633
657
|
try {
|
|
634
658
|
await fs.access(targetPath);
|
|
@@ -637,7 +661,7 @@ async function pathExists(targetPath) {
|
|
|
637
661
|
return false;
|
|
638
662
|
}
|
|
639
663
|
}
|
|
640
|
-
async function replaceSkillDirectoryAtomically(skillDir, populateDir) {
|
|
664
|
+
async function replaceSkillDirectoryAtomically(skillDir, populateDir, preserve) {
|
|
641
665
|
const parentDir = path.dirname(skillDir);
|
|
642
666
|
const skillName = path.basename(skillDir);
|
|
643
667
|
await fs.mkdir(parentDir, { recursive: true });
|
|
@@ -653,6 +677,9 @@ async function replaceSkillDirectoryAtomically(skillDir, populateDir) {
|
|
|
653
677
|
let cleanupError = null;
|
|
654
678
|
try {
|
|
655
679
|
await populateDir(stagingDir);
|
|
680
|
+
if (preserve && await pathExists(skillDir)) {
|
|
681
|
+
await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied);
|
|
682
|
+
}
|
|
656
683
|
if (await pathExists(skillDir)) {
|
|
657
684
|
await fs.rename(skillDir, backupDir);
|
|
658
685
|
movedExisting = true;
|
|
@@ -700,11 +727,12 @@ async function createSkillBundleBase64(skill) {
|
|
|
700
727
|
await fs.rm(zipPath, { force: true });
|
|
701
728
|
}
|
|
702
729
|
}
|
|
703
|
-
async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PATHS) {
|
|
730
|
+
async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PATHS, options = {}) {
|
|
704
731
|
const skillDir = path.join(
|
|
705
732
|
paths.skillsDir,
|
|
706
733
|
safeName(skill.name, paths.skillsDir)
|
|
707
734
|
);
|
|
735
|
+
const preserve = { previouslyApplied: options.previouslyApplied };
|
|
708
736
|
if (bundleBytes?.length) {
|
|
709
737
|
const bundlePath = path.join(
|
|
710
738
|
os.tmpdir(),
|
|
@@ -712,8 +740,11 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
712
740
|
);
|
|
713
741
|
try {
|
|
714
742
|
await fs.writeFile(bundlePath, bundleBytes);
|
|
715
|
-
|
|
716
|
-
|
|
743
|
+
let appliedFiles = [];
|
|
744
|
+
await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {
|
|
745
|
+
appliedFiles = await extractZipToDirectory(bundlePath, stagingDir);
|
|
746
|
+
}, preserve);
|
|
747
|
+
return { written: true, appliedFiles };
|
|
717
748
|
} finally {
|
|
718
749
|
await fs.rm(bundlePath, { force: true });
|
|
719
750
|
}
|
|
@@ -727,6 +758,7 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
727
758
|
`Synced bundle for "${skill.name}" is missing SKILL.md or SKILLS.md`
|
|
728
759
|
);
|
|
729
760
|
}
|
|
761
|
+
const appliedFiles = [];
|
|
730
762
|
await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {
|
|
731
763
|
for (const bundleFile of skill.bundle_files || []) {
|
|
732
764
|
const filePath = resolveSkillBundleFilePath(
|
|
@@ -738,9 +770,10 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
738
770
|
filePath,
|
|
739
771
|
Buffer.from(bundleFile.content_b64, "base64")
|
|
740
772
|
);
|
|
773
|
+
appliedFiles.push(toPosixRelativePath(stagingDir, filePath));
|
|
741
774
|
}
|
|
742
|
-
});
|
|
743
|
-
return true;
|
|
775
|
+
}, preserve);
|
|
776
|
+
return { written: true, appliedFiles: appliedFiles.sort() };
|
|
744
777
|
}
|
|
745
778
|
await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {
|
|
746
779
|
await fs.writeFile(
|
|
@@ -748,8 +781,8 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
|
|
|
748
781
|
skill.skill_md ?? "",
|
|
749
782
|
"utf8"
|
|
750
783
|
);
|
|
751
|
-
});
|
|
752
|
-
return true;
|
|
784
|
+
}, preserve);
|
|
785
|
+
return { written: true, appliedFiles: ["SKILL.md"] };
|
|
753
786
|
}
|
|
754
787
|
|
|
755
788
|
// src/runtime/skill-sync/cloud-client.ts
|
|
@@ -1154,7 +1187,7 @@ async function writeCloudSkillWithBundleFallback(skill, dependencies) {
|
|
|
1154
1187
|
const bundleBytes = await dependencies.downloadSkillBundle(skill.skill_source_url);
|
|
1155
1188
|
const wroteBundleToDisk = await dependencies.writeCloudSkillToDisk(skill, bundleBytes);
|
|
1156
1189
|
if (wroteBundleToDisk) {
|
|
1157
|
-
return
|
|
1190
|
+
return wroteBundleToDisk;
|
|
1158
1191
|
}
|
|
1159
1192
|
dependencies.onWarning?.(
|
|
1160
1193
|
`Bundle sync for "${skill.name}" produced no local changes, falling back to SKILL.md payload.`,
|
|
@@ -1268,12 +1301,20 @@ function collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillN
|
|
|
1268
1301
|
}
|
|
1269
1302
|
return applied;
|
|
1270
1303
|
}
|
|
1271
|
-
function
|
|
1304
|
+
function collectAppliedFiles(pullResponse, previousState, appliedFilesByName) {
|
|
1305
|
+
const applied = {};
|
|
1306
|
+
for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
1307
|
+
applied[skill.name] = appliedFilesByName[skill.name] ?? previousState.skills[skill.name]?.appliedFiles;
|
|
1308
|
+
}
|
|
1309
|
+
return applied;
|
|
1310
|
+
}
|
|
1311
|
+
function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}, appliedFiles = {}) {
|
|
1272
1312
|
const localSkillMap = toSkillMap(localSkills);
|
|
1273
1313
|
const skills = Object.fromEntries(
|
|
1274
1314
|
selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
|
|
1275
1315
|
const localSkill = localSkillMap.get(skill.name);
|
|
1276
1316
|
const appliedCloudFolderHash = appliedRevisions[skill.name];
|
|
1317
|
+
const appliedFileList = appliedFiles[skill.name];
|
|
1277
1318
|
return [
|
|
1278
1319
|
skill.name,
|
|
1279
1320
|
{
|
|
@@ -1284,6 +1325,7 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
|
|
|
1284
1325
|
cloudUpdatedAt: skill.updated_at,
|
|
1285
1326
|
...!failedContentNames.has(skill.name) && !skill.skill_source_url ? { cloudContentHash: cloudContentHash(skill) } : {},
|
|
1286
1327
|
...appliedCloudFolderHash !== void 0 ? { appliedCloudFolderHash } : {},
|
|
1328
|
+
...appliedFileList !== void 0 ? { appliedFiles: appliedFileList } : {},
|
|
1287
1329
|
syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
1288
1330
|
}
|
|
1289
1331
|
];
|
|
@@ -1339,7 +1381,7 @@ function applyLegacyFirstRunState(localSkills, scopedState, legacyState) {
|
|
|
1339
1381
|
skills: migratedSkills
|
|
1340
1382
|
};
|
|
1341
1383
|
}
|
|
1342
|
-
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set()) {
|
|
1384
|
+
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set(), appliedFilesByName = {}) {
|
|
1343
1385
|
const localSkillMap = toSkillMap(localSkills);
|
|
1344
1386
|
const warnSkillSync = (message, error) => {
|
|
1345
1387
|
console.warn(`[Notis] ${message}`, error);
|
|
@@ -1349,13 +1391,21 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
1349
1391
|
if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
|
|
1350
1392
|
continue;
|
|
1351
1393
|
}
|
|
1352
|
-
|
|
1394
|
+
const outcome = await writeCloudSkillWithBundleFallback(cloudSkill, {
|
|
1353
1395
|
downloadSkillBundle: deps.downloadSkillBundle,
|
|
1354
|
-
writeCloudSkillToDisk: (skill, bundleBytes) => deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths
|
|
1396
|
+
writeCloudSkillToDisk: (skill, bundleBytes) => deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths, {
|
|
1397
|
+
// What the last write put there. Everything else in the folder was
|
|
1398
|
+
// produced locally and survives this one.
|
|
1399
|
+
previouslyApplied: previousState.skills[skill.name]?.appliedFiles
|
|
1400
|
+
}),
|
|
1355
1401
|
onWarning: warnSkillSync
|
|
1356
|
-
})
|
|
1402
|
+
});
|
|
1403
|
+
if (outcome) {
|
|
1357
1404
|
downloaded += 1;
|
|
1358
1405
|
writtenSkillNames.add(cloudSkill.name);
|
|
1406
|
+
if (typeof outcome === "object" && Array.isArray(outcome.appliedFiles)) {
|
|
1407
|
+
appliedFilesByName[cloudSkill.name] = outcome.appliedFiles;
|
|
1408
|
+
}
|
|
1359
1409
|
} else {
|
|
1360
1410
|
failures.push({ name: cloudSkill.name, error: "Skill content could not be downloaded or written; sync will retry" });
|
|
1361
1411
|
}
|
|
@@ -1387,6 +1437,7 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
1387
1437
|
const localSkills = await deps.scanLocalSkills(syncPaths);
|
|
1388
1438
|
const failedDownloads = [];
|
|
1389
1439
|
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
1440
|
+
const appliedFilesByName = {};
|
|
1390
1441
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
1391
1442
|
pullResponse,
|
|
1392
1443
|
localSkills,
|
|
@@ -1394,7 +1445,8 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
1394
1445
|
syncPaths,
|
|
1395
1446
|
deps,
|
|
1396
1447
|
failedDownloads,
|
|
1397
|
-
writtenSkillNames
|
|
1448
|
+
writtenSkillNames,
|
|
1449
|
+
appliedFilesByName
|
|
1398
1450
|
);
|
|
1399
1451
|
const finalLocalSkills = await deps.scanLocalSkills(syncPaths);
|
|
1400
1452
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1427,7 +1479,8 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
1427
1479
|
lastSyncedAt,
|
|
1428
1480
|
verifiedLinks,
|
|
1429
1481
|
new Set(failedDownloads.map((item) => item.name)),
|
|
1430
|
-
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
1482
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
|
|
1483
|
+
collectAppliedFiles(pullResponse, previousState, appliedFilesByName)
|
|
1431
1484
|
);
|
|
1432
1485
|
for (const [name, entry] of Object.entries(materializedState.skills)) {
|
|
1433
1486
|
if (writtenSkillNames.has(name)) continue;
|
|
@@ -1608,6 +1661,7 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1608
1661
|
}
|
|
1609
1662
|
const failedDownloads = [];
|
|
1610
1663
|
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
1664
|
+
const appliedFilesByName = {};
|
|
1611
1665
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
1612
1666
|
pullResponse,
|
|
1613
1667
|
localSkills,
|
|
@@ -1615,7 +1669,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1615
1669
|
syncPaths,
|
|
1616
1670
|
deps,
|
|
1617
1671
|
failedDownloads,
|
|
1618
|
-
writtenSkillNames
|
|
1672
|
+
writtenSkillNames,
|
|
1673
|
+
appliedFilesByName
|
|
1619
1674
|
);
|
|
1620
1675
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
|
|
1621
1676
|
const symlinkResult = await deps.syncSymlinks(
|
|
@@ -1632,7 +1687,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1632
1687
|
lastSyncedAt,
|
|
1633
1688
|
verifiedLinks,
|
|
1634
1689
|
new Set(failedDownloads.map((item) => item.name)),
|
|
1635
|
-
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
1690
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
|
|
1691
|
+
collectAppliedFiles(pullResponse, previousState, appliedFilesByName)
|
|
1636
1692
|
),
|
|
1637
1693
|
syncPaths
|
|
1638
1694
|
);
|