@sellable/install 0.1.803 → 0.1.805
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/agents/post-find-leads-message-scout.md +4 -4
- package/agents/registry.json +1 -1
- package/bin/sellable-install.mjs +36 -20
- package/container/Dockerfile +4 -4
- package/container/README.md +1 -1
- package/lib/sellable-agent/default-profile-bundles/shared/manifest.fragment.json +1 -1
- package/lib/sellable-agent/default-profile-bundles/shared/skills/sellable/sellable-create-campaign/SKILL.md +3 -3
- package/lib/sellable-agent/fly-admin-image/Dockerfile +4 -2
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +19 -3
- package/lib/sellable-agent/fly-customer-image/Dockerfile +3 -3
- package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.d.mts +23 -3
- package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.mjs +197 -52
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +48 -22
- package/lib/sellable-agent/fly-customer-image/endpoint-model-selection.d.mts +1 -0
- package/lib/sellable-agent/fly-customer-image/endpoint-model-selection.mjs +21 -4
- package/lib/sellable-agent/fly-customer-image/hermes-runs-client.mjs +61 -5
- package/lib/sellable-agent/fly-customer-image/openai-runs-client.d.mts +3 -1
- package/lib/sellable-agent/fly-customer-image/openai-runs-client.mjs +4 -1
- package/lib/sellable-agent/fly-customer-image/public-endpoint-guard.d.mts +28 -0
- package/lib/sellable-agent/fly-customer-image/public-endpoint-guard.mjs +209 -0
- package/lib/sellable-agent/fly-customer-image/runs-api-connections.d.mts +1 -1
- package/lib/sellable-agent/fly-customer-image/runs-api-connections.mjs +97 -24
- package/lib/sellable-agent/fly-customer-image/runs-api-health-check.mjs +11 -42
- package/lib/sellable-agent/fly-customer-image/workspace-connection-client.d.mts +22 -0
- package/lib/sellable-agent/fly-customer-image/workspace-connection-client.mjs +98 -0
- package/lib/sellable-agent/fly-runtime-identity.mjs +8 -0
- 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/lib/sellable-agent/public-skills.json +4 -4
- package/lib/sellable-agent/public-skills.mjs +35 -0
- package/package.json +1 -1
- package/skill-templates/create-campaign.md +3 -3
- package/skill-templates/{conversation-chess.md → draft-message.md} +7 -6
- package/skill-templates/first-campaign-onboarding.md +8 -7
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import { readFile } from "node:fs/promises";
|
|
3
2
|
import { pathToFileURL } from "node:url";
|
|
4
3
|
import {
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
runsApiConnectionServesEndpoint,
|
|
15
14
|
runsApiConnectionsForRuntime,
|
|
16
15
|
} from "./runs-api-connections.mjs";
|
|
16
|
+
import { probeRunsClient } from "./workspace-connection-client.mjs";
|
|
17
17
|
|
|
18
18
|
// Fixed read-only machine command. Callers supply identities, never a URL,
|
|
19
19
|
// credential path, shell command, or prompt. The installed registry authorizes
|
|
@@ -142,6 +142,10 @@ export async function verifyRunsApiSelection(
|
|
|
142
142
|
// the primary step's check the whole chain, so a machine whose compiler
|
|
143
143
|
// cannot run a chain refuses it here and no chain is saved for it.
|
|
144
144
|
const selection = endpointModelSelectionChain(compiled)[0] ?? null;
|
|
145
|
+
// A workspace connection's key is fetched per attempt by the run, so the
|
|
146
|
+
// machine holds none to probe with. This is a shape check only; the server
|
|
147
|
+
// performs the connectivity probe (228-05).
|
|
148
|
+
if (selection?.modelConnectionId) return result("REACHABLE");
|
|
145
149
|
let verificationWorkKind = "STANDARD";
|
|
146
150
|
if (selection?.runsApiBaseUrl) {
|
|
147
151
|
const connection = runsApiConnectionsForRuntime(
|
|
@@ -171,10 +175,9 @@ export async function verifyRunsApiSelection(
|
|
|
171
175
|
? "/run/sellable-agent/admin-runtime/api-server-key"
|
|
172
176
|
: `/run/sellable-agent/${request.profileId}/api-server-key`;
|
|
173
177
|
let client;
|
|
174
|
-
let run;
|
|
175
178
|
try {
|
|
176
179
|
client = selection?.runsApiBaseUrl
|
|
177
|
-
? makeConfiguredClient({
|
|
180
|
+
? await makeConfiguredClient({
|
|
178
181
|
apiKeyFile,
|
|
179
182
|
env,
|
|
180
183
|
claim: {
|
|
@@ -191,48 +194,14 @@ export async function verifyRunsApiSelection(
|
|
|
191
194
|
},
|
|
192
195
|
})
|
|
193
196
|
: makeDefaultClient({ apiKeyFile });
|
|
194
|
-
// A fresh token per check. Claude Opus 5.5 answers one fixed string
|
|
195
|
-
// ("SELLABLE_CONNECTION_OK") with nothing, so Claude Code fell back to
|
|
196
|
-
// another model and every Claude Code verification failed.
|
|
197
|
-
const token = `ready-${randomUUID().replaceAll("-", "").slice(0, 8)}`;
|
|
198
|
-
const compiled = {
|
|
199
|
-
input: `Connection verification. Reply with ${token}.`,
|
|
200
|
-
instructions: `This is a connection check. Do not use tools or take actions. Return exactly ${token} and nothing else.`,
|
|
201
|
-
conversationHistory: [],
|
|
202
|
-
correlation: `connection-check-${randomUUID()}`,
|
|
203
|
-
...(selection ? { modelSelection: selection } : {}),
|
|
204
|
-
};
|
|
205
|
-
const signal = AbortSignal.timeout(70_000);
|
|
206
|
-
// Default Hermes gets the tool-free verification prefix. A Runs service
|
|
207
|
-
// owns its own tool policy; the Claude and OpenAI adapters are tool-free.
|
|
208
|
-
run = await (
|
|
209
|
-
selection?.runsApiBaseUrl ? client.submit : client.submitVerification
|
|
210
|
-
)(compiled, { signal });
|
|
211
|
-
await client.streamEvents(run.runId, { signal, onEvent: async () => {} });
|
|
212
|
-
const status = await client.status(run.runId, { signal });
|
|
213
|
-
const served = status.servedRuntime;
|
|
214
|
-
if (
|
|
215
|
-
status.status !== "completed" ||
|
|
216
|
-
typeof status.output !== "string" ||
|
|
217
|
-
status.output.trim() !== token ||
|
|
218
|
-
!served
|
|
219
|
-
)
|
|
220
|
-
return result("UNAVAILABLE");
|
|
221
|
-
if (
|
|
222
|
-
selection &&
|
|
223
|
-
(served.provider !== selection.provider ||
|
|
224
|
-
(served.model !== selection.model &&
|
|
225
|
-
!served.model.startsWith(`${selection.model}-`)))
|
|
226
|
-
)
|
|
227
|
-
return result("UNAVAILABLE");
|
|
228
|
-
return result("READY", served);
|
|
229
197
|
} catch {
|
|
230
|
-
if (client && run)
|
|
231
|
-
await client
|
|
232
|
-
.stop(run.runId, { signal: AbortSignal.timeout(3_000) })
|
|
233
|
-
.catch(() => {});
|
|
234
198
|
return result("UNAVAILABLE");
|
|
235
199
|
}
|
|
200
|
+
const probe = await probeRunsClient(client, selection, {
|
|
201
|
+
signal: AbortSignal.timeout(70_000),
|
|
202
|
+
submitVerification: !selection?.runsApiBaseUrl,
|
|
203
|
+
});
|
|
204
|
+
return result(probe.state, probe.servedRuntime);
|
|
236
205
|
}
|
|
237
206
|
|
|
238
207
|
if (
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EndpointModelStep } from "./endpoint-model-selection.mjs";
|
|
2
|
+
export type WorkspaceConnection = Readonly<{
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
protocol: "runs-api" | "openai-chat" | "openai-responses";
|
|
5
|
+
structuredOutputs?: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
/** Every request goes through `fetchImpl`; the key is held in memory only. */
|
|
8
|
+
export function createWorkspaceConnectionRunsClient(options: {
|
|
9
|
+
connection: WorkspaceConnection;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
fetchImpl: typeof fetch;
|
|
12
|
+
modelSelection: EndpointModelStep;
|
|
13
|
+
responseSchema?: unknown;
|
|
14
|
+
}): any;
|
|
15
|
+
export function probeRunsClient(
|
|
16
|
+
client: any,
|
|
17
|
+
selection: EndpointModelStep | null,
|
|
18
|
+
options?: { signal?: AbortSignal; submitVerification?: boolean }
|
|
19
|
+
): Promise<
|
|
20
|
+
| { state: "READY"; servedRuntime: { provider: string; model: string } }
|
|
21
|
+
| { state: "UNAVAILABLE" }
|
|
22
|
+
>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Builds the Runs client for a workspace's own model connection. The Next
|
|
2
|
+
// server and the agent runtime both import only this module for workspace
|
|
3
|
+
// connections, so it has no top-level code beyond imports and declarations.
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { createHermesRunsClient } from "./hermes-runs-client.mjs";
|
|
6
|
+
import { createOpenAiRunsClient } from "./openai-runs-client.mjs";
|
|
7
|
+
|
|
8
|
+
/** @param {any} options */
|
|
9
|
+
export function createWorkspaceConnectionRunsClient({
|
|
10
|
+
connection,
|
|
11
|
+
apiKey,
|
|
12
|
+
fetchImpl,
|
|
13
|
+
modelSelection,
|
|
14
|
+
responseSchema,
|
|
15
|
+
}) {
|
|
16
|
+
// The caller's fetchImpl is the address guard (createPinnedFetch). Without it
|
|
17
|
+
// both clients would fall back to the global fetch and dial unguarded.
|
|
18
|
+
if (typeof fetchImpl !== "function")
|
|
19
|
+
throw new Error("workspace_connection_fetch_required");
|
|
20
|
+
const { baseUrl, protocol, structuredOutputs } = connection;
|
|
21
|
+
// No registry entry and no scope headers: the key belongs to the workspace's
|
|
22
|
+
// connection, and every request goes through the caller's fetchImpl.
|
|
23
|
+
if (protocol === "runs-api")
|
|
24
|
+
return createHermesRunsClient({
|
|
25
|
+
baseUrl,
|
|
26
|
+
apiKey,
|
|
27
|
+
fetchImpl,
|
|
28
|
+
scopeHeaders: {},
|
|
29
|
+
});
|
|
30
|
+
if (protocol !== "openai-chat" && protocol !== "openai-responses")
|
|
31
|
+
throw new Error("workspace_connection_protocol_rejected");
|
|
32
|
+
const { provider, model, reasoningEffort } = modelSelection;
|
|
33
|
+
return createOpenAiRunsClient({
|
|
34
|
+
connection: {
|
|
35
|
+
baseUrl,
|
|
36
|
+
protocol,
|
|
37
|
+
structuredOutputs,
|
|
38
|
+
models: [{ provider, model, reasoningEfforts: [reasoningEffort] }],
|
|
39
|
+
},
|
|
40
|
+
apiKey,
|
|
41
|
+
fetchImpl,
|
|
42
|
+
modelSelection,
|
|
43
|
+
responseSchema,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// One ready-token probe for the machine verify and the server's connection
|
|
48
|
+
// check. It never reads a key: the caller hands it a built client.
|
|
49
|
+
/** @param {any} client @param {any} selection @param {any} [options] */
|
|
50
|
+
export async function probeRunsClient(
|
|
51
|
+
client,
|
|
52
|
+
selection,
|
|
53
|
+
{ signal, submitVerification = false } = {}
|
|
54
|
+
) {
|
|
55
|
+
let run;
|
|
56
|
+
try {
|
|
57
|
+
// A fresh token per check. Claude Opus 5.5 answers one fixed string
|
|
58
|
+
// ("SELLABLE_CONNECTION_OK") with nothing, so Claude Code fell back to
|
|
59
|
+
// another model and every Claude Code verification failed.
|
|
60
|
+
const token = `ready-${randomUUID().replaceAll("-", "").slice(0, 8)}`;
|
|
61
|
+
const compiled = {
|
|
62
|
+
input: `Connection verification. Reply with ${token}.`,
|
|
63
|
+
instructions: `This is a connection check. Do not use tools or take actions. Return exactly ${token} and nothing else.`,
|
|
64
|
+
conversationHistory: [],
|
|
65
|
+
correlation: `connection-check-${randomUUID()}`,
|
|
66
|
+
...(selection ? { modelSelection: selection } : {}),
|
|
67
|
+
};
|
|
68
|
+
// Default Hermes gets the tool-free verification prefix. A Runs service
|
|
69
|
+
// owns its own tool policy; the Claude and OpenAI adapters are tool-free.
|
|
70
|
+
run = await (
|
|
71
|
+
submitVerification ? client.submitVerification : client.submit
|
|
72
|
+
)(compiled, { signal });
|
|
73
|
+
await client.streamEvents(run.runId, { signal, onEvent: async () => {} });
|
|
74
|
+
const status = await client.status(run.runId, { signal });
|
|
75
|
+
const served = status.servedRuntime;
|
|
76
|
+
if (
|
|
77
|
+
status.status !== "completed" ||
|
|
78
|
+
typeof status.output !== "string" ||
|
|
79
|
+
status.output.trim() !== token ||
|
|
80
|
+
!served
|
|
81
|
+
)
|
|
82
|
+
return { state: "UNAVAILABLE" };
|
|
83
|
+
if (
|
|
84
|
+
selection &&
|
|
85
|
+
(served.provider !== selection.provider ||
|
|
86
|
+
(served.model !== selection.model &&
|
|
87
|
+
!served.model.startsWith(`${selection.model}-`)))
|
|
88
|
+
)
|
|
89
|
+
return { state: "UNAVAILABLE" };
|
|
90
|
+
return { state: "READY", servedRuntime: served };
|
|
91
|
+
} catch {
|
|
92
|
+
if (run)
|
|
93
|
+
await client
|
|
94
|
+
.stop(run.runId, { signal: AbortSignal.timeout(3_000) })
|
|
95
|
+
.catch(() => {});
|
|
96
|
+
return { state: "UNAVAILABLE" };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1305,6 +1305,14 @@ export function createFlyCustomerControlClient({
|
|
|
1305
1305
|
{ replaySafe: true }
|
|
1306
1306
|
);
|
|
1307
1307
|
},
|
|
1308
|
+
async fetchEndpointConnectionKey(identityBody) {
|
|
1309
|
+
return post(
|
|
1310
|
+
"endpoint-connection-key",
|
|
1311
|
+
"/api/v3/sellable-agent/worker/endpoint-connection-key",
|
|
1312
|
+
identityBody,
|
|
1313
|
+
{ replaySafe: true }
|
|
1314
|
+
);
|
|
1315
|
+
},
|
|
1308
1316
|
async consumeSecret(identityBody) {
|
|
1309
1317
|
// The broker makes only this verb replay-safe for the exact claim ID.
|
|
1310
1318
|
return post(
|
|
@@ -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.805";
|
|
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.805" ||
|
|
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.805" ||
|
|
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.805";
|
|
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
|