@sellable/install 0.1.579 → 0.1.581
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/container/Dockerfile +7 -7
- package/container/README.md +2 -2
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/fly-admin-image/Dockerfile +13 -0
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +82 -1
- package/lib/sellable-agent/fly-customer-image/Dockerfile +13 -4
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +2 -2
- package/lib/sellable-agent/fly-skills-bridge-exec.mjs +45 -0
- package/lib/sellable-agent/fly-skills-bridge.mjs +977 -0
- package/lib/sellable-agent/hermes-bridge.mjs +126 -3
- package/lib/sellable-agent/host-bootstrap.mjs +2 -2
- package/lib/sellable-agent/host-worker.mjs +12 -5
- package/lib/sellable-agent/profile-materializer.mjs +169 -14
- package/lib/sellable-agent/provisioning-adapter.mjs +5 -8
- package/package.json +1 -1
|
@@ -1596,6 +1596,113 @@ export function patchHermesSourceFile(patchKind, source) {
|
|
|
1596
1596
|
return patched;
|
|
1597
1597
|
}
|
|
1598
1598
|
|
|
1599
|
+
if (patchKind === "skills-cache-freshness-v020") {
|
|
1600
|
+
let patched = replaceExactlyOnce(
|
|
1601
|
+
source,
|
|
1602
|
+
" sig.append((str(d), m))",
|
|
1603
|
+
[
|
|
1604
|
+
" skill_files = []",
|
|
1605
|
+
" for root, dirs, files in os.walk(d, followlinks=False):",
|
|
1606
|
+
" dirs[:] = sorted(",
|
|
1607
|
+
" name for name in dirs",
|
|
1608
|
+
" if not os.path.islink(os.path.join(root, name))",
|
|
1609
|
+
" )",
|
|
1610
|
+
' if "SKILL.md" not in files:',
|
|
1611
|
+
" continue",
|
|
1612
|
+
' skill_path = os.path.join(root, "SKILL.md")',
|
|
1613
|
+
" try:",
|
|
1614
|
+
" skill_stat = os.stat(skill_path, follow_symlinks=False)",
|
|
1615
|
+
" skill_files.append(",
|
|
1616
|
+
" (skill_path, skill_stat.st_mtime_ns, skill_stat.st_size)",
|
|
1617
|
+
" )",
|
|
1618
|
+
" except OSError:",
|
|
1619
|
+
" continue",
|
|
1620
|
+
" sig.append((str(d), m, tuple(skill_files)))",
|
|
1621
|
+
].join("\n"),
|
|
1622
|
+
"v0.20 live SKILL.md signature"
|
|
1623
|
+
);
|
|
1624
|
+
patched = replaceExactlyOnce(
|
|
1625
|
+
patched,
|
|
1626
|
+
" disabled = set() if skip_disabled else _get_disabled_skill_names()",
|
|
1627
|
+
[
|
|
1628
|
+
" disabled = _get_disabled_skill_names()",
|
|
1629
|
+
" filtered_disabled = set() if skip_disabled else disabled",
|
|
1630
|
+
].join("\n"),
|
|
1631
|
+
"v0.20 shared disabled signature"
|
|
1632
|
+
);
|
|
1633
|
+
patched = replaceExactlyOnce(
|
|
1634
|
+
patched,
|
|
1635
|
+
" if name in disabled:\n continue",
|
|
1636
|
+
" if name in filtered_disabled:\n continue",
|
|
1637
|
+
"v0.20 filtered disabled behavior"
|
|
1638
|
+
);
|
|
1639
|
+
patched = replaceExactlyOnce(
|
|
1640
|
+
patched,
|
|
1641
|
+
" skills = []\n seen_names: set = set()",
|
|
1642
|
+
[
|
|
1643
|
+
" # A signature change invalidates both filtered and unfiltered",
|
|
1644
|
+
" # variants together; neither waits for the 30-second TTL.",
|
|
1645
|
+
" if cached is not None and cached[0] != signature:",
|
|
1646
|
+
" _SKILLS_CACHE.clear()",
|
|
1647
|
+
"",
|
|
1648
|
+
" skills = []",
|
|
1649
|
+
" seen_names: set = set()",
|
|
1650
|
+
].join("\n"),
|
|
1651
|
+
"v0.20 both-variant cache invalidation"
|
|
1652
|
+
);
|
|
1653
|
+
return patched;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
if (patchKind === "skills-prompt-freshness-v020") {
|
|
1657
|
+
let patched = replaceExactlyOnce(
|
|
1658
|
+
source,
|
|
1659
|
+
"_SKILLS_PROMPT_CACHE_LOCK = threading.Lock()",
|
|
1660
|
+
[
|
|
1661
|
+
"_SKILLS_PROMPT_CACHE_LOCK = threading.Lock()",
|
|
1662
|
+
"_SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE = None",
|
|
1663
|
+
].join("\n"),
|
|
1664
|
+
"v0.20 prompt signature state"
|
|
1665
|
+
);
|
|
1666
|
+
patched = replaceExactlyOnce(
|
|
1667
|
+
patched,
|
|
1668
|
+
" disabled = get_disabled_skill_names(_platform_hint or None)\n cache_key = (",
|
|
1669
|
+
[
|
|
1670
|
+
" disabled = get_disabled_skill_names(_platform_hint or None)",
|
|
1671
|
+
" signature_roots = [skills_dir, *external_dirs]",
|
|
1672
|
+
" live_signature = tuple(",
|
|
1673
|
+
" (",
|
|
1674
|
+
" str(root),",
|
|
1675
|
+
" tuple(",
|
|
1676
|
+
" (path, tuple(values))",
|
|
1677
|
+
" for path, values in sorted(_build_skills_manifest(root).items())",
|
|
1678
|
+
" ),",
|
|
1679
|
+
" )",
|
|
1680
|
+
" for root in signature_roots",
|
|
1681
|
+
" if root.exists()",
|
|
1682
|
+
" )",
|
|
1683
|
+
" cache_key = (",
|
|
1684
|
+
].join("\n"),
|
|
1685
|
+
"v0.20 prompt live signature"
|
|
1686
|
+
);
|
|
1687
|
+
patched = replaceExactlyOnce(
|
|
1688
|
+
patched,
|
|
1689
|
+
" tuple(sorted(compact_categories or ())),\n )\n with _SKILLS_PROMPT_CACHE_LOCK:\n cached = _SKILLS_PROMPT_CACHE.get(cache_key)",
|
|
1690
|
+
[
|
|
1691
|
+
" tuple(sorted(compact_categories or ())),",
|
|
1692
|
+
" live_signature,",
|
|
1693
|
+
" )",
|
|
1694
|
+
" global _SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE",
|
|
1695
|
+
" with _SKILLS_PROMPT_CACHE_LOCK:",
|
|
1696
|
+
" if _SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE != live_signature:",
|
|
1697
|
+
" _SKILLS_PROMPT_CACHE.clear()",
|
|
1698
|
+
" _SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE = live_signature",
|
|
1699
|
+
" cached = _SKILLS_PROMPT_CACHE.get(cache_key)",
|
|
1700
|
+
].join("\n"),
|
|
1701
|
+
"v0.20 prompt cache invalidation"
|
|
1702
|
+
);
|
|
1703
|
+
return patched;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1599
1706
|
throw new HermesAgentBridgeError(
|
|
1600
1707
|
"HERMES_BRIDGE_UNKNOWN_PATCH_KIND",
|
|
1601
1708
|
`Unknown Hermes bridge patch kind: ${patchKind}`,
|
|
@@ -1820,7 +1927,7 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
1820
1927
|
sourceSha256:
|
|
1821
1928
|
"f9d50eec7b98819fa91b8ce640c8a32eff0726e0f349832763852bd82bbbb958",
|
|
1822
1929
|
patchedSha256:
|
|
1823
|
-
"
|
|
1930
|
+
"4b0bcc47a07a43402958bbd09524483f8adb370bcbfa6ffa99b63b4ca5b35ff1",
|
|
1824
1931
|
},
|
|
1825
1932
|
{
|
|
1826
1933
|
path: "gateway/run.py",
|
|
@@ -1836,7 +1943,7 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
1836
1943
|
sourceSha256:
|
|
1837
1944
|
"a0e136367b64007d7b49ea006ab0aa7dcc66b12134b512a463a03bd69fb8a90c",
|
|
1838
1945
|
patchedSha256:
|
|
1839
|
-
"
|
|
1946
|
+
"5f19a22c832db2e93b3da0fe9ea62606968233aa964318f8f81b0ae3dc7c8a13",
|
|
1840
1947
|
},
|
|
1841
1948
|
{
|
|
1842
1949
|
path: "hermes_cli/web_routers/tools.py",
|
|
@@ -1852,7 +1959,23 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
1852
1959
|
sourceSha256:
|
|
1853
1960
|
"aba4c2b9c8691ccc86518cfa10dcd92e55d7b2103c1c6807f54ddf58919f3f48",
|
|
1854
1961
|
patchedSha256:
|
|
1855
|
-
"
|
|
1962
|
+
"8302998c684a290eae1dd8c34f93c790ced764340d72d52995021b15625f0acb",
|
|
1963
|
+
},
|
|
1964
|
+
{
|
|
1965
|
+
path: "tools/skills_tool.py",
|
|
1966
|
+
patchKind: "skills-cache-freshness-v020",
|
|
1967
|
+
sourceSha256:
|
|
1968
|
+
"6cfe456c8872c91309083694290e5e2cbdb64a1b2f4ac9c2cb38973fab2228ab",
|
|
1969
|
+
patchedSha256:
|
|
1970
|
+
"9a1b1417879d9aac6d5b6e1713237193009e2d67dbc691db35ef5940e94f9397",
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
path: "agent/prompt_builder.py",
|
|
1974
|
+
patchKind: "skills-prompt-freshness-v020",
|
|
1975
|
+
sourceSha256:
|
|
1976
|
+
"2a8a8b2b6c0ff830349f68b5c2f26bf2c9871ae03caf516cff3628c00b5769c7",
|
|
1977
|
+
patchedSha256:
|
|
1978
|
+
"4a55eb6dd4e79d35fca9fd02544444576c892ca02dfebb0c97954156cc470fdb",
|
|
1856
1979
|
},
|
|
1857
1980
|
],
|
|
1858
1981
|
});
|
|
@@ -39,8 +39,8 @@ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
|
39
39
|
const VERSION = "sellable-agent-host-bootstrap/v1";
|
|
40
40
|
const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
|
|
41
41
|
const INSTALLER_PACKAGE =
|
|
42
|
-
"@sellable/install@0.1.
|
|
43
|
-
const MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
42
|
+
"@sellable/install@0.1.581";
|
|
43
|
+
const MCP_PACKAGE = "@sellable/mcp@0.1.859";
|
|
44
44
|
const HERMES_VERSION = "0.18.0";
|
|
45
45
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
46
46
|
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
relative,
|
|
30
30
|
resolve,
|
|
31
31
|
} from "node:path";
|
|
32
|
+
import { readSkillsProofObservation } from "./fly-skills-bridge.mjs";
|
|
32
33
|
import {
|
|
33
34
|
commitRetainedProfilePromotion,
|
|
34
35
|
computeAgentProfileDigest,
|
|
@@ -77,6 +78,11 @@ const SECRET_PATTERN =
|
|
|
77
78
|
/sat_[A-Za-z0-9_-]+|xox[bap]-[A-Za-z0-9-]+|asec\.v1\.|authorization[_-]?code|access[_-]?token|refresh[_-]?token/i;
|
|
78
79
|
const STATE_FILE = "worker-state.json";
|
|
79
80
|
|
|
81
|
+
export function attachSkillsProofObservation(observed, profileRoot) {
|
|
82
|
+
const skills = readSkillsProofObservation(profileRoot);
|
|
83
|
+
return skills ? { ...observed, skills } : observed;
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
function stableJson(value) {
|
|
81
87
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
82
88
|
if (value && typeof value === "object") {
|
|
@@ -1540,10 +1546,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1540
1546
|
]) ||
|
|
1541
1547
|
pinned.hermesCli !== "hermes" ||
|
|
1542
1548
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1543
|
-
pinned.installerPackage !==
|
|
1544
|
-
|
|
1545
|
-
pinned.mcpPackage !==
|
|
1546
|
-
"@sellable/mcp@0.1.858" ||
|
|
1549
|
+
pinned.installerPackage !== "@sellable/install@0.1.581" ||
|
|
1550
|
+
pinned.mcpPackage !== "@sellable/mcp@0.1.859" ||
|
|
1547
1551
|
!Array.isArray(policy.toolInclude) ||
|
|
1548
1552
|
policy.toolInclude.length === 0 ||
|
|
1549
1553
|
!Number.isSafeInteger(slack.generation) ||
|
|
@@ -2348,7 +2352,10 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
|
|
|
2348
2352
|
observed.process.uid !== config.runtimeUid
|
|
2349
2353
|
)
|
|
2350
2354
|
throw new Error("runtime observation identity rejected");
|
|
2351
|
-
return
|
|
2355
|
+
return attachSkillsProofObservation(
|
|
2356
|
+
observed,
|
|
2357
|
+
join(realpathSync(config.profilesRoot), context.profileId)
|
|
2358
|
+
);
|
|
2352
2359
|
};
|
|
2353
2360
|
const ensureRefreshTarget = async () => {
|
|
2354
2361
|
if (refreshPrepared) return;
|
|
@@ -201,10 +201,8 @@ function validateDesired(desired) {
|
|
|
201
201
|
desired.serviceCredentialGeneration < 1 ||
|
|
202
202
|
desired.hermesCli !== "hermes" ||
|
|
203
203
|
desired.hermesVersion !== "0.18.0" ||
|
|
204
|
-
desired.installerPackage !==
|
|
205
|
-
|
|
206
|
-
desired.mcpPackage !==
|
|
207
|
-
"@sellable/mcp@0.1.858" ||
|
|
204
|
+
desired.installerPackage !== "@sellable/install@0.1.581" ||
|
|
205
|
+
desired.mcpPackage !== "@sellable/mcp@0.1.859" ||
|
|
208
206
|
!Array.isArray(desired.toolInclude) ||
|
|
209
207
|
desired.toolInclude.length === 0 ||
|
|
210
208
|
desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
|
|
@@ -408,6 +406,113 @@ function configFor(desired, profileId) {
|
|
|
408
406
|
};
|
|
409
407
|
}
|
|
410
408
|
|
|
409
|
+
const LIVE_SKILLS_CONFIG_KEYS = Object.freeze([
|
|
410
|
+
"disabled",
|
|
411
|
+
"platform_disabled",
|
|
412
|
+
]);
|
|
413
|
+
|
|
414
|
+
function profileConfigWithoutLiveSkills(value) {
|
|
415
|
+
const normalized = structuredClone(value);
|
|
416
|
+
if (
|
|
417
|
+
normalized?.skills &&
|
|
418
|
+
typeof normalized.skills === "object" &&
|
|
419
|
+
!Array.isArray(normalized.skills)
|
|
420
|
+
) {
|
|
421
|
+
for (const key of LIVE_SKILLS_CONFIG_KEYS) delete normalized.skills[key];
|
|
422
|
+
if (Object.keys(normalized.skills).length === 0) delete normalized.skills;
|
|
423
|
+
}
|
|
424
|
+
return normalized;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function normalizedProfileConfigBytes(pathValue) {
|
|
428
|
+
const value = JSON.parse(readFileSync(pathValue, "utf8"));
|
|
429
|
+
return Buffer.from(
|
|
430
|
+
`${JSON.stringify(profileConfigWithoutLiveSkills(value), null, 2)}\n`,
|
|
431
|
+
"utf8"
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function mergeLiveSkillsConfig(sourceRoot, targetRoot) {
|
|
436
|
+
const sourcePath = join(sourceRoot, "config.yaml");
|
|
437
|
+
const targetPath = join(targetRoot, "config.yaml");
|
|
438
|
+
if (!existsSync(sourcePath) || !existsSync(targetPath)) return;
|
|
439
|
+
const source = JSON.parse(readFileSync(sourcePath, "utf8"));
|
|
440
|
+
const target = JSON.parse(readFileSync(targetPath, "utf8"));
|
|
441
|
+
const sourceSkills = source?.skills;
|
|
442
|
+
if (
|
|
443
|
+
!sourceSkills ||
|
|
444
|
+
typeof sourceSkills !== "object" ||
|
|
445
|
+
Array.isArray(sourceSkills)
|
|
446
|
+
) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const preserved = Object.fromEntries(
|
|
450
|
+
LIVE_SKILLS_CONFIG_KEYS.filter((key) =>
|
|
451
|
+
Object.prototype.hasOwnProperty.call(sourceSkills, key)
|
|
452
|
+
).map((key) => [key, structuredClone(sourceSkills[key])])
|
|
453
|
+
);
|
|
454
|
+
if (Object.keys(preserved).length === 0) return;
|
|
455
|
+
target.skills = {
|
|
456
|
+
...(target.skills &&
|
|
457
|
+
typeof target.skills === "object" &&
|
|
458
|
+
!Array.isArray(target.skills)
|
|
459
|
+
? target.skills
|
|
460
|
+
: {}),
|
|
461
|
+
...preserved,
|
|
462
|
+
};
|
|
463
|
+
atomicWrite(
|
|
464
|
+
targetPath,
|
|
465
|
+
`${JSON.stringify(target, null, 2)}\n`,
|
|
466
|
+
lstatSync(targetPath).mode & 0o777
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function mergeLiveSkillsState(sourceRoot, targetRoot) {
|
|
471
|
+
if (!existsSync(sourceRoot)) return;
|
|
472
|
+
const sourceSkills = join(sourceRoot, "skills");
|
|
473
|
+
const targetSkills = join(targetRoot, "skills");
|
|
474
|
+
if (existsSync(sourceSkills)) mergeHermesState(sourceSkills, targetSkills);
|
|
475
|
+
mergeLiveSkillsConfig(sourceRoot, targetRoot);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function preservedSkillsConfigIdentity(profileRoot) {
|
|
479
|
+
if (!existsSync(profileRoot)) return null;
|
|
480
|
+
const entries = [];
|
|
481
|
+
const skillsRoot = join(profileRoot, "skills");
|
|
482
|
+
const walk = (directory, relativePath = "") => {
|
|
483
|
+
for (const name of readdirSync(directory).sort()) {
|
|
484
|
+
const nextRelative = relativePath ? `${relativePath}/${name}` : name;
|
|
485
|
+
if (nextRelative === "sellable") continue;
|
|
486
|
+
const pathValue = join(directory, name);
|
|
487
|
+
const link = lstatSync(pathValue);
|
|
488
|
+
if (link.isSymbolicLink())
|
|
489
|
+
throw new Error("live skills symlink rejected");
|
|
490
|
+
if (link.isDirectory()) walk(pathValue, nextRelative);
|
|
491
|
+
else if (link.isFile()) {
|
|
492
|
+
entries.push({
|
|
493
|
+
path: nextRelative,
|
|
494
|
+
sha256: sha256(readFileSync(pathValue)),
|
|
495
|
+
});
|
|
496
|
+
} else throw new Error("live skills entry rejected");
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
if (existsSync(skillsRoot)) walk(skillsRoot);
|
|
500
|
+
const configPath = join(profileRoot, "config.yaml");
|
|
501
|
+
let disabled = {};
|
|
502
|
+
if (existsSync(configPath)) {
|
|
503
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
504
|
+
const skills = config?.skills;
|
|
505
|
+
if (skills && typeof skills === "object" && !Array.isArray(skills)) {
|
|
506
|
+
disabled = Object.fromEntries(
|
|
507
|
+
LIVE_SKILLS_CONFIG_KEYS.filter((key) =>
|
|
508
|
+
Object.prototype.hasOwnProperty.call(skills, key)
|
|
509
|
+
).map((key) => [key, skills[key]])
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return sha256(stableJson({ disabled, entries }));
|
|
514
|
+
}
|
|
515
|
+
|
|
411
516
|
function mcpFor(desired, profileId) {
|
|
412
517
|
return {
|
|
413
518
|
package: desired.mcpPackage,
|
|
@@ -616,10 +721,8 @@ function validateRuntimeOwnership(observed, runtimeIdentity) {
|
|
|
616
721
|
"skills/sellable",
|
|
617
722
|
"skills/sellable/SKILL.md",
|
|
618
723
|
].sort();
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
stableJson(expectedPaths)
|
|
622
|
-
) {
|
|
724
|
+
const observedPaths = observed.entries.map((entry) => entry.path);
|
|
725
|
+
if (expectedPaths.some((pathValue) => !observedPaths.includes(pathValue))) {
|
|
623
726
|
return false;
|
|
624
727
|
}
|
|
625
728
|
return observed.entries.every(
|
|
@@ -644,9 +747,16 @@ export function validateAgentProfileBundle({ profileRoot, desired }) {
|
|
|
644
747
|
return { ok: false, code: "profile_path_invalid" };
|
|
645
748
|
}
|
|
646
749
|
const foundInventory = inventory(profileRoot);
|
|
750
|
+
const serverOwned = [...AGENT_PROFILE_INVENTORY].sort();
|
|
647
751
|
if (
|
|
648
|
-
|
|
649
|
-
|
|
752
|
+
serverOwned.some(
|
|
753
|
+
(logicalPath) => !foundInventory.includes(logicalPath)
|
|
754
|
+
) ||
|
|
755
|
+
foundInventory.some(
|
|
756
|
+
(logicalPath) =>
|
|
757
|
+
!serverOwned.includes(logicalPath) &&
|
|
758
|
+
!logicalPath.startsWith("skills/")
|
|
759
|
+
)
|
|
650
760
|
) {
|
|
651
761
|
return { ok: false, code: "profile_inventory_open" };
|
|
652
762
|
}
|
|
@@ -667,7 +777,11 @@ export function validateAgentProfileBundle({ profileRoot, desired }) {
|
|
|
667
777
|
.sort()
|
|
668
778
|
.map((logicalPath) => [
|
|
669
779
|
logicalPath,
|
|
670
|
-
sha256(
|
|
780
|
+
sha256(
|
|
781
|
+
logicalPath === "config.yaml"
|
|
782
|
+
? normalizedProfileConfigBytes(join(profileRoot, logicalPath))
|
|
783
|
+
: readFileSync(join(profileRoot, logicalPath))
|
|
784
|
+
),
|
|
671
785
|
])
|
|
672
786
|
);
|
|
673
787
|
if (stableJson(manifest.fileHashes) !== stableJson(expectedHashes)) {
|
|
@@ -686,7 +800,8 @@ export function validateAgentProfileBundle({ profileRoot, desired }) {
|
|
|
686
800
|
readFileSync(join(profileRoot, "sellable", "mcp.json"), "utf8")
|
|
687
801
|
);
|
|
688
802
|
if (
|
|
689
|
-
stableJson(config) !==
|
|
803
|
+
stableJson(profileConfigWithoutLiveSkills(config)) !==
|
|
804
|
+
stableJson(configFor(desired, manifest.profileId))
|
|
690
805
|
) {
|
|
691
806
|
return { ok: false, code: "config_drift" };
|
|
692
807
|
}
|
|
@@ -1625,7 +1740,7 @@ async function rollback({
|
|
|
1625
1740
|
return outcome;
|
|
1626
1741
|
}
|
|
1627
1742
|
|
|
1628
|
-
|
|
1743
|
+
async function materializeAgentProfileLocked(input = {}) {
|
|
1629
1744
|
const valid = validateDesired(input.desired);
|
|
1630
1745
|
if (!valid.ok)
|
|
1631
1746
|
return { ok: false, status: "DESIRED_REJECTED", code: valid.code };
|
|
@@ -1688,7 +1803,15 @@ export async function materializeAgentProfile(input = {}) {
|
|
|
1688
1803
|
revisionId: desired.revisionId,
|
|
1689
1804
|
fence: desired.fence,
|
|
1690
1805
|
});
|
|
1806
|
+
const liveSkillsIdentity = preservedSkillsConfigIdentity(profileRoot);
|
|
1691
1807
|
renderStagedProfile(stagingRoot, desired);
|
|
1808
|
+
mergeLiveSkillsState(profileRoot, stagingRoot);
|
|
1809
|
+
if (
|
|
1810
|
+
liveSkillsIdentity !== null &&
|
|
1811
|
+
preservedSkillsConfigIdentity(stagingRoot) !== liveSkillsIdentity
|
|
1812
|
+
) {
|
|
1813
|
+
throw new Error("live skills identity changed during staging");
|
|
1814
|
+
}
|
|
1692
1815
|
const staged = validateAgentProfileBundle({
|
|
1693
1816
|
profileRoot: stagingRoot,
|
|
1694
1817
|
desired,
|
|
@@ -2016,6 +2139,26 @@ export async function materializeAgentProfile(input = {}) {
|
|
|
2016
2139
|
}
|
|
2017
2140
|
}
|
|
2018
2141
|
|
|
2142
|
+
export async function materializeAgentProfile(input = {}) {
|
|
2143
|
+
const valid = validateDesired(input.desired);
|
|
2144
|
+
const root = traversableProfilesRoot(input.profilesRoot);
|
|
2145
|
+
if (!valid.ok || !root) return materializeAgentProfileLocked(input);
|
|
2146
|
+
const profileId = deriveContainedProfileId(
|
|
2147
|
+
input.desired.workspaceId,
|
|
2148
|
+
input.desired.agentId,
|
|
2149
|
+
input.desired.hostId
|
|
2150
|
+
);
|
|
2151
|
+
try {
|
|
2152
|
+
return await withProfileSoulLock(
|
|
2153
|
+
profileId,
|
|
2154
|
+
() => materializeAgentProfileLocked(input),
|
|
2155
|
+
{ dataRoot: basename(root) === "profiles" ? dirname(root) : root }
|
|
2156
|
+
);
|
|
2157
|
+
} catch {
|
|
2158
|
+
return { ok: false, status: "LOCK_REJECTED", profileId };
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2019
2162
|
export const NATIVE_SLACK_CUSTOMER_PROFILE_INVENTORY = Object.freeze([
|
|
2020
2163
|
"agent-profile.json",
|
|
2021
2164
|
"config.yaml",
|
|
@@ -2326,7 +2469,11 @@ export function validateNativeSlackCustomerProfile({ desired, profileRoot }) {
|
|
|
2326
2469
|
link.isSymbolicLink() ||
|
|
2327
2470
|
!link.isFile() ||
|
|
2328
2471
|
(link.mode & 0o022) !== 0 ||
|
|
2329
|
-
!
|
|
2472
|
+
!(logicalPath === "config.yaml"
|
|
2473
|
+
? normalizedProfileConfigBytes(pathValue).equals(
|
|
2474
|
+
Buffer.from(expected)
|
|
2475
|
+
)
|
|
2476
|
+
: readFileSync(pathValue).equals(Buffer.from(expected)))
|
|
2330
2477
|
) {
|
|
2331
2478
|
return { ok: false, code: "native_slack_profile_file_drift" };
|
|
2332
2479
|
}
|
|
@@ -2393,6 +2540,7 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2393
2540
|
return { ok: false, code: "native_slack_profile_path_rejected" };
|
|
2394
2541
|
}
|
|
2395
2542
|
let soulBytes = Buffer.from(desired.soul, "utf8");
|
|
2543
|
+
const liveSkillsIdentity = preservedSkillsConfigIdentity(activeRoot);
|
|
2396
2544
|
if (existsSync(activeRoot)) {
|
|
2397
2545
|
const soulPath = join(activeRoot, "SOUL.md");
|
|
2398
2546
|
if (existsSync(soulPath)) {
|
|
@@ -2409,6 +2557,13 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2409
2557
|
soulBytes
|
|
2410
2558
|
);
|
|
2411
2559
|
mergeHermesState(activeRoot, stagingRoot);
|
|
2560
|
+
mergeLiveSkillsConfig(activeRoot, stagingRoot);
|
|
2561
|
+
if (
|
|
2562
|
+
liveSkillsIdentity !== null &&
|
|
2563
|
+
preservedSkillsConfigIdentity(stagingRoot) !== liveSkillsIdentity
|
|
2564
|
+
) {
|
|
2565
|
+
throw new Error("live skills identity changed during staging");
|
|
2566
|
+
}
|
|
2412
2567
|
const staged = validateNativeSlackCustomerProfile({
|
|
2413
2568
|
desired,
|
|
2414
2569
|
profileRoot: stagingRoot,
|
|
@@ -25,10 +25,8 @@ import { fileURLToPath } from "node:url";
|
|
|
25
25
|
import { deriveContainedProfileId } from "./profile-materializer.mjs";
|
|
26
26
|
|
|
27
27
|
export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
|
|
28
|
-
export const PINNED_INSTALL_PACKAGE =
|
|
29
|
-
|
|
30
|
-
export const PINNED_MCP_PACKAGE =
|
|
31
|
-
"@sellable/mcp@0.1.858";
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.581";
|
|
29
|
+
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.859";
|
|
32
30
|
|
|
33
31
|
export function deriveAgentProfileId(workspaceId, agentId, hostId) {
|
|
34
32
|
return deriveContainedProfileId(workspaceId, agentId, hostId);
|
|
@@ -613,8 +611,7 @@ function inspectProvisionedProfile({
|
|
|
613
611
|
!matchesReadback(manifest, request) ||
|
|
614
612
|
manifest.serviceCredentialReference !== serviceCredentialReference ||
|
|
615
613
|
manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
|
|
616
|
-
manifest.installerVersion !==
|
|
617
|
-
"0.1.577" ||
|
|
614
|
+
manifest.installerVersion !== "0.1.581" ||
|
|
618
615
|
manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
|
|
619
616
|
manifest.credentialKeyVersion !== credentialKeyVersion ||
|
|
620
617
|
!/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
|
|
@@ -828,7 +825,7 @@ function successReceipt({
|
|
|
828
825
|
subject: `agent-profile:${observed.profileId}`,
|
|
829
826
|
cli: {
|
|
830
827
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
831
|
-
installVersion: "0.1.
|
|
828
|
+
installVersion: "0.1.581",
|
|
832
829
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
833
830
|
command: "hermes profile bootstrap",
|
|
834
831
|
},
|
|
@@ -876,7 +873,7 @@ function failure(code, stages, extra = {}, request = null) {
|
|
|
876
873
|
: "agent-profile:unbound",
|
|
877
874
|
cli: {
|
|
878
875
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
879
|
-
installVersion: "0.1.
|
|
876
|
+
installVersion: "0.1.581",
|
|
880
877
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
881
878
|
command: "hermes profile bootstrap",
|
|
882
879
|
},
|