@iaforged/context-code 1.2.9 → 1.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -119
- package/context-bootstrap.js +28 -26
- package/dist/src/QueryEngine.js +394 -327
- package/dist/src/bridge/bridgeUI.js +1 -1
- package/dist/src/buddy/prompt.js +4 -4
- package/dist/src/cli/handlers/auth.js +126 -9
- package/dist/src/cli/print.js +35 -1
- package/dist/src/commands/agent/agent.js +28 -2
- package/dist/src/commands/agent/agentStore.js +8 -1
- package/dist/src/commands/agent/index.js +1 -1
- package/dist/src/commands/bridge-kick.js +9 -9
- package/dist/src/commands/commit.js +34 -34
- package/dist/src/commands/init-verifiers.js +3 -3
- package/dist/src/commands/init.js +88 -88
- package/dist/src/commands/insights.js +787 -787
- package/dist/src/commands/install.js +19 -19
- package/dist/src/commands/login/login.js +21 -12
- package/dist/src/commands/logout/logout.js +9 -0
- package/dist/src/commands/model/model.js +9 -4
- package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
- package/dist/src/commands/orchestrate/index.js +2 -2
- package/dist/src/commands/orchestrate/orchestrate.js +708 -12
- package/dist/src/commands/pr_comments/index.js +33 -33
- package/dist/src/commands/profile/index.js +1 -1
- package/dist/src/commands/profile/profile.js +52 -3
- package/dist/src/commands/provider/index.js +1 -1
- package/dist/src/commands/provider/provider.js +117 -45
- package/dist/src/commands/resumen/index.js +9 -0
- package/dist/src/commands/resumen/resumen.js +29 -0
- package/dist/src/commands/security-review.js +190 -190
- package/dist/src/commands/swarm-auto/index.js +9 -0
- package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
- package/dist/src/commands/swarm-init/index.js +9 -0
- package/dist/src/commands/swarm-init/swarmInit.js +72 -0
- package/dist/src/commands/team/team.js +39 -6
- package/dist/src/commands.js +14 -0
- package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
- package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
- package/dist/src/components/agents/agentFileUtils.js +6 -6
- package/dist/src/components/permissions/hooks.js +5 -5
- package/dist/src/constants/outputStyles.js +83 -83
- package/dist/src/core/agents/blueprints.js +58 -0
- package/dist/src/core/agents/cliAdapter.js +61 -0
- package/dist/src/core/agents/registry.js +93 -0
- package/dist/src/core/agents/runtime.js +4 -0
- package/dist/src/core/agents/runtime.smoke.js +42 -0
- package/dist/src/core/agents/swarm.smoke.js +48 -0
- package/dist/src/core/agents/swarmTools.js +38 -0
- package/dist/src/core/auth/index.js +2 -0
- package/dist/src/core/auth/loginCliAdapter.js +24 -0
- package/dist/src/core/auth/loginCore.js +67 -0
- package/dist/src/core/auth/logoutCliAdapter.js +34 -0
- package/dist/src/core/auth/logoutCore.js +52 -0
- package/dist/src/core/auth/preflight.smoke.js +151 -0
- package/dist/src/core/index.js +21 -0
- package/dist/src/core/mcp/blueprints.js +27 -0
- package/dist/src/core/mcp/common.js +14 -0
- package/dist/src/core/mcp/runtime.js +67 -0
- package/dist/src/core/mcp/runtime.smoke.js +50 -0
- package/dist/src/core/mcp/swarmClient.js +40 -0
- package/dist/src/core/mcp/swarmSetup.js +43 -0
- package/dist/src/core/providers/cliAdapter.js +39 -0
- package/dist/src/core/providers/contracts.js +1 -0
- package/dist/src/core/providers/index.js +3 -0
- package/dist/src/core/providers/llmCore.js +123 -0
- package/dist/src/core/providers/providerCore.js +141 -0
- package/dist/src/core/providers/providerModelCompatibility.js +98 -0
- package/dist/src/core/providers/providerParitySmoke.js +83 -0
- package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
- package/dist/src/core/query/contracts.js +1 -0
- package/dist/src/core/query/runtime.js +117 -0
- package/dist/src/core/query/runtime.smoke.js +39 -0
- package/dist/src/core/query/timelineThinking.smoke.js +25 -0
- package/dist/src/core/query/wiring.smoke.js +76 -0
- package/dist/src/core/skills/cliAdapter.js +38 -0
- package/dist/src/core/skills/index.js +52 -0
- package/dist/src/core/skills/runtime.smoke.js +53 -0
- package/dist/src/core/tasks/runtime.js +205 -0
- package/dist/src/core/tasks/runtime.smoke.js +63 -0
- package/dist/src/core/tasks/sdkAdapter.js +4 -0
- package/dist/src/core/tools/contracts.js +3 -0
- package/dist/src/core/tools/fileResolution.js +112 -0
- package/dist/src/core/tools/fileResolution.smoke.js +33 -0
- package/dist/src/core/tools/filesCore.js +51 -0
- package/dist/src/core/tools/filesCore.smoke.js +108 -0
- package/dist/src/core/tools/gitCore.js +20 -0
- package/dist/src/core/tools/imageParity.smoke.js +36 -0
- package/dist/src/core/tools/notebookParity.smoke.js +68 -0
- package/dist/src/core/tools/registry.js +22 -0
- package/dist/src/core/tools/runtime.smoke.js +32 -0
- package/dist/src/core/tools/shellCore.js +60 -0
- package/dist/src/core/types/agentContext.js +9 -0
- package/dist/src/core/types/auth.js +3 -0
- package/dist/src/core/types/command.js +13 -0
- package/dist/src/core/types/provider.js +3 -0
- package/dist/src/core/types/sdkEvent.js +10 -0
- package/dist/src/core/types/swarm.js +1 -0
- package/dist/src/cost-tracker.js +3 -3
- package/dist/src/hooks/useAwaySummary.js +22 -9
- package/dist/src/main.js +32 -2
- package/dist/src/screens/REPL.js +9 -0
- package/dist/src/services/AgentSummary/agentSummary.js +10 -10
- package/dist/src/services/autoDream/autoDream.js +5 -5
- package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
- package/dist/src/services/compact/prompt.js +238 -238
- package/dist/src/services/limits/sessionCounter.js +17 -17
- package/dist/src/services/mcp/client.js +27 -1
- package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
- package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
- package/dist/src/skills/bundled/loop.js +57 -57
- package/dist/src/skills/bundled/remember.js +53 -53
- package/dist/src/skills/bundled/simplify.js +49 -49
- package/dist/src/skills/bundled/skillify.js +2 -2
- package/dist/src/state/onChangeAppState.js +6 -0
- package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
- package/dist/src/tasks/LocalMainSessionTask.js +5 -5
- package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
- package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
- package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
- package/dist/src/tools/BashTool/BashTool.js +27 -2
- package/dist/src/tools/BriefTool/prompt.js +14 -14
- package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
- package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
- package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
- package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
- package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
- package/dist/src/tools/FileEditTool/prompt.js +7 -7
- package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
- package/dist/src/tools/FileWriteTool/prompt.js +6 -6
- package/dist/src/tools/GlobTool/prompt.js +4 -4
- package/dist/src/tools/GrepTool/prompt.js +10 -10
- package/dist/src/tools/LSPTool/prompt.js +18 -18
- package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
- package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
- package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
- package/dist/src/tools/SendMessageTool/prompt.js +36 -36
- package/dist/src/tools/SkillTool/prompt.js +21 -21
- package/dist/src/tools/SleepTool/prompt.js +10 -10
- package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
- package/dist/src/tools/TaskGetTool/prompt.js +21 -21
- package/dist/src/tools/TaskListTool/prompt.js +30 -30
- package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
- package/dist/src/tools/TaskStopTool/prompt.js +5 -5
- package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
- package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
- package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
- package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
- package/dist/src/tools/WebFetchTool/prompt.js +31 -31
- package/dist/src/tools/WebSearchTool/prompt.js +26 -26
- package/dist/src/utils/agentContext.js +2 -0
- package/dist/src/utils/agenticSessionSearch.js +38 -38
- package/dist/src/utils/config.js +2 -0
- package/dist/src/utils/genericProcessUtils.js +21 -21
- package/dist/src/utils/heapDumpService.js +4 -4
- package/dist/src/utils/mcpValidation.js +2 -2
- package/dist/src/utils/model/modelStrings.js +1 -1
- package/dist/src/utils/model/providers.js +5 -0
- package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
- package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
- package/dist/src/utils/orchestration/store/runStore.js +68 -68
- package/dist/src/utils/orchestration/store/teamStore.js +28 -28
- package/dist/src/utils/permissions/permissionExplainer.js +6 -6
- package/dist/src/utils/permissions/permissionsDb.js +43 -43
- package/dist/src/utils/sdkEventQueue.js +2 -0
- package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
- package/dist/src/utils/standardMcp/common.js +15 -0
- package/dist/src/utils/standardMcp/setup.js +52 -0
- package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
- package/dist/src/utils/task/framework.js +6 -6
- package/package.json +1 -1
- package/dist/src/commands/usage/index.js +0 -7
- 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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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:
|
|
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() {
|