squads-cli 0.6.2 → 0.7.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 (112) hide show
  1. package/README.md +196 -1152
  2. package/dist/auth-YW3UPFSB.js +23 -0
  3. package/dist/autonomy-BWTVDEAT.js +102 -0
  4. package/dist/autonomy-BWTVDEAT.js.map +1 -0
  5. package/dist/chunk-3KCWNZWW.js +401 -0
  6. package/dist/chunk-3KCWNZWW.js.map +1 -0
  7. package/dist/chunk-67RO2HKR.js +174 -0
  8. package/dist/chunk-67RO2HKR.js.map +1 -0
  9. package/dist/chunk-7JVD7RD4.js +275 -0
  10. package/dist/chunk-7JVD7RD4.js.map +1 -0
  11. package/dist/chunk-BODLDQY7.js +452 -0
  12. package/dist/chunk-BODLDQY7.js.map +1 -0
  13. package/dist/chunk-FFFCFZ6A.js +121 -0
  14. package/dist/chunk-FFFCFZ6A.js.map +1 -0
  15. package/dist/chunk-FIWT2NMM.js +165 -0
  16. package/dist/chunk-FIWT2NMM.js.map +1 -0
  17. package/dist/chunk-L6GQCHDF.js +222 -0
  18. package/dist/chunk-L6GQCHDF.js.map +1 -0
  19. package/dist/{chunk-O7UV3FWI.js → chunk-LDM62TIX.js} +2 -2
  20. package/dist/chunk-LDM62TIX.js.map +1 -0
  21. package/dist/chunk-LOA3KWYJ.js +294 -0
  22. package/dist/chunk-LOA3KWYJ.js.map +1 -0
  23. package/dist/chunk-NA45DFXY.js +616 -0
  24. package/dist/chunk-NA45DFXY.js.map +1 -0
  25. package/dist/{chunk-4CMAEQQY.js → chunk-NQN6JPI7.js} +4 -3
  26. package/dist/chunk-NQN6JPI7.js.map +1 -0
  27. package/dist/chunk-OQJHPULO.js +103 -0
  28. package/dist/chunk-OQJHPULO.js.map +1 -0
  29. package/dist/chunk-QHNUMM4V.js +87 -0
  30. package/dist/chunk-QHNUMM4V.js.map +1 -0
  31. package/dist/chunk-RM6BWILN.js +74 -0
  32. package/dist/chunk-RM6BWILN.js.map +1 -0
  33. package/dist/chunk-WBR5J7EX.js +90 -0
  34. package/dist/chunk-WBR5J7EX.js.map +1 -0
  35. package/dist/chunk-Z2UKDBNL.js +162 -0
  36. package/dist/chunk-Z2UKDBNL.js.map +1 -0
  37. package/dist/cli.js +2136 -12600
  38. package/dist/cli.js.map +1 -1
  39. package/dist/context-M2A2DOFV.js +291 -0
  40. package/dist/context-M2A2DOFV.js.map +1 -0
  41. package/dist/context-feed-JMNW4GAM.js +391 -0
  42. package/dist/context-feed-JMNW4GAM.js.map +1 -0
  43. package/dist/cost-N37I4UTA.js +274 -0
  44. package/dist/cost-N37I4UTA.js.map +1 -0
  45. package/dist/create-554W5HNU.js +286 -0
  46. package/dist/create-554W5HNU.js.map +1 -0
  47. package/dist/daemon-XWPQPPPN.js +546 -0
  48. package/dist/daemon-XWPQPPPN.js.map +1 -0
  49. package/dist/dashboard-L7YKVQEB.js +945 -0
  50. package/dist/dashboard-L7YKVQEB.js.map +1 -0
  51. package/dist/dashboard-MFNRLCEE.js +794 -0
  52. package/dist/dashboard-MFNRLCEE.js.map +1 -0
  53. package/dist/doctor-RG75M5RO.js +346 -0
  54. package/dist/doctor-RG75M5RO.js.map +1 -0
  55. package/dist/env-config-KCLDBKYX.js +21 -0
  56. package/dist/exec-JQKBF7BL.js +197 -0
  57. package/dist/exec-JQKBF7BL.js.map +1 -0
  58. package/dist/feedback-KA2UYBZG.js +229 -0
  59. package/dist/feedback-KA2UYBZG.js.map +1 -0
  60. package/dist/github-UQTM5KMS.js +23 -0
  61. package/dist/goal-EOPC5ZCD.js +168 -0
  62. package/dist/goal-EOPC5ZCD.js.map +1 -0
  63. package/dist/health-3FZDOSR5.js +209 -0
  64. package/dist/health-3FZDOSR5.js.map +1 -0
  65. package/dist/history-TFVXJEDH.js +229 -0
  66. package/dist/history-TFVXJEDH.js.map +1 -0
  67. package/dist/index.js +1 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/init-UOWTNMIE.js +747 -0
  70. package/dist/init-UOWTNMIE.js.map +1 -0
  71. package/dist/kpi-2SQ2WCVT.js +413 -0
  72. package/dist/kpi-2SQ2WCVT.js.map +1 -0
  73. package/dist/learn-6ERTERAO.js +269 -0
  74. package/dist/learn-6ERTERAO.js.map +1 -0
  75. package/dist/list-KSOMUBMB.js +92 -0
  76. package/dist/list-KSOMUBMB.js.map +1 -0
  77. package/dist/login-ST6PAXYE.js +155 -0
  78. package/dist/login-ST6PAXYE.js.map +1 -0
  79. package/dist/memory-3CSNKXIL.js +562 -0
  80. package/dist/memory-3CSNKXIL.js.map +1 -0
  81. package/dist/progress-FKG4V2VH.js +202 -0
  82. package/dist/progress-FKG4V2VH.js.map +1 -0
  83. package/dist/providers-66PDCORB.js +65 -0
  84. package/dist/providers-66PDCORB.js.map +1 -0
  85. package/dist/results-2MJFLWEO.js +224 -0
  86. package/dist/results-2MJFLWEO.js.map +1 -0
  87. package/dist/run-72OQLH5A.js +2685 -0
  88. package/dist/run-72OQLH5A.js.map +1 -0
  89. package/dist/session-6H67XPAQ.js +64 -0
  90. package/dist/session-6H67XPAQ.js.map +1 -0
  91. package/dist/{chunk-NHGLXN2F.js → sessions-GVQIMN4W.js} +23 -459
  92. package/dist/sessions-GVQIMN4W.js.map +1 -0
  93. package/dist/{squad-parser-4BI3G4RS.js → squad-parser-CM3HOIWM.js} +2 -2
  94. package/dist/squad-parser-CM3HOIWM.js.map +1 -0
  95. package/dist/stats-ONZI557Q.js +335 -0
  96. package/dist/stats-ONZI557Q.js.map +1 -0
  97. package/dist/status-FYH42FTB.js +346 -0
  98. package/dist/status-FYH42FTB.js.map +1 -0
  99. package/dist/sync-HJZJNXHW.js +800 -0
  100. package/dist/sync-HJZJNXHW.js.map +1 -0
  101. package/dist/update-B4WMUOPO.js +83 -0
  102. package/dist/update-B4WMUOPO.js.map +1 -0
  103. package/dist/{update-ALJKFFM7.js → update-L7FGHN6W.js} +2 -2
  104. package/dist/update-L7FGHN6W.js.map +1 -0
  105. package/package.json +18 -10
  106. package/dist/chunk-4CMAEQQY.js.map +0 -1
  107. package/dist/chunk-NHGLXN2F.js.map +0 -1
  108. package/dist/chunk-O7UV3FWI.js.map +0 -1
  109. package/dist/sessions-6PB7ALCE.js +0 -16
  110. /package/dist/{sessions-6PB7ALCE.js.map → auth-YW3UPFSB.js.map} +0 -0
  111. /package/dist/{squad-parser-4BI3G4RS.js.map → env-config-KCLDBKYX.js.map} +0 -0
  112. /package/dist/{update-ALJKFFM7.js.map → github-UQTM5KMS.js.map} +0 -0
@@ -0,0 +1,2685 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getCLIConfig,
4
+ isProviderCLIAvailable
5
+ } from "./chunk-QHNUMM4V.js";
6
+ import {
7
+ pushCognitionSignal,
8
+ reportConversationResult,
9
+ reportExecutionStart
10
+ } from "./chunk-WBR5J7EX.js";
11
+ import {
12
+ detectProviderFromModel
13
+ } from "./chunk-LOA3KWYJ.js";
14
+ import {
15
+ getBotGitEnv,
16
+ getBotPushUrl,
17
+ getCoAuthorTrailer
18
+ } from "./chunk-FIWT2NMM.js";
19
+ import {
20
+ parseCooldown
21
+ } from "./chunk-RM6BWILN.js";
22
+ import {
23
+ isLoggedIn,
24
+ loadSession
25
+ } from "./chunk-Z2UKDBNL.js";
26
+ import {
27
+ getApiUrl,
28
+ getBridgeUrl
29
+ } from "./chunk-OQJHPULO.js";
30
+ import {
31
+ Events,
32
+ flushEvents,
33
+ track
34
+ } from "./chunk-L6GQCHDF.js";
35
+ import {
36
+ findSquadsDir,
37
+ listAgents,
38
+ loadAgentDefinition,
39
+ loadSquad,
40
+ parseAgentProvider,
41
+ resolveMcpConfigPath
42
+ } from "./chunk-LDM62TIX.js";
43
+ import {
44
+ findMemoryDir
45
+ } from "./chunk-ZTQ7ISUR.js";
46
+ import {
47
+ RESET,
48
+ bold,
49
+ colors,
50
+ gradient,
51
+ icons,
52
+ writeLine
53
+ } from "./chunk-N7KDWU4W.js";
54
+ import "./chunk-7OCVIDC7.js";
55
+
56
+ // src/commands/run.ts
57
+ import ora from "ora";
58
+ import { spawn, execSync as execSync2 } from "child_process";
59
+ import { join as join2, dirname } from "path";
60
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync, cpSync, unlinkSync } from "fs";
61
+
62
+ // src/lib/permissions.ts
63
+ import { minimatch } from "minimatch";
64
+ function getDefaultContext(squad, agent) {
65
+ return {
66
+ squad,
67
+ agent,
68
+ permissions: {
69
+ mode: "warn",
70
+ bash: ["*"],
71
+ // All commands allowed by default
72
+ write: ["**"],
73
+ // All paths writable by default
74
+ read: ["**"],
75
+ // All paths readable by default
76
+ mcp: {
77
+ allow: ["*"],
78
+ // All MCP servers allowed
79
+ deny: []
80
+ }
81
+ }
82
+ };
83
+ }
84
+ function validateBashCommand(command, allowedCommands) {
85
+ const baseCommand = command.trim().split(/\s+/)[0];
86
+ if (allowedCommands.includes("*")) {
87
+ return null;
88
+ }
89
+ const isAllowed = allowedCommands.some((allowed) => {
90
+ if (allowed === baseCommand) return true;
91
+ if (allowed.includes("*")) {
92
+ return minimatch(baseCommand, allowed);
93
+ }
94
+ return false;
95
+ });
96
+ if (!isAllowed) {
97
+ return {
98
+ type: "bash",
99
+ requested: baseCommand,
100
+ reason: `Bash command '${baseCommand}' not in allowlist: [${allowedCommands.join(", ")}]`,
101
+ severity: "error"
102
+ };
103
+ }
104
+ return null;
105
+ }
106
+ function validateFilePath(path, allowedGlobs, operation) {
107
+ if (allowedGlobs.includes("**") || allowedGlobs.includes("*")) {
108
+ return null;
109
+ }
110
+ const isAllowed = allowedGlobs.some((glob) => minimatch(path, glob));
111
+ if (!isAllowed) {
112
+ return {
113
+ type: operation,
114
+ requested: path,
115
+ reason: `${operation === "write" ? "Write" : "Read"} to '${path}' not allowed. Permitted paths: [${allowedGlobs.join(", ")}]`,
116
+ severity: "error"
117
+ };
118
+ }
119
+ return null;
120
+ }
121
+ function validateMcpServer(server, allow, deny) {
122
+ const isDenied = deny.some((pattern) => {
123
+ if (pattern === server) return true;
124
+ if (pattern.includes("*")) return minimatch(server, pattern);
125
+ return false;
126
+ });
127
+ if (isDenied) {
128
+ return {
129
+ type: "mcp",
130
+ requested: server,
131
+ reason: `MCP server '${server}' is explicitly denied`,
132
+ severity: "error"
133
+ };
134
+ }
135
+ if (allow.includes("*")) {
136
+ return null;
137
+ }
138
+ const isAllowed = allow.some((pattern) => {
139
+ if (pattern === server) return true;
140
+ if (pattern.includes("*")) return minimatch(server, pattern);
141
+ return false;
142
+ });
143
+ if (!isAllowed) {
144
+ return {
145
+ type: "mcp",
146
+ requested: server,
147
+ reason: `MCP server '${server}' not in allowlist: [${allow.join(", ")}]`,
148
+ severity: "error"
149
+ };
150
+ }
151
+ return null;
152
+ }
153
+ function validateExecution(context, request) {
154
+ const violations = [];
155
+ if (request.bashCommands) {
156
+ for (const cmd of request.bashCommands) {
157
+ const violation = validateBashCommand(cmd, context.permissions.bash);
158
+ if (violation) {
159
+ violations.push(violation);
160
+ }
161
+ }
162
+ }
163
+ if (request.writePaths) {
164
+ for (const path of request.writePaths) {
165
+ const violation = validateFilePath(path, context.permissions.write, "write");
166
+ if (violation) {
167
+ violations.push(violation);
168
+ }
169
+ }
170
+ }
171
+ if (request.readPaths) {
172
+ for (const path of request.readPaths) {
173
+ const violation = validateFilePath(path, context.permissions.read, "read");
174
+ if (violation) {
175
+ violations.push(violation);
176
+ }
177
+ }
178
+ }
179
+ if (request.mcpServers) {
180
+ for (const server of request.mcpServers) {
181
+ const violation = validateMcpServer(
182
+ server,
183
+ context.permissions.mcp.allow,
184
+ context.permissions.mcp.deny
185
+ );
186
+ if (violation) {
187
+ violations.push(violation);
188
+ }
189
+ }
190
+ }
191
+ const hasErrors = violations.some((v) => v.severity === "error");
192
+ const allowed = context.permissions.mode !== "strict" || !hasErrors;
193
+ return {
194
+ allowed,
195
+ violations,
196
+ mode: context.permissions.mode
197
+ };
198
+ }
199
+ function formatViolations(result) {
200
+ const lines = [];
201
+ if (result.violations.length === 0) {
202
+ return lines;
203
+ }
204
+ const modeLabel = {
205
+ warn: "\u26A0\uFE0F PERMISSION WARNING",
206
+ strict: "\u{1F6AB} PERMISSION DENIED",
207
+ audit: "\u{1F4DD} PERMISSION AUDIT"
208
+ }[result.mode];
209
+ lines.push(modeLabel);
210
+ lines.push("");
211
+ for (const v of result.violations) {
212
+ const icon = v.severity === "error" ? "\u2717" : "\u26A0";
213
+ lines.push(` ${icon} [${v.type}] ${v.reason}`);
214
+ }
215
+ if (result.mode === "warn") {
216
+ lines.push("");
217
+ lines.push(" Continuing with warnings (mode: warn)");
218
+ } else if (result.mode === "audit") {
219
+ lines.push("");
220
+ lines.push(" Logged for audit, continuing (mode: audit)");
221
+ } else if (!result.allowed) {
222
+ lines.push("");
223
+ lines.push(" Execution blocked (mode: strict)");
224
+ }
225
+ return lines;
226
+ }
227
+ function parsePermissionsYaml(content) {
228
+ const yamlMatch = content.match(/```ya?ml\n([\s\S]*?)```/);
229
+ if (!yamlMatch) return null;
230
+ const yamlContent = yamlMatch[1];
231
+ const permissions = {};
232
+ const modeMatch = yamlContent.match(/^\s*mode:\s*(warn|strict|audit)/m);
233
+ if (modeMatch) {
234
+ permissions.mode = modeMatch[1];
235
+ }
236
+ const bashMatch = yamlContent.match(/^\s*bash:\s*\[(.*?)\]/m);
237
+ if (bashMatch) {
238
+ permissions.bash = bashMatch[1].split(",").map((s) => s.trim());
239
+ }
240
+ const writeMatch = yamlContent.match(/^\s*write:\s*\[(.*?)\]/m);
241
+ if (writeMatch) {
242
+ permissions.write = writeMatch[1].split(",").map((s) => s.trim());
243
+ }
244
+ const readMatch = yamlContent.match(/^\s*read:\s*\[(.*?)\]/m);
245
+ if (readMatch) {
246
+ permissions.read = readMatch[1].split(",").map((s) => s.trim());
247
+ }
248
+ const mcpAllowMatch = yamlContent.match(/^\s*allow:\s*\[(.*?)\]/m);
249
+ const mcpDenyMatch = yamlContent.match(/^\s*deny:\s*\[(.*?)\]/m);
250
+ if (mcpAllowMatch || mcpDenyMatch) {
251
+ permissions.mcp = {
252
+ allow: mcpAllowMatch ? mcpAllowMatch[1].split(",").map((s) => s.trim()) : ["*"],
253
+ deny: mcpDenyMatch ? mcpDenyMatch[1].split(",").map((s) => s.trim()) : []
254
+ };
255
+ }
256
+ return Object.keys(permissions).length > 0 ? permissions : null;
257
+ }
258
+ function buildContextFromSquad(squadName, squadContent, agentName) {
259
+ const context = getDefaultContext(squadName, agentName);
260
+ const parsed = parsePermissionsYaml(squadContent);
261
+ if (parsed) {
262
+ if (parsed.mode) context.permissions.mode = parsed.mode;
263
+ if (parsed.bash) context.permissions.bash = parsed.bash;
264
+ if (parsed.write) context.permissions.write = parsed.write;
265
+ if (parsed.read) context.permissions.read = parsed.read;
266
+ if (parsed.mcp) context.permissions.mcp = parsed.mcp;
267
+ }
268
+ return context;
269
+ }
270
+
271
+ // src/lib/workflow.ts
272
+ import { join } from "path";
273
+ import { existsSync, writeFileSync, mkdirSync } from "fs";
274
+ import { execSync, exec } from "child_process";
275
+ import { promisify } from "util";
276
+
277
+ // src/lib/conversation.ts
278
+ function classifyAgent(agentName, roleDescription) {
279
+ if (roleDescription) {
280
+ const lower = roleDescription.toLowerCase();
281
+ if (lower.includes("orchestrat") || lower.includes("triage") || lower.includes("coordinat")) return "lead";
282
+ if (lower.includes("scan") || lower.includes("monitor") || lower.includes("detect")) return "scanner";
283
+ if (lower.includes("verif") || lower.includes("review") || lower.includes("check") || lower.includes("critic")) return "verifier";
284
+ return "worker";
285
+ }
286
+ const name = agentName.toLowerCase();
287
+ if (name.includes("lead") || name.includes("orchestrator")) return "lead";
288
+ if (name.includes("scanner") || name.includes("scout") || name.includes("monitor")) return "scanner";
289
+ if (name.includes("verifier") || name.includes("critic") || name.includes("reviewer")) return "verifier";
290
+ if (name.includes("worker") || name.includes("solver") || name.includes("builder")) return "worker";
291
+ return null;
292
+ }
293
+ function modelForRole(role) {
294
+ switch (role) {
295
+ case "lead":
296
+ return "sonnet";
297
+ case "worker":
298
+ return "sonnet";
299
+ case "scanner":
300
+ return "haiku";
301
+ case "verifier":
302
+ return "haiku";
303
+ }
304
+ }
305
+ function createTranscript(squad) {
306
+ return {
307
+ squad,
308
+ turns: [],
309
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
310
+ totalCost: 0
311
+ };
312
+ }
313
+ function serializeTranscript(transcript) {
314
+ if (transcript.turns.length === 0) return "";
315
+ let turns = transcript.turns;
316
+ if (turns.length > 5) {
317
+ const firstBrief = turns[0];
318
+ let lastReviewIdx = -1;
319
+ for (let i = turns.length - 1; i > 0; i--) {
320
+ if (turns[i].role === "lead") {
321
+ lastReviewIdx = i;
322
+ break;
323
+ }
324
+ }
325
+ if (lastReviewIdx > 0) {
326
+ turns = [firstBrief, ...turns.slice(lastReviewIdx)];
327
+ } else {
328
+ turns = [firstBrief, ...turns.slice(-3)];
329
+ }
330
+ }
331
+ const lines = ["## Conversation So Far\n"];
332
+ if (turns.length < transcript.turns.length) {
333
+ lines.push(`*(${transcript.turns.length - turns.length} earlier turns compacted \u2014 lead review below summarizes prior work)*
334
+ `);
335
+ }
336
+ for (const turn of turns) {
337
+ lines.push(`### ${turn.agent} (${turn.role}) \u2014 ${turn.timestamp}`);
338
+ lines.push(turn.content);
339
+ lines.push("");
340
+ }
341
+ return lines.join("\n");
342
+ }
343
+ function addTurn(transcript, agent, role, content, estimatedCost) {
344
+ transcript.turns.push({
345
+ agent,
346
+ role,
347
+ content,
348
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
349
+ estimatedCost
350
+ });
351
+ transcript.totalCost += estimatedCost;
352
+ }
353
+ var CONVERGENCE_PHRASES = [
354
+ "pr created",
355
+ "pr merged",
356
+ "issue closed",
357
+ "issue resolved",
358
+ "all tasks complete",
359
+ "all items complete",
360
+ "all issues resolved",
361
+ "nothing left to do",
362
+ "nothing remaining",
363
+ "session complete",
364
+ "queue empty",
365
+ "no open issues",
366
+ "no pending tasks",
367
+ "no pending issues"
368
+ ];
369
+ var VERIFIER_APPROVAL_PHRASES = [
370
+ "approved",
371
+ "lgtm",
372
+ "looks good",
373
+ "all checks pass",
374
+ "all tests pass",
375
+ "tests pass",
376
+ "passed",
377
+ "quality standards met"
378
+ ];
379
+ var VERIFIER_REJECTION_PHRASES = [
380
+ "failed",
381
+ "rejected",
382
+ "needs fixes",
383
+ "needs changes",
384
+ "does not pass",
385
+ "did not pass",
386
+ "failing"
387
+ ];
388
+ var CONTINUATION_PHRASES = [
389
+ "needs review",
390
+ "needs feedback",
391
+ "needs input",
392
+ "need clarification",
393
+ "todo",
394
+ "fixme",
395
+ "blocked",
396
+ "waiting for",
397
+ "waiting on",
398
+ "will continue",
399
+ "will proceed",
400
+ "will work on",
401
+ "next step",
402
+ "in progress"
403
+ ];
404
+ function detectConvergence(transcript, maxTurns, costCeiling) {
405
+ if (transcript.turns.length >= maxTurns) {
406
+ return { converged: true, reason: `Max turns reached (${maxTurns})` };
407
+ }
408
+ if (transcript.totalCost >= costCeiling) {
409
+ return { converged: true, reason: `Cost ceiling reached ($${transcript.totalCost.toFixed(2)}/$${costCeiling})` };
410
+ }
411
+ if (transcript.turns.length === 0) {
412
+ return { converged: false, reason: "No turns yet" };
413
+ }
414
+ const lastTurn = transcript.turns[transcript.turns.length - 1];
415
+ const content = lastTurn.content;
416
+ const lower = content.toLowerCase();
417
+ if (lastTurn.role === "verifier") {
418
+ const rejected = VERIFIER_REJECTION_PHRASES.some((phrase) => lower.includes(phrase));
419
+ if (rejected) {
420
+ return { converged: false, reason: "Verifier rejected \u2014 continuing cycle" };
421
+ }
422
+ const approved = VERIFIER_APPROVAL_PHRASES.some((phrase) => lower.includes(phrase));
423
+ if (approved) {
424
+ return { converged: true, reason: "Verifier approved" };
425
+ }
426
+ }
427
+ const hasContinuation = CONTINUATION_PHRASES.some((phrase) => lower.includes(phrase));
428
+ if (hasContinuation) {
429
+ return { converged: false, reason: "Continuation signal detected" };
430
+ }
431
+ const hasConvergence = CONVERGENCE_PHRASES.some((phrase) => lower.includes(phrase));
432
+ if (hasConvergence) {
433
+ return { converged: true, reason: "Convergence signal detected" };
434
+ }
435
+ return { converged: false, reason: "No signals detected, continuing" };
436
+ }
437
+ var COST_PER_TURN = {
438
+ opus: 2.5,
439
+ sonnet: 0.75,
440
+ haiku: 0.1
441
+ };
442
+ function estimateTurnCost(model) {
443
+ const key = model.includes("opus") ? "opus" : model.includes("haiku") ? "haiku" : "sonnet";
444
+ return COST_PER_TURN[key] || COST_PER_TURN.sonnet;
445
+ }
446
+
447
+ // src/lib/workflow.ts
448
+ var execAsync = promisify(exec);
449
+ var DEFAULT_MAX_TURNS = 20;
450
+ var DEFAULT_COST_CEILING = 25;
451
+ function executeAgentTurn(config) {
452
+ const { agentName, agentPath, role, squadName, model, transcript, task } = config;
453
+ const transcriptContext = serializeTranscript(transcript);
454
+ let roleInstructions;
455
+ switch (role) {
456
+ case "lead":
457
+ if (transcript.turns.length === 0 && task) {
458
+ roleInstructions = `## Founder Directive
459
+
460
+ ${task}
461
+
462
+ Brief the team on this directive. Set priorities and assign work.`;
463
+ } else if (transcript.turns.length === 0) {
464
+ roleInstructions = `## Your Role: Lead
465
+
466
+ You are starting a new squad session. Brief the team:
467
+ 1. Review open issues and PRs
468
+ 2. Set priorities for this session
469
+ 3. Assign work to workers
470
+ 4. Be specific about what each worker should do`;
471
+ } else {
472
+ roleInstructions = `## Your Role: Lead (Review)
473
+
474
+ Review the work done so far. Either:
475
+ - Request specific changes from workers
476
+ - Approve and signal completion if quality is sufficient
477
+ - Merge PRs that pass CI using \`gh pr merge --squash --delete-branch\``;
478
+ }
479
+ break;
480
+ case "scanner":
481
+ roleInstructions = `## Your Role: Scanner
482
+
483
+ Scan for issues, gaps, and opportunities. Report findings concisely. Do NOT fix anything \u2014 just discover and report.`;
484
+ break;
485
+ case "worker":
486
+ roleInstructions = `## Your Role: Worker
487
+
488
+ Execute the work assigned by the lead. Create branches, write code, open PRs to develop. Be focused and efficient.`;
489
+ break;
490
+ case "verifier":
491
+ roleInstructions = `## Your Role: Verifier
492
+
493
+ Verify that work meets quality standards. Check PRs, run tests, validate output. Report pass/fail with specifics.`;
494
+ break;
495
+ }
496
+ const prompt = `You are ${agentName} (${role}) in squad ${squadName}.
497
+
498
+ Read your full agent definition at ${agentPath} and follow its instructions.
499
+
500
+ ${roleInstructions}
501
+
502
+ ${transcriptContext}
503
+
504
+ IMPORTANT:
505
+ - Be concise. Your output becomes part of a shared transcript.
506
+ - Reference specific issue numbers, PR numbers, and file paths.
507
+ - If you create a PR, include the PR number in your output.
508
+ - If there's nothing to do, say "Nothing to do" clearly.
509
+ - When done, summarize what you did in 2-3 sentences.`;
510
+ const resolvedModel = config.model || modelForRole(role);
511
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
512
+ try {
513
+ const output = execSync(
514
+ `claude --print --dangerously-skip-permissions --model ${resolvedModel} -- '${escapedPrompt}'`,
515
+ {
516
+ cwd: config.cwd || process.cwd(),
517
+ timeout: 15 * 60 * 1e3,
518
+ // 15 min per turn
519
+ maxBuffer: 10 * 1024 * 1024,
520
+ // 10MB
521
+ encoding: "utf-8",
522
+ env: {
523
+ ...process.env,
524
+ CLAUDECODE: "",
525
+ // Allow nested sessions
526
+ ANTHROPIC_API_KEY: void 0
527
+ // Use Max subscription
528
+ }
529
+ }
530
+ );
531
+ return output.trim();
532
+ } catch (err) {
533
+ const error = err;
534
+ if (error.stdout && error.stdout.trim().length > 0) {
535
+ return error.stdout.trim();
536
+ }
537
+ return `[ERROR] Agent ${agentName} failed: ${error.message || "unknown error"}`;
538
+ }
539
+ }
540
+ function executeAgentTurnAsync(config) {
541
+ const { agentName, agentPath, role, squadName, model, transcript, task } = config;
542
+ let roleInstructions = "";
543
+ switch (role) {
544
+ case "lead":
545
+ roleInstructions = task ? `FOUNDER DIRECTIVE: ${task}
546
+
547
+ Brief the team on this directive. Assign specific tasks to scanners and workers.` : "Review the conversation so far. Assess worker output. Direct next actions or declare convergence.";
548
+ break;
549
+ case "scanner":
550
+ roleInstructions = "Scan for issues, data, or signals relevant to the lead's brief. Report findings concisely.";
551
+ break;
552
+ case "worker":
553
+ roleInstructions = "Execute the specific task assigned by the lead. Produce concrete output (PRs, issues, content, analysis).";
554
+ break;
555
+ case "verifier":
556
+ roleInstructions = "Verify the worker's output meets quality standards. Check for errors, omissions, and alignment with goals.";
557
+ break;
558
+ }
559
+ const transcriptContext = transcript.turns.length > 0 ? `
560
+ == CONVERSATION SO FAR ==
561
+ ${serializeTranscript(transcript)}
562
+ == END CONVERSATION ==` : "";
563
+ const resolvedModel = config.model || modelForRole(role);
564
+ const prompt = `You are ${agentName} (${role}) in squad ${squadName}.
565
+
566
+ Read your full agent definition at ${agentPath} and follow its instructions.
567
+
568
+ ${roleInstructions}
569
+
570
+ ${transcriptContext}
571
+
572
+ IMPORTANT:
573
+ - Be concise. Your output becomes part of a shared transcript.
574
+ - Reference specific issue numbers, PR numbers, and file paths.
575
+ - If you create a PR, include the PR number in your output.
576
+ - If there's nothing to do, say "Nothing to do" clearly.
577
+ - When done, summarize what you did in 2-3 sentences.`;
578
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
579
+ return new Promise((resolve) => {
580
+ exec(
581
+ `claude --print --dangerously-skip-permissions --model ${resolvedModel} -- '${escapedPrompt}'`,
582
+ {
583
+ cwd: config.cwd || process.cwd(),
584
+ timeout: 15 * 60 * 1e3,
585
+ maxBuffer: 10 * 1024 * 1024,
586
+ encoding: "utf-8",
587
+ env: {
588
+ ...process.env,
589
+ CLAUDECODE: "",
590
+ ANTHROPIC_API_KEY: void 0
591
+ }
592
+ },
593
+ (error, stdout, stderr) => {
594
+ if (stdout && stdout.trim().length > 0) {
595
+ resolve(stdout.trim());
596
+ } else if (error) {
597
+ resolve(`[ERROR] Agent ${agentName} failed: ${error.message || "unknown error"}`);
598
+ } else {
599
+ resolve("[No output]");
600
+ }
601
+ }
602
+ );
603
+ });
604
+ }
605
+ function buildTurnPlan(squad, squadsDir) {
606
+ const agents = [];
607
+ for (const agent of squad.agents) {
608
+ const role = classifyAgent(agent.name, agent.role);
609
+ if (!role) continue;
610
+ const agentPath = join(squadsDir, squad.dir, `${agent.name}.md`);
611
+ if (!existsSync(agentPath)) continue;
612
+ agents.push({ name: agent.name, role, path: agentPath });
613
+ }
614
+ return agents;
615
+ }
616
+ async function runConversation(squad, options = {}) {
617
+ const squadsDir = findSquadsDir();
618
+ if (!squadsDir) {
619
+ return {
620
+ transcript: createTranscript(squad.name),
621
+ turnCount: 0,
622
+ totalCost: 0,
623
+ converged: true,
624
+ reason: "No squads directory found"
625
+ };
626
+ }
627
+ const maxTurns = options.maxTurns || DEFAULT_MAX_TURNS;
628
+ const costCeiling = options.costCeiling || DEFAULT_COST_CEILING;
629
+ const transcript = createTranscript(squad.name);
630
+ let squadCwd = process.cwd();
631
+ if (squad.repo) {
632
+ const repoName = squad.repo.split("/").pop();
633
+ if (repoName) {
634
+ const reposRoot = join(squadsDir, "..", "..", "..");
635
+ const candidatePath = join(reposRoot, repoName);
636
+ if (existsSync(candidatePath)) {
637
+ squadCwd = candidatePath;
638
+ }
639
+ }
640
+ }
641
+ const allAgents = buildTurnPlan(squad, squadsDir);
642
+ const leads = allAgents.filter((a) => a.role === "lead");
643
+ const scanners = allAgents.filter((a) => a.role === "scanner");
644
+ const workers = allAgents.filter((a) => a.role === "worker");
645
+ const verifiers = allAgents.filter((a) => a.role === "verifier");
646
+ if (leads.length === 0) {
647
+ return {
648
+ transcript,
649
+ turnCount: 0,
650
+ totalCost: 0,
651
+ converged: true,
652
+ reason: "No lead agent found \u2014 cannot orchestrate conversation"
653
+ };
654
+ }
655
+ const lead = leads[0];
656
+ const log = (msg) => {
657
+ if (options.verbose) {
658
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
659
+ process.stderr.write(` [${ts}] ${msg}
660
+ `);
661
+ }
662
+ };
663
+ log(`Conversation: ${squad.name} | ${allAgents.length} agents | max ${maxTurns} turns | $${costCeiling} ceiling`);
664
+ log(` Lead: ${lead.name} | Scanners: ${scanners.map((s) => s.name).join(", ") || "none"} | Workers: ${workers.map((w) => w.name).join(", ") || "none"} | Verifiers: ${verifiers.map((v) => v.name).join(", ") || "none"}`);
665
+ let cycleCount = 0;
666
+ const MAX_CYCLES = 5;
667
+ while (cycleCount < MAX_CYCLES) {
668
+ cycleCount++;
669
+ log(`
670
+ --- Cycle ${cycleCount} ---`);
671
+ log(`Turn ${transcript.turns.length + 1}: ${lead.name} (lead)`);
672
+ const leadOutput = executeAgentTurn({
673
+ agentName: lead.name,
674
+ agentPath: lead.path,
675
+ role: "lead",
676
+ squadName: squad.name,
677
+ model: options.model || modelForRole("lead"),
678
+ transcript,
679
+ task: cycleCount === 1 ? options.task : void 0,
680
+ cwd: squadCwd
681
+ });
682
+ addTurn(transcript, lead.name, "lead", leadOutput, estimateTurnCost(options.model || "sonnet"));
683
+ let conv = detectConvergence(transcript, maxTurns, costCeiling);
684
+ if (conv.converged) {
685
+ log(`Converged after lead: ${conv.reason}`);
686
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
687
+ }
688
+ if (cycleCount === 1 && scanners.length > 0) {
689
+ if (scanners.length === 1) {
690
+ log(`Turn ${transcript.turns.length + 1}: ${scanners[0].name} (scanner)`);
691
+ const output = executeAgentTurn({
692
+ agentName: scanners[0].name,
693
+ agentPath: scanners[0].path,
694
+ role: "scanner",
695
+ squadName: squad.name,
696
+ model: options.model || modelForRole("scanner"),
697
+ transcript,
698
+ cwd: squadCwd
699
+ });
700
+ addTurn(transcript, scanners[0].name, "scanner", output, estimateTurnCost(options.model || "haiku"));
701
+ } else {
702
+ log(`Turns ${transcript.turns.length + 1}-${transcript.turns.length + scanners.length}: ${scanners.map((s) => s.name).join(", ")} (scanners, parallel)`);
703
+ const scannerPromises = scanners.map(
704
+ (scanner) => executeAgentTurnAsync({
705
+ agentName: scanner.name,
706
+ agentPath: scanner.path,
707
+ role: "scanner",
708
+ squadName: squad.name,
709
+ model: options.model || modelForRole("scanner"),
710
+ transcript,
711
+ // snapshot — all scanners see same context
712
+ cwd: squadCwd
713
+ }).then((output) => ({ agent: scanner, output }))
714
+ );
715
+ const scannerResults = await Promise.all(scannerPromises);
716
+ for (const { agent, output } of scannerResults) {
717
+ addTurn(transcript, agent.name, "scanner", output, estimateTurnCost(options.model || "haiku"));
718
+ }
719
+ }
720
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
721
+ if (conv.converged) {
722
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
723
+ }
724
+ }
725
+ if (workers.length === 1) {
726
+ log(`Turn ${transcript.turns.length + 1}: ${workers[0].name} (worker)`);
727
+ const output = executeAgentTurn({
728
+ agentName: workers[0].name,
729
+ agentPath: workers[0].path,
730
+ role: "worker",
731
+ squadName: squad.name,
732
+ model: options.model || modelForRole("worker"),
733
+ transcript,
734
+ cwd: squadCwd
735
+ });
736
+ if (output.startsWith("[ERROR]")) {
737
+ process.stderr.write(` [WARN] Worker ${workers[0].name} errored: ${output}
738
+ `);
739
+ }
740
+ addTurn(transcript, workers[0].name, "worker", output, estimateTurnCost(options.model || "sonnet"));
741
+ } else if (workers.length > 1) {
742
+ log(`Turns ${transcript.turns.length + 1}-${transcript.turns.length + workers.length}: ${workers.map((w) => w.name).join(", ")} (workers, parallel)`);
743
+ const workerPromises = workers.map(
744
+ (worker) => executeAgentTurnAsync({
745
+ agentName: worker.name,
746
+ agentPath: worker.path,
747
+ role: "worker",
748
+ squadName: squad.name,
749
+ model: options.model || modelForRole("worker"),
750
+ transcript,
751
+ // snapshot — all workers see same context
752
+ cwd: squadCwd
753
+ }).then((output) => ({ agent: worker, output }))
754
+ );
755
+ const workerResults = await Promise.all(workerPromises);
756
+ for (const { agent, output } of workerResults) {
757
+ if (output.startsWith("[ERROR]")) {
758
+ process.stderr.write(` [WARN] Worker ${agent.name} errored: ${output}
759
+ `);
760
+ }
761
+ addTurn(transcript, agent.name, "worker", output, estimateTurnCost(options.model || "sonnet"));
762
+ }
763
+ }
764
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
765
+ if (conv.converged) {
766
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
767
+ }
768
+ log(`Turn ${transcript.turns.length + 1}: ${lead.name} (lead review)`);
769
+ const reviewOutput = executeAgentTurn({
770
+ agentName: lead.name,
771
+ agentPath: lead.path,
772
+ role: "lead",
773
+ squadName: squad.name,
774
+ model: options.model || modelForRole("lead"),
775
+ transcript,
776
+ cwd: squadCwd
777
+ });
778
+ addTurn(transcript, lead.name, "lead", reviewOutput, estimateTurnCost(options.model || "sonnet"));
779
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
780
+ if (conv.converged) {
781
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
782
+ }
783
+ if (verifiers.length === 1) {
784
+ log(`Turn ${transcript.turns.length + 1}: ${verifiers[0].name} (verifier)`);
785
+ const output = executeAgentTurn({
786
+ agentName: verifiers[0].name,
787
+ agentPath: verifiers[0].path,
788
+ role: "verifier",
789
+ squadName: squad.name,
790
+ model: options.model || modelForRole("verifier"),
791
+ transcript,
792
+ cwd: squadCwd
793
+ });
794
+ addTurn(transcript, verifiers[0].name, "verifier", output, estimateTurnCost(options.model || "haiku"));
795
+ } else if (verifiers.length > 1) {
796
+ log(`Turns ${transcript.turns.length + 1}-${transcript.turns.length + verifiers.length}: ${verifiers.map((v) => v.name).join(", ")} (verifiers, parallel)`);
797
+ const verifierPromises = verifiers.map(
798
+ (verifier) => executeAgentTurnAsync({
799
+ agentName: verifier.name,
800
+ agentPath: verifier.path,
801
+ role: "verifier",
802
+ squadName: squad.name,
803
+ model: options.model || modelForRole("verifier"),
804
+ transcript,
805
+ cwd: squadCwd
806
+ }).then((output) => ({ agent: verifier, output }))
807
+ );
808
+ const verifierResults = await Promise.all(verifierPromises);
809
+ for (const { agent, output } of verifierResults) {
810
+ addTurn(transcript, agent.name, "verifier", output, estimateTurnCost(options.model || "haiku"));
811
+ }
812
+ }
813
+ if (verifiers.length > 0) {
814
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
815
+ if (conv.converged) {
816
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
817
+ }
818
+ }
819
+ }
820
+ return {
821
+ transcript,
822
+ turnCount: transcript.turns.length,
823
+ totalCost: transcript.totalCost,
824
+ converged: false,
825
+ reason: `Max cycles reached (${MAX_CYCLES})`
826
+ };
827
+ }
828
+ function saveTranscript(transcript) {
829
+ const squadsDir = findSquadsDir();
830
+ if (!squadsDir) return null;
831
+ const convDir = join(squadsDir, "..", "conversations", transcript.squad);
832
+ if (!existsSync(convDir)) {
833
+ mkdirSync(convDir, { recursive: true });
834
+ }
835
+ const id = Date.now().toString(36);
836
+ const filePath = join(convDir, `${id}.md`);
837
+ const lines = [
838
+ `# Conversation: ${transcript.squad}`,
839
+ `Started: ${transcript.startedAt}`,
840
+ `Turns: ${transcript.turns.length}`,
841
+ `Estimated cost: $${transcript.totalCost.toFixed(2)}`,
842
+ "",
843
+ "---",
844
+ ""
845
+ ];
846
+ for (const turn of transcript.turns) {
847
+ lines.push(`## ${turn.agent} (${turn.role}) \u2014 ${turn.timestamp}`);
848
+ lines.push("");
849
+ lines.push(turn.content);
850
+ lines.push("");
851
+ lines.push("---");
852
+ lines.push("");
853
+ }
854
+ writeFileSync(filePath, lines.join("\n"));
855
+ return filePath;
856
+ }
857
+
858
+ // src/commands/run.ts
859
+ import { homedir } from "os";
860
+ var CLOUD_POLL_INTERVAL_MS = 3e3;
861
+ var CLOUD_POLL_TIMEOUT_MS = 30 * 60 * 1e3;
862
+ var DEFAULT_LEARNINGS_LIMIT = 5;
863
+ var DEFAULT_CONTEXT_TOKENS = 8e3;
864
+ var DEFAULT_FALLBACK_CHARS = 2e3;
865
+ var MAX_AGENT_BRIEFS = 3;
866
+ var MAX_SQUAD_BRIEFS = 2;
867
+ var MAX_LEARNINGS_CHARS = 1500;
868
+ var MAX_LEAD_STATE_CHARS = 1e3;
869
+ var EXECUTION_EVENT_TIMEOUT_MS = 5e3;
870
+ var VERIFICATION_STATE_MAX_CHARS = 2e3;
871
+ var VERIFICATION_EXEC_TIMEOUT_MS = 3e4;
872
+ var DRYRUN_DEF_MAX_CHARS = 500;
873
+ var DRYRUN_CONTEXT_MAX_CHARS = 800;
874
+ var DEFAULT_SCHEDULED_COOLDOWN_MS = 6 * 60 * 60 * 1e3;
875
+ var DEFAULT_TIMEOUT_MINUTES = 30;
876
+ var SOFT_DEADLINE_RATIO = 0.7;
877
+ var LOG_FILE_INIT_DELAY_MS = 500;
878
+ var VERBOSE_COMMAND_MAX_CHARS = 50;
879
+ async function registerContextWithBridge(ctx) {
880
+ const bridgeUrl = getBridgeUrl();
881
+ try {
882
+ const response = await fetch(`${bridgeUrl}/api/context/register`, {
883
+ method: "POST",
884
+ headers: { "Content-Type": "application/json" },
885
+ body: JSON.stringify({
886
+ execution_id: ctx.executionId,
887
+ squad: ctx.squad,
888
+ agent: ctx.agent,
889
+ task_type: ctx.taskType,
890
+ trigger: ctx.trigger
891
+ })
892
+ });
893
+ if (!response.ok) {
894
+ return false;
895
+ }
896
+ return true;
897
+ } catch {
898
+ return false;
899
+ }
900
+ }
901
+ async function checkPreflightGates(squad, agent) {
902
+ const bridgeUrl = getBridgeUrl();
903
+ try {
904
+ const response = await fetch(`${bridgeUrl}/api/execution/preflight`, {
905
+ method: "POST",
906
+ headers: { "Content-Type": "application/json" },
907
+ body: JSON.stringify({ squad, agent })
908
+ });
909
+ if (!response.ok) {
910
+ return { allowed: true, gates: {} };
911
+ }
912
+ return await response.json();
913
+ } catch {
914
+ return { allowed: true, gates: {} };
915
+ }
916
+ }
917
+ async function fetchLearnings(squad, limit = DEFAULT_LEARNINGS_LIMIT) {
918
+ const bridgeUrl = getBridgeUrl();
919
+ try {
920
+ const response = await fetch(
921
+ `${bridgeUrl}/api/learnings/relevant?squad=${encodeURIComponent(squad)}&limit=${limit}`
922
+ );
923
+ if (!response.ok) {
924
+ return [];
925
+ }
926
+ const data = await response.json();
927
+ return data.learnings || [];
928
+ } catch {
929
+ return [];
930
+ }
931
+ }
932
+ function loadApprovalInstructions() {
933
+ const squadsDir = findSquadsDir();
934
+ if (!squadsDir) return "";
935
+ const instructionsPath = join2(dirname(squadsDir), "config", "approval-instructions.md");
936
+ if (existsSync2(instructionsPath)) {
937
+ try {
938
+ return readFileSync2(instructionsPath, "utf-8");
939
+ } catch {
940
+ return "";
941
+ }
942
+ }
943
+ return "";
944
+ }
945
+ function loadPostExecution(squadName, agentName) {
946
+ const squadsDir = findSquadsDir();
947
+ if (squadsDir) {
948
+ const postExecPath = join2(dirname(squadsDir), "config", "post-execution.md");
949
+ if (existsSync2(postExecPath)) {
950
+ try {
951
+ const template = readFileSync2(postExecPath, "utf-8");
952
+ return template.replace(/\{\{squadName\}\}/g, squadName).replace(/\{\{agentName\}\}/g, agentName);
953
+ } catch {
954
+ }
955
+ }
956
+ }
957
+ return `After completion:
958
+ - Create a branch, commit with Conventional Commits, push, and open a PR targeting develop
959
+ - NEVER commit to main directly
960
+ - Type /exit when done`;
961
+ }
962
+ function gatherSquadContext(squadName, agentName, options = {}) {
963
+ const squadsDir = findSquadsDir();
964
+ if (!squadsDir) return "";
965
+ const memoryDir = findMemoryDir();
966
+ const maxTokens = options.maxTokens || DEFAULT_CONTEXT_TOKENS;
967
+ const sections = [];
968
+ let estimatedTokens = 0;
969
+ const estimateTokens = (text) => Math.ceil(text.length / 4);
970
+ const squadFile = join2(squadsDir, squadName, "SQUAD.md");
971
+ if (existsSync2(squadFile)) {
972
+ try {
973
+ const squadContent = readFileSync2(squadFile, "utf-8");
974
+ const missionMatch = squadContent.match(/## Mission[\s\S]*?(?=\n## |$)/i);
975
+ const goalsMatch = squadContent.match(/## (?:Goals|Objectives)[\s\S]*?(?=\n## |$)/i);
976
+ const outputMatch = squadContent.match(/## Output[\s\S]*?(?=\n## |$)/i);
977
+ const contextMatch = squadContent.match(/## Context[\s\S]*?(?=\n## |$)/i);
978
+ let squadContext = "";
979
+ if (missionMatch) squadContext += missionMatch[0] + "\n";
980
+ if (goalsMatch) squadContext += goalsMatch[0] + "\n";
981
+ if (outputMatch) squadContext += outputMatch[0] + "\n";
982
+ if (contextMatch) squadContext += contextMatch[0] + "\n";
983
+ if (!squadContext && squadContent.length > 0) {
984
+ squadContext = squadContent.substring(0, DEFAULT_FALLBACK_CHARS);
985
+ }
986
+ if (squadContext) {
987
+ const tokens = estimateTokens(squadContext);
988
+ if (estimatedTokens + tokens < maxTokens) {
989
+ sections.push(`## Squad Context (${squadName})
990
+ ${squadContext.trim()}`);
991
+ estimatedTokens += tokens;
992
+ }
993
+ }
994
+ } catch {
995
+ }
996
+ }
997
+ if (memoryDir) {
998
+ const stateFile = join2(memoryDir, squadName, agentName, "state.md");
999
+ if (existsSync2(stateFile)) {
1000
+ try {
1001
+ const stateContent = readFileSync2(stateFile, "utf-8");
1002
+ const tokens = estimateTokens(stateContent);
1003
+ if (estimatedTokens + tokens < maxTokens && stateContent.trim()) {
1004
+ sections.push(`## Your Previous State
1005
+ This is your memory from your last execution:
1006
+
1007
+ ${stateContent.trim()}`);
1008
+ estimatedTokens += tokens;
1009
+ }
1010
+ } catch {
1011
+ }
1012
+ }
1013
+ }
1014
+ if (memoryDir) {
1015
+ const briefsDir = join2(memoryDir, squadName, agentName, "briefs");
1016
+ if (existsSync2(briefsDir)) {
1017
+ try {
1018
+ const briefFiles = readdirSync(briefsDir).filter((f) => f.endsWith(".md")).slice(0, MAX_AGENT_BRIEFS);
1019
+ for (const briefFile of briefFiles) {
1020
+ const briefPath = join2(briefsDir, briefFile);
1021
+ const briefContent = readFileSync2(briefPath, "utf-8");
1022
+ const tokens = estimateTokens(briefContent);
1023
+ if (estimatedTokens + tokens < maxTokens) {
1024
+ sections.push(`## Brief: ${briefFile.replace(".md", "")}
1025
+ ${briefContent.trim()}`);
1026
+ estimatedTokens += tokens;
1027
+ } else {
1028
+ break;
1029
+ }
1030
+ }
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ }
1035
+ if (memoryDir) {
1036
+ const squadBriefsDir = join2(memoryDir, squadName, "_briefs");
1037
+ if (existsSync2(squadBriefsDir)) {
1038
+ try {
1039
+ const squadBriefs = readdirSync(squadBriefsDir).filter((f) => f.endsWith(".md")).slice(0, MAX_SQUAD_BRIEFS);
1040
+ for (const briefFile of squadBriefs) {
1041
+ const briefPath = join2(squadBriefsDir, briefFile);
1042
+ const briefContent = readFileSync2(briefPath, "utf-8");
1043
+ const tokens = estimateTokens(briefContent);
1044
+ if (estimatedTokens + tokens < maxTokens) {
1045
+ sections.push(`## Squad Brief: ${briefFile.replace(".md", "")}
1046
+ ${briefContent.trim()}`);
1047
+ estimatedTokens += tokens;
1048
+ } else {
1049
+ break;
1050
+ }
1051
+ }
1052
+ } catch {
1053
+ }
1054
+ }
1055
+ }
1056
+ if (memoryDir) {
1057
+ const briefingPath = join2(memoryDir, "daily-briefing.md");
1058
+ if (existsSync2(briefingPath)) {
1059
+ try {
1060
+ const briefingContent = readFileSync2(briefingPath, "utf-8");
1061
+ if (briefingContent.trim()) {
1062
+ const tokens = estimateTokens(briefingContent);
1063
+ if (estimatedTokens + tokens < maxTokens) {
1064
+ sections.push(`## Daily Briefing
1065
+ ${briefingContent.trim()}`);
1066
+ estimatedTokens += tokens;
1067
+ }
1068
+ }
1069
+ } catch {
1070
+ }
1071
+ }
1072
+ }
1073
+ if (memoryDir && options.agentPath) {
1074
+ const frontmatter = parseAgentFrontmatter(options.agentPath);
1075
+ if (frontmatter.context_from && frontmatter.context_from.length > 0) {
1076
+ for (const relatedSquad of frontmatter.context_from) {
1077
+ const learningsPath = join2(memoryDir, relatedSquad, "shared", "learnings.md");
1078
+ if (existsSync2(learningsPath)) {
1079
+ try {
1080
+ let learningsContent = readFileSync2(learningsPath, "utf-8");
1081
+ if (learningsContent.trim()) {
1082
+ if (learningsContent.length > MAX_LEARNINGS_CHARS) {
1083
+ learningsContent = learningsContent.slice(0, MAX_LEARNINGS_CHARS) + "\n...(truncated)";
1084
+ }
1085
+ const tokens = estimateTokens(learningsContent);
1086
+ if (estimatedTokens + tokens < maxTokens) {
1087
+ sections.push(`## ${relatedSquad} Squad Learnings
1088
+ ${learningsContent.trim()}`);
1089
+ estimatedTokens += tokens;
1090
+ }
1091
+ }
1092
+ } catch {
1093
+ }
1094
+ }
1095
+ const leadStatePath = join2(memoryDir, relatedSquad, `${relatedSquad}-lead`, "state.md");
1096
+ if (existsSync2(leadStatePath)) {
1097
+ try {
1098
+ let leadState = readFileSync2(leadStatePath, "utf-8");
1099
+ if (leadState.trim()) {
1100
+ if (leadState.length > MAX_LEAD_STATE_CHARS) {
1101
+ leadState = leadState.slice(0, MAX_LEAD_STATE_CHARS) + "\n...(truncated)";
1102
+ }
1103
+ const tokens = estimateTokens(leadState);
1104
+ if (estimatedTokens + tokens < maxTokens) {
1105
+ sections.push(`## ${relatedSquad} Lead State
1106
+ ${leadState.trim()}`);
1107
+ estimatedTokens += tokens;
1108
+ }
1109
+ }
1110
+ } catch {
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+ }
1116
+ if (sections.length === 0) {
1117
+ return "";
1118
+ }
1119
+ if (options.verbose) {
1120
+ writeLine(` ${colors.dim}Context: ${sections.length} sections (~${estimatedTokens} tokens)${RESET}`);
1121
+ }
1122
+ return `
1123
+ # EXISTING CONTEXT
1124
+ Build on this existing knowledge - do NOT start from scratch:
1125
+
1126
+ ${sections.join("\n\n")}
1127
+ `;
1128
+ }
1129
+ function generateExecutionId() {
1130
+ const timestamp = Date.now().toString(36);
1131
+ const random = Math.random().toString(36).substring(2, 8);
1132
+ return `exec_${timestamp}_${random}`;
1133
+ }
1134
+ function selectMcpConfig(squadName, squad) {
1135
+ if (squad?.context?.mcp && squad.context.mcp.length > 0) {
1136
+ return resolveMcpConfigPath(squadName, squad.context.mcp);
1137
+ }
1138
+ const home = process.env.HOME || "";
1139
+ const configsDir = join2(home, ".claude", "mcp-configs");
1140
+ const squadConfigs = {
1141
+ website: "website.json",
1142
+ research: "research.json",
1143
+ intelligence: "research.json",
1144
+ analytics: "data.json",
1145
+ engineering: "data.json"
1146
+ };
1147
+ const configFile = squadConfigs[squadName.toLowerCase()];
1148
+ if (configFile) {
1149
+ const configPath = join2(configsDir, configFile);
1150
+ if (existsSync2(configPath)) {
1151
+ return configPath;
1152
+ }
1153
+ }
1154
+ return "";
1155
+ }
1156
+ function detectTaskType(agentName) {
1157
+ const name = agentName.toLowerCase();
1158
+ if (name.includes("eval") || name.includes("critic") || name.includes("review") || name.includes("test")) {
1159
+ return "evaluation";
1160
+ }
1161
+ if (name.includes("lead") || name.includes("orchestrator")) {
1162
+ return "lead";
1163
+ }
1164
+ if (name.includes("research") || name.includes("analyst") || name.includes("intel")) {
1165
+ return "research";
1166
+ }
1167
+ return "execution";
1168
+ }
1169
+ function getClaudeModelAlias(model) {
1170
+ const lower = model.toLowerCase();
1171
+ if (lower === "opus" || lower === "sonnet" || lower === "haiku") {
1172
+ return lower;
1173
+ }
1174
+ if (lower.includes("opus")) return "opus";
1175
+ if (lower.includes("sonnet")) return "sonnet";
1176
+ if (lower.includes("haiku")) return "haiku";
1177
+ return void 0;
1178
+ }
1179
+ function resolveModel(explicitModel, squad, taskType) {
1180
+ if (explicitModel) {
1181
+ return explicitModel;
1182
+ }
1183
+ const modelConfig = squad?.context?.model;
1184
+ if (!modelConfig) {
1185
+ return void 0;
1186
+ }
1187
+ switch (taskType) {
1188
+ case "evaluation":
1189
+ return modelConfig.cheap || modelConfig.default;
1190
+ case "lead":
1191
+ return modelConfig.expensive || modelConfig.default;
1192
+ case "research":
1193
+ case "execution":
1194
+ default:
1195
+ return modelConfig.default;
1196
+ }
1197
+ }
1198
+ function ensureProjectTrusted(projectPath) {
1199
+ const configPath = join2(process.env.HOME || "", ".claude.json");
1200
+ if (!existsSync2(configPath)) {
1201
+ return;
1202
+ }
1203
+ try {
1204
+ const config = JSON.parse(readFileSync2(configPath, "utf-8"));
1205
+ if (!config.projects) {
1206
+ config.projects = {};
1207
+ }
1208
+ if (!config.projects[projectPath]) {
1209
+ config.projects[projectPath] = {};
1210
+ }
1211
+ if (!config.projects[projectPath].hasTrustDialogAccepted) {
1212
+ config.projects[projectPath].hasTrustDialogAccepted = true;
1213
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
1214
+ }
1215
+ } catch {
1216
+ }
1217
+ }
1218
+ function getProjectRoot() {
1219
+ const squadsDir = findSquadsDir();
1220
+ if (squadsDir) {
1221
+ return dirname(dirname(squadsDir));
1222
+ }
1223
+ return process.cwd();
1224
+ }
1225
+ function getExecutionLogPath(squadName, agentName) {
1226
+ const memoryDir = findMemoryDir();
1227
+ if (!memoryDir) return null;
1228
+ return join2(memoryDir, squadName, agentName, "executions.md");
1229
+ }
1230
+ function logExecution(record) {
1231
+ const logPath = getExecutionLogPath(record.squadName, record.agentName);
1232
+ if (!logPath) return;
1233
+ const dir = dirname(logPath);
1234
+ if (!existsSync2(dir)) {
1235
+ mkdirSync2(dir, { recursive: true });
1236
+ }
1237
+ let content = "";
1238
+ if (existsSync2(logPath)) {
1239
+ content = readFileSync2(logPath, "utf-8").trimEnd();
1240
+ } else {
1241
+ content = `# ${record.squadName}/${record.agentName} - Execution Log`;
1242
+ }
1243
+ const entry = `
1244
+
1245
+ ---
1246
+ <!-- exec:${record.executionId} -->
1247
+ **${record.startTime}** | Status: ${record.status}
1248
+ - ID: \`${record.executionId}\`
1249
+ - Trigger: ${record.trigger || "manual"}
1250
+ - Task Type: ${record.taskType || "execution"}
1251
+ `;
1252
+ writeFileSync2(logPath, content + entry);
1253
+ }
1254
+ function updateExecutionStatus(squadName, agentName, executionId, status, details) {
1255
+ const logPath = getExecutionLogPath(squadName, agentName);
1256
+ if (!logPath || !existsSync2(logPath)) return;
1257
+ let content = readFileSync2(logPath, "utf-8");
1258
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
1259
+ const execMarker = `<!-- exec:${executionId} -->`;
1260
+ const markerIndex = content.indexOf(execMarker);
1261
+ if (markerIndex === -1) return;
1262
+ const nextEntryIndex = content.indexOf("\n---\n", markerIndex + 1);
1263
+ const entryEnd = nextEntryIndex === -1 ? content.length : nextEntryIndex;
1264
+ const entryStart = content.lastIndexOf("\n---\n", markerIndex);
1265
+ const currentEntry = content.slice(entryStart, entryEnd);
1266
+ const durationStr = details?.durationMs ? `${(details.durationMs / 1e3).toFixed(1)}s` : "unknown";
1267
+ let updatedEntry = currentEntry.replace(/Status: running/, `Status: ${status}`) + `- Completed: ${endTime}
1268
+ - Duration: ${durationStr}`;
1269
+ if (details?.outcome) {
1270
+ updatedEntry += `
1271
+ - Outcome: ${details.outcome}`;
1272
+ }
1273
+ if (details?.error) {
1274
+ updatedEntry += `
1275
+ - Error: ${details.error}`;
1276
+ }
1277
+ content = content.slice(0, entryStart) + updatedEntry + content.slice(entryEnd);
1278
+ writeFileSync2(logPath, content);
1279
+ }
1280
+ async function autoCommitAgentWork(squadName, agentName, executionId, provider2) {
1281
+ const { execSync: execSync3 } = await import("child_process");
1282
+ const { detectGitHubRepo } = await import("./github-UQTM5KMS.js");
1283
+ const projectRoot = getProjectRoot();
1284
+ try {
1285
+ const status = execSync3("git status --porcelain", {
1286
+ encoding: "utf-8",
1287
+ cwd: projectRoot
1288
+ }).trim();
1289
+ if (!status) {
1290
+ return { committed: false };
1291
+ }
1292
+ const botEnv = await getBotGitEnv();
1293
+ const execOpts = {
1294
+ cwd: projectRoot,
1295
+ env: { ...process.env, ...botEnv }
1296
+ };
1297
+ execSync3("git add -A", execOpts);
1298
+ const shortExecId = executionId.slice(0, 12);
1299
+ const coAuthor = getCoAuthorTrailer(provider2 || "claude");
1300
+ const msgFile = join2(projectRoot, ".git", "SQUADS_COMMIT_MSG");
1301
+ writeFileSync2(msgFile, `feat(${squadName}/${agentName}): execution ${shortExecId}
1302
+
1303
+ ${coAuthor}
1304
+ `);
1305
+ try {
1306
+ execSync3(`git commit --file "${msgFile}"`, execOpts);
1307
+ } finally {
1308
+ try {
1309
+ unlinkSync(msgFile);
1310
+ } catch {
1311
+ }
1312
+ }
1313
+ try {
1314
+ const { spawnSync } = await import("child_process");
1315
+ const repo = detectGitHubRepo(projectRoot);
1316
+ if (repo && /^[\w.-]+\/[\w.-]+$/.test(repo)) {
1317
+ const pushUrl = await getBotPushUrl(repo);
1318
+ if (pushUrl) {
1319
+ spawnSync("git", ["push", pushUrl, "HEAD"], { ...execOpts, stdio: "pipe" });
1320
+ } else {
1321
+ spawnSync("git", ["push", "origin", "HEAD"], { ...execOpts, stdio: "pipe" });
1322
+ }
1323
+ } else {
1324
+ spawnSync("git", ["push", "origin", "HEAD"], { ...execOpts, stdio: "pipe" });
1325
+ }
1326
+ } catch {
1327
+ }
1328
+ return { committed: true, message: `Committed changes from ${agentName}` };
1329
+ } catch (error) {
1330
+ return { committed: false, error: String(error) };
1331
+ }
1332
+ }
1333
+ function getLastExecutionTime(squadName, agentName) {
1334
+ const logPath = getExecutionLogPath(squadName, agentName);
1335
+ if (!logPath || !existsSync2(logPath)) return null;
1336
+ const content = readFileSync2(logPath, "utf-8");
1337
+ const timestamps = content.match(/\*\*(\d{4}-\d{2}-\d{2}T[\d:.]+Z)\*\*/g);
1338
+ if (!timestamps || timestamps.length === 0) return null;
1339
+ const lastTimestamp = timestamps[timestamps.length - 1].replace(/\*\*/g, "");
1340
+ return new Date(lastTimestamp);
1341
+ }
1342
+ function checkLocalCooldown(squadName, agentName, cooldownMs) {
1343
+ const lastExec = getLastExecutionTime(squadName, agentName);
1344
+ if (!lastExec) return { ok: true, cooldownMs };
1345
+ const elapsedMs = Date.now() - lastExec.getTime();
1346
+ if (elapsedMs < cooldownMs) {
1347
+ return { ok: false, elapsedMs, cooldownMs };
1348
+ }
1349
+ return { ok: true, elapsedMs, cooldownMs };
1350
+ }
1351
+ function formatDuration(ms) {
1352
+ const hours = Math.floor(ms / (60 * 60 * 1e3));
1353
+ const minutes = Math.floor(ms % (60 * 60 * 1e3) / (60 * 1e3));
1354
+ if (hours >= 24) {
1355
+ const days = Math.floor(hours / 24);
1356
+ const remainingHours = hours % 24;
1357
+ return remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
1358
+ }
1359
+ if (hours > 0) {
1360
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
1361
+ }
1362
+ return `${minutes}m`;
1363
+ }
1364
+ function extractMcpServersFromDefinition(definition) {
1365
+ const servers = /* @__PURE__ */ new Set();
1366
+ const knownServers = [
1367
+ "chrome-devtools",
1368
+ "firecrawl",
1369
+ "context7",
1370
+ "huggingface"
1371
+ ];
1372
+ for (const server of knownServers) {
1373
+ if (definition.toLowerCase().includes(server)) {
1374
+ servers.add(server);
1375
+ }
1376
+ }
1377
+ const mcpMatch = definition.match(/mcp:\s*\n((?:\s*-\s*\S+\s*\n?)+)/i);
1378
+ if (mcpMatch) {
1379
+ const lines = mcpMatch[1].split("\n");
1380
+ for (const line of lines) {
1381
+ const serverMatch = line.match(/^\s*-\s*(\S+)/);
1382
+ if (serverMatch) {
1383
+ servers.add(serverMatch[1]);
1384
+ }
1385
+ }
1386
+ }
1387
+ return Array.from(servers);
1388
+ }
1389
+ function parseAgentFrontmatter(agentPath) {
1390
+ if (!existsSync2(agentPath)) return {};
1391
+ const content = readFileSync2(agentPath, "utf-8");
1392
+ const lines = content.split("\n");
1393
+ let inFrontmatter = false;
1394
+ const yamlLines = [];
1395
+ for (const line of lines) {
1396
+ if (line.trim() === "---") {
1397
+ if (inFrontmatter) break;
1398
+ inFrontmatter = true;
1399
+ continue;
1400
+ }
1401
+ if (inFrontmatter) {
1402
+ yamlLines.push(line);
1403
+ }
1404
+ }
1405
+ if (yamlLines.length === 0) return {};
1406
+ const yaml = yamlLines.join("\n");
1407
+ const result = {};
1408
+ const contextMatch = yaml.match(/context_from:\s*\[([^\]]+)\]/);
1409
+ if (contextMatch) {
1410
+ result.context_from = contextMatch[1].split(",").map((s) => s.trim());
1411
+ }
1412
+ const criteriaMatch = yaml.match(/acceptance_criteria:\s*\|\n((?:\s+.+\n?)*)/);
1413
+ if (criteriaMatch) {
1414
+ result.acceptance_criteria = criteriaMatch[1].replace(/^ {2}/gm, "").trim();
1415
+ }
1416
+ const retriesMatch = yaml.match(/max_retries:\s*(\d+)/);
1417
+ if (retriesMatch) {
1418
+ result.max_retries = parseInt(retriesMatch[1], 10);
1419
+ }
1420
+ const cooldownMatch = yaml.match(/cooldown:\s*["']?([^"'\n]+)["']?/);
1421
+ if (cooldownMatch) {
1422
+ result.cooldown = cooldownMatch[1].trim();
1423
+ }
1424
+ return result;
1425
+ }
1426
+ async function emitExecutionEvent(eventType, data) {
1427
+ const apiUrl = getApiUrl();
1428
+ if (apiUrl) {
1429
+ try {
1430
+ await fetch(`${apiUrl}/events/ingest`, {
1431
+ method: "POST",
1432
+ headers: { "Content-Type": "application/json" },
1433
+ body: JSON.stringify({
1434
+ source: "scheduler",
1435
+ event_type: eventType,
1436
+ data: {
1437
+ squad: data.squad,
1438
+ agent: data.agent,
1439
+ execution_id: data.executionId,
1440
+ ...data.error ? { error: data.error } : {}
1441
+ }
1442
+ }),
1443
+ signal: AbortSignal.timeout(EXECUTION_EVENT_TIMEOUT_MS)
1444
+ });
1445
+ return;
1446
+ } catch {
1447
+ }
1448
+ }
1449
+ try {
1450
+ const memDir = findMemoryDir();
1451
+ if (!memDir) return;
1452
+ const eventsDir = join2(memDir, data.squad, data.agent);
1453
+ if (!existsSync2(eventsDir)) {
1454
+ mkdirSync2(eventsDir, { recursive: true });
1455
+ }
1456
+ const eventsPath = join2(eventsDir, "events.md");
1457
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1458
+ const entry = `
1459
+ ## ${timestamp}: ${eventType}
1460
+ - execution_id: ${data.executionId}
1461
+ ${data.error ? `- error: ${data.error}
1462
+ ` : ""}`;
1463
+ let existing = "";
1464
+ if (existsSync2(eventsPath)) {
1465
+ existing = readFileSync2(eventsPath, "utf-8");
1466
+ }
1467
+ writeFileSync2(eventsPath, existing + entry);
1468
+ } catch {
1469
+ }
1470
+ }
1471
+ async function verifyExecution(squadName, agentName, criteria, options = {}) {
1472
+ const { execSync: execSync3 } = await import("child_process");
1473
+ const projectRoot = getProjectRoot();
1474
+ let stateContent = "";
1475
+ const memDir = findMemoryDir();
1476
+ if (memDir) {
1477
+ const statePath = join2(memDir, squadName, agentName, "state.md");
1478
+ if (existsSync2(statePath)) {
1479
+ stateContent = readFileSync2(statePath, "utf-8").slice(0, VERIFICATION_STATE_MAX_CHARS);
1480
+ }
1481
+ }
1482
+ let recentCommits = "";
1483
+ try {
1484
+ recentCommits = execSync3("git log --oneline -5 --no-color", {
1485
+ encoding: "utf-8",
1486
+ cwd: projectRoot
1487
+ }).trim();
1488
+ } catch {
1489
+ recentCommits = "(no commits found)";
1490
+ }
1491
+ const verifyPrompt = `You are verifying whether an agent completed its task successfully.
1492
+
1493
+ Agent: ${squadName}/${agentName}
1494
+
1495
+ ## Acceptance Criteria
1496
+ ${criteria}
1497
+
1498
+ ## Evidence
1499
+
1500
+ ### Agent State File
1501
+ ${stateContent || "(empty or not found)"}
1502
+
1503
+ ### Recent Git Commits
1504
+ ${recentCommits}
1505
+
1506
+ ## Instructions
1507
+ Evaluate whether the acceptance criteria are met based on the evidence.
1508
+ Respond with EXACTLY one line:
1509
+ PASS: <brief reason>
1510
+ or
1511
+ FAIL: <brief reason>`;
1512
+ try {
1513
+ const escapedPrompt = verifyPrompt.replace(/'/g, "'\\''");
1514
+ const result = execSync3(
1515
+ `claude --print --model haiku -- '${escapedPrompt}'`,
1516
+ { encoding: "utf-8", cwd: projectRoot, timeout: VERIFICATION_EXEC_TIMEOUT_MS }
1517
+ ).trim();
1518
+ if (options.verbose) {
1519
+ writeLine(` ${colors.dim}Verification: ${result}${RESET}`);
1520
+ }
1521
+ if (result.startsWith("PASS")) {
1522
+ return { passed: true, reason: result.replace(/^PASS:\s*/, "") };
1523
+ }
1524
+ return { passed: false, reason: result.replace(/^FAIL:\s*/, "") };
1525
+ } catch (error) {
1526
+ if (options.verbose) {
1527
+ writeLine(` ${colors.dim}Verification error (defaulting to PASS): ${error}${RESET}`);
1528
+ }
1529
+ return { passed: true, reason: "Verification unavailable \u2014 defaulting to pass" };
1530
+ }
1531
+ }
1532
+ async function runCloudDispatch(squadName, agentName, options) {
1533
+ const apiUrl = getApiUrl();
1534
+ if (!apiUrl) {
1535
+ writeLine(` ${colors.red}${icons.error} API URL not configured${RESET}`);
1536
+ writeLine(` ${colors.dim}Run: squads config use staging (or set SQUADS_API_URL)${RESET}`);
1537
+ process.exit(1);
1538
+ }
1539
+ if (!isLoggedIn()) {
1540
+ writeLine(` ${colors.red}${icons.error} Not logged in${RESET}`);
1541
+ writeLine(` ${colors.dim}Run \`squads login\` to authenticate before using --cloud${RESET}`);
1542
+ process.exit(1);
1543
+ }
1544
+ const session = loadSession();
1545
+ const headers = {
1546
+ "Content-Type": "application/json"
1547
+ };
1548
+ if (session?.accessToken) {
1549
+ headers["Authorization"] = `Bearer ${session.accessToken}`;
1550
+ }
1551
+ const apiKey = process.env.SQUADS_PLATFORM_API_TOKEN || process.env.SCHEDULER_API_KEY;
1552
+ if (apiKey) {
1553
+ headers["X-API-Key"] = apiKey;
1554
+ }
1555
+ const spinner = ora(`Dispatching ${squadName}/${agentName} to cloud...`).start();
1556
+ try {
1557
+ const dispatchRes = await fetch(`${apiUrl}/agent-dispatch`, {
1558
+ method: "POST",
1559
+ headers,
1560
+ body: JSON.stringify({
1561
+ squad: squadName,
1562
+ agent: agentName,
1563
+ trigger_type: "manual",
1564
+ trigger_data: {
1565
+ source: "cli",
1566
+ cloud: true,
1567
+ model: options.model,
1568
+ provider: options.provider,
1569
+ effort: options.effort
1570
+ }
1571
+ })
1572
+ });
1573
+ if (!dispatchRes.ok) {
1574
+ const error = await dispatchRes.text();
1575
+ spinner.fail(`Dispatch failed: ${dispatchRes.status}`);
1576
+ writeLine(` ${colors.dim}${error}${RESET}`);
1577
+ process.exit(1);
1578
+ }
1579
+ const dispatch = await dispatchRes.json();
1580
+ spinner.succeed(`Dispatched to cloud`);
1581
+ writeLine();
1582
+ writeLine(` ${colors.cyan}Dispatch ID${RESET} ${dispatch.dispatch_id}`);
1583
+ writeLine(` ${colors.cyan}Squad${RESET} ${squadName}`);
1584
+ writeLine(` ${colors.cyan}Agent${RESET} ${agentName}`);
1585
+ writeLine();
1586
+ const pollSpinner = ora("Waiting for execution to start...").start();
1587
+ const pollStart = Date.now();
1588
+ let executionId = null;
1589
+ let lastStatus = "";
1590
+ while (Date.now() - pollStart < CLOUD_POLL_TIMEOUT_MS) {
1591
+ try {
1592
+ const execRes = await fetch(
1593
+ `${apiUrl}/agent-executions?squad=${encodeURIComponent(squadName)}&agent=${encodeURIComponent(agentName)}&limit=1`,
1594
+ { headers }
1595
+ );
1596
+ if (execRes.ok) {
1597
+ const executions = await execRes.json();
1598
+ if (executions.length > 0) {
1599
+ const exec2 = executions[0];
1600
+ if (!executionId && exec2.status === "running") {
1601
+ executionId = exec2.execution_id;
1602
+ pollSpinner.text = `Running (${exec2.execution_id})`;
1603
+ }
1604
+ if (executionId && exec2.execution_id === executionId) {
1605
+ if (exec2.status !== lastStatus) {
1606
+ lastStatus = exec2.status;
1607
+ pollSpinner.text = `Status: ${exec2.status}`;
1608
+ }
1609
+ if (exec2.status === "completed") {
1610
+ pollSpinner.succeed("Execution completed");
1611
+ writeLine();
1612
+ writeLine(` ${colors.cyan}Execution${RESET} ${exec2.execution_id}`);
1613
+ if (exec2.summary) {
1614
+ writeLine(` ${colors.cyan}Summary${RESET} ${exec2.summary}`);
1615
+ }
1616
+ if (exec2.duration_seconds) {
1617
+ writeLine(` ${colors.cyan}Duration${RESET} ${Math.round(exec2.duration_seconds)}s`);
1618
+ }
1619
+ if (exec2.cost_usd) {
1620
+ writeLine(` ${colors.cyan}Cost${RESET} $${exec2.cost_usd.toFixed(4)}`);
1621
+ }
1622
+ writeLine();
1623
+ return;
1624
+ }
1625
+ if (exec2.status === "failed") {
1626
+ pollSpinner.fail("Execution failed");
1627
+ writeLine();
1628
+ if (exec2.error) {
1629
+ writeLine(` ${colors.red}Error: ${exec2.error}${RESET}`);
1630
+ }
1631
+ writeLine();
1632
+ process.exit(1);
1633
+ }
1634
+ if (exec2.status === "cancelled") {
1635
+ pollSpinner.warn("Execution cancelled");
1636
+ return;
1637
+ }
1638
+ }
1639
+ }
1640
+ }
1641
+ } catch {
1642
+ }
1643
+ await new Promise((resolve) => setTimeout(resolve, CLOUD_POLL_INTERVAL_MS));
1644
+ }
1645
+ pollSpinner.warn("Poll timeout \u2014 execution may still be running");
1646
+ writeLine(` ${colors.dim}Check status: squads trigger status${RESET}`);
1647
+ if (executionId) {
1648
+ writeLine(` ${colors.dim}Execution ID: ${executionId}${RESET}`);
1649
+ }
1650
+ } catch (error) {
1651
+ spinner.fail("Cloud dispatch failed");
1652
+ writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
1653
+ writeLine();
1654
+ writeLine(` ${colors.dim}Check your network and SQUADS_API_URL setting${RESET}`);
1655
+ process.exit(1);
1656
+ }
1657
+ }
1658
+ async function runCommand(target, options) {
1659
+ const squadsDir = findSquadsDir();
1660
+ if (!squadsDir) {
1661
+ writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
1662
+ writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
1663
+ process.exit(1);
1664
+ }
1665
+ if (!options.dryRun && options.execute === void 0) {
1666
+ options.execute = true;
1667
+ }
1668
+ let squadName = target;
1669
+ let agentFromSlash;
1670
+ if (target.includes("/")) {
1671
+ const parts = target.split("/");
1672
+ squadName = parts[0];
1673
+ agentFromSlash = parts[1];
1674
+ if (!options.agent) {
1675
+ options.agent = agentFromSlash;
1676
+ }
1677
+ }
1678
+ if (options.cloud) {
1679
+ const agentName = options.agent || agentFromSlash;
1680
+ if (!agentName) {
1681
+ writeLine(` ${colors.red}${icons.error} --cloud requires a specific agent${RESET}`);
1682
+ writeLine(` ${colors.dim}Usage: squads run ${squadName} --cloud -a <agent>${RESET}`);
1683
+ writeLine(` ${colors.dim} or: squads run ${squadName}/<agent> --cloud${RESET}`);
1684
+ process.exit(1);
1685
+ }
1686
+ await track(Events.CLI_RUN, { type: "cloud", target: `${squadName}/${agentName}` });
1687
+ await flushEvents();
1688
+ await runCloudDispatch(squadName, agentName, options);
1689
+ return;
1690
+ }
1691
+ const squad = loadSquad(squadName);
1692
+ if (options.execute && !options.dryRun) {
1693
+ const provider2 = options.provider || squad?.providers?.default || "anthropic";
1694
+ const checksOk = await preflightExecutorCheck(provider2);
1695
+ if (!checksOk) {
1696
+ process.exit(1);
1697
+ }
1698
+ }
1699
+ if (squad) {
1700
+ await track(Events.CLI_RUN, { type: "squad", target: squad.name });
1701
+ await flushEvents();
1702
+ await runSquad(squad, squadsDir, options);
1703
+ } else {
1704
+ const agents = listAgents(squadsDir);
1705
+ const agent = agents.find((a) => a.name === target);
1706
+ if (agent && agent.filePath) {
1707
+ const pathParts = agent.filePath.split("/");
1708
+ const squadIdx = pathParts.indexOf("squads");
1709
+ const squadName2 = squadIdx >= 0 ? pathParts[squadIdx + 1] : "unknown";
1710
+ await runAgent(agent.name, agent.filePath, squadName2, options);
1711
+ } else {
1712
+ writeLine(` ${colors.red}Squad or agent "${target}" not found${RESET}`);
1713
+ writeLine(` ${colors.dim}Run \`squads list\` to see available squads and agents.${RESET}`);
1714
+ process.exit(1);
1715
+ }
1716
+ }
1717
+ }
1718
+ async function runSquad(squad, squadsDir, options) {
1719
+ if (!squad) return;
1720
+ if (!options.effort && squad.effort) {
1721
+ options.effort = squad.effort;
1722
+ }
1723
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
1724
+ writeLine();
1725
+ writeLine(` ${gradient("squads")} ${colors.dim}run${RESET} ${colors.cyan}${squad.name}${RESET}`);
1726
+ writeLine();
1727
+ if (squad.mission) {
1728
+ writeLine(` ${colors.dim}${squad.mission}${RESET}`);
1729
+ writeLine();
1730
+ }
1731
+ writeLine(` ${colors.dim}Started: ${startTime}${RESET}`);
1732
+ writeLine();
1733
+ if (options.lead) {
1734
+ await runLeadMode(squad, squadsDir, options);
1735
+ return;
1736
+ }
1737
+ if (options.parallel) {
1738
+ const agentFiles = squad.agents.map((a) => ({
1739
+ name: a.name,
1740
+ path: join2(squadsDir, squad.dir, `${a.name}.md`)
1741
+ })).filter((a) => existsSync2(a.path));
1742
+ if (agentFiles.length === 0) {
1743
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
1744
+ return;
1745
+ }
1746
+ writeLine(` ${bold}Parallel execution${RESET} ${colors.dim}${agentFiles.length} agents${RESET}`);
1747
+ writeLine();
1748
+ if (!options.execute) {
1749
+ for (const agent of agentFiles) {
1750
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET}`);
1751
+ }
1752
+ writeLine();
1753
+ writeLine(` ${colors.dim}Launch all agents in parallel:${RESET}`);
1754
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --parallel`);
1755
+ writeLine();
1756
+ return;
1757
+ }
1758
+ writeLine(` ${gradient("Launching")} ${agentFiles.length} agents in parallel...`);
1759
+ writeLine();
1760
+ const launches = agentFiles.map(
1761
+ (agent) => runAgent(agent.name, agent.path, squad.dir, options)
1762
+ );
1763
+ await Promise.all(launches);
1764
+ writeLine();
1765
+ writeLine(` ${icons.success} All ${agentFiles.length} agents launched`);
1766
+ writeLine(` ${colors.dim}Monitor: tmux ls | grep squads-${squad.name}${RESET}`);
1767
+ writeLine(` ${colors.dim}Attach: tmux attach -t <session>${RESET}`);
1768
+ writeLine();
1769
+ return;
1770
+ }
1771
+ if (squad.pipelines.length > 0) {
1772
+ const pipeline = squad.pipelines[0];
1773
+ writeLine(` ${bold}Pipeline${RESET} ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
1774
+ writeLine();
1775
+ for (let i = 0; i < pipeline.agents.length; i++) {
1776
+ const agentName = pipeline.agents[i];
1777
+ const agentPath = join2(squadsDir, squad.dir, `${agentName}.md`);
1778
+ if (existsSync2(agentPath)) {
1779
+ writeLine(` ${colors.dim}[${i + 1}/${pipeline.agents.length}]${RESET}`);
1780
+ await runAgent(agentName, agentPath, squad.dir, options);
1781
+ writeLine();
1782
+ } else {
1783
+ writeLine(` ${icons.warning} ${colors.yellow}Agent ${agentName} not found, skipping${RESET}`);
1784
+ }
1785
+ }
1786
+ } else {
1787
+ if (options.agent) {
1788
+ const agentPath = join2(squadsDir, squad.dir, `${options.agent}.md`);
1789
+ if (existsSync2(agentPath)) {
1790
+ await runAgent(options.agent, agentPath, squad.dir, options);
1791
+ } else {
1792
+ writeLine(` ${icons.error} ${colors.red}Agent ${options.agent} not found${RESET}`);
1793
+ return;
1794
+ }
1795
+ } else {
1796
+ if (options.execute) {
1797
+ writeLine(` ${bold}Conversation mode${RESET} ${colors.dim}(lead \u2192 scan \u2192 work \u2192 review \u2192 verify)${RESET}`);
1798
+ writeLine();
1799
+ const convOptions = {
1800
+ task: options.task,
1801
+ maxTurns: options.maxTurns,
1802
+ costCeiling: options.costCeiling,
1803
+ verbose: options.verbose,
1804
+ model: options.model
1805
+ };
1806
+ const apiExecId = await reportExecutionStart(squad.name, "conversation", `conv-${Date.now()}`);
1807
+ const result = await runConversation(squad, convOptions);
1808
+ const transcriptPath = saveTranscript(result.transcript);
1809
+ if (apiExecId) {
1810
+ reportConversationResult(apiExecId, {
1811
+ turnCount: result.turnCount,
1812
+ totalCost: result.totalCost,
1813
+ converged: result.converged,
1814
+ reason: result.reason,
1815
+ agentsInvolved: [...new Set(result.transcript.turns.map((t) => t.agent))]
1816
+ });
1817
+ }
1818
+ pushCognitionSignal({
1819
+ source: "execution",
1820
+ signal_type: result.converged ? "conversation_converged" : "conversation_stopped",
1821
+ value: result.totalCost,
1822
+ unit: "usd",
1823
+ data: {
1824
+ turn_count: result.turnCount,
1825
+ converged: result.converged,
1826
+ reason: result.reason,
1827
+ agents_involved: [...new Set(result.transcript.turns.map((t) => t.agent))]
1828
+ },
1829
+ entity_type: "squad",
1830
+ entity_id: squad.name,
1831
+ confidence: 0.9
1832
+ });
1833
+ writeLine();
1834
+ writeLine(` ${result.converged ? icons.success : icons.warning} ${result.converged ? "Converged" : "Stopped"}: ${result.reason}`);
1835
+ writeLine(` ${colors.dim}Turns: ${result.turnCount} | Cost: ~$${result.totalCost.toFixed(2)}${RESET}`);
1836
+ if (transcriptPath) {
1837
+ writeLine(` ${colors.dim}Transcript: ${transcriptPath}${RESET}`);
1838
+ }
1839
+ writeLine();
1840
+ } else {
1841
+ writeLine(` ${colors.dim}Default mode: conversation (lead \u2192 scan \u2192 work \u2192 review \u2192 verify)${RESET}`);
1842
+ writeLine();
1843
+ for (const agent of squad.agents) {
1844
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1845
+ }
1846
+ writeLine();
1847
+ writeLine(` ${colors.dim}Run conversation:${RESET}`);
1848
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET}`);
1849
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --task "review and merge open PRs"`);
1850
+ writeLine();
1851
+ writeLine(` ${colors.dim}Run single agent:${RESET}`);
1852
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} -a ${colors.cyan}<agent>${RESET}`);
1853
+ }
1854
+ }
1855
+ }
1856
+ writeLine();
1857
+ writeLine(` ${colors.dim}After execution, record outcome:${RESET}`);
1858
+ writeLine(` ${colors.dim}$${RESET} squads feedback add ${colors.cyan}${squad.name}${RESET} ${colors.cyan}<1-5>${RESET} ${colors.cyan}"<feedback>"${RESET}`);
1859
+ writeLine();
1860
+ }
1861
+ async function runLeadMode(squad, squadsDir, options) {
1862
+ if (!squad) return;
1863
+ const agentFiles = squad.agents.map((a) => ({
1864
+ name: a.name,
1865
+ path: join2(squadsDir, squad.dir, `${a.name}.md`),
1866
+ role: a.role || ""
1867
+ })).filter((a) => existsSync2(a.path));
1868
+ if (agentFiles.length === 0) {
1869
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
1870
+ return;
1871
+ }
1872
+ writeLine(` ${bold}Lead mode${RESET} ${colors.dim}orchestrating ${agentFiles.length} agents${RESET}`);
1873
+ writeLine();
1874
+ for (const agent of agentFiles) {
1875
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
1876
+ }
1877
+ writeLine();
1878
+ if (!options.execute) {
1879
+ writeLine(` ${colors.dim}Launch lead session:${RESET}`);
1880
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --lead`);
1881
+ writeLine();
1882
+ return;
1883
+ }
1884
+ const timeoutMins = options.timeout || DEFAULT_TIMEOUT_MINUTES;
1885
+ const agentList = agentFiles.map((a) => `- ${a.name}: ${a.role}`).join("\n");
1886
+ const agentPaths = agentFiles.map((a) => `- ${a.name}: ${a.path}`).join("\n");
1887
+ const prompt = `You are the Lead of the ${squad.name} squad.
1888
+
1889
+ ## Mission
1890
+ ${squad.mission || "Execute squad operations efficiently."}
1891
+
1892
+ ## Available Agents
1893
+ ${agentList}
1894
+
1895
+ ## Agent Definition Files
1896
+ ${agentPaths}
1897
+
1898
+ ## Your Role as Lead
1899
+
1900
+ 1. **Assess the situation**: Check for pending work:
1901
+ - Run \`gh issue list --repo agents-squads/hq --label squad:${squad.name}\` for assigned issues
1902
+ - Check .agents/memory/${squad.dir}/ for squad state and pending tasks
1903
+ - Review recent activity with \`git log --oneline -10\`
1904
+
1905
+ 2. **Delegate work using Task tool**: For each piece of work:
1906
+ - Use the Task tool with subagent_type="general-purpose"
1907
+ - Include the agent definition file path in the prompt
1908
+ - Spawn multiple Task agents IN PARALLEL when work is independent
1909
+ - Example: "Read ${agentFiles[0]?.path || "agent.md"} and execute its instructions for [specific task]"
1910
+
1911
+ 3. **Coordinate parallel execution**:
1912
+ - Independent tasks \u2192 spawn Task agents in parallel (single message, multiple tool calls)
1913
+ - Dependent tasks \u2192 run sequentially
1914
+ - Monitor progress and handle failures
1915
+
1916
+ 4. **Report and update memory**:
1917
+ - Update .agents/memory/${squad.dir}/state.md with completed work
1918
+ - Log learnings to learnings.md
1919
+ - Create issues for follow-up work if needed
1920
+
1921
+ ## Time Budget
1922
+ You have ${timeoutMins} minutes. Prioritize high-impact work.
1923
+
1924
+ ## Critical Instructions
1925
+ - Use Task tool for delegation, NOT direct execution of agent work
1926
+ - Spawn parallel Task agents when work is independent
1927
+ - When done, type /exit to end the session
1928
+ - Do NOT wait for user input - work autonomously
1929
+
1930
+ ## Async Mode (CRITICAL)
1931
+ This is ASYNC execution - Task agents must be fully autonomous:
1932
+ - **Findings** \u2192 Create GitHub issues (gh issue create)
1933
+ - **Code changes** \u2192 Create PRs (gh pr create)
1934
+ - **Analysis results** \u2192 Write to .agents/outputs/ or memory files
1935
+ - **NEVER wait for human review** - complete the work and move on
1936
+ - **NEVER ask clarifying questions** - make reasonable decisions
1937
+
1938
+ Instruct each Task agent: "Work autonomously. Output findings to GitHub issues. Output code changes as PRs. Do not wait for review."
1939
+
1940
+ Begin by assessing pending work, then delegate to agents via Task tool.`;
1941
+ const claudeAvailable = await checkClaudeCliAvailable();
1942
+ if (!claudeAvailable) {
1943
+ writeLine(` ${colors.yellow}Claude CLI not found${RESET}`);
1944
+ writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
1945
+ return;
1946
+ }
1947
+ const isBackground = options.background === true && !options.watch;
1948
+ const isWatch = options.watch === true;
1949
+ const isForeground = !isBackground && !isWatch;
1950
+ const modeText = isBackground ? " (background)" : isWatch ? " (watch)" : "";
1951
+ writeLine(` ${gradient("Launching")} lead session${modeText}...`);
1952
+ writeLine();
1953
+ try {
1954
+ const leadAgentName = agentFiles.find((a) => a.name.includes("lead"))?.name || `${squad.dir}-lead`;
1955
+ const result = await executeWithClaude(prompt, {
1956
+ verbose: options.verbose,
1957
+ timeoutMinutes: timeoutMins,
1958
+ foreground: options.foreground,
1959
+ background: options.background,
1960
+ watch: options.watch,
1961
+ useApi: options.useApi,
1962
+ effort: options.effort,
1963
+ skills: options.skills,
1964
+ trigger: options.trigger || "manual",
1965
+ squadName: squad.dir,
1966
+ agentName: leadAgentName,
1967
+ model: options.model
1968
+ });
1969
+ if (isForeground || isWatch) {
1970
+ writeLine();
1971
+ writeLine(` ${icons.success} Lead session completed`);
1972
+ } else {
1973
+ writeLine(` ${icons.success} Lead session launched in background`);
1974
+ writeLine(` ${colors.dim}${result}${RESET}`);
1975
+ writeLine();
1976
+ writeLine(` ${colors.dim}The lead will:${RESET}`);
1977
+ writeLine(` ${colors.dim} 1. Assess pending work (issues, memory)${RESET}`);
1978
+ writeLine(` ${colors.dim} 2. Spawn Task agents for parallel execution${RESET}`);
1979
+ writeLine(` ${colors.dim} 3. Coordinate and report results${RESET}`);
1980
+ writeLine();
1981
+ writeLine(` ${colors.dim}Monitor: squads workers${RESET}`);
1982
+ }
1983
+ } catch (error) {
1984
+ writeLine(` ${icons.error} ${colors.red}Failed to launch: ${error}${RESET}`);
1985
+ }
1986
+ }
1987
+ async function runAgent(agentName, agentPath, squadName, options) {
1988
+ const spinner = ora(`Running agent: ${agentName}`).start();
1989
+ const startMs = Date.now();
1990
+ const startTime = new Date(startMs).toISOString();
1991
+ const executionId = generateExecutionId();
1992
+ const taskType = detectTaskType(agentName);
1993
+ const definition = loadAgentDefinition(agentPath);
1994
+ const learnings = await fetchLearnings(squadName);
1995
+ const learningContext = learnings.length > 0 ? `
1996
+ ## Learnings from Previous Runs
1997
+ ${learnings.map((l) => `- ${l.content}`).join("\n")}
1998
+ ` : "";
1999
+ if (options.dryRun) {
2000
+ spinner.info(`[DRY RUN] Would run ${agentName}`);
2001
+ const dryRunContext = gatherSquadContext(squadName, agentName, { verbose: options.verbose, agentPath });
2002
+ if (options.verbose) {
2003
+ writeLine(` ${colors.dim}Agent definition:${RESET}`);
2004
+ writeLine(` ${colors.dim}${definition.slice(0, DRYRUN_DEF_MAX_CHARS)}...${RESET}`);
2005
+ if (learnings.length > 0) {
2006
+ writeLine(` ${colors.dim}Learnings: ${learnings.length} from bridge${RESET}`);
2007
+ }
2008
+ if (dryRunContext || learningContext) {
2009
+ const fullContext = `${dryRunContext}${learningContext}`;
2010
+ writeLine();
2011
+ writeLine(` ${colors.cyan}Context to inject (${Math.ceil(fullContext.length / 4)} tokens):${RESET}`);
2012
+ writeLine(` ${colors.dim}${fullContext.slice(0, DRYRUN_CONTEXT_MAX_CHARS)}...${RESET}`);
2013
+ }
2014
+ }
2015
+ return;
2016
+ }
2017
+ const squadsDir = findSquadsDir();
2018
+ if (squadsDir) {
2019
+ const squadFilePath = join2(squadsDir, squadName, "SQUAD.md");
2020
+ if (existsSync2(squadFilePath)) {
2021
+ const squadContent = readFileSync2(squadFilePath, "utf-8");
2022
+ const permContext = buildContextFromSquad(squadName, squadContent, agentName);
2023
+ const mcpServers = extractMcpServersFromDefinition(definition);
2024
+ const execRequest = {
2025
+ mcpServers
2026
+ };
2027
+ const permResult = validateExecution(permContext, execRequest);
2028
+ if (permResult.violations.length > 0) {
2029
+ spinner.stop();
2030
+ const violationLines = formatViolations(permResult);
2031
+ for (const line of violationLines) {
2032
+ writeLine(` ${line}`);
2033
+ }
2034
+ writeLine();
2035
+ if (!permResult.allowed) {
2036
+ writeLine(` ${colors.red}Execution blocked due to permission violations.${RESET}`);
2037
+ writeLine(` ${colors.dim}Configure permissions in ${squadFilePath}${RESET}`);
2038
+ return;
2039
+ }
2040
+ }
2041
+ }
2042
+ }
2043
+ const preflight = await checkPreflightGates(squadName, agentName);
2044
+ if (!preflight.allowed) {
2045
+ spinner.stop();
2046
+ writeLine();
2047
+ writeLine(` ${colors.red}${icons.error} Execution blocked by preflight gates${RESET}`);
2048
+ if (preflight.gates.quota && !preflight.gates.quota.ok) {
2049
+ writeLine(` ${colors.dim}Quota: $${preflight.gates.quota.used.toFixed(2)}/$${preflight.gates.quota.limit}/mo limit exceeded${RESET}`);
2050
+ }
2051
+ if (preflight.gates.cooldown && !preflight.gates.cooldown.ok) {
2052
+ const elapsed = preflight.gates.cooldown.elapsed_sec;
2053
+ const minGap = preflight.gates.cooldown.min_gap_sec;
2054
+ writeLine(` ${colors.dim}Cooldown: ${elapsed}s since last run (min: ${minGap}s)${RESET}`);
2055
+ }
2056
+ writeLine();
2057
+ return;
2058
+ }
2059
+ if (options.verbose && Object.keys(preflight.gates).length > 0) {
2060
+ writeLine(` ${colors.dim}Preflight: quota ${preflight.gates.quota?.ok ? "\u2713" : "\u2717"} cooldown ${preflight.gates.cooldown?.ok ? "\u2713" : "\u2717"}${RESET}`);
2061
+ }
2062
+ const isScheduledRun = options.trigger === "scheduled" || options.trigger === "smart";
2063
+ const bridgeHasNoHistory = preflight.gates.cooldown?.elapsed_sec === null;
2064
+ if (isScheduledRun && (!preflight.gates.cooldown || bridgeHasNoHistory)) {
2065
+ const frontmatterForCooldown = parseAgentFrontmatter(agentPath);
2066
+ const cooldownMs = frontmatterForCooldown.cooldown ? parseCooldown(frontmatterForCooldown.cooldown) || DEFAULT_SCHEDULED_COOLDOWN_MS : DEFAULT_SCHEDULED_COOLDOWN_MS;
2067
+ const localCooldown = checkLocalCooldown(squadName, agentName, cooldownMs);
2068
+ if (!localCooldown.ok) {
2069
+ spinner.stop();
2070
+ writeLine();
2071
+ writeLine(` ${colors.yellow}${icons.warning} Skipping: cooldown not elapsed${RESET}`);
2072
+ writeLine(` ${colors.dim}Last run: ${formatDuration(localCooldown.elapsedMs)} ago (cooldown: ${formatDuration(localCooldown.cooldownMs)})${RESET}`);
2073
+ writeLine();
2074
+ return;
2075
+ }
2076
+ if (options.verbose) {
2077
+ writeLine(` ${colors.dim}Local cooldown: \u2713 (${formatDuration(localCooldown.elapsedMs || 0)} since last run)${RESET}`);
2078
+ }
2079
+ }
2080
+ logExecution({
2081
+ squadName,
2082
+ agentName,
2083
+ executionId,
2084
+ startTime,
2085
+ status: "running",
2086
+ trigger: options.trigger || "manual",
2087
+ taskType
2088
+ });
2089
+ if (options.verbose && learnings.length > 0) {
2090
+ writeLine(` ${colors.dim}Injecting ${learnings.length} learnings${RESET}`);
2091
+ }
2092
+ const approvalInstructions = loadApprovalInstructions();
2093
+ const approvalContext = approvalInstructions ? `
2094
+ ${approvalInstructions}
2095
+ ` : "";
2096
+ const squadContext = gatherSquadContext(squadName, agentName, { verbose: options.verbose, agentPath });
2097
+ let cognitionContext = "";
2098
+ try {
2099
+ const { loadSession: loadSession2 } = await import("./auth-YW3UPFSB.js");
2100
+ const { getApiUrl: getApiUrl2 } = await import("./env-config-KCLDBKYX.js");
2101
+ const session = loadSession2();
2102
+ if (session?.accessToken && session.status === "active") {
2103
+ const safeSquadName = encodeURIComponent(squadName);
2104
+ const res = await fetch(`${getApiUrl2()}/cognition/context/squad:${safeSquadName}`, {
2105
+ headers: { Authorization: `Bearer ${session.accessToken}` },
2106
+ signal: AbortSignal.timeout(3e3)
2107
+ });
2108
+ if (res.ok) {
2109
+ const data = await res.json();
2110
+ if (data.markdown && !data.markdown.includes("No cognition data")) {
2111
+ cognitionContext = `
2112
+ ${data.markdown}
2113
+ `;
2114
+ if (options.verbose) {
2115
+ writeLine(` ${colors.dim}Injecting cognition beliefs${RESET}`);
2116
+ }
2117
+ }
2118
+ }
2119
+ }
2120
+ } catch {
2121
+ }
2122
+ const timeoutMins = options.timeout || DEFAULT_TIMEOUT_MINUTES;
2123
+ const prompt = `Execute the ${agentName} agent from squad ${squadName}.
2124
+
2125
+ Read the agent definition at ${agentPath} and follow its instructions exactly.
2126
+
2127
+ The agent definition contains:
2128
+ - Purpose/role
2129
+ - Tools it can use (MCP servers, skills)
2130
+ - Step-by-step instructions
2131
+ - Expected output format
2132
+
2133
+ TOOL PREFERENCE: Always prefer CLI tools over MCP servers when both can accomplish the task:
2134
+ - Use \`squads\` CLI for squad operations (run, memory, status, feedback)
2135
+ - Use \`gh\` CLI for GitHub (issues, PRs, repos)
2136
+ - Use \`git\` CLI for version control
2137
+ - Use Bash for file operations, builds, tests
2138
+ - Only use MCP tools when CLI cannot do it or MCP is significantly better
2139
+ ${squadContext}${cognitionContext}${learningContext}${approvalContext}
2140
+ TIME LIMIT: You have ${timeoutMins} minutes. Work efficiently:
2141
+ - Focus on the most important tasks first
2142
+ - If a task is taking too long, move on and note it for next run
2143
+ - Aim to complete within ${Math.floor(timeoutMins * SOFT_DEADLINE_RATIO)} minutes
2144
+
2145
+ ${loadPostExecution(squadName, agentName)}`;
2146
+ const agentProvider = parseAgentProvider(agentPath);
2147
+ const squad = loadSquad(squadName);
2148
+ const squadDefaultProvider = squad?.providers?.default;
2149
+ const provider2 = agentProvider || options.provider || squadDefaultProvider || "anthropic";
2150
+ const isAnthropic = provider2 === "anthropic";
2151
+ if (options.verbose && (agentProvider || squadDefaultProvider)) {
2152
+ writeLine(` ${colors.dim}Provider resolution:${RESET}`);
2153
+ if (agentProvider) writeLine(` ${colors.dim}Agent: ${agentProvider}${RESET}`);
2154
+ if (options.provider) writeLine(` ${colors.dim}CLI: ${options.provider}${RESET}`);
2155
+ if (squadDefaultProvider) writeLine(` ${colors.dim}Squad: ${squadDefaultProvider}${RESET}`);
2156
+ writeLine(` ${colors.dim}\u2192 Using: ${provider2}${RESET}`);
2157
+ }
2158
+ const cliAvailable = isAnthropic ? await checkClaudeCliAvailable() : isProviderCLIAvailable(provider2);
2159
+ if (options.execute && cliAvailable) {
2160
+ const cliConfig = getCLIConfig(provider2);
2161
+ const cliName = cliConfig?.displayName || provider2;
2162
+ const isBackground = options.background === true && !options.watch;
2163
+ const isWatch = options.watch === true;
2164
+ const isForeground = !isBackground && !isWatch;
2165
+ spinner.text = isBackground ? `Launching ${agentName} with ${cliName} in background...` : isWatch ? `Starting ${agentName} with ${cliName} (watch mode)...` : `Running ${agentName} with ${cliName}...`;
2166
+ const frontmatter = parseAgentFrontmatter(agentPath);
2167
+ const hasCriteria = !!frontmatter.acceptance_criteria && options.verify !== false;
2168
+ const maxRetries = frontmatter.max_retries ?? 2;
2169
+ let currentPrompt = prompt;
2170
+ for (let attempt = 0; attempt <= (hasCriteria ? maxRetries : 0); attempt++) {
2171
+ try {
2172
+ let result;
2173
+ if (isAnthropic) {
2174
+ result = await executeWithClaude(currentPrompt, {
2175
+ verbose: options.verbose,
2176
+ timeoutMinutes: options.timeout || 30,
2177
+ foreground: options.foreground,
2178
+ background: options.background,
2179
+ watch: options.watch,
2180
+ useApi: options.useApi,
2181
+ effort: options.effort,
2182
+ skills: options.skills,
2183
+ trigger: options.trigger || "manual",
2184
+ squadName,
2185
+ agentName,
2186
+ model: options.model
2187
+ });
2188
+ } else {
2189
+ result = await executeWithProvider(provider2, currentPrompt, {
2190
+ verbose: options.verbose,
2191
+ foreground: !isBackground,
2192
+ squadName,
2193
+ agentName
2194
+ });
2195
+ }
2196
+ if (hasCriteria && (isForeground || isWatch)) {
2197
+ const verification = await verifyExecution(
2198
+ squadName,
2199
+ agentName,
2200
+ frontmatter.acceptance_criteria,
2201
+ { verbose: options.verbose }
2202
+ );
2203
+ if (!verification.passed && attempt < maxRetries) {
2204
+ writeLine(` ${colors.yellow}Verification: FAIL - ${verification.reason}${RESET}`);
2205
+ writeLine(` ${colors.dim}Retrying (${attempt + 1}/${maxRetries})...${RESET}`);
2206
+ currentPrompt = `${prompt}
2207
+
2208
+ ## PREVIOUS ATTEMPT FAILED
2209
+ Verification found: ${verification.reason}
2210
+ Please address this issue and try again.`;
2211
+ continue;
2212
+ }
2213
+ if (verification.passed) {
2214
+ writeLine(` ${colors.green}Verification: PASS - ${verification.reason}${RESET}`);
2215
+ }
2216
+ }
2217
+ emitExecutionEvent("agent.completed", {
2218
+ squad: squadName,
2219
+ agent: agentName,
2220
+ executionId
2221
+ }).catch(() => {
2222
+ });
2223
+ if (isForeground || isWatch) {
2224
+ spinner.succeed(`Agent ${agentName} completed (${cliName})`);
2225
+ } else {
2226
+ spinner.succeed(`Agent ${agentName} launched in background (${cliName})`);
2227
+ writeLine(` ${colors.dim}${result}${RESET}`);
2228
+ writeLine();
2229
+ writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
2230
+ writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
2231
+ }
2232
+ break;
2233
+ } catch (error) {
2234
+ emitExecutionEvent("agent.failed", {
2235
+ squad: squadName,
2236
+ agent: agentName,
2237
+ executionId,
2238
+ error: String(error)
2239
+ }).catch(() => {
2240
+ });
2241
+ spinner.fail(`Agent ${agentName} failed to launch`);
2242
+ updateExecutionStatus(squadName, agentName, executionId, "failed", {
2243
+ error: String(error),
2244
+ durationMs: Date.now() - startMs
2245
+ });
2246
+ writeLine(` ${colors.red}${String(error)}${RESET}`);
2247
+ break;
2248
+ }
2249
+ }
2250
+ } else {
2251
+ spinner.succeed(`Agent ${agentName} ready`);
2252
+ writeLine(` ${colors.dim}Execution logged: ${startTime}${RESET}`);
2253
+ if (!cliAvailable) {
2254
+ const cliConfig = getCLIConfig(provider2);
2255
+ writeLine();
2256
+ writeLine(` ${colors.yellow}${cliConfig?.command || provider2} CLI not found${RESET}`);
2257
+ writeLine(` ${colors.dim}Install: ${cliConfig?.install || "squads providers"}${RESET}`);
2258
+ }
2259
+ writeLine();
2260
+ writeLine(` ${colors.dim}To launch as background task:${RESET}`);
2261
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} -a ${colors.cyan}${agentName}${RESET}`);
2262
+ if (provider2 !== "anthropic") {
2263
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} -a ${colors.cyan}${agentName}${RESET} --provider=${provider2}`);
2264
+ }
2265
+ writeLine();
2266
+ writeLine(` ${colors.dim}Or run interactively:${RESET}`);
2267
+ writeLine(` ${colors.dim}$${RESET} Run the ${colors.cyan}${agentName}${RESET} agent from ${agentPath}`);
2268
+ }
2269
+ }
2270
+ async function checkClaudeCliAvailable() {
2271
+ return new Promise((resolve) => {
2272
+ const check = spawn("which", ["claude"], { stdio: "pipe" });
2273
+ check.on("close", (code) => resolve(code === 0));
2274
+ check.on("error", () => resolve(false));
2275
+ });
2276
+ }
2277
+ async function preflightExecutorCheck(provider2) {
2278
+ if (process.env.SQUADS_SKIP_CHECKS === "1") {
2279
+ return true;
2280
+ }
2281
+ const isAnthropic = provider2 === "anthropic";
2282
+ let cliFound;
2283
+ if (isAnthropic) {
2284
+ cliFound = await checkClaudeCliAvailable();
2285
+ } else {
2286
+ cliFound = isProviderCLIAvailable(provider2);
2287
+ }
2288
+ if (!cliFound) {
2289
+ const cliConfig = getCLIConfig(provider2);
2290
+ const cliName = cliConfig?.command || provider2;
2291
+ const installCmd = cliConfig?.install || `See ${provider2} documentation`;
2292
+ writeLine();
2293
+ writeLine(` ${icons.error} ${colors.red}${cliName} CLI not found${RESET}`);
2294
+ writeLine();
2295
+ writeLine(` ${colors.dim}The ${cliName} command is required to run agents but was not found on your PATH.${RESET}`);
2296
+ writeLine();
2297
+ writeLine(` ${colors.cyan}Install:${RESET} ${installCmd}`);
2298
+ writeLine();
2299
+ writeLine(` ${colors.dim}Skip this check: SQUADS_SKIP_CHECKS=1 squads run ...${RESET}`);
2300
+ writeLine();
2301
+ return false;
2302
+ }
2303
+ if (isAnthropic) {
2304
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
2305
+ const home = homedir();
2306
+ const credentialsPath = join2(home, ".claude", ".credentials.json");
2307
+ const hasOAuthCreds = existsSync2(credentialsPath);
2308
+ if (!hasApiKey && !hasOAuthCreds) {
2309
+ writeLine(` ${colors.dim}${icons.progress} No API key or credentials file found \u2014 assuming OAuth${RESET}`);
2310
+ }
2311
+ }
2312
+ return true;
2313
+ }
2314
+ function buildAgentEnv(baseEnv, execContext, options) {
2315
+ const env = {
2316
+ ...baseEnv,
2317
+ SQUADS_SQUAD: execContext.squad,
2318
+ SQUADS_AGENT: execContext.agent,
2319
+ SQUADS_TASK_TYPE: execContext.taskType,
2320
+ SQUADS_TRIGGER: execContext.trigger,
2321
+ SQUADS_EXECUTION_ID: execContext.executionId,
2322
+ BRIDGE_API: getBridgeUrl()
2323
+ };
2324
+ if (options?.includeOtel) {
2325
+ env.OTEL_RESOURCE_ATTRIBUTES = `squads.squad=${execContext.squad},squads.agent=${execContext.agent},squads.task_type=${execContext.taskType},squads.trigger=${execContext.trigger},squads.execution_id=${execContext.executionId}`;
2326
+ }
2327
+ if (options?.effort) env.CLAUDE_EFFORT = options.effort;
2328
+ if (options?.skills && options.skills.length > 0) env.CLAUDE_SKILLS = options.skills.join(",");
2329
+ return env;
2330
+ }
2331
+ function logVerboseExecution(config) {
2332
+ writeLine(` ${colors.dim}Project: ${config.projectRoot}${RESET}`);
2333
+ writeLine(` ${colors.dim}Mode: ${config.mode}${RESET}`);
2334
+ if (config.logFile) writeLine(` ${colors.dim}Log: ${config.logFile}${RESET}`);
2335
+ if (config.mcpConfigPath) writeLine(` ${colors.dim}MCP config: ${config.mcpConfigPath}${RESET}`);
2336
+ if (config.useApi !== void 0) writeLine(` ${colors.dim}Auth: ${config.useApi ? "API credits" : "subscription"}${RESET}`);
2337
+ writeLine(` ${colors.dim}Execution: ${config.execContext.executionId}${RESET}`);
2338
+ writeLine(` ${colors.dim}Task type: ${config.execContext.taskType}${RESET}`);
2339
+ writeLine(` ${colors.dim}Trigger: ${config.execContext.trigger}${RESET}`);
2340
+ if (config.effort) writeLine(` ${colors.dim}Effort: ${config.effort}${RESET}`);
2341
+ if (config.skills && config.skills.length > 0) writeLine(` ${colors.dim}Skills: ${config.skills.join(", ")}${RESET}`);
2342
+ if (config.resolvedModel || config.claudeModelAlias) {
2343
+ const source = config.explicitModel ? "explicit" : "auto-routed";
2344
+ const displayModel = config.resolvedModel || config.claudeModelAlias;
2345
+ writeLine(` ${colors.dim}Model: ${displayModel} (${source})${RESET}`);
2346
+ }
2347
+ }
2348
+ function createAgentWorktree(projectRoot, squadName, agentName) {
2349
+ const timestamp = Date.now();
2350
+ const branchName = `agent/${squadName}/${agentName}-${timestamp}`;
2351
+ const worktreePath = join2(projectRoot, "..", ".worktrees", `${squadName}-${agentName}-${timestamp}`);
2352
+ try {
2353
+ mkdirSync2(join2(projectRoot, "..", ".worktrees"), { recursive: true });
2354
+ execSync2(`git worktree add '${worktreePath}' -b '${branchName}' HEAD`, { cwd: projectRoot, stdio: "pipe" });
2355
+ return worktreePath;
2356
+ } catch {
2357
+ return projectRoot;
2358
+ }
2359
+ }
2360
+ function buildDetachedShellScript(config) {
2361
+ const modelFlag = config.claudeModelAlias ? `--model ${config.claudeModelAlias}` : "";
2362
+ const branchName = `agent/${config.squadName}/${config.agentName}-${config.timestamp}`;
2363
+ const worktreeDir = `${config.projectRoot}/../.worktrees/${config.squadName}-${config.agentName}-${config.timestamp}`;
2364
+ const script = `mkdir -p '${config.projectRoot}/../.worktrees'; WORK_DIR='${config.projectRoot}'; if git -C '${config.projectRoot}' worktree add '${worktreeDir}' -b '${branchName}' HEAD 2>/dev/null; then WORK_DIR='${worktreeDir}'; fi; cd "\${WORK_DIR}"; claude --print --dangerously-skip-permissions ${modelFlag} -- '${config.escapedPrompt}' > '${config.logFile}' 2>&1`;
2365
+ return `echo $$ > '${config.pidFile}'; ${script}`;
2366
+ }
2367
+ function prepareLogFiles(projectRoot, squadName, agentName, timestamp) {
2368
+ const logDir = join2(projectRoot, ".agents", "logs", squadName);
2369
+ const logFile = join2(logDir, `${agentName}-${timestamp}.log`);
2370
+ const pidFile = join2(logDir, `${agentName}-${timestamp}.pid`);
2371
+ if (!existsSync2(logDir)) {
2372
+ mkdirSync2(logDir, { recursive: true });
2373
+ }
2374
+ return { logDir, logFile, pidFile };
2375
+ }
2376
+ function executeForeground(config) {
2377
+ const workDir = createAgentWorktree(config.projectRoot, config.squadName, config.agentName);
2378
+ return new Promise((resolve, reject) => {
2379
+ const claude = spawn("claude", config.claudeArgs, {
2380
+ stdio: "inherit",
2381
+ cwd: workDir,
2382
+ env: config.agentEnv
2383
+ });
2384
+ claude.on("close", async (code) => {
2385
+ const durationMs = Date.now() - config.startMs;
2386
+ if (code === 0) {
2387
+ updateExecutionStatus(config.squadName, config.agentName, config.execContext.executionId, "completed", {
2388
+ outcome: "Session completed successfully",
2389
+ durationMs
2390
+ });
2391
+ const commitResult = await autoCommitAgentWork(config.squadName, config.agentName, config.execContext.executionId, config.provider);
2392
+ if (commitResult.committed) {
2393
+ writeLine();
2394
+ writeLine(` ${colors.green}Auto-committed agent work${RESET}`);
2395
+ }
2396
+ resolve("Session completed");
2397
+ } else {
2398
+ updateExecutionStatus(config.squadName, config.agentName, config.execContext.executionId, "failed", {
2399
+ error: `Claude exited with code ${code}`,
2400
+ durationMs
2401
+ });
2402
+ reject(new Error(`Claude exited with code ${code}`));
2403
+ }
2404
+ });
2405
+ claude.on("error", (err) => {
2406
+ const durationMs = Date.now() - config.startMs;
2407
+ updateExecutionStatus(config.squadName, config.agentName, config.execContext.executionId, "failed", {
2408
+ error: String(err),
2409
+ durationMs
2410
+ });
2411
+ reject(err);
2412
+ });
2413
+ });
2414
+ }
2415
+ async function executeWatch(config) {
2416
+ const child = spawn("sh", ["-c", config.wrapperScript], {
2417
+ cwd: config.projectRoot,
2418
+ detached: true,
2419
+ stdio: "ignore",
2420
+ env: config.agentEnv
2421
+ });
2422
+ child.unref();
2423
+ await new Promise((resolve) => setTimeout(resolve, LOG_FILE_INIT_DELAY_MS));
2424
+ writeLine(` ${colors.dim}Tailing log (Ctrl+C to stop watching, agent continues)...${RESET}`);
2425
+ writeLine();
2426
+ const tail = spawn("tail", ["-f", config.logFile], { stdio: "inherit" });
2427
+ process.on("SIGINT", () => {
2428
+ tail.kill();
2429
+ writeLine();
2430
+ writeLine(` ${colors.dim}Stopped watching. Agent continues in background.${RESET}`);
2431
+ writeLine(` ${colors.dim}Resume: tail -f ${config.logFile}${RESET}`);
2432
+ process.exit(0);
2433
+ });
2434
+ return new Promise((resolve) => {
2435
+ tail.on("close", () => {
2436
+ resolve(`Agent running in background. Log: ${config.logFile}`);
2437
+ });
2438
+ });
2439
+ }
2440
+ async function executeWithClaude(prompt, options) {
2441
+ const {
2442
+ verbose,
2443
+ timeoutMinutes: _timeoutMinutes = 30,
2444
+ foreground,
2445
+ background,
2446
+ watch,
2447
+ useApi,
2448
+ effort,
2449
+ skills,
2450
+ trigger = "manual",
2451
+ squadName,
2452
+ agentName,
2453
+ model
2454
+ } = options;
2455
+ const runInBackground = background === true && !watch;
2456
+ const runInWatch = watch === true;
2457
+ const runInForeground = !runInBackground && !runInWatch;
2458
+ const startMs = Date.now();
2459
+ const projectRoot = getProjectRoot();
2460
+ ensureProjectTrusted(projectRoot);
2461
+ const squad = squadName !== "unknown" ? loadSquad(squadName) : null;
2462
+ const mcpConfigPath = selectMcpConfig(squadName, squad);
2463
+ const taskType = detectTaskType(agentName);
2464
+ const resolvedModel = resolveModel(model, squad, taskType);
2465
+ const detectedProvider = resolvedModel ? detectProviderFromModel(resolvedModel) : "anthropic";
2466
+ if (detectedProvider !== "anthropic" && detectedProvider !== "unknown") {
2467
+ if (verbose) {
2468
+ const source = model ? "explicit" : "auto-routed";
2469
+ writeLine(` ${colors.dim}Model: ${resolvedModel} (${source})${RESET}`);
2470
+ writeLine(` ${colors.dim}Provider: ${detectedProvider}${RESET}`);
2471
+ }
2472
+ return executeWithProvider(detectedProvider, prompt, {
2473
+ verbose,
2474
+ foreground,
2475
+ cwd: projectRoot,
2476
+ squadName,
2477
+ agentName
2478
+ });
2479
+ }
2480
+ const claudeModelAlias = resolvedModel ? getClaudeModelAlias(resolvedModel) : void 0;
2481
+ const execContext = {
2482
+ squad: squadName,
2483
+ agent: agentName,
2484
+ taskType,
2485
+ trigger,
2486
+ executionId: generateExecutionId()
2487
+ };
2488
+ const { ANTHROPIC_API_KEY: _apiKey, CLAUDECODE: _claudeCode, ...envWithoutApiKey } = process.env;
2489
+ const spawnEnv = useApi ? (() => {
2490
+ const { CLAUDECODE: _, ...rest } = process.env;
2491
+ return rest;
2492
+ })() : envWithoutApiKey;
2493
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
2494
+ await registerContextWithBridge(execContext);
2495
+ if (runInForeground) {
2496
+ if (verbose) {
2497
+ logVerboseExecution({
2498
+ projectRoot,
2499
+ mode: "foreground",
2500
+ useApi,
2501
+ execContext,
2502
+ effort,
2503
+ skills,
2504
+ resolvedModel,
2505
+ claudeModelAlias,
2506
+ explicitModel: model
2507
+ });
2508
+ }
2509
+ const claudeArgs = [];
2510
+ if (!process.stdin.isTTY) claudeArgs.push("--print");
2511
+ claudeArgs.push("--dangerously-skip-permissions");
2512
+ if (mcpConfigPath) claudeArgs.push("--mcp-config", mcpConfigPath);
2513
+ if (claudeModelAlias) claudeArgs.push("--model", claudeModelAlias);
2514
+ claudeArgs.push("--", prompt);
2515
+ const agentEnv2 = buildAgentEnv(spawnEnv, execContext, {
2516
+ effort,
2517
+ skills,
2518
+ includeOtel: true
2519
+ });
2520
+ return executeForeground({
2521
+ prompt,
2522
+ claudeArgs,
2523
+ agentEnv: agentEnv2,
2524
+ projectRoot,
2525
+ squadName,
2526
+ agentName,
2527
+ execContext,
2528
+ startMs,
2529
+ provider
2530
+ });
2531
+ }
2532
+ const timestamp = Date.now();
2533
+ const { logFile, pidFile } = prepareLogFiles(projectRoot, squadName, agentName, timestamp);
2534
+ const agentEnv = buildAgentEnv(spawnEnv, execContext, {
2535
+ effort,
2536
+ skills,
2537
+ includeOtel: !runInWatch
2538
+ });
2539
+ const wrapperScript = buildDetachedShellScript({
2540
+ projectRoot,
2541
+ squadName,
2542
+ agentName,
2543
+ timestamp,
2544
+ claudeModelAlias,
2545
+ escapedPrompt,
2546
+ logFile,
2547
+ pidFile
2548
+ });
2549
+ if (runInWatch) {
2550
+ if (verbose) {
2551
+ logVerboseExecution({
2552
+ projectRoot,
2553
+ mode: "watch (background + tail)",
2554
+ execContext,
2555
+ logFile
2556
+ });
2557
+ }
2558
+ return executeWatch({ projectRoot, agentEnv, logFile, wrapperScript });
2559
+ }
2560
+ if (verbose) {
2561
+ logVerboseExecution({
2562
+ projectRoot,
2563
+ mode: "background",
2564
+ useApi,
2565
+ execContext,
2566
+ effort,
2567
+ skills,
2568
+ resolvedModel,
2569
+ claudeModelAlias,
2570
+ explicitModel: model,
2571
+ logFile,
2572
+ mcpConfigPath
2573
+ });
2574
+ }
2575
+ const child = spawn("sh", ["-c", wrapperScript], {
2576
+ cwd: projectRoot,
2577
+ detached: true,
2578
+ stdio: "ignore",
2579
+ env: agentEnv
2580
+ });
2581
+ child.unref();
2582
+ if (verbose) {
2583
+ writeLine(` ${colors.dim}Monitor: tail -f ${logFile}${RESET}`);
2584
+ }
2585
+ return `Log: ${logFile}. Monitor: tail -f ${logFile}`;
2586
+ }
2587
+ async function executeWithProvider(provider2, prompt, options) {
2588
+ const cliConfig = getCLIConfig(provider2);
2589
+ if (!cliConfig) {
2590
+ throw new Error(`Unknown provider: ${provider2}. Run 'squads providers' to see available providers.`);
2591
+ }
2592
+ if (!isProviderCLIAvailable(provider2)) {
2593
+ throw new Error(`CLI '${cliConfig.command}' not found. Install: ${cliConfig.install}`);
2594
+ }
2595
+ const projectRoot = options.cwd || getProjectRoot();
2596
+ const squadName = options.squadName || "unknown";
2597
+ const agentName = options.agentName || "unknown";
2598
+ const timestamp = Date.now();
2599
+ const { CLAUDECODE: _claudeCode, ...cleanEnv } = process.env;
2600
+ const providerEnv = {
2601
+ ...cleanEnv,
2602
+ SQUADS_SQUAD: squadName,
2603
+ SQUADS_AGENT: agentName,
2604
+ SQUADS_PROVIDER: provider2
2605
+ };
2606
+ const branchName = `agent/${squadName}/${agentName}-${timestamp}`;
2607
+ const worktreePath = join2(projectRoot, "..", ".worktrees", `${squadName}-${agentName}-${timestamp}`);
2608
+ let workDir = projectRoot;
2609
+ try {
2610
+ mkdirSync2(join2(projectRoot, "..", ".worktrees"), { recursive: true });
2611
+ execSync2(`git worktree add '${worktreePath}' -b '${branchName}' HEAD`, { cwd: projectRoot, stdio: "pipe" });
2612
+ workDir = worktreePath;
2613
+ } catch {
2614
+ }
2615
+ let effectivePrompt = prompt;
2616
+ if (workDir !== projectRoot) {
2617
+ const agentsDir = join2(projectRoot, ".agents");
2618
+ const targetAgentsDir = join2(workDir, ".agents");
2619
+ if (existsSync2(agentsDir) && !existsSync2(targetAgentsDir)) {
2620
+ try {
2621
+ cpSync(agentsDir, targetAgentsDir, { recursive: true });
2622
+ } catch {
2623
+ }
2624
+ }
2625
+ effectivePrompt = prompt.replaceAll(projectRoot, workDir);
2626
+ }
2627
+ const args = cliConfig.buildArgs(effectivePrompt);
2628
+ if (options.verbose) {
2629
+ writeLine(` ${colors.dim}Provider: ${cliConfig.displayName}${RESET}`);
2630
+ writeLine(` ${colors.dim}Command: ${cliConfig.command} ${args.join(" ").slice(0, VERBOSE_COMMAND_MAX_CHARS)}...${RESET}`);
2631
+ writeLine(` ${colors.dim}CWD: ${workDir}${RESET}`);
2632
+ if (workDir !== projectRoot) {
2633
+ writeLine(` ${colors.dim}Worktree: ${branchName}${RESET}`);
2634
+ }
2635
+ }
2636
+ if (options.foreground) {
2637
+ return new Promise((resolve, reject) => {
2638
+ const proc = spawn(cliConfig.command, args, {
2639
+ stdio: "inherit",
2640
+ cwd: workDir,
2641
+ env: providerEnv
2642
+ });
2643
+ proc.on("close", (code) => {
2644
+ if (code === 0) {
2645
+ resolve("Session completed");
2646
+ } else {
2647
+ reject(new Error(`${cliConfig.command} exited with code ${code}`));
2648
+ }
2649
+ });
2650
+ proc.on("error", (err) => {
2651
+ reject(err);
2652
+ });
2653
+ });
2654
+ }
2655
+ const logDir = join2(projectRoot, ".agents", "logs", squadName);
2656
+ const logFile = join2(logDir, `${agentName}-${timestamp}.log`);
2657
+ const pidFile = join2(logDir, `${agentName}-${timestamp}.pid`);
2658
+ if (!existsSync2(logDir)) {
2659
+ mkdirSync2(logDir, { recursive: true });
2660
+ }
2661
+ const escapedPrompt = effectivePrompt.replace(/'/g, "'\\''");
2662
+ const providerArgs = cliConfig.buildArgs(escapedPrompt).map((a) => `'${a}'`).join(" ");
2663
+ const shellScript = `cd '${workDir}' && ${cliConfig.command} ${providerArgs} > '${logFile}' 2>&1`;
2664
+ const wrapperScript = `echo $$ > '${pidFile}'; ${shellScript}`;
2665
+ const child = spawn("sh", ["-c", wrapperScript], {
2666
+ cwd: workDir,
2667
+ detached: true,
2668
+ stdio: "ignore",
2669
+ env: providerEnv
2670
+ });
2671
+ child.unref();
2672
+ if (options.verbose) {
2673
+ writeLine(` ${colors.dim}Log: ${logFile}${RESET}`);
2674
+ writeLine(` ${colors.dim}PID file: ${pidFile}${RESET}`);
2675
+ }
2676
+ return `Log: ${logFile}. Monitor: tail -f ${logFile}`;
2677
+ }
2678
+ async function runSquadCommand(squadName, options) {
2679
+ return runCommand(squadName, options);
2680
+ }
2681
+ export {
2682
+ runCommand,
2683
+ runSquadCommand
2684
+ };
2685
+ //# sourceMappingURL=run-72OQLH5A.js.map