squads-cli 0.2.0 → 0.2.1

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 (223) hide show
  1. package/README.md +521 -288
  2. package/dist/auth-YW3UPFSB.js +23 -0
  3. package/dist/auth-YW3UPFSB.js.map +1 -0
  4. package/dist/autonomy-PSVZVX7A.js +105 -0
  5. package/dist/autonomy-PSVZVX7A.js.map +1 -0
  6. package/dist/chunk-67RO2HKR.js +174 -0
  7. package/dist/chunk-67RO2HKR.js.map +1 -0
  8. package/dist/chunk-7OCVIDC7.js +12 -0
  9. package/dist/chunk-7OCVIDC7.js.map +1 -0
  10. package/dist/chunk-BODLDQY7.js +452 -0
  11. package/dist/chunk-BODLDQY7.js.map +1 -0
  12. package/dist/chunk-EHQJHRIW.js +103 -0
  13. package/dist/chunk-EHQJHRIW.js.map +1 -0
  14. package/dist/chunk-FFFCFZ6A.js +121 -0
  15. package/dist/chunk-FFFCFZ6A.js.map +1 -0
  16. package/dist/chunk-FIWT2NMM.js +165 -0
  17. package/dist/chunk-FIWT2NMM.js.map +1 -0
  18. package/dist/chunk-HF4WR7RA.js +154 -0
  19. package/dist/chunk-HF4WR7RA.js.map +1 -0
  20. package/dist/chunk-J6QF4ZQX.js +230 -0
  21. package/dist/chunk-J6QF4ZQX.js.map +1 -0
  22. package/dist/chunk-LOA3KWYJ.js +294 -0
  23. package/dist/chunk-LOA3KWYJ.js.map +1 -0
  24. package/dist/chunk-M5FXNY6Y.js +384 -0
  25. package/dist/chunk-M5FXNY6Y.js.map +1 -0
  26. package/dist/chunk-QHNUMM4V.js +87 -0
  27. package/dist/chunk-QHNUMM4V.js.map +1 -0
  28. package/dist/chunk-QJ7C7CMB.js +223 -0
  29. package/dist/chunk-QJ7C7CMB.js.map +1 -0
  30. package/dist/chunk-RM6BWILN.js +74 -0
  31. package/dist/chunk-RM6BWILN.js.map +1 -0
  32. package/dist/chunk-TYFTF53O.js +613 -0
  33. package/dist/chunk-TYFTF53O.js.map +1 -0
  34. package/dist/chunk-TZXD6WFN.js +420 -0
  35. package/dist/chunk-TZXD6WFN.js.map +1 -0
  36. package/dist/chunk-WVOIY5GW.js +621 -0
  37. package/dist/chunk-WVOIY5GW.js.map +1 -0
  38. package/dist/chunk-Z2UKDBNL.js +162 -0
  39. package/dist/chunk-Z2UKDBNL.js.map +1 -0
  40. package/dist/chunk-ZTQ7ISUR.js +338 -0
  41. package/dist/chunk-ZTQ7ISUR.js.map +1 -0
  42. package/dist/cli.js +2483 -5902
  43. package/dist/cli.js.map +1 -1
  44. package/dist/context-GWPF4SEY.js +291 -0
  45. package/dist/context-GWPF4SEY.js.map +1 -0
  46. package/dist/context-feed-AJGVAR6H.js +394 -0
  47. package/dist/context-feed-AJGVAR6H.js.map +1 -0
  48. package/dist/cost-XBCDJ7XC.js +275 -0
  49. package/dist/cost-XBCDJ7XC.js.map +1 -0
  50. package/dist/create-BLFGG6PF.js +286 -0
  51. package/dist/create-BLFGG6PF.js.map +1 -0
  52. package/dist/dashboard-LGT2B2BL.js +951 -0
  53. package/dist/dashboard-LGT2B2BL.js.map +1 -0
  54. package/dist/dashboard-RMK2BOD2.js +794 -0
  55. package/dist/dashboard-RMK2BOD2.js.map +1 -0
  56. package/dist/doctor-XPUIIBHJ.js +374 -0
  57. package/dist/doctor-XPUIIBHJ.js.map +1 -0
  58. package/dist/env-config-SQEI3Y7Y.js +21 -0
  59. package/dist/env-config-SQEI3Y7Y.js.map +1 -0
  60. package/dist/exec-OUXM7JBF.js +223 -0
  61. package/dist/exec-OUXM7JBF.js.map +1 -0
  62. package/dist/feedback-KNAOG5QK.js +229 -0
  63. package/dist/feedback-KNAOG5QK.js.map +1 -0
  64. package/dist/github-UQTM5KMS.js +23 -0
  65. package/dist/github-UQTM5KMS.js.map +1 -0
  66. package/dist/goal-BVHV5573.js +168 -0
  67. package/dist/goal-BVHV5573.js.map +1 -0
  68. package/dist/health-4UXN44PF.js +218 -0
  69. package/dist/health-4UXN44PF.js.map +1 -0
  70. package/dist/history-ILH3SWHB.js +232 -0
  71. package/dist/history-ILH3SWHB.js.map +1 -0
  72. package/dist/index.d.ts +736 -8
  73. package/dist/index.js +1312 -6
  74. package/dist/index.js.map +1 -1
  75. package/dist/init-XQZ7BOGT.js +812 -0
  76. package/dist/init-XQZ7BOGT.js.map +1 -0
  77. package/dist/kpi-RQIU7WGK.js +413 -0
  78. package/dist/kpi-RQIU7WGK.js.map +1 -0
  79. package/dist/learn-OIFUVZAS.js +269 -0
  80. package/dist/learn-OIFUVZAS.js.map +1 -0
  81. package/dist/login-DXZANWZY.js +155 -0
  82. package/dist/login-DXZANWZY.js.map +1 -0
  83. package/dist/memory-T3ACCS7E.js +560 -0
  84. package/dist/memory-T3ACCS7E.js.map +1 -0
  85. package/dist/memory-VNF2VFRB.js +23 -0
  86. package/dist/memory-VNF2VFRB.js.map +1 -0
  87. package/dist/progress-DAUZMT3N.js +202 -0
  88. package/dist/progress-DAUZMT3N.js.map +1 -0
  89. package/dist/providers-3P5D2XL5.js +65 -0
  90. package/dist/providers-3P5D2XL5.js.map +1 -0
  91. package/dist/results-UECWGLTB.js +224 -0
  92. package/dist/results-UECWGLTB.js.map +1 -0
  93. package/dist/run-I6KAXU6U.js +4049 -0
  94. package/dist/run-I6KAXU6U.js.map +1 -0
  95. package/dist/session-HBU6KZOD.js +64 -0
  96. package/dist/session-HBU6KZOD.js.map +1 -0
  97. package/dist/sessions-CK25VGPL.js +333 -0
  98. package/dist/sessions-CK25VGPL.js.map +1 -0
  99. package/dist/squad-parser-DCG65BJS.js +35 -0
  100. package/dist/squad-parser-DCG65BJS.js.map +1 -0
  101. package/dist/stats-G6NAU5BD.js +334 -0
  102. package/dist/stats-G6NAU5BD.js.map +1 -0
  103. package/dist/status-AQNLDZVN.js +352 -0
  104. package/dist/status-AQNLDZVN.js.map +1 -0
  105. package/dist/sync-ZI3MHA4G.js +836 -0
  106. package/dist/sync-ZI3MHA4G.js.map +1 -0
  107. package/dist/templates/core/AGENTS.md.template +51 -0
  108. package/dist/templates/core/BUSINESS_BRIEF.md.template +29 -0
  109. package/dist/templates/core/CLAUDE.md.template +48 -0
  110. package/dist/templates/core/provider.yaml.template +5 -0
  111. package/dist/templates/first-squad/SQUAD.md.template +23 -0
  112. package/dist/templates/first-squad/lead.md.template +44 -0
  113. package/dist/templates/memory/getting-started/state.md.template +19 -0
  114. package/dist/templates/seed/BUSINESS_BRIEF.md.template +27 -0
  115. package/dist/templates/seed/CLAUDE.md.template +119 -0
  116. package/dist/templates/seed/README.md.template +42 -0
  117. package/dist/templates/seed/config/SYSTEM.md +52 -0
  118. package/dist/templates/seed/config/provider.yaml +4 -0
  119. package/dist/templates/seed/hooks/settings.json.template +31 -0
  120. package/dist/templates/seed/memory/company/directives.md +37 -0
  121. package/dist/templates/seed/memory/company/manager/state.md +16 -0
  122. package/dist/templates/seed/memory/engineering/issue-solver/state.md +12 -0
  123. package/dist/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
  124. package/dist/templates/seed/memory/marketing/content-drafter/state.md +12 -0
  125. package/dist/templates/seed/memory/operations/ops-lead/state.md +12 -0
  126. package/dist/templates/seed/memory/product/lead/state.md +14 -0
  127. package/dist/templates/seed/memory/research/lead/state.md +14 -0
  128. package/dist/templates/seed/skills/gh/SKILL.md +57 -0
  129. package/dist/templates/seed/skills/squads-cli/SKILL.md +84 -0
  130. package/dist/templates/seed/squads/company/SQUAD.md +51 -0
  131. package/dist/templates/seed/squads/company/company-critic.md +49 -0
  132. package/dist/templates/seed/squads/company/company-eval.md +49 -0
  133. package/dist/templates/seed/squads/company/event-dispatcher.md +43 -0
  134. package/dist/templates/seed/squads/company/goal-tracker.md +43 -0
  135. package/dist/templates/seed/squads/company/manager.md +54 -0
  136. package/dist/templates/seed/squads/engineering/SQUAD.md +48 -0
  137. package/dist/templates/seed/squads/engineering/code-reviewer.md +57 -0
  138. package/dist/templates/seed/squads/engineering/issue-solver.md +58 -0
  139. package/dist/templates/seed/squads/engineering/test-writer.md +50 -0
  140. package/dist/templates/seed/squads/intelligence/SQUAD.md +38 -0
  141. package/dist/templates/seed/squads/intelligence/intel-critic.md +36 -0
  142. package/dist/templates/seed/squads/intelligence/intel-eval.md +31 -0
  143. package/dist/templates/seed/squads/intelligence/intel-lead.md +71 -0
  144. package/dist/templates/seed/squads/marketing/SQUAD.md +47 -0
  145. package/dist/templates/seed/squads/marketing/content-drafter.md +71 -0
  146. package/dist/templates/seed/squads/marketing/growth-analyst.md +49 -0
  147. package/dist/templates/seed/squads/marketing/social-poster.md +44 -0
  148. package/dist/templates/seed/squads/operations/SQUAD.md +45 -0
  149. package/dist/templates/seed/squads/operations/finance-tracker.md +47 -0
  150. package/dist/templates/seed/squads/operations/goal-tracker.md +48 -0
  151. package/dist/templates/seed/squads/operations/ops-lead.md +58 -0
  152. package/dist/templates/seed/squads/product/SQUAD.md +41 -0
  153. package/dist/templates/seed/squads/product/lead.md +56 -0
  154. package/dist/templates/seed/squads/product/scanner.md +50 -0
  155. package/dist/templates/seed/squads/product/worker.md +55 -0
  156. package/dist/templates/seed/squads/research/SQUAD.md +38 -0
  157. package/dist/templates/seed/squads/research/analyst.md +50 -0
  158. package/dist/templates/seed/squads/research/lead.md +52 -0
  159. package/dist/templates/seed/squads/research/synthesizer.md +59 -0
  160. package/dist/templates/skills/squads-learn/SKILL.md +86 -0
  161. package/dist/templates/skills/squads-workflow/instruction.md +70 -0
  162. package/dist/terminal-FBQFQTKZ.js +55 -0
  163. package/dist/terminal-FBQFQTKZ.js.map +1 -0
  164. package/dist/update-D7CGIZ3M.js +18 -0
  165. package/dist/update-D7CGIZ3M.js.map +1 -0
  166. package/dist/update-STU276HR.js +83 -0
  167. package/dist/update-STU276HR.js.map +1 -0
  168. package/package.json +31 -13
  169. package/templates/core/AGENTS.md.template +51 -0
  170. package/templates/core/BUSINESS_BRIEF.md.template +29 -0
  171. package/templates/core/CLAUDE.md.template +48 -0
  172. package/templates/core/provider.yaml.template +5 -0
  173. package/templates/first-squad/SQUAD.md.template +23 -0
  174. package/templates/first-squad/lead.md.template +44 -0
  175. package/templates/memory/getting-started/state.md.template +19 -0
  176. package/templates/seed/BUSINESS_BRIEF.md.template +27 -0
  177. package/templates/seed/CLAUDE.md.template +119 -0
  178. package/templates/seed/README.md.template +42 -0
  179. package/templates/seed/config/SYSTEM.md +52 -0
  180. package/templates/seed/config/provider.yaml +4 -0
  181. package/templates/seed/hooks/settings.json.template +31 -0
  182. package/templates/seed/memory/company/directives.md +37 -0
  183. package/templates/seed/memory/company/manager/state.md +16 -0
  184. package/templates/seed/memory/engineering/issue-solver/state.md +12 -0
  185. package/templates/seed/memory/intelligence/intel-lead/state.md +9 -0
  186. package/templates/seed/memory/marketing/content-drafter/state.md +12 -0
  187. package/templates/seed/memory/operations/ops-lead/state.md +12 -0
  188. package/templates/seed/memory/product/lead/state.md +14 -0
  189. package/templates/seed/memory/research/lead/state.md +14 -0
  190. package/templates/seed/skills/gh/SKILL.md +57 -0
  191. package/templates/seed/skills/squads-cli/SKILL.md +84 -0
  192. package/templates/seed/squads/company/SQUAD.md +51 -0
  193. package/templates/seed/squads/company/company-critic.md +49 -0
  194. package/templates/seed/squads/company/company-eval.md +49 -0
  195. package/templates/seed/squads/company/event-dispatcher.md +43 -0
  196. package/templates/seed/squads/company/goal-tracker.md +43 -0
  197. package/templates/seed/squads/company/manager.md +54 -0
  198. package/templates/seed/squads/engineering/SQUAD.md +48 -0
  199. package/templates/seed/squads/engineering/code-reviewer.md +57 -0
  200. package/templates/seed/squads/engineering/issue-solver.md +58 -0
  201. package/templates/seed/squads/engineering/test-writer.md +50 -0
  202. package/templates/seed/squads/intelligence/SQUAD.md +38 -0
  203. package/templates/seed/squads/intelligence/intel-critic.md +36 -0
  204. package/templates/seed/squads/intelligence/intel-eval.md +31 -0
  205. package/templates/seed/squads/intelligence/intel-lead.md +71 -0
  206. package/templates/seed/squads/marketing/SQUAD.md +47 -0
  207. package/templates/seed/squads/marketing/content-drafter.md +71 -0
  208. package/templates/seed/squads/marketing/growth-analyst.md +49 -0
  209. package/templates/seed/squads/marketing/social-poster.md +44 -0
  210. package/templates/seed/squads/operations/SQUAD.md +45 -0
  211. package/templates/seed/squads/operations/finance-tracker.md +47 -0
  212. package/templates/seed/squads/operations/goal-tracker.md +48 -0
  213. package/templates/seed/squads/operations/ops-lead.md +58 -0
  214. package/templates/seed/squads/product/SQUAD.md +41 -0
  215. package/templates/seed/squads/product/lead.md +56 -0
  216. package/templates/seed/squads/product/scanner.md +50 -0
  217. package/templates/seed/squads/product/worker.md +55 -0
  218. package/templates/seed/squads/research/SQUAD.md +38 -0
  219. package/templates/seed/squads/research/analyst.md +50 -0
  220. package/templates/seed/squads/research/lead.md +52 -0
  221. package/templates/seed/squads/research/synthesizer.md +59 -0
  222. package/templates/skills/squads-learn/SKILL.md +86 -0
  223. package/templates/skills/squads-workflow/instruction.md +70 -0
@@ -0,0 +1,4049 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getCLIConfig,
4
+ isProviderCLIAvailable
5
+ } from "./chunk-QHNUMM4V.js";
6
+ import {
7
+ getOutcomeScoreModifier
8
+ } from "./chunk-HF4WR7RA.js";
9
+ import {
10
+ getBotGhEnv,
11
+ getBotGitEnv,
12
+ getBotPushUrl,
13
+ getCoAuthorTrailer
14
+ } from "./chunk-FIWT2NMM.js";
15
+ import {
16
+ detectProviderFromModel
17
+ } from "./chunk-LOA3KWYJ.js";
18
+ import {
19
+ parseCooldown
20
+ } from "./chunk-RM6BWILN.js";
21
+ import {
22
+ getApiUrl,
23
+ getBridgeUrl
24
+ } from "./chunk-EHQJHRIW.js";
25
+ import {
26
+ isLoggedIn,
27
+ loadSession
28
+ } from "./chunk-Z2UKDBNL.js";
29
+ import {
30
+ Events,
31
+ flushEvents,
32
+ track
33
+ } from "./chunk-QJ7C7CMB.js";
34
+ import {
35
+ findSimilarSquads,
36
+ findSquadsDir,
37
+ listAgents,
38
+ listSquads,
39
+ loadAgentDefinition,
40
+ loadSquad,
41
+ parseAgentProvider,
42
+ resolveMcpConfigPath
43
+ } from "./chunk-TYFTF53O.js";
44
+ import {
45
+ findMemoryDir
46
+ } from "./chunk-ZTQ7ISUR.js";
47
+ import {
48
+ RESET,
49
+ bold,
50
+ colors,
51
+ gradient,
52
+ icons,
53
+ writeLine
54
+ } from "./chunk-M5FXNY6Y.js";
55
+ import "./chunk-7OCVIDC7.js";
56
+
57
+ // src/commands/run.ts
58
+ import ora from "ora";
59
+ import { spawn, execSync as execSync3 } from "child_process";
60
+ import { join as join5, dirname as dirname2 } from "path";
61
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, cpSync, unlinkSync } from "fs";
62
+
63
+ // src/lib/permissions.ts
64
+ import { minimatch } from "minimatch";
65
+ function getDefaultContext(squad, agent) {
66
+ return {
67
+ squad,
68
+ agent,
69
+ permissions: {
70
+ mode: "warn",
71
+ bash: ["*"],
72
+ // All commands allowed by default
73
+ write: ["**"],
74
+ // All paths writable by default
75
+ read: ["**"],
76
+ // All paths readable by default
77
+ mcp: {
78
+ allow: ["*"],
79
+ // All MCP servers allowed
80
+ deny: []
81
+ }
82
+ }
83
+ };
84
+ }
85
+ function validateBashCommand(command, allowedCommands) {
86
+ const baseCommand = command.trim().split(/\s+/)[0];
87
+ if (allowedCommands.includes("*")) {
88
+ return null;
89
+ }
90
+ const isAllowed = allowedCommands.some((allowed) => {
91
+ if (allowed === baseCommand) return true;
92
+ if (allowed.includes("*")) {
93
+ return minimatch(baseCommand, allowed);
94
+ }
95
+ return false;
96
+ });
97
+ if (!isAllowed) {
98
+ return {
99
+ type: "bash",
100
+ requested: baseCommand,
101
+ reason: `Bash command '${baseCommand}' not in allowlist: [${allowedCommands.join(", ")}]`,
102
+ severity: "error"
103
+ };
104
+ }
105
+ return null;
106
+ }
107
+ function validateFilePath(path, allowedGlobs, operation) {
108
+ if (allowedGlobs.includes("**") || allowedGlobs.includes("*")) {
109
+ return null;
110
+ }
111
+ const isAllowed = allowedGlobs.some((glob) => minimatch(path, glob));
112
+ if (!isAllowed) {
113
+ return {
114
+ type: operation,
115
+ requested: path,
116
+ reason: `${operation === "write" ? "Write" : "Read"} to '${path}' not allowed. Permitted paths: [${allowedGlobs.join(", ")}]`,
117
+ severity: "error"
118
+ };
119
+ }
120
+ return null;
121
+ }
122
+ function validateMcpServer(server, allow, deny) {
123
+ const isDenied = deny.some((pattern) => {
124
+ if (pattern === server) return true;
125
+ if (pattern.includes("*")) return minimatch(server, pattern);
126
+ return false;
127
+ });
128
+ if (isDenied) {
129
+ return {
130
+ type: "mcp",
131
+ requested: server,
132
+ reason: `MCP server '${server}' is explicitly denied`,
133
+ severity: "error"
134
+ };
135
+ }
136
+ if (allow.includes("*")) {
137
+ return null;
138
+ }
139
+ const isAllowed = allow.some((pattern) => {
140
+ if (pattern === server) return true;
141
+ if (pattern.includes("*")) return minimatch(server, pattern);
142
+ return false;
143
+ });
144
+ if (!isAllowed) {
145
+ return {
146
+ type: "mcp",
147
+ requested: server,
148
+ reason: `MCP server '${server}' not in allowlist: [${allow.join(", ")}]`,
149
+ severity: "error"
150
+ };
151
+ }
152
+ return null;
153
+ }
154
+ function validateExecution(context, request) {
155
+ const violations = [];
156
+ if (request.bashCommands) {
157
+ for (const cmd of request.bashCommands) {
158
+ const violation = validateBashCommand(cmd, context.permissions.bash);
159
+ if (violation) {
160
+ violations.push(violation);
161
+ }
162
+ }
163
+ }
164
+ if (request.writePaths) {
165
+ for (const path of request.writePaths) {
166
+ const violation = validateFilePath(path, context.permissions.write, "write");
167
+ if (violation) {
168
+ violations.push(violation);
169
+ }
170
+ }
171
+ }
172
+ if (request.readPaths) {
173
+ for (const path of request.readPaths) {
174
+ const violation = validateFilePath(path, context.permissions.read, "read");
175
+ if (violation) {
176
+ violations.push(violation);
177
+ }
178
+ }
179
+ }
180
+ if (request.mcpServers) {
181
+ for (const server of request.mcpServers) {
182
+ const violation = validateMcpServer(
183
+ server,
184
+ context.permissions.mcp.allow,
185
+ context.permissions.mcp.deny
186
+ );
187
+ if (violation) {
188
+ violations.push(violation);
189
+ }
190
+ }
191
+ }
192
+ const hasErrors = violations.some((v) => v.severity === "error");
193
+ const allowed = context.permissions.mode !== "strict" || !hasErrors;
194
+ return {
195
+ allowed,
196
+ violations,
197
+ mode: context.permissions.mode
198
+ };
199
+ }
200
+ function formatViolations(result) {
201
+ const lines = [];
202
+ if (result.violations.length === 0) {
203
+ return lines;
204
+ }
205
+ const modeLabel = {
206
+ warn: "\u26A0\uFE0F PERMISSION WARNING",
207
+ strict: "\u{1F6AB} PERMISSION DENIED",
208
+ audit: "\u{1F4DD} PERMISSION AUDIT"
209
+ }[result.mode];
210
+ lines.push(modeLabel);
211
+ lines.push("");
212
+ for (const v of result.violations) {
213
+ const icon = v.severity === "error" ? "\u2717" : "\u26A0";
214
+ lines.push(` ${icon} [${v.type}] ${v.reason}`);
215
+ }
216
+ if (result.mode === "warn") {
217
+ lines.push("");
218
+ lines.push(" Continuing with warnings (mode: warn)");
219
+ } else if (result.mode === "audit") {
220
+ lines.push("");
221
+ lines.push(" Logged for audit, continuing (mode: audit)");
222
+ } else if (!result.allowed) {
223
+ lines.push("");
224
+ lines.push(" Execution blocked (mode: strict)");
225
+ }
226
+ return lines;
227
+ }
228
+ function parsePermissionsYaml(content) {
229
+ const yamlMatch = content.match(/```ya?ml\n([\s\S]*?)```/);
230
+ if (!yamlMatch) return null;
231
+ const yamlContent = yamlMatch[1];
232
+ const permissions = {};
233
+ const modeMatch = yamlContent.match(/^\s*mode:\s*(warn|strict|audit)/m);
234
+ if (modeMatch) {
235
+ permissions.mode = modeMatch[1];
236
+ }
237
+ const bashMatch = yamlContent.match(/^\s*bash:\s*\[(.*?)\]/m);
238
+ if (bashMatch) {
239
+ permissions.bash = bashMatch[1].split(",").map((s) => s.trim());
240
+ }
241
+ const writeMatch = yamlContent.match(/^\s*write:\s*\[(.*?)\]/m);
242
+ if (writeMatch) {
243
+ permissions.write = writeMatch[1].split(",").map((s) => s.trim());
244
+ }
245
+ const readMatch = yamlContent.match(/^\s*read:\s*\[(.*?)\]/m);
246
+ if (readMatch) {
247
+ permissions.read = readMatch[1].split(",").map((s) => s.trim());
248
+ }
249
+ const mcpAllowMatch = yamlContent.match(/^\s*allow:\s*\[(.*?)\]/m);
250
+ const mcpDenyMatch = yamlContent.match(/^\s*deny:\s*\[(.*?)\]/m);
251
+ if (mcpAllowMatch || mcpDenyMatch) {
252
+ permissions.mcp = {
253
+ allow: mcpAllowMatch ? mcpAllowMatch[1].split(",").map((s) => s.trim()) : ["*"],
254
+ deny: mcpDenyMatch ? mcpDenyMatch[1].split(",").map((s) => s.trim()) : []
255
+ };
256
+ }
257
+ return Object.keys(permissions).length > 0 ? permissions : null;
258
+ }
259
+ function buildContextFromSquad(squadName, squadContent, agentName) {
260
+ const context = getDefaultContext(squadName, agentName);
261
+ const parsed = parsePermissionsYaml(squadContent);
262
+ if (parsed) {
263
+ if (parsed.mode) context.permissions.mode = parsed.mode;
264
+ if (parsed.bash) context.permissions.bash = parsed.bash;
265
+ if (parsed.write) context.permissions.write = parsed.write;
266
+ if (parsed.read) context.permissions.read = parsed.read;
267
+ if (parsed.mcp) context.permissions.mcp = parsed.mcp;
268
+ }
269
+ return context;
270
+ }
271
+
272
+ // src/lib/workflow.ts
273
+ import { join as join2 } from "path";
274
+ import { existsSync as existsSync2, writeFileSync, mkdirSync } from "fs";
275
+ import { execSync, exec } from "child_process";
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 LEAD_COMPLETION_PHRASES = [
389
+ "session complete",
390
+ "session is complete",
391
+ "nothing to do",
392
+ "nothing more to do",
393
+ "nothing left to do",
394
+ "all work is done",
395
+ "all work complete",
396
+ "work is complete",
397
+ "work is done",
398
+ "all tasks complete",
399
+ "all tasks done",
400
+ "approved",
401
+ "approving",
402
+ "declaring convergence",
403
+ "signaling convergence",
404
+ "signal convergence",
405
+ "no further action",
406
+ "no further work",
407
+ "no action needed",
408
+ "no actions needed",
409
+ "wrapping up",
410
+ "closing out",
411
+ "conversation complete",
412
+ "cycle complete"
413
+ ];
414
+ var CONTINUATION_PHRASES = [
415
+ "needs review",
416
+ "needs feedback",
417
+ "needs input",
418
+ "need clarification",
419
+ "todo",
420
+ "fixme",
421
+ "blocked",
422
+ "waiting for",
423
+ "waiting on",
424
+ "will continue",
425
+ "will proceed",
426
+ "will work on",
427
+ "next step",
428
+ "in progress"
429
+ ];
430
+ function detectConvergence(transcript, maxTurns, costCeiling) {
431
+ if (transcript.turns.length >= maxTurns) {
432
+ return { converged: true, reason: `Max turns reached (${maxTurns})` };
433
+ }
434
+ if (transcript.totalCost >= costCeiling) {
435
+ return { converged: true, reason: `Cost ceiling reached ($${transcript.totalCost.toFixed(2)}/$${costCeiling})` };
436
+ }
437
+ if (transcript.turns.length === 0) {
438
+ return { converged: false, reason: "No turns yet" };
439
+ }
440
+ const lastTurn = transcript.turns[transcript.turns.length - 1];
441
+ const content = lastTurn.content;
442
+ const lower = content.toLowerCase();
443
+ if (lastTurn.role === "verifier") {
444
+ const rejected = VERIFIER_REJECTION_PHRASES.some((phrase) => lower.includes(phrase));
445
+ if (rejected) {
446
+ return { converged: false, reason: "Verifier rejected \u2014 continuing cycle" };
447
+ }
448
+ const approved = VERIFIER_APPROVAL_PHRASES.some((phrase) => lower.includes(phrase));
449
+ if (approved) {
450
+ return { converged: true, reason: "Verifier approved" };
451
+ }
452
+ }
453
+ if (lastTurn.role === "lead") {
454
+ const leadDone = LEAD_COMPLETION_PHRASES.some((phrase) => lower.includes(phrase));
455
+ if (leadDone) {
456
+ return { converged: true, reason: "Lead signaled completion" };
457
+ }
458
+ }
459
+ const hasContinuation = CONTINUATION_PHRASES.some((phrase) => lower.includes(phrase));
460
+ if (hasContinuation) {
461
+ return { converged: false, reason: "Continuation signal detected" };
462
+ }
463
+ const hasConvergence = CONVERGENCE_PHRASES.some((phrase) => lower.includes(phrase));
464
+ if (hasConvergence) {
465
+ return { converged: true, reason: "Convergence signal detected" };
466
+ }
467
+ return { converged: false, reason: "No signals detected, continuing" };
468
+ }
469
+ var COST_PER_TURN = {
470
+ opus: 2.5,
471
+ sonnet: 0.75,
472
+ haiku: 0.1
473
+ };
474
+ function estimateTurnCost(model) {
475
+ const key = model.includes("opus") ? "opus" : model.includes("haiku") ? "haiku" : "sonnet";
476
+ return COST_PER_TURN[key] || COST_PER_TURN.sonnet;
477
+ }
478
+
479
+ // src/lib/run-context.ts
480
+ import { join, dirname } from "path";
481
+ import { existsSync, readFileSync, readdirSync } from "fs";
482
+ var ROLE_BUDGETS = {
483
+ scanner: 4e3,
484
+ // ~1000 tokens — identity + priorities + state
485
+ worker: 12e3,
486
+ // ~3000 tokens — + directives, feedback, active-work
487
+ lead: 24e3,
488
+ // ~6000 tokens — all sections
489
+ coo: 32e3
490
+ // ~8000 tokens — all sections + expanded
491
+ };
492
+ var ROLE_SECTIONS = {
493
+ scanner: /* @__PURE__ */ new Set([1, 2, 5]),
494
+ // SQUAD.md, priorities, state
495
+ worker: /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 6]),
496
+ // + directives, feedback, active-work
497
+ lead: /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
498
+ // all sections
499
+ coo: /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
500
+ // all sections + expanded budget
501
+ };
502
+ function parseAgentFrontmatter(agentPath) {
503
+ if (!agentPath || !existsSync(agentPath)) return {};
504
+ let content;
505
+ try {
506
+ content = readFileSync(agentPath, "utf-8");
507
+ } catch {
508
+ return {};
509
+ }
510
+ if (!content) return {};
511
+ const lines = content.split("\n");
512
+ let inFrontmatter = false;
513
+ const yamlLines = [];
514
+ for (const line of lines) {
515
+ if (line.trim() === "---") {
516
+ if (inFrontmatter) break;
517
+ inFrontmatter = true;
518
+ continue;
519
+ }
520
+ if (inFrontmatter) {
521
+ yamlLines.push(line);
522
+ }
523
+ }
524
+ if (yamlLines.length === 0) return {};
525
+ const yaml = yamlLines.join("\n");
526
+ const result = {};
527
+ const contextMatch = yaml.match(/context_from:\s*\[([^\]]+)\]/);
528
+ if (contextMatch) {
529
+ result.context_from = contextMatch[1].split(",").map((s) => s.trim());
530
+ }
531
+ const criteriaMatch = yaml.match(/acceptance_criteria:\s*\|\n((?:\s+.+\n?)*)/);
532
+ if (criteriaMatch) {
533
+ result.acceptance_criteria = criteriaMatch[1].replace(/^ {2}/gm, "").trim();
534
+ }
535
+ const retriesMatch = yaml.match(/max_retries:\s*(\d+)/);
536
+ if (retriesMatch) {
537
+ result.max_retries = parseInt(retriesMatch[1], 10);
538
+ }
539
+ const cooldownMatch = yaml.match(/cooldown:\s*["']?([^"'\n]+)["']?/);
540
+ if (cooldownMatch) {
541
+ result.cooldown = cooldownMatch[1].trim();
542
+ }
543
+ return result;
544
+ }
545
+ function extractMcpServersFromDefinition(definition) {
546
+ const servers = /* @__PURE__ */ new Set();
547
+ const knownServers = [
548
+ "chrome-devtools",
549
+ "firecrawl",
550
+ "context7",
551
+ "huggingface"
552
+ ];
553
+ for (const server of knownServers) {
554
+ if (definition.toLowerCase().includes(server)) {
555
+ servers.add(server);
556
+ }
557
+ }
558
+ const mcpMatch = definition.match(/mcp:\s*\n((?:\s*-\s*\S+\s*\n?)+)/i);
559
+ if (mcpMatch) {
560
+ const lines = mcpMatch[1].split("\n");
561
+ for (const line of lines) {
562
+ const serverMatch = line.match(/^\s*-\s*(\S+)/);
563
+ if (serverMatch) {
564
+ servers.add(serverMatch[1]);
565
+ }
566
+ }
567
+ }
568
+ return Array.from(servers);
569
+ }
570
+ function readAgentsFile(relativePath, warnLabel) {
571
+ const squadsDir = findSquadsDir();
572
+ if (!squadsDir) return "";
573
+ const filePath = join(dirname(squadsDir), relativePath);
574
+ if (!existsSync(filePath)) return "";
575
+ try {
576
+ return readFileSync(filePath, "utf-8").trim();
577
+ } catch (e) {
578
+ writeLine(` ${colors.dim}warn: failed reading ${warnLabel}: ${e instanceof Error ? e.message : String(e)}${RESET}`);
579
+ return "";
580
+ }
581
+ }
582
+ function loadSystemProtocol() {
583
+ const systemMd = readAgentsFile("config/SYSTEM.md", "SYSTEM.md");
584
+ if (systemMd) return systemMd;
585
+ return loadApprovalInstructions();
586
+ }
587
+ function loadApprovalInstructions() {
588
+ return readAgentsFile("config/approval-instructions.md", "approval instructions");
589
+ }
590
+ function safeRead(path) {
591
+ try {
592
+ return existsSync(path) ? readFileSync(path, "utf-8").trim() : "";
593
+ } catch {
594
+ return "";
595
+ }
596
+ }
597
+ function readDirMd(dirPath, maxChars) {
598
+ if (!existsSync(dirPath)) return "";
599
+ try {
600
+ const files = readdirSync(dirPath).filter((f) => f.endsWith(".md")).sort();
601
+ const parts = [];
602
+ let totalChars = 0;
603
+ for (const file of files) {
604
+ const content = safeRead(join(dirPath, file));
605
+ if (!content) continue;
606
+ if (totalChars + content.length > maxChars) break;
607
+ parts.push(content);
608
+ totalChars += content.length;
609
+ }
610
+ return parts.join("\n\n");
611
+ } catch {
612
+ return "";
613
+ }
614
+ }
615
+ function gatherSquadContext(squadName, agentName, options = {}) {
616
+ const squadsDir = findSquadsDir();
617
+ if (!squadsDir) return "";
618
+ const memoryDir = findMemoryDir();
619
+ const role = options.role || "worker";
620
+ const budget = options.maxTokens ? options.maxTokens * 4 : ROLE_BUDGETS[role];
621
+ const allowedSections = ROLE_SECTIONS[role];
622
+ const sections = [];
623
+ let usedChars = 0;
624
+ function addSection(sectionNum, header, content, maxChars) {
625
+ if (!allowedSections.has(sectionNum)) return false;
626
+ if (!content) return false;
627
+ let text = content;
628
+ const cap = maxChars || budget - usedChars;
629
+ if (text.length > cap) {
630
+ text = text.substring(0, cap) + "\n...";
631
+ }
632
+ if (usedChars + text.length > budget) {
633
+ if (options.verbose) {
634
+ writeLine(` ${colors.dim}Context budget exhausted at section ${sectionNum} (${header})${RESET}`);
635
+ }
636
+ return false;
637
+ }
638
+ sections.push(`## ${header}
639
+ ${text}`);
640
+ usedChars += text.length;
641
+ return true;
642
+ }
643
+ const squadFile = join(squadsDir, squadName, "SQUAD.md");
644
+ if (existsSync(squadFile)) {
645
+ try {
646
+ const content = readFileSync(squadFile, "utf-8");
647
+ const missionMatch = content.match(/## Mission[\s\S]*?(?=\n## |$)/i);
648
+ const squad = missionMatch ? missionMatch[0] : content.substring(0, 2e3);
649
+ addSection(1, `Squad: ${squadName}`, squad.trim());
650
+ } catch (e) {
651
+ if (options.verbose) writeLine(` ${colors.dim}warn: failed reading SQUAD.md: ${e instanceof Error ? e.message : String(e)}${RESET}`);
652
+ }
653
+ }
654
+ if (memoryDir) {
655
+ const prioritiesFile = join(memoryDir, squadName, "priorities.md");
656
+ const goalsFile = join(memoryDir, squadName, "goals.md");
657
+ const file = existsSync(prioritiesFile) ? prioritiesFile : goalsFile;
658
+ const content = safeRead(file);
659
+ if (content) {
660
+ addSection(2, "Priorities", content);
661
+ }
662
+ }
663
+ if (memoryDir) {
664
+ const directivesFile = join(memoryDir, "company", "directives.md");
665
+ const content = safeRead(directivesFile);
666
+ if (content) {
667
+ addSection(3, "Directives", content);
668
+ }
669
+ }
670
+ if (memoryDir) {
671
+ const feedbackFile = join(memoryDir, squadName, "feedback.md");
672
+ const content = safeRead(feedbackFile);
673
+ if (content) {
674
+ addSection(4, "Feedback", content);
675
+ }
676
+ }
677
+ if (memoryDir) {
678
+ const stateFile = join(memoryDir, squadName, agentName, "state.md");
679
+ const content = safeRead(stateFile);
680
+ if (content) {
681
+ const stateCap = role === "scanner" ? 2e3 : void 0;
682
+ addSection(5, "Previous State", content, stateCap);
683
+ }
684
+ }
685
+ if (memoryDir) {
686
+ const activeWorkFile = join(memoryDir, squadName, "active-work.md");
687
+ const content = safeRead(activeWorkFile);
688
+ if (content) {
689
+ addSection(6, "Active Work", content);
690
+ }
691
+ }
692
+ if (memoryDir) {
693
+ const briefsDir = join(memoryDir, squadName, agentName, "briefs");
694
+ const content = readDirMd(briefsDir, 3e3);
695
+ if (content) {
696
+ addSection(7, "Agent Briefs", content);
697
+ }
698
+ }
699
+ if (memoryDir) {
700
+ const briefsDir = join(memoryDir, squadName, "_briefs");
701
+ const content = readDirMd(briefsDir, 3e3);
702
+ if (content) {
703
+ addSection(8, "Squad Briefs", content);
704
+ }
705
+ }
706
+ if (memoryDir) {
707
+ const dailyFile = join(memoryDir, "daily-briefing.md");
708
+ const content = safeRead(dailyFile);
709
+ if (content) {
710
+ addSection(9, "Daily Briefing", content);
711
+ }
712
+ }
713
+ if (memoryDir) {
714
+ const frontmatter = options.agentPath ? parseAgentFrontmatter(options.agentPath) : {};
715
+ const contextSquads = frontmatter.context_from || [];
716
+ const learningParts = [];
717
+ for (const ctx of contextSquads) {
718
+ const learningsFile = join(memoryDir, ctx, "shared", "learnings.md");
719
+ const content = safeRead(learningsFile);
720
+ if (content) {
721
+ learningParts.push(`### ${ctx}
722
+ ${content}`);
723
+ }
724
+ }
725
+ if (learningParts.length > 0) {
726
+ addSection(10, "Cross-Squad Learnings", learningParts.join("\n\n"));
727
+ }
728
+ }
729
+ if (sections.length === 0) return "";
730
+ if (options.verbose) {
731
+ writeLine(` ${colors.dim}Context: ${sections.length} sections, ~${Math.ceil(usedChars / 4)} tokens (${role} role, budget: ~${Math.ceil(budget / 4)})${RESET}`);
732
+ }
733
+ return `
734
+ # CONTEXT
735
+ ${sections.join("\n\n")}
736
+ `;
737
+ }
738
+
739
+ // src/lib/workflow.ts
740
+ var DEFAULT_MAX_TURNS = 20;
741
+ var DEFAULT_COST_CEILING = 25;
742
+ function executeAgentTurn(config) {
743
+ const { agentName, agentPath, role, squadName, model: _model, transcript, task } = config;
744
+ const transcriptContext = serializeTranscript(transcript);
745
+ const contextRole = agentName.includes("company-lead") ? "coo" : role;
746
+ const squadContext = gatherSquadContext(squadName, agentName, {
747
+ agentPath,
748
+ role: contextRole
749
+ });
750
+ let roleInstructions;
751
+ switch (role) {
752
+ case "lead":
753
+ if (transcript.turns.length === 0 && task) {
754
+ roleInstructions = `## Founder Directive
755
+
756
+ ${task}
757
+
758
+ Brief the team on this directive. Set priorities and assign work.`;
759
+ } else if (transcript.turns.length === 0) {
760
+ roleInstructions = `## Your Role: Lead
761
+
762
+ You are starting a new squad session. Brief the team:
763
+ 1. Review open issues and PRs
764
+ 2. Set priorities for this session
765
+ 3. Assign work to workers
766
+ 4. Be specific about what each worker should do`;
767
+ } else {
768
+ roleInstructions = `## Your Role: Lead (Review)
769
+
770
+ Review the work done so far. Either:
771
+ - Request specific changes from workers
772
+ - Approve and signal completion if quality is sufficient
773
+ - Merge PRs using \`gh pr merge --squash --delete-branch --auto\` (waits for required checks)`;
774
+ }
775
+ break;
776
+ case "scanner":
777
+ roleInstructions = `## Your Role: Scanner
778
+
779
+ Scan for issues, gaps, and opportunities. Report findings concisely. Do NOT fix anything \u2014 just discover and report.`;
780
+ break;
781
+ case "worker":
782
+ roleInstructions = `## Your Role: Worker
783
+
784
+ Execute the work assigned by the lead. Create branches, write code, open PRs to develop. Be focused and efficient.`;
785
+ break;
786
+ case "verifier":
787
+ roleInstructions = `## Your Role: Verifier
788
+
789
+ Verify that work meets quality standards. Check PRs, run tests, validate output. Report pass/fail with specifics.`;
790
+ break;
791
+ }
792
+ const prompt = `You are ${agentName} (${role}) in squad ${squadName}.
793
+
794
+ Read your full agent definition at ${agentPath} and follow its instructions.
795
+
796
+ ${roleInstructions}
797
+ ${squadContext}
798
+ ${transcriptContext}
799
+
800
+ IMPORTANT:
801
+ - Be concise. Your output becomes part of a shared transcript.
802
+ - Reference specific issue numbers, PR numbers, and file paths.
803
+ - If you create a PR, include the PR number in your output.
804
+ - If there's nothing to do, say "Nothing to do" clearly.
805
+ - When done, summarize what you did in 2-3 sentences.`;
806
+ const resolvedModel = config.model || modelForRole(role);
807
+ const { CLAUDECODE: _cc, ANTHROPIC_API_KEY: _ak, ...cleanEnv } = process.env;
808
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
809
+ try {
810
+ const output = execSync(
811
+ `claude --print --dangerously-skip-permissions --model ${resolvedModel} -- '${escapedPrompt}'`,
812
+ {
813
+ cwd: config.cwd || process.cwd(),
814
+ timeout: 15 * 60 * 1e3,
815
+ // 15 min per turn
816
+ maxBuffer: 10 * 1024 * 1024,
817
+ // 10MB
818
+ encoding: "utf-8",
819
+ env: cleanEnv
820
+ }
821
+ );
822
+ return output.trim();
823
+ } catch (err) {
824
+ const error = err;
825
+ if (error.stdout && error.stdout.trim().length > 0) {
826
+ return error.stdout.trim();
827
+ }
828
+ return `[ERROR] Agent ${agentName} failed: ${error.message || "unknown error"}`;
829
+ }
830
+ }
831
+ function executeAgentTurnAsync(config) {
832
+ const { agentName, agentPath, role, squadName, model: _model, transcript, task } = config;
833
+ let roleInstructions = "";
834
+ switch (role) {
835
+ case "lead":
836
+ roleInstructions = task ? `FOUNDER DIRECTIVE: ${task}
837
+
838
+ 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.";
839
+ break;
840
+ case "scanner":
841
+ roleInstructions = "Scan for issues, data, or signals relevant to the lead's brief. Report findings concisely.";
842
+ break;
843
+ case "worker":
844
+ roleInstructions = "Execute the specific task assigned by the lead. Produce concrete output (PRs, issues, content, analysis).";
845
+ break;
846
+ case "verifier":
847
+ roleInstructions = "Verify the worker's output meets quality standards. Check for errors, omissions, and alignment with goals.";
848
+ break;
849
+ }
850
+ const transcriptContext = transcript.turns.length > 0 ? `
851
+ == CONVERSATION SO FAR ==
852
+ ${serializeTranscript(transcript)}
853
+ == END CONVERSATION ==` : "";
854
+ const resolvedModel = config.model || modelForRole(role);
855
+ const prompt = `You are ${agentName} (${role}) in squad ${squadName}.
856
+
857
+ Read your full agent definition at ${agentPath} and follow its instructions.
858
+
859
+ ${roleInstructions}
860
+
861
+ ${transcriptContext}
862
+
863
+ IMPORTANT:
864
+ - Be concise. Your output becomes part of a shared transcript.
865
+ - Reference specific issue numbers, PR numbers, and file paths.
866
+ - If you create a PR, include the PR number in your output.
867
+ - If there's nothing to do, say "Nothing to do" clearly.
868
+ - When done, summarize what you did in 2-3 sentences.`;
869
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
870
+ const { CLAUDECODE: _cc2, ANTHROPIC_API_KEY: _ak2, ...cleanEnvAsync } = process.env;
871
+ return new Promise((resolve) => {
872
+ exec(
873
+ `claude --print --dangerously-skip-permissions --model ${resolvedModel} -- '${escapedPrompt}'`,
874
+ {
875
+ cwd: config.cwd || process.cwd(),
876
+ timeout: 15 * 60 * 1e3,
877
+ maxBuffer: 10 * 1024 * 1024,
878
+ encoding: "utf-8",
879
+ env: cleanEnvAsync
880
+ },
881
+ (error, stdout, _stderr) => {
882
+ if (stdout && stdout.trim().length > 0) {
883
+ resolve(stdout.trim());
884
+ } else if (error) {
885
+ resolve(`[ERROR] Agent ${agentName} failed: ${error.message || "unknown error"}`);
886
+ } else {
887
+ resolve("[No output]");
888
+ }
889
+ }
890
+ );
891
+ });
892
+ }
893
+ function buildTurnPlan(squad, squadsDir) {
894
+ const agents = [];
895
+ for (const agent of squad.agents) {
896
+ const role = classifyAgent(agent.name, agent.role);
897
+ if (!role) continue;
898
+ const agentPath = join2(squadsDir, squad.dir, `${agent.name}.md`);
899
+ if (!existsSync2(agentPath)) continue;
900
+ agents.push({ name: agent.name, role, path: agentPath });
901
+ }
902
+ return agents;
903
+ }
904
+ async function runConversation(squad, options = {}) {
905
+ const squadsDir = findSquadsDir();
906
+ if (!squadsDir) {
907
+ return {
908
+ transcript: createTranscript(squad.name),
909
+ turnCount: 0,
910
+ totalCost: 0,
911
+ converged: true,
912
+ reason: "No squads directory found"
913
+ };
914
+ }
915
+ const maxTurns = options.maxTurns || DEFAULT_MAX_TURNS;
916
+ const costCeiling = options.costCeiling || DEFAULT_COST_CEILING;
917
+ const transcript = createTranscript(squad.name);
918
+ let squadCwd = process.cwd();
919
+ if (squad.repo) {
920
+ const repoName = squad.repo.split("/").pop();
921
+ if (repoName) {
922
+ const reposRoot = join2(squadsDir, "..", "..", "..");
923
+ const candidatePath = join2(reposRoot, repoName);
924
+ if (existsSync2(candidatePath)) {
925
+ squadCwd = candidatePath;
926
+ }
927
+ }
928
+ }
929
+ const allAgents = buildTurnPlan(squad, squadsDir);
930
+ const leads = allAgents.filter((a) => a.role === "lead");
931
+ const scanners = allAgents.filter((a) => a.role === "scanner");
932
+ const workers = allAgents.filter((a) => a.role === "worker");
933
+ const verifiers = allAgents.filter((a) => a.role === "verifier");
934
+ if (leads.length === 0) {
935
+ return {
936
+ transcript,
937
+ turnCount: 0,
938
+ totalCost: 0,
939
+ converged: true,
940
+ reason: "No lead agent found \u2014 cannot orchestrate conversation"
941
+ };
942
+ }
943
+ const lead = leads[0];
944
+ const log = (msg) => {
945
+ if (options.verbose) {
946
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
947
+ process.stderr.write(` [${ts}] ${msg}
948
+ `);
949
+ }
950
+ };
951
+ log(`Conversation: ${squad.name} | ${allAgents.length} agents | max ${maxTurns} turns | $${costCeiling} ceiling`);
952
+ 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"}`);
953
+ let cycleCount = 0;
954
+ const MAX_CYCLES = 5;
955
+ while (cycleCount < MAX_CYCLES) {
956
+ cycleCount++;
957
+ log(`
958
+ --- Cycle ${cycleCount} ---`);
959
+ log(`Turn ${transcript.turns.length + 1}: ${lead.name} (lead)`);
960
+ const leadOutput = executeAgentTurn({
961
+ agentName: lead.name,
962
+ agentPath: lead.path,
963
+ role: "lead",
964
+ squadName: squad.name,
965
+ model: options.model || modelForRole("lead"),
966
+ transcript,
967
+ task: cycleCount === 1 ? options.task : void 0,
968
+ cwd: squadCwd
969
+ });
970
+ addTurn(transcript, lead.name, "lead", leadOutput, estimateTurnCost(options.model || "sonnet"));
971
+ let conv = detectConvergence(transcript, maxTurns, costCeiling);
972
+ if (conv.converged) {
973
+ log(`Converged after lead: ${conv.reason}`);
974
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
975
+ }
976
+ if (cycleCount === 1 && scanners.length > 0) {
977
+ if (scanners.length === 1) {
978
+ log(`Turn ${transcript.turns.length + 1}: ${scanners[0].name} (scanner)`);
979
+ const output = executeAgentTurn({
980
+ agentName: scanners[0].name,
981
+ agentPath: scanners[0].path,
982
+ role: "scanner",
983
+ squadName: squad.name,
984
+ model: options.model || modelForRole("scanner"),
985
+ transcript,
986
+ cwd: squadCwd
987
+ });
988
+ addTurn(transcript, scanners[0].name, "scanner", output, estimateTurnCost(options.model || "haiku"));
989
+ } else {
990
+ log(`Turns ${transcript.turns.length + 1}-${transcript.turns.length + scanners.length}: ${scanners.map((s) => s.name).join(", ")} (scanners, parallel)`);
991
+ const scannerPromises = scanners.map(
992
+ (scanner) => executeAgentTurnAsync({
993
+ agentName: scanner.name,
994
+ agentPath: scanner.path,
995
+ role: "scanner",
996
+ squadName: squad.name,
997
+ model: options.model || modelForRole("scanner"),
998
+ transcript,
999
+ // snapshot — all scanners see same context
1000
+ cwd: squadCwd
1001
+ }).then((output) => ({ agent: scanner, output }))
1002
+ );
1003
+ const scannerResults = await Promise.all(scannerPromises);
1004
+ for (const { agent, output } of scannerResults) {
1005
+ addTurn(transcript, agent.name, "scanner", output, estimateTurnCost(options.model || "haiku"));
1006
+ }
1007
+ }
1008
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
1009
+ if (conv.converged) {
1010
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
1011
+ }
1012
+ }
1013
+ if (workers.length === 1) {
1014
+ log(`Turn ${transcript.turns.length + 1}: ${workers[0].name} (worker)`);
1015
+ const output = executeAgentTurn({
1016
+ agentName: workers[0].name,
1017
+ agentPath: workers[0].path,
1018
+ role: "worker",
1019
+ squadName: squad.name,
1020
+ model: options.model || modelForRole("worker"),
1021
+ transcript,
1022
+ cwd: squadCwd
1023
+ });
1024
+ if (output.startsWith("[ERROR]")) {
1025
+ process.stderr.write(` [WARN] Worker ${workers[0].name} errored: ${output}
1026
+ `);
1027
+ }
1028
+ addTurn(transcript, workers[0].name, "worker", output, estimateTurnCost(options.model || "sonnet"));
1029
+ } else if (workers.length > 1) {
1030
+ log(`Turns ${transcript.turns.length + 1}-${transcript.turns.length + workers.length}: ${workers.map((w) => w.name).join(", ")} (workers, parallel)`);
1031
+ const workerPromises = workers.map(
1032
+ (worker) => executeAgentTurnAsync({
1033
+ agentName: worker.name,
1034
+ agentPath: worker.path,
1035
+ role: "worker",
1036
+ squadName: squad.name,
1037
+ model: options.model || modelForRole("worker"),
1038
+ transcript,
1039
+ // snapshot — all workers see same context
1040
+ cwd: squadCwd
1041
+ }).then((output) => ({ agent: worker, output }))
1042
+ );
1043
+ const workerResults = await Promise.all(workerPromises);
1044
+ for (const { agent, output } of workerResults) {
1045
+ if (output.startsWith("[ERROR]")) {
1046
+ process.stderr.write(` [WARN] Worker ${agent.name} errored: ${output}
1047
+ `);
1048
+ }
1049
+ addTurn(transcript, agent.name, "worker", output, estimateTurnCost(options.model || "sonnet"));
1050
+ }
1051
+ }
1052
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
1053
+ if (conv.converged) {
1054
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
1055
+ }
1056
+ log(`Turn ${transcript.turns.length + 1}: ${lead.name} (lead review)`);
1057
+ const reviewOutput = executeAgentTurn({
1058
+ agentName: lead.name,
1059
+ agentPath: lead.path,
1060
+ role: "lead",
1061
+ squadName: squad.name,
1062
+ model: options.model || modelForRole("lead"),
1063
+ transcript,
1064
+ cwd: squadCwd
1065
+ });
1066
+ addTurn(transcript, lead.name, "lead", reviewOutput, estimateTurnCost(options.model || "sonnet"));
1067
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
1068
+ if (conv.converged) {
1069
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
1070
+ }
1071
+ if (verifiers.length === 1) {
1072
+ log(`Turn ${transcript.turns.length + 1}: ${verifiers[0].name} (verifier)`);
1073
+ const output = executeAgentTurn({
1074
+ agentName: verifiers[0].name,
1075
+ agentPath: verifiers[0].path,
1076
+ role: "verifier",
1077
+ squadName: squad.name,
1078
+ model: options.model || modelForRole("verifier"),
1079
+ transcript,
1080
+ cwd: squadCwd
1081
+ });
1082
+ addTurn(transcript, verifiers[0].name, "verifier", output, estimateTurnCost(options.model || "haiku"));
1083
+ } else if (verifiers.length > 1) {
1084
+ log(`Turns ${transcript.turns.length + 1}-${transcript.turns.length + verifiers.length}: ${verifiers.map((v) => v.name).join(", ")} (verifiers, parallel)`);
1085
+ const verifierPromises = verifiers.map(
1086
+ (verifier) => executeAgentTurnAsync({
1087
+ agentName: verifier.name,
1088
+ agentPath: verifier.path,
1089
+ role: "verifier",
1090
+ squadName: squad.name,
1091
+ model: options.model || modelForRole("verifier"),
1092
+ transcript,
1093
+ cwd: squadCwd
1094
+ }).then((output) => ({ agent: verifier, output }))
1095
+ );
1096
+ const verifierResults = await Promise.all(verifierPromises);
1097
+ for (const { agent, output } of verifierResults) {
1098
+ addTurn(transcript, agent.name, "verifier", output, estimateTurnCost(options.model || "haiku"));
1099
+ }
1100
+ }
1101
+ if (verifiers.length > 0) {
1102
+ conv = detectConvergence(transcript, maxTurns, costCeiling);
1103
+ if (conv.converged) {
1104
+ return { transcript, turnCount: transcript.turns.length, totalCost: transcript.totalCost, converged: true, reason: conv.reason };
1105
+ }
1106
+ }
1107
+ }
1108
+ return {
1109
+ transcript,
1110
+ turnCount: transcript.turns.length,
1111
+ totalCost: transcript.totalCost,
1112
+ converged: false,
1113
+ reason: `Max cycles reached (${MAX_CYCLES})`
1114
+ };
1115
+ }
1116
+ function saveTranscript(transcript) {
1117
+ const squadsDir = findSquadsDir();
1118
+ if (!squadsDir) return null;
1119
+ const convDir = join2(squadsDir, "..", "conversations", transcript.squad);
1120
+ if (!existsSync2(convDir)) {
1121
+ mkdirSync(convDir, { recursive: true });
1122
+ }
1123
+ const id = Date.now().toString(36);
1124
+ const filePath = join2(convDir, `${id}.md`);
1125
+ const lines = [
1126
+ `# Conversation: ${transcript.squad}`,
1127
+ `Started: ${transcript.startedAt}`,
1128
+ `Turns: ${transcript.turns.length}`,
1129
+ `Estimated cost: $${transcript.totalCost.toFixed(2)}`,
1130
+ "",
1131
+ "---",
1132
+ ""
1133
+ ];
1134
+ for (const turn of transcript.turns) {
1135
+ lines.push(`## ${turn.agent} (${turn.role}) \u2014 ${turn.timestamp}`);
1136
+ lines.push("");
1137
+ lines.push(turn.content);
1138
+ lines.push("");
1139
+ lines.push("---");
1140
+ lines.push("");
1141
+ }
1142
+ writeFileSync(filePath, lines.join("\n"));
1143
+ return filePath;
1144
+ }
1145
+
1146
+ // src/lib/api-client.ts
1147
+ var API_TIMEOUT_MS = 5e3;
1148
+ function getApiConfig() {
1149
+ const session = loadSession();
1150
+ if (!session?.accessToken || session.status !== "active") return null;
1151
+ const apiUrl = getApiUrl();
1152
+ return { apiUrl, token: session.accessToken };
1153
+ }
1154
+ async function apiRequest(path, method, body) {
1155
+ const config = getApiConfig();
1156
+ if (!config) return false;
1157
+ try {
1158
+ const response = await fetch(`${config.apiUrl}${path}`, {
1159
+ method,
1160
+ headers: {
1161
+ "Content-Type": "application/json",
1162
+ Authorization: `Bearer ${config.token}`
1163
+ },
1164
+ body: JSON.stringify(body),
1165
+ signal: AbortSignal.timeout(API_TIMEOUT_MS)
1166
+ });
1167
+ return response.ok;
1168
+ } catch {
1169
+ return false;
1170
+ }
1171
+ }
1172
+ async function reportExecutionStart(squad, agent, executionId, metadata) {
1173
+ const config = getApiConfig();
1174
+ if (!config) return null;
1175
+ try {
1176
+ const response = await fetch(`${config.apiUrl}/agent-executions`, {
1177
+ method: "POST",
1178
+ headers: {
1179
+ "Content-Type": "application/json",
1180
+ Authorization: `Bearer ${config.token}`
1181
+ },
1182
+ body: JSON.stringify({
1183
+ squad,
1184
+ agent,
1185
+ executor: "cli",
1186
+ brief: metadata?.brief,
1187
+ model: metadata?.model,
1188
+ metadata: {
1189
+ local_execution_id: executionId,
1190
+ trigger: metadata?.trigger || "manual"
1191
+ }
1192
+ }),
1193
+ signal: AbortSignal.timeout(API_TIMEOUT_MS)
1194
+ });
1195
+ if (!response.ok) return null;
1196
+ const data = await response.json();
1197
+ return data.execution_id;
1198
+ } catch {
1199
+ return null;
1200
+ }
1201
+ }
1202
+ async function reportConversationResult(executionId, result) {
1203
+ return apiRequest(`/agent-executions/${executionId}`, "PATCH", {
1204
+ status: result.converged ? "completed" : "stopped",
1205
+ summary: `${result.converged ? "Converged" : "Stopped"}: ${result.reason}`,
1206
+ cost_usd: result.totalCost,
1207
+ extra_data: {
1208
+ conversation: {
1209
+ turn_count: result.turnCount,
1210
+ total_cost: result.totalCost,
1211
+ converged: result.converged,
1212
+ reason: result.reason,
1213
+ agents_involved: result.agentsInvolved
1214
+ }
1215
+ }
1216
+ });
1217
+ }
1218
+ async function pushCognitionSignal(signal) {
1219
+ return apiRequest("/cognition/signals", "POST", signal);
1220
+ }
1221
+ async function ingestMemorySignal(body) {
1222
+ const config = getApiConfig();
1223
+ if (!config) return null;
1224
+ try {
1225
+ const response = await fetch(`${config.apiUrl}/cognition/signals/ingest-memory`, {
1226
+ method: "POST",
1227
+ headers: {
1228
+ "Content-Type": "application/json",
1229
+ Authorization: `Bearer ${config.token}`
1230
+ },
1231
+ body: JSON.stringify(body),
1232
+ signal: AbortSignal.timeout(API_TIMEOUT_MS)
1233
+ });
1234
+ if (!response.ok) return null;
1235
+ return await response.json();
1236
+ } catch {
1237
+ return null;
1238
+ }
1239
+ }
1240
+
1241
+ // src/lib/squad-loop.ts
1242
+ import { createHash } from "crypto";
1243
+ import { execSync as execSync2 } from "child_process";
1244
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
1245
+ import { join as join3 } from "path";
1246
+ import { homedir } from "os";
1247
+ var MIN_PHANTOM_DURATION_MS = 30 * 1e3;
1248
+ var INGESTIBLE_FILES = ["state", "learnings", "executions"];
1249
+ var STATE_DIR = join3(homedir(), ".squads", "daemon");
1250
+ var STATE_FILE = join3(STATE_DIR, "state.json");
1251
+ function defaultState() {
1252
+ return {
1253
+ lastCycle: "",
1254
+ dailyCost: 0,
1255
+ dailyCostDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
1256
+ recentRuns: [],
1257
+ failCounts: {},
1258
+ memoryHashes: {},
1259
+ cooldowns: {}
1260
+ };
1261
+ }
1262
+ function loadLoopState() {
1263
+ if (!existsSync3(STATE_DIR)) mkdirSync2(STATE_DIR, { recursive: true });
1264
+ if (!existsSync3(STATE_FILE)) return defaultState();
1265
+ try {
1266
+ const raw = JSON.parse(readFileSync2(STATE_FILE, "utf-8"));
1267
+ if (!raw.cooldowns) raw.cooldowns = {};
1268
+ if (!raw.memoryHashes) raw.memoryHashes = {};
1269
+ return raw;
1270
+ } catch {
1271
+ return defaultState();
1272
+ }
1273
+ }
1274
+ function saveLoopState(state) {
1275
+ if (!existsSync3(STATE_DIR)) mkdirSync2(STATE_DIR, { recursive: true });
1276
+ writeFileSync2(STATE_FILE, JSON.stringify(state, null, 2));
1277
+ }
1278
+ function classifyRunOutcome(exitCode, durationMs) {
1279
+ if (exitCode !== 0) return "failed";
1280
+ if (durationMs < MIN_PHANTOM_DURATION_MS) return "skipped";
1281
+ return "completed";
1282
+ }
1283
+ function checkCooldown(state, squad, agentType, cooldownMs) {
1284
+ const key = `${squad}:${agentType}`;
1285
+ const lastDispatch = state.cooldowns[key];
1286
+ if (lastDispatch === void 0) return true;
1287
+ return Date.now() - lastDispatch >= cooldownMs;
1288
+ }
1289
+ function getOpenIssues(repo, ghEnv = {}) {
1290
+ try {
1291
+ const raw = execSync2(
1292
+ `gh issue list -R ${repo} --state open --json number,title,labels --limit 20`,
1293
+ { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, ...ghEnv } }
1294
+ );
1295
+ const issues = JSON.parse(raw);
1296
+ return issues.map((i) => ({
1297
+ number: i.number,
1298
+ title: i.title,
1299
+ labels: i.labels.map((l) => l.name),
1300
+ repo
1301
+ }));
1302
+ } catch {
1303
+ return [];
1304
+ }
1305
+ }
1306
+ function getLastRunAge(squad, agent) {
1307
+ const memDir = findMemoryDir();
1308
+ if (!memDir) return null;
1309
+ const execPath = join3(memDir, squad, agent, "executions.md");
1310
+ if (!existsSync3(execPath)) return null;
1311
+ try {
1312
+ const content = readFileSync2(execPath, "utf-8");
1313
+ const timestamps = content.match(/\*\*(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\*\*/g);
1314
+ if (!timestamps || timestamps.length === 0) return null;
1315
+ const last = timestamps[timestamps.length - 1].replace(/\*\*/g, "");
1316
+ const lastDate = new Date(last);
1317
+ return Date.now() - lastDate.getTime();
1318
+ } catch {
1319
+ return null;
1320
+ }
1321
+ }
1322
+ function hasUnresolvedEscalation(repo, ghEnv = {}) {
1323
+ try {
1324
+ const raw = execSync2(
1325
+ `gh issue list -R ${repo} --label "blocked" --state open --json number,title --limit 1`,
1326
+ { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, ...ghEnv } }
1327
+ );
1328
+ const issues = JSON.parse(raw);
1329
+ if (issues.length > 0) {
1330
+ return { blocked: true, issue: issues[0] };
1331
+ }
1332
+ const raw2 = execSync2(
1333
+ `gh issue list -R ${repo} --label "needs-human" --state open --json number,title --limit 1`,
1334
+ { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, ...ghEnv } }
1335
+ );
1336
+ const issues2 = JSON.parse(raw2);
1337
+ if (issues2.length > 0) {
1338
+ return { blocked: true, issue: issues2[0] };
1339
+ }
1340
+ return { blocked: false };
1341
+ } catch {
1342
+ return { blocked: false };
1343
+ }
1344
+ }
1345
+ function getSquadRepos() {
1346
+ const repos = {};
1347
+ const squadsDir = findSquadsDir();
1348
+ if (!squadsDir) return repos;
1349
+ try {
1350
+ const squads = listSquads(squadsDir);
1351
+ for (const squad of squads) {
1352
+ const squadMd = join3(squadsDir, squad, "SQUAD.md");
1353
+ if (!existsSync3(squadMd)) continue;
1354
+ const content = readFileSync2(squadMd, "utf-8");
1355
+ const repoMatch = content.match(/^repo:\s*(.+)/m);
1356
+ if (repoMatch) {
1357
+ repos[squad] = repoMatch[1].trim();
1358
+ }
1359
+ }
1360
+ } catch {
1361
+ }
1362
+ return repos;
1363
+ }
1364
+ function scoreSquads(state, squadRepos, ghEnv = {}) {
1365
+ const signals = [];
1366
+ const squadsDir = findSquadsDir();
1367
+ if (!squadsDir) return signals;
1368
+ let squads;
1369
+ try {
1370
+ squads = listSquads(squadsDir);
1371
+ } catch {
1372
+ return signals;
1373
+ }
1374
+ for (const squadName of squads) {
1375
+ try {
1376
+ const repo = squadRepos[squadName];
1377
+ if (repo) {
1378
+ const escalation = hasUnresolvedEscalation(repo, ghEnv);
1379
+ if (escalation.blocked) {
1380
+ signals.push({
1381
+ squad: squadName,
1382
+ score: 0,
1383
+ reason: `PAUSED: unresolved escalation #${escalation.issue?.number} \u2014 ${escalation.issue?.title}`,
1384
+ issues: []
1385
+ });
1386
+ continue;
1387
+ }
1388
+ }
1389
+ const issues = repo ? getOpenIssues(repo, ghEnv) : [];
1390
+ let score = 0;
1391
+ let reason = "";
1392
+ const CONVERSATION_ISSUE_THRESHOLD = 3;
1393
+ const CONVERSATION_COOLDOWN_MS = 48 * 60 * 60 * 1e3;
1394
+ const lastConvAge = getLastRunAge(squadName, "conversation");
1395
+ const conversationStale = lastConvAge === null || lastConvAge > CONVERSATION_COOLDOWN_MS;
1396
+ const useConversation = issues.length >= CONVERSATION_ISSUE_THRESHOLD && conversationStale;
1397
+ const targetAgent = useConversation ? void 0 : "issue-solver";
1398
+ if (repo) {
1399
+ const p0Issues = issues.filter(
1400
+ (i) => i.labels.some((l) => l.includes("P0") || l.includes("priority:P0"))
1401
+ );
1402
+ const p1Issues = issues.filter(
1403
+ (i) => i.labels.some((l) => l.includes("P1") || l.includes("priority:P1"))
1404
+ );
1405
+ if (p0Issues.length > 0) {
1406
+ score += 80;
1407
+ reason = `${p0Issues.length} P0 issues: ${p0Issues[0].title}`;
1408
+ } else if (p1Issues.length > 0) {
1409
+ score += 60;
1410
+ reason = `${p1Issues.length} P1 issues: ${p1Issues[0].title}`;
1411
+ } else if (issues.length > 0) {
1412
+ score += 30;
1413
+ reason = `${issues.length} open issues`;
1414
+ }
1415
+ } else {
1416
+ reason = "no repo configured \u2014 staleness-based dispatch";
1417
+ }
1418
+ if (useConversation) {
1419
+ score += 10;
1420
+ reason += " \u2192 conversation mode";
1421
+ }
1422
+ const agentForStaleness = targetAgent ?? "conversation";
1423
+ const lastAge = getLastRunAge(squadName, agentForStaleness);
1424
+ if (lastAge !== null) {
1425
+ const hoursAgo = lastAge / (1e3 * 60 * 60);
1426
+ if (hoursAgo > 48) {
1427
+ score += 20;
1428
+ reason += ` (stale: ${Math.floor(hoursAgo)}h since last run)`;
1429
+ } else if (hoursAgo > 24) {
1430
+ score += 10;
1431
+ reason += ` (${Math.floor(hoursAgo)}h since last run)`;
1432
+ } else if (hoursAgo < 2) {
1433
+ score -= 30;
1434
+ reason += ` (ran ${Math.floor(hoursAgo * 60)}m ago)`;
1435
+ }
1436
+ } else if (!repo) {
1437
+ score += 15;
1438
+ reason += " (never run)";
1439
+ }
1440
+ const failKey = `${squadName}:${agentForStaleness}`;
1441
+ const failures = state.failCounts[failKey] || 0;
1442
+ if (failures >= 3) {
1443
+ score -= 40;
1444
+ reason += ` (${failures} consecutive failures \u2014 needs human)`;
1445
+ } else if (failures >= 1) {
1446
+ score -= 10 * failures;
1447
+ }
1448
+ const outcomeModifier = getOutcomeScoreModifier(squadName, agentForStaleness);
1449
+ if (outcomeModifier !== 0) {
1450
+ score += outcomeModifier;
1451
+ reason += ` (outcome: ${outcomeModifier > 0 ? "+" : ""}${outcomeModifier})`;
1452
+ }
1453
+ if (score > 0 && (issues.length > 0 || !repo)) {
1454
+ signals.push({ squad: squadName, score, reason, agent: targetAgent, issues });
1455
+ }
1456
+ } catch {
1457
+ continue;
1458
+ }
1459
+ }
1460
+ signals.sort((a, b) => b.score - a.score);
1461
+ return signals;
1462
+ }
1463
+ async function slackNotify(message) {
1464
+ try {
1465
+ const envPath = join3(homedir(), "agents-squads", "hq", ".env");
1466
+ if (!existsSync3(envPath)) return;
1467
+ const env = readFileSync2(envPath, "utf-8");
1468
+ const tokenMatch = env.match(/SLACK_BOT_TOKEN=(.+)/);
1469
+ if (!tokenMatch) return;
1470
+ const token = tokenMatch[1].trim();
1471
+ const founderId = "U0A6NQ3U0JG";
1472
+ await fetch("https://slack.com/api/chat.postMessage", {
1473
+ method: "POST",
1474
+ headers: {
1475
+ "Authorization": `Bearer ${token}`,
1476
+ "Content-Type": "application/json"
1477
+ },
1478
+ body: JSON.stringify({ channel: founderId, text: message }),
1479
+ signal: AbortSignal.timeout(1e4)
1480
+ });
1481
+ } catch {
1482
+ }
1483
+ }
1484
+ async function pushMemorySignals(squads, state, verbose) {
1485
+ const memDir = findMemoryDir();
1486
+ if (!memDir) return;
1487
+ if (!state.memoryHashes) {
1488
+ state.memoryHashes = {};
1489
+ }
1490
+ const promises = [];
1491
+ for (const squad of squads) {
1492
+ const squadPath = join3(memDir, squad);
1493
+ if (!existsSync3(squadPath)) continue;
1494
+ let agents;
1495
+ try {
1496
+ agents = readdirSync2(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
1497
+ } catch {
1498
+ continue;
1499
+ }
1500
+ for (const agent of agents) {
1501
+ for (const fileType of INGESTIBLE_FILES) {
1502
+ const filePath = join3(squadPath, agent, `${fileType}.md`);
1503
+ if (!existsSync3(filePath)) continue;
1504
+ let content;
1505
+ try {
1506
+ content = readFileSync2(filePath, "utf-8");
1507
+ } catch {
1508
+ continue;
1509
+ }
1510
+ if (!content.trim()) continue;
1511
+ const hash = createHash("sha256").update(content).digest("hex").slice(0, 16);
1512
+ const key = `${squad}/${agent}/${fileType}`;
1513
+ if (state.memoryHashes[key] === hash) continue;
1514
+ const p = ingestMemorySignal({
1515
+ squad,
1516
+ agent,
1517
+ file_type: fileType,
1518
+ content,
1519
+ content_hash: hash
1520
+ }).then((result) => {
1521
+ if (result) {
1522
+ state.memoryHashes[key] = hash;
1523
+ if (verbose && result.status === "ingested") {
1524
+ writeLine(` ${colors.dim}Memory: ${key} \u2192 ${result.signals_created || 0} signals${RESET}`);
1525
+ }
1526
+ }
1527
+ }).catch(() => {
1528
+ });
1529
+ promises.push(p);
1530
+ }
1531
+ }
1532
+ }
1533
+ if (promises.length > 0) {
1534
+ await Promise.race([
1535
+ Promise.allSettled(promises),
1536
+ new Promise((resolve) => setTimeout(resolve, 1e4))
1537
+ ]);
1538
+ }
1539
+ }
1540
+ function computePhases(squadNames) {
1541
+ const squadsDir = findSquadsDir();
1542
+ if (!squadsDir) return /* @__PURE__ */ new Map([[0, squadNames || []]]);
1543
+ const names = squadNames || listSquads(squadsDir);
1544
+ const deps = /* @__PURE__ */ new Map();
1545
+ const starSquads = [];
1546
+ for (const name of names) {
1547
+ const squad = loadSquad(name);
1548
+ if (!squad) continue;
1549
+ if (squad.depends_on && squad.depends_on.length === 1 && squad.depends_on[0] === "*") {
1550
+ starSquads.push(name);
1551
+ continue;
1552
+ }
1553
+ const validDeps = (squad.depends_on || []).filter((d) => names.includes(d));
1554
+ if (squad.depends_on) {
1555
+ const invalid = squad.depends_on.filter((d) => d !== "*" && !names.includes(d));
1556
+ if (invalid.length > 0) {
1557
+ writeLine(` ${colors.dim}warn: ${name} depends_on unknown squads: ${invalid.join(", ")}${RESET}`);
1558
+ }
1559
+ }
1560
+ deps.set(name, validDeps);
1561
+ }
1562
+ const inDegree = /* @__PURE__ */ new Map();
1563
+ const adjList = /* @__PURE__ */ new Map();
1564
+ for (const [squad, squadDeps] of deps) {
1565
+ if (!inDegree.has(squad)) inDegree.set(squad, 0);
1566
+ for (const dep of squadDeps) {
1567
+ if (!adjList.has(dep)) adjList.set(dep, []);
1568
+ adjList.get(dep).push(squad);
1569
+ inDegree.set(squad, (inDegree.get(squad) || 0) + 1);
1570
+ if (!inDegree.has(dep)) inDegree.set(dep, 0);
1571
+ }
1572
+ }
1573
+ for (const [squad] of deps) {
1574
+ if (!inDegree.has(squad)) inDegree.set(squad, 0);
1575
+ }
1576
+ const phases = /* @__PURE__ */ new Map();
1577
+ let phase = 0;
1578
+ const processed = /* @__PURE__ */ new Set();
1579
+ const remaining = /* @__PURE__ */ new Set([...deps.keys()]);
1580
+ while (remaining.size > 0) {
1581
+ const ready = [];
1582
+ for (const squad of remaining) {
1583
+ if ((inDegree.get(squad) || 0) <= 0) {
1584
+ ready.push(squad);
1585
+ }
1586
+ }
1587
+ if (ready.length === 0) {
1588
+ const cycled = [...remaining];
1589
+ if (!phases.has(phase)) phases.set(phase, []);
1590
+ phases.get(phase).push(...cycled);
1591
+ for (const s of cycled) processed.add(s);
1592
+ break;
1593
+ }
1594
+ phases.set(phase, ready);
1595
+ for (const squad of ready) {
1596
+ processed.add(squad);
1597
+ remaining.delete(squad);
1598
+ for (const dependent of adjList.get(squad) || []) {
1599
+ inDegree.set(dependent, (inDegree.get(dependent) || 0) - 1);
1600
+ }
1601
+ }
1602
+ phase++;
1603
+ }
1604
+ if (starSquads.length > 0) {
1605
+ phases.set(phase, starSquads);
1606
+ }
1607
+ return phases;
1608
+ }
1609
+ function scoreSquadsForPhase(phaseSquads, state, squadRepos, ghEnv) {
1610
+ const allSignals = scoreSquads(state, squadRepos, ghEnv);
1611
+ return allSignals.filter((s) => phaseSquads.includes(s.squad));
1612
+ }
1613
+
1614
+ // src/lib/cognition.ts
1615
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync3 } from "fs";
1616
+ import { join as join4 } from "path";
1617
+ import { createHash as createHash2 } from "crypto";
1618
+ import { spawnSync } from "child_process";
1619
+ var COGNITION_DIR_NAME = "cognition";
1620
+ var STATE_FILE2 = "state.json";
1621
+ var SYNTHESIZE_INTERVAL_MS = 30 * 60 * 1e3;
1622
+ var REFLECT_INTERVAL_MS = 4 * 60 * 60 * 1e3;
1623
+ var MAX_SIGNALS_KEPT = 500;
1624
+ var MAX_REFLECTIONS_KEPT = 50;
1625
+ var CONFIDENCE_PRIOR_WEIGHT = 0.7;
1626
+ var CONFIDENCE_EVIDENCE_WEIGHT = 0.3;
1627
+ var CONFIDENCE_MIN = 0.05;
1628
+ var CONFIDENCE_MAX = 0.95;
1629
+ var BELIEF_SHIFT_THRESHOLD = 0.15;
1630
+ var MAX_SUPPORTING_IDS = 20;
1631
+ var INGESTIBLE_FILES2 = ["state", "learnings", "executions"];
1632
+ var FILE_TYPE_MAPPING = {
1633
+ state: { source: "memory", signal_type: "state_update" },
1634
+ learnings: { source: "memory", signal_type: "learning" },
1635
+ executions: { source: "execution", signal_type: "execution_log" },
1636
+ events: { source: "market", signal_type: "external_event" },
1637
+ directives: { source: "execution", signal_type: "directive" }
1638
+ };
1639
+ function callClaude(prompt, model, timeoutMs) {
1640
+ const { CLAUDECODE: _, ANTHROPIC_API_KEY: _k, ...cleanEnv } = process.env;
1641
+ const result = spawnSync("claude", ["--print", "--model", model], {
1642
+ input: prompt,
1643
+ encoding: "utf-8",
1644
+ timeout: timeoutMs,
1645
+ env: cleanEnv,
1646
+ stdio: ["pipe", "pipe", "pipe"]
1647
+ });
1648
+ if (result.status !== 0 || !result.stdout) return null;
1649
+ return result.stdout;
1650
+ }
1651
+ function getCognitionDir() {
1652
+ const memDir = findMemoryDir();
1653
+ const dir = memDir ? join4(memDir, COGNITION_DIR_NAME) : join4(process.cwd(), ".agents", "memory", COGNITION_DIR_NAME);
1654
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
1655
+ return dir;
1656
+ }
1657
+ function defaultState2() {
1658
+ return {
1659
+ signals: [],
1660
+ beliefs: [],
1661
+ decisions: [],
1662
+ reflections: [],
1663
+ last_synthesize: null,
1664
+ last_reflect: null,
1665
+ next_signal_id: 1,
1666
+ next_decision_id: 1,
1667
+ next_reflection_id: 1,
1668
+ memory_hashes: {}
1669
+ };
1670
+ }
1671
+ function loadCognitionState() {
1672
+ const dir = getCognitionDir();
1673
+ const path = join4(dir, STATE_FILE2);
1674
+ if (!existsSync4(path)) return defaultState2();
1675
+ try {
1676
+ return JSON.parse(readFileSync3(path, "utf-8"));
1677
+ } catch {
1678
+ return defaultState2();
1679
+ }
1680
+ }
1681
+ function saveCognitionState(state) {
1682
+ const dir = getCognitionDir();
1683
+ writeFileSync3(join4(dir, STATE_FILE2), JSON.stringify(state, null, 2));
1684
+ }
1685
+ function addSignal(state, signal) {
1686
+ const newSignal = {
1687
+ ...signal,
1688
+ id: state.next_signal_id++,
1689
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1690
+ };
1691
+ state.signals.push(newSignal);
1692
+ if (state.signals.length > MAX_SIGNALS_KEPT) {
1693
+ state.signals = state.signals.slice(-MAX_SIGNALS_KEPT);
1694
+ }
1695
+ pushCognitionSignal({
1696
+ source: signal.source,
1697
+ signal_type: signal.signal_type,
1698
+ value: signal.value ?? void 0,
1699
+ unit: signal.unit ?? void 0,
1700
+ data: signal.data,
1701
+ entity_type: signal.entity_type ?? void 0,
1702
+ entity_id: signal.entity_id ?? void 0,
1703
+ confidence: signal.confidence
1704
+ });
1705
+ return newSignal;
1706
+ }
1707
+ function ingestMemoryFiles(state, squads, verbose = false) {
1708
+ const memDir = findMemoryDir();
1709
+ if (!memDir) return 0;
1710
+ let signalsCreated = 0;
1711
+ for (const squad of squads) {
1712
+ const squadPath = join4(memDir, squad);
1713
+ if (!existsSync4(squadPath)) continue;
1714
+ let agents;
1715
+ try {
1716
+ agents = readdirSync3(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== COGNITION_DIR_NAME).map((e) => e.name);
1717
+ } catch {
1718
+ continue;
1719
+ }
1720
+ for (const agent of agents) {
1721
+ for (const fileType of INGESTIBLE_FILES2) {
1722
+ const filePath = join4(squadPath, agent, `${fileType}.md`);
1723
+ if (!existsSync4(filePath)) continue;
1724
+ let content;
1725
+ try {
1726
+ content = readFileSync3(filePath, "utf-8");
1727
+ } catch {
1728
+ continue;
1729
+ }
1730
+ if (!content.trim()) continue;
1731
+ const hash = createHash2("sha256").update(content).digest("hex").slice(0, 16);
1732
+ const key = `${squad}/${agent}/${fileType}`;
1733
+ if (state.memory_hashes[key] === hash) continue;
1734
+ state.memory_hashes[key] = hash;
1735
+ const mapping = FILE_TYPE_MAPPING[fileType] || FILE_TYPE_MAPPING.state;
1736
+ const bullets = content.split("\n").filter((line) => line.trim().startsWith("- ") || line.trim().startsWith("* ")).map((line) => line.trim().replace(/^[-*]\s+/, "")).filter((line) => line.length > 10);
1737
+ if (bullets.length === 0) {
1738
+ addSignal(state, {
1739
+ source: mapping.source,
1740
+ signal_type: mapping.signal_type,
1741
+ value: null,
1742
+ unit: null,
1743
+ data: { content: content.slice(0, 500), content_hash: hash },
1744
+ entity_type: "memory_file",
1745
+ entity_id: key,
1746
+ confidence: 0.8
1747
+ });
1748
+ signalsCreated++;
1749
+ } else {
1750
+ for (const bullet of bullets.slice(0, 10)) {
1751
+ addSignal(state, {
1752
+ source: mapping.source,
1753
+ signal_type: mapping.signal_type,
1754
+ value: null,
1755
+ unit: null,
1756
+ data: { content: bullet, file: key, content_hash: hash },
1757
+ entity_type: "memory_file",
1758
+ entity_id: key,
1759
+ confidence: 0.8
1760
+ });
1761
+ signalsCreated++;
1762
+ }
1763
+ }
1764
+ ingestMemorySignal({ squad, agent, file_type: fileType, content, content_hash: hash });
1765
+ if (verbose) {
1766
+ writeLine(` ${colors.dim}Cognition: ${key} \u2192 ${bullets.length || 1} signals${RESET}`);
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ return signalsCreated;
1772
+ }
1773
+ async function synthesizeSignals(state, verbose = false) {
1774
+ if (state.beliefs.length === 0) return 0;
1775
+ const cutoff = state.last_synthesize ? new Date(state.last_synthesize).getTime() : 0;
1776
+ const recentSignals = state.signals.filter(
1777
+ (s) => new Date(s.created_at).getTime() > cutoff
1778
+ );
1779
+ if (recentSignals.length === 0) return 0;
1780
+ let beliefsUpdated = 0;
1781
+ for (const belief of state.beliefs) {
1782
+ const signalList = recentSignals.map((s, i) => `${i + 1}. [${s.source}] ${s.signal_type}${s.value !== null ? " = " + s.value : ""}${s.unit ? " " + s.unit : ""}: ${(s.data.content || "").slice(0, 100)}`).join("\n");
1783
+ const prompt = `Given this belief: "${belief.statement}"
1784
+
1785
+ Classify each signal as SUPPORTING or CONTRADICTING or NEUTRAL.
1786
+
1787
+ Signals:
1788
+ ${signalList}
1789
+
1790
+ Respond with JSON only: {"supporting": [indexes], "contradicting": [indexes], "neutral": [indexes]}`;
1791
+ try {
1792
+ const result = callClaude(prompt, "haiku", 3e4);
1793
+ if (!result) continue;
1794
+ const jsonMatch = result.match(/\{[\s\S]*\}/);
1795
+ if (!jsonMatch) continue;
1796
+ const classification = JSON.parse(jsonMatch[0]);
1797
+ const supportingCount = classification.supporting?.length || 0;
1798
+ const contradictingCount = classification.contradicting?.length || 0;
1799
+ if (supportingCount + contradictingCount === 0) continue;
1800
+ const supportingIds = (classification.supporting || []).map((i) => recentSignals[i - 1]?.id).filter((id) => id !== void 0);
1801
+ const contradictingIds = (classification.contradicting || []).map((i) => recentSignals[i - 1]?.id).filter((id) => id !== void 0);
1802
+ const oldConfidence = belief.confidence;
1803
+ const evidenceRatio = supportingCount / (supportingCount + contradictingCount);
1804
+ let newConfidence = oldConfidence * CONFIDENCE_PRIOR_WEIGHT + evidenceRatio * CONFIDENCE_EVIDENCE_WEIGHT;
1805
+ newConfidence = Math.max(CONFIDENCE_MIN, Math.min(CONFIDENCE_MAX, newConfidence));
1806
+ belief.confidence = newConfidence;
1807
+ belief.supporting_signals = [...belief.supporting_signals, ...supportingIds].slice(-MAX_SUPPORTING_IDS);
1808
+ belief.contradicting_signals = [...belief.contradicting_signals, ...contradictingIds].slice(-MAX_SUPPORTING_IDS);
1809
+ belief.revision++;
1810
+ belief.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1811
+ belief.temperature = "hot";
1812
+ beliefsUpdated++;
1813
+ const shift = Math.abs(newConfidence - oldConfidence);
1814
+ if (shift >= BELIEF_SHIFT_THRESHOLD) {
1815
+ const direction = newConfidence > oldConfidence ? "\u2191" : "\u2193";
1816
+ slackNotify(
1817
+ `*Belief shift* ${direction} ${belief.belief_key}: ${(oldConfidence * 100).toFixed(0)}% \u2192 ${(newConfidence * 100).toFixed(0)}%
1818
+ ${belief.statement}`
1819
+ );
1820
+ }
1821
+ if (verbose) {
1822
+ writeLine(` ${colors.dim}Belief: ${belief.belief_key} ${(oldConfidence * 100).toFixed(0)}% \u2192 ${(newConfidence * 100).toFixed(0)}% (+${supportingCount}/-${contradictingCount})${RESET}`);
1823
+ }
1824
+ } catch {
1825
+ continue;
1826
+ }
1827
+ }
1828
+ state.last_synthesize = (/* @__PURE__ */ new Date()).toISOString();
1829
+ return beliefsUpdated;
1830
+ }
1831
+ function evaluateDecisions(state) {
1832
+ const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1e3;
1833
+ let evaluated = 0;
1834
+ for (const decision of state.decisions) {
1835
+ if (decision.outcome_score !== null) continue;
1836
+ if (new Date(decision.decided_at).getTime() > twoHoursAgo) continue;
1837
+ const decisionTime = new Date(decision.decided_at).getTime();
1838
+ const relevantSignals = state.signals.filter(
1839
+ (s) => new Date(s.created_at).getTime() > decisionTime
1840
+ );
1841
+ const completed = relevantSignals.filter(
1842
+ (s) => s.signal_type === "agent_completed" || s.signal_type === "conversation_converged"
1843
+ ).length;
1844
+ const failed = relevantSignals.filter(
1845
+ (s) => s.signal_type === "agent_failed" || s.signal_type === "conversation_stopped"
1846
+ ).length;
1847
+ if (completed + failed < 3) continue;
1848
+ decision.outcome_score = completed / (completed + failed) * 2 - 1;
1849
+ decision.actual_outcome = { completed, failed, total: completed + failed };
1850
+ evaluated++;
1851
+ }
1852
+ return evaluated;
1853
+ }
1854
+ async function reflect(state, verbose = false) {
1855
+ if (state.last_reflect) {
1856
+ const elapsed = Date.now() - new Date(state.last_reflect).getTime();
1857
+ if (elapsed < REFLECT_INTERVAL_MS) return null;
1858
+ }
1859
+ const lastReflectTime = state.last_reflect ? new Date(state.last_reflect).getTime() : 0;
1860
+ const newSignals = state.signals.filter((s) => new Date(s.created_at).getTime() > lastReflectTime);
1861
+ if (newSignals.length === 0) return null;
1862
+ const beliefsText = state.beliefs.map((b) => `- [${b.domain}] ${b.belief_key} (${(b.confidence * 100).toFixed(0)}%, ${b.temperature}, r${b.revision}): ${b.statement}`).join("\n");
1863
+ const signalsText = newSignals.slice(-30).map((s) => `- [${s.source}] ${s.signal_type}: ${(s.data.content || "").slice(0, 80)} (${new Date(s.created_at).toLocaleTimeString()})`).join("\n");
1864
+ const decisionsText = state.decisions.map((d) => `- ${d.title} (score: ${d.outcome_score !== null ? d.outcome_score.toFixed(2) : "pending"})`).join("\n");
1865
+ const lastReflection = state.reflections.length > 0 ? state.reflections[state.reflections.length - 1] : null;
1866
+ const prompt = `You are the cognition engine for an AI-native company called Agents Squads.
1867
+ Your job is to reflect on the current state of the business and produce actionable insights.
1868
+
1869
+ ## Current Beliefs (world model)
1870
+ ${beliefsText || "(none)"}
1871
+
1872
+ ## Recent Signals (since last reflection)
1873
+ ${signalsText || "(none)"}
1874
+
1875
+ ## Decision Journal
1876
+ ${decisionsText || "(none)"}
1877
+
1878
+ ${lastReflection ? `Previous reflection (${lastReflection.created_at}):
1879
+ ${lastReflection.assessment}
1880
+ ` : ""}
1881
+
1882
+ ## Your Task
1883
+ Produce a business reflection. Respond as JSON only:
1884
+ {
1885
+ "assessment": "2-3 sentence summary of business state",
1886
+ "insights": [{"type": "highlight|warning|recommendation", "message": "..."}],
1887
+ "belief_updates": [{"belief_key": "...", "suggested_confidence": 0.X, "reason": "..."}],
1888
+ "priority_adjustments": [{"description": "...", "urgency": "high|medium|low"}],
1889
+ "founder_escalations": [{"issue": "...", "why_human_needed": "...", "suggested_action": "...", "urgency": "immediate|today|this_week"}]
1890
+ }`;
1891
+ try {
1892
+ const result = callClaude(prompt, "sonnet", 6e4);
1893
+ if (!result) return null;
1894
+ const jsonMatch = result.match(/\{[\s\S]*\}/);
1895
+ if (!jsonMatch) return null;
1896
+ const parsed = JSON.parse(jsonMatch[0]);
1897
+ const reflection = {
1898
+ id: state.next_reflection_id++,
1899
+ scope: "business",
1900
+ assessment: parsed.assessment || "",
1901
+ insights: parsed.insights || [],
1902
+ belief_updates: parsed.belief_updates || [],
1903
+ priority_adjustments: parsed.priority_adjustments || [],
1904
+ founder_escalations: parsed.founder_escalations || [],
1905
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1906
+ };
1907
+ state.reflections.push(reflection);
1908
+ if (state.reflections.length > MAX_REFLECTIONS_KEPT) {
1909
+ state.reflections = state.reflections.slice(-MAX_REFLECTIONS_KEPT);
1910
+ }
1911
+ for (const update of reflection.belief_updates) {
1912
+ const belief = state.beliefs.find((b) => b.belief_key === update.belief_key);
1913
+ if (belief && update.suggested_confidence >= 0 && update.suggested_confidence <= 1) {
1914
+ belief.confidence = update.suggested_confidence;
1915
+ belief.revision++;
1916
+ belief.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1917
+ belief.temperature = "hot";
1918
+ }
1919
+ }
1920
+ if (reflection.founder_escalations.length > 0) {
1921
+ const escalationText = reflection.founder_escalations.map((e) => `\u2022 *${e.issue}*: ${e.suggested_action} (${e.urgency})`).join("\n");
1922
+ slackNotify(`\u{1F9E0} *Cognition reflection*
1923
+ ${reflection.assessment}
1924
+
1925
+ *Escalations:*
1926
+ ${escalationText}`);
1927
+ } else if (verbose) {
1928
+ slackNotify(`\u{1F9E0} *Cognition reflection*
1929
+ ${reflection.assessment}`);
1930
+ }
1931
+ state.last_reflect = (/* @__PURE__ */ new Date()).toISOString();
1932
+ if (verbose) {
1933
+ writeLine(` ${colors.dim}Reflection: ${reflection.insights.length} insights, ${reflection.belief_updates.length} belief updates, ${reflection.founder_escalations.length} escalations${RESET}`);
1934
+ }
1935
+ return reflection;
1936
+ } catch {
1937
+ return null;
1938
+ }
1939
+ }
1940
+ function updateBeliefTemperatures(state) {
1941
+ const now = Date.now();
1942
+ for (const belief of state.beliefs) {
1943
+ const age = now - new Date(belief.updated_at).getTime();
1944
+ if (age < 4 * 60 * 60 * 1e3) {
1945
+ belief.temperature = "hot";
1946
+ } else if (age < 24 * 60 * 60 * 1e3) {
1947
+ belief.temperature = "warm";
1948
+ } else {
1949
+ belief.temperature = "cold";
1950
+ }
1951
+ }
1952
+ }
1953
+ async function runCognitionCycle(squads, verbose = false) {
1954
+ const state = loadCognitionState();
1955
+ const signalsIngested = ingestMemoryFiles(state, squads, verbose);
1956
+ let beliefsUpdated = 0;
1957
+ const timeSinceSynthesize = state.last_synthesize ? Date.now() - new Date(state.last_synthesize).getTime() : Infinity;
1958
+ if (timeSinceSynthesize >= SYNTHESIZE_INTERVAL_MS && state.signals.length > 0) {
1959
+ beliefsUpdated = await synthesizeSignals(state, verbose);
1960
+ }
1961
+ const decisionsEvaluated = evaluateDecisions(state);
1962
+ const reflection = await reflect(state, verbose);
1963
+ updateBeliefTemperatures(state);
1964
+ saveCognitionState(state);
1965
+ if (verbose || signalsIngested > 0 || beliefsUpdated > 0 || reflection) {
1966
+ writeLine(` ${colors.dim}Cognition: ${signalsIngested} signals, ${beliefsUpdated} beliefs updated${reflection ? ", reflected" : ""}${RESET}`);
1967
+ }
1968
+ return {
1969
+ signalsIngested,
1970
+ beliefsUpdated,
1971
+ decisionsEvaluated,
1972
+ reflected: !!reflection
1973
+ };
1974
+ }
1975
+ function seedBeliefsIfEmpty(state) {
1976
+ if (state.beliefs.length > 0) return;
1977
+ const seeds = [
1978
+ { belief_key: "retention_critical", domain: "product", statement: "D1 retention (10%) is the primary blocker to product-market fit. Must reach 30% before monetizing.", confidence: 0.9 },
1979
+ { belief_key: "cli_is_os", domain: "product", statement: "The CLI is our operating system. Every improvement multiplies autonomous capability.", confidence: 0.85 },
1980
+ { belief_key: "zero_revenue", domain: "revenue", statement: "Revenue is $0. Consulting is the near-term path. Pro tier gated on retention.", confidence: 0.95 },
1981
+ { belief_key: "agent_autonomy_low", domain: "operations", statement: "Agents run but do not think autonomously. Scanners and leads never fire. Intelligence loop is broken.", confidence: 0.8 },
1982
+ { belief_key: "first_run_broken", domain: "product", statement: "First-run experience is broken. v0.7.0 crashes on squads run. Users cannot complete the core flow.", confidence: 0.9 },
1983
+ { belief_key: "global_developer_focus", domain: "market", statement: "Target market is global developers, not Chilean enterprises. Product-first, not consulting-first.", confidence: 0.75 },
1984
+ { belief_key: "test_user_simulation", domain: "operations", statement: "Simulating test users (fresh install \u2192 init \u2192 run \u2192 evaluate friction) is the most effective way to find and fix retention blockers.", confidence: 0.7 },
1985
+ { belief_key: "cognition_engine_needed", domain: "operations", statement: "Without a working cognition engine, the organization cannot learn or improve autonomously. This is the difference between a cron job and intelligence.", confidence: 0.85 }
1986
+ ];
1987
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1988
+ for (const seed of seeds) {
1989
+ state.beliefs.push({
1990
+ ...seed,
1991
+ supporting_signals: [],
1992
+ contradicting_signals: [],
1993
+ temperature: "warm",
1994
+ revision: 1,
1995
+ updated_at: now
1996
+ });
1997
+ }
1998
+ }
1999
+
2000
+ // src/commands/run.ts
2001
+ var CLOUD_POLL_INTERVAL_MS = 3e3;
2002
+ var CLOUD_POLL_TIMEOUT_MS = 30 * 60 * 1e3;
2003
+ var DEFAULT_LEARNINGS_LIMIT = 5;
2004
+ var EXECUTION_EVENT_TIMEOUT_MS = 5e3;
2005
+ var VERIFICATION_STATE_MAX_CHARS = 2e3;
2006
+ var VERIFICATION_EXEC_TIMEOUT_MS = 3e4;
2007
+ var DRYRUN_DEF_MAX_CHARS = 500;
2008
+ var DRYRUN_CONTEXT_MAX_CHARS = 800;
2009
+ var DEFAULT_SCHEDULED_COOLDOWN_MS = 6 * 60 * 60 * 1e3;
2010
+ var DEFAULT_TIMEOUT_MINUTES = 30;
2011
+ var SOFT_DEADLINE_RATIO = 0.7;
2012
+ var LOG_FILE_INIT_DELAY_MS = 500;
2013
+ var VERBOSE_COMMAND_MAX_CHARS = 50;
2014
+ async function registerContextWithBridge(ctx) {
2015
+ const bridgeUrl = getBridgeUrl();
2016
+ try {
2017
+ const response = await fetch(`${bridgeUrl}/api/context/register`, {
2018
+ method: "POST",
2019
+ headers: { "Content-Type": "application/json" },
2020
+ body: JSON.stringify({
2021
+ execution_id: ctx.executionId,
2022
+ squad: ctx.squad,
2023
+ agent: ctx.agent,
2024
+ task_type: ctx.taskType,
2025
+ trigger: ctx.trigger
2026
+ }),
2027
+ signal: AbortSignal.timeout(3e3)
2028
+ });
2029
+ if (!response.ok) {
2030
+ return false;
2031
+ }
2032
+ return true;
2033
+ } catch (e) {
2034
+ writeLine(` ${colors.dim}warn: bridge registration failed: ${e instanceof Error ? e.message : String(e)}${RESET}`);
2035
+ return false;
2036
+ }
2037
+ }
2038
+ async function checkPreflightGates(squad, agent) {
2039
+ const bridgeUrl = getBridgeUrl();
2040
+ try {
2041
+ const response = await fetch(`${bridgeUrl}/api/execution/preflight`, {
2042
+ method: "POST",
2043
+ headers: { "Content-Type": "application/json" },
2044
+ body: JSON.stringify({ squad, agent }),
2045
+ signal: AbortSignal.timeout(3e3)
2046
+ });
2047
+ if (!response.ok) {
2048
+ return { allowed: true, gates: {} };
2049
+ }
2050
+ return await response.json();
2051
+ } catch (e) {
2052
+ writeLine(` ${colors.dim}warn: preflight gate check failed (allowing execution): ${e instanceof Error ? e.message : String(e)}${RESET}`);
2053
+ return { allowed: true, gates: {} };
2054
+ }
2055
+ }
2056
+ async function fetchLearnings(squad, limit = DEFAULT_LEARNINGS_LIMIT) {
2057
+ const bridgeUrl = getBridgeUrl();
2058
+ try {
2059
+ const response = await fetch(
2060
+ `${bridgeUrl}/api/learnings/relevant?squad=${encodeURIComponent(squad)}&limit=${limit}`,
2061
+ { signal: AbortSignal.timeout(3e3) }
2062
+ );
2063
+ if (!response.ok) {
2064
+ return [];
2065
+ }
2066
+ const data = await response.json();
2067
+ return data.learnings || [];
2068
+ } catch (e) {
2069
+ writeLine(` ${colors.dim}warn: learnings fetch failed: ${e instanceof Error ? e.message : String(e)}${RESET}`);
2070
+ return [];
2071
+ }
2072
+ }
2073
+ function generateExecutionId() {
2074
+ const timestamp = Date.now().toString(36);
2075
+ const random = Math.random().toString(36).substring(2, 8);
2076
+ return `exec_${timestamp}_${random}`;
2077
+ }
2078
+ function selectMcpConfig(squadName, squad) {
2079
+ if (squad?.context?.mcp && squad.context.mcp.length > 0) {
2080
+ return resolveMcpConfigPath(squadName, squad.context.mcp);
2081
+ }
2082
+ const home = process.env.HOME || "";
2083
+ const configsDir = join5(home, ".claude", "mcp-configs");
2084
+ const squadConfigs = {
2085
+ website: "website.json",
2086
+ research: "research.json",
2087
+ intelligence: "research.json",
2088
+ analytics: "data.json",
2089
+ engineering: "data.json"
2090
+ };
2091
+ const configFile = squadConfigs[squadName.toLowerCase()];
2092
+ if (configFile) {
2093
+ const configPath = join5(configsDir, configFile);
2094
+ if (existsSync5(configPath)) {
2095
+ return configPath;
2096
+ }
2097
+ }
2098
+ return "";
2099
+ }
2100
+ function detectTaskType(agentName) {
2101
+ const name = agentName.toLowerCase();
2102
+ if (name.includes("eval") || name.includes("critic") || name.includes("review") || name.includes("test")) {
2103
+ return "evaluation";
2104
+ }
2105
+ if (name.includes("lead") || name.includes("orchestrator")) {
2106
+ return "lead";
2107
+ }
2108
+ if (name.includes("research") || name.includes("analyst") || name.includes("intel")) {
2109
+ return "research";
2110
+ }
2111
+ return "execution";
2112
+ }
2113
+ function getClaudeModelAlias(model) {
2114
+ const lower = model.toLowerCase();
2115
+ if (lower === "opus" || lower === "sonnet" || lower === "haiku") {
2116
+ return lower;
2117
+ }
2118
+ if (lower.includes("opus")) return "opus";
2119
+ if (lower.includes("sonnet")) return "sonnet";
2120
+ if (lower.includes("haiku")) return "haiku";
2121
+ return void 0;
2122
+ }
2123
+ function resolveModel(explicitModel, squad, taskType) {
2124
+ if (explicitModel) {
2125
+ return explicitModel;
2126
+ }
2127
+ const modelConfig = squad?.context?.model;
2128
+ if (!modelConfig) {
2129
+ return void 0;
2130
+ }
2131
+ switch (taskType) {
2132
+ case "evaluation":
2133
+ return modelConfig.cheap || modelConfig.default;
2134
+ case "lead":
2135
+ return modelConfig.expensive || modelConfig.default;
2136
+ case "research":
2137
+ case "execution":
2138
+ default:
2139
+ return modelConfig.default;
2140
+ }
2141
+ }
2142
+ function ensureProjectTrusted(projectPath) {
2143
+ const configPath = join5(process.env.HOME || "", ".claude.json");
2144
+ if (!existsSync5(configPath)) {
2145
+ return;
2146
+ }
2147
+ try {
2148
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
2149
+ if (!config.projects) {
2150
+ config.projects = {};
2151
+ }
2152
+ if (!config.projects[projectPath]) {
2153
+ config.projects[projectPath] = {};
2154
+ }
2155
+ if (!config.projects[projectPath].hasTrustDialogAccepted) {
2156
+ config.projects[projectPath].hasTrustDialogAccepted = true;
2157
+ writeFileSync4(configPath, JSON.stringify(config, null, 2));
2158
+ }
2159
+ } catch (e) {
2160
+ writeLine(` ${colors.dim}warn: config update failed: ${e instanceof Error ? e.message : String(e)}${RESET}`);
2161
+ }
2162
+ }
2163
+ function getProjectRoot() {
2164
+ const squadsDir = findSquadsDir();
2165
+ if (squadsDir) {
2166
+ return dirname2(dirname2(squadsDir));
2167
+ }
2168
+ return process.cwd();
2169
+ }
2170
+ function getExecutionLogPath(squadName, agentName) {
2171
+ const memoryDir = findMemoryDir();
2172
+ if (!memoryDir) return null;
2173
+ return join5(memoryDir, squadName, agentName, "executions.md");
2174
+ }
2175
+ function logExecution(record) {
2176
+ const logPath = getExecutionLogPath(record.squadName, record.agentName);
2177
+ if (!logPath) return;
2178
+ const dir = dirname2(logPath);
2179
+ if (!existsSync5(dir)) {
2180
+ mkdirSync4(dir, { recursive: true });
2181
+ }
2182
+ let content = "";
2183
+ if (existsSync5(logPath)) {
2184
+ content = readFileSync4(logPath, "utf-8").trimEnd();
2185
+ } else {
2186
+ content = `# ${record.squadName}/${record.agentName} - Execution Log`;
2187
+ }
2188
+ const entry = `
2189
+
2190
+ ---
2191
+ <!-- exec:${record.executionId} -->
2192
+ **${record.startTime}** | Status: ${record.status}
2193
+ - ID: \`${record.executionId}\`
2194
+ - Trigger: ${record.trigger || "manual"}
2195
+ - Task Type: ${record.taskType || "execution"}
2196
+ `;
2197
+ writeFileSync4(logPath, content + entry);
2198
+ }
2199
+ function updateExecutionStatus(squadName, agentName, executionId, status, details) {
2200
+ const logPath = getExecutionLogPath(squadName, agentName);
2201
+ if (!logPath || !existsSync5(logPath)) return;
2202
+ let content = readFileSync4(logPath, "utf-8");
2203
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
2204
+ const execMarker = `<!-- exec:${executionId} -->`;
2205
+ const markerIndex = content.indexOf(execMarker);
2206
+ if (markerIndex === -1) return;
2207
+ const nextEntryIndex = content.indexOf("\n---\n", markerIndex + 1);
2208
+ const entryEnd = nextEntryIndex === -1 ? content.length : nextEntryIndex;
2209
+ const entryStart = content.lastIndexOf("\n---\n", markerIndex);
2210
+ const currentEntry = content.slice(entryStart, entryEnd);
2211
+ const durationStr = details?.durationMs ? `${(details.durationMs / 1e3).toFixed(1)}s` : "unknown";
2212
+ let updatedEntry = currentEntry.replace(/Status: running/, `Status: ${status}`) + `- Completed: ${endTime}
2213
+ - Duration: ${durationStr}`;
2214
+ if (details?.outcome) {
2215
+ updatedEntry += `
2216
+ - Outcome: ${details.outcome}`;
2217
+ }
2218
+ if (details?.error) {
2219
+ updatedEntry += `
2220
+ - Error: ${details.error}`;
2221
+ }
2222
+ content = content.slice(0, entryStart) + updatedEntry + content.slice(entryEnd);
2223
+ writeFileSync4(logPath, content);
2224
+ }
2225
+ async function autoCommitAgentWork(squadName, agentName, executionId, provider) {
2226
+ const { execSync: execSync4 } = await import("child_process");
2227
+ const { detectGitHubRepo } = await import("./github-UQTM5KMS.js");
2228
+ const projectRoot = getProjectRoot();
2229
+ try {
2230
+ const status = execSync4("git status --porcelain", {
2231
+ encoding: "utf-8",
2232
+ cwd: projectRoot
2233
+ }).trim();
2234
+ if (!status) {
2235
+ return { committed: false };
2236
+ }
2237
+ const botEnv = await getBotGitEnv();
2238
+ const execOpts = {
2239
+ cwd: projectRoot,
2240
+ env: { ...process.env, ...botEnv }
2241
+ };
2242
+ execSync4("git add -A", execOpts);
2243
+ const shortExecId = executionId.slice(0, 12);
2244
+ const coAuthor = getCoAuthorTrailer(provider || "claude");
2245
+ const msgFile = join5(projectRoot, ".git", "SQUADS_COMMIT_MSG");
2246
+ writeFileSync4(msgFile, `feat(${squadName}/${agentName}): execution ${shortExecId}
2247
+
2248
+ ${coAuthor}
2249
+ `);
2250
+ try {
2251
+ execSync4(`git commit --file "${msgFile}"`, execOpts);
2252
+ } finally {
2253
+ try {
2254
+ unlinkSync(msgFile);
2255
+ } catch {
2256
+ }
2257
+ }
2258
+ try {
2259
+ const { spawnSync: spawnSync2 } = await import("child_process");
2260
+ const repo = detectGitHubRepo(projectRoot);
2261
+ if (repo && /^[\w.-]+\/[\w.-]+$/.test(repo)) {
2262
+ const pushUrl = await getBotPushUrl(repo);
2263
+ if (pushUrl) {
2264
+ spawnSync2("git", ["push", pushUrl, "HEAD"], { ...execOpts, stdio: "pipe" });
2265
+ } else {
2266
+ spawnSync2("git", ["push", "origin", "HEAD"], { ...execOpts, stdio: "pipe" });
2267
+ }
2268
+ } else {
2269
+ spawnSync2("git", ["push", "origin", "HEAD"], { ...execOpts, stdio: "pipe" });
2270
+ }
2271
+ } catch (e) {
2272
+ writeLine(` ${colors.dim}warn: git push failed (commit is still local): ${e instanceof Error ? e.message : String(e)}${RESET}`);
2273
+ }
2274
+ return { committed: true, message: `Committed changes from ${agentName}` };
2275
+ } catch (error) {
2276
+ return { committed: false, error: String(error) };
2277
+ }
2278
+ }
2279
+ function getLastExecutionTime(squadName, agentName) {
2280
+ const logPath = getExecutionLogPath(squadName, agentName);
2281
+ if (!logPath || !existsSync5(logPath)) return null;
2282
+ const content = readFileSync4(logPath, "utf-8");
2283
+ const timestamps = content.match(/\*\*(\d{4}-\d{2}-\d{2}T[\d:.]+Z)\*\*/g);
2284
+ if (!timestamps || timestamps.length === 0) return null;
2285
+ const lastTimestamp = timestamps[timestamps.length - 1].replace(/\*\*/g, "");
2286
+ return new Date(lastTimestamp);
2287
+ }
2288
+ function checkLocalCooldown(squadName, agentName, cooldownMs) {
2289
+ const lastExec = getLastExecutionTime(squadName, agentName);
2290
+ if (!lastExec) return { ok: true, cooldownMs };
2291
+ const elapsedMs = Date.now() - lastExec.getTime();
2292
+ if (elapsedMs < cooldownMs) {
2293
+ return { ok: false, elapsedMs, cooldownMs };
2294
+ }
2295
+ return { ok: true, elapsedMs, cooldownMs };
2296
+ }
2297
+ function formatDuration(ms) {
2298
+ const hours = Math.floor(ms / (60 * 60 * 1e3));
2299
+ const minutes = Math.floor(ms % (60 * 60 * 1e3) / (60 * 1e3));
2300
+ if (hours >= 24) {
2301
+ const days = Math.floor(hours / 24);
2302
+ const remainingHours = hours % 24;
2303
+ return remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
2304
+ }
2305
+ if (hours > 0) {
2306
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
2307
+ }
2308
+ return `${minutes}m`;
2309
+ }
2310
+ async function emitExecutionEvent(eventType, data) {
2311
+ const apiUrl = getApiUrl();
2312
+ if (apiUrl) {
2313
+ try {
2314
+ await fetch(`${apiUrl}/events/ingest`, {
2315
+ method: "POST",
2316
+ headers: { "Content-Type": "application/json" },
2317
+ body: JSON.stringify({
2318
+ source: "scheduler",
2319
+ event_type: eventType,
2320
+ data: {
2321
+ squad: data.squad,
2322
+ agent: data.agent,
2323
+ execution_id: data.executionId,
2324
+ ...data.error ? { error: data.error } : {}
2325
+ }
2326
+ }),
2327
+ signal: AbortSignal.timeout(EXECUTION_EVENT_TIMEOUT_MS)
2328
+ });
2329
+ return;
2330
+ } catch {
2331
+ }
2332
+ }
2333
+ try {
2334
+ const memDir = findMemoryDir();
2335
+ if (!memDir) return;
2336
+ const eventsDir = join5(memDir, data.squad, data.agent);
2337
+ if (!existsSync5(eventsDir)) {
2338
+ mkdirSync4(eventsDir, { recursive: true });
2339
+ }
2340
+ const eventsPath = join5(eventsDir, "events.md");
2341
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2342
+ const entry = `
2343
+ ## ${timestamp}: ${eventType}
2344
+ - execution_id: ${data.executionId}
2345
+ ${data.error ? `- error: ${data.error}
2346
+ ` : ""}`;
2347
+ let existing = "";
2348
+ if (existsSync5(eventsPath)) {
2349
+ existing = readFileSync4(eventsPath, "utf-8");
2350
+ }
2351
+ writeFileSync4(eventsPath, existing + entry);
2352
+ } catch {
2353
+ }
2354
+ }
2355
+ async function verifyExecution(squadName, agentName, criteria, options = {}) {
2356
+ const { execSync: execSync4 } = await import("child_process");
2357
+ const projectRoot = getProjectRoot();
2358
+ let stateContent = "";
2359
+ const memDir = findMemoryDir();
2360
+ if (memDir) {
2361
+ const statePath = join5(memDir, squadName, agentName, "state.md");
2362
+ if (existsSync5(statePath)) {
2363
+ stateContent = readFileSync4(statePath, "utf-8").slice(0, VERIFICATION_STATE_MAX_CHARS);
2364
+ }
2365
+ }
2366
+ let recentCommits = "";
2367
+ try {
2368
+ recentCommits = execSync4("git log --oneline -5 --no-color", {
2369
+ encoding: "utf-8",
2370
+ cwd: projectRoot
2371
+ }).trim();
2372
+ } catch (e) {
2373
+ if (options.verbose) writeLine(` ${colors.dim}warn: git log failed: ${e instanceof Error ? e.message : String(e)}${RESET}`);
2374
+ recentCommits = "(no commits found)";
2375
+ }
2376
+ const verifyPrompt = `You are verifying whether an agent completed its task successfully.
2377
+
2378
+ Agent: ${squadName}/${agentName}
2379
+
2380
+ ## Acceptance Criteria
2381
+ ${criteria}
2382
+
2383
+ ## Evidence
2384
+
2385
+ ### Agent State File
2386
+ ${stateContent || "(empty or not found)"}
2387
+
2388
+ ### Recent Git Commits
2389
+ ${recentCommits}
2390
+
2391
+ ## Instructions
2392
+ Evaluate whether the acceptance criteria are met based on the evidence.
2393
+ Respond with EXACTLY one line:
2394
+ PASS: <brief reason>
2395
+ or
2396
+ FAIL: <brief reason>`;
2397
+ try {
2398
+ const escapedPrompt = verifyPrompt.replace(/'/g, "'\\''");
2399
+ const result = execSync4(
2400
+ `unset CLAUDECODE; claude --print --model haiku -- '${escapedPrompt}'`,
2401
+ { encoding: "utf-8", cwd: projectRoot, timeout: VERIFICATION_EXEC_TIMEOUT_MS, shell: "/bin/sh" }
2402
+ ).trim();
2403
+ if (options.verbose) {
2404
+ writeLine(` ${colors.dim}Verification: ${result}${RESET}`);
2405
+ }
2406
+ if (result.startsWith("PASS")) {
2407
+ return { passed: true, reason: result.replace(/^PASS:\s*/, "") };
2408
+ }
2409
+ return { passed: false, reason: result.replace(/^FAIL:\s*/, "") };
2410
+ } catch (error) {
2411
+ if (options.verbose) {
2412
+ writeLine(` ${colors.dim}Verification error (defaulting to PASS): ${error}${RESET}`);
2413
+ }
2414
+ return { passed: true, reason: "Verification unavailable \u2014 defaulting to pass" };
2415
+ }
2416
+ }
2417
+ async function runCloudDispatch(squadName, agentName, options) {
2418
+ const apiUrl = getApiUrl();
2419
+ if (!apiUrl) {
2420
+ writeLine(` ${colors.red}${icons.error} API URL not configured${RESET}`);
2421
+ writeLine(` ${colors.dim}Run: squads config use staging (or set SQUADS_API_URL)${RESET}`);
2422
+ process.exit(1);
2423
+ }
2424
+ if (!isLoggedIn()) {
2425
+ writeLine(` ${colors.red}${icons.error} Not logged in${RESET}`);
2426
+ writeLine(` ${colors.dim}Run \`squads login\` to authenticate before using --cloud${RESET}`);
2427
+ process.exit(1);
2428
+ }
2429
+ const session = loadSession();
2430
+ const headers = {
2431
+ "Content-Type": "application/json"
2432
+ };
2433
+ if (session?.accessToken) {
2434
+ headers["Authorization"] = `Bearer ${session.accessToken}`;
2435
+ }
2436
+ const apiKey = process.env.SQUADS_PLATFORM_API_TOKEN || process.env.SCHEDULER_API_KEY;
2437
+ if (apiKey) {
2438
+ headers["X-API-Key"] = apiKey;
2439
+ }
2440
+ const spinner = ora(`Dispatching ${squadName}/${agentName} to cloud...`).start();
2441
+ try {
2442
+ const dispatchRes = await fetch(`${apiUrl}/agent-dispatch`, {
2443
+ method: "POST",
2444
+ headers,
2445
+ body: JSON.stringify({
2446
+ squad: squadName,
2447
+ agent: agentName,
2448
+ trigger_type: "manual",
2449
+ trigger_data: {
2450
+ source: "cli",
2451
+ cloud: true,
2452
+ model: options.model,
2453
+ provider: options.provider,
2454
+ effort: options.effort
2455
+ }
2456
+ })
2457
+ });
2458
+ if (!dispatchRes.ok) {
2459
+ const error = await dispatchRes.text();
2460
+ spinner.fail(`Dispatch failed: ${dispatchRes.status}`);
2461
+ writeLine(` ${colors.dim}${error}${RESET}`);
2462
+ process.exit(1);
2463
+ }
2464
+ const dispatch = await dispatchRes.json();
2465
+ spinner.succeed(`Dispatched to cloud`);
2466
+ writeLine();
2467
+ writeLine(` ${colors.cyan}Dispatch ID${RESET} ${dispatch.dispatch_id}`);
2468
+ writeLine(` ${colors.cyan}Squad${RESET} ${squadName}`);
2469
+ writeLine(` ${colors.cyan}Agent${RESET} ${agentName}`);
2470
+ writeLine();
2471
+ const pollSpinner = ora("Waiting for execution to start...").start();
2472
+ const pollStart = Date.now();
2473
+ let executionId = null;
2474
+ let lastStatus = "";
2475
+ while (Date.now() - pollStart < CLOUD_POLL_TIMEOUT_MS) {
2476
+ try {
2477
+ const execRes = await fetch(
2478
+ `${apiUrl}/agent-executions?squad=${encodeURIComponent(squadName)}&agent=${encodeURIComponent(agentName)}&limit=1`,
2479
+ { headers }
2480
+ );
2481
+ if (execRes.ok) {
2482
+ const executions = await execRes.json();
2483
+ if (executions.length > 0) {
2484
+ const exec2 = executions[0];
2485
+ if (!executionId && exec2.status === "running") {
2486
+ executionId = exec2.execution_id;
2487
+ pollSpinner.text = `Running (${exec2.execution_id})`;
2488
+ }
2489
+ if (executionId && exec2.execution_id === executionId) {
2490
+ if (exec2.status !== lastStatus) {
2491
+ lastStatus = exec2.status;
2492
+ pollSpinner.text = `Status: ${exec2.status}`;
2493
+ }
2494
+ if (exec2.status === "completed") {
2495
+ pollSpinner.succeed("Execution completed");
2496
+ writeLine();
2497
+ writeLine(` ${colors.cyan}Execution${RESET} ${exec2.execution_id}`);
2498
+ if (exec2.summary) {
2499
+ writeLine(` ${colors.cyan}Summary${RESET} ${exec2.summary}`);
2500
+ }
2501
+ if (exec2.duration_seconds) {
2502
+ writeLine(` ${colors.cyan}Duration${RESET} ${Math.round(exec2.duration_seconds)}s`);
2503
+ }
2504
+ if (exec2.cost_usd) {
2505
+ writeLine(` ${colors.cyan}Cost${RESET} $${exec2.cost_usd.toFixed(4)}`);
2506
+ }
2507
+ writeLine();
2508
+ return;
2509
+ }
2510
+ if (exec2.status === "failed") {
2511
+ pollSpinner.fail("Execution failed");
2512
+ writeLine();
2513
+ if (exec2.error) {
2514
+ writeLine(` ${colors.red}Error: ${exec2.error}${RESET}`);
2515
+ }
2516
+ writeLine();
2517
+ process.exit(1);
2518
+ }
2519
+ if (exec2.status === "cancelled") {
2520
+ pollSpinner.warn("Execution cancelled");
2521
+ return;
2522
+ }
2523
+ }
2524
+ }
2525
+ }
2526
+ } catch (e) {
2527
+ if (options.verbose) writeLine(` ${colors.dim}warn: cloud poll failed (retrying): ${e instanceof Error ? e.message : String(e)}${RESET}`);
2528
+ }
2529
+ await new Promise((resolve) => setTimeout(resolve, CLOUD_POLL_INTERVAL_MS));
2530
+ }
2531
+ pollSpinner.warn("Poll timeout \u2014 execution may still be running");
2532
+ writeLine(` ${colors.dim}Check status: squads trigger status${RESET}`);
2533
+ if (executionId) {
2534
+ writeLine(` ${colors.dim}Execution ID: ${executionId}${RESET}`);
2535
+ }
2536
+ } catch (error) {
2537
+ spinner.fail("Cloud dispatch failed");
2538
+ writeLine(` ${colors.red}${error instanceof Error ? error.message : String(error)}${RESET}`);
2539
+ writeLine();
2540
+ writeLine(` ${colors.dim}Check your network and SQUADS_API_URL setting${RESET}`);
2541
+ process.exit(1);
2542
+ }
2543
+ }
2544
+ async function runCommand(target, options) {
2545
+ const squadsDir = findSquadsDir();
2546
+ if (!squadsDir) {
2547
+ writeLine(` ${colors.red}No .agents/squads directory found${RESET}`);
2548
+ writeLine(` ${colors.dim}Run \`squads init\` to create one.${RESET}`);
2549
+ process.exit(1);
2550
+ }
2551
+ if (!options.dryRun && options.execute === void 0) {
2552
+ options.execute = true;
2553
+ }
2554
+ if (!target) {
2555
+ await runAutopilot(squadsDir, options);
2556
+ return;
2557
+ }
2558
+ let squadName = target;
2559
+ let agentFromSlash;
2560
+ if (target.includes("/")) {
2561
+ const parts = target.split("/");
2562
+ squadName = parts[0];
2563
+ agentFromSlash = parts[1];
2564
+ if (!options.agent) {
2565
+ options.agent = agentFromSlash;
2566
+ }
2567
+ }
2568
+ if (options.cloud) {
2569
+ const agentName = options.agent || agentFromSlash;
2570
+ if (!agentName) {
2571
+ writeLine(` ${colors.red}${icons.error} --cloud requires a specific agent${RESET}`);
2572
+ writeLine(` ${colors.dim}Usage: squads run ${squadName} --cloud -a <agent>${RESET}`);
2573
+ writeLine(` ${colors.dim} or: squads run ${squadName}/<agent> --cloud${RESET}`);
2574
+ process.exit(1);
2575
+ }
2576
+ await track(Events.CLI_RUN, { type: "cloud", target: `${squadName}/${agentName}` });
2577
+ await flushEvents();
2578
+ await runCloudDispatch(squadName, agentName, options);
2579
+ return;
2580
+ }
2581
+ const squad = loadSquad(squadName);
2582
+ if (options.execute && !options.dryRun) {
2583
+ const provider = options.provider || squad?.providers?.default || "anthropic";
2584
+ const checksOk = await preflightExecutorCheck(provider);
2585
+ if (!checksOk) {
2586
+ process.exit(1);
2587
+ }
2588
+ }
2589
+ if (squad) {
2590
+ await track(Events.CLI_RUN, { type: "squad", target: squad.name });
2591
+ await flushEvents();
2592
+ await runSquad(squad, squadsDir, options);
2593
+ await runPostEvaluation([squad.name], options);
2594
+ } else {
2595
+ const agents = listAgents(squadsDir);
2596
+ const agent = agents.find((a) => a.name === target);
2597
+ if (agent && agent.filePath) {
2598
+ const pathParts = agent.filePath.split("/");
2599
+ const squadIdx = pathParts.indexOf("squads");
2600
+ const resolvedSquadName = squadIdx >= 0 ? pathParts[squadIdx + 1] : "unknown";
2601
+ await runAgent(agent.name, agent.filePath, resolvedSquadName, options);
2602
+ await runPostEvaluation([resolvedSquadName], options);
2603
+ } else {
2604
+ writeLine(` ${colors.red}Squad or agent "${target}" not found${RESET}`);
2605
+ const similar = findSimilarSquads(target, listSquads(squadsDir));
2606
+ if (similar.length > 0) {
2607
+ writeLine(` ${colors.dim}Did you mean: ${similar.join(", ")}?${RESET}`);
2608
+ }
2609
+ writeLine(` ${colors.dim}Run \`squads status\` to see available squads and agents.${RESET}`);
2610
+ process.exit(1);
2611
+ }
2612
+ }
2613
+ }
2614
+ async function runSquad(squad, squadsDir, options) {
2615
+ if (!squad) return;
2616
+ if (!options.effort && squad.effort) {
2617
+ options.effort = squad.effort;
2618
+ }
2619
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
2620
+ writeLine();
2621
+ writeLine(` ${gradient("squads")} ${colors.dim}run${RESET} ${colors.cyan}${squad.name}${RESET}`);
2622
+ writeLine();
2623
+ if (squad.mission) {
2624
+ writeLine(` ${colors.dim}${squad.mission}${RESET}`);
2625
+ writeLine();
2626
+ }
2627
+ writeLine(` ${colors.dim}Started: ${startTime}${RESET}`);
2628
+ writeLine();
2629
+ if (options.lead) {
2630
+ await runLeadMode(squad, squadsDir, options);
2631
+ return;
2632
+ }
2633
+ if (options.parallel) {
2634
+ const agentFiles = squad.agents.map((a) => ({
2635
+ name: a.name,
2636
+ path: join5(squadsDir, squad.dir, `${a.name}.md`)
2637
+ })).filter((a) => existsSync5(a.path));
2638
+ if (agentFiles.length === 0) {
2639
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
2640
+ return;
2641
+ }
2642
+ writeLine(` ${bold}Parallel execution${RESET} ${colors.dim}${agentFiles.length} agents${RESET}`);
2643
+ writeLine();
2644
+ if (!options.execute) {
2645
+ for (const agent of agentFiles) {
2646
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET}`);
2647
+ }
2648
+ writeLine();
2649
+ writeLine(` ${colors.dim}Launch all agents in parallel:${RESET}`);
2650
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --parallel`);
2651
+ writeLine();
2652
+ return;
2653
+ }
2654
+ writeLine(` ${gradient("Launching")} ${agentFiles.length} agents in parallel...`);
2655
+ writeLine();
2656
+ const launches = agentFiles.map(
2657
+ (agent) => runAgent(agent.name, agent.path, squad.dir, options)
2658
+ );
2659
+ await Promise.all(launches);
2660
+ writeLine();
2661
+ writeLine(` ${icons.success} All ${agentFiles.length} agents launched`);
2662
+ writeLine(` ${colors.dim}Monitor: tmux ls | grep squads-${squad.name}${RESET}`);
2663
+ writeLine(` ${colors.dim}Attach: tmux attach -t <session>${RESET}`);
2664
+ writeLine();
2665
+ return;
2666
+ }
2667
+ if (squad.pipelines.length > 0) {
2668
+ const pipeline = squad.pipelines[0];
2669
+ writeLine(` ${bold}Pipeline${RESET} ${colors.dim}${pipeline.agents.join(" \u2192 ")}${RESET}`);
2670
+ writeLine();
2671
+ for (let i = 0; i < pipeline.agents.length; i++) {
2672
+ const agentName = pipeline.agents[i];
2673
+ const agentPath = join5(squadsDir, squad.dir, `${agentName}.md`);
2674
+ if (existsSync5(agentPath)) {
2675
+ writeLine(` ${colors.dim}[${i + 1}/${pipeline.agents.length}]${RESET}`);
2676
+ await runAgent(agentName, agentPath, squad.dir, options);
2677
+ writeLine();
2678
+ } else {
2679
+ writeLine(` ${icons.warning} ${colors.yellow}Agent ${agentName} not found, skipping${RESET}`);
2680
+ }
2681
+ }
2682
+ } else {
2683
+ if (options.agent) {
2684
+ const agentPath = join5(squadsDir, squad.dir, `${options.agent}.md`);
2685
+ if (existsSync5(agentPath)) {
2686
+ await runAgent(options.agent, agentPath, squad.dir, options);
2687
+ } else {
2688
+ writeLine(` ${icons.error} ${colors.red}Agent ${options.agent} not found${RESET}`);
2689
+ return;
2690
+ }
2691
+ } else {
2692
+ if (options.execute) {
2693
+ writeLine(` ${bold}Conversation mode${RESET} ${colors.dim}(lead \u2192 scan \u2192 work \u2192 review \u2192 verify)${RESET}`);
2694
+ writeLine();
2695
+ const convOptions = {
2696
+ task: options.task,
2697
+ maxTurns: options.maxTurns,
2698
+ costCeiling: options.costCeiling,
2699
+ verbose: options.verbose,
2700
+ model: options.model
2701
+ };
2702
+ const apiExecId = await reportExecutionStart(squad.name, "conversation", `conv-${Date.now()}`);
2703
+ const result = await runConversation(squad, convOptions);
2704
+ const transcriptPath = saveTranscript(result.transcript);
2705
+ if (apiExecId) {
2706
+ reportConversationResult(apiExecId, {
2707
+ turnCount: result.turnCount,
2708
+ totalCost: result.totalCost,
2709
+ converged: result.converged,
2710
+ reason: result.reason,
2711
+ agentsInvolved: [...new Set(result.transcript.turns.map((t) => t.agent))]
2712
+ });
2713
+ }
2714
+ pushCognitionSignal({
2715
+ source: "execution",
2716
+ signal_type: result.converged ? "conversation_converged" : "conversation_stopped",
2717
+ value: result.totalCost,
2718
+ unit: "usd",
2719
+ data: {
2720
+ turn_count: result.turnCount,
2721
+ converged: result.converged,
2722
+ reason: result.reason,
2723
+ agents_involved: [...new Set(result.transcript.turns.map((t) => t.agent))]
2724
+ },
2725
+ entity_type: "squad",
2726
+ entity_id: squad.name,
2727
+ confidence: 0.9
2728
+ });
2729
+ writeLine();
2730
+ writeLine(` ${result.converged ? icons.success : icons.warning} ${result.converged ? "Converged" : "Stopped"}: ${result.reason}`);
2731
+ writeLine(` ${colors.dim}Turns: ${result.turnCount} | Cost: ~$${result.totalCost.toFixed(2)}${RESET}`);
2732
+ if (transcriptPath) {
2733
+ writeLine(` ${colors.dim}Transcript: ${transcriptPath}${RESET}`);
2734
+ }
2735
+ writeLine();
2736
+ } else {
2737
+ writeLine(` ${colors.dim}Default mode: conversation (lead \u2192 scan \u2192 work \u2192 review \u2192 verify)${RESET}`);
2738
+ writeLine();
2739
+ for (const agent of squad.agents) {
2740
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
2741
+ }
2742
+ writeLine();
2743
+ writeLine(` ${colors.dim}Run conversation:${RESET}`);
2744
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET}`);
2745
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --task "review and merge open PRs"`);
2746
+ writeLine();
2747
+ writeLine(` ${colors.dim}Run single agent:${RESET}`);
2748
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} -a ${colors.cyan}<agent>${RESET}`);
2749
+ }
2750
+ }
2751
+ }
2752
+ writeLine();
2753
+ writeLine(` ${colors.dim}After execution, record outcome:${RESET}`);
2754
+ writeLine(` ${colors.dim}$${RESET} squads feedback add ${colors.cyan}${squad.name}${RESET} ${colors.cyan}<1-5>${RESET} ${colors.cyan}"<feedback>"${RESET}`);
2755
+ writeLine();
2756
+ }
2757
+ var EVAL_TIMEOUT_MINUTES = 15;
2758
+ async function runPostEvaluation(squadsRun, options) {
2759
+ if (squadsRun.length === 1 && squadsRun[0] === "company") return;
2760
+ if (options.eval === false) return;
2761
+ if (options.dryRun) return;
2762
+ if (options.background) return;
2763
+ const squadsDir = findSquadsDir();
2764
+ if (!squadsDir) return;
2765
+ const cooPath = join5(squadsDir, "company", "company-lead.md");
2766
+ if (!existsSync5(cooPath)) {
2767
+ if (options.verbose) {
2768
+ writeLine(` ${colors.dim}Skipping evaluation: company-lead.md not found${RESET}`);
2769
+ }
2770
+ return;
2771
+ }
2772
+ const squadList = squadsRun.join(", ");
2773
+ writeLine();
2774
+ writeLine(` ${gradient("eval")} ${colors.dim}COO evaluating: ${squadList}${RESET}`);
2775
+ const evalTask = `Post-run evaluation for: ${squadList}.
2776
+
2777
+ ## Evaluation Process
2778
+
2779
+ For each squad (${squadList}):
2780
+
2781
+ ### 1. Read previous feedback FIRST
2782
+ Read \`.agents/memory/{squad}/feedback.md\` if it exists. Note the previous grade, identified patterns, and priorities. This is your baseline \u2014 you are measuring CHANGE, not just current state.
2783
+
2784
+ ### 2. Gather current evidence
2785
+ - PRs (last 7 days): \`gh pr list --state all --limit 20 --json number,title,state,mergedAt,createdAt\`
2786
+ - Recent commits (last 7 days): \`gh api repos/{owner}/{repo}/commits?since=YYYY-MM-DDT00:00:00Z&per_page=20 --jq '.[].commit.message'\`
2787
+ - Open issues: \`gh issue list --state open --limit 15 --json number,title,labels\`
2788
+ - Read \`.agents/memory/{squad}/priorities.md\` and \`.agents/memory/company/directives.md\`
2789
+ - Read \`.agents/memory/{squad}/active-work.md\` (previous cycle's work tracking)
2790
+
2791
+ ### 3. Write feedback.md (APPEND history, don't overwrite)
2792
+ \`\`\`markdown
2793
+ # Feedback \u2014 {squad}
2794
+
2795
+ ## Current Assessment (YYYY-MM-DD): [A-F]
2796
+ Merge rate: X% | Noise ratio: Y% | Priority alignment: Z%
2797
+
2798
+ ## Trajectory: [improving | stable | declining | new]
2799
+ Previous grade: [grade] \u2192 Current: [grade]. [1-line explanation of why]
2800
+
2801
+ ## Valuable (continue)
2802
+ - [specific PR/issue that advanced priorities]
2803
+
2804
+ ## Noise (stop)
2805
+ - [specific anti-pattern observed]
2806
+
2807
+ ## Next Cycle Priorities
2808
+ 1. [specific actionable item]
2809
+
2810
+ ## History
2811
+ | Date | Grade | Key Signal |
2812
+ |------|-------|------------|
2813
+ | YYYY-MM-DD | X | [what drove this grade] |
2814
+ [keep last 10 entries, append new row]
2815
+ \`\`\`
2816
+
2817
+ ### 4. Write active-work.md
2818
+ \`\`\`markdown
2819
+ # Active Work \u2014 {squad} (YYYY-MM-DD)
2820
+ ## Continue (open PRs)
2821
+ - #{number}: {title} \u2014 {status/next action}
2822
+ ## Backlog (assigned issues)
2823
+ - #{number}: {title} \u2014 {priority}
2824
+ ## Do NOT Create
2825
+ - {description of known duplicate patterns from feedback history}
2826
+ \`\`\`
2827
+
2828
+ ### 5. Commit to hq main
2829
+ ${squadsRun.length > 1 ? `
2830
+ ### 6. Cross-squad assessment
2831
+ Evaluate how outputs from ${squadList} connect:
2832
+ - Duplicated efforts across squads?
2833
+ - Missing handoffs (one squad's output should feed another)?
2834
+ - Coordination gaps (conflicting PRs, redundant issues)?
2835
+ - Combined trajectory: is the org getting more effective or more noisy?
2836
+ Write cross-squad findings to \`.agents/memory/company/cross-squad-review.md\`.
2837
+ ` : ""}
2838
+ CRITICAL: You are measuring DIRECTION not just position. A C-grade squad improving from F is better than a B-grade squad declining from A. The history table IS the feedback loop \u2014 agents read it next cycle.`;
2839
+ await runAgent("company-lead", cooPath, "company", {
2840
+ ...options,
2841
+ task: evalTask,
2842
+ timeout: EVAL_TIMEOUT_MINUTES,
2843
+ eval: false,
2844
+ // prevent recursion
2845
+ trigger: "manual"
2846
+ });
2847
+ }
2848
+ var ROLE_COOLDOWNS = {
2849
+ scanner: 60 * 60 * 1e3,
2850
+ // 1h — fast, cheap
2851
+ lead: 4 * 60 * 60 * 1e3,
2852
+ // 4h — orchestration
2853
+ worker: 30 * 60 * 1e3,
2854
+ // 30m — if work exists
2855
+ verifier: 30 * 60 * 1e3,
2856
+ // 30m — follows workers
2857
+ "issue-solver": 30 * 60 * 1e3
2858
+ // 30m — default worker
2859
+ };
2860
+ function classifyAgentRole(name) {
2861
+ return classifyAgent(name) ?? "worker";
2862
+ }
2863
+ async function runAutopilot(squadsDir, options) {
2864
+ const interval = parseInt(String(options.interval || "30"), 10);
2865
+ const maxParallel = parseInt(String(options.maxParallel || "2"), 10);
2866
+ const budget = parseFloat(String(options.budget || "0"));
2867
+ const once = !!options.once;
2868
+ const cognitionState = loadCognitionState();
2869
+ seedBeliefsIfEmpty(cognitionState);
2870
+ saveCognitionState(cognitionState);
2871
+ writeLine();
2872
+ writeLine(` ${gradient("squads")} ${colors.dim}autopilot${RESET}`);
2873
+ writeLine(` ${colors.dim}Interval: ${interval}m | Parallel: ${maxParallel} | Budget: ${budget > 0 ? "$" + budget + "/day" : "unlimited"}${RESET}`);
2874
+ writeLine(` ${colors.dim}Cognition: ${cognitionState.beliefs.length} beliefs, ${cognitionState.signals.length} signals${RESET}`);
2875
+ writeLine();
2876
+ let running = true;
2877
+ const handleSignal = () => {
2878
+ running = false;
2879
+ };
2880
+ process.on("SIGINT", handleSignal);
2881
+ process.on("SIGTERM", handleSignal);
2882
+ while (running) {
2883
+ const cycleStart = Date.now();
2884
+ const state = loadLoopState();
2885
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2886
+ if (state.dailyCostDate !== today) {
2887
+ state.dailyCost = 0;
2888
+ state.dailyCostDate = today;
2889
+ }
2890
+ if (budget > 0 && state.dailyCost >= budget) {
2891
+ writeLine(` ${icons.warning} ${colors.yellow}Daily budget reached ($${state.dailyCost.toFixed(2)}/$${budget})${RESET}`);
2892
+ saveLoopState(state);
2893
+ if (once) break;
2894
+ await sleep(interval * 60 * 1e3);
2895
+ continue;
2896
+ }
2897
+ writeLine(` ${colors.dim}\u2500\u2500 Cycle ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \u2500\u2500${RESET}`);
2898
+ let ghEnv = {};
2899
+ try {
2900
+ ghEnv = await getBotGhEnv();
2901
+ } catch {
2902
+ }
2903
+ const squadRepos = getSquadRepos();
2904
+ let dispatchedSquadNames;
2905
+ const failed = [];
2906
+ const completed = [];
2907
+ if (options.phased) {
2908
+ const phases = computePhases();
2909
+ const phaseCount = phases.size;
2910
+ writeLine(` ${colors.dim}Phased mode: ${phaseCount} phase(s)${RESET}`);
2911
+ dispatchedSquadNames = [];
2912
+ for (const [phaseNum, phaseSquads] of phases) {
2913
+ writeLine(` ${colors.dim}\u2500\u2500 Phase ${phaseNum} (${phaseSquads.join(", ")}) \u2500\u2500${RESET}`);
2914
+ const phaseSignals = scoreSquadsForPhase(phaseSquads, state, squadRepos, ghEnv);
2915
+ const phaseDispatch = phaseSignals.filter((s) => s.score > 0).slice(0, maxParallel);
2916
+ if (phaseDispatch.length === 0) {
2917
+ writeLine(` ${colors.dim}No squads need attention in this phase${RESET}`);
2918
+ continue;
2919
+ }
2920
+ for (const sig of phaseDispatch) {
2921
+ writeLine(` ${colors.cyan}${sig.squad}${RESET} (score: ${sig.score}) \u2014 ${sig.reason}`);
2922
+ }
2923
+ if (options.dryRun) {
2924
+ continue;
2925
+ }
2926
+ const phaseResults = await Promise.allSettled(
2927
+ phaseDispatch.map((sig) => {
2928
+ const squad = loadSquad(sig.squad);
2929
+ if (!squad) return Promise.resolve();
2930
+ return runSquadLoop(squad, squadsDir, state, ghEnv, options);
2931
+ })
2932
+ );
2933
+ for (let i = 0; i < phaseResults.length; i++) {
2934
+ const name = phaseDispatch[i].squad;
2935
+ dispatchedSquadNames.push(name);
2936
+ if (phaseResults[i].status === "rejected") {
2937
+ failed.push(name);
2938
+ state.failCounts[name] = (state.failCounts[name] || 0) + 1;
2939
+ } else {
2940
+ completed.push(name);
2941
+ delete state.failCounts[name];
2942
+ }
2943
+ }
2944
+ }
2945
+ if (options.dryRun) {
2946
+ writeLine(` ${colors.yellow}[DRY RUN] Would dispatch above squads in phase order${RESET}`);
2947
+ saveLoopState(state);
2948
+ if (once) break;
2949
+ await sleep(interval * 60 * 1e3);
2950
+ continue;
2951
+ }
2952
+ } else {
2953
+ const signals = scoreSquads(state, squadRepos, ghEnv);
2954
+ if (signals.length === 0 || signals.every((s) => s.score <= 0)) {
2955
+ writeLine(` ${colors.dim}No squads need attention${RESET}`);
2956
+ saveLoopState(state);
2957
+ if (once) break;
2958
+ await sleep(interval * 60 * 1e3);
2959
+ continue;
2960
+ }
2961
+ const toDispatch = signals.filter((s) => s.score > 0).slice(0, maxParallel);
2962
+ writeLine(` ${colors.dim}Dispatching ${toDispatch.length} squad(s):${RESET}`);
2963
+ for (const sig of toDispatch) {
2964
+ writeLine(` ${colors.cyan}${sig.squad}${RESET} (score: ${sig.score}) \u2014 ${sig.reason}`);
2965
+ }
2966
+ if (options.dryRun) {
2967
+ writeLine(` ${colors.yellow}[DRY RUN] Would dispatch above squads${RESET}`);
2968
+ saveLoopState(state);
2969
+ if (once) break;
2970
+ await sleep(interval * 60 * 1e3);
2971
+ continue;
2972
+ }
2973
+ const results = await Promise.allSettled(
2974
+ toDispatch.map((sig) => {
2975
+ const squad = loadSquad(sig.squad);
2976
+ if (!squad) return Promise.resolve();
2977
+ return runSquadLoop(squad, squadsDir, state, ghEnv, options);
2978
+ })
2979
+ );
2980
+ for (let i = 0; i < results.length; i++) {
2981
+ const r = results[i];
2982
+ const name = toDispatch[i].squad;
2983
+ if (r.status === "rejected") {
2984
+ failed.push(name);
2985
+ state.failCounts[name] = (state.failCounts[name] || 0) + 1;
2986
+ } else {
2987
+ completed.push(name);
2988
+ delete state.failCounts[name];
2989
+ }
2990
+ }
2991
+ dispatchedSquadNames = toDispatch.map((s) => s.squad);
2992
+ }
2993
+ const cycleCost = dispatchedSquadNames.length * 1;
2994
+ state.dailyCost += cycleCost;
2995
+ await pushMemorySignals(dispatchedSquadNames, state, !!options.verbose);
2996
+ state.recentRuns = state.recentRuns.slice(-100);
2997
+ state.lastCycle = (/* @__PURE__ */ new Date()).toISOString();
2998
+ saveLoopState(state);
2999
+ if (failed.length > 0) {
3000
+ slackNotify([
3001
+ `*Autopilot cycle \u2014 failures*`,
3002
+ `Failed: ${failed.join(", ")}`,
3003
+ `Completed: ${completed.join(", ")}`,
3004
+ `Daily: $${state.dailyCost.toFixed(2)}${budget > 0 ? "/$" + budget : ""}`
3005
+ ].join("\n"));
3006
+ }
3007
+ for (const [key, count] of Object.entries(state.failCounts)) {
3008
+ if (count >= 3) {
3009
+ slackNotify(`\u{1F6A8} *Escalation*: ${key} has failed ${count} times consecutively.`);
3010
+ }
3011
+ }
3012
+ if (dispatchedSquadNames.length > 0) {
3013
+ await runPostEvaluation(dispatchedSquadNames, options);
3014
+ }
3015
+ writeLine(` ${colors.dim}Cognition cycle...${RESET}`);
3016
+ const cognitionResult = await runCognitionCycle(dispatchedSquadNames, !!options.verbose);
3017
+ if (cognitionResult.signalsIngested > 0 || cognitionResult.beliefsUpdated > 0 || cognitionResult.reflected) {
3018
+ writeLine(` ${colors.dim}\u{1F9E0} ${cognitionResult.signalsIngested} signals \u2192 ${cognitionResult.beliefsUpdated} beliefs updated${cognitionResult.reflected ? " \u2192 reflected" : ""}${RESET}`);
3019
+ }
3020
+ const elapsed = ((Date.now() - cycleStart) / 1e3).toFixed(0);
3021
+ writeLine(` ${colors.dim}Cycle done in ${elapsed}s | Daily: $${state.dailyCost.toFixed(2)}${RESET}`);
3022
+ writeLine();
3023
+ if (once) break;
3024
+ await sleep(interval * 60 * 1e3);
3025
+ }
3026
+ process.off("SIGINT", handleSignal);
3027
+ process.off("SIGTERM", handleSignal);
3028
+ }
3029
+ async function runSquadLoop(squad, squadsDir, state, ghEnv, options) {
3030
+ writeLine(` ${gradient("\u25B8")} ${colors.cyan}${squad.name}${RESET} \u2014 full loop`);
3031
+ const agentsByRole = {
3032
+ scanner: [],
3033
+ lead: [],
3034
+ worker: [],
3035
+ verifier: []
3036
+ };
3037
+ for (const agent of squad.agents) {
3038
+ const role = classifyAgentRole(agent.name);
3039
+ const agentPath = join5(squadsDir, squad.dir, `${agent.name}.md`);
3040
+ if (existsSync5(agentPath)) {
3041
+ agentsByRole[role].push({ name: agent.name, path: agentPath });
3042
+ }
3043
+ }
3044
+ const loopSteps = [
3045
+ { role: "scanner", agents: agentsByRole.scanner },
3046
+ { role: "lead", agents: agentsByRole.lead },
3047
+ { role: "worker", agents: agentsByRole.worker },
3048
+ { role: "verifier", agents: agentsByRole.verifier }
3049
+ ];
3050
+ for (const step of loopSteps) {
3051
+ if (step.agents.length === 0) continue;
3052
+ for (const agent of step.agents) {
3053
+ const cooldownMs = ROLE_COOLDOWNS[step.role] || ROLE_COOLDOWNS.worker;
3054
+ if (!checkCooldown(state, squad.name, agent.name, cooldownMs)) {
3055
+ if (options.verbose) {
3056
+ writeLine(` ${colors.dim}\u21B3 ${agent.name} (${step.role}) \u2014 in cooldown, skip${RESET}`);
3057
+ }
3058
+ continue;
3059
+ }
3060
+ writeLine(` ${colors.dim}\u21B3 ${agent.name} (${step.role})${RESET}`);
3061
+ const startMs = Date.now();
3062
+ try {
3063
+ if (step.role === "worker" && step.agents.length > 1) {
3064
+ const convOptions = {
3065
+ task: options.task,
3066
+ maxTurns: options.maxTurns || 20,
3067
+ costCeiling: options.costCeiling || 25,
3068
+ verbose: options.verbose,
3069
+ model: options.model
3070
+ };
3071
+ await runConversation(squad, convOptions);
3072
+ } else {
3073
+ await runAgent(agent.name, agent.path, squad.dir, {
3074
+ ...options,
3075
+ background: false,
3076
+ watch: false,
3077
+ execute: true
3078
+ });
3079
+ }
3080
+ const durationMs = Date.now() - startMs;
3081
+ const outcome = classifyRunOutcome(0, durationMs);
3082
+ state.cooldowns[`${squad.name}:${agent.name}`] = Date.now();
3083
+ state.recentRuns.push({
3084
+ squad: squad.name,
3085
+ agent: agent.name,
3086
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3087
+ result: outcome === "skipped" ? "completed" : outcome,
3088
+ durationMs
3089
+ });
3090
+ pushCognitionSignal({
3091
+ source: "execution",
3092
+ signal_type: `${step.role}_${outcome}`,
3093
+ value: durationMs / 1e3,
3094
+ unit: "seconds",
3095
+ data: {
3096
+ squad: squad.name,
3097
+ agent: agent.name,
3098
+ role: step.role,
3099
+ duration_ms: durationMs
3100
+ },
3101
+ entity_type: "agent",
3102
+ entity_id: `${squad.name}/${agent.name}`,
3103
+ confidence: 0.9
3104
+ });
3105
+ if (outcome === "skipped") {
3106
+ writeLine(` ${colors.dim}\u21B3 ${agent.name} \u2014 phantom (${(durationMs / 1e3).toFixed(0)}s), skipped${RESET}`);
3107
+ }
3108
+ if (step.role === "worker" && step.agents.length > 1) break;
3109
+ } catch (err) {
3110
+ const durationMs = Date.now() - startMs;
3111
+ state.cooldowns[`${squad.name}:${agent.name}`] = Date.now();
3112
+ state.recentRuns.push({
3113
+ squad: squad.name,
3114
+ agent: agent.name,
3115
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3116
+ result: "failed",
3117
+ durationMs
3118
+ });
3119
+ writeLine(` ${colors.red}\u21B3 ${agent.name} failed: ${err instanceof Error ? err.message : "unknown"}${RESET}`);
3120
+ }
3121
+ }
3122
+ }
3123
+ writeLine(` ${colors.dim}\u21B3 ${squad.name} loop complete${RESET}`);
3124
+ }
3125
+ function sleep(ms) {
3126
+ return new Promise((resolve) => setTimeout(resolve, ms));
3127
+ }
3128
+ async function runLeadMode(squad, squadsDir, options) {
3129
+ if (!squad) return;
3130
+ const agentFiles = squad.agents.map((a) => ({
3131
+ name: a.name,
3132
+ path: join5(squadsDir, squad.dir, `${a.name}.md`),
3133
+ role: a.role || ""
3134
+ })).filter((a) => existsSync5(a.path));
3135
+ if (agentFiles.length === 0) {
3136
+ writeLine(` ${icons.error} ${colors.red}No agent files found${RESET}`);
3137
+ return;
3138
+ }
3139
+ writeLine(` ${bold}Lead mode${RESET} ${colors.dim}orchestrating ${agentFiles.length} agents${RESET}`);
3140
+ writeLine();
3141
+ for (const agent of agentFiles) {
3142
+ writeLine(` ${icons.empty} ${colors.cyan}${agent.name}${RESET} ${colors.dim}${agent.role}${RESET}`);
3143
+ }
3144
+ writeLine();
3145
+ if (!options.execute) {
3146
+ writeLine(` ${colors.dim}Launch lead session:${RESET}`);
3147
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squad.name}${RESET} --lead`);
3148
+ writeLine();
3149
+ return;
3150
+ }
3151
+ const timeoutMins = options.timeout || DEFAULT_TIMEOUT_MINUTES;
3152
+ const agentList = agentFiles.map((a) => `- ${a.name}: ${a.role}`).join("\n");
3153
+ const agentPaths = agentFiles.map((a) => `- ${a.name}: ${a.path}`).join("\n");
3154
+ const prompt = `You are the Lead of the ${squad.name} squad.
3155
+
3156
+ ## Mission
3157
+ ${squad.mission || "Execute squad operations efficiently."}
3158
+
3159
+ ## Available Agents
3160
+ ${agentList}
3161
+
3162
+ ## Agent Definition Files
3163
+ ${agentPaths}
3164
+
3165
+ ## Your Role as Lead
3166
+
3167
+ 1. **Assess the situation**: Check for pending work:
3168
+ - Run \`gh issue list --repo agents-squads/hq --label squad:${squad.name}\` for assigned issues
3169
+ - Check .agents/memory/${squad.dir}/ for squad state and pending tasks
3170
+ - Review recent activity with \`git log --oneline -10\`
3171
+
3172
+ 2. **Delegate work using Task tool**: For each piece of work:
3173
+ - Use the Task tool with subagent_type="general-purpose"
3174
+ - Include the agent definition file path in the prompt
3175
+ - Spawn multiple Task agents IN PARALLEL when work is independent
3176
+ - Example: "Read ${agentFiles[0]?.path || "agent.md"} and execute its instructions for [specific task]"
3177
+
3178
+ 3. **Coordinate parallel execution**:
3179
+ - Independent tasks \u2192 spawn Task agents in parallel (single message, multiple tool calls)
3180
+ - Dependent tasks \u2192 run sequentially
3181
+ - Monitor progress and handle failures
3182
+
3183
+ 4. **Report and update memory**:
3184
+ - Update .agents/memory/${squad.dir}/state.md with completed work
3185
+ - Log learnings to learnings.md
3186
+ - Create issues for follow-up work if needed
3187
+
3188
+ ## Time Budget
3189
+ You have ${timeoutMins} minutes. Prioritize high-impact work.
3190
+
3191
+ ## Critical Instructions
3192
+ - Use Task tool for delegation, NOT direct execution of agent work
3193
+ - Spawn parallel Task agents when work is independent
3194
+ - When done, type /exit to end the session
3195
+ - Do NOT wait for user input - work autonomously
3196
+
3197
+ ## Async Mode (CRITICAL)
3198
+ This is ASYNC execution - Task agents must be fully autonomous:
3199
+ - **Findings** \u2192 Create GitHub issues (gh issue create)
3200
+ - **Code changes** \u2192 Create PRs (gh pr create)
3201
+ - **Analysis results** \u2192 Write to .agents/outputs/ or memory files
3202
+ - **NEVER wait for human review** - complete the work and move on
3203
+ - **NEVER ask clarifying questions** - make reasonable decisions
3204
+
3205
+ Instruct each Task agent: "Work autonomously. Output findings to GitHub issues. Output code changes as PRs. Do not wait for review."
3206
+
3207
+ Begin by assessing pending work, then delegate to agents via Task tool.`;
3208
+ const provider = options.provider || squad?.providers?.default || "anthropic";
3209
+ const isAnthropic = provider === "anthropic";
3210
+ if (isAnthropic) {
3211
+ const claudeAvailable = await checkClaudeCliAvailable();
3212
+ if (!claudeAvailable) {
3213
+ writeLine(` ${colors.yellow}Claude CLI not found${RESET}`);
3214
+ writeLine(` ${colors.dim}Install: npm install -g @anthropic-ai/claude-code${RESET}`);
3215
+ return;
3216
+ }
3217
+ } else {
3218
+ if (!isProviderCLIAvailable(provider)) {
3219
+ const cliConfig = getCLIConfig(provider);
3220
+ writeLine(` ${colors.yellow}${cliConfig?.displayName || provider} CLI not found${RESET}`);
3221
+ if (cliConfig?.install) {
3222
+ writeLine(` ${colors.dim}Install: ${cliConfig.install}${RESET}`);
3223
+ }
3224
+ return;
3225
+ }
3226
+ }
3227
+ const isBackground = options.background === true && !options.watch;
3228
+ const isWatch = options.watch === true;
3229
+ const isForeground = !isBackground && !isWatch;
3230
+ const modeText = isBackground ? " (background)" : isWatch ? " (watch)" : "";
3231
+ const providerDisplay = isAnthropic ? "Claude" : getCLIConfig(provider)?.displayName || provider;
3232
+ writeLine(` ${gradient("Launching")} lead session${modeText} with ${providerDisplay}...`);
3233
+ writeLine();
3234
+ try {
3235
+ const leadAgentName = agentFiles.find((a) => a.name.includes("lead"))?.name || `${squad.dir}-lead`;
3236
+ let result;
3237
+ if (isAnthropic) {
3238
+ result = await executeWithClaude(prompt, {
3239
+ verbose: options.verbose,
3240
+ timeoutMinutes: timeoutMins,
3241
+ foreground: options.foreground,
3242
+ background: options.background,
3243
+ watch: options.watch,
3244
+ useApi: options.useApi,
3245
+ effort: options.effort,
3246
+ skills: options.skills,
3247
+ trigger: options.trigger || "manual",
3248
+ squadName: squad.dir,
3249
+ agentName: leadAgentName,
3250
+ model: options.model
3251
+ });
3252
+ } else {
3253
+ result = await executeWithProvider(provider, prompt, {
3254
+ verbose: options.verbose,
3255
+ foreground: isForeground || isWatch,
3256
+ squadName: squad.dir,
3257
+ agentName: leadAgentName
3258
+ });
3259
+ }
3260
+ if (isForeground || isWatch) {
3261
+ writeLine();
3262
+ writeLine(` ${icons.success} Lead session completed`);
3263
+ } else {
3264
+ writeLine(` ${icons.success} Lead session launched in background`);
3265
+ writeLine(` ${colors.dim}${result}${RESET}`);
3266
+ writeLine();
3267
+ writeLine(` ${colors.dim}The lead will:${RESET}`);
3268
+ writeLine(` ${colors.dim} 1. Assess pending work (issues, memory)${RESET}`);
3269
+ writeLine(` ${colors.dim} 2. Spawn Task agents for parallel execution${RESET}`);
3270
+ writeLine(` ${colors.dim} 3. Coordinate and report results${RESET}`);
3271
+ writeLine();
3272
+ writeLine(` ${colors.dim}Monitor: squads workers${RESET}`);
3273
+ }
3274
+ } catch (error) {
3275
+ const msg = error instanceof Error ? error.message : String(error);
3276
+ writeLine(` ${icons.error} ${colors.red}Failed to launch agent${RESET}`);
3277
+ writeLine(` ${colors.dim}${msg}${RESET}`);
3278
+ writeLine(` ${colors.dim}Run \`squads doctor\` to check your setup.${RESET}`);
3279
+ }
3280
+ }
3281
+ async function runAgent(agentName, agentPath, squadName, options) {
3282
+ const spinner = ora(`Running agent: ${agentName}`).start();
3283
+ const startMs = Date.now();
3284
+ const startTime = new Date(startMs).toISOString();
3285
+ const executionId = generateExecutionId();
3286
+ const taskType = detectTaskType(agentName);
3287
+ const definition = loadAgentDefinition(agentPath);
3288
+ const learnings = await fetchLearnings(squadName);
3289
+ const learningContext = learnings.length > 0 ? `
3290
+ ## Learnings from Previous Runs
3291
+ ${learnings.map((l) => `- ${l.content}`).join("\n")}
3292
+ ` : "";
3293
+ if (options.dryRun) {
3294
+ spinner.info(`[DRY RUN] Would run ${agentName}`);
3295
+ const dryRunAgentRole = classifyAgent(agentName);
3296
+ const dryRunContextRole = agentName.includes("company-lead") ? "coo" : dryRunAgentRole ?? "worker";
3297
+ const dryRunContext = gatherSquadContext(squadName, agentName, {
3298
+ verbose: options.verbose,
3299
+ agentPath,
3300
+ role: dryRunContextRole
3301
+ });
3302
+ if (options.verbose) {
3303
+ writeLine(` ${colors.dim}Agent definition:${RESET}`);
3304
+ writeLine(` ${colors.dim}${definition.slice(0, DRYRUN_DEF_MAX_CHARS)}...${RESET}`);
3305
+ if (learnings.length > 0) {
3306
+ writeLine(` ${colors.dim}Learnings: ${learnings.length} from bridge${RESET}`);
3307
+ }
3308
+ if (dryRunContext || learningContext) {
3309
+ const fullContext = `${dryRunContext}${learningContext}`;
3310
+ writeLine();
3311
+ writeLine(` ${colors.cyan}Context to inject (${Math.ceil(fullContext.length / 4)} tokens):${RESET}`);
3312
+ writeLine(` ${colors.dim}${fullContext.slice(0, DRYRUN_CONTEXT_MAX_CHARS)}...${RESET}`);
3313
+ }
3314
+ }
3315
+ return;
3316
+ }
3317
+ const squadsDir = findSquadsDir();
3318
+ if (squadsDir) {
3319
+ const squadFilePath = join5(squadsDir, squadName, "SQUAD.md");
3320
+ if (existsSync5(squadFilePath)) {
3321
+ const squadContent = readFileSync4(squadFilePath, "utf-8");
3322
+ const permContext = buildContextFromSquad(squadName, squadContent, agentName);
3323
+ const mcpServers = extractMcpServersFromDefinition(definition);
3324
+ const execRequest = {
3325
+ mcpServers
3326
+ };
3327
+ const permResult = validateExecution(permContext, execRequest);
3328
+ if (permResult.violations.length > 0) {
3329
+ spinner.stop();
3330
+ const violationLines = formatViolations(permResult);
3331
+ for (const line of violationLines) {
3332
+ writeLine(` ${line}`);
3333
+ }
3334
+ writeLine();
3335
+ if (!permResult.allowed) {
3336
+ writeLine(` ${colors.red}Execution blocked due to permission violations.${RESET}`);
3337
+ writeLine(` ${colors.dim}Configure permissions in ${squadFilePath}${RESET}`);
3338
+ return;
3339
+ }
3340
+ }
3341
+ }
3342
+ }
3343
+ const preflight = await checkPreflightGates(squadName, agentName);
3344
+ if (!preflight.allowed) {
3345
+ spinner.stop();
3346
+ writeLine();
3347
+ writeLine(` ${colors.red}${icons.error} Execution blocked by preflight gates${RESET}`);
3348
+ if (preflight.gates.quota && !preflight.gates.quota.ok) {
3349
+ writeLine(` ${colors.dim}Quota: $${preflight.gates.quota.used.toFixed(2)}/$${preflight.gates.quota.limit}/mo limit exceeded${RESET}`);
3350
+ }
3351
+ if (preflight.gates.cooldown && !preflight.gates.cooldown.ok) {
3352
+ const elapsed = preflight.gates.cooldown.elapsed_sec;
3353
+ const minGap = preflight.gates.cooldown.min_gap_sec;
3354
+ writeLine(` ${colors.dim}Cooldown: ${elapsed}s since last run (min: ${minGap}s)${RESET}`);
3355
+ }
3356
+ writeLine();
3357
+ return;
3358
+ }
3359
+ if (options.verbose && Object.keys(preflight.gates).length > 0) {
3360
+ writeLine(` ${colors.dim}Preflight: quota ${preflight.gates.quota?.ok ? "\u2713" : "\u2717"} cooldown ${preflight.gates.cooldown?.ok ? "\u2713" : "\u2717"}${RESET}`);
3361
+ }
3362
+ const isScheduledRun = options.trigger === "scheduled" || options.trigger === "smart";
3363
+ const bridgeHasNoHistory = preflight.gates.cooldown?.elapsed_sec === null;
3364
+ if (isScheduledRun && (!preflight.gates.cooldown || bridgeHasNoHistory)) {
3365
+ const frontmatterForCooldown = parseAgentFrontmatter(agentPath);
3366
+ const cooldownMs = frontmatterForCooldown.cooldown ? parseCooldown(frontmatterForCooldown.cooldown) || DEFAULT_SCHEDULED_COOLDOWN_MS : DEFAULT_SCHEDULED_COOLDOWN_MS;
3367
+ const localCooldown = checkLocalCooldown(squadName, agentName, cooldownMs);
3368
+ if (!localCooldown.ok) {
3369
+ spinner.stop();
3370
+ writeLine();
3371
+ writeLine(` ${colors.yellow}${icons.warning} Skipping: cooldown not elapsed${RESET}`);
3372
+ writeLine(` ${colors.dim}Last run: ${formatDuration(localCooldown.elapsedMs)} ago (cooldown: ${formatDuration(localCooldown.cooldownMs)})${RESET}`);
3373
+ writeLine();
3374
+ return;
3375
+ }
3376
+ if (options.verbose) {
3377
+ writeLine(` ${colors.dim}Local cooldown: \u2713 (${formatDuration(localCooldown.elapsedMs || 0)} since last run)${RESET}`);
3378
+ }
3379
+ }
3380
+ logExecution({
3381
+ squadName,
3382
+ agentName,
3383
+ executionId,
3384
+ startTime,
3385
+ status: "running",
3386
+ trigger: options.trigger || "manual",
3387
+ taskType
3388
+ });
3389
+ if (options.verbose && learnings.length > 0) {
3390
+ writeLine(` ${colors.dim}Injecting ${learnings.length} learnings${RESET}`);
3391
+ }
3392
+ const systemProtocol = loadSystemProtocol();
3393
+ const systemContext = systemProtocol ? `
3394
+ ${systemProtocol}
3395
+ ` : "";
3396
+ const agentRole = classifyAgent(agentName);
3397
+ const contextRole = agentName.includes("company-lead") ? "coo" : agentRole ?? "worker";
3398
+ const squadContext = gatherSquadContext(squadName, agentName, {
3399
+ verbose: options.verbose,
3400
+ agentPath,
3401
+ role: contextRole
3402
+ });
3403
+ let cognitionContext = "";
3404
+ try {
3405
+ const { loadSession: loadSession2 } = await import("./auth-YW3UPFSB.js");
3406
+ const { getApiUrl: getApiUrl2 } = await import("./env-config-SQEI3Y7Y.js");
3407
+ const session = loadSession2();
3408
+ if (session?.accessToken && session.status === "active") {
3409
+ const safeSquadName = encodeURIComponent(squadName);
3410
+ const res = await fetch(`${getApiUrl2()}/cognition/context/squad:${safeSquadName}`, {
3411
+ headers: { Authorization: `Bearer ${session.accessToken}` },
3412
+ signal: AbortSignal.timeout(3e3)
3413
+ });
3414
+ if (res.ok) {
3415
+ const data = await res.json();
3416
+ if (data.markdown && !data.markdown.includes("No cognition data")) {
3417
+ cognitionContext = `
3418
+ ${data.markdown}
3419
+ `;
3420
+ if (options.verbose) {
3421
+ writeLine(` ${colors.dim}Injecting cognition beliefs${RESET}`);
3422
+ }
3423
+ }
3424
+ }
3425
+ }
3426
+ } catch (e) {
3427
+ if (options.verbose) writeLine(` ${colors.dim}warn: cognition fetch failed: ${e instanceof Error ? e.message : String(e)}${RESET}`);
3428
+ }
3429
+ const timeoutMins = options.timeout || DEFAULT_TIMEOUT_MINUTES;
3430
+ const taskDirective = options.task ? `
3431
+ ## TASK DIRECTIVE (overrides default behavior)
3432
+ ${options.task}
3433
+ ` : "";
3434
+ const prompt = `Execute the ${agentName} agent from squad ${squadName}.
3435
+
3436
+ Read the agent definition at ${agentPath} and follow its instructions exactly.
3437
+ ${taskDirective}
3438
+ The agent definition contains:
3439
+ - Purpose/role
3440
+ - Tools it can use (MCP servers, skills)
3441
+ - Step-by-step instructions
3442
+ - Expected output format
3443
+
3444
+ TOOL PREFERENCE: Always prefer CLI tools over MCP servers when both can accomplish the task:
3445
+ - Use \`squads\` CLI for squad operations (run, memory, status, feedback)
3446
+ - Use \`gh\` CLI for GitHub (issues, PRs, repos)
3447
+ - Use \`git\` CLI for version control
3448
+ - Use Bash for file operations, builds, tests
3449
+ - Only use MCP tools when CLI cannot do it or MCP is significantly better
3450
+ ${systemContext}${squadContext}${cognitionContext}${learningContext}
3451
+ TIME LIMIT: You have ${timeoutMins} minutes. Work efficiently:
3452
+ - Focus on the most important tasks first
3453
+ - If a task is taking too long, move on and note it for next run
3454
+ - Aim to complete within ${Math.floor(timeoutMins * SOFT_DEADLINE_RATIO)} minutes`;
3455
+ const agentProvider = parseAgentProvider(agentPath);
3456
+ const squad = loadSquad(squadName);
3457
+ const squadDefaultProvider = squad?.providers?.default;
3458
+ const provider = agentProvider || options.provider || squadDefaultProvider || "anthropic";
3459
+ const isAnthropic = provider === "anthropic";
3460
+ if (options.verbose && (agentProvider || squadDefaultProvider)) {
3461
+ writeLine(` ${colors.dim}Provider resolution:${RESET}`);
3462
+ if (agentProvider) writeLine(` ${colors.dim}Agent: ${agentProvider}${RESET}`);
3463
+ if (options.provider) writeLine(` ${colors.dim}CLI: ${options.provider}${RESET}`);
3464
+ if (squadDefaultProvider) writeLine(` ${colors.dim}Squad: ${squadDefaultProvider}${RESET}`);
3465
+ writeLine(` ${colors.dim}\u2192 Using: ${provider}${RESET}`);
3466
+ }
3467
+ const cliAvailable = isAnthropic ? await checkClaudeCliAvailable() : isProviderCLIAvailable(provider);
3468
+ if (options.execute && cliAvailable) {
3469
+ const cliConfig = getCLIConfig(provider);
3470
+ const cliName = cliConfig?.displayName || provider;
3471
+ const isBackground = options.background === true && !options.watch;
3472
+ const isWatch = options.watch === true;
3473
+ const isForeground = !isBackground && !isWatch;
3474
+ spinner.text = isBackground ? `Launching ${agentName} with ${cliName} in background...` : isWatch ? `Starting ${agentName} with ${cliName} (watch mode)...` : `Running ${agentName} with ${cliName}...`;
3475
+ const frontmatter = parseAgentFrontmatter(agentPath);
3476
+ const hasCriteria = !!frontmatter.acceptance_criteria && options.verify !== false;
3477
+ const maxRetries = frontmatter.max_retries ?? 2;
3478
+ let currentPrompt = prompt;
3479
+ for (let attempt = 0; attempt <= (hasCriteria ? maxRetries : 0); attempt++) {
3480
+ try {
3481
+ let result;
3482
+ if (isAnthropic) {
3483
+ result = await executeWithClaude(currentPrompt, {
3484
+ verbose: options.verbose,
3485
+ timeoutMinutes: options.timeout || 30,
3486
+ foreground: options.foreground,
3487
+ background: options.background,
3488
+ watch: options.watch,
3489
+ useApi: options.useApi,
3490
+ effort: options.effort,
3491
+ skills: options.skills,
3492
+ trigger: options.trigger || "manual",
3493
+ squadName,
3494
+ agentName,
3495
+ model: options.model
3496
+ });
3497
+ } else {
3498
+ result = await executeWithProvider(provider, currentPrompt, {
3499
+ verbose: options.verbose,
3500
+ foreground: !isBackground,
3501
+ squadName,
3502
+ agentName
3503
+ });
3504
+ }
3505
+ if (hasCriteria && (isForeground || isWatch)) {
3506
+ const verification = await verifyExecution(
3507
+ squadName,
3508
+ agentName,
3509
+ frontmatter.acceptance_criteria,
3510
+ { verbose: options.verbose }
3511
+ );
3512
+ if (!verification.passed && attempt < maxRetries) {
3513
+ writeLine(` ${colors.yellow}Verification: FAIL - ${verification.reason}${RESET}`);
3514
+ writeLine(` ${colors.dim}Retrying (${attempt + 1}/${maxRetries})...${RESET}`);
3515
+ currentPrompt = `${prompt}
3516
+
3517
+ ## PREVIOUS ATTEMPT FAILED
3518
+ Verification found: ${verification.reason}
3519
+ Please address this issue and try again.`;
3520
+ continue;
3521
+ }
3522
+ if (verification.passed) {
3523
+ writeLine(` ${colors.green}Verification: PASS - ${verification.reason}${RESET}`);
3524
+ }
3525
+ }
3526
+ emitExecutionEvent("agent.completed", {
3527
+ squad: squadName,
3528
+ agent: agentName,
3529
+ executionId
3530
+ }).catch(() => {
3531
+ });
3532
+ if (isForeground || isWatch) {
3533
+ spinner.succeed(`Agent ${agentName} completed (${cliName})`);
3534
+ } else {
3535
+ spinner.succeed(`Agent ${agentName} launched in background (${cliName})`);
3536
+ writeLine(` ${colors.dim}${result}${RESET}`);
3537
+ writeLine();
3538
+ writeLine(` ${colors.dim}Monitor:${RESET} squads workers`);
3539
+ writeLine(` ${colors.dim}Memory:${RESET} squads memory show ${squadName}`);
3540
+ }
3541
+ break;
3542
+ } catch (error) {
3543
+ emitExecutionEvent("agent.failed", {
3544
+ squad: squadName,
3545
+ agent: agentName,
3546
+ executionId,
3547
+ error: String(error)
3548
+ }).catch(() => {
3549
+ });
3550
+ spinner.fail(`Agent ${agentName} failed to launch`);
3551
+ updateExecutionStatus(squadName, agentName, executionId, "failed", {
3552
+ error: String(error),
3553
+ durationMs: Date.now() - startMs
3554
+ });
3555
+ const msg = error instanceof Error ? error.message : String(error);
3556
+ const isLikelyBug = error instanceof ReferenceError || error instanceof TypeError || error instanceof SyntaxError;
3557
+ writeLine(` ${colors.red}${msg}${RESET}`);
3558
+ writeLine();
3559
+ if (isLikelyBug) {
3560
+ writeLine(` ${colors.yellow}This looks like a bug. Please try:${RESET}`);
3561
+ writeLine(` ${colors.dim}$${RESET} squads doctor ${colors.dim}\u2014 check your setup${RESET}`);
3562
+ writeLine(` ${colors.dim}$${RESET} squads update ${colors.dim}\u2014 get the latest fixes${RESET}`);
3563
+ writeLine();
3564
+ writeLine(` ${colors.dim}If the problem persists, file an issue:${RESET}`);
3565
+ writeLine(` ${colors.dim}https://github.com/agents-squads/squads-cli/issues${RESET}`);
3566
+ } else {
3567
+ writeLine(` ${colors.dim}Run \`squads doctor\` to check your setup, or \`squads run ${agentName} --verbose\` for details.${RESET}`);
3568
+ }
3569
+ break;
3570
+ }
3571
+ }
3572
+ } else {
3573
+ spinner.succeed(`Agent ${agentName} ready`);
3574
+ writeLine(` ${colors.dim}Execution logged: ${startTime}${RESET}`);
3575
+ if (!cliAvailable) {
3576
+ const cliConfig = getCLIConfig(provider);
3577
+ writeLine();
3578
+ writeLine(` ${colors.yellow}${cliConfig?.command || provider} CLI not found${RESET}`);
3579
+ writeLine(` ${colors.dim}Install: ${cliConfig?.install || "squads providers"}${RESET}`);
3580
+ }
3581
+ writeLine();
3582
+ writeLine(` ${colors.dim}To launch as background task:${RESET}`);
3583
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} -a ${colors.cyan}${agentName}${RESET}`);
3584
+ if (provider !== "anthropic") {
3585
+ writeLine(` ${colors.dim}$${RESET} squads run ${colors.cyan}${squadName}${RESET} -a ${colors.cyan}${agentName}${RESET} --provider=${provider}`);
3586
+ }
3587
+ writeLine();
3588
+ writeLine(` ${colors.dim}Or run interactively:${RESET}`);
3589
+ writeLine(` ${colors.dim}$${RESET} Run the ${colors.cyan}${agentName}${RESET} agent from ${agentPath}`);
3590
+ }
3591
+ }
3592
+ async function checkClaudeCliAvailable() {
3593
+ return new Promise((resolve) => {
3594
+ const check = spawn("which", ["claude"], { stdio: "pipe" });
3595
+ check.on("close", (code) => resolve(code === 0));
3596
+ check.on("error", () => resolve(false));
3597
+ });
3598
+ }
3599
+ async function preflightExecutorCheck(provider) {
3600
+ if (process.env.SQUADS_SKIP_CHECKS === "1") {
3601
+ return true;
3602
+ }
3603
+ const isAnthropic = provider === "anthropic";
3604
+ let cliFound;
3605
+ if (isAnthropic) {
3606
+ cliFound = await checkClaudeCliAvailable();
3607
+ } else {
3608
+ cliFound = isProviderCLIAvailable(provider);
3609
+ }
3610
+ if (!cliFound) {
3611
+ const cliConfig = getCLIConfig(provider);
3612
+ const cliName = cliConfig?.command || provider;
3613
+ const installCmd = cliConfig?.install || `See ${provider} documentation`;
3614
+ writeLine();
3615
+ writeLine(` ${icons.error} ${colors.red}${cliName} CLI not found${RESET}`);
3616
+ writeLine();
3617
+ writeLine(` ${colors.dim}The ${cliName} command is required to run agents but was not found on your PATH.${RESET}`);
3618
+ writeLine();
3619
+ writeLine(` ${colors.cyan}Install:${RESET} ${installCmd}`);
3620
+ writeLine();
3621
+ writeLine(` ${colors.dim}Skip this check: SQUADS_SKIP_CHECKS=1 squads run ...${RESET}`);
3622
+ writeLine();
3623
+ return false;
3624
+ }
3625
+ return true;
3626
+ }
3627
+ function buildAgentEnv(baseEnv, execContext, options) {
3628
+ const { CLAUDECODE: _, ...cleanEnv } = baseEnv;
3629
+ const env = {
3630
+ ...cleanEnv,
3631
+ SQUADS_SQUAD: execContext.squad,
3632
+ SQUADS_AGENT: execContext.agent,
3633
+ SQUADS_TASK_TYPE: execContext.taskType,
3634
+ SQUADS_TRIGGER: execContext.trigger,
3635
+ SQUADS_EXECUTION_ID: execContext.executionId,
3636
+ BRIDGE_API: getBridgeUrl()
3637
+ };
3638
+ if (options?.ghToken) env.GH_TOKEN = options.ghToken;
3639
+ if (options?.includeOtel) {
3640
+ 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}`;
3641
+ }
3642
+ if (options?.effort) env.CLAUDE_EFFORT = options.effort;
3643
+ if (options?.skills && options.skills.length > 0) env.CLAUDE_SKILLS = options.skills.join(",");
3644
+ return env;
3645
+ }
3646
+ function logVerboseExecution(config) {
3647
+ writeLine(` ${colors.dim}Project: ${config.projectRoot}${RESET}`);
3648
+ writeLine(` ${colors.dim}Mode: ${config.mode}${RESET}`);
3649
+ if (config.logFile) writeLine(` ${colors.dim}Log: ${config.logFile}${RESET}`);
3650
+ if (config.mcpConfigPath) writeLine(` ${colors.dim}MCP config: ${config.mcpConfigPath}${RESET}`);
3651
+ if (config.useApi !== void 0) writeLine(` ${colors.dim}Auth: ${config.useApi ? "API credits" : "subscription"}${RESET}`);
3652
+ writeLine(` ${colors.dim}Execution: ${config.execContext.executionId}${RESET}`);
3653
+ writeLine(` ${colors.dim}Task type: ${config.execContext.taskType}${RESET}`);
3654
+ writeLine(` ${colors.dim}Trigger: ${config.execContext.trigger}${RESET}`);
3655
+ if (config.effort) writeLine(` ${colors.dim}Effort: ${config.effort}${RESET}`);
3656
+ if (config.skills && config.skills.length > 0) writeLine(` ${colors.dim}Skills: ${config.skills.join(", ")}${RESET}`);
3657
+ if (config.resolvedModel || config.claudeModelAlias) {
3658
+ const source = config.explicitModel ? "explicit" : "auto-routed";
3659
+ const displayModel = config.resolvedModel || config.claudeModelAlias;
3660
+ writeLine(` ${colors.dim}Model: ${displayModel} (${source})${RESET}`);
3661
+ }
3662
+ }
3663
+ function resolveTargetRepoRoot(projectRoot, squad) {
3664
+ if (!squad?.repo) return projectRoot;
3665
+ const repoName = squad.repo.split("/").pop();
3666
+ if (!repoName) return projectRoot;
3667
+ const candidatePath = join5(projectRoot, "..", repoName);
3668
+ return existsSync5(candidatePath) ? candidatePath : projectRoot;
3669
+ }
3670
+ function createAgentWorktree(projectRoot, squadName, agentName) {
3671
+ const timestamp = Date.now();
3672
+ const branchName = `agent/${squadName}/${agentName}-${timestamp}`;
3673
+ const worktreePath = join5(projectRoot, "..", ".worktrees", `${squadName}-${agentName}-${timestamp}`);
3674
+ try {
3675
+ mkdirSync4(join5(projectRoot, "..", ".worktrees"), { recursive: true });
3676
+ execSync3(`git worktree add '${worktreePath}' -b '${branchName}' HEAD`, { cwd: projectRoot, stdio: "pipe" });
3677
+ return worktreePath;
3678
+ } catch (e) {
3679
+ writeLine(` ${colors.dim}warn: worktree creation failed, using project root: ${e instanceof Error ? e.message : String(e)}${RESET}`);
3680
+ return projectRoot;
3681
+ }
3682
+ }
3683
+ function cleanupWorktree(worktreePath, projectRoot) {
3684
+ if (worktreePath === projectRoot) return;
3685
+ try {
3686
+ const branchInfo = execSync3(`git -C '${projectRoot}' worktree list --porcelain`, { encoding: "utf-8" });
3687
+ let branchName = "";
3688
+ const lines = branchInfo.split("\n");
3689
+ for (let i = 0; i < lines.length; i++) {
3690
+ if (lines[i] === `worktree ${worktreePath}` && i + 2 < lines.length) {
3691
+ const branchLine = lines[i + 2];
3692
+ if (branchLine.startsWith("branch refs/heads/")) {
3693
+ branchName = branchLine.replace("branch refs/heads/", "");
3694
+ }
3695
+ break;
3696
+ }
3697
+ }
3698
+ execSync3(`git -C '${projectRoot}' worktree remove '${worktreePath}' --force`, { stdio: "pipe" });
3699
+ if (branchName && branchName.startsWith("agent/")) {
3700
+ execSync3(`git -C '${projectRoot}' branch -D '${branchName}'`, { stdio: "pipe" });
3701
+ }
3702
+ } catch {
3703
+ }
3704
+ }
3705
+ function buildDetachedShellScript(config) {
3706
+ const modelFlag = config.claudeModelAlias ? `--model ${config.claudeModelAlias}` : "";
3707
+ const branchName = `agent/${config.squadName}/${config.agentName}-${config.timestamp}`;
3708
+ const worktreeDir = `${config.projectRoot}/../.worktrees/${config.squadName}-${config.agentName}-${config.timestamp}`;
3709
+ const cleanup = `if [ "\${WORK_DIR}" != '${config.projectRoot}' ]; then git -C '${config.projectRoot}' worktree remove "\${WORK_DIR}" --force 2>/dev/null; BRANCH='${branchName}'; git -C '${config.projectRoot}' branch -D "\${BRANCH}" 2>/dev/null; fi`;
3710
+ 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}"; unset CLAUDECODE; claude --print --dangerously-skip-permissions --disable-slash-commands ${modelFlag} -- '${config.escapedPrompt}' > '${config.logFile}' 2>&1; ${cleanup}`;
3711
+ return `echo $$ > '${config.pidFile}'; ${script}`;
3712
+ }
3713
+ function prepareLogFiles(projectRoot, squadName, agentName, timestamp) {
3714
+ const logDir = join5(projectRoot, ".agents", "logs", squadName);
3715
+ const logFile = join5(logDir, `${agentName}-${timestamp}.log`);
3716
+ const pidFile = join5(logDir, `${agentName}-${timestamp}.pid`);
3717
+ if (!existsSync5(logDir)) {
3718
+ mkdirSync4(logDir, { recursive: true });
3719
+ }
3720
+ return { logDir, logFile, pidFile };
3721
+ }
3722
+ function executeForeground(config) {
3723
+ const workDir = createAgentWorktree(config.projectRoot, config.squadName, config.agentName);
3724
+ return new Promise((resolve, reject) => {
3725
+ const claude = spawn("claude", config.claudeArgs, {
3726
+ stdio: "inherit",
3727
+ cwd: workDir,
3728
+ env: config.agentEnv
3729
+ });
3730
+ claude.on("close", async (code) => {
3731
+ const durationMs = Date.now() - config.startMs;
3732
+ if (code === 0) {
3733
+ updateExecutionStatus(config.squadName, config.agentName, config.execContext.executionId, "completed", {
3734
+ outcome: "Session completed successfully",
3735
+ durationMs
3736
+ });
3737
+ const commitResult = await autoCommitAgentWork(config.squadName, config.agentName, config.execContext.executionId, config.provider);
3738
+ if (commitResult.committed) {
3739
+ writeLine();
3740
+ writeLine(` ${colors.green}Auto-committed agent work${RESET}`);
3741
+ }
3742
+ cleanupWorktree(workDir, config.projectRoot);
3743
+ resolve("Session completed");
3744
+ } else {
3745
+ updateExecutionStatus(config.squadName, config.agentName, config.execContext.executionId, "failed", {
3746
+ error: `Claude exited with code ${code}`,
3747
+ durationMs
3748
+ });
3749
+ cleanupWorktree(workDir, config.projectRoot);
3750
+ reject(new Error(`Claude exited with code ${code}`));
3751
+ }
3752
+ });
3753
+ claude.on("error", (err) => {
3754
+ const durationMs = Date.now() - config.startMs;
3755
+ updateExecutionStatus(config.squadName, config.agentName, config.execContext.executionId, "failed", {
3756
+ error: String(err),
3757
+ durationMs
3758
+ });
3759
+ cleanupWorktree(workDir, config.projectRoot);
3760
+ reject(err);
3761
+ });
3762
+ });
3763
+ }
3764
+ async function executeWatch(config) {
3765
+ const child = spawn("sh", ["-c", config.wrapperScript], {
3766
+ cwd: config.projectRoot,
3767
+ detached: true,
3768
+ stdio: "ignore",
3769
+ env: config.agentEnv
3770
+ });
3771
+ child.unref();
3772
+ await new Promise((resolve) => setTimeout(resolve, LOG_FILE_INIT_DELAY_MS));
3773
+ writeLine(` ${colors.dim}Tailing log (Ctrl+C to stop watching, agent continues)...${RESET}`);
3774
+ writeLine();
3775
+ const tail = spawn("tail", ["-f", config.logFile], { stdio: "inherit" });
3776
+ process.on("SIGINT", () => {
3777
+ tail.kill();
3778
+ writeLine();
3779
+ writeLine(` ${colors.dim}Stopped watching. Agent continues in background.${RESET}`);
3780
+ writeLine(` ${colors.dim}Resume: tail -f ${config.logFile}${RESET}`);
3781
+ process.exit(0);
3782
+ });
3783
+ return new Promise((resolve) => {
3784
+ tail.on("close", () => {
3785
+ resolve(`Agent running in background. Log: ${config.logFile}`);
3786
+ });
3787
+ });
3788
+ }
3789
+ async function executeWithClaude(prompt, options) {
3790
+ const {
3791
+ verbose,
3792
+ timeoutMinutes: _timeoutMinutes = 30,
3793
+ foreground,
3794
+ background,
3795
+ watch,
3796
+ useApi,
3797
+ effort,
3798
+ skills,
3799
+ trigger = "manual",
3800
+ squadName,
3801
+ agentName,
3802
+ model
3803
+ } = options;
3804
+ const runInBackground = background === true && !watch;
3805
+ const runInWatch = watch === true;
3806
+ const runInForeground = !runInBackground && !runInWatch;
3807
+ const startMs = Date.now();
3808
+ const projectRoot = getProjectRoot();
3809
+ ensureProjectTrusted(projectRoot);
3810
+ const squad = squadName !== "unknown" ? loadSquad(squadName) : null;
3811
+ const mcpConfigPath = selectMcpConfig(squadName, squad);
3812
+ const taskType = detectTaskType(agentName);
3813
+ const resolvedModel = resolveModel(model, squad, taskType);
3814
+ const provider = resolvedModel ? detectProviderFromModel(resolvedModel) : "anthropic";
3815
+ const targetRepoRoot = resolveTargetRepoRoot(projectRoot, squad);
3816
+ if (provider !== "anthropic" && provider !== "unknown") {
3817
+ if (verbose) {
3818
+ const source = model ? "explicit" : "auto-routed";
3819
+ writeLine(` ${colors.dim}Model: ${resolvedModel} (${source})${RESET}`);
3820
+ writeLine(` ${colors.dim}Provider: ${provider}${RESET}`);
3821
+ }
3822
+ return executeWithProvider(provider, prompt, {
3823
+ verbose,
3824
+ foreground,
3825
+ cwd: targetRepoRoot,
3826
+ squadName,
3827
+ agentName
3828
+ });
3829
+ }
3830
+ const claudeModelAlias = resolvedModel ? getClaudeModelAlias(resolvedModel) : void 0;
3831
+ const execContext = {
3832
+ squad: squadName,
3833
+ agent: agentName,
3834
+ taskType,
3835
+ trigger,
3836
+ executionId: generateExecutionId()
3837
+ };
3838
+ const { ANTHROPIC_API_KEY: _apiKey, CLAUDECODE: _claudeCode, ...envWithoutApiKey } = process.env;
3839
+ const spawnEnv = useApi ? (() => {
3840
+ const { CLAUDECODE: _, ...rest } = process.env;
3841
+ return rest;
3842
+ })() : envWithoutApiKey;
3843
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
3844
+ await registerContextWithBridge(execContext);
3845
+ let botGhToken;
3846
+ try {
3847
+ const ghEnv = await getBotGhEnv();
3848
+ botGhToken = ghEnv.GH_TOKEN;
3849
+ } catch {
3850
+ }
3851
+ if (runInForeground) {
3852
+ if (verbose) {
3853
+ logVerboseExecution({
3854
+ projectRoot,
3855
+ mode: "foreground",
3856
+ useApi,
3857
+ execContext,
3858
+ effort,
3859
+ skills,
3860
+ resolvedModel,
3861
+ claudeModelAlias,
3862
+ explicitModel: model
3863
+ });
3864
+ }
3865
+ const claudeArgs = [];
3866
+ if (!process.stdin.isTTY) claudeArgs.push("--print");
3867
+ claudeArgs.push("--dangerously-skip-permissions");
3868
+ claudeArgs.push("--disable-slash-commands");
3869
+ if (mcpConfigPath) claudeArgs.push("--mcp-config", mcpConfigPath);
3870
+ if (claudeModelAlias) claudeArgs.push("--model", claudeModelAlias);
3871
+ claudeArgs.push("--", prompt);
3872
+ const agentEnv2 = buildAgentEnv(spawnEnv, execContext, {
3873
+ effort,
3874
+ skills,
3875
+ includeOtel: true,
3876
+ ghToken: botGhToken
3877
+ });
3878
+ return executeForeground({
3879
+ prompt,
3880
+ claudeArgs,
3881
+ agentEnv: agentEnv2,
3882
+ projectRoot: targetRepoRoot,
3883
+ squadName,
3884
+ agentName,
3885
+ execContext,
3886
+ startMs,
3887
+ provider
3888
+ });
3889
+ }
3890
+ const timestamp = Date.now();
3891
+ const { logFile, pidFile } = prepareLogFiles(projectRoot, squadName, agentName, timestamp);
3892
+ const agentEnv = buildAgentEnv(spawnEnv, execContext, {
3893
+ effort,
3894
+ skills,
3895
+ includeOtel: !runInWatch,
3896
+ ghToken: botGhToken
3897
+ });
3898
+ const wrapperScript = buildDetachedShellScript({
3899
+ projectRoot: targetRepoRoot,
3900
+ squadName,
3901
+ agentName,
3902
+ timestamp,
3903
+ claudeModelAlias,
3904
+ escapedPrompt,
3905
+ logFile,
3906
+ pidFile
3907
+ });
3908
+ if (runInWatch) {
3909
+ if (verbose) {
3910
+ logVerboseExecution({
3911
+ projectRoot,
3912
+ mode: "watch (background + tail)",
3913
+ execContext,
3914
+ logFile
3915
+ });
3916
+ }
3917
+ return executeWatch({ projectRoot: targetRepoRoot, agentEnv, logFile, wrapperScript });
3918
+ }
3919
+ if (verbose) {
3920
+ logVerboseExecution({
3921
+ projectRoot,
3922
+ mode: "background",
3923
+ useApi,
3924
+ execContext,
3925
+ effort,
3926
+ skills,
3927
+ resolvedModel,
3928
+ claudeModelAlias,
3929
+ explicitModel: model,
3930
+ logFile,
3931
+ mcpConfigPath
3932
+ });
3933
+ }
3934
+ const child = spawn("sh", ["-c", wrapperScript], {
3935
+ cwd: targetRepoRoot,
3936
+ detached: true,
3937
+ stdio: "ignore",
3938
+ env: agentEnv
3939
+ });
3940
+ child.unref();
3941
+ if (verbose) {
3942
+ writeLine(` ${colors.dim}Monitor: tail -f ${logFile}${RESET}`);
3943
+ }
3944
+ return `Log: ${logFile}. Monitor: tail -f ${logFile}`;
3945
+ }
3946
+ async function executeWithProvider(provider, prompt, options) {
3947
+ const cliConfig = getCLIConfig(provider);
3948
+ if (!cliConfig) {
3949
+ throw new Error(`Unknown provider: ${provider}. Run 'squads providers' to see available providers.`);
3950
+ }
3951
+ if (!isProviderCLIAvailable(provider)) {
3952
+ throw new Error(`CLI '${cliConfig.command}' not found. Install: ${cliConfig.install}`);
3953
+ }
3954
+ const projectRoot = options.cwd || getProjectRoot();
3955
+ const squadName = options.squadName || "unknown";
3956
+ const agentName = options.agentName || "unknown";
3957
+ const timestamp = Date.now();
3958
+ const { CLAUDECODE: _claudeCode, ...cleanEnv } = process.env;
3959
+ const providerEnv = {
3960
+ ...cleanEnv,
3961
+ SQUADS_SQUAD: squadName,
3962
+ SQUADS_AGENT: agentName,
3963
+ SQUADS_PROVIDER: provider
3964
+ };
3965
+ const branchName = `agent/${squadName}/${agentName}-${timestamp}`;
3966
+ const worktreePath = join5(projectRoot, "..", ".worktrees", `${squadName}-${agentName}-${timestamp}`);
3967
+ let workDir = projectRoot;
3968
+ try {
3969
+ mkdirSync4(join5(projectRoot, "..", ".worktrees"), { recursive: true });
3970
+ execSync3(`git worktree add '${worktreePath}' -b '${branchName}' HEAD`, { cwd: projectRoot, stdio: "pipe" });
3971
+ workDir = worktreePath;
3972
+ } catch (e) {
3973
+ writeLine(` ${colors.dim}warn: worktree creation failed, using project root: ${e instanceof Error ? e.message : String(e)}${RESET}`);
3974
+ }
3975
+ let effectivePrompt = prompt;
3976
+ if (workDir !== projectRoot) {
3977
+ const agentsDir = join5(projectRoot, ".agents");
3978
+ const targetAgentsDir = join5(workDir, ".agents");
3979
+ if (existsSync5(agentsDir) && !existsSync5(targetAgentsDir)) {
3980
+ try {
3981
+ cpSync(agentsDir, targetAgentsDir, { recursive: true });
3982
+ } catch (e) {
3983
+ writeLine(` ${colors.dim}warn: .agents copy failed: ${e instanceof Error ? e.message : String(e)}${RESET}`);
3984
+ }
3985
+ }
3986
+ effectivePrompt = prompt.replaceAll(projectRoot, workDir);
3987
+ }
3988
+ const args = cliConfig.buildArgs(effectivePrompt);
3989
+ if (options.verbose) {
3990
+ writeLine(` ${colors.dim}Provider: ${cliConfig.displayName}${RESET}`);
3991
+ writeLine(` ${colors.dim}Command: ${cliConfig.command} ${args.join(" ").slice(0, VERBOSE_COMMAND_MAX_CHARS)}...${RESET}`);
3992
+ writeLine(` ${colors.dim}CWD: ${workDir}${RESET}`);
3993
+ if (workDir !== projectRoot) {
3994
+ writeLine(` ${colors.dim}Worktree: ${branchName}${RESET}`);
3995
+ }
3996
+ }
3997
+ if (options.foreground) {
3998
+ return new Promise((resolve, reject) => {
3999
+ const proc = spawn(cliConfig.command, args, {
4000
+ stdio: "inherit",
4001
+ cwd: workDir,
4002
+ env: providerEnv
4003
+ });
4004
+ proc.on("close", (code) => {
4005
+ cleanupWorktree(workDir, projectRoot);
4006
+ if (code === 0) {
4007
+ resolve("Session completed");
4008
+ } else {
4009
+ reject(new Error(`${cliConfig.command} exited with code ${code}`));
4010
+ }
4011
+ });
4012
+ proc.on("error", (err) => {
4013
+ cleanupWorktree(workDir, projectRoot);
4014
+ reject(err);
4015
+ });
4016
+ });
4017
+ }
4018
+ const logDir = join5(projectRoot, ".agents", "logs", squadName);
4019
+ const logFile = join5(logDir, `${agentName}-${timestamp}.log`);
4020
+ const pidFile = join5(logDir, `${agentName}-${timestamp}.pid`);
4021
+ if (!existsSync5(logDir)) {
4022
+ mkdirSync4(logDir, { recursive: true });
4023
+ }
4024
+ const escapedPrompt = effectivePrompt.replace(/'/g, "'\\''");
4025
+ const providerArgs = cliConfig.buildArgs(escapedPrompt).map((a) => `'${a}'`).join(" ");
4026
+ const cleanupCmd = workDir !== projectRoot ? `; git -C '${projectRoot}' worktree remove '${workDir}' --force 2>/dev/null; git -C '${projectRoot}' branch -D '${branchName}' 2>/dev/null` : "";
4027
+ const shellScript = `cd '${workDir}' && ${cliConfig.command} ${providerArgs} > '${logFile}' 2>&1${cleanupCmd}`;
4028
+ const wrapperScript = `echo $$ > '${pidFile}'; ${shellScript}`;
4029
+ const child = spawn("sh", ["-c", wrapperScript], {
4030
+ cwd: workDir,
4031
+ detached: true,
4032
+ stdio: "ignore",
4033
+ env: providerEnv
4034
+ });
4035
+ child.unref();
4036
+ if (options.verbose) {
4037
+ writeLine(` ${colors.dim}Log: ${logFile}${RESET}`);
4038
+ writeLine(` ${colors.dim}PID file: ${pidFile}${RESET}`);
4039
+ }
4040
+ return `Log: ${logFile}. Monitor: tail -f ${logFile}`;
4041
+ }
4042
+ async function runSquadCommand(squadName, options) {
4043
+ return runCommand(squadName, options);
4044
+ }
4045
+ export {
4046
+ runCommand,
4047
+ runSquadCommand
4048
+ };
4049
+ //# sourceMappingURL=run-I6KAXU6U.js.map