@sellable/install 0.1.792 → 0.1.794
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 +1 -1
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +12 -2
- package/lib/sellable-agent/fly-customer-image/Dockerfile +1 -1
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +40 -1
- package/lib/sellable-agent/fly-customer-image/hermes-runs-client.mjs +228 -16
- package/lib/sellable-agent/hermes-bridge.mjs +255 -167
- package/lib/sellable-agent/hermes-memory-snapshot.mjs +64 -13
- 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 +1 -1
- package/lib/sellable-agent/provisioning-adapter.mjs +1 -1
- 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.794
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.957
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
9
|
ARG MCP_INTEGRITY=sha512-tIZuO5IEanT/afamDIWdorUNMdqZA6xnjL9jGAmGqYIlIqkXdHB9slWiwfLfmM44iEp7OT98IBHJlvbpMThk6A==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.794" \
|
|
12
12
|
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.957" \
|
|
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.792" \
|
|
|
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.794' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.957') 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.794', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.957', 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,7 +3,7 @@
|
|
|
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.794` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
sellableSlackToolsetsForMaterialization,
|
|
63
63
|
} from "../fly-capabilities-bridge.mjs";
|
|
64
64
|
import { createCustomerEndpointRelay } from "../fly-customer-image/customer-runtime.mjs";
|
|
65
|
+
import { createHermesSessionRetention } from "../fly-customer-image/hermes-runs-client.mjs";
|
|
65
66
|
import {
|
|
66
67
|
claimWithSoulObservation,
|
|
67
68
|
createFlyCustomerControlClient,
|
|
@@ -88,12 +89,12 @@ import {
|
|
|
88
89
|
HERMES_SNAPSHOT_HOOK_ALLOWLIST_BYTES,
|
|
89
90
|
applyHermesSellableHostedPolicy,
|
|
90
91
|
ensureHermesClientRelationshipsCrons,
|
|
91
|
-
ensureHermesRefillSendsCron,
|
|
92
92
|
ensureHermesCustomerProgramCrons,
|
|
93
93
|
ensureHermesOpportunitySequencesCron,
|
|
94
|
+
ensureHermesRefillSendsCron,
|
|
94
95
|
ensureHermesSnapshotCron,
|
|
95
|
-
markHermesSnapshotDirty,
|
|
96
96
|
linkHermesState,
|
|
97
|
+
markHermesSnapshotDirty,
|
|
97
98
|
migrateLegacyHermesUserState,
|
|
98
99
|
quarantineLegacyLightfieldVenv,
|
|
99
100
|
reconcileHermesSnapshot,
|
|
@@ -142,6 +143,7 @@ const PROFILES_ROOT = join(DATA_ROOT, "profiles");
|
|
|
142
143
|
const RUNTIME_ROOT = join(DATA_ROOT, ".sellable-agent", "admin-runtime");
|
|
143
144
|
const DELIVERY_ROOT = join(RUNTIME_ROOT, "delivery");
|
|
144
145
|
const SECRET_ROOT = "/run/sellable-agent/admin-runtime";
|
|
146
|
+
const sessionRetention = createHermesSessionRetention();
|
|
145
147
|
// Turn descriptor + consumed roots for the Product MCP context proxy. They are
|
|
146
148
|
// normally empty and receive one signed descriptor only while the fenced
|
|
147
149
|
// endpoint relay owns a turn; the proxy fails closed if either root is absent.
|
|
@@ -3972,6 +3974,14 @@ async function runAdminProduction() {
|
|
|
3972
3974
|
bytes
|
|
3973
3975
|
),
|
|
3974
3976
|
});
|
|
3977
|
+
void sessionRetention.tick({
|
|
3978
|
+
apiKeyFile: join(SECRET_ROOT, "api-server-key"),
|
|
3979
|
+
statePath: join(
|
|
3980
|
+
activeLaunch.profile.profileHome,
|
|
3981
|
+
".sellable-agent",
|
|
3982
|
+
"session-retention.json"
|
|
3983
|
+
),
|
|
3984
|
+
});
|
|
3975
3985
|
}
|
|
3976
3986
|
await sleep(1_000);
|
|
3977
3987
|
continue;
|
|
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
|
|
8
8
|
|
|
9
9
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.957
|
|
10
10
|
ARG MCP_INTEGRITY=sha512-tIZuO5IEanT/afamDIWdorUNMdqZA6xnjL9jGAmGqYIlIqkXdHB9slWiwfLfmM44iEp7OT98IBHJlvbpMThk6A==
|
|
11
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
11
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.794
|
|
12
12
|
ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.202
|
|
13
13
|
ARG ADMIN_MCP_INTEGRITY=sha512-ym3aye/0vQ8tfULJlKz9JFOxvIQsl1G+n3np+TTgK/tecwNblBo/j3AkTnRqH9iomKzPbZzP+n4tEQsvsU8ByA==
|
|
14
14
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
@@ -94,6 +94,7 @@ import {
|
|
|
94
94
|
} from "./endpoint-uat-completion-gate.mjs";
|
|
95
95
|
import {
|
|
96
96
|
createHermesRunsClient,
|
|
97
|
+
createHermesSessionRetention,
|
|
97
98
|
HERMES_RUNS_FIXTURE_IDENTITY,
|
|
98
99
|
validateEndpointModelSelection,
|
|
99
100
|
validateOpenerOutputContract,
|
|
@@ -106,6 +107,7 @@ import {
|
|
|
106
107
|
const DATA_ROOT = "/opt/data";
|
|
107
108
|
const PROFILES_ROOT = join(DATA_ROOT, "profiles");
|
|
108
109
|
const SECRET_ROOT = "/run/sellable-agent";
|
|
110
|
+
const sessionRetention = createHermesSessionRetention();
|
|
109
111
|
const TRUST_ROOT = "/usr/local/share/sellable-agent/mcp-turn-trust-root.pem";
|
|
110
112
|
const HERMES = "/opt/hermes/.venv/bin/hermes";
|
|
111
113
|
const HERMES_PYTHON = "/opt/hermes/.venv/bin/python3";
|
|
@@ -124,7 +126,7 @@ const RUNTIME_GENERATION = Number(
|
|
|
124
126
|
const BOOT_SESSION_ID = randomUUID();
|
|
125
127
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.21.5";
|
|
126
128
|
const INSTALLER_PACKAGE =
|
|
127
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.
|
|
129
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.794";
|
|
128
130
|
const MCP_PACKAGE =
|
|
129
131
|
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.957";
|
|
130
132
|
export const ENDPOINT_MAX_CONCURRENT_RUNS = 10;
|
|
@@ -1394,6 +1396,25 @@ export function endpointProductProjection(productExecution, output) {
|
|
|
1394
1396
|
|
|
1395
1397
|
export function endpointOpenerOutputContract(claim) {
|
|
1396
1398
|
if (!usesOpenerDraftStrategy(claim)) return undefined;
|
|
1399
|
+
return openerOutputContractFor(claim);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// A single-run opener on a caller-named model (Opus on OpenRouter) is held to
|
|
1403
|
+
// the same strict schema, which the bridge sends as the provider's json_schema
|
|
1404
|
+
// so the model cannot return malformed JSON. The machine's default model has
|
|
1405
|
+
// no strict transport, so it gets no contract.
|
|
1406
|
+
export function endpointStrictOutputContract(claim, modelSelection) {
|
|
1407
|
+
if (
|
|
1408
|
+
!modelSelection ||
|
|
1409
|
+
usesOpenerDraftStrategy(claim) ||
|
|
1410
|
+
claim?.run?.productExecution?.workKind !== "OPENER" ||
|
|
1411
|
+
!claim.run.productExecution.providerOutputSchema
|
|
1412
|
+
)
|
|
1413
|
+
return undefined;
|
|
1414
|
+
return openerOutputContractFor(claim);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function openerOutputContractFor(claim) {
|
|
1397
1418
|
const execution = claim.run.productExecution;
|
|
1398
1419
|
if (
|
|
1399
1420
|
execution.nativeProposalSchemaVersion !==
|
|
@@ -2153,6 +2174,12 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
2153
2174
|
correlation: `${claim.attempt.conversationId}:${claim.attempt.runId}:${claim.attempt.id}`,
|
|
2154
2175
|
...(modelSelection ? { modelSelection } : {}),
|
|
2155
2176
|
};
|
|
2177
|
+
const strictOutputContract = endpointStrictOutputContract(
|
|
2178
|
+
claim,
|
|
2179
|
+
modelSelection
|
|
2180
|
+
);
|
|
2181
|
+
if (strictOutputContract)
|
|
2182
|
+
submitRequest.outputContract = strictOutputContract;
|
|
2156
2183
|
while (!submitted) {
|
|
2157
2184
|
try {
|
|
2158
2185
|
submitted = await runs.submit(submitRequest, {
|
|
@@ -4236,6 +4263,18 @@ async function main() {
|
|
|
4236
4263
|
bytes
|
|
4237
4264
|
),
|
|
4238
4265
|
});
|
|
4266
|
+
void sessionRetention.tick({
|
|
4267
|
+
apiKeyFile: join(
|
|
4268
|
+
SECRET_ROOT,
|
|
4269
|
+
activeRuntime.desired.profileId,
|
|
4270
|
+
"api-server-key"
|
|
4271
|
+
),
|
|
4272
|
+
statePath: join(
|
|
4273
|
+
activeRuntime.profileRoot,
|
|
4274
|
+
".sellable-agent",
|
|
4275
|
+
"session-retention.json"
|
|
4276
|
+
),
|
|
4277
|
+
});
|
|
4239
4278
|
}
|
|
4240
4279
|
if (
|
|
4241
4280
|
activeRuntime?.action !== "INSTANTIATE" &&
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { dirname, isAbsolute } from "node:path";
|
|
4
10
|
|
|
5
11
|
const LOOPBACK_ORIGIN = "http://127.0.0.1:8642";
|
|
6
12
|
const RUN_ID = /^run_[a-f0-9]{32}$/;
|
|
@@ -265,7 +271,8 @@ function compileCreateBody(input, openerStage = false) {
|
|
|
265
271
|
"input",
|
|
266
272
|
"instructions",
|
|
267
273
|
"correlation",
|
|
268
|
-
|
|
274
|
+
"outputContract",
|
|
275
|
+
...(openerStage ? [] : ["modelSelection"]),
|
|
269
276
|
];
|
|
270
277
|
if (Object.keys(input).some((key) => !allowed.includes(key))) {
|
|
271
278
|
throw new Error("hermes_runs_submit_rejected");
|
|
@@ -291,6 +298,24 @@ function compileCreateBody(input, openerStage = false) {
|
|
|
291
298
|
input.modelSelection === undefined
|
|
292
299
|
? null
|
|
293
300
|
: validateEndpointModelSelection(input.modelSelection);
|
|
301
|
+
// A per-run model's opener is held to the strict output contract by its
|
|
302
|
+
// provider (the bridge sends it as OpenRouter json_schema). Outside opener
|
|
303
|
+
// stages the contract only travels with an allowlisted model selection.
|
|
304
|
+
if (!openerStage && input.outputContract !== undefined && !modelSelection) {
|
|
305
|
+
throw new Error("hermes_runs_submit_rejected");
|
|
306
|
+
}
|
|
307
|
+
const outputContract =
|
|
308
|
+
input.outputContract === undefined
|
|
309
|
+
? null
|
|
310
|
+
: validateOpenerOutputContract(input.outputContract);
|
|
311
|
+
const modelOptions = {
|
|
312
|
+
...(modelSelection === null
|
|
313
|
+
? {}
|
|
314
|
+
: { reasoning_effort: modelSelection.reasoningEffort }),
|
|
315
|
+
...(outputContract === null
|
|
316
|
+
? {}
|
|
317
|
+
: { sellable_opener_output_contract: outputContract }),
|
|
318
|
+
};
|
|
294
319
|
return Object.freeze({
|
|
295
320
|
input: compiledInput,
|
|
296
321
|
instructions,
|
|
@@ -300,20 +325,10 @@ function compileCreateBody(input, openerStage = false) {
|
|
|
300
325
|
// resolves the provider's credential from the machine's own pool.
|
|
301
326
|
...(modelSelection === null
|
|
302
327
|
? {}
|
|
303
|
-
: {
|
|
304
|
-
|
|
305
|
-
model: modelSelection.model,
|
|
306
|
-
model_options: { reasoning_effort: modelSelection.reasoningEffort },
|
|
307
|
-
}),
|
|
308
|
-
...(input.outputContract === undefined
|
|
328
|
+
: { provider: modelSelection.provider, model: modelSelection.model }),
|
|
329
|
+
...(Object.keys(modelOptions).length === 0
|
|
309
330
|
? {}
|
|
310
|
-
: {
|
|
311
|
-
model_options: {
|
|
312
|
-
sellable_opener_output_contract: validateOpenerOutputContract(
|
|
313
|
-
input.outputContract
|
|
314
|
-
),
|
|
315
|
-
},
|
|
316
|
-
}),
|
|
331
|
+
: { model_options: modelOptions }),
|
|
317
332
|
});
|
|
318
333
|
}
|
|
319
334
|
|
|
@@ -887,3 +902,200 @@ export function createHermesRunsClient(options = {}) {
|
|
|
887
902
|
},
|
|
888
903
|
});
|
|
889
904
|
}
|
|
905
|
+
|
|
906
|
+
// Endpoint runs (`api_server`) and cron jobs each leave a Hermes session that
|
|
907
|
+
// nothing reads again: an endpoint attempt gets a fresh session and its result
|
|
908
|
+
// lives in Sellable. Hermes only closes these after 90 idle days and deletes
|
|
909
|
+
// them after another 90, so state.db grows with every run, and so do the
|
|
910
|
+
// backup and the volume. Retention deletes them after a week. It goes through
|
|
911
|
+
// the running gateway's session API because Hermes refuses bulk deletes from a
|
|
912
|
+
// second writer, and it never touches conversations (Slack, Desktop, TUI).
|
|
913
|
+
export const HERMES_AUTOMATION_SESSION_SOURCES = Object.freeze([
|
|
914
|
+
"api_server",
|
|
915
|
+
"cron",
|
|
916
|
+
]);
|
|
917
|
+
export const HERMES_AUTOMATION_SESSION_RETENTION_DAYS = 7;
|
|
918
|
+
const SESSION_PAGE_SIZE = 200;
|
|
919
|
+
const SESSION_RETENTION_MAX_DELETES = 1_000;
|
|
920
|
+
const SESSION_RETENTION_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
921
|
+
const SESSION_RETENTION_RETRY_MS = 60 * 60 * 1000;
|
|
922
|
+
// A runtime that just started is still bringing its gateway up; the first pass
|
|
923
|
+
// waits so it does not fail against an API that is not serving yet.
|
|
924
|
+
const SESSION_RETENTION_START_DELAY_MS = 10 * 60 * 1000;
|
|
925
|
+
|
|
926
|
+
// A bounded reason for the operator log; never a raw error message.
|
|
927
|
+
function sessionRetentionFailureReason(error) {
|
|
928
|
+
const message = String(error?.message ?? "");
|
|
929
|
+
const http =
|
|
930
|
+
/^hermes_session_retention_(list|delete)_http_([1-5][0-9]{2})$/.exec(
|
|
931
|
+
message
|
|
932
|
+
);
|
|
933
|
+
if (http) return `${http[1]}_http_${http[2]}`;
|
|
934
|
+
if (
|
|
935
|
+
message === "hermes_runs_api_key_file_rejected" ||
|
|
936
|
+
error?.code === "ENOENT"
|
|
937
|
+
) {
|
|
938
|
+
return "key_rejected";
|
|
939
|
+
}
|
|
940
|
+
if (error?.name === "TimeoutError" || error?.name === "AbortError") {
|
|
941
|
+
return "timeout";
|
|
942
|
+
}
|
|
943
|
+
return "request_failed";
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function sessionLastActiveSeconds(session) {
|
|
947
|
+
return Math.max(
|
|
948
|
+
...[session.last_active, session.ended_at, session.started_at].filter(
|
|
949
|
+
(value) => typeof value === "number" && Number.isFinite(value)
|
|
950
|
+
),
|
|
951
|
+
0
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/** @param {any} options */
|
|
956
|
+
export async function pruneHermesAutomationSessions({
|
|
957
|
+
apiKeyFile,
|
|
958
|
+
fetchImpl = fetch,
|
|
959
|
+
now = Date.now(),
|
|
960
|
+
retentionDays = HERMES_AUTOMATION_SESSION_RETENTION_DAYS,
|
|
961
|
+
maxDeletes = SESSION_RETENTION_MAX_DELETES,
|
|
962
|
+
}) {
|
|
963
|
+
const apiKey = readApiKey(apiKeyFile);
|
|
964
|
+
const call = (method, path) =>
|
|
965
|
+
fetchImpl(`${LOOPBACK_ORIGIN}${path}`, {
|
|
966
|
+
method,
|
|
967
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
968
|
+
signal: AbortSignal.timeout(20_000),
|
|
969
|
+
});
|
|
970
|
+
const cutoff = now / 1000 - retentionDays * 86_400;
|
|
971
|
+
const stale = new Set();
|
|
972
|
+
for (const source of HERMES_AUTOMATION_SESSION_SOURCES) {
|
|
973
|
+
// Newest activity first, so every page is read; listing stops once one
|
|
974
|
+
// pass worth of stale sessions is known.
|
|
975
|
+
for (
|
|
976
|
+
let offset = 0;
|
|
977
|
+
stale.size <= maxDeletes;
|
|
978
|
+
offset += SESSION_PAGE_SIZE
|
|
979
|
+
) {
|
|
980
|
+
const response = await call(
|
|
981
|
+
"GET",
|
|
982
|
+
`/api/sessions?source=${source}&limit=${SESSION_PAGE_SIZE}&offset=${offset}`
|
|
983
|
+
);
|
|
984
|
+
if (!response.ok) {
|
|
985
|
+
throw new Error(
|
|
986
|
+
`hermes_session_retention_list_http_${response.status}`
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
const page = await response.json();
|
|
990
|
+
const rows = Array.isArray(page?.data) ? page.data : [];
|
|
991
|
+
for (const session of rows) {
|
|
992
|
+
if (
|
|
993
|
+
session?.source === source &&
|
|
994
|
+
typeof session.id === "string" &&
|
|
995
|
+
session.pinned !== true &&
|
|
996
|
+
sessionLastActiveSeconds(session) < cutoff
|
|
997
|
+
) {
|
|
998
|
+
stale.add(session.id);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (page?.has_more !== true || rows.length === 0) break;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
let deleted = 0;
|
|
1005
|
+
for (const id of [...stale].slice(0, maxDeletes)) {
|
|
1006
|
+
const response = await call(
|
|
1007
|
+
"DELETE",
|
|
1008
|
+
`/api/sessions/${encodeURIComponent(id)}`
|
|
1009
|
+
);
|
|
1010
|
+
if (response.ok) deleted += 1;
|
|
1011
|
+
else if (response.status !== 404) {
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
`hermes_session_retention_delete_http_${response.status}`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
ok: true,
|
|
1019
|
+
deleted,
|
|
1020
|
+
remaining: Math.max(0, stale.size - maxDeletes),
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function readRetentionNextRunAt(statePath) {
|
|
1025
|
+
try {
|
|
1026
|
+
const value = JSON.parse(readFileSync(statePath, "utf8"))?.nextRunAt;
|
|
1027
|
+
return Number.isFinite(value) ? value : 0;
|
|
1028
|
+
} catch {
|
|
1029
|
+
return 0;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function writeRetentionNextRunAt(statePath, nextRunAt) {
|
|
1034
|
+
mkdirSync(dirname(statePath), { recursive: true, mode: 0o700 });
|
|
1035
|
+
const temporary = `${statePath}.${process.pid}.tmp`;
|
|
1036
|
+
writeFileSync(temporary, `${JSON.stringify({ nextRunAt })}\n`, {
|
|
1037
|
+
mode: 0o600,
|
|
1038
|
+
});
|
|
1039
|
+
renameSync(temporary, statePath);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Daily, single-flight session retention for a runtime's idle loop. `tick`
|
|
1044
|
+
* returns the running pass (or null when not due) and never rejects, so the
|
|
1045
|
+
* loop can fire it without waiting.
|
|
1046
|
+
* @param {any} options
|
|
1047
|
+
*/
|
|
1048
|
+
export function createHermesSessionRetention({
|
|
1049
|
+
fetchImpl = fetch,
|
|
1050
|
+
now = () => Date.now(),
|
|
1051
|
+
maxDeletes = SESSION_RETENTION_MAX_DELETES,
|
|
1052
|
+
startDelayMs = SESSION_RETENTION_START_DELAY_MS,
|
|
1053
|
+
log = (entry) => process.stderr.write(`${JSON.stringify(entry)}\n`),
|
|
1054
|
+
} = {}) {
|
|
1055
|
+
let running = null;
|
|
1056
|
+
const startedAt = now();
|
|
1057
|
+
return {
|
|
1058
|
+
tick({ apiKeyFile, statePath }) {
|
|
1059
|
+
if (running) return running;
|
|
1060
|
+
if (now() < startedAt + startDelayMs) return null;
|
|
1061
|
+
if (now() < readRetentionNextRunAt(statePath)) return null;
|
|
1062
|
+
running = (async () => {
|
|
1063
|
+
let nextRunAt;
|
|
1064
|
+
try {
|
|
1065
|
+
const result = await pruneHermesAutomationSessions({
|
|
1066
|
+
apiKeyFile,
|
|
1067
|
+
fetchImpl,
|
|
1068
|
+
now: now(),
|
|
1069
|
+
maxDeletes,
|
|
1070
|
+
});
|
|
1071
|
+
log({
|
|
1072
|
+
ok: true,
|
|
1073
|
+
code: "hermes_session_retention_pruned",
|
|
1074
|
+
deleted: result.deleted,
|
|
1075
|
+
remaining: result.remaining,
|
|
1076
|
+
});
|
|
1077
|
+
nextRunAt =
|
|
1078
|
+
now() +
|
|
1079
|
+
(result.remaining > 0
|
|
1080
|
+
? SESSION_RETENTION_RETRY_MS
|
|
1081
|
+
: SESSION_RETENTION_INTERVAL_MS);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
log({
|
|
1084
|
+
ok: false,
|
|
1085
|
+
code: "hermes_session_retention_failed",
|
|
1086
|
+
reason: sessionRetentionFailureReason(error),
|
|
1087
|
+
});
|
|
1088
|
+
nextRunAt = now() + SESSION_RETENTION_RETRY_MS;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
writeRetentionNextRunAt(statePath, nextRunAt);
|
|
1092
|
+
} catch {
|
|
1093
|
+
// The next tick simply runs again.
|
|
1094
|
+
}
|
|
1095
|
+
})().finally(() => {
|
|
1096
|
+
running = null;
|
|
1097
|
+
});
|
|
1098
|
+
return running;
|
|
1099
|
+
},
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
@@ -1244,13 +1244,16 @@ def _sellable_opener_output_format(model_options):
|
|
|
1244
1244
|
reject()
|
|
1245
1245
|
return {"type": "json_schema", "name": "sellable_opener_proposal", "strict": True, "schema": schema}
|
|
1246
1246
|
|
|
1247
|
+
def _sellable_opener_json_schema(output_format):
|
|
1248
|
+
# Chat-completions spelling of the same strict format (OpenRouter response_format).
|
|
1249
|
+
return {"type": "json_schema", "json_schema": {"name": output_format["name"], "strict": True, "schema": output_format["schema"]}}
|
|
1250
|
+
|
|
1247
1251
|
def _sellable_opener_stage_kwargs(session_id, agent_kwargs, model_options=None):
|
|
1248
1252
|
import os
|
|
1249
1253
|
import re
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
)
|
|
1254
|
+
runtime = os.getenv("SELLABLE_AGENT_RUNTIME", "") == "1"
|
|
1255
|
+
stage = runtime and re.fullmatch(r"sellable_opener_stage_[a-f0-9]{32}", str(session_id or "")) is not None
|
|
1256
|
+
endpoint = runtime and re.fullmatch(r"sellable_endpoint_[a-f0-9]{32}", str(session_id or "")) is not None
|
|
1254
1257
|
if stage:
|
|
1255
1258
|
agent_kwargs.update(
|
|
1256
1259
|
enabled_toolsets=[], skip_memory=True, skip_background_review=True,
|
|
@@ -1265,6 +1268,17 @@ def _sellable_opener_stage_kwargs(session_id, agent_kwargs, model_options=None):
|
|
|
1265
1268
|
extra_body["text"] = {"format": output_format}
|
|
1266
1269
|
overrides["extra_body"] = extra_body
|
|
1267
1270
|
agent_kwargs.update(request_overrides=overrides, fallback_model=None)
|
|
1271
|
+
elif endpoint and _sellable_opener_output_format(model_options) is not None:
|
|
1272
|
+
# A per-run model's endpoint session keeps its tools; only its output
|
|
1273
|
+
# is held to the strict schema, routed to providers that enforce it.
|
|
1274
|
+
output_format = _sellable_opener_output_format(model_options)
|
|
1275
|
+
overrides = dict(agent_kwargs.get("request_overrides") or {})
|
|
1276
|
+
extra_body = dict(overrides.get("extra_body") or {})
|
|
1277
|
+
if "text" in overrides or "text" in extra_body or "response_format" in overrides or "response_format" in extra_body:
|
|
1278
|
+
raise RuntimeError("sellable_opener_output_format_conflict")
|
|
1279
|
+
extra_body["response_format"] = _sellable_opener_json_schema(output_format)
|
|
1280
|
+
overrides["extra_body"] = extra_body
|
|
1281
|
+
agent_kwargs.update(request_overrides=overrides, fallback_model=None, provider_require_parameters=True)
|
|
1268
1282
|
elif isinstance(model_options, dict) and "sellable_opener_output_contract" in model_options:
|
|
1269
1283
|
raise RuntimeError("sellable_opener_output_contract_session_rejected")
|
|
1270
1284
|
return stage
|
|
@@ -1272,20 +1286,27 @@ def _sellable_opener_stage_kwargs(session_id, agent_kwargs, model_options=None):
|
|
|
1272
1286
|
def _sellable_assert_opener_stage_tools(stage, agent, model_options=None):
|
|
1273
1287
|
if stage and (agent.tools or agent.valid_tool_names):
|
|
1274
1288
|
raise RuntimeError("sellable_opener_stage_tools_not_empty")
|
|
1275
|
-
output_format = _sellable_opener_output_format(model_options)
|
|
1289
|
+
output_format = _sellable_opener_output_format(model_options)
|
|
1276
1290
|
if output_format is None:
|
|
1277
1291
|
return
|
|
1278
|
-
if agent.api_mode != "codex_responses" or agent.provider != "openai-codex" or agent._fallback_chain:
|
|
1279
|
-
raise RuntimeError("sellable_opener_output_format_unsupported")
|
|
1280
1292
|
import copy
|
|
1281
|
-
|
|
1293
|
+
if stage:
|
|
1294
|
+
if agent.api_mode != "codex_responses" or agent.provider != "openai-codex" or agent._fallback_chain:
|
|
1295
|
+
raise RuntimeError("sellable_opener_output_format_unsupported")
|
|
1296
|
+
expected_key, expected_value = "text", {"format": copy.deepcopy(output_format)}
|
|
1297
|
+
else:
|
|
1298
|
+
if agent.api_mode != "chat_completions" or agent.provider != "openrouter" or agent._fallback_chain:
|
|
1299
|
+
raise RuntimeError("sellable_opener_output_format_unsupported")
|
|
1300
|
+
expected_key, expected_value = "response_format", _sellable_opener_json_schema(copy.deepcopy(output_format))
|
|
1282
1301
|
selected = (agent.model, agent.provider, agent.api_mode)
|
|
1283
1302
|
build_api_kwargs = agent._build_api_kwargs
|
|
1284
1303
|
def strict_build_api_kwargs(*args, **kwargs):
|
|
1285
1304
|
built = build_api_kwargs(*args, **kwargs)
|
|
1305
|
+
extra_body = built.get("extra_body") or {}
|
|
1286
1306
|
if (
|
|
1287
1307
|
(agent.model, agent.provider, agent.api_mode) != selected or agent._fallback_chain
|
|
1288
|
-
or
|
|
1308
|
+
or extra_body.get(expected_key) != expected_value
|
|
1309
|
+
or (not stage and (extra_body.get("provider") or {}).get("require_parameters") is not True)
|
|
1289
1310
|
):
|
|
1290
1311
|
raise RuntimeError("sellable_opener_output_format_not_enforced")
|
|
1291
1312
|
return built
|
|
@@ -2033,11 +2054,11 @@ def getenv(name, default=""):
|
|
|
2033
2054
|
" prerun_script = None",
|
|
2034
2055
|
' script_path = job.get("script")',
|
|
2035
2056
|
" if (",
|
|
2036
|
-
|
|
2057
|
+
' str(job.get("name") or "") in _SELLABLE_MANAGED_CUSTOMER_PROGRAM_JOBS',
|
|
2037
2058
|
" and not script_path",
|
|
2038
2059
|
" ):",
|
|
2039
2060
|
" return _sellable_agent_managed_gate_block(",
|
|
2040
|
-
|
|
2061
|
+
' job_id, job_name, "managed wake gate script is missing"',
|
|
2041
2062
|
" )",
|
|
2042
2063
|
" if script_path:",
|
|
2043
2064
|
].join("\n"),
|
|
@@ -2065,9 +2086,9 @@ def getenv(name, default=""):
|
|
|
2065
2086
|
" cancel_event=cancel_event,",
|
|
2066
2087
|
" )",
|
|
2067
2088
|
" except Exception as _managed_launch_exc:",
|
|
2068
|
-
|
|
2089
|
+
' if str(job.get("name") or "") in _SELLABLE_MANAGED_CUSTOMER_PROGRAM_JOBS:',
|
|
2069
2090
|
" return _sellable_agent_managed_gate_block(",
|
|
2070
|
-
|
|
2091
|
+
' job_id, job_name, f"managed wake gate could not start: {_managed_launch_exc}"',
|
|
2071
2092
|
" )",
|
|
2072
2093
|
" raise",
|
|
2073
2094
|
].join("\n"),
|
|
@@ -2082,9 +2103,9 @@ def getenv(name, default=""):
|
|
|
2082
2103
|
" try:",
|
|
2083
2104
|
" prerun_script = _run_job_script_with_claim_heartbeat(job, script_path, cancel_event=cancel_event)",
|
|
2084
2105
|
" except Exception as _managed_launch_exc:",
|
|
2085
|
-
|
|
2106
|
+
' if str(job.get("name") or "") in _SELLABLE_MANAGED_CUSTOMER_PROGRAM_JOBS:',
|
|
2086
2107
|
" return _sellable_agent_managed_gate_block(",
|
|
2087
|
-
|
|
2108
|
+
' job_id, job_name, f"managed wake gate could not start: {_managed_launch_exc}"',
|
|
2088
2109
|
" )",
|
|
2089
2110
|
" raise",
|
|
2090
2111
|
].join("\n"),
|
|
@@ -2542,231 +2563,298 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
2542
2563
|
});
|
|
2543
2564
|
|
|
2544
2565
|
export const HERMES_AGENT_BRIDGE_DEDICATED_V021_CONTRACT = normalizeContract({
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2566
|
+
bridgeId: "sellable-agent-hermes-bridge/v1",
|
|
2567
|
+
hermesVersion: "0.21.1",
|
|
2568
|
+
versionFile: {
|
|
2569
|
+
path: "hermes_cli/__init__.py",
|
|
2570
|
+
sha256: "efae023ac37622277aac298ac15c646b855e870132c38970feeda9552f1ecc27",
|
|
2550
2571
|
},
|
|
2551
|
-
|
|
2572
|
+
files: [
|
|
2552
2573
|
{
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2574
|
+
path: "gateway/platforms/api_server.py",
|
|
2575
|
+
patchKind: "api-opener-stage-v021",
|
|
2576
|
+
sourceSha256:
|
|
2577
|
+
"221589ae8c45349736bb291b4679b86e9e12cb22f5a33220ae3d22929fea8297",
|
|
2578
|
+
patchedSha256:
|
|
2579
|
+
"b029a26050aa777a520ab136c203f3755b178e04af11ca6e9f810046a9eae6d4",
|
|
2557
2580
|
},
|
|
2558
2581
|
{
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2582
|
+
path: "gateway/config_env.py",
|
|
2583
|
+
patchKind: "gateway-config-dedicated-v021",
|
|
2584
|
+
sourceSha256:
|
|
2585
|
+
"984ae7b68befecd52fc17b6d38e763f40c7040a2ba69ed4a2c39f1a88ae6734a",
|
|
2586
|
+
patchedSha256:
|
|
2587
|
+
"0180fbcb1df6e5a7651f172c492a8e317f7455c8ed0e955c7a4f0488fa23b6c2",
|
|
2563
2588
|
},
|
|
2564
2589
|
{
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2590
|
+
path: "plugins/platforms/slack/adapter.py",
|
|
2591
|
+
patchKind: "slack-adapter-dedicated-v021",
|
|
2592
|
+
sourceSha256:
|
|
2593
|
+
"23048a107304993686250af044256199be2f73171bb5b4cca6a900ea272a6064",
|
|
2594
|
+
patchedSha256:
|
|
2595
|
+
"bc05fd5f227e42f733ee78c80ffab6c7c53840975fc5bc9755aa2ef8f2c8ff47",
|
|
2569
2596
|
},
|
|
2570
2597
|
{
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2598
|
+
path: "tools/mcp_tool_handlers.py",
|
|
2599
|
+
patchKind: "mcp-tool-endpoint-v021",
|
|
2600
|
+
sourceSha256:
|
|
2601
|
+
"e6af250f0fbfeb12a936c340a46f40f8d40332ed8bbc4eb63fded66e37aaa7bf",
|
|
2602
|
+
patchedSha256:
|
|
2603
|
+
"6c4765aad3074b6f8ad6951299edafa36cf536c9a9f0b57a48bdd541e3209790",
|
|
2575
2604
|
},
|
|
2576
2605
|
{
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2606
|
+
path: "gateway/run.py",
|
|
2607
|
+
patchKind: "gateway-session-v021",
|
|
2608
|
+
sourceSha256:
|
|
2609
|
+
"3b1f670243d4932ac1f2a2a68bee61a8c7f9bcdac7bd48d43289db90f17bab67",
|
|
2610
|
+
patchedSha256:
|
|
2611
|
+
"2e25d50add6c49748b2cdd4c2bd115039e72c0b4eeb9c882eb1cd07a2a25c304",
|
|
2581
2612
|
},
|
|
2582
2613
|
{
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2614
|
+
path: "gateway/run_turn_runner.py",
|
|
2615
|
+
patchKind: "gateway-provider-preflight-v021",
|
|
2616
|
+
sourceSha256:
|
|
2617
|
+
"8ebd74318db2259ac52c35dbf9568b98dbb19c1d2e30289f4efc76a12ac8ceeb",
|
|
2618
|
+
patchedSha256:
|
|
2619
|
+
"26a13f167835036c37ff92da3c1d036267544cf8a6529f5d40c3ee0ae58fa1cb",
|
|
2587
2620
|
},
|
|
2588
2621
|
{
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2622
|
+
path: "agent/turn_context.py",
|
|
2623
|
+
patchKind: "turn-context-v021",
|
|
2624
|
+
sourceSha256:
|
|
2625
|
+
"d1a84a24c5b50a7a866268fde453694c6b98790e31d7d4310d8756027149863a",
|
|
2626
|
+
patchedSha256:
|
|
2627
|
+
"13517e034866224c072a860dbd9fbb7eb25c9d3cb86a1f59ae3a2e5ec1cef7ad",
|
|
2593
2628
|
},
|
|
2594
2629
|
{
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2630
|
+
path: "cron/scheduler.py",
|
|
2631
|
+
patchKind: "cron-scheduler-v020",
|
|
2632
|
+
sourceSha256:
|
|
2633
|
+
"f1891e94697ccf5f7b66ae7788345368b452af447fbc43356ae5376a3c753518",
|
|
2634
|
+
patchedSha256:
|
|
2635
|
+
"03d0848e584b7cbc5b126e0fae989dbd5d3d83ccee3ad989c08ffc8f8ef423b9",
|
|
2599
2636
|
},
|
|
2600
2637
|
{
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2638
|
+
path: "tools/skills_tool.py",
|
|
2639
|
+
patchKind: "skills-cache-freshness-v021",
|
|
2640
|
+
sourceSha256:
|
|
2641
|
+
"0f205b8c2d9266d272d8c6cae3d189b86a5aebce98f03b61605d266b65f9497c",
|
|
2642
|
+
patchedSha256:
|
|
2643
|
+
"8696b37ef58c0cc570c437abac730ac0e670e44365ddeae452ad40a501f13a1b",
|
|
2605
2644
|
},
|
|
2606
2645
|
{
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2646
|
+
path: "agent/prompt_builder.py",
|
|
2647
|
+
patchKind: "skills-prompt-freshness-v021",
|
|
2648
|
+
sourceSha256:
|
|
2649
|
+
"83160fab72772012bc308bc730a0085e7acb126f1bea49d62f990505f00ce677",
|
|
2650
|
+
patchedSha256:
|
|
2651
|
+
"6ab2aa30e06b124333d28ed73fc7b56394fbc71f040f3387f6fbc835d799f266",
|
|
2652
|
+
},
|
|
2612
2653
|
],
|
|
2613
|
-
|
|
2654
|
+
contractDigest:
|
|
2655
|
+
"a8520b6decd64b43de1074005c8eda2d9be4a451f0f9a43b1777659acd5a86b6",
|
|
2614
2656
|
});
|
|
2615
2657
|
|
|
2616
2658
|
export const HERMES_AGENT_BRIDGE_DEDICATED_V0213_CONTRACT = normalizeContract({
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2659
|
+
bridgeId: "sellable-agent-hermes-bridge/v1",
|
|
2660
|
+
hermesVersion: "0.21.3",
|
|
2661
|
+
versionFile: {
|
|
2662
|
+
path: "hermes_cli/__init__.py",
|
|
2663
|
+
sha256: "0d78a58a9f27f32adfdac959e89767cecde93a424bcfbd39d64fb95f6cf13e6c",
|
|
2622
2664
|
},
|
|
2623
|
-
|
|
2665
|
+
files: [
|
|
2624
2666
|
{
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2667
|
+
path: "gateway/config_env.py",
|
|
2668
|
+
patchKind: "gateway-config-dedicated-v021",
|
|
2669
|
+
sourceSha256:
|
|
2670
|
+
"40bb1029e482ebe423a5a6ddc9175ef96f821f89e9e040b5cf62cdd7ec903e4c",
|
|
2671
|
+
patchedSha256:
|
|
2672
|
+
"c189faaa2ab7c0ea603b44eb3e6b37dcf847a83b12f603ca185f37ba0ca582fc",
|
|
2629
2673
|
},
|
|
2630
2674
|
{
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2675
|
+
path: "plugins/platforms/slack/adapter.py",
|
|
2676
|
+
patchKind: "slack-adapter-dedicated-v0213",
|
|
2677
|
+
sourceSha256:
|
|
2678
|
+
"12bb80dffde14eca299f6f0fa57907fbe1636519402c08dc07d96d1c144e5f83",
|
|
2679
|
+
patchedSha256:
|
|
2680
|
+
"da13c90b8a81c32b57909e5d82f9e3500d14b290cf9a38ffd6dd53e77785e038",
|
|
2635
2681
|
},
|
|
2636
2682
|
{
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2683
|
+
path: "tools/mcp_tool_handlers.py",
|
|
2684
|
+
patchKind: "mcp-tool-endpoint-v021",
|
|
2685
|
+
sourceSha256:
|
|
2686
|
+
"c4713aa289c7ca17b53c0ae53ceb6a276321f6d36e259084974e8b5738b6a4dd",
|
|
2687
|
+
patchedSha256:
|
|
2688
|
+
"1bd801ebf1c6e17a0d81899b2ee0401f038ae4d1a493552f021a1b66fccf0e98",
|
|
2641
2689
|
},
|
|
2642
2690
|
{
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2691
|
+
path: "gateway/run.py",
|
|
2692
|
+
patchKind: "gateway-session-v021",
|
|
2693
|
+
sourceSha256:
|
|
2694
|
+
"ab619aa0dc193c6d8741f3cabc9a028c2fb7459c22262c69597b0e872091bde5",
|
|
2695
|
+
patchedSha256:
|
|
2696
|
+
"50fc00c3fa236c201d4f4e0831ec728d87ee7ad16b0865c69242304a5de65a23",
|
|
2647
2697
|
},
|
|
2648
2698
|
{
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2699
|
+
path: "gateway/run_turn.py",
|
|
2700
|
+
patchKind: "gateway-first-contact-v0213",
|
|
2701
|
+
sourceSha256:
|
|
2702
|
+
"56e6fb62b60e954ef0d258c7c44ed3fca99c4aa6d2c502d8f80a1c516569f537",
|
|
2703
|
+
patchedSha256:
|
|
2704
|
+
"d66e2aecd00ebb1c6f30a1ecc522b1f408d4a8ff1413b63516752e17ea0e55a1",
|
|
2653
2705
|
},
|
|
2654
2706
|
{
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2707
|
+
path: "gateway/run_turn_runner.py",
|
|
2708
|
+
patchKind: "gateway-provider-preflight-v021",
|
|
2709
|
+
sourceSha256:
|
|
2710
|
+
"b87d6072f2cc13634398d380eb0857d550f75502a8ace137138cf16e00d9a763",
|
|
2711
|
+
patchedSha256:
|
|
2712
|
+
"0113b3df868e73c7f06bacf0c5cec916077e21973e3c1b03b6778c3b91e92b8b",
|
|
2659
2713
|
},
|
|
2660
2714
|
{
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2715
|
+
path: "agent/turn_context.py",
|
|
2716
|
+
patchKind: "turn-context-v021",
|
|
2717
|
+
sourceSha256:
|
|
2718
|
+
"46ed030ee35a13f75994bf8e28cde3633db14969d1fb5c859fc0ff6964cbb0bd",
|
|
2719
|
+
patchedSha256:
|
|
2720
|
+
"facc6ff45a479766b0c0fe719d96fd6867ee25c40e638d5e82499cc97bd72e04",
|
|
2665
2721
|
},
|
|
2666
2722
|
{
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2723
|
+
path: "cron/scheduler.py",
|
|
2724
|
+
patchKind: "cron-scheduler-v020",
|
|
2725
|
+
sourceSha256:
|
|
2726
|
+
"df7b82c50485cece408264f3cd44dc910b6b2a7d319b6d138bd42e2cf7ba2e86",
|
|
2727
|
+
patchedSha256:
|
|
2728
|
+
"ea8b4df9d12dbd82c59044d401de5e99ed466657ac873f018ac5d8d741893b2c",
|
|
2671
2729
|
},
|
|
2672
2730
|
{
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2731
|
+
path: "tools/skills_tool.py",
|
|
2732
|
+
patchKind: "skills-cache-freshness-v021",
|
|
2733
|
+
sourceSha256:
|
|
2734
|
+
"1bec6a244ac61603b1adeb5599149a4097b68263458f63eafe55836415e5f3a0",
|
|
2735
|
+
patchedSha256:
|
|
2736
|
+
"168bad88d45af80328c38296be3f1c9b3509f680a7939735836feac94328843e",
|
|
2677
2737
|
},
|
|
2678
2738
|
{
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2739
|
+
path: "agent/prompt_builder.py",
|
|
2740
|
+
patchKind: "skills-prompt-freshness-v021",
|
|
2741
|
+
sourceSha256:
|
|
2742
|
+
"586ea363fa1e70bb0fdd5426af40758976a16c54f07efeb7a1b4f3fe0ad99309",
|
|
2743
|
+
patchedSha256:
|
|
2744
|
+
"769a0662a396bcb0ffb82e14cacd03b2b864526e68c1cde601f68ccea61e2836",
|
|
2683
2745
|
},
|
|
2684
2746
|
{
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2747
|
+
path: "gateway/platforms/api_server.py",
|
|
2748
|
+
patchKind: "api-opener-stage-v021",
|
|
2749
|
+
sourceSha256:
|
|
2750
|
+
"7ec405dc2a59789582ad9f61a189a41079e621f14642d3c9dc754e312616a868",
|
|
2751
|
+
patchedSha256:
|
|
2752
|
+
"1423ea98ec142e9b0dc5ba423dadfe9cf192c39c0565cf58cafc374805725052",
|
|
2753
|
+
},
|
|
2690
2754
|
],
|
|
2691
|
-
|
|
2755
|
+
contractDigest:
|
|
2756
|
+
"75b5d16081dd2fdc5795d3b86d341f2ff341bf3c599e4c57de5ec14b3dd63ce0",
|
|
2692
2757
|
});
|
|
2693
2758
|
|
|
2694
2759
|
export const HERMES_AGENT_BRIDGE_DEDICATED_V0215_CONTRACT = normalizeContract({
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2760
|
+
bridgeId: "sellable-agent-hermes-bridge/v1",
|
|
2761
|
+
hermesVersion: "0.21.5",
|
|
2762
|
+
versionFile: {
|
|
2763
|
+
path: "hermes_cli/__init__.py",
|
|
2764
|
+
sha256: "d05c98cd44e22781497a20cd968af0d9b8962406b909f1889168c624545b229e",
|
|
2700
2765
|
},
|
|
2701
|
-
|
|
2766
|
+
files: [
|
|
2702
2767
|
{
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2768
|
+
path: "gateway/config_env.py",
|
|
2769
|
+
patchKind: "gateway-config-dedicated-v021",
|
|
2770
|
+
sourceSha256:
|
|
2771
|
+
"5661d9cfb7d798d6c995b03727bdd4018669ae12c96d1e7a8f3c99a3ea85acc7",
|
|
2772
|
+
patchedSha256:
|
|
2773
|
+
"cd8efa414e0313bca77d0d8093acc505d9f345b8fb48d734821e66027545bf57",
|
|
2707
2774
|
},
|
|
2708
2775
|
{
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2776
|
+
path: "plugins/platforms/slack/adapter.py",
|
|
2777
|
+
patchKind: "slack-adapter-dedicated-v0213",
|
|
2778
|
+
sourceSha256:
|
|
2779
|
+
"3f2ec0cef97964bf4cb1e9791c0917a03cd1e73bb0bc3293768680cd6302f613",
|
|
2780
|
+
patchedSha256:
|
|
2781
|
+
"26cedfad352e8dadae82bebcedf8813ef948622a350b392ce99097a8ede2f056",
|
|
2713
2782
|
},
|
|
2714
2783
|
{
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2784
|
+
path: "tools/mcp_tool_handlers.py",
|
|
2785
|
+
patchKind: "mcp-tool-endpoint-v021",
|
|
2786
|
+
sourceSha256:
|
|
2787
|
+
"d1b4463309a2de351aaf6c7cd8f7a2a1e83f5e26dbe058371c9eeb9d0c567903",
|
|
2788
|
+
patchedSha256:
|
|
2789
|
+
"61ceab8c24aef50e88d6c1dad1bbf80daed66f8950b05fb642525a62a0bb789c",
|
|
2719
2790
|
},
|
|
2720
2791
|
{
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2792
|
+
path: "gateway/run.py",
|
|
2793
|
+
patchKind: "gateway-session-v021",
|
|
2794
|
+
sourceSha256:
|
|
2795
|
+
"324f7095223816b865dd68a75dc83bfd1c3d101ac8c1c38dba69026dab2b6af1",
|
|
2796
|
+
patchedSha256:
|
|
2797
|
+
"4b398b8c033e5aaba90b6ba312baf01e7d88d46a91843e64949ade0b65f2ade8",
|
|
2725
2798
|
},
|
|
2726
2799
|
{
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2800
|
+
path: "gateway/run_turn.py",
|
|
2801
|
+
patchKind: "gateway-first-contact-v0213",
|
|
2802
|
+
sourceSha256:
|
|
2803
|
+
"5bdee5c82c00e02f664516f9da0af99674e23493ec2a30eff06cb03afc6718a0",
|
|
2804
|
+
patchedSha256:
|
|
2805
|
+
"0729145f27559df970c16d5be11e01ebce657874e4be1d9e151562042f5208c0",
|
|
2731
2806
|
},
|
|
2732
2807
|
{
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2808
|
+
path: "gateway/run_turn_runner.py",
|
|
2809
|
+
patchKind: "gateway-provider-preflight-v021",
|
|
2810
|
+
sourceSha256:
|
|
2811
|
+
"216bdca083b7d2bfae481d07bd0791a76834674b3614fd4ee0aafac5c58f30db",
|
|
2812
|
+
patchedSha256:
|
|
2813
|
+
"7b6beabeed7b78781d142dcdb0eff4c0110263ee173f7696737c4c965e1c72ab",
|
|
2737
2814
|
},
|
|
2738
2815
|
{
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2816
|
+
path: "agent/turn_context.py",
|
|
2817
|
+
patchKind: "turn-context-v021",
|
|
2818
|
+
sourceSha256:
|
|
2819
|
+
"85f857dc3c317366d918516bc8b37a022f1020b34da0c3966a31bd289b2942e1",
|
|
2820
|
+
patchedSha256:
|
|
2821
|
+
"a6487c43bc2d21f40e3a86fe31a9a0c2e4e603fff813f7c8429bbf5c22591648",
|
|
2743
2822
|
},
|
|
2744
2823
|
{
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2824
|
+
path: "cron/scheduler.py",
|
|
2825
|
+
patchKind: "cron-scheduler-v020",
|
|
2826
|
+
sourceSha256:
|
|
2827
|
+
"dcf0e6794a220e478584a21f786b8451b915fc7397d38eda357af1363249fa2a",
|
|
2828
|
+
patchedSha256:
|
|
2829
|
+
"e98d197ce3d0b414184e3c746fe3814c213407da68ce0a46a8b4dd63a9054a11",
|
|
2749
2830
|
},
|
|
2750
2831
|
{
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2832
|
+
path: "tools/skills_tool.py",
|
|
2833
|
+
patchKind: "skills-cache-freshness-v021",
|
|
2834
|
+
sourceSha256:
|
|
2835
|
+
"0d47bda41ed9d1ec84856df3d336a42bb94161e89bd9a8b857e76ec76be9f7b7",
|
|
2836
|
+
patchedSha256:
|
|
2837
|
+
"1bdc17603f3bb9fb34c5d04099dee2ca7d631eedc58b523bcd74d6e946dc9eb7",
|
|
2755
2838
|
},
|
|
2756
2839
|
{
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2840
|
+
path: "agent/prompt_builder.py",
|
|
2841
|
+
patchKind: "skills-prompt-freshness-v0215",
|
|
2842
|
+
sourceSha256:
|
|
2843
|
+
"64bc77a26641754b8c5ca76fc876687d3c9e81aad7816c2e76ce251a41121526",
|
|
2844
|
+
patchedSha256:
|
|
2845
|
+
"56edefd92c50f38a3fed8d56b6fc02352f8e5892b43553ab277e3113606d25fb",
|
|
2761
2846
|
},
|
|
2762
2847
|
{
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2848
|
+
path: "gateway/platforms/api_server.py",
|
|
2849
|
+
patchKind: "api-opener-stage-v021",
|
|
2850
|
+
sourceSha256:
|
|
2851
|
+
"fa83a20bd4f9f3a90a3ac010db0a68f548259587946ea80b81b2f3253bafd5ec",
|
|
2852
|
+
patchedSha256:
|
|
2853
|
+
"dbe6e400c7c7a9d3b377b7f9f08e05f2e9f6b622f9019f873e4fdb0cd828b19f",
|
|
2854
|
+
},
|
|
2768
2855
|
],
|
|
2769
|
-
|
|
2856
|
+
contractDigest:
|
|
2857
|
+
"d8b2ee47591435091a81a3e9913ba5acace2f9c1418cf75c0e23b4d0632e4024",
|
|
2770
2858
|
});
|
|
2771
2859
|
|
|
2772
2860
|
function assertRelativeContractPath(path) {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
readdirSync,
|
|
17
17
|
renameSync,
|
|
18
18
|
rmSync,
|
|
19
|
+
statfsSync,
|
|
19
20
|
writeFileSync,
|
|
20
21
|
} from "node:fs";
|
|
21
22
|
import { tmpdir } from "node:os";
|
|
@@ -1006,12 +1007,14 @@ import time
|
|
|
1006
1007
|
|
|
1007
1008
|
source_path, target_path = sys.argv[1], sys.argv[2]
|
|
1008
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.
|
|
1009
1012
|
source = sqlite3.connect("file:" + source_path + "?mode=ro", uri=True)
|
|
1010
|
-
destination = sqlite3.connect(target_path)
|
|
1011
1013
|
try:
|
|
1012
|
-
source.
|
|
1014
|
+
source.execute("VACUUM INTO ?", (target_path,))
|
|
1013
1015
|
finally:
|
|
1014
1016
|
source.close()
|
|
1017
|
+
destination = sqlite3.connect(target_path)
|
|
1015
1018
|
|
|
1016
1019
|
try:
|
|
1017
1020
|
if destination.execute("PRAGMA quick_check").fetchone()[0] != "ok":
|
|
@@ -1038,8 +1041,11 @@ try:
|
|
|
1038
1041
|
)
|
|
1039
1042
|
destination.execute(
|
|
1040
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.
|
|
1041
1046
|
" SELECT id FROM sessions"
|
|
1042
|
-
" WHERE source = 'cron' AND ended_at IS NOT NULL"
|
|
1047
|
+
" WHERE ((source = 'cron' AND ended_at IS NOT NULL)"
|
|
1048
|
+
" OR source = 'api_server')"
|
|
1043
1049
|
" AND COALESCE(last_activity_at, ended_at, started_at) < ? "
|
|
1044
1050
|
+ pinned_clause
|
|
1045
1051
|
+ " UNION"
|
|
@@ -1077,13 +1083,14 @@ try:
|
|
|
1077
1083
|
")"
|
|
1078
1084
|
)
|
|
1079
1085
|
destination.commit()
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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")
|
|
1087
1094
|
if destination.execute("PRAGMA quick_check").fetchone()[0] != "ok":
|
|
1088
1095
|
raise RuntimeError("compacted backup failed quick_check")
|
|
1089
1096
|
if destination.execute("PRAGMA foreign_key_check").fetchone() is not None:
|
|
@@ -1101,12 +1108,38 @@ print(json.dumps({
|
|
|
1101
1108
|
}, separators=(",", ":")))
|
|
1102
1109
|
`;
|
|
1103
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
|
+
|
|
1104
1136
|
export async function compactHermesSnapshotStateDb({
|
|
1105
1137
|
sourcePath,
|
|
1106
1138
|
targetPath,
|
|
1107
1139
|
retentionHours = HERMES_SNAPSHOT_CRON_SESSION_RETENTION_HOURS,
|
|
1108
1140
|
hermesPython = HERMES_PYTHON,
|
|
1109
1141
|
scratchDirectory = tmpdir(),
|
|
1142
|
+
volumeFreeBytes = freeBytesAt,
|
|
1110
1143
|
runChild = /** @type {typeof runBoundedHermesChild | undefined} */ (
|
|
1111
1144
|
undefined
|
|
1112
1145
|
),
|
|
@@ -1161,7 +1194,7 @@ export async function compactHermesSnapshotStateDb({
|
|
|
1161
1194
|
maxOutputBytes: 64 * 1024,
|
|
1162
1195
|
});
|
|
1163
1196
|
if (childFailed(result) || !existsSync(scratchTargetPath)) {
|
|
1164
|
-
throw
|
|
1197
|
+
throw compactionFailure(compactionChildFailureReason(result));
|
|
1165
1198
|
}
|
|
1166
1199
|
const scratchTarget = regularFile(scratchTargetPath);
|
|
1167
1200
|
const summary = JSON.parse(String(result.stdout ?? ""));
|
|
@@ -1174,7 +1207,16 @@ export async function compactHermesSnapshotStateDb({
|
|
|
1174
1207
|
summary.prunedMessages < 0 ||
|
|
1175
1208
|
summary.archiveStateBytes !== scratchTarget.size
|
|
1176
1209
|
) {
|
|
1177
|
-
throw
|
|
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");
|
|
1178
1220
|
}
|
|
1179
1221
|
finalTemporaryPath = join(
|
|
1180
1222
|
dirname(targetPath),
|
|
@@ -1204,8 +1246,17 @@ export async function compactHermesSnapshotStateDb({
|
|
|
1204
1246
|
throw new Error("hermes_snapshot_state_db_compaction_failed");
|
|
1205
1247
|
}
|
|
1206
1248
|
return summary;
|
|
1207
|
-
} catch {
|
|
1249
|
+
} catch (error) {
|
|
1208
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
|
+
);
|
|
1209
1260
|
throw new Error("hermes_snapshot_state_db_compaction_failed");
|
|
1210
1261
|
} finally {
|
|
1211
1262
|
if (finalTemporaryPath) rmSync(finalTemporaryPath, { force: true });
|
|
@@ -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.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.794";
|
|
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.
|
|
1549
|
+
pinned.installerPackage !== "@sellable/install@0.1.794" ||
|
|
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.
|
|
315
|
+
desired.installerPackage !== "@sellable/install@0.1.794" ||
|
|
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.
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.794";
|
|
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
|