@desplega.ai/agent-swarm 1.80.0 → 1.80.2
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/openapi.json +399 -14
- package/package.json +3 -1
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/db.ts +1 -1
- package/src/be/migrations/064_scripts.sql +39 -0
- package/src/be/migrations/065_script_embeddings.sql +7 -0
- package/src/be/migrations/066_scripts_args_json_schema.sql +1 -0
- package/src/be/scripts/db.ts +417 -0
- package/src/be/scripts/embeddings.ts +233 -0
- package/src/be/scripts/extract-schema.ts +55 -0
- package/src/be/scripts/maintenance.ts +9 -0
- package/src/be/scripts/typecheck.ts +199 -0
- package/src/cli.tsx +22 -5
- package/src/commands/artifact.ts +3 -2
- package/src/commands/claude-managed-setup.ts +2 -1
- package/src/commands/codex-login.ts +5 -3
- package/src/commands/onboard.tsx +2 -1
- package/src/commands/runner.ts +153 -20
- package/src/commands/setup.tsx +5 -3
- package/src/hooks/hook.ts +4 -3
- package/src/http/index.ts +40 -29
- package/src/http/memory.ts +28 -0
- package/src/http/openapi.ts +1 -0
- package/src/http/page-proxy.ts +2 -1
- package/src/http/route-def.ts +1 -0
- package/src/http/schedules.ts +37 -0
- package/src/http/scripts.ts +388 -0
- package/src/linear/outbound.ts +9 -2
- package/src/otel.ts +5 -0
- package/src/providers/claude-adapter.ts +23 -1
- package/src/providers/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +23 -0
- package/src/scripts-runtime/eval-harness.ts +63 -0
- package/src/scripts-runtime/executors/native.ts +232 -0
- package/src/scripts-runtime/executors/registry.ts +16 -0
- package/src/scripts-runtime/executors/types.ts +63 -0
- package/src/scripts-runtime/extract-args-schema.ts +69 -0
- package/src/scripts-runtime/extract-signature.ts +81 -0
- package/src/scripts-runtime/import-allowlist.ts +109 -0
- package/src/scripts-runtime/loader.ts +96 -0
- package/src/scripts-runtime/redacted.ts +48 -0
- package/src/scripts-runtime/sdk-allowlist.ts +29 -0
- package/src/scripts-runtime/stdlib/fetch.ts +46 -0
- package/src/scripts-runtime/stdlib/glob.ts +8 -0
- package/src/scripts-runtime/stdlib/grep.ts +34 -0
- package/src/scripts-runtime/stdlib/index.ts +16 -0
- package/src/scripts-runtime/stdlib/table.ts +17 -0
- package/src/scripts-runtime/swarm-config.ts +35 -0
- package/src/scripts-runtime/swarm-sdk.ts +197 -0
- package/src/scripts-runtime/types/stdlib.d.ts +104 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +86 -0
- package/src/server.ts +12 -0
- package/src/tests/api-key.test.ts +33 -0
- package/src/tests/codex-login.test.ts +1 -1
- package/src/tests/error-tracker.test.ts +44 -0
- package/src/tests/linear-outbound-sync.test.ts +109 -0
- package/src/tests/mcp-tools.test.ts +69 -0
- package/src/tests/rate-limit-event.test.ts +292 -0
- package/src/tests/redacted.test.ts +29 -0
- package/src/tests/runner-tool-spans.test.ts +268 -0
- package/src/tests/script-executor-conformance.test.ts +142 -0
- package/src/tests/script-executor-registry.test.ts +17 -0
- package/src/tests/scripts-db.test.ts +329 -0
- package/src/tests/scripts-embeddings.test.ts +291 -0
- package/src/tests/scripts-extract-signature.test.ts +47 -0
- package/src/tests/scripts-http.test.ts +403 -0
- package/src/tests/scripts-import-allowlist.test.ts +55 -0
- package/src/tests/scripts-mcp-e2e.test.ts +269 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +44 -0
- package/src/tests/scripts-runtime.test.ts +344 -0
- package/src/tests/sdk-allowlist.test.ts +59 -0
- package/src/tests/secret-scrubber.test.ts +35 -1
- package/src/tests/swarm-config.test.ts +38 -0
- package/src/tests/tool-annotations.test.ts +2 -2
- package/src/tests/tool-call-progress.test.ts +30 -0
- package/src/tests/workflow-e2e.test.ts +218 -0
- package/src/tests/workflow-executors.test.ts +32 -2
- package/src/tests/workflow-input-redaction.test.ts +232 -0
- package/src/tests/workflow-swarm-script.test.ts +273 -0
- package/src/tools/memory-rate.ts +2 -1
- package/src/tools/script-common.ts +88 -0
- package/src/tools/script-delete.ts +35 -0
- package/src/tools/script-query-types.ts +37 -0
- package/src/tools/script-run.ts +43 -0
- package/src/tools/script-search.ts +32 -0
- package/src/tools/script-upsert.ts +43 -0
- package/src/tools/tool-config.ts +7 -0
- package/src/types.ts +61 -1
- package/src/utils/api-key.ts +28 -0
- package/src/utils/error-tracker.ts +58 -0
- package/src/utils/page-session.ts +8 -6
- package/src/utils/secret-scrubber.ts +22 -1
- package/src/workflows/engine.ts +12 -4
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/script.ts +12 -1
- package/src/workflows/executors/swarm-script.ts +170 -0
- package/src/workflows/input.ts +65 -0
- package/src/workflows/recovery.ts +31 -3
- package/src/workflows/resume.ts +43 -5
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export type ScriptTypecheckResult = { ok: true } | { ok: false; diagnostics: string[] };
|
|
4
|
+
|
|
5
|
+
export const SCRIPT_SDK_TYPES = `
|
|
6
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
7
|
+
export type ScriptScope = "agent" | "global";
|
|
8
|
+
export type ScriptFsMode = "none" | "workspace-rw";
|
|
9
|
+
|
|
10
|
+
export interface Redacted<T> {
|
|
11
|
+
readonly __redactedBrand?: T;
|
|
12
|
+
toString(): "<redacted>";
|
|
13
|
+
toJSON(): "<redacted>";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RedactedStatic {
|
|
17
|
+
value<T>(self: Redacted<T>): T;
|
|
18
|
+
meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
|
|
19
|
+
isSecret<T>(self: Redacted<T>): boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SwarmConfig {
|
|
23
|
+
apiKey: Redacted<string>;
|
|
24
|
+
agentId: Redacted<string>;
|
|
25
|
+
mcpBaseUrl: Redacted<string>;
|
|
26
|
+
get<T = string>(key: string): Redacted<T> | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SwarmSdk {
|
|
30
|
+
memory_search(args: { query: string; scope?: "all" | "agent" | "swarm"; limit?: number; source?: string }): Promise<unknown>;
|
|
31
|
+
memory_get(args: { memoryId: string }): Promise<unknown>;
|
|
32
|
+
memory_rate(args: { id: string; useful: boolean; note?: string }): Promise<unknown>;
|
|
33
|
+
task_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
34
|
+
task_get(args: { taskId: string }): Promise<unknown>;
|
|
35
|
+
task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
|
|
36
|
+
kv_get(args: { key: string; namespace?: string }): Promise<unknown>;
|
|
37
|
+
kv_set(args: { key: string; value: unknown; namespace?: string; ttlSeconds?: number; valueType?: "string" | "json" | "integer" }): Promise<unknown>;
|
|
38
|
+
kv_del(args: { key: string; namespace?: string }): Promise<unknown>;
|
|
39
|
+
kv_incr(args: { key: string; by?: number; namespace?: string }): Promise<unknown>;
|
|
40
|
+
kv_list(args?: { prefix?: string; namespace?: string; limit?: number }): Promise<unknown>;
|
|
41
|
+
repo_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
42
|
+
schedule_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
43
|
+
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
44
|
+
script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode }): Promise<unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ScriptStdlib {
|
|
48
|
+
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
49
|
+
fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
|
|
50
|
+
grep(pattern: string, files?: string | string[]): Promise<string>;
|
|
51
|
+
glob(pattern: string): Promise<string[]>;
|
|
52
|
+
table(rows: Array<Record<string, unknown>>): string;
|
|
53
|
+
Redacted: RedactedStatic;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ScriptLogger extends Console {}
|
|
57
|
+
|
|
58
|
+
export interface ScriptContext {
|
|
59
|
+
swarm: SwarmSdk & { config: SwarmConfig };
|
|
60
|
+
stdlib: ScriptStdlib;
|
|
61
|
+
logger: ScriptLogger;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
|
|
65
|
+
export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
export const SCRIPT_STDLIB_TYPES = `
|
|
69
|
+
declare module "stdlib" {
|
|
70
|
+
export interface Redacted<T> {
|
|
71
|
+
readonly __redactedBrand?: T;
|
|
72
|
+
toString(): "<redacted>";
|
|
73
|
+
toJSON(): "<redacted>";
|
|
74
|
+
}
|
|
75
|
+
export const Redacted: {
|
|
76
|
+
value<T>(self: Redacted<T>): T;
|
|
77
|
+
meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
|
|
78
|
+
isSecret<T>(self: Redacted<T>): boolean;
|
|
79
|
+
};
|
|
80
|
+
export function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
81
|
+
export function fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
|
|
82
|
+
export function grep(pattern: string, files?: string | string[]): Promise<string>;
|
|
83
|
+
export function glob(pattern: string): Promise<string[]>;
|
|
84
|
+
export function table(rows: Array<Record<string, unknown>>): string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
declare module "swarm-sdk" {
|
|
88
|
+
${SCRIPT_SDK_TYPES.replace(/^/gm, " ")}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const USER_FILE = "/virtual/user-script.ts";
|
|
93
|
+
const CHECK_FILE = "/virtual/check.ts";
|
|
94
|
+
const SDK_FILE = "/virtual/swarm-sdk.d.ts";
|
|
95
|
+
const STDLIB_FILE = "/virtual/stdlib.d.ts";
|
|
96
|
+
|
|
97
|
+
function createCompilerHost(
|
|
98
|
+
files: Map<string, string>,
|
|
99
|
+
options: ts.CompilerOptions,
|
|
100
|
+
): ts.CompilerHost {
|
|
101
|
+
const host = ts.createCompilerHost(options, true);
|
|
102
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
103
|
+
|
|
104
|
+
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
105
|
+
const normalized = fileName.replace(/\\/g, "/");
|
|
106
|
+
const source = files.get(normalized);
|
|
107
|
+
if (source !== undefined) {
|
|
108
|
+
return ts.createSourceFile(normalized, source, languageVersion, true, ts.ScriptKind.TS);
|
|
109
|
+
}
|
|
110
|
+
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
host.fileExists = (fileName) => {
|
|
114
|
+
const normalized = fileName.replace(/\\/g, "/");
|
|
115
|
+
return files.has(normalized) || ts.sys.fileExists(fileName);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
host.readFile = (fileName) => {
|
|
119
|
+
const normalized = fileName.replace(/\\/g, "/");
|
|
120
|
+
return files.get(normalized) ?? ts.sys.readFile(fileName);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Resolve external packages (e.g. "zod") from the project root rather than
|
|
124
|
+
// the virtual path "/virtual/..." so TypeScript can find real node_modules.
|
|
125
|
+
const projectBase = new URL("../../index.ts", import.meta.url).pathname;
|
|
126
|
+
|
|
127
|
+
host.resolveModuleNames = (moduleNames, containingFile) =>
|
|
128
|
+
moduleNames.map((moduleName) => {
|
|
129
|
+
if (moduleName === "./user-script") {
|
|
130
|
+
return { resolvedFileName: USER_FILE, extension: ts.Extension.Ts };
|
|
131
|
+
}
|
|
132
|
+
if (moduleName === "swarm-sdk") {
|
|
133
|
+
return { resolvedFileName: SDK_FILE, extension: ts.Extension.Dts };
|
|
134
|
+
}
|
|
135
|
+
if (moduleName === "stdlib") {
|
|
136
|
+
return { resolvedFileName: STDLIB_FILE, extension: ts.Extension.Dts };
|
|
137
|
+
}
|
|
138
|
+
// For external packages, resolve from project root so node_modules is found
|
|
139
|
+
const base = containingFile.startsWith("/virtual/") ? projectBase : containingFile;
|
|
140
|
+
return ts.resolveModuleName(moduleName, base, options, host).resolvedModule;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// In compiled binary mode, TypeScript's lib .d.ts files live alongside
|
|
144
|
+
// typescript.js in /$bunfs/ — but .d.ts files are not embedded in the binary.
|
|
145
|
+
// Redirect lib lookups to TS_LIB_DIR where the Dockerfile copies real copies.
|
|
146
|
+
const tsLibDir = process.env.TS_LIB_DIR;
|
|
147
|
+
if (tsLibDir) {
|
|
148
|
+
host.getDefaultLibLocation = () => tsLibDir;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return host;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function typecheckScript(source: string): ScriptTypecheckResult {
|
|
155
|
+
const options: ts.CompilerOptions = {
|
|
156
|
+
allowImportingTsExtensions: true,
|
|
157
|
+
module: ts.ModuleKind.ESNext,
|
|
158
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
159
|
+
noEmit: true,
|
|
160
|
+
skipLibCheck: true,
|
|
161
|
+
strict: true,
|
|
162
|
+
target: ts.ScriptTarget.ES2022,
|
|
163
|
+
types: [],
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const files = new Map<string, string>([
|
|
167
|
+
[USER_FILE, source],
|
|
168
|
+
[SDK_FILE, SCRIPT_SDK_TYPES],
|
|
169
|
+
[STDLIB_FILE, SCRIPT_STDLIB_TYPES],
|
|
170
|
+
[
|
|
171
|
+
CHECK_FILE,
|
|
172
|
+
`import run from "./user-script";
|
|
173
|
+
import type { ScriptMain } from "swarm-sdk";
|
|
174
|
+
const _scriptMain: ScriptMain = run;
|
|
175
|
+
void _scriptMain;
|
|
176
|
+
`,
|
|
177
|
+
],
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
const host = createCompilerHost(files, options);
|
|
181
|
+
const program = ts.createProgram([USER_FILE, CHECK_FILE, SDK_FILE, STDLIB_FILE], options, host);
|
|
182
|
+
const diagnostics = [
|
|
183
|
+
...program.getSyntacticDiagnostics(),
|
|
184
|
+
...program.getSemanticDiagnostics(),
|
|
185
|
+
].filter((diagnostic) => {
|
|
186
|
+
const fileName = diagnostic.file?.fileName.replace(/\\/g, "/");
|
|
187
|
+
return fileName === USER_FILE || fileName === CHECK_FILE;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (diagnostics.length === 0) return { ok: true };
|
|
191
|
+
|
|
192
|
+
const formatted = ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
193
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
194
|
+
getCurrentDirectory: () => "/virtual",
|
|
195
|
+
getNewLine: () => "\n",
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return { ok: false, diagnostics: formatted.split("\n\n").filter(Boolean) };
|
|
199
|
+
}
|
package/src/cli.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { runLead } from "./commands/lead.ts";
|
|
|
9
9
|
import { Onboard } from "./commands/onboard.tsx";
|
|
10
10
|
import { Setup as Connect } from "./commands/setup.tsx";
|
|
11
11
|
import { runWorker } from "./commands/worker.ts";
|
|
12
|
+
import { getApiKey, setApiKey } from "./utils/api-key.ts";
|
|
12
13
|
|
|
13
14
|
// Get CLI name from bin field (assumes single key)
|
|
14
15
|
const binName = Object.keys(pkg.bin)[0];
|
|
@@ -43,7 +44,7 @@ interface ParsedArgs {
|
|
|
43
44
|
function parseArgs(args: string[]): ParsedArgs {
|
|
44
45
|
const command = args[0] && !args[0].startsWith("-") ? args[0] : undefined;
|
|
45
46
|
let port = process.env.PORT || "3013";
|
|
46
|
-
let key =
|
|
47
|
+
let key = getApiKey();
|
|
47
48
|
let msg = "";
|
|
48
49
|
let headless = false;
|
|
49
50
|
let dryRun = false;
|
|
@@ -151,7 +152,7 @@ const COMMAND_HELP: Record<
|
|
|
151
152
|
connect: {
|
|
152
153
|
usage: `${binName} connect [options]`,
|
|
153
154
|
description:
|
|
154
|
-
"Connect this project to an existing swarm.\nCreates .mcp.json and .claude/settings.local.json with server URL and API key.\nAuto-reads API_KEY from .env if present.",
|
|
155
|
+
"Connect this project to an existing swarm.\nCreates .mcp.json and .claude/settings.local.json with server URL and API key.\nAuto-reads AGENT_SWARM_API_KEY (or legacy API_KEY) from .env if present.",
|
|
155
156
|
options: [
|
|
156
157
|
" --dry-run Show what would be changed without writing",
|
|
157
158
|
" --restore Restore files from .bak backups",
|
|
@@ -248,13 +249,19 @@ const COMMAND_HELP: Record<
|
|
|
248
249
|
options: " -h, --help Show this help",
|
|
249
250
|
examples: [` ${binName} artifact serve`, ` ${binName} artifact help`].join("\n"),
|
|
250
251
|
},
|
|
252
|
+
scripts: {
|
|
253
|
+
usage: `${binName} scripts reembed`,
|
|
254
|
+
description: "Maintenance commands for reusable swarm scripts.",
|
|
255
|
+
options: " -h, --help Show this help",
|
|
256
|
+
examples: ` ${binName} scripts reembed`,
|
|
257
|
+
},
|
|
251
258
|
"codex-login": {
|
|
252
259
|
usage: `${binName} codex-login [options]`,
|
|
253
260
|
description:
|
|
254
261
|
"Authenticate Codex via ChatGPT OAuth (browser or manual paste).\nPrompts interactively for the target API URL and a best-effort masked API key, then stores credentials in the swarm API config store for deployed workers.",
|
|
255
262
|
options: [
|
|
256
263
|
" --api-url <url> Swarm API URL (default: MCP_BASE_URL or http://localhost:3013)",
|
|
257
|
-
" --api-key <key> Swarm API key (default:
|
|
264
|
+
" --api-key <key> Swarm API key (default: AGENT_SWARM_API_KEY or API_KEY, falling back to 123123)",
|
|
258
265
|
" -h, --help Show this help",
|
|
259
266
|
].join("\n"),
|
|
260
267
|
examples: [
|
|
@@ -269,7 +276,7 @@ const COMMAND_HELP: Record<
|
|
|
269
276
|
"Bootstrap Anthropic Managed Agents for the swarm: create the cloud environment, upload plugin/commands/*.md skills, create the managed agent, and persist the resulting IDs to swarm_config so deployed workers restore them at boot. Prompts interactively for ANTHROPIC_API_KEY when not set in env. Idempotent — re-run with --force to recreate.",
|
|
270
277
|
options: [
|
|
271
278
|
" --api-url <url> Swarm API URL (default: MCP_BASE_URL or http://localhost:3013)",
|
|
272
|
-
" --api-key <key> Swarm API key (default:
|
|
279
|
+
" --api-key <key> Swarm API key (default: AGENT_SWARM_API_KEY or API_KEY, falling back to 123123)",
|
|
273
280
|
" --force Recreate Anthropic-side resources even if already configured",
|
|
274
281
|
" -h, --help Show this help",
|
|
275
282
|
].join("\n"),
|
|
@@ -306,6 +313,7 @@ function printHelp(command?: string) {
|
|
|
306
313
|
["claude", "Run Claude CLI"],
|
|
307
314
|
["hook", "Handle Claude Code hook events (stdin)"],
|
|
308
315
|
["artifact", "Manage agent artifacts"],
|
|
316
|
+
["scripts", "Reusable scripts maintenance"],
|
|
309
317
|
["docs", "Open documentation (--open to launch in browser)"],
|
|
310
318
|
["codex-login", "Authenticate Codex via ChatGPT OAuth"],
|
|
311
319
|
["claude-managed-setup", "Bootstrap Anthropic Managed Agents (agent + env + skills)"],
|
|
@@ -324,7 +332,7 @@ function McpServer({ port, apiKey, dbPath }: { port: string; apiKey: string; dbP
|
|
|
324
332
|
|
|
325
333
|
useEffect(() => {
|
|
326
334
|
process.env.PORT = port;
|
|
327
|
-
|
|
335
|
+
setApiKey(apiKey);
|
|
328
336
|
if (dbPath) {
|
|
329
337
|
process.env.DATABASE_PATH = dbPath;
|
|
330
338
|
}
|
|
@@ -547,6 +555,15 @@ if (args.showHelp || args.command === "help" || args.command === undefined) {
|
|
|
547
555
|
port: args.port,
|
|
548
556
|
key: args.key,
|
|
549
557
|
});
|
|
558
|
+
} else if (args.command === "scripts") {
|
|
559
|
+
const scriptsArgs = process.argv.slice(process.argv.indexOf("scripts") + 1);
|
|
560
|
+
if (args.showHelp || scriptsArgs[0] !== "reembed") {
|
|
561
|
+
printHelp("scripts");
|
|
562
|
+
process.exit(scriptsArgs[0] === "reembed" || args.showHelp ? 0 : 1);
|
|
563
|
+
}
|
|
564
|
+
const { runScriptsMaintenanceCommand } = await import("./be/scripts/maintenance");
|
|
565
|
+
await runScriptsMaintenanceCommand(scriptsArgs);
|
|
566
|
+
console.log("Scripts re-embedded.");
|
|
550
567
|
} else if (args.command === "codex-login") {
|
|
551
568
|
const { runCodexLogin } = await import("./commands/codex-login");
|
|
552
569
|
const codexLoginArgs = process.argv.slice(process.argv.indexOf("codex-login") + 1);
|
package/src/commands/artifact.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { createArtifactServer } from "../artifact-sdk";
|
|
3
|
+
import { getApiKey } from "../utils/api-key";
|
|
3
4
|
|
|
4
5
|
interface ArtifactArgs {
|
|
5
6
|
additionalArgs: string[];
|
|
@@ -137,7 +138,7 @@ async function artifactServe(args: ArtifactArgs) {
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
async function artifactList() {
|
|
140
|
-
const apiKey =
|
|
141
|
+
const apiKey = getApiKey();
|
|
141
142
|
const mcpBaseUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
142
143
|
const agentId = process.env.AGENT_ID || "";
|
|
143
144
|
|
|
@@ -195,7 +196,7 @@ async function artifactStop(args: ArtifactArgs) {
|
|
|
195
196
|
process.exit(1);
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
const apiKey =
|
|
199
|
+
const apiKey = getApiKey();
|
|
199
200
|
const mcpBaseUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
200
201
|
const agentId = process.env.AGENT_ID || "";
|
|
201
202
|
|
|
@@ -33,6 +33,7 @@ import type { BetaEnvironment } from "@anthropic-ai/sdk/resources/beta/environme
|
|
|
33
33
|
import type { SkillCreateResponse } from "@anthropic-ai/sdk/resources/beta/skills";
|
|
34
34
|
import { toFile } from "@anthropic-ai/sdk/uploads";
|
|
35
35
|
|
|
36
|
+
import { getApiKey } from "../utils/api-key";
|
|
36
37
|
import { promptHiddenInput } from "./codex-login.js";
|
|
37
38
|
|
|
38
39
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -397,7 +398,7 @@ export async function resolveClaudeManagedSetupConfig(
|
|
|
397
398
|
const isInteractive = deps.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
398
399
|
|
|
399
400
|
const apiUrl = parsed.apiUrl ?? env.MCP_BASE_URL ?? "http://localhost:3013";
|
|
400
|
-
const apiKey = parsed.apiKey ?? env
|
|
401
|
+
const apiKey = parsed.apiKey ?? (getApiKey(env) || "123123");
|
|
401
402
|
|
|
402
403
|
let anthropicApiKey = env.ANTHROPIC_API_KEY ?? "";
|
|
403
404
|
if (!anthropicApiKey && isInteractive) {
|
|
@@ -14,6 +14,7 @@ import { emitKeypressEvents } from "node:readline";
|
|
|
14
14
|
|
|
15
15
|
import { loginCodexOAuth } from "../providers/codex-oauth/flow.js";
|
|
16
16
|
import { storeCodexOAuth } from "../providers/codex-oauth/storage.js";
|
|
17
|
+
import { getApiKey } from "../utils/api-key";
|
|
17
18
|
|
|
18
19
|
type PromptTextFn = (label: string, defaultValue: string) => Promise<string>;
|
|
19
20
|
type PromptSecretFn = (label: string, defaultValue: string, helpText?: string) => Promise<string>;
|
|
@@ -146,7 +147,8 @@ export async function resolveCodexLoginConfig(
|
|
|
146
147
|
const promptSecret = deps.promptSecret ?? promptHiddenInput;
|
|
147
148
|
const isInteractive = deps.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
148
149
|
const defaultApiUrl = env.MCP_BASE_URL || "http://localhost:3013";
|
|
149
|
-
const
|
|
150
|
+
const envApiKey = getApiKey(env);
|
|
151
|
+
const defaultApiKey = envApiKey || "123123";
|
|
150
152
|
|
|
151
153
|
let apiUrl = parsed.apiUrl ?? defaultApiUrl;
|
|
152
154
|
let apiKey = parsed.apiKey ?? defaultApiKey;
|
|
@@ -156,8 +158,8 @@ export async function resolveCodexLoginConfig(
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
if (!parsed.apiKey && isInteractive) {
|
|
159
|
-
const apiKeyHelp =
|
|
160
|
-
? "Press Enter to use API_KEY from the environment"
|
|
161
|
+
const apiKeyHelp = envApiKey
|
|
162
|
+
? "Press Enter to use AGENT_SWARM_API_KEY/API_KEY from the environment"
|
|
161
163
|
: "Press Enter to use the default local API key";
|
|
162
164
|
apiKey =
|
|
163
165
|
(await promptSecret("Swarm API key", defaultApiKey, apiKeyHelp)).trim() || defaultApiKey;
|
package/src/commands/onboard.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Select } from "@inkjs/ui";
|
|
|
3
3
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
5
|
import pkg from "../../package.json";
|
|
6
|
+
import { getApiKey } from "../utils/api-key.ts";
|
|
6
7
|
import { getAgentSummary, getPresetById, PRESETS } from "./onboard/presets.ts";
|
|
7
8
|
import { CoreCredentialsStep } from "./onboard/steps/core-credentials.tsx";
|
|
8
9
|
import { CustomTemplatesStep } from "./onboard/steps/custom-templates.tsx";
|
|
@@ -140,7 +141,7 @@ export function Onboard({ dryRun = false, yes = false, preset }: OnboardProps) {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
const credentialType = anthropicKey ? "api_key" : "oauth";
|
|
143
|
-
const apiKey =
|
|
144
|
+
const apiKey = getApiKey() || crypto.randomBytes(16).toString("hex");
|
|
144
145
|
|
|
145
146
|
const agentIds: Record<string, string> = {};
|
|
146
147
|
for (const svc of selectedPreset.services) {
|
package/src/commands/runner.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type Attributes,
|
|
7
7
|
initOtel,
|
|
8
8
|
injectTraceContext,
|
|
9
|
+
isPollTracingEnabled,
|
|
9
10
|
type SwarmSpan,
|
|
10
11
|
startSpan,
|
|
11
12
|
withSpan,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
} from "../providers/index.ts";
|
|
32
33
|
import { initTelemetry, telemetry } from "../telemetry.ts";
|
|
33
34
|
import type { ProviderName, RepoGuidelines } from "../types.ts";
|
|
35
|
+
import { getApiKey } from "../utils/api-key.ts";
|
|
34
36
|
import { computeBudgetBackoffMs } from "../utils/budget-backoff.ts";
|
|
35
37
|
import { getContextWindowSize } from "../utils/context-window.ts";
|
|
36
38
|
import { type CredentialSelection, resolveCredentialPools } from "../utils/credentials.ts";
|
|
@@ -366,7 +368,17 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
|
|
|
366
368
|
"inject-learning": "🧠 Storing learning",
|
|
367
369
|
"memory-search": "🧠 Searching memory",
|
|
368
370
|
"memory-get": "🧠 Retrieving memory",
|
|
371
|
+
"memory-delete": "🧠 Deleting memory",
|
|
369
372
|
"update-profile": "🪪 Updating profile",
|
|
373
|
+
// Users
|
|
374
|
+
"manage-user": "👤 Managing user",
|
|
375
|
+
"resolve-user": "👤 Resolving user",
|
|
376
|
+
// Key-value store
|
|
377
|
+
"kv-get": "🔑 Reading KV value",
|
|
378
|
+
"kv-set": "🔑 Setting KV value",
|
|
379
|
+
"kv-list": "🔑 Listing KV keys",
|
|
380
|
+
"kv-delete": "🔑 Deleting KV value",
|
|
381
|
+
"kv-incr": "🔑 Incrementing KV value",
|
|
370
382
|
// Slack
|
|
371
383
|
"slack-post": "💬 Posting to Slack",
|
|
372
384
|
"slack-start-thread": "💬 Starting Slack thread",
|
|
@@ -386,20 +398,37 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
|
|
|
386
398
|
"get-workflow": "⚙️ Checking workflow",
|
|
387
399
|
"list-workflows": "⚙️ Listing workflows",
|
|
388
400
|
"create-workflow": "⚙️ Creating workflow",
|
|
401
|
+
"update-workflow": "⚙️ Updating workflow",
|
|
402
|
+
"delete-workflow": "⚙️ Deleting workflow",
|
|
403
|
+
"patch-workflow": "⚙️ Patching workflow",
|
|
404
|
+
"patch-workflow-node": "⚙️ Patching workflow node",
|
|
405
|
+
"get-workflow-run": "⚙️ Checking workflow run",
|
|
406
|
+
"list-workflow-runs": "⚙️ Listing workflow runs",
|
|
407
|
+
"cancel-workflow-run": "⚙️ Cancelling workflow run",
|
|
408
|
+
"retry-workflow-run": "⚙️ Retrying workflow run",
|
|
389
409
|
// Skills
|
|
390
410
|
"skill-search": "🔎 Searching skills",
|
|
391
411
|
"skill-install": "📦 Installing skill",
|
|
392
412
|
"skill-install-remote": "📦 Installing remote skill",
|
|
393
413
|
"skill-get": "📦 Getting skill details",
|
|
394
414
|
"skill-list": "📦 Listing skills",
|
|
415
|
+
"skill-create": "📦 Creating skill",
|
|
416
|
+
"skill-update": "📦 Updating skill",
|
|
417
|
+
"skill-delete": "📦 Deleting skill",
|
|
418
|
+
"skill-publish": "📦 Publishing skill",
|
|
419
|
+
"skill-uninstall": "📦 Uninstalling skill",
|
|
420
|
+
"skill-sync-remote": "📦 Syncing remote skills",
|
|
395
421
|
// Config
|
|
396
422
|
"get-config": "⚙️ Reading config",
|
|
397
423
|
"set-config": "⚙️ Setting config",
|
|
398
424
|
"list-config": "⚙️ Listing config",
|
|
425
|
+
"delete-config": "⚙️ Deleting config",
|
|
399
426
|
// Schedules
|
|
400
427
|
"create-schedule": "📅 Creating schedule",
|
|
401
428
|
"list-schedules": "📅 Listing schedules",
|
|
402
429
|
"run-schedule-now": "📅 Running schedule",
|
|
430
|
+
"update-schedule": "📅 Updating schedule",
|
|
431
|
+
"delete-schedule": "📅 Deleting schedule",
|
|
403
432
|
// Context
|
|
404
433
|
"context-diff": "📜 Viewing context diff",
|
|
405
434
|
"context-history": "📜 Viewing context history",
|
|
@@ -412,12 +441,42 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
|
|
|
412
441
|
"list-services": "🔌 Listing services",
|
|
413
442
|
"unregister-service": "🔌 Unregistering service",
|
|
414
443
|
"update-service-status": "🔌 Updating service status",
|
|
444
|
+
// Reusable scripts
|
|
445
|
+
"script-search": "📜 Searching scripts",
|
|
446
|
+
"script-run": "📜 Running script",
|
|
447
|
+
"script-upsert": "📜 Saving script",
|
|
448
|
+
"script-delete": "📜 Deleting script",
|
|
449
|
+
"script-query-types": "📜 Reading script types",
|
|
415
450
|
};
|
|
416
451
|
|
|
417
|
-
/**
|
|
452
|
+
/** Words that keep specific casing when humanizing tool names. */
|
|
453
|
+
const TOOL_NAME_ACRONYMS: Record<string, string> = {
|
|
454
|
+
mcp: "MCP",
|
|
455
|
+
kv: "KV",
|
|
456
|
+
api: "API",
|
|
457
|
+
url: "URL",
|
|
458
|
+
id: "ID",
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Convert kebab/snake-case to sentence case, preserving known acronyms.
|
|
463
|
+
* "get-task-details" → "Get task details"; "mcp-server-create" → "MCP server create".
|
|
464
|
+
*/
|
|
418
465
|
export function humanizeToolName(name: string): string {
|
|
419
466
|
if (!name) return name;
|
|
420
|
-
|
|
467
|
+
const words = name
|
|
468
|
+
.replaceAll("_", " ")
|
|
469
|
+
.replaceAll("-", " ")
|
|
470
|
+
.trim()
|
|
471
|
+
.split(/\s+/)
|
|
472
|
+
.filter(Boolean)
|
|
473
|
+
.map((w) => TOOL_NAME_ACRONYMS[w.toLowerCase()] ?? w);
|
|
474
|
+
const first = words[0];
|
|
475
|
+
if (!first) return name;
|
|
476
|
+
const head = TOOL_NAME_ACRONYMS[first.toLowerCase()]
|
|
477
|
+
? first
|
|
478
|
+
: first.charAt(0).toUpperCase() + first.slice(1);
|
|
479
|
+
return [head, ...words.slice(1)].join(" ");
|
|
421
480
|
}
|
|
422
481
|
|
|
423
482
|
/**
|
|
@@ -487,15 +546,13 @@ export function toolCallToProgress(toolName: string, args: unknown): string | nu
|
|
|
487
546
|
}
|
|
488
547
|
|
|
489
548
|
// Pi-mono exposes tools from the built-in swarm MCP endpoint as bare
|
|
490
|
-
// names ("store-progress", "send-task", ...), not as mcp__ names.
|
|
549
|
+
// names ("store-progress", "send-task", "script-run", ...), not as mcp__ names.
|
|
491
550
|
// Treat those names as agent-swarm tools so activity stays readable.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (label) return label;
|
|
496
|
-
}
|
|
551
|
+
const label = SWARM_TOOL_LABELS[toolName];
|
|
552
|
+
if (label === null) return null;
|
|
553
|
+
if (label) return label;
|
|
497
554
|
|
|
498
|
-
return `🔧 ${toolName}`;
|
|
555
|
+
return `🔧 ${humanizeToolName(toolName)}`;
|
|
499
556
|
}
|
|
500
557
|
}
|
|
501
558
|
}
|
|
@@ -1513,6 +1570,9 @@ async function registerAgent(opts: {
|
|
|
1513
1570
|
|
|
1514
1571
|
/** Poll for triggers via HTTP API */
|
|
1515
1572
|
async function pollForTrigger(opts: PollOptions): Promise<Trigger | null> {
|
|
1573
|
+
if (!isPollTracingEnabled()) {
|
|
1574
|
+
return pollForTriggerOnce(opts);
|
|
1575
|
+
}
|
|
1516
1576
|
return withSpan(
|
|
1517
1577
|
"worker.poll",
|
|
1518
1578
|
async (span) => {
|
|
@@ -1917,6 +1977,47 @@ function providerEventAttributes(event: ProviderEvent): Attributes {
|
|
|
1917
1977
|
}
|
|
1918
1978
|
}
|
|
1919
1979
|
|
|
1980
|
+
/**
|
|
1981
|
+
* Entry shape for the `activeToolSpans` map maintained by `runWithSession`.
|
|
1982
|
+
* Exported for unit tests that exercise `implicitCloseActiveToolSpans`.
|
|
1983
|
+
*/
|
|
1984
|
+
export type ActiveToolSpanEntry = {
|
|
1985
|
+
span: SwarmSpan;
|
|
1986
|
+
startedAt: number;
|
|
1987
|
+
};
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* Closes any still-open tool spans (both `worker.tool` and `worker.mcp.tool`)
|
|
1991
|
+
* in `activeToolSpans` at the assistant-message boundary, tagging them with
|
|
1992
|
+
* `agentswarm.tool.implicit_close=true` and removing them from the map.
|
|
1993
|
+
*
|
|
1994
|
+
* The Claude SDK adapter doesn't emit per-tool completion events for any
|
|
1995
|
+
* tool kind, MCP or harness-side — so the assistant-message boundary serves
|
|
1996
|
+
* as an implicit `tool_end` for everything. Spans closed here are still
|
|
1997
|
+
* successful (code 1, OK); the boundary is just a signal that the prior
|
|
1998
|
+
* tool turn is done.
|
|
1999
|
+
*
|
|
2000
|
+
* Returns the number of spans closed (handy for tests / metrics).
|
|
2001
|
+
*/
|
|
2002
|
+
export function implicitCloseActiveToolSpans(
|
|
2003
|
+
activeToolSpans: Map<string, ActiveToolSpanEntry>,
|
|
2004
|
+
now: number = Date.now(),
|
|
2005
|
+
): number {
|
|
2006
|
+
let closed = 0;
|
|
2007
|
+
for (const [toolCallId, active] of activeToolSpans) {
|
|
2008
|
+
active.span.setAttributes({
|
|
2009
|
+
"agentswarm.tool.duration_ms": now - active.startedAt,
|
|
2010
|
+
"agentswarm.tool.implicit_close": true,
|
|
2011
|
+
"agentswarm.tool.call_id": toolCallId,
|
|
2012
|
+
});
|
|
2013
|
+
active.span.setStatus({ code: 1 });
|
|
2014
|
+
active.span.end();
|
|
2015
|
+
activeToolSpans.delete(toolCallId);
|
|
2016
|
+
closed++;
|
|
2017
|
+
}
|
|
2018
|
+
return closed;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1920
2021
|
async function spawnProviderProcess(
|
|
1921
2022
|
adapter: ReturnType<typeof createProviderAdapter>,
|
|
1922
2023
|
opts: {
|
|
@@ -2155,6 +2256,18 @@ async function spawnProviderProcess(
|
|
|
2155
2256
|
sessionId: event.sessionId,
|
|
2156
2257
|
});
|
|
2157
2258
|
break;
|
|
2259
|
+
case "message": {
|
|
2260
|
+
// Assistant-message boundary acts as an implicit tool_end for ALL
|
|
2261
|
+
// still-open tool spans (both `worker.tool` and `worker.mcp.tool`).
|
|
2262
|
+
// The Claude SDK adapter doesn't emit per-tool completion events for
|
|
2263
|
+
// any tool kind, so without this their spans would only close at
|
|
2264
|
+
// session shutdown via `closeActiveToolSpans` and report wall-clock
|
|
2265
|
+
// duration from tool_start to session end.
|
|
2266
|
+
if (event.role === "assistant") {
|
|
2267
|
+
implicitCloseActiveToolSpans(activeToolSpans);
|
|
2268
|
+
}
|
|
2269
|
+
break;
|
|
2270
|
+
}
|
|
2158
2271
|
case "tool_start": {
|
|
2159
2272
|
const tool = classifyTool(event.toolName, event.args);
|
|
2160
2273
|
const toolSpan = startSpan(tool.kind === "mcp" ? "worker.mcp.tool" : "worker.tool", {
|
|
@@ -2619,15 +2732,35 @@ async function checkCompletedProcesses(
|
|
|
2619
2732
|
credentialInfo &&
|
|
2620
2733
|
/rate.?limit|hit your limit|usage[ _-]?limit|too many requests/i.test(failureReason)
|
|
2621
2734
|
) {
|
|
2622
|
-
//
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2735
|
+
// Three-tier reset-time resolver (most to least precise):
|
|
2736
|
+
// Tier 1: structured rate_limit_event from Claude CLI (resetsAt epoch sec)
|
|
2737
|
+
// Tier 2: regex on the error message (e.g. "resets 3pm (UTC)")
|
|
2738
|
+
// Tier 3: 5-min hard fallback — only when both structured and regex fail
|
|
2739
|
+
// Tiers 1 & 2 are clamped to [now+60s, now+6h] at their source.
|
|
2740
|
+
const clampResetTime = (isoString: string): string => {
|
|
2741
|
+
const nowMs = Date.now();
|
|
2742
|
+
const minMs = nowMs + 60_000;
|
|
2743
|
+
const maxMs = nowMs + 6 * 60 * 60 * 1000;
|
|
2744
|
+
const candidateMs = new Date(isoString).getTime();
|
|
2745
|
+
return new Date(Math.min(Math.max(candidateMs, minMs), maxMs)).toISOString();
|
|
2746
|
+
};
|
|
2747
|
+
|
|
2748
|
+
let rateLimitedUntil: string;
|
|
2749
|
+
if (result.rateLimitResetAt) {
|
|
2750
|
+
rateLimitedUntil = clampResetTime(result.rateLimitResetAt);
|
|
2628
2751
|
console.log(
|
|
2629
|
-
`[credentials]
|
|
2752
|
+
`[credentials] Rate limit reset from rate_limit_event: ${rateLimitedUntil}`,
|
|
2630
2753
|
);
|
|
2754
|
+
} else {
|
|
2755
|
+
const parsedResetTime = parseRateLimitResetTime(failureReason);
|
|
2756
|
+
if (parsedResetTime) {
|
|
2757
|
+
rateLimitedUntil = clampResetTime(parsedResetTime);
|
|
2758
|
+
console.log(
|
|
2759
|
+
`[credentials] Parsed rate limit reset time from error: ${rateLimitedUntil}`,
|
|
2760
|
+
);
|
|
2761
|
+
} else {
|
|
2762
|
+
rateLimitedUntil = new Date(Date.now() + 5 * 60 * 1000).toISOString();
|
|
2763
|
+
}
|
|
2631
2764
|
}
|
|
2632
2765
|
reportKeyRateLimit(
|
|
2633
2766
|
apiConfig.apiUrl,
|
|
@@ -2772,7 +2905,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
2772
2905
|
|
|
2773
2906
|
const apiUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
2774
2907
|
const swarmUrl = process.env.SWARM_URL || "localhost";
|
|
2775
|
-
const apiKey =
|
|
2908
|
+
const apiKey = getApiKey();
|
|
2776
2909
|
|
|
2777
2910
|
// Resolve the boot harness provider from swarm_config (repo > agent > global,
|
|
2778
2911
|
// overlaid on top of `process.env`). This is what selects the adapter for
|
|
@@ -2798,8 +2931,8 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
2798
2931
|
let adapter = createProviderAdapter(bootProvider);
|
|
2799
2932
|
|
|
2800
2933
|
// Configure HTTP-based template resolution (workers resolve via API, not local DB)
|
|
2801
|
-
if (
|
|
2802
|
-
configureHttpResolver(apiUrl,
|
|
2934
|
+
if (apiKey) {
|
|
2935
|
+
configureHttpResolver(apiUrl, apiKey);
|
|
2803
2936
|
}
|
|
2804
2937
|
|
|
2805
2938
|
// Initialize anonymized telemetry (opt-out via ANONYMIZED_TELEMETRY=false).
|
|
@@ -2810,7 +2943,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
2810
2943
|
// skips telemetry instead of minting a fresh `install_<hex>` ID per
|
|
2811
2944
|
// restart, which floods prod metrics with phantom installs.
|
|
2812
2945
|
{
|
|
2813
|
-
const telemetryApiKey =
|
|
2946
|
+
const telemetryApiKey = apiKey || undefined;
|
|
2814
2947
|
await initTelemetry(
|
|
2815
2948
|
"worker",
|
|
2816
2949
|
async (key) => {
|