@sellable/install 0.1.589 → 0.1.591
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-customer-image/Dockerfile +8 -1
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +134 -13
- package/lib/sellable-agent/hermes-bridge.mjs +5 -2
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +1 -1
- package/lib/sellable-agent/profile-materializer.mjs +169 -3
- package/lib/sellable-agent/provisioning-adapter.mjs +4 -4
- package/package.json +1 -1
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.591
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.860
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
9
|
ARG MCP_INTEGRITY=sha512-jRA1KsCV2d4Zcuo5/UmJiP8P0NaoC3ZDvUFRQOc2AMSNDVmUR6gWvtkyAiqb1lukbFWKzfVlz2/4EWzxvb7hkQ==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.591" \
|
|
12
12
|
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.860" \
|
|
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.589" \
|
|
|
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.591' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.860') 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.591', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.860', 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.591` 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.591 .
|
|
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`.
|
|
@@ -6,7 +6,9 @@ USER root
|
|
|
6
6
|
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.860
|
|
8
8
|
ARG MCP_INTEGRITY=sha512-jRA1KsCV2d4Zcuo5/UmJiP8P0NaoC3ZDvUFRQOc2AMSNDVmUR6gWvtkyAiqb1lukbFWKzfVlz2/4EWzxvb7hkQ==
|
|
9
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
9
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.591
|
|
10
|
+
ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
|
|
11
|
+
ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
|
|
10
12
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
11
13
|
ARG YAML_VERSION=2.9.0
|
|
12
14
|
ARG HERMES_VERSION=0.20.0
|
|
@@ -23,6 +25,11 @@ RUN --mount=type=secret,id=mcp_trust_root,required=true \
|
|
|
23
25
|
&& npm install --prefix /usr/local/lib/sellable-agent/external-runtime/mcp \
|
|
24
26
|
--omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
|
|
25
27
|
&& test "$(node -p "require('/usr/local/lib/sellable-agent/external-runtime/mcp/node_modules/@sellable/mcp/package.json').version")" = "${MCP_PACKAGE##*@}" \
|
|
28
|
+
&& test "$(npm view "${ADMIN_MCP_PACKAGE}" dist.integrity)" = "${ADMIN_MCP_INTEGRITY}" \
|
|
29
|
+
&& npm install --prefix /usr/local/lib/sellable-agent/internal-admin-mcp \
|
|
30
|
+
--omit=dev --ignore-scripts --no-audit --no-fund "${ADMIN_MCP_PACKAGE}" \
|
|
31
|
+
&& test "$(node -p "require('/usr/local/lib/sellable-agent/internal-admin-mcp/node_modules/@sellable/admin-mcp/package.json').version")" = "${ADMIN_MCP_PACKAGE##*@}" \
|
|
32
|
+
&& test -x /usr/local/lib/sellable-agent/internal-admin-mcp/node_modules/.bin/sellable-admin-mcp \
|
|
26
33
|
&& npm install --prefix /usr/local/lib/sellable-agent \
|
|
27
34
|
--omit=dev --ignore-scripts --no-audit --no-fund \
|
|
28
35
|
"tus-js-client@${TUS_JS_CLIENT_VERSION}" "yaml@${YAML_VERSION}" \
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
unlinkSync,
|
|
15
15
|
writeFileSync,
|
|
16
16
|
} from "node:fs";
|
|
17
|
+
import { loadavg } from "node:os";
|
|
17
18
|
import { dirname, join } from "node:path";
|
|
18
19
|
import { pathToFileURL } from "node:url";
|
|
19
20
|
import {
|
|
@@ -61,6 +62,7 @@ import {
|
|
|
61
62
|
import { createModelHealthReporter } from "../model-health-reporter.mjs";
|
|
62
63
|
import {
|
|
63
64
|
materializeNativeSlackCustomerProfile,
|
|
65
|
+
SELLABLE_INTERNAL_AGENT_WORKSPACE_ID,
|
|
64
66
|
validateNativeSlackCustomerProfile,
|
|
65
67
|
} from "../profile-materializer.mjs";
|
|
66
68
|
import {
|
|
@@ -93,7 +95,7 @@ const RUNTIME_GENERATION = Number(
|
|
|
93
95
|
const BOOT_SESSION_ID = randomUUID();
|
|
94
96
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
|
|
95
97
|
const INSTALLER_PACKAGE =
|
|
96
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.
|
|
98
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.591";
|
|
97
99
|
const MCP_PACKAGE =
|
|
98
100
|
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.860";
|
|
99
101
|
|
|
@@ -119,6 +121,7 @@ async function run(
|
|
|
119
121
|
{ env = process.env, timeoutMs = 60_000 } = {}
|
|
120
122
|
) {
|
|
121
123
|
return new Promise((resolve) => {
|
|
124
|
+
const startedAt = Date.now();
|
|
122
125
|
const child = spawn(command, args, {
|
|
123
126
|
env,
|
|
124
127
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -139,6 +142,7 @@ async function run(
|
|
|
139
142
|
stdout: "",
|
|
140
143
|
stderr: "",
|
|
141
144
|
timedOut,
|
|
145
|
+
elapsedMs: Date.now() - startedAt,
|
|
142
146
|
error: error.message,
|
|
143
147
|
});
|
|
144
148
|
});
|
|
@@ -149,11 +153,70 @@ async function run(
|
|
|
149
153
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
150
154
|
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
151
155
|
timedOut,
|
|
156
|
+
elapsedMs: Date.now() - startedAt,
|
|
152
157
|
});
|
|
153
158
|
});
|
|
154
159
|
});
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
const EXACT_INSTALLER_PACKAGE =
|
|
163
|
+
/^@sellable\/install@\d+\.\d+\.\d+(?:[-.A-Za-z0-9]+)?$/;
|
|
164
|
+
|
|
165
|
+
export function assertCustomerRuntimeInstallerPackage({
|
|
166
|
+
expected,
|
|
167
|
+
actual = INSTALLER_PACKAGE,
|
|
168
|
+
}) {
|
|
169
|
+
if (
|
|
170
|
+
!EXACT_INSTALLER_PACKAGE.test(expected ?? "") ||
|
|
171
|
+
!EXACT_INSTALLER_PACKAGE.test(actual ?? "")
|
|
172
|
+
) {
|
|
173
|
+
throw new Error("customer_runtime_installer_package_invalid");
|
|
174
|
+
}
|
|
175
|
+
if (expected !== actual) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`customer_runtime_installer_package_mismatch:expected=${expected}:actual=${actual}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return actual;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function doctorCheckName(output) {
|
|
184
|
+
const line = String(output)
|
|
185
|
+
.split(/\r?\n/)
|
|
186
|
+
.map((value) => value.trim())
|
|
187
|
+
.find((value) =>
|
|
188
|
+
/(?:✗|\b(?:fail|error|missing)\b|Lazy-installing|pip install|npm install)/i.test(
|
|
189
|
+
value
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
return (line ?? "unknown")
|
|
193
|
+
.replace(/^[^A-Za-z0-9.]+/, "")
|
|
194
|
+
.replace(/[^A-Za-z0-9.]+/g, "_")
|
|
195
|
+
.replace(/^_+|_+$/g, "")
|
|
196
|
+
.slice(0, 160);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function customerRuntimeDoctorFailureCode(
|
|
200
|
+
doctor,
|
|
201
|
+
{ loadAverage = loadavg() } = {}
|
|
202
|
+
) {
|
|
203
|
+
if (doctor?.timedOut) {
|
|
204
|
+
const observedLoad = [...loadAverage]
|
|
205
|
+
.slice(0, 3)
|
|
206
|
+
.map((value) => Number(value).toFixed(2))
|
|
207
|
+
.join(",");
|
|
208
|
+
return `customer_runtime_doctor_timeout:elapsed_ms=${Math.max(0, Math.round(Number(doctor.elapsedMs) || 0))}:loadavg=${observedLoad}`;
|
|
209
|
+
}
|
|
210
|
+
const output = `${doctor?.stdout ?? ""}\n${doctor?.stderr ?? ""}`;
|
|
211
|
+
if (
|
|
212
|
+
doctor?.status !== 0 ||
|
|
213
|
+
/Lazy-installing|pip install|npm install/i.test(output)
|
|
214
|
+
) {
|
|
215
|
+
return `customer_runtime_doctor_rejected:check=${doctorCheckName(output)}`;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
157
220
|
function exactArtifact(claim, type) {
|
|
158
221
|
const artifacts = claim?.revision?.artifacts?.filter(
|
|
159
222
|
(artifact) =>
|
|
@@ -182,6 +245,7 @@ function compileDesired(claim, trustRootPem) {
|
|
|
182
245
|
const service = claim?.runtimeContract?.service;
|
|
183
246
|
const model = claim?.runtimeContract?.model;
|
|
184
247
|
const apiServer = claim?.runtimeContract?.apiServer;
|
|
248
|
+
const installerPackage = claim?.runtimeContract?.installerPackage;
|
|
185
249
|
const selectedBindings = slack?.selectedBindings;
|
|
186
250
|
const toolInclude = service?.toolInclude;
|
|
187
251
|
if (
|
|
@@ -191,6 +255,7 @@ function compileDesired(claim, trustRootPem) {
|
|
|
191
255
|
!service ||
|
|
192
256
|
!model ||
|
|
193
257
|
!apiServer ||
|
|
258
|
+
!installerPackage ||
|
|
194
259
|
apiServer.host !== "127.0.0.1" ||
|
|
195
260
|
apiServer.port !== 8642 ||
|
|
196
261
|
apiServer.cors !== false ||
|
|
@@ -210,6 +275,7 @@ function compileDesired(claim, trustRootPem) {
|
|
|
210
275
|
) {
|
|
211
276
|
throw new Error("customer_claim_contract_rejected");
|
|
212
277
|
}
|
|
278
|
+
assertCustomerRuntimeInstallerPackage({ expected: installerPackage });
|
|
213
279
|
const modelEnvelope = claim.secretEnvelopes.modelCodex;
|
|
214
280
|
if (
|
|
215
281
|
model.provider !== "codex" ||
|
|
@@ -262,7 +328,7 @@ function compileDesired(claim, trustRootPem) {
|
|
|
262
328
|
});
|
|
263
329
|
const runtimeDesiredHash = flyRuntimeDigest({
|
|
264
330
|
contract: "customer-runtime-desired/v1",
|
|
265
|
-
installerPackage
|
|
331
|
+
installerPackage,
|
|
266
332
|
accessPolicyHash,
|
|
267
333
|
});
|
|
268
334
|
if (
|
|
@@ -302,7 +368,7 @@ function compileDesired(claim, trustRootPem) {
|
|
|
302
368
|
accessPolicyHash,
|
|
303
369
|
runtimeDesiredHash,
|
|
304
370
|
hermesVersion: HERMES_VERSION,
|
|
305
|
-
installerPackage
|
|
371
|
+
installerPackage,
|
|
306
372
|
mcpPackage: MCP_PACKAGE,
|
|
307
373
|
policyHash: service.policyHash,
|
|
308
374
|
soul: exactArtifact(claim, "SOUL"),
|
|
@@ -1811,6 +1877,58 @@ function writeRuntimeHermesConfig({
|
|
|
1811
1877
|
};
|
|
1812
1878
|
}
|
|
1813
1879
|
|
|
1880
|
+
const INTERNAL_ADMIN_SETTINGS_FILES = Object.freeze([
|
|
1881
|
+
".env",
|
|
1882
|
+
"settings.json",
|
|
1883
|
+
"config-state.json",
|
|
1884
|
+
]);
|
|
1885
|
+
|
|
1886
|
+
export function installInternalAdminRuntimeSettings({
|
|
1887
|
+
desired,
|
|
1888
|
+
profileRoot,
|
|
1889
|
+
secretsRoot = SECRET_ROOT,
|
|
1890
|
+
}) {
|
|
1891
|
+
if (desired.workspaceId !== SELLABLE_INTERNAL_AGENT_WORKSPACE_ID) {
|
|
1892
|
+
return { ok: true, enabled: false };
|
|
1893
|
+
}
|
|
1894
|
+
const sourceRoot = join(profileRoot, ".sellable-admin");
|
|
1895
|
+
const targetRoot = join(secretsRoot, desired.profileId, "admin-settings");
|
|
1896
|
+
try {
|
|
1897
|
+
const source = lstatSync(sourceRoot);
|
|
1898
|
+
if (
|
|
1899
|
+
source.isSymbolicLink() ||
|
|
1900
|
+
!source.isDirectory() ||
|
|
1901
|
+
(source.mode & 0o077) !== 0
|
|
1902
|
+
) {
|
|
1903
|
+
throw new Error("customer_internal_admin_settings_root_rejected");
|
|
1904
|
+
}
|
|
1905
|
+
mkdirSync(targetRoot, { recursive: true, mode: 0o700 });
|
|
1906
|
+
chmodSync(targetRoot, 0o700);
|
|
1907
|
+
for (const name of INTERNAL_ADMIN_SETTINGS_FILES) {
|
|
1908
|
+
const pathValue = join(sourceRoot, name);
|
|
1909
|
+
const entry = lstatSync(pathValue);
|
|
1910
|
+
if (
|
|
1911
|
+
entry.isSymbolicLink() ||
|
|
1912
|
+
!entry.isFile() ||
|
|
1913
|
+
(entry.mode & 0o077) !== 0 ||
|
|
1914
|
+
entry.size < 1 ||
|
|
1915
|
+
entry.size > 1024 * 1024
|
|
1916
|
+
) {
|
|
1917
|
+
throw new Error("customer_internal_admin_settings_file_rejected");
|
|
1918
|
+
}
|
|
1919
|
+
atomicFile(join(targetRoot, name), readFileSync(pathValue), 0o600);
|
|
1920
|
+
}
|
|
1921
|
+
return { ok: true, enabled: true, settingsRoot: targetRoot };
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
throw new Error(
|
|
1924
|
+
error instanceof Error &&
|
|
1925
|
+
error.message.startsWith("customer_internal_admin_settings_")
|
|
1926
|
+
? error.message
|
|
1927
|
+
: "customer_internal_admin_settings_missing"
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1814
1932
|
function allowTestProviders() {
|
|
1815
1933
|
return (
|
|
1816
1934
|
process.env.SELLABLE_AGENT_ALLOW_TEST_PROVIDERS === "1" ||
|
|
@@ -2216,16 +2334,10 @@ async function proveApplication({
|
|
|
2216
2334
|
const runtimeEnv = applicationRuntimeEnv({ desired, runtimeConfig, secrets });
|
|
2217
2335
|
const doctor = await run(HERMES, ["doctor"], {
|
|
2218
2336
|
env: runtimeEnv,
|
|
2219
|
-
timeoutMs:
|
|
2337
|
+
timeoutMs: 300_000,
|
|
2220
2338
|
});
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
/Lazy-installing|pip install|npm install/i.test(
|
|
2224
|
-
`${doctor.stdout}\n${doctor.stderr}`
|
|
2225
|
-
)
|
|
2226
|
-
) {
|
|
2227
|
-
throw new Error("customer_runtime_doctor_rejected");
|
|
2228
|
-
}
|
|
2339
|
+
const doctorFailure = customerRuntimeDoctorFailureCode(doctor);
|
|
2340
|
+
if (doctorFailure) throw new Error(doctorFailure);
|
|
2229
2341
|
const slackResponse = await fetch(`${slackApiOrigin()}/api/auth.test`, {
|
|
2230
2342
|
method: "POST",
|
|
2231
2343
|
headers: {
|
|
@@ -2449,12 +2561,21 @@ async function processOperation({ claim, exchange, client, identity }) {
|
|
|
2449
2561
|
desired,
|
|
2450
2562
|
profilesRoot: PROFILES_ROOT,
|
|
2451
2563
|
});
|
|
2452
|
-
if (!materialized.ok)
|
|
2564
|
+
if (!materialized.ok) {
|
|
2565
|
+
const detail =
|
|
2566
|
+
materialized.code === "native_slack_profile_insufficient_disk"
|
|
2567
|
+
? `:available_bytes=${materialized.availableBytes}:required_bytes=${materialized.requiredBytes}`
|
|
2568
|
+
: materialized.causeCode
|
|
2569
|
+
? `:cause=${materialized.causeCode}`
|
|
2570
|
+
: "";
|
|
2571
|
+
throw new Error(`${materialized.code}${detail}`);
|
|
2572
|
+
}
|
|
2453
2573
|
const pristineProfile = validateNativeSlackCustomerProfile({
|
|
2454
2574
|
desired,
|
|
2455
2575
|
profileRoot,
|
|
2456
2576
|
});
|
|
2457
2577
|
if (!pristineProfile.ok) throw new Error(pristineProfile.code);
|
|
2578
|
+
installInternalAdminRuntimeSettings({ desired, profileRoot });
|
|
2458
2579
|
const runtimeConfig = writeRuntimeHermesConfig({
|
|
2459
2580
|
desired,
|
|
2460
2581
|
toolInclude,
|
|
@@ -497,7 +497,10 @@ def _sellable_agent_restore_or_build_proven_soul(
|
|
|
497
497
|
or len(marker["generation"]) < 8
|
|
498
498
|
or len(marker["generation"]) > 128
|
|
499
499
|
):
|
|
500
|
-
|
|
500
|
+
marker_state = marker.get("state") if isinstance(marker, dict) else "invalid"
|
|
501
|
+
raise RuntimeError(
|
|
502
|
+
f"Sellable SOUL proof marker is not proven: path={marker_path} state={marker_state}"
|
|
503
|
+
)
|
|
501
504
|
|
|
502
505
|
soul_path = expected_home / "SOUL.md"
|
|
503
506
|
try:
|
|
@@ -1943,7 +1946,7 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
1943
1946
|
sourceSha256:
|
|
1944
1947
|
"a0e136367b64007d7b49ea006ab0aa7dcc66b12134b512a463a03bd69fb8a90c",
|
|
1945
1948
|
patchedSha256:
|
|
1946
|
-
"
|
|
1949
|
+
"cd9dc106b741f3351550f21f5349c42c909b3bd938155d2e5d391e857e1e3f46",
|
|
1947
1950
|
},
|
|
1948
1951
|
{
|
|
1949
1952
|
path: "hermes_cli/web_routers/tools.py",
|
|
@@ -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.591";
|
|
43
43
|
const MCP_PACKAGE = "@sellable/mcp@0.1.860";
|
|
44
44
|
const HERMES_VERSION = "0.18.0";
|
|
45
45
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -1546,7 +1546,7 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1546
1546
|
]) ||
|
|
1547
1547
|
pinned.hermesCli !== "hermes" ||
|
|
1548
1548
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1549
|
-
pinned.installerPackage !== "@sellable/install@0.1.
|
|
1549
|
+
pinned.installerPackage !== "@sellable/install@0.1.591" ||
|
|
1550
1550
|
pinned.mcpPackage !== "@sellable/mcp@0.1.860" ||
|
|
1551
1551
|
!Array.isArray(policy.toolInclude) ||
|
|
1552
1552
|
policy.toolInclude.length === 0 ||
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
realpathSync,
|
|
14
14
|
renameSync,
|
|
15
15
|
rmSync,
|
|
16
|
+
statfsSync,
|
|
16
17
|
writeFileSync,
|
|
17
18
|
} from "node:fs";
|
|
18
19
|
import {
|
|
@@ -64,6 +65,15 @@ const FINGERPRINT = /^[a-f0-9]{16}$/;
|
|
|
64
65
|
const PRIVATE_REFERENCE = /^(?:secret|oauth-private):\/\/[A-Za-z0-9/_-]+$/;
|
|
65
66
|
const RAW_SECRET =
|
|
66
67
|
/(?:^|[^A-Za-z0-9])(?:xapp|xoxb|xoxa|sat)_[A-Za-z0-9_-]+|(?:xapp|xoxb|xoxa)-[A-Za-z0-9_-]+|oauth-provider-secret-byte|asec\.v1\./i;
|
|
68
|
+
const NATIVE_SLACK_COPY_MARGIN_BYTES = 64 * 1024 * 1024;
|
|
69
|
+
const NATIVE_SLACK_PEAK_COPY_MULTIPLIER = 2;
|
|
70
|
+
export const SELLABLE_INTERNAL_AGENT_WORKSPACE_ID = "cmj0wv9pe02jx23xdv6wfhg9x";
|
|
71
|
+
const PRESSURE_CACHE_PATHS = Object.freeze([
|
|
72
|
+
[".npm", "_npx"],
|
|
73
|
+
[".npm", "_cacache"],
|
|
74
|
+
[".cache", "uv"],
|
|
75
|
+
[".cache", "pip"],
|
|
76
|
+
]);
|
|
67
77
|
|
|
68
78
|
function sha256(value) {
|
|
69
79
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -80,6 +90,71 @@ function stableJson(value) {
|
|
|
80
90
|
return JSON.stringify(value);
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
function logicalTreeBytes(pathValue) {
|
|
94
|
+
if (!existsSync(pathValue)) return 0;
|
|
95
|
+
const entry = lstatSync(pathValue);
|
|
96
|
+
if (entry.isSymbolicLink()) {
|
|
97
|
+
throw new Error("native_slack_profile_symlink_rejected");
|
|
98
|
+
}
|
|
99
|
+
if (entry.isFile()) return entry.size;
|
|
100
|
+
if (!entry.isDirectory()) return 0;
|
|
101
|
+
return readdirSync(pathValue).reduce(
|
|
102
|
+
(total, name) => total + logicalTreeBytes(join(pathValue, name)),
|
|
103
|
+
0
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function filesystemAvailableBytes(pathValue) {
|
|
108
|
+
const stats = statfsSync(pathValue);
|
|
109
|
+
return Number(stats.bavail) * Number(stats.bsize);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function removeWithoutFollowing(pathValue) {
|
|
113
|
+
if (!existsSync(pathValue)) return false;
|
|
114
|
+
const entry = lstatSync(pathValue);
|
|
115
|
+
rmSync(pathValue, {
|
|
116
|
+
recursive: entry.isDirectory() && !entry.isSymbolicLink(),
|
|
117
|
+
force: true,
|
|
118
|
+
});
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sweepStaleNativeSlackStages(root, profileId) {
|
|
123
|
+
const prefix = `.${profileId}.`;
|
|
124
|
+
const suffix = ".staging";
|
|
125
|
+
let removed = 0;
|
|
126
|
+
for (const name of readdirSync(root)) {
|
|
127
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix)) continue;
|
|
128
|
+
if (removeWithoutFollowing(join(root, name))) removed += 1;
|
|
129
|
+
}
|
|
130
|
+
return removed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function prunePressureCaches(root) {
|
|
134
|
+
if (basename(root) !== "profiles") return [];
|
|
135
|
+
const dataRoot = dirname(root);
|
|
136
|
+
const removed = [];
|
|
137
|
+
for (const segments of PRESSURE_CACHE_PATHS) {
|
|
138
|
+
const target = join(dataRoot, ...segments);
|
|
139
|
+
if (removeWithoutFollowing(target)) removed.push(target);
|
|
140
|
+
}
|
|
141
|
+
return removed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function materializationCauseCode(error) {
|
|
145
|
+
if (error && typeof error === "object") {
|
|
146
|
+
const code = error.code;
|
|
147
|
+
if (typeof code === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(code)) {
|
|
148
|
+
return code;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (error instanceof Error) {
|
|
152
|
+
const code = error.message.split(/[:\s]/, 1)[0];
|
|
153
|
+
if (/^[A-Za-z0-9_.-]{1,128}$/.test(code)) return code;
|
|
154
|
+
}
|
|
155
|
+
return "native_slack_profile_unknown_failure";
|
|
156
|
+
}
|
|
157
|
+
|
|
83
158
|
function exactKeys(value, keys) {
|
|
84
159
|
return (
|
|
85
160
|
value &&
|
|
@@ -202,7 +277,7 @@ function validateDesired(desired) {
|
|
|
202
277
|
desired.serviceCredentialGeneration < 1 ||
|
|
203
278
|
desired.hermesCli !== "hermes" ||
|
|
204
279
|
desired.hermesVersion !== "0.18.0" ||
|
|
205
|
-
desired.installerPackage !== "@sellable/install@0.1.
|
|
280
|
+
desired.installerPackage !== "@sellable/install@0.1.591" ||
|
|
206
281
|
desired.mcpPackage !== "@sellable/mcp@0.1.860" ||
|
|
207
282
|
!Array.isArray(desired.toolInclude) ||
|
|
208
283
|
desired.toolInclude.length === 0 ||
|
|
@@ -2178,6 +2253,7 @@ export async function materializeAgentProfile(input = {}) {
|
|
|
2178
2253
|
}
|
|
2179
2254
|
|
|
2180
2255
|
export const NATIVE_SLACK_CUSTOMER_PROFILE_INVENTORY = Object.freeze([
|
|
2256
|
+
".env",
|
|
2181
2257
|
"agent-profile.json",
|
|
2182
2258
|
"config.yaml",
|
|
2183
2259
|
"shell-hooks-allowlist.json",
|
|
@@ -2187,6 +2263,32 @@ export const NATIVE_SLACK_CUSTOMER_PROFILE_INVENTORY = Object.freeze([
|
|
|
2187
2263
|
"skills/sellable/SKILL.md",
|
|
2188
2264
|
]);
|
|
2189
2265
|
|
|
2266
|
+
const LEGACY_INTERNAL_ADMIN_PROFILE_ROOT = "/opt/data/profiles/sellable-admin";
|
|
2267
|
+
|
|
2268
|
+
function repointInternalCronProfileReferences(desired, profileRoot) {
|
|
2269
|
+
if (desired.workspaceId !== SELLABLE_INTERNAL_AGENT_WORKSPACE_ID) return;
|
|
2270
|
+
const jobsPath = join(profileRoot, "cron", "jobs.json");
|
|
2271
|
+
if (!existsSync(jobsPath)) return;
|
|
2272
|
+
const entry = lstatSync(jobsPath);
|
|
2273
|
+
if (
|
|
2274
|
+
entry.isSymbolicLink() ||
|
|
2275
|
+
!entry.isFile() ||
|
|
2276
|
+
(entry.mode & 0o022) !== 0 ||
|
|
2277
|
+
entry.size > 1024 * 1024
|
|
2278
|
+
) {
|
|
2279
|
+
throw new Error("native_slack_cron_jobs_rejected");
|
|
2280
|
+
}
|
|
2281
|
+
const current = readFileSync(jobsPath, "utf8");
|
|
2282
|
+
JSON.parse(current);
|
|
2283
|
+
if (!current.includes(LEGACY_INTERNAL_ADMIN_PROFILE_ROOT)) return;
|
|
2284
|
+
const updated = current.replaceAll(
|
|
2285
|
+
LEGACY_INTERNAL_ADMIN_PROFILE_ROOT,
|
|
2286
|
+
`/opt/data/profiles/${desired.profileId}`
|
|
2287
|
+
);
|
|
2288
|
+
JSON.parse(updated);
|
|
2289
|
+
atomicWrite(jobsPath, updated, entry.mode & 0o777);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2190
2292
|
const NATIVE_SLACK_KEYS = [
|
|
2191
2293
|
"runtimeMode",
|
|
2192
2294
|
"workspaceId",
|
|
@@ -2352,6 +2454,21 @@ function nativeSlackCustomerFiles(desired, soulBytes = desired.soul) {
|
|
|
2352
2454
|
SELLABLE_REQUIRE_WORKSPACE_LOCK: "1",
|
|
2353
2455
|
},
|
|
2354
2456
|
},
|
|
2457
|
+
...(desired.workspaceId === SELLABLE_INTERNAL_AGENT_WORKSPACE_ID
|
|
2458
|
+
? {
|
|
2459
|
+
"sellable-admin": {
|
|
2460
|
+
command:
|
|
2461
|
+
"/usr/local/lib/sellable-agent/internal-admin-mcp/node_modules/.bin/sellable-admin-mcp",
|
|
2462
|
+
args: [],
|
|
2463
|
+
enabled: true,
|
|
2464
|
+
env: {
|
|
2465
|
+
SELLABLE_ADMIN_HOME: `${runtimeSecretRoot}/admin-settings`,
|
|
2466
|
+
SELLABLE_ADMIN_CONFIG_PATH: `${runtimeSecretRoot}/admin-settings/settings.json`,
|
|
2467
|
+
SELLABLE_ADMIN_STATE_PATH: `${runtimeSecretRoot}/admin-settings/config-state.json`,
|
|
2468
|
+
},
|
|
2469
|
+
},
|
|
2470
|
+
}
|
|
2471
|
+
: {}),
|
|
2355
2472
|
},
|
|
2356
2473
|
});
|
|
2357
2474
|
const mcp = {
|
|
@@ -2376,6 +2493,7 @@ function nativeSlackCustomerFiles(desired, soulBytes = desired.soul) {
|
|
|
2376
2493
|
},
|
|
2377
2494
|
};
|
|
2378
2495
|
return {
|
|
2496
|
+
".env": "",
|
|
2379
2497
|
"config.yaml": `${JSON.stringify(config, null, 2)}\n`,
|
|
2380
2498
|
"shell-hooks-allowlist.json": HERMES_SNAPSHOT_HOOK_ALLOWLIST_BYTES,
|
|
2381
2499
|
"sellable/mcp.json": `${JSON.stringify(mcp, null, 2)}\n`,
|
|
@@ -2539,6 +2657,7 @@ export function validateNativeSlackCustomerProfile({ desired, profileRoot }) {
|
|
|
2539
2657
|
export function materializeNativeSlackCustomerProfile({
|
|
2540
2658
|
desired,
|
|
2541
2659
|
profilesRoot,
|
|
2660
|
+
diskFreeBytes = filesystemAvailableBytes,
|
|
2542
2661
|
}) {
|
|
2543
2662
|
const valid = validateNativeSlackDesired(desired);
|
|
2544
2663
|
if (!valid.ok) return valid;
|
|
@@ -2557,6 +2676,50 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2557
2676
|
if (dirname(activeRoot) !== root || dirname(stagingRoot) !== root) {
|
|
2558
2677
|
return { ok: false, code: "native_slack_profile_path_rejected" };
|
|
2559
2678
|
}
|
|
2679
|
+
sweepStaleNativeSlackStages(root, desired.profileId);
|
|
2680
|
+
const extraProfiles = readdirSync(root).filter((name) => {
|
|
2681
|
+
if (name === desired.profileId || name.startsWith(".")) return false;
|
|
2682
|
+
const pathValue = join(root, name);
|
|
2683
|
+
const entry = lstatSync(pathValue);
|
|
2684
|
+
return entry.isDirectory() || entry.isSymbolicLink();
|
|
2685
|
+
});
|
|
2686
|
+
if (extraProfiles.length > 0) {
|
|
2687
|
+
return {
|
|
2688
|
+
ok: false,
|
|
2689
|
+
code: "native_slack_extra_profile_rejected",
|
|
2690
|
+
extraProfiles: extraProfiles.sort(),
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
const desiredSeedBytes = Object.values(
|
|
2694
|
+
nativeSlackCustomerFiles(desired)
|
|
2695
|
+
).reduce(
|
|
2696
|
+
(total, bytes) => total + Buffer.byteLength(bytes),
|
|
2697
|
+
Buffer.byteLength(JSON.stringify(nativeSlackManifest(desired, {})))
|
|
2698
|
+
);
|
|
2699
|
+
const profileBytes = Math.max(
|
|
2700
|
+
desiredSeedBytes,
|
|
2701
|
+
logicalTreeBytes(activeRoot)
|
|
2702
|
+
);
|
|
2703
|
+
const requiredBytes =
|
|
2704
|
+
profileBytes * NATIVE_SLACK_PEAK_COPY_MULTIPLIER +
|
|
2705
|
+
NATIVE_SLACK_COPY_MARGIN_BYTES;
|
|
2706
|
+
let availableBytes = Number(diskFreeBytes(root));
|
|
2707
|
+
if (!Number.isFinite(availableBytes) || availableBytes < 0) {
|
|
2708
|
+
throw new Error("native_slack_profile_disk_readback_invalid");
|
|
2709
|
+
}
|
|
2710
|
+
if (availableBytes < requiredBytes) {
|
|
2711
|
+
prunePressureCaches(root);
|
|
2712
|
+
availableBytes = Number(diskFreeBytes(root));
|
|
2713
|
+
}
|
|
2714
|
+
if (availableBytes < requiredBytes) {
|
|
2715
|
+
return {
|
|
2716
|
+
ok: false,
|
|
2717
|
+
code: "native_slack_profile_insufficient_disk",
|
|
2718
|
+
requiredBytes,
|
|
2719
|
+
availableBytes,
|
|
2720
|
+
profileBytes,
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2560
2723
|
let soulBytes = Buffer.from(desired.soul, "utf8");
|
|
2561
2724
|
const liveSkillsIdentity = preservedSkillsConfigIdentity(activeRoot);
|
|
2562
2725
|
if (existsSync(activeRoot)) {
|
|
@@ -2575,6 +2738,7 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2575
2738
|
soulBytes
|
|
2576
2739
|
);
|
|
2577
2740
|
mergeHermesState(activeRoot, stagingRoot);
|
|
2741
|
+
repointInternalCronProfileReferences(desired, stagingRoot);
|
|
2578
2742
|
mergeLiveSkillsConfig(activeRoot, stagingRoot);
|
|
2579
2743
|
if (
|
|
2580
2744
|
liveSkillsIdentity !== null &&
|
|
@@ -2605,7 +2769,7 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2605
2769
|
profileDigest: manifest.profileDigest,
|
|
2606
2770
|
observedSoulRawSha256: sha256(soulBytes),
|
|
2607
2771
|
};
|
|
2608
|
-
} catch {
|
|
2772
|
+
} catch (error) {
|
|
2609
2773
|
if (existsSync(stagingRoot) && !lstatSync(stagingRoot).isSymbolicLink()) {
|
|
2610
2774
|
rmSync(stagingRoot, { recursive: true, force: true });
|
|
2611
2775
|
}
|
|
@@ -2615,6 +2779,7 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2615
2779
|
return {
|
|
2616
2780
|
ok: false,
|
|
2617
2781
|
code: "native_slack_profile_materialization_failed",
|
|
2782
|
+
causeCode: materializationCauseCode(error),
|
|
2618
2783
|
};
|
|
2619
2784
|
}
|
|
2620
2785
|
};
|
|
@@ -2622,10 +2787,11 @@ export function materializeNativeSlackCustomerProfile({
|
|
|
2622
2787
|
return withProfileSoulLock(desired.profileId, materialize, {
|
|
2623
2788
|
dataRoot: basename(root) === "profiles" ? dirname(root) : root,
|
|
2624
2789
|
});
|
|
2625
|
-
} catch {
|
|
2790
|
+
} catch (error) {
|
|
2626
2791
|
return {
|
|
2627
2792
|
ok: false,
|
|
2628
2793
|
code: "native_slack_profile_materialization_failed",
|
|
2794
|
+
causeCode: materializationCauseCode(error),
|
|
2629
2795
|
};
|
|
2630
2796
|
}
|
|
2631
2797
|
}
|
|
@@ -25,7 +25,7 @@ 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 = "@sellable/install@0.1.
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.591";
|
|
29
29
|
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.860";
|
|
30
30
|
|
|
31
31
|
export function deriveAgentProfileId(workspaceId, agentId, hostId) {
|
|
@@ -611,7 +611,7 @@ function inspectProvisionedProfile({
|
|
|
611
611
|
!matchesReadback(manifest, request) ||
|
|
612
612
|
manifest.serviceCredentialReference !== serviceCredentialReference ||
|
|
613
613
|
manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
|
|
614
|
-
manifest.installerVersion !== "0.1.
|
|
614
|
+
manifest.installerVersion !== "0.1.591" ||
|
|
615
615
|
manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
|
|
616
616
|
manifest.credentialKeyVersion !== credentialKeyVersion ||
|
|
617
617
|
!/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
|
|
@@ -825,7 +825,7 @@ function successReceipt({
|
|
|
825
825
|
subject: `agent-profile:${observed.profileId}`,
|
|
826
826
|
cli: {
|
|
827
827
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
828
|
-
installVersion: "0.1.
|
|
828
|
+
installVersion: "0.1.591",
|
|
829
829
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
830
830
|
command: "hermes profile bootstrap",
|
|
831
831
|
},
|
|
@@ -873,7 +873,7 @@ function failure(code, stages, extra = {}, request = null) {
|
|
|
873
873
|
: "agent-profile:unbound",
|
|
874
874
|
cli: {
|
|
875
875
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
876
|
-
installVersion: "0.1.
|
|
876
|
+
installVersion: "0.1.591",
|
|
877
877
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
878
878
|
command: "hermes profile bootstrap",
|
|
879
879
|
},
|