@sellable/install 0.1.713 → 0.1.715
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 +7 -7
- package/container/README.md +1 -1
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +34 -1
- package/lib/sellable-agent/fly-customer-image/Dockerfile +3 -3
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +184 -81
- package/lib/sellable-agent/hermes-bridge.mjs +7 -12
- package/lib/sellable-agent/host-bootstrap.mjs +2 -2
- package/lib/sellable-agent/host-worker.mjs +2 -2
- package/lib/sellable-agent/mcp-context-proxy.mjs +100 -21
- package/lib/sellable-agent/profile-materializer.mjs +2 -2
- package/lib/sellable-agent/provisioning-adapter.mjs +2 -2
- package/package.json +1 -1
package/container/Dockerfile
CHANGED
|
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
7
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.715
|
|
7
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.915
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
|
-
ARG MCP_INTEGRITY=sha512
|
|
9
|
+
ARG MCP_INTEGRITY=sha512-+WdyO3WxQ9U7fHMvxBojPRDIZun/INy9mKOz/A1IcBFEWXFkpPooT0yMUzY1qCYcewTM1AUxP3p2QOtsxRIYQA==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
12
|
-
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.715" \
|
|
12
|
+
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.915" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
15
15
|
&& test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.713" \
|
|
|
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.715' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.915') 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.715', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.915', 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.715` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
|
|
30
30
|
const MCP_PACKAGE = "@sellable/mcp";
|
|
31
|
-
const MCP_VERSION = "0.1.
|
|
31
|
+
const MCP_VERSION = "0.1.915";
|
|
32
32
|
const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
|
|
33
33
|
const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
|
|
34
34
|
|
|
@@ -134,6 +134,13 @@ const PROFILES_ROOT = join(DATA_ROOT, "profiles");
|
|
|
134
134
|
const RUNTIME_ROOT = join(DATA_ROOT, ".sellable-agent", "admin-runtime");
|
|
135
135
|
const DELIVERY_ROOT = join(RUNTIME_ROOT, "delivery");
|
|
136
136
|
const SECRET_ROOT = "/run/sellable-agent/admin-runtime";
|
|
137
|
+
// Keep the Product MCP's Agent credential outside its mutable JSON config.
|
|
138
|
+
// The MCP re-reads that config on every request and ordinary login tooling can
|
|
139
|
+
// update it, so the credential file is the durable service-authority source.
|
|
140
|
+
const PRODUCT_MCP_CREDENTIAL_ROOT = join(
|
|
141
|
+
SECRET_ROOT,
|
|
142
|
+
"product-mcp-credentials"
|
|
143
|
+
);
|
|
137
144
|
// Turn descriptor + consumed roots for the Product MCP context proxy. They are
|
|
138
145
|
// normally empty and receive one signed descriptor only while the fenced
|
|
139
146
|
// endpoint relay owns a turn; the proxy fails closed if either root is absent.
|
|
@@ -1878,14 +1885,29 @@ function installRuntimeSecrets({ claim, secrets, desiredLifecycle }) {
|
|
|
1878
1885
|
slackSecretFile,
|
|
1879
1886
|
`SLACK_BOT_TOKEN=${secrets.slackBot}\nSLACK_APP_TOKEN=${secrets.slackApp}\n`
|
|
1880
1887
|
);
|
|
1888
|
+
const productCredentialDirectory = join(
|
|
1889
|
+
PRODUCT_MCP_CREDENTIAL_ROOT,
|
|
1890
|
+
claim.operation.profileId
|
|
1891
|
+
);
|
|
1892
|
+
const productCredentialPath = join(
|
|
1893
|
+
productCredentialDirectory,
|
|
1894
|
+
"service-credential"
|
|
1895
|
+
);
|
|
1896
|
+
privateDirectory(PRODUCT_MCP_CREDENTIAL_ROOT);
|
|
1897
|
+
privateDirectory(productCredentialDirectory);
|
|
1898
|
+
atomicPrivateFile(productCredentialPath, `${secrets.mcpService}\n`);
|
|
1881
1899
|
const productConfigPath = join(SECRET_ROOT, "sellable-config.json");
|
|
1882
1900
|
atomicPrivateFile(
|
|
1883
1901
|
productConfigPath,
|
|
1884
1902
|
`${JSON.stringify({
|
|
1885
1903
|
apiUrl: ORIGIN,
|
|
1886
|
-
token: secrets.mcpService,
|
|
1887
1904
|
activeWorkspaceId: claim.operation.workspaceId,
|
|
1888
1905
|
workspaceId: claim.operation.workspaceId,
|
|
1906
|
+
serviceCredential: {
|
|
1907
|
+
reference: `secret://agent/service/g${serviceAuthority.credentialGeneration}`,
|
|
1908
|
+
fingerprint: serviceAuthority.credentialFingerprint,
|
|
1909
|
+
generation: serviceAuthority.credentialGeneration,
|
|
1910
|
+
},
|
|
1889
1911
|
})}\n`
|
|
1890
1912
|
);
|
|
1891
1913
|
return {
|
|
@@ -2085,6 +2107,7 @@ function clearRuntimeSecrets() {
|
|
|
2085
2107
|
]) {
|
|
2086
2108
|
rmSync(join(SECRET_ROOT, name), { force: true });
|
|
2087
2109
|
}
|
|
2110
|
+
rmSync(PRODUCT_MCP_CREDENTIAL_ROOT, { recursive: true, force: true });
|
|
2088
2111
|
}
|
|
2089
2112
|
|
|
2090
2113
|
function adminAgentServiceEnvironment(claim) {
|
|
@@ -2106,6 +2129,7 @@ function adminAgentServiceEnvironment(claim) {
|
|
|
2106
2129
|
!Number.isSafeInteger(RUNTIME_GENERATION) ||
|
|
2107
2130
|
RUNTIME_GENERATION < 1 ||
|
|
2108
2131
|
!operation ||
|
|
2132
|
+
!/^[A-Za-z0-9_-]{1,128}$/.test(operation.profileId ?? "") ||
|
|
2109
2133
|
!service ||
|
|
2110
2134
|
!/^[A-Za-z0-9_-]{1,128}$/.test(service.principalId ?? "") ||
|
|
2111
2135
|
!Array.isArray(toolInclude) ||
|
|
@@ -2139,7 +2163,15 @@ function adminAgentServiceEnvironment(claim) {
|
|
|
2139
2163
|
// customer profile while the signed Agent service context remains the home
|
|
2140
2164
|
// workspace authority.
|
|
2141
2165
|
SELLABLE_AGENT_SERVER_AUTHENTICATED_KIND: "ADMIN",
|
|
2166
|
+
SELLABLE_AGENT_RUNTIME: "1",
|
|
2142
2167
|
SELLABLE_AGENT_RUNTIME_GENERATION: String(RUNTIME_GENERATION),
|
|
2168
|
+
SELLABLE_AGENT_PROFILE_ID: operation.profileId,
|
|
2169
|
+
SELLABLE_AGENT_RUNTIME_SECRET_ROOT: PRODUCT_MCP_CREDENTIAL_ROOT,
|
|
2170
|
+
SELLABLE_AGENT_SERVICE_CREDENTIAL_FILE: join(
|
|
2171
|
+
PRODUCT_MCP_CREDENTIAL_ROOT,
|
|
2172
|
+
operation.profileId,
|
|
2173
|
+
"service-credential"
|
|
2174
|
+
),
|
|
2143
2175
|
SELLABLE_AGENT_ADMIN_GENERATION_ID: claim.adminRuntimeGeneration.id,
|
|
2144
2176
|
SELLABLE_AGENT_ADMIN_GENERATION_FENCE: String(
|
|
2145
2177
|
claim.adminRuntimeGeneration.fence
|
|
@@ -3261,6 +3293,7 @@ export async function runAdminCanary() {
|
|
|
3261
3293
|
operation: {
|
|
3262
3294
|
workspaceId: fixtureWorkspaceId,
|
|
3263
3295
|
agentId: fixtureAgentId,
|
|
3296
|
+
profileId: "phase158-admin-canary",
|
|
3264
3297
|
fence: "phase158-canary-operation-fence-a",
|
|
3265
3298
|
},
|
|
3266
3299
|
runtimeContract: {
|
|
@@ -6,9 +6,9 @@ USER root
|
|
|
6
6
|
|
|
7
7
|
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
|
8
8
|
|
|
9
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
10
|
-
ARG MCP_INTEGRITY=sha512
|
|
11
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
9
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.915
|
|
10
|
+
ARG MCP_INTEGRITY=sha512-+WdyO3WxQ9U7fHMvxBojPRDIZun/INy9mKOz/A1IcBFEWXFkpPooT0yMUzY1qCYcewTM1AUxP3p2QOtsxRIYQA==
|
|
11
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.715
|
|
12
12
|
ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
|
|
13
13
|
ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
|
|
14
14
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
restoreLatestHermesSnapshot,
|
|
59
59
|
} from "../hermes-memory-snapshot.mjs";
|
|
60
60
|
import {
|
|
61
|
+
endpointHermesSessionId,
|
|
62
|
+
endpointMcpDescriptorPath,
|
|
61
63
|
installMcpTurnDescriptor,
|
|
62
64
|
replaceMcpTurnDescriptorAtomically,
|
|
63
65
|
} from "../mcp-context-proxy.mjs";
|
|
@@ -105,9 +107,10 @@ const RUNTIME_GENERATION = Number(
|
|
|
105
107
|
const BOOT_SESSION_ID = randomUUID();
|
|
106
108
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.4";
|
|
107
109
|
const INSTALLER_PACKAGE =
|
|
108
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.
|
|
110
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.715";
|
|
109
111
|
const MCP_PACKAGE =
|
|
110
|
-
process.env.SELLABLE_AGENT_MCP_PACKAGE ??
|
|
112
|
+
process.env.SELLABLE_AGENT_MCP_PACKAGE ??
|
|
113
|
+
"@sellable/mcp@0.1.915";
|
|
111
114
|
const DEFAULT_PROFILE_BUNDLE_ROOT =
|
|
112
115
|
"/usr/local/share/sellable-agent/default-profile-bundle";
|
|
113
116
|
const DEFAULT_PROFILE_CRON_PYTHON = [
|
|
@@ -1191,7 +1194,8 @@ function endpointProductProjection(productExecution, output) {
|
|
|
1191
1194
|
!action ||
|
|
1192
1195
|
action.actionKind !== "SEND_MESSAGE" ||
|
|
1193
1196
|
action.status !== "PROPOSED" ||
|
|
1194
|
-
action.argumentHash !==
|
|
1197
|
+
action.argumentHash !==
|
|
1198
|
+
endpointCanonicalOutputDigest(projectedArguments) ||
|
|
1195
1199
|
/\[SUPPRESS\]|\bSUPPRESS\b|\b(?:no|nothing)\s+truthful\b|\b(?:do|should)\s+not\s+send\b/i.test(
|
|
1196
1200
|
`${subject ?? ""}\n${candidate}`
|
|
1197
1201
|
)
|
|
@@ -1388,33 +1392,51 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1388
1392
|
) {
|
|
1389
1393
|
throw new Error("customer_endpoint_relay_configuration_rejected");
|
|
1390
1394
|
}
|
|
1391
|
-
|
|
1395
|
+
const activeAttempts = new Set();
|
|
1392
1396
|
let failures = 0;
|
|
1397
|
+
const maximumConcurrentRuns = 4;
|
|
1393
1398
|
|
|
1394
|
-
|
|
1399
|
+
// Each redacted event is bounded to 8 KiB and the signed request is bounded
|
|
1400
|
+
// to 64 KiB. Six events leave room for identity/signature envelope overhead
|
|
1401
|
+
// while replacing the previous one-HTTP-request-per-event handoff.
|
|
1402
|
+
const endpointEventBatchSize = 6;
|
|
1403
|
+
|
|
1404
|
+
const appendMany = async (claim, identity, indexedEvents) => {
|
|
1395
1405
|
if (!endpointRuntimeCurrent(claim, getActiveRuntime(), runtimeGeneration)) {
|
|
1396
1406
|
throw new Error("customer_endpoint_generation_fenced");
|
|
1397
1407
|
}
|
|
1398
|
-
const
|
|
1408
|
+
const envelopes = indexedEvents.map(({ event, ordinal }) =>
|
|
1409
|
+
endpointEventEnvelope(event, ordinal)
|
|
1410
|
+
);
|
|
1399
1411
|
const value = await getControlClient().appendEndpointEvents({
|
|
1400
1412
|
...identity,
|
|
1401
1413
|
schemaVersion: "sellable-agent-endpoint-events/v1",
|
|
1402
|
-
events:
|
|
1414
|
+
events: envelopes,
|
|
1403
1415
|
});
|
|
1404
1416
|
if (
|
|
1405
1417
|
value?.accepted !== true ||
|
|
1406
1418
|
!Array.isArray(value.events) ||
|
|
1407
|
-
value.events.length !==
|
|
1408
|
-
value.events
|
|
1409
|
-
|
|
1419
|
+
value.events.length !== envelopes.length ||
|
|
1420
|
+
value.events.some(
|
|
1421
|
+
(persisted, index) =>
|
|
1422
|
+
persisted?.nativeEventId !== envelopes[index].nativeEventId ||
|
|
1423
|
+
typeof persisted?.sequence !== "string"
|
|
1424
|
+
)
|
|
1410
1425
|
) {
|
|
1411
1426
|
throw new Error(
|
|
1412
|
-
|
|
1427
|
+
indexedEvents.some(
|
|
1428
|
+
({ event }) => event.kind === "LATE_COMPLETION_NOTICE"
|
|
1429
|
+
)
|
|
1413
1430
|
? "customer_endpoint_late_notice_rejected"
|
|
1414
1431
|
: "customer_endpoint_event_persist_rejected"
|
|
1415
1432
|
);
|
|
1416
1433
|
}
|
|
1417
|
-
return value.events
|
|
1434
|
+
return value.events.map((event) => event.sequence);
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
const append = async (claim, identity, event, ordinal) => {
|
|
1438
|
+
const sequences = await appendMany(claim, identity, [{ event, ordinal }]);
|
|
1439
|
+
return sequences[0];
|
|
1418
1440
|
};
|
|
1419
1441
|
|
|
1420
1442
|
const complete = async (claim, identity, completion) => {
|
|
@@ -1666,7 +1688,16 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1666
1688
|
claim,
|
|
1667
1689
|
activeRuntime
|
|
1668
1690
|
);
|
|
1669
|
-
const
|
|
1691
|
+
const hermesSessionId = endpointHermesSessionId(
|
|
1692
|
+
descriptorBinding.descriptor.body
|
|
1693
|
+
);
|
|
1694
|
+
const descriptorPath = endpointMcpDescriptorPath(
|
|
1695
|
+
activeRuntime.runtimeConfig.turnDescriptorPath,
|
|
1696
|
+
hermesSessionId
|
|
1697
|
+
);
|
|
1698
|
+
if (!descriptorPath) {
|
|
1699
|
+
throw new Error("customer_endpoint_hermes_session_rejected");
|
|
1700
|
+
}
|
|
1670
1701
|
const installedDescriptor = installDescriptor({
|
|
1671
1702
|
descriptorPath,
|
|
1672
1703
|
descriptorRoot: dirname(descriptorPath),
|
|
@@ -1732,7 +1763,7 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1732
1763
|
});
|
|
1733
1764
|
await held.controlChain;
|
|
1734
1765
|
};
|
|
1735
|
-
|
|
1766
|
+
activeAttempts.add(held);
|
|
1736
1767
|
const heartbeat = (async () => {
|
|
1737
1768
|
while (!controller.signal.aborted && !heartbeatStopped) {
|
|
1738
1769
|
if (deadlineExpired || now().getTime() >= deadlineMs) {
|
|
@@ -1783,6 +1814,14 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1783
1814
|
})();
|
|
1784
1815
|
let terminalEvent = null;
|
|
1785
1816
|
let ordinal = 0;
|
|
1817
|
+
const pendingStreamEvents = [];
|
|
1818
|
+
const flushStreamEvents = async () => {
|
|
1819
|
+
if (pendingStreamEvents.length === 0) return;
|
|
1820
|
+
const batch = pendingStreamEvents.slice(0, endpointEventBatchSize);
|
|
1821
|
+
const sequences = await appendMany(claim, identity, batch);
|
|
1822
|
+
pendingStreamEvents.splice(0, batch.length);
|
|
1823
|
+
held.committedSequence = sequences.at(-1);
|
|
1824
|
+
};
|
|
1786
1825
|
try {
|
|
1787
1826
|
let submitted;
|
|
1788
1827
|
try {
|
|
@@ -1817,20 +1856,30 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1817
1856
|
ordinal++
|
|
1818
1857
|
);
|
|
1819
1858
|
if (held.pendingControl) await queueControl(held.pendingControl);
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
late
|
|
1859
|
+
let streamed;
|
|
1860
|
+
try {
|
|
1861
|
+
streamed = await runs.streamEvents(submitted.runId, {
|
|
1862
|
+
signal: controller.signal,
|
|
1863
|
+
onEvent: async (event) => {
|
|
1864
|
+
const observedEvent = await holdEndpointUatNativeCompletion({
|
|
1865
|
+
control: uatCompletionControl,
|
|
1866
|
+
claim,
|
|
1867
|
+
event,
|
|
1868
|
+
now,
|
|
1869
|
+
sleep: sleepImpl,
|
|
1870
|
+
signal: controller.signal,
|
|
1871
|
+
});
|
|
1872
|
+
const late = deadlineExpired || now().getTime() >= deadlineMs;
|
|
1873
|
+
const lateCompletion =
|
|
1874
|
+
late && observedEvent.kind === "RUN_COMPLETED";
|
|
1875
|
+
// Late notices use a distinct post-deadline authorization path and
|
|
1876
|
+
// cannot share a signed batch with ordinary pre-deadline events.
|
|
1877
|
+
if (lateCompletion) {
|
|
1878
|
+
while (pendingStreamEvents.length > 0) {
|
|
1879
|
+
await flushStreamEvents();
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const durableEvent = lateCompletion
|
|
1834
1883
|
? {
|
|
1835
1884
|
kind: "LATE_COMPLETION_NOTICE",
|
|
1836
1885
|
outputDigest: observedEvent.outputDigest,
|
|
@@ -1838,19 +1887,30 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1838
1887
|
nativeEventIdentity: observedEvent.nativeEventIdentity,
|
|
1839
1888
|
}
|
|
1840
1889
|
: observedEvent;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1890
|
+
pendingStreamEvents.push({
|
|
1891
|
+
event: durableEvent,
|
|
1892
|
+
ordinal: ordinal++,
|
|
1893
|
+
});
|
|
1894
|
+
const approvalBoundary =
|
|
1895
|
+
observedEvent.classification === "AWAITING_APPROVAL" ||
|
|
1896
|
+
String(observedEvent.kind ?? "").includes("APPROVAL");
|
|
1897
|
+
if (
|
|
1898
|
+
pendingStreamEvents.length >= endpointEventBatchSize ||
|
|
1899
|
+
observedEvent.terminal ||
|
|
1900
|
+
approvalBoundary
|
|
1901
|
+
) {
|
|
1902
|
+
await flushStreamEvents();
|
|
1903
|
+
}
|
|
1904
|
+
// Terminal state is assigned only after its durable event batch.
|
|
1905
|
+
if (observedEvent.terminal)
|
|
1906
|
+
terminalEvent = { ...observedEvent, late };
|
|
1907
|
+
},
|
|
1908
|
+
});
|
|
1909
|
+
} finally {
|
|
1910
|
+
while (pendingStreamEvents.length > 0) {
|
|
1911
|
+
await flushStreamEvents();
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1854
1914
|
if (!terminalEvent && !controller.signal.aborted) {
|
|
1855
1915
|
const status = await runs.status(submitted.runId, {
|
|
1856
1916
|
signal: controller.signal,
|
|
@@ -1967,7 +2027,7 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1967
2027
|
await heartbeat;
|
|
1968
2028
|
await held.controlChain;
|
|
1969
2029
|
if (existsSync(descriptorPath)) unlinkSync(descriptorPath);
|
|
1970
|
-
|
|
2030
|
+
activeAttempts.delete(held);
|
|
1971
2031
|
}
|
|
1972
2032
|
};
|
|
1973
2033
|
|
|
@@ -1986,49 +2046,96 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
1986
2046
|
});
|
|
1987
2047
|
},
|
|
1988
2048
|
async fenceActive(reason = "GATEWAY_RESTART") {
|
|
1989
|
-
const
|
|
1990
|
-
|
|
2049
|
+
const heldAttempts = [...activeAttempts].filter(
|
|
2050
|
+
(held) => !held.stopRequested
|
|
2051
|
+
);
|
|
2052
|
+
if (heldAttempts.length === 0)
|
|
1991
2053
|
return { ok: true, code: "NO_ACTIVE_ATTEMPT" };
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2054
|
+
const results = await Promise.all(
|
|
2055
|
+
heldAttempts.map(async (held) => {
|
|
2056
|
+
held.stopRequested = true;
|
|
2057
|
+
let stopResult = {
|
|
2058
|
+
ok: false,
|
|
2059
|
+
classification: "RESTART_LOST",
|
|
2060
|
+
certainty: "UNCERTAIN",
|
|
2061
|
+
};
|
|
2062
|
+
if (held.nativeRunId) {
|
|
2063
|
+
try {
|
|
2064
|
+
stopResult = await held.runs.stop(held.nativeRunId, {
|
|
2065
|
+
signal: AbortSignal.timeout(5_000),
|
|
2066
|
+
});
|
|
2067
|
+
} catch {
|
|
2068
|
+
stopResult = {
|
|
2069
|
+
ok: false,
|
|
2070
|
+
classification: "STOP_UNCERTAIN",
|
|
2071
|
+
certainty: "UNCERTAIN",
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
try {
|
|
2076
|
+
await append(
|
|
2077
|
+
held.claim,
|
|
2078
|
+
held.identity,
|
|
2079
|
+
{
|
|
2080
|
+
kind: "GATEWAY_FENCE_REQUESTED",
|
|
2081
|
+
classification: stopResult.classification ?? "STOP_UNCERTAIN",
|
|
2082
|
+
requestDigest: sha256(reason),
|
|
2083
|
+
},
|
|
2084
|
+
Number.MAX_SAFE_INTEGER
|
|
2085
|
+
);
|
|
2086
|
+
} catch {
|
|
2087
|
+
// A newer runtime generation is expected to reject this old fence.
|
|
2088
|
+
}
|
|
2089
|
+
held.controller.abort(new Error("customer_endpoint_gateway_fenced"));
|
|
2090
|
+
return stopResult;
|
|
2091
|
+
})
|
|
2092
|
+
);
|
|
2093
|
+
const uncertain = results.find(
|
|
2094
|
+
(result) => result.positiveCancellation !== true
|
|
2095
|
+
);
|
|
2018
2096
|
return {
|
|
2019
|
-
ok:
|
|
2020
|
-
code:
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
: (stopResult.classification ?? "STOP_UNCERTAIN"),
|
|
2097
|
+
ok: !uncertain,
|
|
2098
|
+
code: uncertain
|
|
2099
|
+
? (uncertain.classification ?? "STOP_UNCERTAIN")
|
|
2100
|
+
: "CANCELLED",
|
|
2024
2101
|
};
|
|
2025
2102
|
},
|
|
2026
2103
|
async run(signal) {
|
|
2027
2104
|
if (!(signal instanceof AbortSignal)) {
|
|
2028
2105
|
throw new Error("customer_endpoint_relay_signal_rejected");
|
|
2029
2106
|
}
|
|
2107
|
+
const inFlight = new Set();
|
|
2108
|
+
const launch = (claim) => {
|
|
2109
|
+
let task;
|
|
2110
|
+
task = (async () => {
|
|
2111
|
+
try {
|
|
2112
|
+
const result = await runClaim(claim, signal);
|
|
2113
|
+
diagnostic({
|
|
2114
|
+
ok: result.ok,
|
|
2115
|
+
code: result.code,
|
|
2116
|
+
attemptIdDigest: sha256(claim.attempt?.id ?? ""),
|
|
2117
|
+
});
|
|
2118
|
+
} catch (error) {
|
|
2119
|
+
diagnostic({
|
|
2120
|
+
ok: false,
|
|
2121
|
+
code:
|
|
2122
|
+
error instanceof Error
|
|
2123
|
+
? redactRuntimeDiagnostic(error.message)
|
|
2124
|
+
: "customer_endpoint_run_failed",
|
|
2125
|
+
attemptIdDigest: sha256(claim.attempt?.id ?? ""),
|
|
2126
|
+
});
|
|
2127
|
+
} finally {
|
|
2128
|
+
inFlight.delete(task);
|
|
2129
|
+
}
|
|
2130
|
+
})();
|
|
2131
|
+
inFlight.add(task);
|
|
2132
|
+
};
|
|
2030
2133
|
while (!signal.aborted) {
|
|
2031
2134
|
try {
|
|
2135
|
+
if (inFlight.size >= maximumConcurrentRuns) {
|
|
2136
|
+
await Promise.race(inFlight);
|
|
2137
|
+
continue;
|
|
2138
|
+
}
|
|
2032
2139
|
const activeRuntime = getActiveRuntime();
|
|
2033
2140
|
if (!activeRuntime?.gateway?.readback?.().running) {
|
|
2034
2141
|
await abortablePause(500, signal, sleepImpl);
|
|
@@ -2040,12 +2147,7 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
2040
2147
|
await abortablePause(1_000, signal, sleepImpl);
|
|
2041
2148
|
continue;
|
|
2042
2149
|
}
|
|
2043
|
-
|
|
2044
|
-
diagnostic({
|
|
2045
|
-
ok: result.ok,
|
|
2046
|
-
code: result.code,
|
|
2047
|
-
attemptIdDigest: sha256(claim.attempt?.id ?? ""),
|
|
2048
|
-
});
|
|
2150
|
+
launch(claim);
|
|
2049
2151
|
failures = 0;
|
|
2050
2152
|
} catch (error) {
|
|
2051
2153
|
failures = Math.min(failures + 1, 6);
|
|
@@ -2063,6 +2165,7 @@ export function createCustomerEndpointRelay(options = {}) {
|
|
|
2063
2165
|
);
|
|
2064
2166
|
}
|
|
2065
2167
|
}
|
|
2168
|
+
await Promise.allSettled(inFlight);
|
|
2066
2169
|
},
|
|
2067
2170
|
});
|
|
2068
2171
|
}
|
|
@@ -744,17 +744,8 @@ def _sellable_agent_call_meta():
|
|
|
744
744
|
if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
|
|
745
745
|
return None
|
|
746
746
|
from gateway.session_context import get_session_env, session_context_engaged
|
|
747
|
-
if (
|
|
748
|
-
not session_context_engaged()
|
|
749
|
-
or get_session_env("HERMES_SESSION_PLATFORM", "").strip().lower() != "slack"
|
|
750
|
-
):
|
|
751
|
-
# Non-Slack runtime sessions (console/TUI, cron) carry no Slack actor
|
|
752
|
-
# identity. Send no actor meta; the serving API enforces its own actor
|
|
753
|
-
# gates, as with installs outside the Sellable runtime.
|
|
754
|
-
return None
|
|
755
|
-
from gateway.session_context import get_session_env, session_context_engaged
|
|
756
747
|
if not session_context_engaged():
|
|
757
|
-
|
|
748
|
+
return None
|
|
758
749
|
legacy_names = (
|
|
759
750
|
"HERMES_SESSION_PLATFORM",
|
|
760
751
|
"HERMES_SESSION_CHAT_ID",
|
|
@@ -764,6 +755,11 @@ def _sellable_agent_call_meta():
|
|
|
764
755
|
if any(os.getenv(name) for name in legacy_names):
|
|
765
756
|
raise RuntimeError("Sellable MCP identity must not come from process-global session variables")
|
|
766
757
|
platform = get_session_env("HERMES_SESSION_PLATFORM", "").strip().lower()
|
|
758
|
+
session_id = get_session_env("HERMES_SESSION_ID", "").strip()
|
|
759
|
+
if platform == "api_server":
|
|
760
|
+
if not session_id:
|
|
761
|
+
raise RuntimeError("Sellable endpoint MCP calls require a bound Hermes session")
|
|
762
|
+
return {"sellableEndpoint": {"sessionId": session_id}}
|
|
767
763
|
if platform != "slack":
|
|
768
764
|
descriptor_path = os.getenv("SELLABLE_AGENT_TURN_DESCRIPTOR_PATH", "")
|
|
769
765
|
if descriptor_path.startswith("/"):
|
|
@@ -778,7 +774,6 @@ def _sellable_agent_call_meta():
|
|
|
778
774
|
channel_id = get_session_env("HERMES_SESSION_CHAT_ID", "").strip()
|
|
779
775
|
requester_id = get_session_env("HERMES_SESSION_USER_ID", "").strip()
|
|
780
776
|
message_id = get_session_env("HERMES_SESSION_MESSAGE_ID", "").strip()
|
|
781
|
-
session_id = get_session_env("HERMES_SESSION_ID", "").strip()
|
|
782
777
|
if not channel_id or not requester_id or not message_id or not session_id:
|
|
783
778
|
raise RuntimeError("Sellable MCP calls require a bound Slack requester, channel, message, and session")
|
|
784
779
|
return {
|
|
@@ -2021,7 +2016,7 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
2021
2016
|
sourceSha256:
|
|
2022
2017
|
"8ce75e7f2d999edf6554a8f5a037289c885bc9427263da1cd6e3448e608fe20f",
|
|
2023
2018
|
patchedSha256:
|
|
2024
|
-
"
|
|
2019
|
+
"2a32c22e499bdb16f31ddd77f143b8f4907df9f4972553b07129fc2a9607b174",
|
|
2025
2020
|
},
|
|
2026
2021
|
{
|
|
2027
2022
|
path: "gateway/run.py",
|
|
@@ -38,8 +38,8 @@ 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.
|
|
42
|
-
const MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.715";
|
|
42
|
+
const MCP_PACKAGE = "@sellable/mcp@0.1.915";
|
|
43
43
|
const HERMES_VERSION = "0.18.0";
|
|
44
44
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
45
45
|
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
@@ -1547,8 +1547,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1547
1547
|
pinned.hermesCli !== "hermes" ||
|
|
1548
1548
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1549
1549
|
pinned.installerPackage !==
|
|
1550
|
-
"@sellable/install@0.1.
|
|
1551
|
-
pinned.mcpPackage !== "@sellable/mcp@0.1.
|
|
1550
|
+
"@sellable/install@0.1.715" ||
|
|
1551
|
+
pinned.mcpPackage !== "@sellable/mcp@0.1.915" ||
|
|
1552
1552
|
!Array.isArray(policy.toolInclude) ||
|
|
1553
1553
|
policy.toolInclude.length === 0 ||
|
|
1554
1554
|
!Number.isSafeInteger(slack.generation) ||
|
|
@@ -947,13 +947,38 @@ function isHermesSlackToolCall(request) {
|
|
|
947
947
|
);
|
|
948
948
|
}
|
|
949
949
|
|
|
950
|
-
function endpointHermesSessionId(body) {
|
|
950
|
+
export function endpointHermesSessionId(body) {
|
|
951
951
|
if (body?.schemaVersion !== "sellable-agent-mcp-endpoint-run/v1") return null;
|
|
952
952
|
return `sellable_endpoint_${sha256(
|
|
953
953
|
`${body.conversationId}:${body.runId}:${body.attemptId}`
|
|
954
954
|
).slice(0, 32)}`;
|
|
955
955
|
}
|
|
956
956
|
|
|
957
|
+
export function endpointMcpDescriptorPath(descriptorPath, sessionId) {
|
|
958
|
+
if (
|
|
959
|
+
!isAbsolute(descriptorPath ?? "") ||
|
|
960
|
+
typeof sessionId !== "string" ||
|
|
961
|
+
!SAFE_ID.test(sessionId)
|
|
962
|
+
) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
return join(dirname(descriptorPath), `endpoint-${sha256(sessionId)}.json`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function requestEndpointHermesSessionId(request) {
|
|
969
|
+
if (request?.method !== "tools/call" && request?.method !== "tools/list") {
|
|
970
|
+
return null;
|
|
971
|
+
}
|
|
972
|
+
const meta = request?.params?._meta;
|
|
973
|
+
if (!exactKeys(meta, ["sellableEndpoint"])) return null;
|
|
974
|
+
const endpoint = meta.sellableEndpoint;
|
|
975
|
+
if (!exactKeys(endpoint, ["sessionId"])) return null;
|
|
976
|
+
return typeof endpoint.sessionId === "string" &&
|
|
977
|
+
SAFE_ID.test(endpoint.sessionId)
|
|
978
|
+
? endpoint.sessionId
|
|
979
|
+
: null;
|
|
980
|
+
}
|
|
981
|
+
|
|
957
982
|
function isExactEndpointHermesSessionToolCall(request, body) {
|
|
958
983
|
if (!isHermesSlackToolCall(request)) return false;
|
|
959
984
|
const expectedSessionId = endpointHermesSessionId(body);
|
|
@@ -1079,13 +1104,45 @@ export async function runMcpContextProxyStdio({
|
|
|
1079
1104
|
}
|
|
1080
1105
|
return response;
|
|
1081
1106
|
};
|
|
1082
|
-
const
|
|
1107
|
+
const legacyEndpointSession = createMcpEndpointSession();
|
|
1108
|
+
const endpointSessionsByHermesId = new Map();
|
|
1083
1109
|
|
|
1084
1110
|
for await (const line of inputLines) {
|
|
1085
1111
|
const request = parseJsonRpcLine(line);
|
|
1086
1112
|
if (!request) throw new Error("mcp_upstream_protocol_rejected");
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1113
|
+
const requestNow = Date.now();
|
|
1114
|
+
for (const [sessionId, session] of endpointSessionsByHermesId) {
|
|
1115
|
+
const body = endpointSessionBody(session);
|
|
1116
|
+
if (!body || requestNow >= Date.parse(body.expiresAt)) {
|
|
1117
|
+
endpointSessionsByHermesId.delete(sessionId);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
const endpointHermesId = requestEndpointHermesSessionId(request);
|
|
1121
|
+
const requestDescriptorPath = endpointHermesId
|
|
1122
|
+
? endpointMcpDescriptorPath(descriptorPath, endpointHermesId)
|
|
1123
|
+
: descriptorPath;
|
|
1124
|
+
if (!requestDescriptorPath) {
|
|
1125
|
+
await writeJsonLine(output, {
|
|
1126
|
+
jsonrpc: "2.0",
|
|
1127
|
+
id: request.id ?? null,
|
|
1128
|
+
error: {
|
|
1129
|
+
code: -32001,
|
|
1130
|
+
message: "endpoint_session_rejected",
|
|
1131
|
+
data: { code: "endpoint_session_rejected" },
|
|
1132
|
+
},
|
|
1133
|
+
});
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
let endpointSession = legacyEndpointSession;
|
|
1137
|
+
if (endpointHermesId) {
|
|
1138
|
+
endpointSession = endpointSessionsByHermesId.get(endpointHermesId);
|
|
1139
|
+
if (!endpointSession) {
|
|
1140
|
+
endpointSession = createMcpEndpointSession();
|
|
1141
|
+
endpointSessionsByHermesId.set(endpointHermesId, endpointSession);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const candidate = existsSync(requestDescriptorPath)
|
|
1145
|
+
? descriptorCandidate(requestDescriptorPath, descriptorRoot)
|
|
1089
1146
|
: null;
|
|
1090
1147
|
const activeBody = endpointSessionBody(endpointSession);
|
|
1091
1148
|
const endpointBody =
|
|
@@ -1097,16 +1154,38 @@ export async function runMcpContextProxyStdio({
|
|
|
1097
1154
|
const endpointMode = Boolean(
|
|
1098
1155
|
mcpEndpointSessionActivated(endpointSession) || endpointBody
|
|
1099
1156
|
);
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1157
|
+
if (
|
|
1158
|
+
endpointHermesId &&
|
|
1159
|
+
endpointBody &&
|
|
1160
|
+
endpointHermesSessionId(endpointBody) !== endpointHermesId
|
|
1161
|
+
) {
|
|
1162
|
+
await writeJsonLine(output, {
|
|
1163
|
+
jsonrpc: "2.0",
|
|
1164
|
+
id: request.id ?? null,
|
|
1165
|
+
error: {
|
|
1166
|
+
code: -32001,
|
|
1167
|
+
message: "endpoint_session_binding_mismatch",
|
|
1168
|
+
data: { code: "endpoint_session_binding_mismatch" },
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (endpointHermesId && !endpointMode) {
|
|
1174
|
+
await writeJsonLine(output, {
|
|
1175
|
+
jsonrpc: "2.0",
|
|
1176
|
+
id: request.id ?? null,
|
|
1177
|
+
error: {
|
|
1178
|
+
code: -32001,
|
|
1179
|
+
message: "descriptor_missing",
|
|
1180
|
+
data: { code: "descriptor_missing" },
|
|
1181
|
+
},
|
|
1182
|
+
});
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
// The patched Hermes bridge adds request-scoped metadata from task-local
|
|
1186
|
+
// ContextVars; the model controls only `arguments` and cannot author MCP
|
|
1187
|
+
// `_meta`. Genuine Slack calls pass through with their actor identity.
|
|
1188
|
+
// API endpoint calls select their own signed descriptor/session above.
|
|
1110
1189
|
if (
|
|
1111
1190
|
isHermesSlackToolCall(request) &&
|
|
1112
1191
|
!(
|
|
@@ -1118,16 +1197,16 @@ export async function runMcpContextProxyStdio({
|
|
|
1118
1197
|
continue;
|
|
1119
1198
|
}
|
|
1120
1199
|
if (request.method === "tools/list" && endpointMode) {
|
|
1121
|
-
const expected = existsSync(
|
|
1200
|
+
const expected = existsSync(requestDescriptorPath)
|
|
1122
1201
|
? deriveExpectedMcpTurnBinding({
|
|
1123
|
-
descriptorPath,
|
|
1202
|
+
descriptorPath: requestDescriptorPath,
|
|
1124
1203
|
descriptorRoot,
|
|
1125
1204
|
runtimeBindingPath,
|
|
1126
1205
|
})
|
|
1127
1206
|
: { ok: true, expected: null };
|
|
1128
1207
|
const authorized = expected.ok
|
|
1129
1208
|
? await authorizeMcpToolList({
|
|
1130
|
-
descriptorPath,
|
|
1209
|
+
descriptorPath: requestDescriptorPath,
|
|
1131
1210
|
descriptorRoot,
|
|
1132
1211
|
consumedRoot,
|
|
1133
1212
|
trustRootPublicKeyPem,
|
|
@@ -1160,20 +1239,20 @@ export async function runMcpContextProxyStdio({
|
|
|
1160
1239
|
// The existing Slack bridge remains the task-local authority when no
|
|
1161
1240
|
// server-minted endpoint descriptor is installed. Installing this proxy
|
|
1162
1241
|
// for endpoint runs must not weaken or fabricate that Slack path.
|
|
1163
|
-
if (!existsSync(
|
|
1242
|
+
if (!existsSync(requestDescriptorPath) && !endpointMode) {
|
|
1164
1243
|
await writeJsonLine(child.stdin, request);
|
|
1165
1244
|
continue;
|
|
1166
1245
|
}
|
|
1167
|
-
const expected = existsSync(
|
|
1246
|
+
const expected = existsSync(requestDescriptorPath)
|
|
1168
1247
|
? deriveExpectedMcpTurnBinding({
|
|
1169
|
-
descriptorPath,
|
|
1248
|
+
descriptorPath: requestDescriptorPath,
|
|
1170
1249
|
descriptorRoot,
|
|
1171
1250
|
runtimeBindingPath,
|
|
1172
1251
|
})
|
|
1173
1252
|
: { ok: true, expected: null };
|
|
1174
1253
|
const authorized = expected.ok
|
|
1175
1254
|
? await authorizeMcpToolCall({
|
|
1176
|
-
descriptorPath,
|
|
1255
|
+
descriptorPath: requestDescriptorPath,
|
|
1177
1256
|
descriptorRoot,
|
|
1178
1257
|
consumedRoot,
|
|
1179
1258
|
trustRootPublicKeyPem,
|
|
@@ -283,8 +283,8 @@ function validateDesired(desired) {
|
|
|
283
283
|
desired.hermesCli !== "hermes" ||
|
|
284
284
|
desired.hermesVersion !== "0.18.0" ||
|
|
285
285
|
desired.installerPackage !==
|
|
286
|
-
"@sellable/install@0.1.
|
|
287
|
-
desired.mcpPackage !== "@sellable/mcp@0.1.
|
|
286
|
+
"@sellable/install@0.1.715" ||
|
|
287
|
+
desired.mcpPackage !== "@sellable/mcp@0.1.915" ||
|
|
288
288
|
!Array.isArray(desired.toolInclude) ||
|
|
289
289
|
desired.toolInclude.length === 0 ||
|
|
290
290
|
desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
|
|
@@ -26,8 +26,8 @@ import { deriveContainedProfileId } from "./profile-materializer.mjs";
|
|
|
26
26
|
|
|
27
27
|
export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
|
|
28
28
|
export const PINNED_INSTALL_PACKAGE =
|
|
29
|
-
"@sellable/install@0.1.
|
|
30
|
-
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
29
|
+
"@sellable/install@0.1.715";
|
|
30
|
+
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.915";
|
|
31
31
|
const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
|
|
32
32
|
PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
|
|
33
33
|
);
|