@sellable/install 0.1.578-wip.phase170.20260809.2 → 0.1.579
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 +4 -4
- package/container/README.md +2 -2
- package/lib/sellable-agent/fly-admin-image/Dockerfile +0 -13
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +16 -82
- package/lib/sellable-agent/fly-customer-image/Dockerfile +2 -11
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +29 -4
- package/lib/sellable-agent/hermes-bridge.mjs +34 -145
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +5 -12
- package/lib/sellable-agent/profile-materializer.mjs +14 -169
- package/lib/sellable-agent/provisioning-adapter.mjs +4 -4
- package/package.json +1 -1
- package/skill-templates/refill-sends.md +8 -0
- package/lib/sellable-agent/fly-skills-bridge-exec.mjs +0 -45
- package/lib/sellable-agent/fly-skills-bridge.mjs +0 -955
package/container/Dockerfile
CHANGED
|
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.577
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.858
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
9
|
ARG MCP_INTEGRITY=sha512-8yiUhwMbBk/KxZAXGXTsEHiN8XeB/qCqNgfApew0rhViJqiLlDN8oS67pZW5iB5gdmeuv4du0jx9Lhq/blr9hQ==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.577" \
|
|
12
12
|
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.858" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.578-wip.phase170.202608
|
|
|
23
23
|
&& mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
|
|
24
24
|
&& npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
|
|
25
25
|
&& npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
|
|
26
|
-
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.
|
|
26
|
+
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.577' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.858') throw new Error('package identity rejected');" \
|
|
27
27
|
&& node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
|
|
28
|
-
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.
|
|
28
|
+
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.577', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.858', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
|
|
29
29
|
&& npm cache clean --force
|
|
30
30
|
|
|
31
31
|
COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
|
package/container/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
|
|
4
4
|
|
|
5
5
|
Build from this directory after
|
|
6
|
-
`@sellable/install@0.1.
|
|
6
|
+
`@sellable/install@0.1.577` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
docker build --platform linux/amd64 \
|
|
11
11
|
--build-arg INSTALLER_INTEGRITY='sha512-…' \
|
|
12
|
-
--tag sellable-agent-host:0.1.
|
|
12
|
+
--tag sellable-agent-host:0.1.577 .
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
|
|
@@ -86,8 +86,6 @@ COPY hermes-bridge.mjs /usr/local/lib/sellable-agent/hermes-bridge.mjs
|
|
|
86
86
|
COPY hermes-memory-snapshot.mjs /usr/local/lib/sellable-agent/hermes-memory-snapshot.mjs
|
|
87
87
|
COPY fly-soul-bridge.mjs /usr/local/lib/sellable-agent/fly-soul-bridge.mjs
|
|
88
88
|
COPY fly-soul-bridge-exec.mjs /usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs
|
|
89
|
-
COPY fly-skills-bridge.mjs /usr/local/lib/sellable-agent/fly-skills-bridge.mjs
|
|
90
|
-
COPY fly-skills-bridge-exec.mjs /usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs
|
|
91
89
|
COPY soul-artifact-validator.mjs /usr/local/lib/sellable-agent/soul-artifact-validator.mjs
|
|
92
90
|
COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
|
|
93
91
|
COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
|
|
@@ -109,18 +107,9 @@ RUN node --input-type=module -e \
|
|
|
109
107
|
'import("file:///usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs").then(({ runFlySoulBridgeExecCli }) => runFlySoulBridgeExecCli());' \
|
|
110
108
|
> /usr/local/bin/sellable-soul-bridge-exec \
|
|
111
109
|
&& chmod 0555 /usr/local/bin/sellable-soul-bridge-exec \
|
|
112
|
-
&& printf '%s\n' \
|
|
113
|
-
'#!/usr/bin/env node' \
|
|
114
|
-
'import("file:///usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs").then(({ runFlySkillsBridgeExecCli }) => runFlySkillsBridgeExecCli());' \
|
|
115
|
-
> /usr/local/bin/sellable-skills-bridge-exec \
|
|
116
|
-
&& chmod 0555 /usr/local/bin/sellable-skills-bridge-exec \
|
|
117
110
|
&& sha256sum /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
|
|
118
111
|
> /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
|
|
119
|
-
&& sha256sum /usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
|
|
120
|
-
/usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
|
|
121
|
-
> /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
|
|
122
112
|
&& chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
|
|
123
|
-
/usr/local/share/sellable-agent/skills-bridge-source.sha256 \
|
|
124
113
|
&& chmod 0755 /etc \
|
|
125
114
|
&& chmod 0555 \
|
|
126
115
|
/usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs \
|
|
@@ -133,8 +122,6 @@ RUN node --input-type=module -e \
|
|
|
133
122
|
/usr/local/lib/sellable-agent/hermes-memory-snapshot.mjs \
|
|
134
123
|
/usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
|
|
135
124
|
/usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs \
|
|
136
|
-
/usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
|
|
137
|
-
/usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
|
|
138
125
|
/usr/local/lib/sellable-agent/soul-artifact-validator.mjs \
|
|
139
126
|
/opt/sellable/bin/hermes-memory-dirty \
|
|
140
127
|
/opt/sellable/bin/hermes-endpoint-cron \
|
|
@@ -606,6 +606,21 @@ function installAdminModelConfig(profileRoot, modelConfig) {
|
|
|
606
606
|
modelConfig.credential_pool_strategies
|
|
607
607
|
);
|
|
608
608
|
document.set("fallback_providers", modelConfig.fallback_providers);
|
|
609
|
+
if (document.get("provider_routing") === undefined) {
|
|
610
|
+
// OpenRouter provider selection defaults to highest throughput; an
|
|
611
|
+
// explicit admin config value wins.
|
|
612
|
+
document.set("provider_routing", { sort: "throughput" });
|
|
613
|
+
}
|
|
614
|
+
const currentAuxiliary = document.toJS().auxiliary ?? {};
|
|
615
|
+
if (!currentAuxiliary.vision) {
|
|
616
|
+
// Non-vision main models need a working vision auxiliary; the Nous
|
|
617
|
+
// portal has no credentials on these machines, so image input would
|
|
618
|
+
// silently fail without this default.
|
|
619
|
+
document.set("auxiliary", {
|
|
620
|
+
...currentAuxiliary,
|
|
621
|
+
vision: { provider: "openrouter", model: "google/gemini-3.5-flash" },
|
|
622
|
+
});
|
|
623
|
+
}
|
|
609
624
|
atomicPrivateFile(path, document.toString());
|
|
610
625
|
const readback = parseDocument(readFileSync(path, "utf8")).toJS();
|
|
611
626
|
if (
|
|
@@ -622,78 +637,6 @@ function installAdminModelConfig(profileRoot, modelConfig) {
|
|
|
622
637
|
}
|
|
623
638
|
}
|
|
624
639
|
|
|
625
|
-
const ADMIN_LIVE_SKILLS_CONFIG_KEYS = Object.freeze([
|
|
626
|
-
"disabled",
|
|
627
|
-
"platform_disabled",
|
|
628
|
-
]);
|
|
629
|
-
|
|
630
|
-
function adminPreservedSkillsIdentity(profileRoot) {
|
|
631
|
-
if (!existsSync(profileRoot)) return null;
|
|
632
|
-
const entries = [];
|
|
633
|
-
const skillsRoot = join(profileRoot, "skills");
|
|
634
|
-
const walk = (directory, relativePath = "") => {
|
|
635
|
-
for (const name of readdirSync(directory).sort()) {
|
|
636
|
-
const nextRelative = relativePath ? `${relativePath}/${name}` : name;
|
|
637
|
-
if (nextRelative === "sellable") continue;
|
|
638
|
-
const path = join(directory, name);
|
|
639
|
-
const stat = lstatSync(path);
|
|
640
|
-
if (stat.isSymbolicLink()) {
|
|
641
|
-
throw new Error("admin_runtime_live_skills_symlink_rejected");
|
|
642
|
-
}
|
|
643
|
-
if (stat.isDirectory()) walk(path, nextRelative);
|
|
644
|
-
else if (stat.isFile()) {
|
|
645
|
-
entries.push({
|
|
646
|
-
path: nextRelative,
|
|
647
|
-
sha256: sha256(readFileSync(path)),
|
|
648
|
-
});
|
|
649
|
-
} else throw new Error("admin_runtime_live_skills_entry_rejected");
|
|
650
|
-
}
|
|
651
|
-
};
|
|
652
|
-
if (existsSync(skillsRoot)) walk(skillsRoot);
|
|
653
|
-
const configPath = join(profileRoot, "config.yaml");
|
|
654
|
-
let disabled = {};
|
|
655
|
-
if (existsSync(configPath)) {
|
|
656
|
-
const document = parseDocument(readFileSync(configPath, "utf8"));
|
|
657
|
-
if (document.errors.length > 0) {
|
|
658
|
-
throw new Error("admin_runtime_live_skills_config_rejected");
|
|
659
|
-
}
|
|
660
|
-
const skills = document.toJS()?.skills;
|
|
661
|
-
if (skills && typeof skills === "object" && !Array.isArray(skills)) {
|
|
662
|
-
disabled = Object.fromEntries(
|
|
663
|
-
ADMIN_LIVE_SKILLS_CONFIG_KEYS.filter((key) =>
|
|
664
|
-
Object.prototype.hasOwnProperty.call(skills, key)
|
|
665
|
-
).map((key) => [key, skills[key]])
|
|
666
|
-
);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
return sha256(canonicalJson({ disabled, entries }));
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
function mergeAdminLiveSkillsConfig(sourceRoot, targetRoot) {
|
|
673
|
-
const sourcePath = join(sourceRoot, "config.yaml");
|
|
674
|
-
const targetPath = join(targetRoot, "config.yaml");
|
|
675
|
-
if (!existsSync(sourcePath) || !existsSync(targetPath)) return;
|
|
676
|
-
const sourceDocument = parseDocument(readFileSync(sourcePath, "utf8"));
|
|
677
|
-
const targetDocument = parseDocument(readFileSync(targetPath, "utf8"));
|
|
678
|
-
if (sourceDocument.errors.length > 0 || targetDocument.errors.length > 0) {
|
|
679
|
-
throw new Error("admin_runtime_live_skills_config_rejected");
|
|
680
|
-
}
|
|
681
|
-
const sourceSkills = sourceDocument.toJS()?.skills;
|
|
682
|
-
if (
|
|
683
|
-
!sourceSkills ||
|
|
684
|
-
typeof sourceSkills !== "object" ||
|
|
685
|
-
Array.isArray(sourceSkills)
|
|
686
|
-
) {
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
for (const key of ADMIN_LIVE_SKILLS_CONFIG_KEYS) {
|
|
690
|
-
if (Object.prototype.hasOwnProperty.call(sourceSkills, key)) {
|
|
691
|
-
targetDocument.setIn(["skills", key], structuredClone(sourceSkills[key]));
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
atomicPrivateFile(targetPath, targetDocument.toString());
|
|
695
|
-
}
|
|
696
|
-
|
|
697
640
|
async function materializeAdminProfile({
|
|
698
641
|
profileContract,
|
|
699
642
|
settingsRoot,
|
|
@@ -799,6 +742,7 @@ async function materializeAdminProfile({
|
|
|
799
742
|
},
|
|
800
743
|
});
|
|
801
744
|
installAdminModelConfig(stageRoot, modelConfig);
|
|
745
|
+
mergeHermesState(profileHome, stageRoot);
|
|
802
746
|
installAdminHermesMemoryHook(stageRoot);
|
|
803
747
|
stageAdminRuntimeTree(stageRoot);
|
|
804
748
|
return stage;
|
|
@@ -822,16 +766,6 @@ async function materializeAdminProfile({
|
|
|
822
766
|
rmSync(stageRoot, { recursive: true, force: true });
|
|
823
767
|
return { status: "RESTAGE", stagedAuthority: finalAuthority };
|
|
824
768
|
}
|
|
825
|
-
const liveSkillsIdentity = adminPreservedSkillsIdentity(profileHome);
|
|
826
|
-
mergeHermesState(profileHome, stageRoot);
|
|
827
|
-
mergeAdminLiveSkillsConfig(profileHome, stageRoot);
|
|
828
|
-
if (
|
|
829
|
-
liveSkillsIdentity !== null &&
|
|
830
|
-
adminPreservedSkillsIdentity(stageRoot) !== liveSkillsIdentity
|
|
831
|
-
) {
|
|
832
|
-
rmSync(stageRoot, { recursive: true, force: true });
|
|
833
|
-
return { status: "RESTAGE", stagedAuthority: finalAuthority };
|
|
834
|
-
}
|
|
835
769
|
const stagedSoulPath = join(stageRoot, "SOUL.md");
|
|
836
770
|
const stagedSoul = lstatSync(stagedSoulPath);
|
|
837
771
|
const stagedSoulBytes = readFileSync(stagedSoulPath);
|
|
@@ -6,7 +6,7 @@ USER root
|
|
|
6
6
|
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.858
|
|
8
8
|
ARG MCP_INTEGRITY=sha512-8yiUhwMbBk/KxZAXGXTsEHiN8XeB/qCqNgfApew0rhViJqiLlDN8oS67pZW5iB5gdmeuv4du0jx9Lhq/blr9hQ==
|
|
9
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
9
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.577
|
|
10
10
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
11
11
|
ARG HERMES_VERSION=0.20.0
|
|
12
12
|
ARG TIRITH_VERSION=0.3.3
|
|
@@ -59,7 +59,7 @@ RUN --mount=type=secret,id=mcp_trust_root,required=true \
|
|
|
59
59
|
/etc/cont-init.d/02-reconcile-profiles \
|
|
60
60
|
&& npm cache clean --force
|
|
61
61
|
|
|
62
|
-
COPY mcp-context-proxy.mjs mcp-runtime-binding.mjs fly-customer-model.mjs fly-customer-worker.mjs profile-materializer.mjs hermes-bridge.mjs hermes-memory-snapshot.mjs fly-runtime-identity.mjs fly-soul-bridge.mjs fly-soul-bridge-exec.mjs
|
|
62
|
+
COPY mcp-context-proxy.mjs mcp-runtime-binding.mjs fly-customer-model.mjs fly-customer-worker.mjs profile-materializer.mjs hermes-bridge.mjs hermes-memory-snapshot.mjs fly-runtime-identity.mjs fly-soul-bridge.mjs fly-soul-bridge-exec.mjs soul-artifact-validator.mjs model-auth-reconciler.mjs model-auth-canary-reseed.mjs model-health-reporter.mjs \
|
|
63
63
|
/usr/local/lib/sellable-agent/
|
|
64
64
|
COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
|
|
65
65
|
COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
|
|
@@ -86,18 +86,9 @@ RUN node --input-type=module -e \
|
|
|
86
86
|
'import("file:///usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs").then(({ runFlySoulBridgeExecCli }) => runFlySoulBridgeExecCli());' \
|
|
87
87
|
> /usr/local/bin/sellable-soul-bridge-exec \
|
|
88
88
|
&& chmod 0555 /usr/local/bin/sellable-soul-bridge-exec \
|
|
89
|
-
&& printf '%s\n' \
|
|
90
|
-
'#!/usr/bin/env node' \
|
|
91
|
-
'import("file:///usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs").then(({ runFlySkillsBridgeExecCli }) => runFlySkillsBridgeExecCli());' \
|
|
92
|
-
> /usr/local/bin/sellable-skills-bridge-exec \
|
|
93
|
-
&& chmod 0555 /usr/local/bin/sellable-skills-bridge-exec \
|
|
94
89
|
&& sha256sum /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
|
|
95
90
|
> /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
|
|
96
|
-
&& sha256sum /usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
|
|
97
|
-
/usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
|
|
98
|
-
> /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
|
|
99
91
|
&& chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
|
|
100
|
-
/usr/local/share/sellable-agent/skills-bridge-source.sha256 \
|
|
101
92
|
&& chmod 0755 /etc \
|
|
102
93
|
&& chmod 0555 /usr/local/lib/sellable-agent/*.mjs \
|
|
103
94
|
&& chmod 0555 /opt/sellable/bin/hermes-memory-dirty /opt/sellable/bin/hermes-endpoint-cron \
|
|
@@ -93,11 +93,9 @@ const RUNTIME_GENERATION = Number(
|
|
|
93
93
|
const BOOT_SESSION_ID = randomUUID();
|
|
94
94
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
|
|
95
95
|
const INSTALLER_PACKAGE =
|
|
96
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
|
|
97
|
-
"@sellable/install@0.1.578-wip.phase170.20260809.2";
|
|
96
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.577";
|
|
98
97
|
const MCP_PACKAGE =
|
|
99
|
-
process.env.SELLABLE_AGENT_MCP_PACKAGE ??
|
|
100
|
-
"@sellable/mcp@0.1.858";
|
|
98
|
+
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.858";
|
|
101
99
|
|
|
102
100
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
103
101
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1639,6 +1637,16 @@ function writeRuntimeHermesConfig({
|
|
|
1639
1637
|
);
|
|
1640
1638
|
const sellable = baseConfig.mcp_servers?.sellable;
|
|
1641
1639
|
if (!sellable?.env) throw new Error("customer_mcp_config_rejected");
|
|
1640
|
+
const adminServer = baseConfig.mcp_servers?.["sellable-admin"];
|
|
1641
|
+
if (adminServer?.command && existsSync(adminServer.command)) {
|
|
1642
|
+
// The admin MCP installer has shipped this wrapper without an execute
|
|
1643
|
+
// bit; repair it here so a config refresh never revives a dead admin
|
|
1644
|
+
// server entry.
|
|
1645
|
+
const wrapperMode = lstatSync(adminServer.command).mode;
|
|
1646
|
+
if ((wrapperMode & 0o100) === 0) {
|
|
1647
|
+
chmodSync(adminServer.command, wrapperMode | 0o100);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1642
1650
|
// The named-profile materializer owns the one canonical SOUL file and seeds
|
|
1643
1651
|
// it only when absent. Runtime config refreshes must never rewrite live SOUL
|
|
1644
1652
|
// or create a root-level second copy.
|
|
@@ -1699,6 +1707,23 @@ function writeRuntimeHermesConfig({
|
|
|
1699
1707
|
: {
|
|
1700
1708
|
...modelConfig,
|
|
1701
1709
|
}),
|
|
1710
|
+
provider_routing: {
|
|
1711
|
+
// OpenRouter provider selection defaults to highest throughput; an
|
|
1712
|
+
// explicit profile value always wins.
|
|
1713
|
+
sort: "throughput",
|
|
1714
|
+
...(baseConfig.provider_routing ?? {}),
|
|
1715
|
+
},
|
|
1716
|
+
auxiliary: {
|
|
1717
|
+
...(baseConfig.auxiliary ?? {}),
|
|
1718
|
+
vision: {
|
|
1719
|
+
// Non-vision main models (e.g. deepseek) need a working vision
|
|
1720
|
+
// auxiliary; the Nous portal has no credentials on customer
|
|
1721
|
+
// machines, so image input silently fails without this default.
|
|
1722
|
+
provider: "openrouter",
|
|
1723
|
+
model: "google/gemini-3.5-flash",
|
|
1724
|
+
...(baseConfig.auxiliary?.vision ?? {}),
|
|
1725
|
+
},
|
|
1726
|
+
},
|
|
1702
1727
|
mcp_servers: {
|
|
1703
1728
|
...baseConfig.mcp_servers,
|
|
1704
1729
|
sellable: {
|
|
@@ -297,6 +297,15 @@ const MCP_META_HELPER = `
|
|
|
297
297
|
def _sellable_agent_call_meta():
|
|
298
298
|
if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
|
|
299
299
|
return None
|
|
300
|
+
from gateway.session_context import get_session_env, session_context_engaged
|
|
301
|
+
if (
|
|
302
|
+
not session_context_engaged()
|
|
303
|
+
or get_session_env("HERMES_SESSION_PLATFORM", "").strip().lower() != "slack"
|
|
304
|
+
):
|
|
305
|
+
# Non-Slack runtime sessions (console/TUI, cron) carry no Slack actor
|
|
306
|
+
# identity. Send no actor meta; the serving API enforces its own actor
|
|
307
|
+
# gates, as with installs outside the Sellable runtime.
|
|
308
|
+
return None
|
|
300
309
|
import json
|
|
301
310
|
gate_path = os.getenv("SELLABLE_AGENT_RUNTIME_GATE_FILE", "")
|
|
302
311
|
if not gate_path.startswith("/"):
|
|
@@ -376,8 +385,18 @@ def _sellable_agent_restore_or_build_proven_soul(
|
|
|
376
385
|
expected_home = data_root / "profiles" / profile_id
|
|
377
386
|
try:
|
|
378
387
|
observed_home = Path(get_hermes_home())
|
|
379
|
-
|
|
380
|
-
|
|
388
|
+
observed_resolved = observed_home.resolve(strict=True)
|
|
389
|
+
expected_resolved = expected_home.resolve(strict=True)
|
|
390
|
+
if observed_resolved != expected_resolved:
|
|
391
|
+
# HERMES_HOME may legitimately be the data root (the directory
|
|
392
|
+
# that CONTAINS profiles/<id>) when no per-session override is
|
|
393
|
+
# active yet -- e.g. brand-new sessions in hermes serve. Accept
|
|
394
|
+
# it only when root/profiles/<profile_id> strictly resolves to
|
|
395
|
+
# the canonical profile directory.
|
|
396
|
+
if (
|
|
397
|
+
observed_home / "profiles" / profile_id
|
|
398
|
+
).resolve(strict=True) != expected_resolved:
|
|
399
|
+
raise RuntimeError("Hermes resolved a different SOUL profile")
|
|
381
400
|
except OSError as exc:
|
|
382
401
|
raise RuntimeError("Hermes SOUL profile cannot be resolved") from exc
|
|
383
402
|
|
|
@@ -574,6 +593,15 @@ const MCP_ENDPOINT_META_HELPER = `
|
|
|
574
593
|
def _sellable_agent_call_meta():
|
|
575
594
|
if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
|
|
576
595
|
return None
|
|
596
|
+
from gateway.session_context import get_session_env, session_context_engaged
|
|
597
|
+
if (
|
|
598
|
+
not session_context_engaged()
|
|
599
|
+
or get_session_env("HERMES_SESSION_PLATFORM", "").strip().lower() != "slack"
|
|
600
|
+
):
|
|
601
|
+
# Non-Slack runtime sessions (console/TUI, cron) carry no Slack actor
|
|
602
|
+
# identity. Send no actor meta; the serving API enforces its own actor
|
|
603
|
+
# gates, as with installs outside the Sellable runtime.
|
|
604
|
+
return None
|
|
577
605
|
import json
|
|
578
606
|
gate_path = os.getenv("SELLABLE_AGENT_RUNTIME_GATE_FILE", "")
|
|
579
607
|
if not gate_path.startswith("/"):
|
|
@@ -634,15 +662,11 @@ const MCP_ENDPOINT_META_HELPER_OPTIONAL_SESSION =
|
|
|
634
662
|
);
|
|
635
663
|
|
|
636
664
|
const CRON_MCP_ISOLATION_HELPER = `
|
|
637
|
-
# ${HERMES_AGENT_BRIDGE_MARKER}: cron
|
|
665
|
+
# ${HERMES_AGENT_BRIDGE_MARKER}: cron sessions may use Sellable MCP toolsets.
|
|
638
666
|
def _sellable_agent_strip_cron_mcp(toolsets, cfg):
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
from hermes_cli.tools_config import enabled_mcp_server_names
|
|
643
|
-
mcp_names = set(enabled_mcp_server_names(cfg or {}))
|
|
644
|
-
mcp_names.add("mcp")
|
|
645
|
-
return [name for name in values if name not in mcp_names]
|
|
667
|
+
# Pass-through, kept for injection-site stability: cron sessions carry no
|
|
668
|
+
# Slack actor meta and the serving API enforces its own actor gates.
|
|
669
|
+
return list(toolsets or [])
|
|
646
670
|
`;
|
|
647
671
|
|
|
648
672
|
const V020_WEB_TOOLSET_DISCOVERY_HELPER = `
|
|
@@ -1569,125 +1593,6 @@ export function patchHermesSourceFile(patchKind, source) {
|
|
|
1569
1593
|
' return _sellable_agent_strip_cron_mcp(\n sorted(_get_platform_tools(cfg or {}, "cron")), cfg or {}\n )',
|
|
1570
1594
|
"v0.20 platform cron MCP isolation"
|
|
1571
1595
|
);
|
|
1572
|
-
patched = replaceExactlyOnce(
|
|
1573
|
-
patched,
|
|
1574
|
-
" return None\n\n# Valid delivery platforms",
|
|
1575
|
-
[
|
|
1576
|
-
' if os.getenv("SELLABLE_AGENT_RUNTIME", "") == "1":',
|
|
1577
|
-
' return _sellable_agent_strip_cron_mcp(["safe"], cfg or {})',
|
|
1578
|
-
" return None",
|
|
1579
|
-
"",
|
|
1580
|
-
"# Valid delivery platforms",
|
|
1581
|
-
].join("\n"),
|
|
1582
|
-
"v0.20 cron MCP fail-closed fallback"
|
|
1583
|
-
);
|
|
1584
|
-
return patched;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
if (patchKind === "skills-cache-freshness-v020") {
|
|
1588
|
-
let patched = replaceExactlyOnce(
|
|
1589
|
-
source,
|
|
1590
|
-
" sig.append((str(d), m))",
|
|
1591
|
-
[
|
|
1592
|
-
" skill_files = []",
|
|
1593
|
-
" for root, dirs, files in os.walk(d, followlinks=False):",
|
|
1594
|
-
" dirs[:] = sorted(",
|
|
1595
|
-
" name for name in dirs",
|
|
1596
|
-
" if not os.path.islink(os.path.join(root, name))",
|
|
1597
|
-
" )",
|
|
1598
|
-
' if "SKILL.md" not in files:',
|
|
1599
|
-
" continue",
|
|
1600
|
-
' skill_path = os.path.join(root, "SKILL.md")',
|
|
1601
|
-
" try:",
|
|
1602
|
-
" skill_stat = os.stat(skill_path, follow_symlinks=False)",
|
|
1603
|
-
" skill_files.append(",
|
|
1604
|
-
" (skill_path, skill_stat.st_mtime_ns, skill_stat.st_size)",
|
|
1605
|
-
" )",
|
|
1606
|
-
" except OSError:",
|
|
1607
|
-
" continue",
|
|
1608
|
-
" sig.append((str(d), m, tuple(skill_files)))",
|
|
1609
|
-
].join("\n"),
|
|
1610
|
-
"v0.20 live SKILL.md signature"
|
|
1611
|
-
);
|
|
1612
|
-
patched = replaceExactlyOnce(
|
|
1613
|
-
patched,
|
|
1614
|
-
" disabled = set() if skip_disabled else _get_disabled_skill_names()",
|
|
1615
|
-
[
|
|
1616
|
-
" disabled = _get_disabled_skill_names()",
|
|
1617
|
-
" filtered_disabled = set() if skip_disabled else disabled",
|
|
1618
|
-
].join("\n"),
|
|
1619
|
-
"v0.20 shared disabled signature"
|
|
1620
|
-
);
|
|
1621
|
-
patched = replaceExactlyOnce(
|
|
1622
|
-
patched,
|
|
1623
|
-
" if name in disabled:\n continue",
|
|
1624
|
-
" if name in filtered_disabled:\n continue",
|
|
1625
|
-
"v0.20 filtered disabled behavior"
|
|
1626
|
-
);
|
|
1627
|
-
patched = replaceExactlyOnce(
|
|
1628
|
-
patched,
|
|
1629
|
-
" skills = []\n seen_names: set = set()",
|
|
1630
|
-
[
|
|
1631
|
-
" # A signature change invalidates both filtered and unfiltered",
|
|
1632
|
-
" # variants together; neither waits for the 30-second TTL.",
|
|
1633
|
-
" if cached is not None and cached[0] != signature:",
|
|
1634
|
-
" _SKILLS_CACHE.clear()",
|
|
1635
|
-
"",
|
|
1636
|
-
" skills = []",
|
|
1637
|
-
" seen_names: set = set()",
|
|
1638
|
-
].join("\n"),
|
|
1639
|
-
"v0.20 both-variant cache invalidation"
|
|
1640
|
-
);
|
|
1641
|
-
return patched;
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
if (patchKind === "skills-prompt-freshness-v020") {
|
|
1645
|
-
let patched = replaceExactlyOnce(
|
|
1646
|
-
source,
|
|
1647
|
-
"_SKILLS_PROMPT_CACHE_LOCK = threading.Lock()",
|
|
1648
|
-
[
|
|
1649
|
-
"_SKILLS_PROMPT_CACHE_LOCK = threading.Lock()",
|
|
1650
|
-
"_SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE = None",
|
|
1651
|
-
].join("\n"),
|
|
1652
|
-
"v0.20 prompt signature state"
|
|
1653
|
-
);
|
|
1654
|
-
patched = replaceExactlyOnce(
|
|
1655
|
-
patched,
|
|
1656
|
-
" disabled = get_disabled_skill_names(_platform_hint or None)\n cache_key = (",
|
|
1657
|
-
[
|
|
1658
|
-
" disabled = get_disabled_skill_names(_platform_hint or None)",
|
|
1659
|
-
" signature_roots = [skills_dir, *external_dirs]",
|
|
1660
|
-
" live_signature = tuple(",
|
|
1661
|
-
" (",
|
|
1662
|
-
" str(root),",
|
|
1663
|
-
" tuple(",
|
|
1664
|
-
" (path, tuple(values))",
|
|
1665
|
-
" for path, values in sorted(_build_skills_manifest(root).items())",
|
|
1666
|
-
" ),",
|
|
1667
|
-
" )",
|
|
1668
|
-
" for root in signature_roots",
|
|
1669
|
-
" if root.exists()",
|
|
1670
|
-
" )",
|
|
1671
|
-
" cache_key = (",
|
|
1672
|
-
].join("\n"),
|
|
1673
|
-
"v0.20 prompt live signature"
|
|
1674
|
-
);
|
|
1675
|
-
patched = replaceExactlyOnce(
|
|
1676
|
-
patched,
|
|
1677
|
-
" tuple(sorted(compact_categories or ())),\n )\n with _SKILLS_PROMPT_CACHE_LOCK:\n cached = _SKILLS_PROMPT_CACHE.get(cache_key)",
|
|
1678
|
-
[
|
|
1679
|
-
" tuple(sorted(compact_categories or ())),",
|
|
1680
|
-
" live_signature,",
|
|
1681
|
-
" )",
|
|
1682
|
-
" global _SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE",
|
|
1683
|
-
" with _SKILLS_PROMPT_CACHE_LOCK:",
|
|
1684
|
-
" if _SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE != live_signature:",
|
|
1685
|
-
" _SKILLS_PROMPT_CACHE.clear()",
|
|
1686
|
-
" _SELLABLE_AGENT_SKILLS_PROMPT_SIGNATURE = live_signature",
|
|
1687
|
-
" cached = _SKILLS_PROMPT_CACHE.get(cache_key)",
|
|
1688
|
-
].join("\n"),
|
|
1689
|
-
"v0.20 prompt cache invalidation"
|
|
1690
|
-
);
|
|
1691
1596
|
return patched;
|
|
1692
1597
|
}
|
|
1693
1598
|
|
|
@@ -1949,22 +1854,6 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
1949
1854
|
patchedSha256:
|
|
1950
1855
|
"90c98514a8c6c4078dacdd67a92cf749bd174ec2c074226163c6cb07ca3df59f",
|
|
1951
1856
|
},
|
|
1952
|
-
{
|
|
1953
|
-
path: "tools/skills_tool.py",
|
|
1954
|
-
patchKind: "skills-cache-freshness-v020",
|
|
1955
|
-
sourceSha256:
|
|
1956
|
-
"6cfe456c8872c91309083694290e5e2cbdb64a1b2f4ac9c2cb38973fab2228ab",
|
|
1957
|
-
patchedSha256:
|
|
1958
|
-
"9a1b1417879d9aac6d5b6e1713237193009e2d67dbc691db35ef5940e94f9397",
|
|
1959
|
-
},
|
|
1960
|
-
{
|
|
1961
|
-
path: "agent/prompt_builder.py",
|
|
1962
|
-
patchKind: "skills-prompt-freshness-v020",
|
|
1963
|
-
sourceSha256:
|
|
1964
|
-
"2a8a8b2b6c0ff830349f68b5c2f26bf2c9871ae03caf516cff3628c00b5769c7",
|
|
1965
|
-
patchedSha256:
|
|
1966
|
-
"4a55eb6dd4e79d35fca9fd02544444576c892ca02dfebb0c97954156cc470fdb",
|
|
1967
|
-
},
|
|
1968
1857
|
],
|
|
1969
1858
|
});
|
|
1970
1859
|
|
|
@@ -39,7 +39,7 @@ 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.
|
|
42
|
+
"@sellable/install@0.1.577";
|
|
43
43
|
const MCP_PACKAGE = "@sellable/mcp@0.1.858";
|
|
44
44
|
const HERMES_VERSION = "0.18.0";
|
|
45
45
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
relative,
|
|
30
30
|
resolve,
|
|
31
31
|
} from "node:path";
|
|
32
|
-
import { readSkillsProofObservation } from "./fly-skills-bridge.mjs";
|
|
33
32
|
import {
|
|
34
33
|
commitRetainedProfilePromotion,
|
|
35
34
|
computeAgentProfileDigest,
|
|
@@ -78,11 +77,6 @@ const SECRET_PATTERN =
|
|
|
78
77
|
/sat_[A-Za-z0-9_-]+|xox[bap]-[A-Za-z0-9-]+|asec\.v1\.|authorization[_-]?code|access[_-]?token|refresh[_-]?token/i;
|
|
79
78
|
const STATE_FILE = "worker-state.json";
|
|
80
79
|
|
|
81
|
-
export function attachSkillsProofObservation(observed, profileRoot) {
|
|
82
|
-
const skills = readSkillsProofObservation(profileRoot);
|
|
83
|
-
return skills ? { ...observed, skills } : observed;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
80
|
function stableJson(value) {
|
|
87
81
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
88
82
|
if (value && typeof value === "object") {
|
|
@@ -1546,8 +1540,10 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1546
1540
|
]) ||
|
|
1547
1541
|
pinned.hermesCli !== "hermes" ||
|
|
1548
1542
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1549
|
-
pinned.installerPackage !==
|
|
1550
|
-
|
|
1543
|
+
pinned.installerPackage !==
|
|
1544
|
+
"@sellable/install@0.1.577" ||
|
|
1545
|
+
pinned.mcpPackage !==
|
|
1546
|
+
"@sellable/mcp@0.1.858" ||
|
|
1551
1547
|
!Array.isArray(policy.toolInclude) ||
|
|
1552
1548
|
policy.toolInclude.length === 0 ||
|
|
1553
1549
|
!Number.isSafeInteger(slack.generation) ||
|
|
@@ -2352,10 +2348,7 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
|
|
|
2352
2348
|
observed.process.uid !== config.runtimeUid
|
|
2353
2349
|
)
|
|
2354
2350
|
throw new Error("runtime observation identity rejected");
|
|
2355
|
-
return
|
|
2356
|
-
observed,
|
|
2357
|
-
join(realpathSync(config.profilesRoot), context.profileId)
|
|
2358
|
-
);
|
|
2351
|
+
return observed;
|
|
2359
2352
|
};
|
|
2360
2353
|
const ensureRefreshTarget = async () => {
|
|
2361
2354
|
if (refreshPrepared) return;
|