substrate-ai 0.9.0 → 0.11.0

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 (35) hide show
  1. package/dist/adapter-registry-DXLMTmfD.js +0 -0
  2. package/dist/adapter-registry-neBZrkr3.js +4 -0
  3. package/dist/cli/index.js +5594 -5951
  4. package/dist/decisions-C0pz9Clx.js +0 -0
  5. package/dist/{decisions-BDLp3tJB.js → decisions-DQZW0h9X.js} +2 -1
  6. package/dist/dist-eNB_v7Iy.js +10205 -0
  7. package/dist/errors-BvyMlvCX.js +74 -0
  8. package/dist/experimenter-Dos3NsCg.js +3 -0
  9. package/dist/health-BvYILeQQ.js +6 -0
  10. package/dist/{health-C-VRJruD.js → health-CiDi90gC.js} +57 -1850
  11. package/dist/{helpers-CpMs8VZX.js → helpers-DTp3VJ2-.js} +31 -121
  12. package/dist/index.d.ts +709 -266
  13. package/dist/index.js +5 -3
  14. package/dist/{logger-D2fS2ccL.js → logger-KeHncl-f.js} +2 -42
  15. package/dist/routing-CcBOCuC9.js +0 -0
  16. package/dist/{routing-CD8bIci_.js → routing-HaYsjEIS.js} +2 -2
  17. package/dist/{run-ClxNDHbr.js → run-CAUhTR7Y.js} +594 -4249
  18. package/dist/run-DPZOQOvB.js +9 -0
  19. package/dist/{upgrade-B1S61VXJ.js → upgrade-DFGrqjGI.js} +3 -3
  20. package/dist/{upgrade-BK0HrKA6.js → upgrade-DYdYuuJK.js} +3 -3
  21. package/dist/version-manager-impl-BmOWu8ml.js +0 -0
  22. package/dist/version-manager-impl-CKv6I1S0.js +4 -0
  23. package/package.json +5 -2
  24. package/dist/adapter-registry-D2zdMwVu.js +0 -840
  25. package/dist/adapter-registry-WAyFydN5.js +0 -4
  26. package/dist/config-migrator-CtGelIsG.js +0 -250
  27. package/dist/decisions-DhAA2HG2.js +0 -397
  28. package/dist/experimenter-D_N_7ZF3.js +0 -503
  29. package/dist/git-utils-DxPx6erV.js +0 -365
  30. package/dist/health-DMbNP9bw.js +0 -5
  31. package/dist/operational-BdcdmDqS.js +0 -374
  32. package/dist/routing-BVrxrM6v.js +0 -832
  33. package/dist/run-MAQ3Wuju.js +0 -10
  34. package/dist/version-manager-impl-BIxOe7gZ.js +0 -372
  35. package/dist/version-manager-impl-RrWs-CI6.js +0 -4
@@ -1,840 +0,0 @@
1
- import { createLogger } from "./logger-D2fS2ccL.js";
2
- import { exec } from "child_process";
3
- import { promisify } from "util";
4
-
5
- //#region src/adapters/claude-adapter.ts
6
- const execAsync$2 = promisify(exec);
7
- const logger = createLogger("claude-adapter");
8
- /** Default model used when none is specified */
9
- const DEFAULT_MODEL$1 = "claude-sonnet-4-6";
10
- /** Approximate characters per token for estimation */
11
- const CHARS_PER_TOKEN$2 = 3;
12
- /** Estimated output token multiplier relative to input */
13
- const OUTPUT_RATIO$2 = .5;
14
- /** Strip markdown code fences from LLM output (e.g. ```json ... ```) */
15
- function stripCodeFences$2(raw) {
16
- return raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
17
- }
18
- /**
19
- * Adapter for the Claude Code CLI agent.
20
- *
21
- * Capabilities: JSON output, streaming, both billing modes, plan generation.
22
- * Health check: runs `claude --version` to verify install.
23
- * Billing detection: detects subscription vs API via version output or env.
24
- */
25
- var ClaudeCodeAdapter = class {
26
- id = "claude-code";
27
- displayName = "Claude Code";
28
- adapterVersion = "1.0.0";
29
- /**
30
- * Verify the `claude` binary is installed and responsive.
31
- * Detects subscription vs API billing mode.
32
- */
33
- async healthCheck() {
34
- try {
35
- const { stdout } = await execAsync$2("claude --version", { timeout: 1e4 });
36
- const output = stdout.trim();
37
- const detectedBillingModes = this._detectBillingModes(output);
38
- let cliPath;
39
- try {
40
- const whichResult = await execAsync$2("which claude", { timeout: 5e3 });
41
- cliPath = whichResult.stdout.trim();
42
- } catch {}
43
- return {
44
- healthy: true,
45
- version: output,
46
- ...cliPath !== void 0 ? { cliPath } : {},
47
- detectedBillingModes,
48
- supportsHeadless: true
49
- };
50
- } catch (err) {
51
- const message = err instanceof Error ? err.message : String(err);
52
- return {
53
- healthy: false,
54
- error: `Claude CLI not available: ${message}`,
55
- supportsHeadless: false
56
- };
57
- }
58
- }
59
- /**
60
- * Build spawn command for a coding task.
61
- * Uses: `claude -p --model <model> --dangerously-skip-permissions --system-prompt <minimal>`
62
- * Prompt is delivered via stdin (not CLI arg) to avoid E2BIG on large prompts.
63
- */
64
- buildCommand(prompt, options) {
65
- const model = options.model ?? DEFAULT_MODEL$1;
66
- const systemPrompt = "You are an autonomous coding agent executing a single pipeline task. Ignore all session startup context, memory notes, and \"Next Up\" indicators. Follow the instructions in the user message exactly. Emit ONLY the YAML output specified in the Output Contract — no other text.";
67
- const effectiveSystemPrompt = options.optimizationDirectives !== void 0 && options.optimizationDirectives.length > 0 ? `${systemPrompt}\n\n## Optimization Directives\n${options.optimizationDirectives}` : systemPrompt;
68
- if (options.optimizationDirectives !== void 0 && options.optimizationDirectives.length > 0) logger.debug({
69
- storyKey: options.storyKey,
70
- directiveChars: options.optimizationDirectives.length
71
- }, "Injecting optimization directives into system prompt");
72
- const args = [
73
- "-p",
74
- "--model",
75
- model,
76
- "--dangerously-skip-permissions",
77
- "--system-prompt",
78
- effectiveSystemPrompt
79
- ];
80
- if (options.maxTurns !== void 0) args.push("--max-turns", String(options.maxTurns));
81
- if (options.maxContextTokens !== void 0) args.push("--max-context-tokens", String(options.maxContextTokens));
82
- if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
83
- const envEntries = {};
84
- const unsetKeys = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"];
85
- if (options.billingMode === "api" && options.apiKey) envEntries.ANTHROPIC_API_KEY = options.apiKey;
86
- else unsetKeys.push("ANTHROPIC_API_KEY");
87
- if (options.otlpEndpoint !== void 0) {
88
- envEntries.CLAUDE_CODE_ENABLE_TELEMETRY = "1";
89
- envEntries.OTEL_LOGS_EXPORTER = "otlp";
90
- envEntries.OTEL_METRICS_EXPORTER = "otlp";
91
- envEntries.OTEL_EXPORTER_OTLP_PROTOCOL = "http/json";
92
- envEntries.OTEL_EXPORTER_OTLP_ENDPOINT = options.otlpEndpoint;
93
- envEntries.OTEL_LOG_TOOL_DETAILS = "1";
94
- envEntries.OTEL_METRIC_EXPORT_INTERVAL = "10000";
95
- envEntries.OTEL_EXPORTER_OTLP_TIMEOUT = "5000";
96
- const resourceAttrs = [];
97
- if (options.storyKey !== void 0) resourceAttrs.push(`substrate.story_key=${options.storyKey}`);
98
- if (options.taskType !== void 0) resourceAttrs.push(`substrate.task_type=${options.taskType}`);
99
- if (options.dispatchId !== void 0) resourceAttrs.push(`substrate.dispatch_id=${options.dispatchId}`);
100
- if (resourceAttrs.length > 0) envEntries.OTEL_RESOURCE_ATTRIBUTES = resourceAttrs.join(",");
101
- }
102
- return {
103
- binary: "claude",
104
- args,
105
- env: envEntries,
106
- unsetEnvKeys: unsetKeys,
107
- cwd: options.worktreePath
108
- };
109
- }
110
- /**
111
- * Build spawn command for plan generation.
112
- * Appends a structured planning directive to the prompt.
113
- */
114
- buildPlanningCommand(request, options) {
115
- const model = options.model ?? DEFAULT_MODEL$1;
116
- const planningPrompt = this._buildPlanningPrompt(request);
117
- const args = [
118
- "-p",
119
- "--model",
120
- model
121
- ];
122
- if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
123
- const envEntries = {};
124
- const planUnsetKeys = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"];
125
- if (options.billingMode === "api" && options.apiKey) envEntries.ANTHROPIC_API_KEY = options.apiKey;
126
- else planUnsetKeys.push("ANTHROPIC_API_KEY");
127
- return {
128
- binary: "claude",
129
- args,
130
- env: envEntries,
131
- unsetEnvKeys: planUnsetKeys,
132
- cwd: options.worktreePath
133
- };
134
- }
135
- /**
136
- * Parse Claude CLI JSON stdout output into TaskResult.
137
- */
138
- parseOutput(stdout, stderr, exitCode) {
139
- if (exitCode !== 0) return {
140
- success: false,
141
- output: stdout,
142
- error: stderr || `Process exited with code ${String(exitCode)}`,
143
- exitCode
144
- };
145
- if (stdout.trim() === "") return {
146
- success: true,
147
- output: "",
148
- exitCode
149
- };
150
- try {
151
- const parsed = JSON.parse(stdout.trim());
152
- const success = parsed.status === "completed" || parsed.status === void 0;
153
- const rawTokens = parsed.metadata?.tokensUsed;
154
- const tokensUsed = rawTokens !== void 0 ? {
155
- input: rawTokens.input ?? 0,
156
- output: rawTokens.output ?? 0,
157
- total: (rawTokens.input ?? 0) + (rawTokens.output ?? 0)
158
- } : void 0;
159
- const executionTime = parsed.metadata?.executionTime;
160
- return {
161
- success: success && !parsed.error,
162
- output: parsed.output ?? stdout,
163
- ...parsed.error ? { error: parsed.error } : {},
164
- exitCode,
165
- metadata: {
166
- ...executionTime !== void 0 ? { executionTime } : {},
167
- ...tokensUsed !== void 0 ? { tokensUsed } : {}
168
- }
169
- };
170
- } catch {
171
- return {
172
- success: true,
173
- output: stdout,
174
- exitCode
175
- };
176
- }
177
- }
178
- /**
179
- * Parse plan generation output from Claude.
180
- */
181
- parsePlanOutput(stdout, stderr, exitCode) {
182
- if (exitCode !== 0) return {
183
- success: false,
184
- tasks: [],
185
- error: stderr || `Process exited with code ${String(exitCode)}`,
186
- rawOutput: stdout
187
- };
188
- if (stdout.trim() === "") return {
189
- success: false,
190
- tasks: [],
191
- error: "Empty output from plan generation",
192
- rawOutput: stdout
193
- };
194
- try {
195
- const parsed = JSON.parse(stripCodeFences$2(stdout));
196
- if (!Array.isArray(parsed.tasks)) return {
197
- success: false,
198
- tasks: [],
199
- error: "Plan output missing tasks array",
200
- rawOutput: stdout
201
- };
202
- const tasks = parsed.tasks.map((t) => ({
203
- title: t.title ?? "Untitled task",
204
- description: t.description ?? "",
205
- ...t.complexity !== void 0 ? { complexity: t.complexity } : {},
206
- ...t.dependencies !== void 0 ? { dependencies: t.dependencies } : {}
207
- }));
208
- return {
209
- success: true,
210
- tasks,
211
- rawOutput: stdout
212
- };
213
- } catch {
214
- return {
215
- success: false,
216
- tasks: [],
217
- error: "Failed to parse plan output as JSON",
218
- rawOutput: stdout
219
- };
220
- }
221
- }
222
- /**
223
- * Estimate token count using character-based heuristic.
224
- * Approximation: 1 token ≈ 3 characters for English text.
225
- */
226
- estimateTokens(prompt) {
227
- const input = Math.ceil(prompt.length / CHARS_PER_TOKEN$2);
228
- const output = Math.ceil(input * OUTPUT_RATIO$2);
229
- return {
230
- input,
231
- output,
232
- total: input + output
233
- };
234
- }
235
- /**
236
- * Return Claude Code's capabilities.
237
- */
238
- getCapabilities() {
239
- return {
240
- supportsJsonOutput: true,
241
- supportsStreaming: true,
242
- supportsSubscriptionBilling: true,
243
- supportsApiBilling: true,
244
- supportsPlanGeneration: true,
245
- maxContextTokens: 1e6,
246
- supportedTaskTypes: [
247
- "code",
248
- "refactor",
249
- "test",
250
- "review",
251
- "debug",
252
- "document",
253
- "analyze"
254
- ],
255
- supportedLanguages: ["*"]
256
- };
257
- }
258
- _detectBillingModes(_versionOutput) {
259
- const explicit = process.env.ADT_BILLING_MODE;
260
- if (explicit === "subscription" || explicit === "api" || explicit === "free") return [explicit];
261
- const modes = ["subscription"];
262
- if (process.env.ANTHROPIC_API_KEY) modes.push("api");
263
- return modes;
264
- }
265
- _buildPlanningPrompt(request) {
266
- const maxTasks = request.maxTasks ?? 10;
267
- const contextSection = request.context ? `\n\nAdditional context:\n${request.context}` : "";
268
- return `Generate a detailed task plan for the following goal:\n${request.goal}${contextSection}\n\nOutput a JSON object with a "tasks" array. Each task should have: "title" (string), "description" (string), "complexity" (1-10 integer), "dependencies" (array of task titles this depends on). Produce at most ${String(maxTasks)} tasks. Output ONLY raw valid JSON — no markdown, no code fences, no explanation. Start your response with { and end with }.`;
269
- }
270
- };
271
-
272
- //#endregion
273
- //#region src/adapters/codex-adapter.ts
274
- const execAsync$1 = promisify(exec);
275
- /** Approximate characters per token for estimation */
276
- const CHARS_PER_TOKEN$1 = 3;
277
- /** Estimated output token multiplier relative to input */
278
- const OUTPUT_RATIO$1 = .5;
279
- /** Strip markdown code fences from LLM output (e.g. ```json ... ```) */
280
- function stripCodeFences$1(raw) {
281
- return raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
282
- }
283
- /** Codex default billing modes — subscription via `codex login`, or API key */
284
- const CODEX_BILLING_MODES = ["subscription", "api"];
285
- /**
286
- * Adapter for the OpenAI Codex CLI agent.
287
- *
288
- * Codex CLI uses stdin for the prompt and outputs JSON when --json flag is used.
289
- * Codex supports subscription billing (via `codex login`) and API key billing.
290
- */
291
- var CodexCLIAdapter = class {
292
- id = "codex";
293
- displayName = "Codex CLI";
294
- adapterVersion = "1.0.0";
295
- /**
296
- * Verify the `codex` binary is installed and responsive.
297
- */
298
- async healthCheck() {
299
- try {
300
- const { stdout } = await execAsync$1("codex --version", { timeout: 1e4 });
301
- const output = stdout.trim();
302
- let cliPath;
303
- try {
304
- const whichResult = await execAsync$1("which codex", { timeout: 5e3 });
305
- cliPath = whichResult.stdout.trim();
306
- } catch {}
307
- return {
308
- healthy: true,
309
- version: output,
310
- ...cliPath !== void 0 ? { cliPath } : {},
311
- detectedBillingModes: CODEX_BILLING_MODES,
312
- supportsHeadless: true
313
- };
314
- } catch (err) {
315
- const message = err instanceof Error ? err.message : String(err);
316
- return {
317
- healthy: false,
318
- error: `Codex CLI not available: ${message}`,
319
- supportsHeadless: false
320
- };
321
- }
322
- }
323
- /**
324
- * Build spawn command for a coding task.
325
- * Uses: `codex exec --json` with prompt delivered via stdin.
326
- */
327
- buildCommand(prompt, options) {
328
- const args = ["exec", "--json"];
329
- if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
330
- const envEntries = {};
331
- if (options.apiKey) envEntries.OPENAI_API_KEY = options.apiKey;
332
- const hasEnv = Object.keys(envEntries).length > 0;
333
- return {
334
- binary: "codex",
335
- args,
336
- ...hasEnv ? { env: envEntries } : {},
337
- cwd: options.worktreePath,
338
- stdin: prompt
339
- };
340
- }
341
- /**
342
- * Build spawn command for plan generation.
343
- * Uses codex exec with a JSON plan generation prompt via stdin.
344
- */
345
- buildPlanningCommand(request, options) {
346
- const planningPrompt = this._buildPlanningPrompt(request);
347
- const args = [
348
- "exec",
349
- planningPrompt,
350
- "--sandbox",
351
- "read-only"
352
- ];
353
- if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
354
- const envEntries = {};
355
- if (options.apiKey) envEntries.OPENAI_API_KEY = options.apiKey;
356
- const hasEnv = Object.keys(envEntries).length > 0;
357
- return {
358
- binary: "codex",
359
- args,
360
- ...hasEnv ? { env: envEntries } : {},
361
- cwd: options.worktreePath
362
- };
363
- }
364
- /**
365
- * Parse Codex CLI JSON output into a TaskResult.
366
- */
367
- parseOutput(stdout, stderr, exitCode) {
368
- if (exitCode !== 0) return {
369
- success: false,
370
- output: stdout,
371
- error: stderr || `Process exited with code ${String(exitCode)}`,
372
- exitCode
373
- };
374
- if (stdout.trim() === "") return {
375
- success: true,
376
- output: "",
377
- exitCode
378
- };
379
- try {
380
- const parsed = JSON.parse(stdout.trim());
381
- const success = parsed.status === "success" || parsed.status === "completed" || parsed.status === void 0 && !parsed.error;
382
- const inputTokens = parsed.tokens?.input ?? 0;
383
- const outputTokens = parsed.tokens?.output ?? 0;
384
- const totalTokens = parsed.tokens?.total ?? inputTokens + outputTokens;
385
- const hasTokens = inputTokens > 0 || outputTokens > 0;
386
- const tokensUsed = hasTokens ? {
387
- input: inputTokens,
388
- output: outputTokens,
389
- total: totalTokens
390
- } : void 0;
391
- const executionTime = parsed.executionTime;
392
- return {
393
- success: success && !parsed.error,
394
- output: parsed.output ?? parsed.result ?? stdout,
395
- ...parsed.error ? { error: parsed.error } : {},
396
- exitCode,
397
- metadata: {
398
- ...executionTime !== void 0 ? { executionTime } : {},
399
- ...tokensUsed !== void 0 ? { tokensUsed } : {}
400
- }
401
- };
402
- } catch {
403
- return {
404
- success: true,
405
- output: stdout,
406
- exitCode
407
- };
408
- }
409
- }
410
- /**
411
- * Parse Codex plan generation output.
412
- */
413
- parsePlanOutput(stdout, stderr, exitCode) {
414
- if (exitCode !== 0) return {
415
- success: false,
416
- tasks: [],
417
- error: stderr || `Process exited with code ${String(exitCode)}`,
418
- rawOutput: stdout
419
- };
420
- if (stdout.trim() === "") return {
421
- success: false,
422
- tasks: [],
423
- error: "Empty output from plan generation",
424
- rawOutput: stdout
425
- };
426
- try {
427
- const parsed = JSON.parse(stripCodeFences$1(stdout));
428
- if (parsed.tasks === void 0 && parsed.plan === void 0) return {
429
- success: false,
430
- tasks: [],
431
- error: "Plan output missing tasks array",
432
- rawOutput: stdout
433
- };
434
- const rawTasks = parsed.tasks ?? parsed.plan ?? [];
435
- if (!Array.isArray(rawTasks)) return {
436
- success: false,
437
- tasks: [],
438
- error: "Plan output missing tasks array",
439
- rawOutput: stdout
440
- };
441
- const tasks = rawTasks.map((t) => {
442
- const deps = "dependencies" in t ? t.dependencies : t.deps;
443
- return {
444
- title: t.title ?? "Untitled task",
445
- description: t.description ?? "",
446
- ...t.complexity !== void 0 ? { complexity: t.complexity } : {},
447
- ...deps !== void 0 ? { dependencies: deps } : {}
448
- };
449
- });
450
- return {
451
- success: true,
452
- tasks,
453
- rawOutput: stdout
454
- };
455
- } catch {
456
- return {
457
- success: false,
458
- tasks: [],
459
- error: "Failed to parse Codex plan output as JSON",
460
- rawOutput: stdout
461
- };
462
- }
463
- }
464
- /**
465
- * Estimate token count using character-based heuristic.
466
- */
467
- estimateTokens(prompt) {
468
- const input = Math.ceil(prompt.length / CHARS_PER_TOKEN$1);
469
- const output = Math.ceil(input * OUTPUT_RATIO$1);
470
- return {
471
- input,
472
- output,
473
- total: input + output
474
- };
475
- }
476
- /**
477
- * Return Codex CLI capabilities.
478
- */
479
- getCapabilities() {
480
- return {
481
- supportsJsonOutput: true,
482
- supportsStreaming: false,
483
- supportsSubscriptionBilling: true,
484
- supportsApiBilling: true,
485
- supportsPlanGeneration: true,
486
- maxContextTokens: 128e3,
487
- supportedTaskTypes: [
488
- "code",
489
- "refactor",
490
- "test",
491
- "debug",
492
- "analyze"
493
- ],
494
- supportedLanguages: ["*"]
495
- };
496
- }
497
- _buildPlanningPrompt(request) {
498
- const maxTasks = request.maxTasks ?? 10;
499
- const contextSection = request.context ? `\n\nAdditional context:\n${request.context}` : "";
500
- return `Generate a detailed task plan for the following goal:\n${request.goal}${contextSection}\n\nOutput a JSON object with a "tasks" array. Each task should have: "title" (string), "description" (string), "complexity" (1-10 integer), "dependencies" (array of task titles this depends on). Produce at most ${String(maxTasks)} tasks. Output ONLY raw valid JSON — no markdown, no code fences, no explanation. Start your response with { and end with }.`;
501
- }
502
- };
503
-
504
- //#endregion
505
- //#region src/adapters/gemini-adapter.ts
506
- const execAsync = promisify(exec);
507
- /** Default model used when none is specified */
508
- const DEFAULT_MODEL = "gemini-2.0-flash";
509
- /** Approximate characters per token for estimation */
510
- const CHARS_PER_TOKEN = 3;
511
- /** Estimated output token multiplier relative to input */
512
- const OUTPUT_RATIO = .5;
513
- /**
514
- * Strip markdown code fences from LLM output.
515
- * LLMs often wrap JSON in ```json ... ``` despite being told not to.
516
- */
517
- function stripCodeFences(raw) {
518
- const stripped = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "");
519
- return stripped.trim();
520
- }
521
- /**
522
- * Adapter for the Google Gemini CLI agent.
523
- *
524
- * Gemini CLI follows similar patterns to Claude Code: prompt via `-p` flag,
525
- * JSON output via `--output-format json`, and model via `--model`.
526
- */
527
- var GeminiCLIAdapter = class {
528
- id = "gemini";
529
- displayName = "Gemini CLI";
530
- adapterVersion = "1.0.0";
531
- /**
532
- * Verify the `gemini` binary is installed and responsive.
533
- * Detects subscription vs API billing mode.
534
- */
535
- async healthCheck() {
536
- try {
537
- const { stdout } = await execAsync("gemini --version", { timeout: 1e4 });
538
- const output = stdout.trim();
539
- const detectedBillingModes = this._detectBillingModes(output);
540
- let cliPath;
541
- try {
542
- const whichResult = await execAsync("which gemini", { timeout: 5e3 });
543
- cliPath = whichResult.stdout.trim();
544
- } catch {}
545
- return {
546
- healthy: true,
547
- version: output,
548
- ...cliPath !== void 0 ? { cliPath } : {},
549
- detectedBillingModes,
550
- supportsHeadless: true
551
- };
552
- } catch (err) {
553
- const message = err instanceof Error ? err.message : String(err);
554
- return {
555
- healthy: false,
556
- error: `Gemini CLI not available: ${message}`,
557
- supportsHeadless: false
558
- };
559
- }
560
- }
561
- /**
562
- * Build spawn command for a coding task.
563
- * Uses: `gemini -p <prompt> --output-format json --model <model>`
564
- */
565
- buildCommand(prompt, options) {
566
- const model = options.model ?? DEFAULT_MODEL;
567
- const args = [
568
- "-p",
569
- prompt,
570
- "--output-format",
571
- "json",
572
- "--model",
573
- model
574
- ];
575
- if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
576
- const envEntries = {};
577
- if (options.billingMode === "api" && options.apiKey) envEntries.GEMINI_API_KEY = options.apiKey;
578
- const hasEnv = Object.keys(envEntries).length > 0;
579
- return {
580
- binary: "gemini",
581
- args,
582
- ...hasEnv ? { env: envEntries } : {},
583
- cwd: options.worktreePath
584
- };
585
- }
586
- /**
587
- * Build spawn command for plan generation.
588
- */
589
- buildPlanningCommand(request, options) {
590
- const model = options.model ?? DEFAULT_MODEL;
591
- const planningPrompt = this._buildPlanningPrompt(request);
592
- const args = [
593
- planningPrompt,
594
- "--model",
595
- model
596
- ];
597
- if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
598
- const envEntries = {};
599
- if (options.billingMode === "api" && options.apiKey) envEntries.GEMINI_API_KEY = options.apiKey;
600
- const hasEnv = Object.keys(envEntries).length > 0;
601
- return {
602
- binary: "gemini",
603
- args,
604
- ...hasEnv ? { env: envEntries } : {},
605
- cwd: options.worktreePath
606
- };
607
- }
608
- /**
609
- * Parse Gemini CLI JSON output into a TaskResult.
610
- */
611
- parseOutput(stdout, stderr, exitCode) {
612
- if (exitCode !== 0) return {
613
- success: false,
614
- output: stdout,
615
- error: stderr || `Process exited with code ${String(exitCode)}`,
616
- exitCode
617
- };
618
- if (stdout.trim() === "") return {
619
- success: true,
620
- output: "",
621
- exitCode
622
- };
623
- try {
624
- const parsed = JSON.parse(stdout.trim());
625
- const success = parsed.status === "completed" || parsed.status === void 0;
626
- const usageMeta = parsed.metadata?.usageMetadata;
627
- const inputTokens = parsed.metadata?.tokensUsed?.input ?? usageMeta?.promptTokenCount ?? 0;
628
- const outputTokens = parsed.metadata?.tokensUsed?.output ?? usageMeta?.candidatesTokenCount ?? 0;
629
- const hasTokens = inputTokens > 0 || outputTokens > 0;
630
- const tokensUsed = hasTokens ? {
631
- input: inputTokens,
632
- output: outputTokens,
633
- total: inputTokens + outputTokens
634
- } : void 0;
635
- const executionTime = parsed.metadata?.executionTime;
636
- return {
637
- success: success && !parsed.error,
638
- output: parsed.output ?? parsed.response ?? stdout,
639
- ...parsed.error ? { error: parsed.error } : {},
640
- exitCode,
641
- metadata: {
642
- ...executionTime !== void 0 ? { executionTime } : {},
643
- ...tokensUsed !== void 0 ? { tokensUsed } : {}
644
- }
645
- };
646
- } catch {
647
- return {
648
- success: true,
649
- output: stdout,
650
- exitCode
651
- };
652
- }
653
- }
654
- /**
655
- * Parse Gemini plan generation output.
656
- */
657
- parsePlanOutput(stdout, stderr, exitCode) {
658
- if (exitCode !== 0) return {
659
- success: false,
660
- tasks: [],
661
- error: stderr || `Process exited with code ${String(exitCode)}`,
662
- rawOutput: stdout
663
- };
664
- if (stdout.trim() === "") return {
665
- success: false,
666
- tasks: [],
667
- error: "Empty output from plan generation",
668
- rawOutput: stdout
669
- };
670
- try {
671
- const parsed = JSON.parse(stripCodeFences(stdout));
672
- if (!Array.isArray(parsed.tasks)) return {
673
- success: false,
674
- tasks: [],
675
- error: "Plan output missing tasks array",
676
- rawOutput: stdout
677
- };
678
- const tasks = parsed.tasks.map((t) => ({
679
- title: t.title ?? "Untitled task",
680
- description: t.description ?? "",
681
- ...t.complexity !== void 0 ? { complexity: t.complexity } : {},
682
- ...t.dependencies !== void 0 ? { dependencies: t.dependencies } : {}
683
- }));
684
- return {
685
- success: true,
686
- tasks,
687
- rawOutput: stdout
688
- };
689
- } catch {
690
- return {
691
- success: false,
692
- tasks: [],
693
- error: "Failed to parse Gemini plan output as JSON",
694
- rawOutput: stdout
695
- };
696
- }
697
- }
698
- /**
699
- * Estimate token count using character-based heuristic.
700
- */
701
- estimateTokens(prompt) {
702
- const input = Math.ceil(prompt.length / CHARS_PER_TOKEN);
703
- const output = Math.ceil(input * OUTPUT_RATIO);
704
- return {
705
- input,
706
- output,
707
- total: input + output
708
- };
709
- }
710
- /**
711
- * Return Gemini CLI capabilities.
712
- */
713
- getCapabilities() {
714
- return {
715
- supportsJsonOutput: true,
716
- supportsStreaming: true,
717
- supportsSubscriptionBilling: true,
718
- supportsApiBilling: true,
719
- supportsPlanGeneration: true,
720
- maxContextTokens: 1e6,
721
- supportedTaskTypes: [
722
- "code",
723
- "refactor",
724
- "test",
725
- "review",
726
- "debug",
727
- "document",
728
- "analyze"
729
- ],
730
- supportedLanguages: ["*"]
731
- };
732
- }
733
- _detectBillingModes(versionOutput) {
734
- const explicit = process.env.ADT_BILLING_MODE;
735
- if (explicit === "subscription" || explicit === "api" || explicit === "free") return [explicit];
736
- const modes = [];
737
- if (process.env.GEMINI_API_KEY) modes.push("api");
738
- if (versionOutput.toLowerCase().includes("subscription")) modes.push("subscription");
739
- if (modes.length === 0) return ["subscription", "api"];
740
- return modes;
741
- }
742
- _buildPlanningPrompt(request) {
743
- const maxTasks = request.maxTasks ?? 10;
744
- const contextSection = request.context ? `\n\nAdditional context:\n${request.context}` : "";
745
- return `Generate a detailed task plan for the following goal:\n${request.goal}${contextSection}\n\nOutput a JSON object with a "tasks" array. Each task should have: "title" (string), "description" (string), "complexity" (1-10 integer), "dependencies" (array of task titles this depends on). Produce at most ${String(maxTasks)} tasks. Output ONLY raw valid JSON — no markdown, no code fences, no explanation. Start your response with { and end with }.`;
746
- }
747
- };
748
-
749
- //#endregion
750
- //#region src/adapters/adapter-registry.ts
751
- /**
752
- * AdapterRegistry manages the lifecycle of WorkerAdapter instances.
753
- *
754
- * Usage:
755
- * ```typescript
756
- * const registry = new AdapterRegistry()
757
- * const report = await registry.discoverAndRegister()
758
- * const claude = registry.get('claude-code')
759
- * ```
760
- */
761
- var AdapterRegistry = class {
762
- _adapters = new Map();
763
- /**
764
- * Register an adapter by its id.
765
- * Overwrites any existing adapter with the same id.
766
- */
767
- register(adapter) {
768
- this._adapters.set(adapter.id, adapter);
769
- }
770
- /**
771
- * Retrieve a registered adapter by id.
772
- * @returns The adapter, or undefined if not registered
773
- */
774
- get(id) {
775
- return this._adapters.get(id);
776
- }
777
- /**
778
- * Return all registered adapters as an array.
779
- */
780
- getAll() {
781
- return Array.from(this._adapters.values());
782
- }
783
- /**
784
- * Return all registered adapters that support plan generation.
785
- */
786
- getPlanningCapable() {
787
- return this.getAll().filter((adapter) => adapter.getCapabilities().supportsPlanGeneration);
788
- }
789
- /**
790
- * Instantiate all built-in adapters, run health checks sequentially,
791
- * and register those that pass.
792
- *
793
- * Failed adapters are included in the report but do NOT prevent startup.
794
- *
795
- * @returns Discovery report with per-adapter results
796
- */
797
- async discoverAndRegister() {
798
- const builtInAdapters = [
799
- new ClaudeCodeAdapter(),
800
- new CodexCLIAdapter(),
801
- new GeminiCLIAdapter()
802
- ];
803
- const results = [];
804
- let registeredCount = 0;
805
- let failedCount = 0;
806
- for (const adapter of builtInAdapters) {
807
- let healthResult;
808
- try {
809
- healthResult = await adapter.healthCheck();
810
- } catch (err) {
811
- const message = err instanceof Error ? err.message : String(err);
812
- healthResult = {
813
- healthy: false,
814
- error: `Unexpected error during health check: ${message}`,
815
- supportsHeadless: false
816
- };
817
- }
818
- const registered = healthResult.healthy;
819
- if (registered) {
820
- this.register(adapter);
821
- registeredCount++;
822
- } else failedCount++;
823
- results.push({
824
- adapterId: adapter.id,
825
- displayName: adapter.displayName,
826
- healthResult,
827
- registered
828
- });
829
- }
830
- return {
831
- registeredCount,
832
- failedCount,
833
- results
834
- };
835
- }
836
- };
837
-
838
- //#endregion
839
- export { AdapterRegistry, ClaudeCodeAdapter, CodexCLIAdapter, GeminiCLIAdapter };
840
- //# sourceMappingURL=adapter-registry-D2zdMwVu.js.map