@sellable/install 0.1.800 → 0.1.802

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.
@@ -16,10 +16,8 @@ import {
16
16
  readdirSync,
17
17
  renameSync,
18
18
  rmSync,
19
- statfsSync,
20
19
  writeFileSync,
21
20
  } from "node:fs";
22
- import { tmpdir } from "node:os";
23
21
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
24
22
  import { promisify } from "node:util";
25
23
  import { parseDocument } from "yaml";
@@ -213,18 +211,11 @@ export const HERMES_SNAPSHOT_MAX_ARCHIVE_BYTES = 512 * 1024 * 1024;
213
211
  const MAX_PLUGIN_DATA_BYTES = 10 * 1024 * 1024;
214
212
  const MAX_PORTABLE_EXTERNAL_BYTES = 32 * 1024 * 1024;
215
213
  const MAX_PORTABLE_CONFIG_BYTES = 64 * 1024;
216
- // Portable snapshots contain user-authored/customizable state plus the compact
217
- // Hermes session database and routing records required to continue an existing
218
- // Slack thread after a Machine/volume migration. If native Hermes cannot
219
- // package that bounded set within two minutes, abort the helper and let the
220
- // persistent retry scheduler back off instead of contending with the live
221
- // Slack gateway for a full ten minutes.
214
+ // Portable snapshots contain only state that compounds (see
215
+ // HERMES_SNAPSHOT_USER_STATE_ENTRIES), a few megabytes. If native Hermes cannot
216
+ // package that within two minutes, abort the helper and let the persistent
217
+ // retry scheduler back off instead of contending with the live Slack gateway.
222
218
  const NATIVE_COMMAND_TIMEOUT_MS = 120_000;
223
- // A healthy established Admin state DB can exceed 500 MiB. Keep compaction
224
- // bounded inside the 15-minute Upgrade backup stage without killing SQLite's
225
- // final VACUUM on shared Fly CPU.
226
- const STATE_DB_COMPACTION_TIMEOUT_MS = 600_000;
227
- export const HERMES_SNAPSHOT_CRON_SESSION_RETENTION_HOURS = 48;
228
219
  const TRANSIENT_PROFILE_ROOTS = new Set([
229
220
  ".artifact-venv",
230
221
  ".cache",
@@ -259,11 +250,11 @@ export function isHermesTransientProfilePath(relativePath) {
259
250
  TRANSIENT_PROFILE_PATHS.has(relativePath)
260
251
  );
261
252
  }
262
- // The encrypted Fly volume remains the authoritative full profile. The
263
- // portable disaster-recovery archive also carries state.db and sessions so a
264
- // replacement volume retains chat history and Slack thread routing. Caches,
265
- // dependency trees, secrets, and live logs remain excluded.
266
- export const HERMES_SNAPSHOT_USER_STATE_ENTRIES = Object.freeze([
253
+ // Durable profile entries the pre-profile layout kept directly under
254
+ // HERMES_HOME. The one-time legacy move carries all of them, run history
255
+ // included, because it moves live state on the volume rather than packing a
256
+ // recovery archive.
257
+ const HERMES_LEGACY_USER_STATE_ENTRIES = Object.freeze([
267
258
  "AGENTS.md",
268
259
  "SOUL.md",
269
260
  "artifacts",
@@ -286,6 +277,26 @@ export const HERMES_SNAPSHOT_USER_STATE_ENTRIES = Object.freeze([
286
277
  "state.db",
287
278
  "workspace",
288
279
  ]);
280
+ // The encrypted Fly volume remains the authoritative full profile and keeps
281
+ // everything across an in-place Upgrade. The portable archive, used only to
282
+ // rebuild an Agent on a replacement volume, carries just the state that
283
+ // compounds: who the Agent is (SOUL, AGENTS), what it remembers, the skills it
284
+ // learned, its scheduled work (cron and the scripts cron runs), hooks and
285
+ // plugins. Run history (state.db, sessions), task boards and scratch areas
286
+ // never travel: a rebuilt Agent starts fresh conversations with all of the
287
+ // above. Caches, dependency trees, secrets and live logs remain excluded.
288
+ export const HERMES_SNAPSHOT_USER_STATE_ENTRIES = Object.freeze([
289
+ "AGENTS.md",
290
+ "SOUL.md",
291
+ "cron",
292
+ "hooks",
293
+ "memories",
294
+ "memory",
295
+ "plugin-data",
296
+ "plugins",
297
+ "scripts",
298
+ "skills",
299
+ ]);
289
300
  export const HERMES_SNAPSHOT_RETRY_BACKOFF_MS = Object.freeze([
290
301
  15 * 60_000,
291
302
  60 * 60_000,
@@ -998,272 +1009,6 @@ const childRunner = (runChild) => runChild ?? runBoundedHermesChild;
998
1009
  const childFailed = (result) =>
999
1010
  result?.exitCode !== 0 || result?.timedOut || result?.outputLimitExceeded;
1000
1011
 
1001
- const STATE_DB_COMPACTION_SCRIPT = String.raw`
1002
- import json
1003
- import os
1004
- import sqlite3
1005
- import sys
1006
- import time
1007
-
1008
- source_path, target_path = sys.argv[1], sys.argv[2]
1009
- retention_hours = int(sys.argv[3])
1010
- # VACUUM INTO writes one consistent, compact copy of the live pages only, so
1011
- # space the live database has freed never travels with the backup.
1012
- source = sqlite3.connect("file:" + source_path + "?mode=ro", uri=True)
1013
- try:
1014
- source.execute("VACUUM INTO ?", (target_path,))
1015
- finally:
1016
- source.close()
1017
- destination = sqlite3.connect(target_path)
1018
-
1019
- try:
1020
- if destination.execute("PRAGMA quick_check").fetchone()[0] != "ok":
1021
- raise RuntimeError("source backup failed quick_check")
1022
- destination.execute("PRAGMA foreign_keys=ON")
1023
- table_names = {
1024
- row[0]
1025
- for row in destination.execute(
1026
- "SELECT name FROM sqlite_master WHERE type='table'"
1027
- )
1028
- }
1029
- pruned_sessions = 0
1030
- pruned_messages = 0
1031
- if {"sessions", "messages"}.issubset(table_names):
1032
- cutoff = time.time() - retention_hours * 60 * 60
1033
- session_columns = {
1034
- row[1] for row in destination.execute("PRAGMA table_info(sessions)")
1035
- }
1036
- pinned_clause = (
1037
- "AND COALESCE(pinned, 0) = 0" if "pinned" in session_columns else ""
1038
- )
1039
- destination.execute(
1040
- "CREATE TEMP TABLE snapshot_pruned_sessions(id TEXT PRIMARY KEY)"
1041
- )
1042
- destination.execute(
1043
- "WITH RECURSIVE doomed(id) AS ("
1044
- # Hermes never ends an endpoint run's (api_server) session; past
1045
- # the retention it is as finished as an ended cron session.
1046
- " SELECT id FROM sessions"
1047
- " WHERE ((source = 'cron' AND ended_at IS NOT NULL)"
1048
- " OR source = 'api_server')"
1049
- " AND COALESCE(last_activity_at, ended_at, started_at) < ? "
1050
- + pinned_clause
1051
- + " UNION"
1052
- " SELECT child.id FROM sessions child"
1053
- " JOIN doomed parent ON child.parent_session_id = parent.id"
1054
- ") INSERT OR IGNORE INTO snapshot_pruned_sessions(id)"
1055
- " SELECT id FROM doomed",
1056
- (cutoff,),
1057
- )
1058
- pruned_sessions = destination.execute(
1059
- "SELECT COUNT(*) FROM snapshot_pruned_sessions"
1060
- ).fetchone()[0]
1061
- pruned_messages = destination.execute(
1062
- "SELECT COUNT(*) FROM messages WHERE session_id IN "
1063
- "(SELECT id FROM snapshot_pruned_sessions)"
1064
- ).fetchone()[0]
1065
- destination.execute(
1066
- "DELETE FROM messages WHERE session_id IN "
1067
- "(SELECT id FROM snapshot_pruned_sessions)"
1068
- )
1069
- if "session_model_usage" in table_names:
1070
- destination.execute(
1071
- "DELETE FROM session_model_usage WHERE session_id IN "
1072
- "(SELECT id FROM snapshot_pruned_sessions)"
1073
- )
1074
- destination.execute(
1075
- "DELETE FROM sessions WHERE id IN "
1076
- "(SELECT id FROM snapshot_pruned_sessions)"
1077
- )
1078
- if "system_prompts" in table_names:
1079
- destination.execute(
1080
- "DELETE FROM system_prompts WHERE NOT EXISTS ("
1081
- " SELECT 1 FROM sessions"
1082
- " WHERE sessions.system_prompt_hash = system_prompts.hash"
1083
- ")"
1084
- )
1085
- destination.commit()
1086
- if pruned_sessions:
1087
- for table in ("messages_fts", "messages_fts_trigram", "messages_fts_cjk"):
1088
- if table in table_names:
1089
- destination.execute(
1090
- 'INSERT INTO "' + table + '" ("' + table + '") VALUES (\'optimize\')'
1091
- )
1092
- destination.commit()
1093
- destination.execute("VACUUM")
1094
- if destination.execute("PRAGMA quick_check").fetchone()[0] != "ok":
1095
- raise RuntimeError("compacted backup failed quick_check")
1096
- if destination.execute("PRAGMA foreign_key_check").fetchone() is not None:
1097
- raise RuntimeError("compacted backup failed foreign_key_check")
1098
- finally:
1099
- destination.close()
1100
-
1101
- os.chmod(target_path, 0o600)
1102
- print(json.dumps({
1103
- "ok": True,
1104
- "prunedSessions": pruned_sessions,
1105
- "prunedMessages": pruned_messages,
1106
- "retentionHours": retention_hours,
1107
- "archiveStateBytes": os.path.getsize(target_path),
1108
- }, separators=(",", ":")))
1109
- `;
1110
-
1111
- // Room the volume keeps after the compact copy lands, so a backup never
1112
- // leaves the live gateway without space to write.
1113
- const STATE_DB_VOLUME_HEADROOM_BYTES = 64 * 1024 * 1024;
1114
-
1115
- function freeBytesAt(directory) {
1116
- const stats = statfsSync(directory);
1117
- return stats.bavail * stats.bsize;
1118
- }
1119
-
1120
- const compactionFailure = (reason) =>
1121
- new Error("hermes_snapshot_state_db_compaction_failed", { cause: reason });
1122
-
1123
- // A bounded reason for the operator log; never the child's raw output.
1124
- function compactionChildFailureReason(result) {
1125
- if (result?.timedOut) return "timeout";
1126
- if (result?.outputLimitExceeded) return "output_limit";
1127
- const stderr = String(result?.stderr ?? "");
1128
- if (/database or disk is full|No space left on device/i.test(stderr)) {
1129
- return "disk_full";
1130
- }
1131
- if (/quick_check|foreign_key_check/.test(stderr)) return "integrity";
1132
- if (/sqlite3\.\w*Error/.test(stderr)) return "sqlite_error";
1133
- return "child_failed";
1134
- }
1135
-
1136
- export async function compactHermesSnapshotStateDb({
1137
- sourcePath,
1138
- targetPath,
1139
- retentionHours = HERMES_SNAPSHOT_CRON_SESSION_RETENTION_HOURS,
1140
- hermesPython = HERMES_PYTHON,
1141
- scratchDirectory = tmpdir(),
1142
- volumeFreeBytes = freeBytesAt,
1143
- runChild = /** @type {typeof runBoundedHermesChild | undefined} */ (
1144
- undefined
1145
- ),
1146
- }) {
1147
- if (
1148
- !isAbsolute(sourcePath) ||
1149
- !isAbsolute(targetPath) ||
1150
- !isAbsolute(scratchDirectory) ||
1151
- resolve(sourcePath) === resolve(targetPath) ||
1152
- !existsSync(sourcePath) ||
1153
- existsSync(targetPath) ||
1154
- !existsSync(scratchDirectory) ||
1155
- !Number.isSafeInteger(retentionHours) ||
1156
- retentionHours < 1 ||
1157
- retentionHours > 30 * 24
1158
- ) {
1159
- throw new Error("hermes_snapshot_state_db_compaction_rejected");
1160
- }
1161
- regularFile(sourcePath);
1162
- const scratchStat = lstatSync(scratchDirectory);
1163
- if (scratchStat.isSymbolicLink() || !scratchStat.isDirectory()) {
1164
- throw new Error("hermes_snapshot_state_db_compaction_rejected");
1165
- }
1166
- mkdirSync(dirname(targetPath), { recursive: true, mode: 0o700 });
1167
- let scratchRoot;
1168
- let finalTemporaryPath;
1169
- let targetCreated = false;
1170
- try {
1171
- // Keep SQLite's destination plus VACUUM journal on the Machine's
1172
- // ephemeral root filesystem. Building both beside the protected staging
1173
- // tree can briefly require multiple full state.db copies on the small
1174
- // persistent Fly volume and strand an otherwise healthy Upgrade at
1175
- // BACKING_UP with ENOSPC. Only the verified compact database crosses back
1176
- // onto the volume.
1177
- scratchRoot = mkdtempSync(
1178
- join(scratchDirectory, ".sellable-hermes-state-db-")
1179
- );
1180
- chmodSync(scratchRoot, 0o700);
1181
- const scratchTargetPath = join(scratchRoot, "state.db");
1182
- const result = await childRunner(runChild)({
1183
- command: hermesPython,
1184
- args: [
1185
- "-c",
1186
- STATE_DB_COMPACTION_SCRIPT,
1187
- sourcePath,
1188
- scratchTargetPath,
1189
- String(retentionHours),
1190
- ],
1191
- env: { ...process.env, NO_COLOR: "1" },
1192
- shell: false,
1193
- timeoutMs: STATE_DB_COMPACTION_TIMEOUT_MS,
1194
- maxOutputBytes: 64 * 1024,
1195
- });
1196
- if (childFailed(result) || !existsSync(scratchTargetPath)) {
1197
- throw compactionFailure(compactionChildFailureReason(result));
1198
- }
1199
- const scratchTarget = regularFile(scratchTargetPath);
1200
- const summary = JSON.parse(String(result.stdout ?? ""));
1201
- if (
1202
- summary?.ok !== true ||
1203
- summary.retentionHours !== retentionHours ||
1204
- !Number.isSafeInteger(summary.prunedSessions) ||
1205
- summary.prunedSessions < 0 ||
1206
- !Number.isSafeInteger(summary.prunedMessages) ||
1207
- summary.prunedMessages < 0 ||
1208
- summary.archiveStateBytes !== scratchTarget.size
1209
- ) {
1210
- throw compactionFailure("invalid_result");
1211
- }
1212
- // Only this compact copy lands on the persistent volume (the rest of the
1213
- // staged profile is hard links). Refuse up front when it cannot fit,
1214
- // rather than half-writing it and failing as a generic error.
1215
- if (
1216
- volumeFreeBytes(dirname(targetPath)) <
1217
- scratchTarget.size + STATE_DB_VOLUME_HEADROOM_BYTES
1218
- ) {
1219
- throw compactionFailure("disk_full");
1220
- }
1221
- finalTemporaryPath = join(
1222
- dirname(targetPath),
1223
- `.${basename(targetPath)}.${randomUUID()}`
1224
- );
1225
- copyFileSync(
1226
- scratchTargetPath,
1227
- finalTemporaryPath,
1228
- constants.COPYFILE_EXCL
1229
- );
1230
- chmodSync(finalTemporaryPath, 0o600);
1231
- const targetFd = openSync(finalTemporaryPath, constants.O_RDONLY);
1232
- try {
1233
- fsyncSync(targetFd);
1234
- } finally {
1235
- closeSync(targetFd);
1236
- }
1237
- // link(2) gives this staged publication no-replace semantics. If another
1238
- // writer created targetPath after admission, fail without deleting or
1239
- // overwriting the other writer's file.
1240
- linkSync(finalTemporaryPath, targetPath);
1241
- targetCreated = true;
1242
- rmSync(finalTemporaryPath, { force: true });
1243
- finalTemporaryPath = undefined;
1244
- fsyncDirectory(dirname(targetPath));
1245
- if (regularFile(targetPath).size !== scratchTarget.size) {
1246
- throw new Error("hermes_snapshot_state_db_compaction_failed");
1247
- }
1248
- return summary;
1249
- } catch (error) {
1250
- if (targetCreated) rmSync(targetPath, { force: true });
1251
- const reason =
1252
- error?.cause ?? (error?.code === "ENOSPC" ? "disk_full" : "copy_failed");
1253
- process.stderr.write(
1254
- `${JSON.stringify({
1255
- ok: false,
1256
- code: "hermes_snapshot_state_db_compaction_failed",
1257
- reason,
1258
- })}\n`
1259
- );
1260
- throw new Error("hermes_snapshot_state_db_compaction_failed");
1261
- } finally {
1262
- if (finalTemporaryPath) rmSync(finalTemporaryPath, { force: true });
1263
- if (scratchRoot) rmSync(scratchRoot, { recursive: true, force: true });
1264
- }
1265
- }
1266
-
1267
1012
  function nativeSummaryAccepted(result, complete, incomplete) {
1268
1013
  const output = `${result?.stdout ?? ""}\n${result?.stderr ?? ""}`;
1269
1014
  return (
@@ -1387,8 +1132,8 @@ function stageHermesUserState({ profileId, profileRoot, hermesRoot }) {
1387
1132
  }
1388
1133
  const target = join(stagedProfileRoot, name);
1389
1134
  // Hermes rejects the live SOUL when its link count is not exactly one.
1390
- // Snapshot compaction can run for minutes, so a staged hard link would
1391
- // make otherwise healthy Agent turns fail for the whole capture window.
1135
+ // A capture can run for a while, so a staged hard link would make
1136
+ // otherwise healthy Agent turns fail for the whole capture window.
1392
1137
  copyFileSync(source, target, constants.COPYFILE_EXCL);
1393
1138
  chmodSync(target, 0o600);
1394
1139
  if (regularFile(target).nlink !== 1) {
@@ -1396,10 +1141,6 @@ function stageHermesUserState({ profileId, profileRoot, hermesRoot }) {
1396
1141
  }
1397
1142
  continue;
1398
1143
  }
1399
- // state.db is copied online and compacted after the rest of the staged
1400
- // tree is assembled. Never hard-link this one file: pruning the staged
1401
- // recovery copy must be physically incapable of touching the live DB.
1402
- if (name === "state.db") continue;
1403
1144
  stageUserStateEntry(
1404
1145
  source,
1405
1146
  join(stagedProfileRoot, name),
@@ -1411,20 +1152,6 @@ function stageHermesUserState({ profileId, profileRoot, hermesRoot }) {
1411
1152
  }
1412
1153
  : null
1413
1154
  );
1414
- // Keep live SQLite sidecars beside their database hard link so Hermes'
1415
- // sqlite3.backup() observes committed WAL data. Hermes excludes the
1416
- // sidecars themselves from the resulting ZIP.
1417
- if (name.endsWith(".db")) {
1418
- for (const suffix of ["-wal", "-shm", "-journal"]) {
1419
- const sidecar = `${source}${suffix}`;
1420
- if (existsSync(sidecar)) {
1421
- stageUserStateEntry(
1422
- sidecar,
1423
- join(stagedProfileRoot, `${name}${suffix}`)
1424
- );
1425
- }
1426
- }
1427
- }
1428
1155
  }
1429
1156
  }
1430
1157
  for (const external of HERMES_SNAPSHOT_EXTERNAL_USER_STATE) {
@@ -1485,7 +1212,7 @@ function readLegacyUserStateMigration(path, profileId) {
1485
1212
  value.entries.some(
1486
1213
  (name) =>
1487
1214
  typeof name !== "string" ||
1488
- !HERMES_SNAPSHOT_USER_STATE_ENTRIES.includes(name)
1215
+ !HERMES_LEGACY_USER_STATE_ENTRIES.includes(name)
1489
1216
  ) ||
1490
1217
  new Set(value.entries).size !== value.entries.length
1491
1218
  ) {
@@ -1501,9 +1228,9 @@ function readLegacyUserStateMigration(path, profileId) {
1501
1228
  * (for example /opt/data/skills and /opt/data/cron). Profile isolation moved
1502
1229
  * those paths under profiles/<id>. Snapshot restore cannot discover that
1503
1230
  * unscoped state because it intentionally restores profile archives only.
1504
- * Stage only the same bounded user-state allowlist used by snapshots, merge
1505
- * without overwriting newer profile bytes, then mark the resulting profile
1506
- * dirty so the next portable snapshot becomes self-contained.
1231
+ * Stage only the bounded legacy user-state allowlist, merge without
1232
+ * overwriting newer profile bytes, then mark the resulting profile dirty so the
1233
+ * next portable snapshot becomes self-contained.
1507
1234
  */
1508
1235
  export function migrateLegacyHermesUserState({
1509
1236
  profileId,
@@ -1524,7 +1251,7 @@ export function migrateLegacyHermesUserState({
1524
1251
  entries: receipt.entries,
1525
1252
  };
1526
1253
  }
1527
- const entries = HERMES_SNAPSHOT_USER_STATE_ENTRIES.filter((name) =>
1254
+ const entries = HERMES_LEGACY_USER_STATE_ENTRIES.filter((name) =>
1528
1255
  existsSync(join(hermesRoot, name))
1529
1256
  );
1530
1257
  if (entries.length === 0) {
@@ -2565,10 +2292,7 @@ export async function captureHermesBackup({
2565
2292
  hermesVersion,
2566
2293
  archiveDirectory = "/tmp",
2567
2294
  hermesCli = HERMES,
2568
- hermesPython = HERMES_PYTHON,
2569
2295
  maxArchiveBytes = HERMES_SNAPSHOT_MAX_ARCHIVE_BYTES,
2570
- cronSessionRetentionHours = HERMES_SNAPSHOT_CRON_SESSION_RETENTION_HOURS,
2571
- stateDbCompactor = compactHermesSnapshotStateDb,
2572
2296
  runChild,
2573
2297
  operationIdentity = /** @type {any} */ (undefined),
2574
2298
  }) {
@@ -2607,16 +2331,6 @@ export async function captureHermesBackup({
2607
2331
  try {
2608
2332
  stagingRoot = stageHermesUserState({ profileId, profileRoot, hermesRoot });
2609
2333
  const stagedProfileRoot = join(stagingRoot, "profiles", profileId);
2610
- const sourceStateDb = join(profileRoot, "state.db");
2611
- if (existsSync(sourceStateDb)) {
2612
- await stateDbCompactor({
2613
- sourcePath: sourceStateDb,
2614
- targetPath: join(stagedProfileRoot, "state.db"),
2615
- retentionHours: cronSessionRetentionHours,
2616
- hermesPython,
2617
- runChild,
2618
- });
2619
- }
2620
2334
  assertStagedCronDependencies(stagedProfileRoot);
2621
2335
  const result = await childRunner(runChild)(
2622
2336
  invocation(["backup", "-o", archivePath], stagingRoot, hermesCli)
@@ -2652,7 +2366,7 @@ export async function captureHermesBackup({
2652
2366
  } catch (error) {
2653
2367
  const code = error instanceof Error ? error.message : "";
2654
2368
  return failed(
2655
- /^hermes_snapshot_(?:external_state|plugin_data|portable_config|dependency|state_db|soul_metadata)/.test(
2369
+ /^hermes_snapshot_(?:external_state|plugin_data|portable_config|dependency|soul_metadata)/.test(
2656
2370
  code
2657
2371
  )
2658
2372
  ? code
@@ -38,7 +38,7 @@ import { installAgentHostServices } from "./service-installer.mjs";
38
38
  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
- const INSTALLER_PACKAGE = "@sellable/install@0.1.800";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.802";
42
42
  const MCP_PACKAGE = "@sellable/mcp@0.1.957";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  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.800" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.802" ||
1550
1550
  pinned.mcpPackage !== "@sellable/mcp@0.1.957" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
@@ -312,7 +312,7 @@ function validateDesired(desired) {
312
312
  desired.serviceCredentialGeneration < 1 ||
313
313
  desired.hermesCli !== "hermes" ||
314
314
  desired.hermesVersion !== "0.18.0" ||
315
- desired.installerPackage !== "@sellable/install@0.1.800" ||
315
+ desired.installerPackage !== "@sellable/install@0.1.802" ||
316
316
  desired.mcpPackage !== "@sellable/mcp@0.1.957" ||
317
317
  !Array.isArray(desired.toolInclude) ||
318
318
  desired.toolInclude.length === 0 ||
@@ -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.800";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.802";
29
29
  export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.957";
30
30
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
31
31
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.800",
3
+ "version": "0.1.802",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {