@notis_ai/cli 0.2.0-beta.154.1 → 0.2.0-beta.155.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 +203 -106
- package/dist/skill-sync/index.js +134 -72
- package/dist/skill-sync/index.js.map +2 -2
- package/package.json +1 -1
- package/src/command-specs/skills.js +3 -2
- package/src/command-specs/tools.js +5 -0
- package/src/runtime/app-dev-build-supervisor.js +47 -0
- package/src/runtime/app-dev-build.js +41 -0
- package/src/runtime/app-dev-server.js +2 -6
- package/src/runtime/skill-sync/cloud-client.ts +5 -3
- package/src/runtime/skill-sync/index.ts +73 -65
- package/src/runtime/skill-sync/symlink-manager.ts +66 -16
- package/src/runtime/skill-sync/types.ts +5 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +1 -1
package/dist/skill-sync/index.js
CHANGED
|
@@ -798,12 +798,13 @@ async function downloadSkillBundle(bundleUrl) {
|
|
|
798
798
|
}
|
|
799
799
|
return Buffer.from(await response.arrayBuffer());
|
|
800
800
|
}
|
|
801
|
-
async function updateAgentTargets(serverUrl, jwt, skillId, targets) {
|
|
801
|
+
async function updateAgentTargets(serverUrl, jwt, skillId, targets, expectedUpdatedAt) {
|
|
802
802
|
return requestJson(`${serverUrl}/portal_skills/agent-targets`, jwt, {
|
|
803
803
|
method: "PATCH",
|
|
804
804
|
body: {
|
|
805
805
|
skill_id: skillId,
|
|
806
|
-
agent_targets: targets
|
|
806
|
+
agent_targets: targets,
|
|
807
|
+
...expectedUpdatedAt ? { expected_updated_at: expectedUpdatedAt } : {}
|
|
807
808
|
}
|
|
808
809
|
});
|
|
809
810
|
}
|
|
@@ -819,6 +820,18 @@ var EXTERNAL_AGENT_SKILL_DIRS = {
|
|
|
819
820
|
codex: path2.join(HOME_DIR2, ".codex", "skills")
|
|
820
821
|
};
|
|
821
822
|
var EXTERNAL_AGENTS = Object.keys(EXTERNAL_AGENT_SKILL_DIRS);
|
|
823
|
+
var AGENT_FAILURE_LABELS = {
|
|
824
|
+
claude_code: "Claude Code",
|
|
825
|
+
cursor: "Cursor",
|
|
826
|
+
codex: "Codex",
|
|
827
|
+
legacy: "legacy ~/.agents/skills"
|
|
828
|
+
};
|
|
829
|
+
function agentFailureLabel(agent) {
|
|
830
|
+
return AGENT_FAILURE_LABELS[agent] ?? agent;
|
|
831
|
+
}
|
|
832
|
+
function agentFolderFailureName(agent) {
|
|
833
|
+
return `${agentFailureLabel(agent)} skills folder`;
|
|
834
|
+
}
|
|
822
835
|
async function removeForeignAccountSymlinks(skillsDir, options = {}) {
|
|
823
836
|
const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };
|
|
824
837
|
const currentRoot = path2.resolve(skillsDir);
|
|
@@ -879,8 +892,9 @@ async function isManagedSymlink(linkPath, managedRoots) {
|
|
|
879
892
|
const target = await fs2.readlink(linkPath);
|
|
880
893
|
const resolvedTarget = path2.resolve(path2.dirname(linkPath), target);
|
|
881
894
|
return managedRoots.some((root) => resolvedTarget === root || resolvedTarget.startsWith(`${root}${path2.sep}`));
|
|
882
|
-
} catch {
|
|
883
|
-
return false;
|
|
895
|
+
} catch (error) {
|
|
896
|
+
if (error?.code === "ENOENT") return false;
|
|
897
|
+
throw error;
|
|
884
898
|
}
|
|
885
899
|
}
|
|
886
900
|
async function ensureCorrectSymlink(linkPath, targetPath) {
|
|
@@ -896,7 +910,8 @@ async function ensureCorrectSymlink(linkPath, targetPath) {
|
|
|
896
910
|
} else {
|
|
897
911
|
return "blocked";
|
|
898
912
|
}
|
|
899
|
-
} catch {
|
|
913
|
+
} catch (error) {
|
|
914
|
+
if (error?.code !== "ENOENT") throw error;
|
|
900
915
|
}
|
|
901
916
|
const relativePath = path2.relative(path2.dirname(linkPath), targetPath);
|
|
902
917
|
await fs2.symlink(relativePath, linkPath);
|
|
@@ -908,19 +923,26 @@ function defaultAgentSkillDirs(skillsDir) {
|
|
|
908
923
|
...EXTERNAL_AGENT_SKILL_DIRS
|
|
909
924
|
};
|
|
910
925
|
}
|
|
911
|
-
async function removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots) {
|
|
926
|
+
async function removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots, failures, agent) {
|
|
912
927
|
let removed = 0;
|
|
913
928
|
try {
|
|
914
929
|
await fs2.mkdir(agentDir, { recursive: true });
|
|
915
930
|
const existingEntries = await fs2.readdir(agentDir, { withFileTypes: true });
|
|
916
931
|
for (const entry of existingEntries) {
|
|
917
932
|
const entryPath = path2.join(agentDir, entry.name);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
933
|
+
try {
|
|
934
|
+
if (!desiredSkills.has(entry.name) && await isManagedSymlink(entryPath, managedRoots)) {
|
|
935
|
+
await fs2.unlink(entryPath);
|
|
936
|
+
removed += 1;
|
|
937
|
+
}
|
|
938
|
+
} catch (error) {
|
|
939
|
+
if (error?.code !== "ENOENT") {
|
|
940
|
+
failures.push({ name: entry.name, error: `${agentFailureLabel(agent)}: could not remove skill link (${error.message})` });
|
|
941
|
+
}
|
|
921
942
|
}
|
|
922
943
|
}
|
|
923
|
-
} catch {
|
|
944
|
+
} catch (error) {
|
|
945
|
+
failures.push({ name: agentFolderFailureName(agent), error: `Could not read agent skills directory (${error.message})` });
|
|
924
946
|
}
|
|
925
947
|
return removed;
|
|
926
948
|
}
|
|
@@ -965,7 +987,7 @@ async function detectDeletedAgentSymlinks(cloudSkills, previousState, skillsDir
|
|
|
965
987
|
continue;
|
|
966
988
|
}
|
|
967
989
|
const previous = previousState.skills[skill.name];
|
|
968
|
-
if (!previous) {
|
|
990
|
+
if (!previous || previous.cloudId !== skill.id || previous.verifiedAgentLinks?.[agent] !== true || !skill.updated_at || previous.cloudUpdatedAt !== skill.updated_at) {
|
|
969
991
|
continue;
|
|
970
992
|
}
|
|
971
993
|
const cloudTargets = normalizeAgentTargets(skill.agent_targets);
|
|
@@ -996,7 +1018,9 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
996
1018
|
const result = {
|
|
997
1019
|
linked: 0,
|
|
998
1020
|
removed: 0,
|
|
999
|
-
skipped: 0
|
|
1021
|
+
skipped: 0,
|
|
1022
|
+
verifiedAgentLinks: {},
|
|
1023
|
+
failures: []
|
|
1000
1024
|
};
|
|
1001
1025
|
const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };
|
|
1002
1026
|
const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;
|
|
@@ -1031,21 +1055,34 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
1031
1055
|
result.skipped += desiredByAgent[agent].size;
|
|
1032
1056
|
continue;
|
|
1033
1057
|
}
|
|
1034
|
-
|
|
1058
|
+
try {
|
|
1059
|
+
await fs2.mkdir(agentDir, { recursive: true });
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
result.failures.push({ name: agentFolderFailureName(agent), error: `Could not create agent skills directory (${error.message})` });
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1035
1064
|
const desiredSkills = desiredByAgent[agent];
|
|
1036
1065
|
if (options.removeUndesired !== false) {
|
|
1037
|
-
result.removed += await removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots);
|
|
1066
|
+
result.removed += await removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots, result.failures, agent);
|
|
1038
1067
|
}
|
|
1039
1068
|
for (const skillName of desiredSkills) {
|
|
1040
1069
|
const targetPath = path2.join(skillsDir, skillName);
|
|
1041
1070
|
const linkPath = path2.join(agentDir, safeName(skillName, agentDir));
|
|
1042
1071
|
try {
|
|
1043
|
-
await fs2.
|
|
1072
|
+
if (!(await fs2.stat(path2.join(targetPath, "SKILL.md"))).isFile()) throw new Error("Missing SKILL.md");
|
|
1044
1073
|
} catch {
|
|
1045
1074
|
result.skipped += 1;
|
|
1075
|
+
result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: SKILL.md is missing or unreadable` });
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
let syncOutcome;
|
|
1079
|
+
try {
|
|
1080
|
+
syncOutcome = await ensureCorrectSymlink(linkPath, targetPath);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
result.skipped += 1;
|
|
1083
|
+
result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: could not create skill link (${error.message})` });
|
|
1046
1084
|
continue;
|
|
1047
1085
|
}
|
|
1048
|
-
const syncOutcome = await ensureCorrectSymlink(linkPath, targetPath);
|
|
1049
1086
|
if (syncOutcome === "linked") {
|
|
1050
1087
|
result.linked += 1;
|
|
1051
1088
|
} else if (syncOutcome === "blocked") {
|
|
@@ -1053,9 +1090,13 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
1053
1090
|
`[skill-sync] Could not link "${skillName}" for ${agent}: non-symlink entry blocks ${linkPath}`
|
|
1054
1091
|
);
|
|
1055
1092
|
result.skipped += 1;
|
|
1093
|
+
result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: an existing file or folder blocks the skill link` });
|
|
1056
1094
|
} else {
|
|
1057
1095
|
result.skipped += 1;
|
|
1058
1096
|
}
|
|
1097
|
+
if (syncOutcome !== "blocked") {
|
|
1098
|
+
result.verifiedAgentLinks[skillName] = { ...result.verifiedAgentLinks[skillName], [agent]: true };
|
|
1099
|
+
}
|
|
1059
1100
|
}
|
|
1060
1101
|
}
|
|
1061
1102
|
if (options.removeUndesired !== false && !Object.values(agentSkillDirs).some(
|
|
@@ -1064,7 +1105,9 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
1064
1105
|
result.removed += await removeUndesiredManagedSymlinks(
|
|
1065
1106
|
legacyGlobalSkillsDir,
|
|
1066
1107
|
/* @__PURE__ */ new Set(),
|
|
1067
|
-
managedRoots
|
|
1108
|
+
managedRoots,
|
|
1109
|
+
result.failures,
|
|
1110
|
+
"legacy"
|
|
1068
1111
|
);
|
|
1069
1112
|
}
|
|
1070
1113
|
return result;
|
|
@@ -1188,7 +1231,7 @@ function shouldWriteCloudSkill(cloudSkill, localSkills, previousState) {
|
|
|
1188
1231
|
const localChangedSinceLastSync = !previous || previous.folderHash !== localSkill.folderHash;
|
|
1189
1232
|
return !localChangedSinceLastSync && Boolean(cloudHash) && cloudHash !== localSkill.folderHash;
|
|
1190
1233
|
}
|
|
1191
|
-
function buildSyncState(pullResponse, localSkills, lastSyncedAt) {
|
|
1234
|
+
function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}) {
|
|
1192
1235
|
const localSkillMap = toSkillMap(localSkills);
|
|
1193
1236
|
const skills = Object.fromEntries(
|
|
1194
1237
|
pullResponse.skills.map((skill) => {
|
|
@@ -1199,6 +1242,8 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt) {
|
|
|
1199
1242
|
cloudId: skill.id,
|
|
1200
1243
|
folderHash: localSkill?.folderHash || skill.skill_folder_hash || "",
|
|
1201
1244
|
agentTargets: normalizeAgentTargets(skill.agent_targets),
|
|
1245
|
+
verifiedAgentLinks: skill.status === "active" ? verifiedAgentLinks[skill.name] ?? {} : {},
|
|
1246
|
+
cloudUpdatedAt: skill.updated_at,
|
|
1202
1247
|
syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
1203
1248
|
}
|
|
1204
1249
|
];
|
|
@@ -1254,7 +1299,7 @@ function applyLegacyFirstRunState(localSkills, scopedState, legacyState) {
|
|
|
1254
1299
|
skills: migratedSkills
|
|
1255
1300
|
};
|
|
1256
1301
|
}
|
|
1257
|
-
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps) {
|
|
1302
|
+
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = []) {
|
|
1258
1303
|
const localSkillMap = toSkillMap(localSkills);
|
|
1259
1304
|
const warnSkillSync = (message, error) => {
|
|
1260
1305
|
console.warn(`[Notis] ${message}`, error);
|
|
@@ -1270,6 +1315,8 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
1270
1315
|
onWarning: warnSkillSync
|
|
1271
1316
|
})) {
|
|
1272
1317
|
downloaded += 1;
|
|
1318
|
+
} else {
|
|
1319
|
+
failures.push({ name: cloudSkill.name, error: "Skill content could not be downloaded or written; sync will retry" });
|
|
1273
1320
|
}
|
|
1274
1321
|
}
|
|
1275
1322
|
return downloaded;
|
|
@@ -1297,25 +1344,42 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
1297
1344
|
assertSkillsPullAuthorized(pullResponse);
|
|
1298
1345
|
const previousState = await deps.readSyncState(syncPaths);
|
|
1299
1346
|
const localSkills = await deps.scanLocalSkills(syncPaths);
|
|
1347
|
+
const failedDownloads = [];
|
|
1300
1348
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
1301
1349
|
pullResponse,
|
|
1302
1350
|
localSkills,
|
|
1303
1351
|
previousState,
|
|
1304
1352
|
syncPaths,
|
|
1305
|
-
deps
|
|
1353
|
+
deps,
|
|
1354
|
+
failedDownloads
|
|
1306
1355
|
);
|
|
1307
1356
|
const finalLocalSkills = await deps.scanLocalSkills(syncPaths);
|
|
1308
1357
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
1309
1358
|
const relinkSkillNames = new Set(options.relinkSkillNames || []);
|
|
1359
|
+
const failures = [...failedDownloads];
|
|
1360
|
+
const verifiedLinks = {};
|
|
1361
|
+
for (const skill of pullResponse.skills) {
|
|
1362
|
+
const previous = previousState.skills[skill.name];
|
|
1363
|
+
if (skill.updated_at && previous?.cloudId === skill.id && previous.cloudUpdatedAt === skill.updated_at) {
|
|
1364
|
+
verifiedLinks[skill.name] = { ...previous.verifiedAgentLinks };
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1310
1367
|
if (relinkSkillNames.size > 0) {
|
|
1311
|
-
await deps.syncSymlinks(
|
|
1368
|
+
const relinked = await deps.syncSymlinks(
|
|
1312
1369
|
pullResponse.skills.filter((skill) => relinkSkillNames.has(skill.name)),
|
|
1313
1370
|
syncPaths.skillsDir,
|
|
1314
1371
|
{ removeUndesired: false }
|
|
1315
1372
|
);
|
|
1373
|
+
failures.push(...(relinked.failures ?? []).filter(
|
|
1374
|
+
(failure) => !failedDownloads.some((download) => download.name === failure.name)
|
|
1375
|
+
));
|
|
1376
|
+
for (const name of relinkSkillNames) {
|
|
1377
|
+
verifiedLinks[name] = relinked.verifiedAgentLinks?.[name] ?? {};
|
|
1378
|
+
}
|
|
1316
1379
|
}
|
|
1380
|
+
for (const failure of failedDownloads) delete verifiedLinks[failure.name];
|
|
1317
1381
|
await deps.writeSyncState(
|
|
1318
|
-
buildSyncState(pullResponse, finalLocalSkills, lastSyncedAt),
|
|
1382
|
+
buildSyncState(pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks),
|
|
1319
1383
|
syncPaths
|
|
1320
1384
|
);
|
|
1321
1385
|
return {
|
|
@@ -1323,10 +1387,11 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
1323
1387
|
downloaded,
|
|
1324
1388
|
deleted: 0,
|
|
1325
1389
|
removed: 0,
|
|
1326
|
-
lastSyncedAt
|
|
1390
|
+
lastSyncedAt,
|
|
1391
|
+
...failures.length ? { failedLinks: failures } : {}
|
|
1327
1392
|
};
|
|
1328
1393
|
}
|
|
1329
|
-
async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previousState, scopedState, skillsDir, deps) {
|
|
1394
|
+
async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previousState, scopedState, skillsDir, deps, failures) {
|
|
1330
1395
|
if (isEmptySyncState(scopedState)) {
|
|
1331
1396
|
return 0;
|
|
1332
1397
|
}
|
|
@@ -1338,22 +1403,6 @@ async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previo
|
|
|
1338
1403
|
if (deletions.length === 0) {
|
|
1339
1404
|
return 0;
|
|
1340
1405
|
}
|
|
1341
|
-
const latestSkillsById = new Map(pullResponse.skills.map((skill) => [skill.id, skill]));
|
|
1342
|
-
let fresh = null;
|
|
1343
|
-
try {
|
|
1344
|
-
fresh = await deps.pullSkills(serverUrl, jwt);
|
|
1345
|
-
} catch (error) {
|
|
1346
|
-
console.warn(
|
|
1347
|
-
"[skill-sync] Could not re-pull latest agent targets before deactivation; using the top-of-sync snapshot.",
|
|
1348
|
-
error
|
|
1349
|
-
);
|
|
1350
|
-
}
|
|
1351
|
-
if (fresh) {
|
|
1352
|
-
assertSkillsPullAuthorized(fresh);
|
|
1353
|
-
for (const skill of fresh.skills) {
|
|
1354
|
-
latestSkillsById.set(skill.id, skill);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
1406
|
const agentsBySkill = /* @__PURE__ */ new Map();
|
|
1358
1407
|
for (const deletion of deletions) {
|
|
1359
1408
|
const entry = agentsBySkill.get(deletion.skillId) ?? {
|
|
@@ -1363,31 +1412,35 @@ async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previo
|
|
|
1363
1412
|
entry.agents.add(deletion.agent);
|
|
1364
1413
|
agentsBySkill.set(deletion.skillId, entry);
|
|
1365
1414
|
}
|
|
1366
|
-
const
|
|
1415
|
+
const fresh = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));
|
|
1416
|
+
assertSkillsPullAuthorized(fresh);
|
|
1417
|
+
Object.assign(pullResponse, fresh);
|
|
1418
|
+
let needsRefresh = false;
|
|
1367
1419
|
let deactivated = 0;
|
|
1368
1420
|
for (const [skillId, { skillName, agents }] of agentsBySkill) {
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
const nextTargets = { ...normalizeAgentTargets(latest.agent_targets) };
|
|
1374
|
-
for (const agent of agents) {
|
|
1375
|
-
nextTargets[agent] = false;
|
|
1376
|
-
}
|
|
1421
|
+
const skill = pullResponse.skills.find((item) => item.id === skillId);
|
|
1422
|
+
const previous = previousState.skills[skillName];
|
|
1423
|
+
if (!skill?.updated_at || previous?.cloudUpdatedAt !== skill.updated_at) continue;
|
|
1424
|
+
const patch = Object.fromEntries([...agents].map((agent) => [agent, false]));
|
|
1377
1425
|
try {
|
|
1378
|
-
await deps.updateAgentTargets(serverUrl, jwt, skillId,
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
inMemory.agent_targets = nextTargets;
|
|
1426
|
+
const saved = await deps.updateAgentTargets(serverUrl, jwt, skillId, patch, skill.updated_at);
|
|
1427
|
+
if (saved.success !== true || !saved.updated_at?.trim() || saved.updated_at === skill.updated_at || !["notis", "claude_code", "cursor", "codex"].every((agent) => typeof saved.agent_targets?.[agent] === "boolean") || ![...agents].every((agent) => saved.agent_targets[agent] === false)) {
|
|
1428
|
+
throw new Error("Assignment update did not return a verified saved revision");
|
|
1382
1429
|
}
|
|
1430
|
+
skill.agent_targets = saved.agent_targets;
|
|
1431
|
+
skill.updated_at = saved.updated_at;
|
|
1383
1432
|
deactivated += agents.size;
|
|
1384
1433
|
} catch (error) {
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
);
|
|
1434
|
+
needsRefresh = true;
|
|
1435
|
+
failures.push({ name: skillName, error: "Could not save the local agent removal; refreshed saved assignments" });
|
|
1436
|
+
console.warn(`[skill-sync] Assignment changed or could not be saved for "${skillName}"; refreshing before reconciliation.`, error);
|
|
1389
1437
|
}
|
|
1390
1438
|
}
|
|
1439
|
+
if (needsRefresh) {
|
|
1440
|
+
const refreshed = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));
|
|
1441
|
+
assertSkillsPullAuthorized(refreshed);
|
|
1442
|
+
Object.assign(pullResponse, refreshed);
|
|
1443
|
+
}
|
|
1391
1444
|
return deactivated;
|
|
1392
1445
|
}
|
|
1393
1446
|
async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
@@ -1430,6 +1483,18 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1430
1483
|
pullResponse.skills.filter((skill) => skill.source === "curated").map((skill) => skill.name)
|
|
1431
1484
|
);
|
|
1432
1485
|
const protectedSkillNames = /* @__PURE__ */ new Set([...cloudCuratedSkillNames, ...BASE_SKILL_NAMES]);
|
|
1486
|
+
const scopedState = withoutBaseSkillState(await deps.readSyncState(syncPaths));
|
|
1487
|
+
const assignmentFailures = [];
|
|
1488
|
+
const deactivated = syncSettings.agent_targets_conditional_updates === true ? await deactivateDeletedAgentSkills(
|
|
1489
|
+
serverUrl,
|
|
1490
|
+
jwt,
|
|
1491
|
+
pullResponse,
|
|
1492
|
+
scopedState,
|
|
1493
|
+
scopedState,
|
|
1494
|
+
syncPaths.skillsDir,
|
|
1495
|
+
deps,
|
|
1496
|
+
assignmentFailures
|
|
1497
|
+
) : 0;
|
|
1433
1498
|
const authUserId = decodeJwtSubject(jwt);
|
|
1434
1499
|
let previousAuthState = null;
|
|
1435
1500
|
if (authUserId && authUserId !== syncUserId) {
|
|
@@ -1444,22 +1509,12 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1444
1509
|
protectedSkillNames
|
|
1445
1510
|
});
|
|
1446
1511
|
const localSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
|
|
1447
|
-
const scopedState = withoutBaseSkillState(await deps.readSyncState(syncPaths));
|
|
1448
1512
|
const previousState = withoutBaseSkillState(applyLegacyFirstRunState(
|
|
1449
1513
|
localSkills,
|
|
1450
1514
|
scopedState,
|
|
1451
1515
|
isEmptySyncState(scopedState) ? !previousAuthState || isEmptySyncState(previousAuthState) ? await deps.readLegacySyncState(syncPaths) : previousAuthState : null
|
|
1452
1516
|
));
|
|
1453
|
-
const
|
|
1454
|
-
serverUrl,
|
|
1455
|
-
jwt,
|
|
1456
|
-
pullResponse,
|
|
1457
|
-
previousState,
|
|
1458
|
-
scopedState,
|
|
1459
|
-
syncPaths.skillsDir,
|
|
1460
|
-
deps
|
|
1461
|
-
);
|
|
1462
|
-
await deps.syncSymlinks(
|
|
1517
|
+
const gatheredSymlinkResult = await deps.syncSymlinks(
|
|
1463
1518
|
buildLocalSymlinkCandidates(pullResponse, localSkills, previousState),
|
|
1464
1519
|
syncPaths.skillsDir
|
|
1465
1520
|
);
|
|
@@ -1490,21 +1545,25 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1490
1545
|
deleted += 1;
|
|
1491
1546
|
}
|
|
1492
1547
|
}
|
|
1548
|
+
const failedDownloads = [];
|
|
1493
1549
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
1494
1550
|
pullResponse,
|
|
1495
1551
|
localSkills,
|
|
1496
1552
|
previousState,
|
|
1497
1553
|
syncPaths,
|
|
1498
|
-
deps
|
|
1554
|
+
deps,
|
|
1555
|
+
failedDownloads
|
|
1499
1556
|
);
|
|
1500
1557
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
|
|
1501
1558
|
const symlinkResult = await deps.syncSymlinks(
|
|
1502
1559
|
buildLocalSymlinkCandidates(pullResponse, finalLocalSkills, previousState),
|
|
1503
1560
|
syncPaths.skillsDir
|
|
1504
1561
|
);
|
|
1562
|
+
const verifiedLinks = { ...symlinkResult.verifiedAgentLinks ?? {} };
|
|
1563
|
+
for (const failure of failedDownloads) delete verifiedLinks[failure.name];
|
|
1505
1564
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
1506
1565
|
await deps.writeSyncState(
|
|
1507
|
-
buildSyncState(pullResponse, finalLocalSkills, lastSyncedAt),
|
|
1566
|
+
buildSyncState(pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks),
|
|
1508
1567
|
syncPaths
|
|
1509
1568
|
);
|
|
1510
1569
|
return {
|
|
@@ -1514,9 +1573,12 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1514
1573
|
downloaded,
|
|
1515
1574
|
deleted,
|
|
1516
1575
|
deactivated,
|
|
1517
|
-
linked: symlinkResult.linked,
|
|
1518
|
-
removed: foreignLinksRemoved + symlinkResult.removed,
|
|
1576
|
+
linked: gatheredSymlinkResult.linked + symlinkResult.linked,
|
|
1577
|
+
removed: foreignLinksRemoved + gatheredSymlinkResult.removed + symlinkResult.removed,
|
|
1519
1578
|
skipped: symlinkResult.skipped,
|
|
1579
|
+
failedLinks: [...assignmentFailures, ...failedDownloads, ...(symlinkResult.failures ?? []).filter(
|
|
1580
|
+
(failure) => !failedDownloads.some((download) => download.name === failure.name)
|
|
1581
|
+
)],
|
|
1520
1582
|
lastSyncedAt,
|
|
1521
1583
|
failedPushes
|
|
1522
1584
|
};
|