@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.
Files changed (36) hide show
  1. package/agents/post-find-leads-message-scout.md +4 -4
  2. package/agents/registry.json +1 -1
  3. package/bin/sellable-install.mjs +36 -20
  4. package/container/Dockerfile +4 -4
  5. package/container/README.md +1 -1
  6. package/lib/sellable-agent/default-profile-bundles/shared/manifest.fragment.json +1 -1
  7. package/lib/sellable-agent/default-profile-bundles/shared/skills/sellable/sellable-create-campaign/SKILL.md +3 -3
  8. package/lib/sellable-agent/fly-admin-image/Dockerfile +4 -2
  9. package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +19 -3
  10. package/lib/sellable-agent/fly-customer-image/Dockerfile +3 -3
  11. package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.d.mts +23 -3
  12. package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.mjs +197 -52
  13. package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +48 -22
  14. package/lib/sellable-agent/fly-customer-image/endpoint-model-selection.d.mts +1 -0
  15. package/lib/sellable-agent/fly-customer-image/endpoint-model-selection.mjs +21 -4
  16. package/lib/sellable-agent/fly-customer-image/hermes-runs-client.mjs +61 -5
  17. package/lib/sellable-agent/fly-customer-image/openai-runs-client.d.mts +3 -1
  18. package/lib/sellable-agent/fly-customer-image/openai-runs-client.mjs +4 -1
  19. package/lib/sellable-agent/fly-customer-image/public-endpoint-guard.d.mts +28 -0
  20. package/lib/sellable-agent/fly-customer-image/public-endpoint-guard.mjs +209 -0
  21. package/lib/sellable-agent/fly-customer-image/runs-api-connections.d.mts +1 -1
  22. package/lib/sellable-agent/fly-customer-image/runs-api-connections.mjs +97 -24
  23. package/lib/sellable-agent/fly-customer-image/runs-api-health-check.mjs +11 -42
  24. package/lib/sellable-agent/fly-customer-image/workspace-connection-client.d.mts +22 -0
  25. package/lib/sellable-agent/fly-customer-image/workspace-connection-client.mjs +98 -0
  26. package/lib/sellable-agent/fly-runtime-identity.mjs +8 -0
  27. package/lib/sellable-agent/host-bootstrap.mjs +1 -1
  28. package/lib/sellable-agent/host-worker.mjs +1 -1
  29. package/lib/sellable-agent/profile-materializer.mjs +1 -1
  30. package/lib/sellable-agent/provisioning-adapter.mjs +1 -1
  31. package/lib/sellable-agent/public-skills.json +4 -4
  32. package/lib/sellable-agent/public-skills.mjs +35 -0
  33. package/package.json +1 -1
  34. package/skill-templates/create-campaign.md +3 -3
  35. package/skill-templates/{conversation-chess.md → draft-message.md} +7 -6
  36. 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.803";
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.803" ||
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.803" ||
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.803";
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