@iaforged/context-code 1.2.8 → 1.2.10

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 (172) hide show
  1. package/README.md +119 -119
  2. package/context-bootstrap.js +26 -26
  3. package/dist/src/QueryEngine.js +394 -327
  4. package/dist/src/bridge/bridgeUI.js +1 -1
  5. package/dist/src/buddy/prompt.js +4 -4
  6. package/dist/src/cli/handlers/auth.js +126 -9
  7. package/dist/src/cli/print.js +35 -1
  8. package/dist/src/commands/agent/agent.js +28 -2
  9. package/dist/src/commands/agent/agentStore.js +8 -1
  10. package/dist/src/commands/agent/index.js +1 -1
  11. package/dist/src/commands/bridge-kick.js +9 -9
  12. package/dist/src/commands/commit.js +34 -34
  13. package/dist/src/commands/init-verifiers.js +3 -3
  14. package/dist/src/commands/init.js +88 -88
  15. package/dist/src/commands/insights.js +787 -787
  16. package/dist/src/commands/install.js +19 -19
  17. package/dist/src/commands/login/login.js +21 -12
  18. package/dist/src/commands/logout/logout.js +9 -0
  19. package/dist/src/commands/model/model.js +9 -4
  20. package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
  21. package/dist/src/commands/orchestrate/index.js +2 -2
  22. package/dist/src/commands/orchestrate/orchestrate.js +708 -12
  23. package/dist/src/commands/pr_comments/index.js +33 -33
  24. package/dist/src/commands/profile/index.js +1 -1
  25. package/dist/src/commands/profile/profile.js +52 -3
  26. package/dist/src/commands/provider/index.js +1 -1
  27. package/dist/src/commands/provider/provider.js +117 -45
  28. package/dist/src/commands/resumen/index.js +9 -0
  29. package/dist/src/commands/resumen/resumen.js +29 -0
  30. package/dist/src/commands/security-review.js +190 -190
  31. package/dist/src/commands/swarm-auto/index.js +9 -0
  32. package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
  33. package/dist/src/commands/swarm-init/index.js +9 -0
  34. package/dist/src/commands/swarm-init/swarmInit.js +72 -0
  35. package/dist/src/commands/team/team.js +39 -6
  36. package/dist/src/commands.js +14 -0
  37. package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
  38. package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
  39. package/dist/src/components/agents/agentFileUtils.js +6 -6
  40. package/dist/src/components/permissions/hooks.js +5 -5
  41. package/dist/src/constants/outputStyles.js +83 -83
  42. package/dist/src/core/agents/blueprints.js +58 -0
  43. package/dist/src/core/agents/cliAdapter.js +61 -0
  44. package/dist/src/core/agents/registry.js +93 -0
  45. package/dist/src/core/agents/runtime.js +4 -0
  46. package/dist/src/core/agents/runtime.smoke.js +42 -0
  47. package/dist/src/core/agents/swarm.smoke.js +48 -0
  48. package/dist/src/core/agents/swarmTools.js +38 -0
  49. package/dist/src/core/auth/index.js +2 -0
  50. package/dist/src/core/auth/loginCliAdapter.js +24 -0
  51. package/dist/src/core/auth/loginCore.js +67 -0
  52. package/dist/src/core/auth/logoutCliAdapter.js +34 -0
  53. package/dist/src/core/auth/logoutCore.js +52 -0
  54. package/dist/src/core/auth/preflight.smoke.js +151 -0
  55. package/dist/src/core/index.js +21 -0
  56. package/dist/src/core/mcp/blueprints.js +27 -0
  57. package/dist/src/core/mcp/common.js +14 -0
  58. package/dist/src/core/mcp/runtime.js +67 -0
  59. package/dist/src/core/mcp/runtime.smoke.js +50 -0
  60. package/dist/src/core/mcp/swarmClient.js +40 -0
  61. package/dist/src/core/mcp/swarmSetup.js +43 -0
  62. package/dist/src/core/providers/cliAdapter.js +39 -0
  63. package/dist/src/core/providers/contracts.js +1 -0
  64. package/dist/src/core/providers/index.js +3 -0
  65. package/dist/src/core/providers/llmCore.js +123 -0
  66. package/dist/src/core/providers/providerCore.js +141 -0
  67. package/dist/src/core/providers/providerModelCompatibility.js +98 -0
  68. package/dist/src/core/providers/providerParitySmoke.js +83 -0
  69. package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
  70. package/dist/src/core/query/contracts.js +1 -0
  71. package/dist/src/core/query/runtime.js +117 -0
  72. package/dist/src/core/query/runtime.smoke.js +39 -0
  73. package/dist/src/core/query/timelineThinking.smoke.js +25 -0
  74. package/dist/src/core/query/wiring.smoke.js +76 -0
  75. package/dist/src/core/skills/cliAdapter.js +38 -0
  76. package/dist/src/core/skills/index.js +52 -0
  77. package/dist/src/core/skills/runtime.smoke.js +53 -0
  78. package/dist/src/core/tasks/runtime.js +205 -0
  79. package/dist/src/core/tasks/runtime.smoke.js +63 -0
  80. package/dist/src/core/tasks/sdkAdapter.js +4 -0
  81. package/dist/src/core/tools/contracts.js +3 -0
  82. package/dist/src/core/tools/fileResolution.js +112 -0
  83. package/dist/src/core/tools/fileResolution.smoke.js +33 -0
  84. package/dist/src/core/tools/filesCore.js +51 -0
  85. package/dist/src/core/tools/filesCore.smoke.js +108 -0
  86. package/dist/src/core/tools/gitCore.js +20 -0
  87. package/dist/src/core/tools/imageParity.smoke.js +36 -0
  88. package/dist/src/core/tools/notebookParity.smoke.js +68 -0
  89. package/dist/src/core/tools/registry.js +22 -0
  90. package/dist/src/core/tools/runtime.smoke.js +32 -0
  91. package/dist/src/core/tools/shellCore.js +60 -0
  92. package/dist/src/core/types/agentContext.js +9 -0
  93. package/dist/src/core/types/auth.js +3 -0
  94. package/dist/src/core/types/command.js +13 -0
  95. package/dist/src/core/types/provider.js +3 -0
  96. package/dist/src/core/types/sdkEvent.js +10 -0
  97. package/dist/src/core/types/swarm.js +1 -0
  98. package/dist/src/cost-tracker.js +3 -3
  99. package/dist/src/hooks/useAwaySummary.js +22 -9
  100. package/dist/src/main.js +32 -2
  101. package/dist/src/screens/REPL.js +9 -0
  102. package/dist/src/services/AgentSummary/agentSummary.js +10 -10
  103. package/dist/src/services/autoDream/autoDream.js +5 -5
  104. package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
  105. package/dist/src/services/compact/prompt.js +238 -238
  106. package/dist/src/services/limits/sessionCounter.js +17 -17
  107. package/dist/src/services/mcp/client.js +27 -1
  108. package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
  109. package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
  110. package/dist/src/skills/bundled/loop.js +57 -57
  111. package/dist/src/skills/bundled/remember.js +53 -53
  112. package/dist/src/skills/bundled/simplify.js +49 -49
  113. package/dist/src/skills/bundled/skillify.js +2 -2
  114. package/dist/src/state/onChangeAppState.js +6 -0
  115. package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
  116. package/dist/src/tasks/LocalMainSessionTask.js +5 -5
  117. package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
  118. package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
  119. package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
  120. package/dist/src/tools/BashTool/BashTool.js +27 -2
  121. package/dist/src/tools/BriefTool/prompt.js +14 -14
  122. package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
  123. package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
  124. package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
  125. package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
  126. package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
  127. package/dist/src/tools/FileEditTool/prompt.js +7 -7
  128. package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
  129. package/dist/src/tools/FileWriteTool/prompt.js +6 -6
  130. package/dist/src/tools/GlobTool/prompt.js +4 -4
  131. package/dist/src/tools/GrepTool/prompt.js +10 -10
  132. package/dist/src/tools/LSPTool/prompt.js +18 -18
  133. package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
  134. package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
  135. package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
  136. package/dist/src/tools/SendMessageTool/prompt.js +36 -36
  137. package/dist/src/tools/SkillTool/prompt.js +21 -21
  138. package/dist/src/tools/SleepTool/prompt.js +10 -10
  139. package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
  140. package/dist/src/tools/TaskGetTool/prompt.js +21 -21
  141. package/dist/src/tools/TaskListTool/prompt.js +30 -30
  142. package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
  143. package/dist/src/tools/TaskStopTool/prompt.js +5 -5
  144. package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
  145. package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
  146. package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
  147. package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
  148. package/dist/src/tools/WebFetchTool/prompt.js +31 -31
  149. package/dist/src/tools/WebSearchTool/prompt.js +26 -26
  150. package/dist/src/utils/agentContext.js +2 -0
  151. package/dist/src/utils/agenticSessionSearch.js +38 -38
  152. package/dist/src/utils/config.js +2 -0
  153. package/dist/src/utils/genericProcessUtils.js +21 -21
  154. package/dist/src/utils/heapDumpService.js +4 -4
  155. package/dist/src/utils/mcpValidation.js +2 -2
  156. package/dist/src/utils/model/modelStrings.js +1 -1
  157. package/dist/src/utils/model/providers.js +5 -0
  158. package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
  159. package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
  160. package/dist/src/utils/orchestration/store/runStore.js +68 -68
  161. package/dist/src/utils/orchestration/store/teamStore.js +28 -28
  162. package/dist/src/utils/permissions/permissionExplainer.js +6 -6
  163. package/dist/src/utils/permissions/permissionsDb.js +43 -43
  164. package/dist/src/utils/sdkEventQueue.js +2 -0
  165. package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
  166. package/dist/src/utils/standardMcp/common.js +15 -0
  167. package/dist/src/utils/standardMcp/setup.js +52 -0
  168. package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
  169. package/dist/src/utils/task/framework.js +6 -6
  170. package/package.json +1 -1
  171. package/dist/src/commands/usage/index.js +0 -7
  172. package/dist/src/commands/usage/usage.js +0 -5
@@ -12,6 +12,7 @@ import memoize from 'lodash-es/memoize.js';
12
12
  import zipObject from 'lodash-es/zipObject.js';
13
13
  import pMap from 'p-map';
14
14
  import { getOriginalCwd, getSessionId } from '../../bootstrap/state.js';
15
+ import { coreMcpRuntime, isCoreMcpEnabled } from '../../core/mcp/runtime.js';
15
16
  import { getOauthConfig } from '../../constants/oauth.js';
16
17
  import { PRODUCT_URL } from '../../constants/product.js';
17
18
  import { toolMatchesName, } from '../../Tool.js';
@@ -310,6 +311,12 @@ const IMAGE_MIME_TYPES = new Set([
310
311
  function getConnectionTimeoutMs() {
311
312
  return parseInt(process.env.MCP_TIMEOUT || '', 10) || 30000;
312
313
  }
314
+ function syncCoreMcpServer(connection, extras) {
315
+ if (!isCoreMcpEnabled()) {
316
+ return;
317
+ }
318
+ coreMcpRuntime.upsertServer(connection, extras);
319
+ }
313
320
  /**
314
321
  * Default timeout for individual MCP requests (auth, tool calls, etc.)
315
322
  */
@@ -1320,6 +1327,7 @@ export const fetchToolsForClient = memoizeWithLRU(async (client) => {
1320
1327
  return [];
1321
1328
  try {
1322
1329
  if (!client.capabilities?.tools) {
1330
+ syncCoreMcpServer(client, { tools: [] });
1323
1331
  return [];
1324
1332
  }
1325
1333
  const result = (await client.client.request({ method: 'tools/list' }, ListToolsResultSchema));
@@ -1329,7 +1337,7 @@ export const fetchToolsForClient = memoizeWithLRU(async (client) => {
1329
1337
  const skipPrefix = client.config.type === 'sdk' &&
1330
1338
  isEnvTruthy(process.env.CLAUDE_AGENT_SDK_MCP_NO_PREFIX);
1331
1339
  // Convert MCP tools to our Tool format
1332
- return toolsToProcess
1340
+ const mappedTools = toolsToProcess
1333
1341
  .map((tool) => {
1334
1342
  const fullyQualifiedName = buildMcpToolName(client.name, tool.name);
1335
1343
  return {
@@ -1523,6 +1531,8 @@ export const fetchToolsForClient = memoizeWithLRU(async (client) => {
1523
1531
  };
1524
1532
  })
1525
1533
  .filter(isIncludedMcpTool);
1534
+ syncCoreMcpServer(client, { tools: mappedTools });
1535
+ return mappedTools;
1526
1536
  }
1527
1537
  catch (error) {
1528
1538
  logMCPError(client.name, `Failed to fetch tools: ${errorMessage(error)}`);
@@ -1641,6 +1651,7 @@ export async function reconnectMcpServerImpl(name, config) {
1641
1651
  await clearServerCache(name, config);
1642
1652
  const client = await connectToServer(name, config);
1643
1653
  if (client.type !== 'connected') {
1654
+ syncCoreMcpServer(client);
1644
1655
  return {
1645
1656
  client,
1646
1657
  tools: [],
@@ -1660,6 +1671,11 @@ export async function reconnectMcpServerImpl(name, config) {
1660
1671
  supportsResources ? fetchResourcesForClient(client) : Promise.resolve([]),
1661
1672
  ]);
1662
1673
  const commands = [...mcpCommands, ...mcpSkills];
1674
+ syncCoreMcpServer(client, {
1675
+ tools,
1676
+ commands,
1677
+ resources,
1678
+ });
1663
1679
  // Check if we need to add resource tools
1664
1680
  const resourceTools = [];
1665
1681
  if (supportsResources) {
@@ -1770,6 +1786,7 @@ export async function getMcpToolsCommandsAndResources(onConnectionAttempt, mcpCo
1770
1786
  }
1771
1787
  const client = await connectToServer(name, config, serverStats);
1772
1788
  if (client.type !== 'connected') {
1789
+ syncCoreMcpServer(client);
1773
1790
  onConnectionAttempt({
1774
1791
  client,
1775
1792
  tools: client.type === 'needs-auth'
@@ -1796,6 +1813,11 @@ export async function getMcpToolsCommandsAndResources(onConnectionAttempt, mcpCo
1796
1813
  : Promise.resolve([]),
1797
1814
  ]);
1798
1815
  const commands = [...mcpCommands, ...mcpSkills];
1816
+ syncCoreMcpServer(client, {
1817
+ tools,
1818
+ commands,
1819
+ resources,
1820
+ });
1799
1821
  // If this server resources and we haven't added resource tools yet,
1800
1822
  // include our resource tools with this client's tools
1801
1823
  const resourceTools = [];
@@ -1819,6 +1841,7 @@ export async function getMcpToolsCommandsAndResources(onConnectionAttempt, mcpCo
1819
1841
  tools: [],
1820
1842
  commands: [],
1821
1843
  });
1844
+ syncCoreMcpServer({ name, type: 'failed', config });
1822
1845
  }
1823
1846
  };
1824
1847
  // Process both groups concurrently, each with their own concurrency limits:
@@ -2473,6 +2496,9 @@ export async function setupSdkMcpClients(sdkMcpConfigs, sendMcpMessage) {
2473
2496
  if (result.status === 'fulfilled') {
2474
2497
  clients.push(result.value.client);
2475
2498
  tools.push(...result.value.tools);
2499
+ syncCoreMcpServer(result.value.client, {
2500
+ tools: result.value.tools,
2501
+ });
2476
2502
  }
2477
2503
  // If rejected (unexpected), the error was already logged inside the promise
2478
2504
  }
@@ -137,27 +137,46 @@ function extractAnthropicOutput(payload) {
137
137
  .filter(Boolean)
138
138
  .join('\n') || null);
139
139
  }
140
- async function postJson(url, headers, body) {
141
- const response = await fetch(url, {
142
- method: 'POST',
143
- headers: {
144
- 'Content-Type': 'application/json',
145
- ...headers,
146
- },
147
- body: JSON.stringify(body),
148
- });
149
- const text = await response.text();
150
- const data = tryParseJson(text) ?? text;
151
- if (!response.ok) {
152
- return {
153
- ok: false,
154
- status: response.status,
155
- error: typeof data === 'string'
156
- ? data
157
- : JSON.stringify(data),
158
- };
140
+ async function postJson(url, headers, body, retries = 3) {
141
+ let lastError = null;
142
+ for (let attempt = 0; attempt <= retries; attempt++) {
143
+ if (attempt > 0) {
144
+ const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
145
+ await new Promise(resolve => setTimeout(resolve, delay));
146
+ }
147
+ try {
148
+ const response = await fetch(url, {
149
+ method: 'POST',
150
+ headers: {
151
+ 'Content-Type': 'application/json',
152
+ ...headers,
153
+ },
154
+ body: JSON.stringify(body),
155
+ });
156
+ const text = await response.text();
157
+ const data = tryParseJson(text) ?? text;
158
+ if (!response.ok) {
159
+ lastError = {
160
+ status: response.status,
161
+ error: typeof data === 'string' ? data : JSON.stringify(data),
162
+ };
163
+ // Only retry on rate limits (429) or server errors (5xx)
164
+ if (response.status === 429 || response.status >= 500) {
165
+ continue;
166
+ }
167
+ return { ok: false, ...lastError };
168
+ }
169
+ return { ok: true, data };
170
+ }
171
+ catch (e) {
172
+ lastError = {
173
+ status: 0,
174
+ error: e instanceof Error ? e.message : String(e),
175
+ };
176
+ continue;
177
+ }
159
178
  }
160
- return { ok: true, data };
179
+ return { ok: false, ...lastError };
161
180
  }
162
181
  async function executeOpenAICompatible(input, model, baseUrl, credential) {
163
182
  const headers = {};
@@ -205,17 +205,17 @@ async function loadRunState(runId) {
205
205
  }
206
206
  const db = await getOrchestrationDatabase();
207
207
  const tasks = db
208
- .prepare(`SELECT id, run_id, parent_task_id, scope_type, team_unit_id, assigned_agent_id, title, instructions, status, result_summary, created_at, finished_at
209
- FROM orchestration_tasks
210
- WHERE run_id = ?
208
+ .prepare(`SELECT id, run_id, parent_task_id, scope_type, team_unit_id, assigned_agent_id, title, instructions, status, result_summary, created_at, finished_at
209
+ FROM orchestration_tasks
210
+ WHERE run_id = ?
211
211
  ORDER BY created_at, id`)
212
212
  .all(requireText(runId, 'runId'))
213
213
  .map(rowToTask)
214
214
  .filter((task) => task !== null);
215
215
  const messages = db
216
- .prepare(`SELECT id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
217
- FROM orchestration_messages
218
- WHERE run_id = ?
216
+ .prepare(`SELECT id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
217
+ FROM orchestration_messages
218
+ WHERE run_id = ?
219
219
  ORDER BY created_at, id`)
220
220
  .all(requireText(runId, 'runId'))
221
221
  .map(rowToMessage)
@@ -225,8 +225,8 @@ async function loadRunState(runId) {
225
225
  async function loadTaskAndRunState(taskId) {
226
226
  const db = await getOrchestrationDatabase();
227
227
  const row = db
228
- .prepare(`SELECT id, run_id, parent_task_id, scope_type, team_unit_id, assigned_agent_id, title, instructions, status, result_summary, created_at, finished_at
229
- FROM orchestration_tasks
228
+ .prepare(`SELECT id, run_id, parent_task_id, scope_type, team_unit_id, assigned_agent_id, title, instructions, status, result_summary, created_at, finished_at
229
+ FROM orchestration_tasks
230
230
  WHERE id = ?`)
231
231
  .get(requireText(taskId, 'taskId'));
232
232
  const task = rowToTask(row);
@@ -242,8 +242,8 @@ async function loadTaskAndRunState(taskId) {
242
242
  async function updateTasksByRun(runId, transform) {
243
243
  const db = await getOrchestrationDatabase();
244
244
  const rows = db
245
- .prepare(`SELECT id, run_id, parent_task_id, scope_type, team_unit_id, assigned_agent_id, title, instructions, status, result_summary, created_at, finished_at
246
- FROM orchestration_tasks
245
+ .prepare(`SELECT id, run_id, parent_task_id, scope_type, team_unit_id, assigned_agent_id, title, instructions, status, result_summary, created_at, finished_at
246
+ FROM orchestration_tasks
247
247
  WHERE run_id = ?`)
248
248
  .all(requireText(runId, 'runId'))
249
249
  .map(rowToTask)
@@ -253,8 +253,8 @@ async function updateTasksByRun(runId, transform) {
253
253
  if (!patch) {
254
254
  continue;
255
255
  }
256
- db.prepare(`UPDATE orchestration_tasks
257
- SET status = ?, result_summary = ?, finished_at = ?
256
+ db.prepare(`UPDATE orchestration_tasks
257
+ SET status = ?, result_summary = ?, finished_at = ?
258
258
  WHERE id = ?`).run(patch.status ?? task.status, patch.result_summary ?? task.resultSummary ?? null, patch.finished_at ?? task.finishedAt ?? null, task.id);
259
259
  }
260
260
  }
@@ -271,8 +271,8 @@ async function writeRunTransition(runId, nextStatus, options) {
271
271
  const finishedAt = options?.finishedAt === undefined
272
272
  ? current.finishedAt
273
273
  : toNullableText(options.finishedAt);
274
- db.prepare(`UPDATE orchestration_runs
275
- SET status = ?, started_at = ?, finished_at = ?
274
+ db.prepare(`UPDATE orchestration_runs
275
+ SET status = ?, started_at = ?, finished_at = ?
276
276
  WHERE id = ?`).run(nextStatus, startedAt, finishedAt, runId);
277
277
  }
278
278
  async function writeTaskTransition(taskId, nextStatus, options) {
@@ -287,8 +287,8 @@ async function writeTaskTransition(taskId, nextStatus, options) {
287
287
  ? nowIso()
288
288
  : null
289
289
  : toNullableText(options.finishedAt);
290
- db.prepare(`UPDATE orchestration_tasks
291
- SET status = ?, result_summary = ?, finished_at = ?
290
+ db.prepare(`UPDATE orchestration_tasks
291
+ SET status = ?, result_summary = ?, finished_at = ?
292
292
  WHERE id = ?`).run(nextStatus, options?.resultSummary === undefined
293
293
  ? current.task.resultSummary
294
294
  : toNullableText(options.resultSummary), finishedAt, taskId);
@@ -407,20 +407,20 @@ export class OrchestrationExecutionRuntime {
407
407
  const changed = runState.run.status !== 'in_progress';
408
408
  await withOrchestrationTransaction(async (db) => {
409
409
  const startedAt = runState.run.startedAt ?? nowIso();
410
- db.prepare(`UPDATE orchestration_runs
411
- SET status = ?, started_at = ?, finished_at = NULL
410
+ db.prepare(`UPDATE orchestration_runs
411
+ SET status = ?, started_at = ?, finished_at = NULL
412
412
  WHERE id = ?`).run('in_progress', startedAt, runId);
413
413
  if (input.promotePendingTasks !== false) {
414
414
  for (const task of runState.tasks) {
415
415
  if (task.status === 'pending' || task.status === 'blocked') {
416
- db.prepare(`UPDATE orchestration_tasks
417
- SET status = ?, finished_at = NULL
416
+ db.prepare(`UPDATE orchestration_tasks
417
+ SET status = ?, finished_at = NULL
418
418
  WHERE id = ?`).run('ready', task.id);
419
419
  }
420
420
  }
421
421
  }
422
- db.prepare(`INSERT INTO orchestration_messages (
423
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
422
+ db.prepare(`INSERT INTO orchestration_messages (
423
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
424
424
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, null, null, null, 'run.start', nowMessage('start', input.note), nowIso());
425
425
  });
426
426
  const refreshed = await loadRunState(runId);
@@ -452,20 +452,20 @@ export class OrchestrationExecutionRuntime {
452
452
  const changed = runState.run.status !== 'in_progress';
453
453
  await withOrchestrationTransaction(async (db) => {
454
454
  const startedAt = runState.run.startedAt ?? nowIso();
455
- db.prepare(`UPDATE orchestration_runs
456
- SET status = ?, started_at = ?, finished_at = NULL
455
+ db.prepare(`UPDATE orchestration_runs
456
+ SET status = ?, started_at = ?, finished_at = NULL
457
457
  WHERE id = ?`).run('in_progress', startedAt, runId);
458
458
  if (input.reactivateBlockedTasks !== false) {
459
459
  for (const task of runState.tasks) {
460
460
  if (task.status === 'blocked' || task.status === 'failed' || task.status === 'pending') {
461
- db.prepare(`UPDATE orchestration_tasks
462
- SET status = ?, finished_at = NULL
461
+ db.prepare(`UPDATE orchestration_tasks
462
+ SET status = ?, finished_at = NULL
463
463
  WHERE id = ?`).run('ready', task.id);
464
464
  }
465
465
  }
466
466
  }
467
- db.prepare(`INSERT INTO orchestration_messages (
468
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
467
+ db.prepare(`INSERT INTO orchestration_messages (
468
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
469
469
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, null, null, null, 'run.resume', nowMessage('resume', input.note), nowIso());
470
470
  });
471
471
  const refreshed = await loadRunState(runId);
@@ -494,20 +494,20 @@ export class OrchestrationExecutionRuntime {
494
494
  const changed = runState.run.status !== 'cancelled';
495
495
  await withOrchestrationTransaction(async (db) => {
496
496
  const finishedAt = nowIso();
497
- db.prepare(`UPDATE orchestration_runs
498
- SET status = ?, finished_at = ?
497
+ db.prepare(`UPDATE orchestration_runs
498
+ SET status = ?, finished_at = ?
499
499
  WHERE id = ?`).run('cancelled', finishedAt, runId);
500
500
  if (input.blockOpenTasks !== false) {
501
501
  for (const task of runState.tasks) {
502
502
  if (task.status !== 'completed') {
503
- db.prepare(`UPDATE orchestration_tasks
504
- SET status = ?, finished_at = ?
503
+ db.prepare(`UPDATE orchestration_tasks
504
+ SET status = ?, finished_at = ?
505
505
  WHERE id = ?`).run('blocked', finishedAt, task.id);
506
506
  }
507
507
  }
508
508
  }
509
- db.prepare(`INSERT INTO orchestration_messages (
510
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
509
+ db.prepare(`INSERT INTO orchestration_messages (
510
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
511
511
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, null, null, null, 'run.cancel', nowMessage('cancel', input.reason), nowIso());
512
512
  });
513
513
  const refreshed = await loadRunState(runId);
@@ -529,16 +529,16 @@ export class OrchestrationExecutionRuntime {
529
529
  const changed = current.status !== input.status;
530
530
  await withOrchestrationTransaction(async (db) => {
531
531
  validateRunTransition(current.status, input.status);
532
- db.prepare(`UPDATE orchestration_runs
533
- SET status = ?, started_at = ?, finished_at = ?
532
+ db.prepare(`UPDATE orchestration_runs
533
+ SET status = ?, started_at = ?, finished_at = ?
534
534
  WHERE id = ?`).run(input.status, input.startedAt === undefined
535
535
  ? current.startedAt
536
536
  : toNullableText(input.startedAt), input.finishedAt === undefined
537
537
  ? current.finishedAt
538
538
  : toNullableText(input.finishedAt), runId);
539
539
  if (input.note) {
540
- db.prepare(`INSERT INTO orchestration_messages (
541
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
540
+ db.prepare(`INSERT INTO orchestration_messages (
541
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
542
542
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, null, null, null, 'run.transition', nowMessage('transition-run', input.note), nowIso());
543
543
  }
544
544
  });
@@ -561,8 +561,8 @@ export class OrchestrationExecutionRuntime {
561
561
  const changed = loaded.task.status !== input.status;
562
562
  await withOrchestrationTransaction(async (db) => {
563
563
  validateTaskTransition(loaded.task.status, input.status);
564
- db.prepare(`UPDATE orchestration_tasks
565
- SET status = ?, result_summary = ?, finished_at = ?
564
+ db.prepare(`UPDATE orchestration_tasks
565
+ SET status = ?, result_summary = ?, finished_at = ?
566
566
  WHERE id = ?`).run(input.status, input.resultSummary === undefined
567
567
  ? loaded.task.resultSummary
568
568
  : toNullableText(input.resultSummary), input.finishedAt === undefined
@@ -571,8 +571,8 @@ export class OrchestrationExecutionRuntime {
571
571
  : null
572
572
  : toNullableText(input.finishedAt), taskId);
573
573
  if (input.note) {
574
- db.prepare(`INSERT INTO orchestration_messages (
575
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
574
+ db.prepare(`INSERT INTO orchestration_messages (
575
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
576
576
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), loaded.run.id, taskId, null, null, 'task.transition', nowMessage('transition-task', input.note), nowIso());
577
577
  }
578
578
  });
@@ -654,6 +654,7 @@ export class OrchestrationExecutionRuntime {
654
654
  domainName,
655
655
  phase: 'blocked',
656
656
  detail: 'sin agente asignado',
657
+ output: summary,
657
658
  progressPercent: Math.min(100, Math.round(((processedReadyTasks + 1) / totalReadyTasks) * 100)),
658
659
  });
659
660
  await writeTaskTransition(task.id, 'blocked', {
@@ -720,6 +721,7 @@ export class OrchestrationExecutionRuntime {
720
721
  agentLabel: selectedAgentId,
721
722
  phase: 'blocked',
722
723
  detail: 'agente deshabilitado o inexistente',
724
+ output: summary,
723
725
  progressPercent: Math.min(100, Math.round(((processedReadyTasks + 1) / totalReadyTasks) * 100)),
724
726
  });
725
727
  await writeTaskTransition(task.id, 'blocked', {
@@ -786,6 +788,7 @@ export class OrchestrationExecutionRuntime {
786
788
  agentLabel,
787
789
  phase: 'blocked',
788
790
  detail: 'workspace deshabilitado o inexistente',
791
+ output: summary,
789
792
  progressPercent: Math.min(100, Math.round(((processedReadyTasks + 1) / totalReadyTasks) * 100)),
790
793
  });
791
794
  await writeTaskTransition(task.id, 'blocked', {
@@ -841,11 +844,11 @@ export class OrchestrationExecutionRuntime {
841
844
  continue;
842
845
  }
843
846
  await withOrchestrationTransaction(async (db) => {
844
- db.prepare(`UPDATE orchestration_tasks
845
- SET status = ?, assigned_agent_id = ?, finished_at = NULL
847
+ db.prepare(`UPDATE orchestration_tasks
848
+ SET status = ?, assigned_agent_id = ?, finished_at = NULL
846
849
  WHERE id = ?`).run('in_progress', selectedAgentId, task.id);
847
- db.prepare(`INSERT INTO orchestration_messages (
848
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
850
+ db.prepare(`INSERT INTO orchestration_messages (
851
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
849
852
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, task.id, initial.run.globalOrchestratorAgentId, selectedAgentId, 'task.dispatch', `Equipo ${domainName}: tarea enviada a ${agent.name}.`, nowIso());
850
853
  });
851
854
  input.onProgress?.({
@@ -889,6 +892,7 @@ export class OrchestrationExecutionRuntime {
889
892
  agentLabel,
890
893
  phase: executionResult.status,
891
894
  detail: executionResult.model ? `modelo ${executionResult.model}` : null,
895
+ output: executionResult.output || summary,
892
896
  progressPercent: Math.min(100, Math.round(((processedReadyTasks + 1) / totalReadyTasks) * 100)),
893
897
  });
894
898
  await writeTaskTransition(task.id, nextTaskStatus, {
@@ -949,6 +953,9 @@ export class OrchestrationExecutionRuntime {
949
953
  completedAt,
950
954
  });
951
955
  processedReadyTasks += 1;
956
+ if (processedReadyTasks < totalReadyTasks) {
957
+ await new Promise(resolve => setTimeout(resolve, 1000));
958
+ }
952
959
  }
953
960
  input.onProgress?.({
954
961
  runId,
@@ -1003,11 +1010,11 @@ export class OrchestrationExecutionRuntime {
1003
1010
  ? report.generatedAt
1004
1011
  : null;
1005
1012
  await withOrchestrationTransaction(async (db) => {
1006
- db.prepare(`UPDATE orchestration_runs
1007
- SET status = ?, finished_at = ?
1013
+ db.prepare(`UPDATE orchestration_runs
1014
+ SET status = ?, finished_at = ?
1008
1015
  WHERE id = ?`).run(report.status, finishedAt, runId);
1009
- db.prepare(`INSERT INTO orchestration_messages (
1010
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1016
+ db.prepare(`INSERT INTO orchestration_messages (
1017
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1011
1018
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, null, null, state.run.globalOrchestratorAgentId, 'global.consolidated-report', report.summary, report.generatedAt);
1012
1019
  });
1013
1020
  await upsertOrchestrationRunReport({
@@ -1045,12 +1052,12 @@ export class OrchestrationExecutionRuntime {
1045
1052
  const db = await getOrchestrationDatabase();
1046
1053
  const id = randomUUID();
1047
1054
  const createdAt = nowIso();
1048
- db.prepare(`INSERT INTO orchestration_messages (
1049
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1055
+ db.prepare(`INSERT INTO orchestration_messages (
1056
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1050
1057
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, requireText(runId, 'runId'), toNullableText(taskId), toNullableText(fromAgentId), toNullableText(toAgentId), requireText(messageType, 'messageType'), requireText(content, 'content'), createdAt);
1051
1058
  const row = db
1052
- .prepare(`SELECT id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1053
- FROM orchestration_messages
1059
+ .prepare(`SELECT id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1060
+ FROM orchestration_messages
1054
1061
  WHERE id = ?`)
1055
1062
  .get(id);
1056
1063
  const created = rowToMessage(row);
@@ -1073,11 +1080,11 @@ export class OrchestrationExecutionRuntime {
1073
1080
  finished_at: finishedAt,
1074
1081
  result_summary: task.resultSummary ?? null,
1075
1082
  });
1076
- db.prepare(`INSERT INTO orchestration_messages (
1077
- id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1083
+ db.prepare(`INSERT INTO orchestration_messages (
1084
+ id, run_id, task_id, from_agent_id, to_agent_id, message_type, content, created_at
1078
1085
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), runId, null, null, null, 'run.block', nowMessage('cancel', reason), nowIso());
1079
- db.prepare(`UPDATE orchestration_runs
1080
- SET status = ?, finished_at = ?
1086
+ db.prepare(`UPDATE orchestration_runs
1087
+ SET status = ?, finished_at = ?
1081
1088
  WHERE id = ?`).run('cancelled', finishedAt, runId);
1082
1089
  });
1083
1090
  const refreshed = await loadRunState(runId);
@@ -1,65 +1,65 @@
1
1
  import { CRON_CREATE_TOOL_NAME, CRON_DELETE_TOOL_NAME, DEFAULT_MAX_AGE_DAYS, isKairosCronEnabled, } from '../../tools/ScheduleCronTool/prompt.js';
2
2
  import { registerBundledSkill } from '../bundledSkills.js';
3
3
  const DEFAULT_INTERVAL = '10m';
4
- const USAGE_MESSAGE = `Usage: /loop [interval] <prompt>
5
-
6
- Run a prompt or slash command on a recurring interval.
7
-
8
- Intervals: Ns, Nm, Nh, Nd (e.g. 5m, 30m, 2h, 1d). Minimum granularity is 1 minute.
9
- If no interval is specified, defaults to ${DEFAULT_INTERVAL}.
10
-
11
- Examples:
12
- /loop 5m /babysit-prs
13
- /loop 30m check the deploy
14
- /loop 1h /standup 1
15
- /loop check the deploy (defaults to ${DEFAULT_INTERVAL})
4
+ const USAGE_MESSAGE = `Usage: /loop [interval] <prompt>
5
+
6
+ Run a prompt or slash command on a recurring interval.
7
+
8
+ Intervals: Ns, Nm, Nh, Nd (e.g. 5m, 30m, 2h, 1d). Minimum granularity is 1 minute.
9
+ If no interval is specified, defaults to ${DEFAULT_INTERVAL}.
10
+
11
+ Examples:
12
+ /loop 5m /babysit-prs
13
+ /loop 30m check the deploy
14
+ /loop 1h /standup 1
15
+ /loop check the deploy (defaults to ${DEFAULT_INTERVAL})
16
16
  /loop check the deploy every 20m`;
17
17
  function buildPrompt(args) {
18
- return `# /loop — schedule a recurring prompt
19
-
20
- Parse the input below into \`[interval] <prompt…>\` and schedule it with ${CRON_CREATE_TOOL_NAME}.
21
-
22
- ## Parsing (in priority order)
23
-
24
- 1. **Leading token**: if the first whitespace-delimited token matches \`^\\d+[smhd]$\` (e.g. \`5m\`, \`2h\`), that's the interval; the rest is the prompt.
25
- 2. **Trailing "every" clause**: otherwise, if the input ends with \`every <N><unit>\` or \`every <N> <unit-word>\` (e.g. \`every 20m\`, \`every 5 minutes\`, \`every 2 hours\`), extract that as the interval and strip it from the prompt. Only match when what follows "every" is a time expression — \`check every PR\` has no interval.
26
- 3. **Default**: otherwise, interval is \`${DEFAULT_INTERVAL}\` and the entire input is the prompt.
27
-
28
- If the resulting prompt is empty, show usage \`/loop [interval] <prompt>\` and stop — do not call ${CRON_CREATE_TOOL_NAME}.
29
-
30
- Examples:
31
- - \`5m /babysit-prs\` → interval \`5m\`, prompt \`/babysit-prs\` (rule 1)
32
- - \`check the deploy every 20m\` → interval \`20m\`, prompt \`check the deploy\` (rule 2)
33
- - \`run tests every 5 minutes\` → interval \`5m\`, prompt \`run tests\` (rule 2)
34
- - \`check the deploy\` → interval \`${DEFAULT_INTERVAL}\`, prompt \`check the deploy\` (rule 3)
35
- - \`check every PR\` → interval \`${DEFAULT_INTERVAL}\`, prompt \`check every PR\` (rule 3 — "every" not followed by time)
36
- - \`5m\` → empty prompt → show usage
37
-
38
- ## Interval → cron
39
-
40
- Supported suffixes: \`s\` (seconds, rounded up to nearest minute, min 1), \`m\` (minutes), \`h\` (hours), \`d\` (days). Convert:
41
-
42
- | Interval pattern | Cron expression | Notes |
43
- |-----------------------|---------------------|------------------------------------------|
44
- | \`Nm\` where N ≤ 59 | \`*/N * * * *\` | every N minutes |
45
- | \`Nm\` where N ≥ 60 | \`0 */H * * *\` | round to hours (H = N/60, must divide 24)|
46
- | \`Nh\` where N ≤ 23 | \`0 */N * * *\` | every N hours |
47
- | \`Nd\` | \`0 0 */N * *\` | every N days at midnight local |
48
- | \`Ns\` | treat as \`ceil(N/60)m\` | cron minimum granularity is 1 minute |
49
-
50
- **If the interval doesn't cleanly divide its unit** (e.g. \`7m\` → \`*/7 * * * *\` gives uneven gaps at :56→:00; \`90m\` → 1.5h which cron can't express), pick the nearest clean interval and tell the user what you rounded to before scheduling.
51
-
52
- ## Action
53
-
54
- 1. Call ${CRON_CREATE_TOOL_NAME} with:
55
- - \`cron\`: the expression from the table above
56
- - \`prompt\`: the parsed prompt from above, verbatim (slash commands are passed through unchanged)
57
- - \`recurring\`: \`true\`
58
- 2. Briefly confirm: what's scheduled, the cron expression, the human-readable cadence, that recurring tasks auto-expire after ${DEFAULT_MAX_AGE_DAYS} days, and that they can cancel sooner with ${CRON_DELETE_TOOL_NAME} (include the job ID).
59
- 3. **Then immediately execute the parsed prompt now** — don't wait for the first cron fire. If it's a slash command, invoke it via the Skill tool; otherwise act on it directly.
60
-
61
- ## Input
62
-
18
+ return `# /loop — schedule a recurring prompt
19
+
20
+ Parse the input below into \`[interval] <prompt…>\` and schedule it with ${CRON_CREATE_TOOL_NAME}.
21
+
22
+ ## Parsing (in priority order)
23
+
24
+ 1. **Leading token**: if the first whitespace-delimited token matches \`^\\d+[smhd]$\` (e.g. \`5m\`, \`2h\`), that's the interval; the rest is the prompt.
25
+ 2. **Trailing "every" clause**: otherwise, if the input ends with \`every <N><unit>\` or \`every <N> <unit-word>\` (e.g. \`every 20m\`, \`every 5 minutes\`, \`every 2 hours\`), extract that as the interval and strip it from the prompt. Only match when what follows "every" is a time expression — \`check every PR\` has no interval.
26
+ 3. **Default**: otherwise, interval is \`${DEFAULT_INTERVAL}\` and the entire input is the prompt.
27
+
28
+ If the resulting prompt is empty, show usage \`/loop [interval] <prompt>\` and stop — do not call ${CRON_CREATE_TOOL_NAME}.
29
+
30
+ Examples:
31
+ - \`5m /babysit-prs\` → interval \`5m\`, prompt \`/babysit-prs\` (rule 1)
32
+ - \`check the deploy every 20m\` → interval \`20m\`, prompt \`check the deploy\` (rule 2)
33
+ - \`run tests every 5 minutes\` → interval \`5m\`, prompt \`run tests\` (rule 2)
34
+ - \`check the deploy\` → interval \`${DEFAULT_INTERVAL}\`, prompt \`check the deploy\` (rule 3)
35
+ - \`check every PR\` → interval \`${DEFAULT_INTERVAL}\`, prompt \`check every PR\` (rule 3 — "every" not followed by time)
36
+ - \`5m\` → empty prompt → show usage
37
+
38
+ ## Interval → cron
39
+
40
+ Supported suffixes: \`s\` (seconds, rounded up to nearest minute, min 1), \`m\` (minutes), \`h\` (hours), \`d\` (days). Convert:
41
+
42
+ | Interval pattern | Cron expression | Notes |
43
+ |-----------------------|---------------------|------------------------------------------|
44
+ | \`Nm\` where N ≤ 59 | \`*/N * * * *\` | every N minutes |
45
+ | \`Nm\` where N ≥ 60 | \`0 */H * * *\` | round to hours (H = N/60, must divide 24)|
46
+ | \`Nh\` where N ≤ 23 | \`0 */N * * *\` | every N hours |
47
+ | \`Nd\` | \`0 0 */N * *\` | every N days at midnight local |
48
+ | \`Ns\` | treat as \`ceil(N/60)m\` | cron minimum granularity is 1 minute |
49
+
50
+ **If the interval doesn't cleanly divide its unit** (e.g. \`7m\` → \`*/7 * * * *\` gives uneven gaps at :56→:00; \`90m\` → 1.5h which cron can't express), pick the nearest clean interval and tell the user what you rounded to before scheduling.
51
+
52
+ ## Action
53
+
54
+ 1. Call ${CRON_CREATE_TOOL_NAME} with:
55
+ - \`cron\`: the expression from the table above
56
+ - \`prompt\`: the parsed prompt from above, verbatim (slash commands are passed through unchanged)
57
+ - \`recurring\`: \`true\`
58
+ 2. Briefly confirm: what's scheduled, the cron expression, the human-readable cadence, that recurring tasks auto-expire after ${DEFAULT_MAX_AGE_DAYS} days, and that they can cancel sooner with ${CRON_DELETE_TOOL_NAME} (include the job ID).
59
+ 3. **Then immediately execute the parsed prompt now** — don't wait for the first cron fire. If it's a slash command, invoke it via the Skill tool; otherwise act on it directly.
60
+
61
+ ## Input
62
+
63
63
  ${args}`;
64
64
  }
65
65
  export function registerLoopSkill() {