@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.
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.589
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.589" \
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.589' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.860') throw new Error('package identity rejected');" \
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.589', 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}" \
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
@@ -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.589` is
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.589 .
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.589
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.589";
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: INSTALLER_PACKAGE,
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: INSTALLER_PACKAGE,
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: 180_000,
2337
+ timeoutMs: 300_000,
2220
2338
  });
2221
- if (
2222
- doctor.status !== 0 ||
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) throw new Error(materialized.code);
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
- raise RuntimeError("Sellable SOUL proof marker is not proven")
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
- "5f19a22c832db2e93b3da0fe9ea62606968233aa964318f8f81b0ae3dc7c8a13",
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.589";
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.589" ||
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.589" ||
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.589";
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.589" ||
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.589",
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.589",
876
+ installVersion: "0.1.591",
877
877
  mcpPackage: PINNED_MCP_PACKAGE,
878
878
  command: "hermes profile bootstrap",
879
879
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.589",
3
+ "version": "0.1.591",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {