@runfusion/fusion 0.9.0 → 0.9.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.
- package/dist/bin.js +434 -103
- package/dist/client/assets/{AgentDetailView-Ca1euvPo.js → AgentDetailView-khrspqE3.js} +3 -3
- package/dist/client/assets/{AgentsView-pile-9ZM.js → AgentsView-DvN5EsoE.js} +11 -11
- package/dist/client/assets/AgentsView-MotzGhZJ.css +1 -0
- package/dist/client/assets/{ChatView-C4SCF_Hj.js → ChatView-DsETFRQp.js} +1 -1
- package/dist/client/assets/{DevServerView-1HvWligZ.js → DevServerView-D1_7SL1h.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-B1eztZna.js → DirectoryPicker-BBcm5G9F.js} +1 -1
- package/dist/client/assets/{DocumentsView-D4m9s6Ye.js → DocumentsView-OuVpmTPp.js} +1 -1
- package/dist/client/assets/InsightsView-4KiUKzbz.css +1 -0
- package/dist/client/assets/InsightsView-DMC_bLUc.js +11 -0
- package/dist/client/assets/{MemoryView-DEmn8fA2.js → MemoryView-cI4IK-lz.js} +1 -1
- package/dist/client/assets/{NodesView-Blg8NHiu.js → NodesView-C_LWLFNr.js} +2 -2
- package/dist/client/assets/{PiExtensionsManager-DkXenPK0.js → PiExtensionsManager-DCTHvy2u.js} +1 -1
- package/dist/client/assets/{PluginManager-Czso7ZUF.js → PluginManager-BXMpkZx-.js} +1 -1
- package/dist/client/assets/{RoadmapsView-DATopkaE.js → RoadmapsView-BHTePn2u.js} +1 -1
- package/dist/client/assets/SettingsModal-2s-L1oWD.js +31 -0
- package/dist/client/assets/{SettingsModal-D_mcRJO2.js → SettingsModal-BSZIno8y.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-JBNr-XIW.js → SetupWizardModal-DVoRhy_V.js} +1 -1
- package/dist/client/assets/{SkillsView-CkT6-elZ.js → SkillsView-CK52SRz5.js} +1 -1
- package/dist/client/assets/{TodoView-CstzLvjw.js → TodoView-BKqIV8P6.js} +1 -1
- package/dist/client/assets/{folder-open-BEDPztlF.js → folder-open-C0SfzRFt.js} +1 -1
- package/dist/client/assets/index-DawWARY5.css +1 -0
- package/dist/client/assets/index-DiC9GfBH.js +656 -0
- package/dist/client/assets/{list-checks-DgZgg3rh.js → list-checks-D5c428sr.js} +1 -1
- package/dist/client/assets/{star-DXieIAlP.js → star-GGmbVi2b.js} +1 -1
- package/dist/client/assets/{upload-Cbvs_TSB.js → upload-CrJJybRJ.js} +1 -1
- package/dist/client/assets/{users-DAMIrlue.js → users-C56SMdh4.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +427 -103
- package/dist/pi-claude-cli/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/AgentsView-Cq-SEhLc.css +0 -1
- package/dist/client/assets/InsightsView-6LHF7OdE.css +0 -1
- package/dist/client/assets/InsightsView-u535o96R.js +0 -11
- package/dist/client/assets/SettingsModal-Cs5qO84M.js +0 -31
- package/dist/client/assets/index-CPStj9Az.css +0 -1
- package/dist/client/assets/index-DZB5f-Bl.js +0 -656
package/dist/extension.js
CHANGED
|
@@ -1342,7 +1342,7 @@ function getAvailableTemplates(config) {
|
|
|
1342
1342
|
function getTemplatesForRole(role, config) {
|
|
1343
1343
|
return getAvailableTemplates(config).filter((t) => t.role === role);
|
|
1344
1344
|
}
|
|
1345
|
-
var EXECUTOR_PROMPT_TEXT, TRIAGE_PROMPT_TEXT, REVIEWER_PROMPT_TEXT, MERGER_BASE_PROMPT_TEXT, SENIOR_ENGINEER_PROMPT_TEXT, STRICT_REVIEWER_PROMPT_TEXT, CONCISE_TRIAGE_PROMPT_TEXT, BUILTIN_AGENT_PROMPTS;
|
|
1345
|
+
var EXECUTOR_PROMPT_TEXT, TRIAGE_PROMPT_TEXT, REVIEWER_PROMPT_TEXT, MERGER_BASE_PROMPT_TEXT, SENIOR_ENGINEER_PROMPT_TEXT, STRICT_REVIEWER_PROMPT_TEXT, CONCISE_TRIAGE_PROMPT_TEXT, EXECUTOR_HEARTBEAT_GUIDANCE, TRIAGE_HEARTBEAT_GUIDANCE, REVIEWER_HEARTBEAT_GUIDANCE, MERGER_HEARTBEAT_GUIDANCE, SENIOR_ENGINEER_HEARTBEAT_GUIDANCE, STRICT_REVIEWER_HEARTBEAT_GUIDANCE, CONCISE_TRIAGE_HEARTBEAT_GUIDANCE, BUILTIN_AGENT_PROMPTS;
|
|
1346
1346
|
var init_agent_prompts = __esm({
|
|
1347
1347
|
"../core/src/agent-prompts.ts"() {
|
|
1348
1348
|
"use strict";
|
|
@@ -2164,13 +2164,64 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2164
2164
|
5. **No placeholders:** Real content only.
|
|
2165
2165
|
6. **Read first:** Examine codebase before writing spec.
|
|
2166
2166
|
7. **Be concise:** Short descriptions, minimal prose. Focus on what matters.`;
|
|
2167
|
+
EXECUTOR_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2168
|
+
|
|
2169
|
+
Treat each heartbeat as a short autonomous execution cycle.
|
|
2170
|
+
|
|
2171
|
+
- If a task is assigned: inspect the latest task state, continue the next concrete implementation step, run the smallest useful verification, and either advance the task or log the blocker precisely.
|
|
2172
|
+
- If no task is assigned: execute your standing instructions. Review unread messages, scan for blocked or failing engineering work, create narrowly scoped follow-up tasks, and capture durable implementation notes other agents will need later.
|
|
2173
|
+
- Do not idle simply because no task is linked. Use heartbeat time to reduce engineering risk, unblock work, and keep execution moving in small, concrete increments.`;
|
|
2174
|
+
TRIAGE_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2175
|
+
|
|
2176
|
+
Use heartbeat runs to keep the planning pipeline healthy.
|
|
2177
|
+
|
|
2178
|
+
- If a task is assigned: turn the rough request into a complete, execution-ready PROMPT.md with clear scope, steps, dependencies, and verification criteria.
|
|
2179
|
+
- If no task is assigned: execute your planning instructions. Patrol for vague requests, blocked tasks that need better specification, review follow-ups that should become new tasks, and dependency gaps that are slowing executors down.
|
|
2180
|
+
- Favor ambiguity reduction over busywork. Every heartbeat should leave the queue more actionable than you found it.`;
|
|
2181
|
+
REVIEWER_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2182
|
+
|
|
2183
|
+
Use heartbeat runs to keep review quality high and queues moving.
|
|
2184
|
+
|
|
2185
|
+
- If a task is assigned: perform the review with findings first, focusing on correctness, regressions, missing tests, and operational risk.
|
|
2186
|
+
- If no task is assigned: execute your review instructions. Look for work waiting on review, failed validations, suspicious recent changes, and places where a second pass would prevent a bad merge.
|
|
2187
|
+
- Prefer surfacing concrete findings, follow-up tasks, or merge blockers over rewriting implementation yourself.`;
|
|
2188
|
+
MERGER_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2189
|
+
|
|
2190
|
+
Use heartbeat runs to keep merge-ready work from stalling.
|
|
2191
|
+
|
|
2192
|
+
- If a task is assigned: verify merge preconditions, resolve the next safe merge step, and surface conflicts or missing gates immediately.
|
|
2193
|
+
- If no task is assigned: execute your merge instructions. Inspect the in-review and merge-ready queue, look for unresolved conflicts, missing approvals, broken post-review state, and tasks that are ready for the final merge push.
|
|
2194
|
+
- Optimize for safe flow, not raw throughput. Clear blockers, communicate risks, and only move merge work forward when the repository stays trustworthy.`;
|
|
2195
|
+
SENIOR_ENGINEER_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2196
|
+
|
|
2197
|
+
Treat each heartbeat as an autonomous senior-engineering pass.
|
|
2198
|
+
|
|
2199
|
+
- If a task is assigned: push the implementation forward decisively, making sound architectural choices, validating risky changes early, and documenting trade-offs that downstream agents should inherit.
|
|
2200
|
+
- If no task is assigned: execute your standing instructions. Hunt for architectural drift, flaky quality gates, latent integration risk, and follow-up work that needs a strong technical owner.
|
|
2201
|
+
- Spend heartbeat time where leverage is highest: unblock teams, reduce complexity, and turn vague engineering risk into concrete next actions.`;
|
|
2202
|
+
STRICT_REVIEWER_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2203
|
+
|
|
2204
|
+
Use heartbeat runs to enforce a high review bar.
|
|
2205
|
+
|
|
2206
|
+
- If a task is assigned: review for worst-case failure modes first, especially security, backward compatibility, edge cases, and missing regression coverage.
|
|
2207
|
+
- If no task is assigned: execute your review instructions. Look for merges that feel under-reviewed, risky diffs that deserve another pass, and follow-up work needed before code should land.
|
|
2208
|
+
- Bias toward precise findings and explicit risk articulation. A quiet heartbeat should mean the code is genuinely clean, not that you stopped looking.`;
|
|
2209
|
+
CONCISE_TRIAGE_HEARTBEAT_GUIDANCE = `## Heartbeat Run Behavior
|
|
2210
|
+
|
|
2211
|
+
Keep heartbeat output lean and useful.
|
|
2212
|
+
|
|
2213
|
+
- If a task is assigned: produce the minimum complete PROMPT.md needed for an executor to act safely.
|
|
2214
|
+
- If no task is assigned: execute your planning instructions, scan for underspecified or blocked work, and turn it into short, actionable task specs or follow-up tickets.
|
|
2215
|
+
- Prefer crisp decisions, clear file scope, and concrete verification steps over narrative detail.`;
|
|
2167
2216
|
BUILTIN_AGENT_PROMPTS = [
|
|
2168
2217
|
{
|
|
2169
2218
|
id: "default-executor",
|
|
2170
2219
|
name: "Default Executor",
|
|
2171
2220
|
description: "Standard task execution agent with full tooling and review support.",
|
|
2172
2221
|
role: "executor",
|
|
2173
|
-
prompt: EXECUTOR_PROMPT_TEXT
|
|
2222
|
+
prompt: `${EXECUTOR_PROMPT_TEXT}
|
|
2223
|
+
|
|
2224
|
+
${EXECUTOR_HEARTBEAT_GUIDANCE}`,
|
|
2174
2225
|
builtIn: true
|
|
2175
2226
|
},
|
|
2176
2227
|
{
|
|
@@ -2178,7 +2229,9 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2178
2229
|
name: "Default Triage",
|
|
2179
2230
|
description: "Standard task specification agent producing detailed PROMPT.md files.",
|
|
2180
2231
|
role: "triage",
|
|
2181
|
-
prompt: TRIAGE_PROMPT_TEXT
|
|
2232
|
+
prompt: `${TRIAGE_PROMPT_TEXT}
|
|
2233
|
+
|
|
2234
|
+
${TRIAGE_HEARTBEAT_GUIDANCE}`,
|
|
2182
2235
|
builtIn: true
|
|
2183
2236
|
},
|
|
2184
2237
|
{
|
|
@@ -2186,7 +2239,9 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2186
2239
|
name: "Default Reviewer",
|
|
2187
2240
|
description: "Standard independent code and plan reviewer with balanced criteria.",
|
|
2188
2241
|
role: "reviewer",
|
|
2189
|
-
prompt: REVIEWER_PROMPT_TEXT
|
|
2242
|
+
prompt: `${REVIEWER_PROMPT_TEXT}
|
|
2243
|
+
|
|
2244
|
+
${REVIEWER_HEARTBEAT_GUIDANCE}`,
|
|
2190
2245
|
builtIn: true
|
|
2191
2246
|
},
|
|
2192
2247
|
{
|
|
@@ -2194,7 +2249,9 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2194
2249
|
name: "Default Merger",
|
|
2195
2250
|
description: "Standard merge agent for squash merges with conflict resolution.",
|
|
2196
2251
|
role: "merger",
|
|
2197
|
-
prompt: MERGER_BASE_PROMPT_TEXT
|
|
2252
|
+
prompt: `${MERGER_BASE_PROMPT_TEXT}
|
|
2253
|
+
|
|
2254
|
+
${MERGER_HEARTBEAT_GUIDANCE}`,
|
|
2198
2255
|
builtIn: true
|
|
2199
2256
|
},
|
|
2200
2257
|
{
|
|
@@ -2202,7 +2259,9 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2202
2259
|
name: "Senior Engineer",
|
|
2203
2260
|
description: "Autonomous executor with architectural awareness, performance focus, and minimal hand-holding. Makes independent decisions on routine matters.",
|
|
2204
2261
|
role: "executor",
|
|
2205
|
-
prompt: SENIOR_ENGINEER_PROMPT_TEXT
|
|
2262
|
+
prompt: `${SENIOR_ENGINEER_PROMPT_TEXT}
|
|
2263
|
+
|
|
2264
|
+
${SENIOR_ENGINEER_HEARTBEAT_GUIDANCE}`,
|
|
2206
2265
|
builtIn: true
|
|
2207
2266
|
},
|
|
2208
2267
|
{
|
|
@@ -2210,7 +2269,9 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2210
2269
|
name: "Strict Reviewer",
|
|
2211
2270
|
description: "Rigorous reviewer with stricter criteria for security, edge cases, backward compatibility, and type safety. Issues REVISE more readily.",
|
|
2212
2271
|
role: "reviewer",
|
|
2213
|
-
prompt: STRICT_REVIEWER_PROMPT_TEXT
|
|
2272
|
+
prompt: `${STRICT_REVIEWER_PROMPT_TEXT}
|
|
2273
|
+
|
|
2274
|
+
${STRICT_REVIEWER_HEARTBEAT_GUIDANCE}`,
|
|
2214
2275
|
builtIn: true
|
|
2215
2276
|
},
|
|
2216
2277
|
{
|
|
@@ -2218,7 +2279,9 @@ Write a PROMPT.md specification to the given path. Be brief and precise \u2014 a
|
|
|
2218
2279
|
name: "Concise Triage",
|
|
2219
2280
|
description: "Shorter, more focused specification format with minimal prose. Produces compact PROMPT.md files with essential information only.",
|
|
2220
2281
|
role: "triage",
|
|
2221
|
-
prompt: CONCISE_TRIAGE_PROMPT_TEXT
|
|
2282
|
+
prompt: `${CONCISE_TRIAGE_PROMPT_TEXT}
|
|
2283
|
+
|
|
2284
|
+
${CONCISE_TRIAGE_HEARTBEAT_GUIDANCE}`,
|
|
2222
2285
|
builtIn: true
|
|
2223
2286
|
}
|
|
2224
2287
|
];
|
|
@@ -31992,6 +32055,9 @@ ${newTask.description}
|
|
|
31992
32055
|
task.nextRecoveryAt = void 0;
|
|
31993
32056
|
}
|
|
31994
32057
|
await this.atomicWriteTaskJson(dir, task);
|
|
32058
|
+
if (toColumn === "done") {
|
|
32059
|
+
this.clearLinkedAgentTaskIds(id, task.updatedAt);
|
|
32060
|
+
}
|
|
31995
32061
|
if (this.isWatching) this.taskCache.set(id, { ...task });
|
|
31996
32062
|
this.emit("task:moved", { task, from: fromColumn, to: toColumn });
|
|
31997
32063
|
return task;
|
|
@@ -32699,11 +32765,34 @@ ${task.description}
|
|
|
32699
32765
|
}
|
|
32700
32766
|
rewrittenDependents.push(updatedDependent);
|
|
32701
32767
|
}
|
|
32768
|
+
this.clearLinkedAgentTaskIds(taskId);
|
|
32702
32769
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(taskId);
|
|
32703
32770
|
this.db.bumpLastModified();
|
|
32704
32771
|
});
|
|
32705
32772
|
return rewrittenDependents;
|
|
32706
32773
|
}
|
|
32774
|
+
/**
|
|
32775
|
+
* Clear `agent.taskId` links that point at a task which has transitioned out
|
|
32776
|
+
* of active work. This keeps heartbeat scheduling aligned with live task
|
|
32777
|
+
* storage and prevents stale task-scoped heartbeat runs.
|
|
32778
|
+
*/
|
|
32779
|
+
clearLinkedAgentTaskIds(taskId, updatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
32780
|
+
const linkedAgents = this.db.prepare("SELECT id FROM agents WHERE taskId = ?").all(taskId);
|
|
32781
|
+
if (linkedAgents.length === 0) {
|
|
32782
|
+
return;
|
|
32783
|
+
}
|
|
32784
|
+
this.db.prepare(`
|
|
32785
|
+
UPDATE agents
|
|
32786
|
+
SET
|
|
32787
|
+
taskId = NULL,
|
|
32788
|
+
updatedAt = ?,
|
|
32789
|
+
data = CASE
|
|
32790
|
+
WHEN json_valid(data) THEN json_set(json_remove(data, '$.taskId'), '$.updatedAt', ?)
|
|
32791
|
+
ELSE data
|
|
32792
|
+
END
|
|
32793
|
+
WHERE taskId = ?
|
|
32794
|
+
`).run(updatedAt, updatedAt, taskId);
|
|
32795
|
+
}
|
|
32707
32796
|
/**
|
|
32708
32797
|
* Clean up the git branch associated with a task.
|
|
32709
32798
|
*
|
|
@@ -32985,6 +33074,7 @@ ${task.description}
|
|
|
32985
33074
|
});
|
|
32986
33075
|
if (!cleanup) {
|
|
32987
33076
|
await this.atomicWriteTaskJson(dir, task);
|
|
33077
|
+
this.clearLinkedAgentTaskIds(id, task.updatedAt);
|
|
32988
33078
|
if (this.isWatching) this.taskCache.set(id, { ...task });
|
|
32989
33079
|
this.emit("task:moved", { task, from: "done", to: "archived" });
|
|
32990
33080
|
return task;
|
|
@@ -32998,6 +33088,7 @@ ${task.description}
|
|
|
32998
33088
|
}
|
|
32999
33089
|
const entry = await this.taskToArchiveEntry(task, task.columnMovedAt);
|
|
33000
33090
|
this.archiveDb.upsert(entry);
|
|
33091
|
+
this.clearLinkedAgentTaskIds(id, task.updatedAt);
|
|
33001
33092
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
33002
33093
|
this.db.bumpLastModified();
|
|
33003
33094
|
const { rm: rm4 } = await import("node:fs/promises");
|
|
@@ -36911,10 +37002,105 @@ async function summarizeCommitBody(diffStat, rootDir, provider, modelId, opts) {
|
|
|
36911
37002
|
}
|
|
36912
37003
|
}
|
|
36913
37004
|
}
|
|
37005
|
+
async function summarizeCommitSubject(diffStat, rootDir, provider, modelId, opts) {
|
|
37006
|
+
const trimmedStat = (diffStat ?? "").trim();
|
|
37007
|
+
const trimmedCommitLog = (opts?.commitLog ?? "").trim();
|
|
37008
|
+
if (trimmedStat.length === 0 && trimmedCommitLog.length === 0) {
|
|
37009
|
+
return null;
|
|
37010
|
+
}
|
|
37011
|
+
const truncatedStat = trimmedStat.length > MAX_COMMIT_BODY_INPUT_LENGTH ? trimmedStat.slice(0, MAX_COMMIT_BODY_INPUT_LENGTH) + "\n\u2026(truncated)" : trimmedStat;
|
|
37012
|
+
const truncatedCommitLog = trimmedCommitLog.length > MAX_COMMIT_BODY_INPUT_LENGTH ? trimmedCommitLog.slice(0, MAX_COMMIT_BODY_INPUT_LENGTH) + "\n\u2026(truncated)" : trimmedCommitLog;
|
|
37013
|
+
const userPromptParts = [];
|
|
37014
|
+
if (opts?.branch) userPromptParts.push(`Branch: ${opts.branch}`);
|
|
37015
|
+
if (opts?.taskId) userPromptParts.push(`Task: ${opts.taskId}`);
|
|
37016
|
+
if (userPromptParts.length > 0) userPromptParts.push("");
|
|
37017
|
+
if (truncatedCommitLog.length > 0) {
|
|
37018
|
+
userPromptParts.push("Step commits being merged in (most recent first):");
|
|
37019
|
+
userPromptParts.push(truncatedCommitLog);
|
|
37020
|
+
userPromptParts.push("");
|
|
37021
|
+
}
|
|
37022
|
+
if (truncatedStat.length > 0) {
|
|
37023
|
+
userPromptParts.push("Files changed (`git diff --stat`):");
|
|
37024
|
+
userPromptParts.push(truncatedStat);
|
|
37025
|
+
userPromptParts.push("");
|
|
37026
|
+
}
|
|
37027
|
+
userPromptParts.push("Write the commit subject now.");
|
|
37028
|
+
const userPrompt = userPromptParts.join("\n");
|
|
37029
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_COMMIT_SUBJECT_TIMEOUT_MS;
|
|
37030
|
+
const aborter = new AbortController();
|
|
37031
|
+
const timer = setTimeout(() => aborter.abort(), timeoutMs);
|
|
37032
|
+
if (opts?.signal) {
|
|
37033
|
+
if (opts.signal.aborted) aborter.abort();
|
|
37034
|
+
else opts.signal.addEventListener("abort", () => aborter.abort(), { once: true });
|
|
37035
|
+
}
|
|
37036
|
+
let session;
|
|
37037
|
+
try {
|
|
37038
|
+
const createFnAgent5 = await getFnAgent();
|
|
37039
|
+
if (!createFnAgent5) {
|
|
37040
|
+
if (DEBUG) console.log("[ai-summarize] AI engine not available for commit subject");
|
|
37041
|
+
return null;
|
|
37042
|
+
}
|
|
37043
|
+
const agentOptions = {
|
|
37044
|
+
cwd: rootDir,
|
|
37045
|
+
systemPrompt: COMMIT_SUBJECT_SYSTEM_PROMPT,
|
|
37046
|
+
tools: "readonly"
|
|
37047
|
+
};
|
|
37048
|
+
if (provider && modelId) {
|
|
37049
|
+
agentOptions.defaultProvider = provider;
|
|
37050
|
+
agentOptions.defaultModelId = modelId;
|
|
37051
|
+
}
|
|
37052
|
+
const agentResult = await createFnAgent5(agentOptions);
|
|
37053
|
+
if (!agentResult?.session) return null;
|
|
37054
|
+
session = agentResult.session;
|
|
37055
|
+
await session.prompt(userPrompt);
|
|
37056
|
+
if (aborter.signal.aborted) return null;
|
|
37057
|
+
if (session.state?.error) {
|
|
37058
|
+
if (DEBUG) console.log(`[ai-summarize] Commit-subject session error: ${session.state.error}`);
|
|
37059
|
+
return null;
|
|
37060
|
+
}
|
|
37061
|
+
const messages = session.state?.messages ?? [];
|
|
37062
|
+
const assistant = messages.filter((m) => m.role === "assistant").pop();
|
|
37063
|
+
if (!assistant?.content) return null;
|
|
37064
|
+
let raw = "";
|
|
37065
|
+
if (typeof assistant.content === "string") {
|
|
37066
|
+
raw = assistant.content;
|
|
37067
|
+
} else if (Array.isArray(assistant.content)) {
|
|
37068
|
+
raw = assistant.content.filter(
|
|
37069
|
+
(c) => c.type === "text" && typeof c.text === "string"
|
|
37070
|
+
).map((c) => c.text).join("");
|
|
37071
|
+
}
|
|
37072
|
+
return sanitizeCommitSubject(raw);
|
|
37073
|
+
} catch (err) {
|
|
37074
|
+
if (DEBUG) {
|
|
37075
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
37076
|
+
console.log(`[ai-summarize] Commit-subject generation failed: ${message}`);
|
|
37077
|
+
}
|
|
37078
|
+
return null;
|
|
37079
|
+
} finally {
|
|
37080
|
+
clearTimeout(timer);
|
|
37081
|
+
try {
|
|
37082
|
+
session?.dispose?.();
|
|
37083
|
+
} catch {
|
|
37084
|
+
}
|
|
37085
|
+
}
|
|
37086
|
+
}
|
|
37087
|
+
function sanitizeCommitSubject(raw) {
|
|
37088
|
+
if (!raw) return null;
|
|
37089
|
+
const firstLine = raw.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
|
|
37090
|
+
if (!firstLine) return null;
|
|
37091
|
+
let subject = firstLine.replace(/^[-*]\s+/, "").replace(/^["'`]+|["'`]+$/g, "").trim();
|
|
37092
|
+
subject = subject.replace(/^[a-z]+(?:\([^)]+\))?:\s*/i, "").trim();
|
|
37093
|
+
subject = subject.replace(/\.+$/, "").trim();
|
|
37094
|
+
if (!subject) return null;
|
|
37095
|
+
if (subject.length > MAX_COMMIT_SUBJECT_LENGTH) {
|
|
37096
|
+
subject = subject.slice(0, MAX_COMMIT_SUBJECT_LENGTH).trim();
|
|
37097
|
+
}
|
|
37098
|
+
return subject || null;
|
|
37099
|
+
}
|
|
36914
37100
|
function __resetSummarizeState() {
|
|
36915
37101
|
rateLimits.clear();
|
|
36916
37102
|
}
|
|
36917
|
-
var SUMMARIZE_SYSTEM_PROMPT, MAX_DESCRIPTION_LENGTH, MIN_DESCRIPTION_LENGTH, MAX_TITLE_LENGTH, MAX_REQUESTS_PER_HOUR, RATE_LIMIT_WINDOW_MS, CLEANUP_INTERVAL_MS, rateLimits, cleanupInterval, ValidationError, RateLimitError, AiServiceError, DEBUG, COMMIT_BODY_SYSTEM_PROMPT, MAX_COMMIT_BODY_INPUT_LENGTH, MAX_COMMIT_BODY_LENGTH, DEFAULT_COMMIT_BODY_TIMEOUT_MS;
|
|
37103
|
+
var SUMMARIZE_SYSTEM_PROMPT, MAX_DESCRIPTION_LENGTH, MIN_DESCRIPTION_LENGTH, MAX_TITLE_LENGTH, MAX_REQUESTS_PER_HOUR, RATE_LIMIT_WINDOW_MS, CLEANUP_INTERVAL_MS, rateLimits, cleanupInterval, ValidationError, RateLimitError, AiServiceError, DEBUG, COMMIT_BODY_SYSTEM_PROMPT, MAX_COMMIT_BODY_INPUT_LENGTH, MAX_COMMIT_BODY_LENGTH, DEFAULT_COMMIT_BODY_TIMEOUT_MS, COMMIT_SUBJECT_SYSTEM_PROMPT, MAX_COMMIT_SUBJECT_LENGTH, DEFAULT_COMMIT_SUBJECT_TIMEOUT_MS;
|
|
36918
37104
|
var init_ai_summarize = __esm({
|
|
36919
37105
|
"../core/src/ai-summarize.ts"() {
|
|
36920
37106
|
"use strict";
|
|
@@ -36974,6 +37160,20 @@ Your job is to summarize what landed \u2014 using the branch's step commit subje
|
|
|
36974
37160
|
MAX_COMMIT_BODY_INPUT_LENGTH = 4e3;
|
|
36975
37161
|
MAX_COMMIT_BODY_LENGTH = 2e3;
|
|
36976
37162
|
DEFAULT_COMMIT_BODY_TIMEOUT_MS = 3e4;
|
|
37163
|
+
COMMIT_SUBJECT_SYSTEM_PROMPT = `You write commit message subjects for merge commits.
|
|
37164
|
+
|
|
37165
|
+
Your job is to summarize what landed \u2014 using the branch's step commit subjects (when provided) and the \`git diff --stat\` \u2014 into a single subject line that conveys the change's essence at a glance.
|
|
37166
|
+
|
|
37167
|
+
## Guidelines
|
|
37168
|
+
- Output ONLY the subject text \u2014 no quotes, no markdown, no body, no trailing period
|
|
37169
|
+
- Do NOT include any \`feat:\`, \`fix:\`, scope, or task-id prefix \u2014 the caller adds that
|
|
37170
|
+
- Imperative mood ("add X", "fix Y", "refactor Z") and lower-case first word
|
|
37171
|
+
- Hard cap: 60 characters; aim for 40\u201355
|
|
37172
|
+
- Be specific: name the most consequential module/feature/behavior that changed
|
|
37173
|
+
- If the branch has one clear theme, describe it; if it's mixed, lead with the largest change
|
|
37174
|
+
- Do not invent details that aren't in the input`;
|
|
37175
|
+
MAX_COMMIT_SUBJECT_LENGTH = 60;
|
|
37176
|
+
DEFAULT_COMMIT_SUBJECT_TIMEOUT_MS = 15e3;
|
|
36977
37177
|
}
|
|
36978
37178
|
});
|
|
36979
37179
|
|
|
@@ -49154,6 +49354,7 @@ __export(src_exports, {
|
|
|
49154
49354
|
COLUMN_DESCRIPTIONS: () => COLUMN_DESCRIPTIONS,
|
|
49155
49355
|
COLUMN_LABELS: () => COLUMN_LABELS,
|
|
49156
49356
|
COMMIT_BODY_SYSTEM_PROMPT: () => COMMIT_BODY_SYSTEM_PROMPT,
|
|
49357
|
+
COMMIT_SUBJECT_SYSTEM_PROMPT: () => COMMIT_SUBJECT_SYSTEM_PROMPT,
|
|
49157
49358
|
COMPACT_MEMORY_SYSTEM_PROMPT: () => COMPACT_MEMORY_SYSTEM_PROMPT,
|
|
49158
49359
|
CentralCore: () => CentralCore,
|
|
49159
49360
|
CentralDatabase: () => CentralDatabase,
|
|
@@ -49164,6 +49365,7 @@ __export(src_exports, {
|
|
|
49164
49365
|
DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS: () => DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS,
|
|
49165
49366
|
DEFAULT_AUTO_SUMMARIZE_SCHEDULE: () => DEFAULT_AUTO_SUMMARIZE_SCHEDULE,
|
|
49166
49367
|
DEFAULT_COMMIT_BODY_TIMEOUT_MS: () => DEFAULT_COMMIT_BODY_TIMEOUT_MS,
|
|
49368
|
+
DEFAULT_COMMIT_SUBJECT_TIMEOUT_MS: () => DEFAULT_COMMIT_SUBJECT_TIMEOUT_MS,
|
|
49167
49369
|
DEFAULT_EXECUTION_MODE: () => DEFAULT_EXECUTION_MODE,
|
|
49168
49370
|
DEFAULT_GLOBAL_SETTINGS: () => DEFAULT_GLOBAL_SETTINGS,
|
|
49169
49371
|
DEFAULT_INSIGHT_SCHEDULE: () => DEFAULT_INSIGHT_SCHEDULE,
|
|
@@ -49187,6 +49389,7 @@ __export(src_exports, {
|
|
|
49187
49389
|
InsightStore: () => InsightStore,
|
|
49188
49390
|
MAX_COMMIT_BODY_INPUT_LENGTH: () => MAX_COMMIT_BODY_INPUT_LENGTH,
|
|
49189
49391
|
MAX_COMMIT_BODY_LENGTH: () => MAX_COMMIT_BODY_LENGTH,
|
|
49392
|
+
MAX_COMMIT_SUBJECT_LENGTH: () => MAX_COMMIT_SUBJECT_LENGTH,
|
|
49190
49393
|
MAX_DESCRIPTION_LENGTH: () => MAX_DESCRIPTION_LENGTH,
|
|
49191
49394
|
MAX_REQUESTS_PER_HOUR: () => MAX_REQUESTS_PER_HOUR,
|
|
49192
49395
|
MAX_ROUTINE_RUN_HISTORY: () => MAX_ROUTINE_RUN_HISTORY,
|
|
@@ -49423,6 +49626,7 @@ __export(src_exports, {
|
|
|
49423
49626
|
runGhAsync: () => runGhAsync,
|
|
49424
49627
|
runGhJson: () => runGhJson,
|
|
49425
49628
|
runGhJsonAsync: () => runGhJsonAsync,
|
|
49629
|
+
sanitizeCommitSubject: () => sanitizeCommitSubject,
|
|
49426
49630
|
scheduleQmdInstallAndRefresh: () => scheduleQmdInstallAndRefresh,
|
|
49427
49631
|
scheduleQmdProjectMemoryRefresh: () => scheduleQmdProjectMemoryRefresh,
|
|
49428
49632
|
searchProjectMemory: () => searchProjectMemory,
|
|
@@ -49432,6 +49636,7 @@ __export(src_exports, {
|
|
|
49432
49636
|
slugify: () => slugify,
|
|
49433
49637
|
sortTasksByPriorityThenAgeAndId: () => sortTasksByPriorityThenAgeAndId,
|
|
49434
49638
|
summarizeCommitBody: () => summarizeCommitBody,
|
|
49639
|
+
summarizeCommitSubject: () => summarizeCommitSubject,
|
|
49435
49640
|
summarizeTitle: () => summarizeTitle,
|
|
49436
49641
|
syncAutoSummarizeAutomation: () => syncAutoSummarizeAutomation,
|
|
49437
49642
|
syncBackupAutomation: () => syncBackupAutomation,
|
|
@@ -55808,21 +56013,37 @@ function resetMergeWithWarn(rootDir, taskId, label) {
|
|
|
55808
56013
|
async function buildDeterministicMergeMessage(params) {
|
|
55809
56014
|
const { taskId, branch, commitLog, diffStat, includeTaskId, rootDir, settings, signal } = params;
|
|
55810
56015
|
const prefix = includeTaskId ? `feat(${taskId})` : "feat";
|
|
55811
|
-
const
|
|
56016
|
+
const fallbackSubject = `${prefix}: merge ${branch}`;
|
|
55812
56017
|
const trimmedCommitLog = commitLog?.trim() ?? "";
|
|
55813
56018
|
const trimmedDiffStat = diffStat?.trim() ?? "";
|
|
55814
56019
|
const commitsSection = trimmedCommitLog.length > 0 ? trimmedCommitLog : `- merge ${branch}`;
|
|
55815
56020
|
let aiSummary = null;
|
|
56021
|
+
let aiSubject = null;
|
|
55816
56022
|
if (rootDir && settings && (trimmedCommitLog.length > 0 || trimmedDiffStat.length > 0)) {
|
|
55817
56023
|
const useTitleSummarizer = !!settings.titleSummarizerProvider && !!settings.titleSummarizerModelId;
|
|
55818
56024
|
const provider = useTitleSummarizer ? settings.titleSummarizerProvider : settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider;
|
|
55819
56025
|
const modelId = useTitleSummarizer ? settings.titleSummarizerModelId : settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId;
|
|
55820
|
-
|
|
55821
|
-
|
|
55822
|
-
|
|
55823
|
-
|
|
55824
|
-
|
|
55825
|
-
|
|
56026
|
+
const [bodyResult, subjectResult] = await Promise.all([
|
|
56027
|
+
summarizeCommitBody(trimmedDiffStat, rootDir, provider, modelId, {
|
|
56028
|
+
branch,
|
|
56029
|
+
taskId,
|
|
56030
|
+
commitLog: trimmedCommitLog,
|
|
56031
|
+
signal
|
|
56032
|
+
}).catch(() => null),
|
|
56033
|
+
summarizeCommitSubject(trimmedDiffStat, rootDir, provider, modelId, {
|
|
56034
|
+
branch,
|
|
56035
|
+
taskId,
|
|
56036
|
+
commitLog: trimmedCommitLog,
|
|
56037
|
+
signal
|
|
56038
|
+
}).catch(() => null)
|
|
56039
|
+
]);
|
|
56040
|
+
aiSummary = bodyResult;
|
|
56041
|
+
aiSubject = subjectResult;
|
|
56042
|
+
}
|
|
56043
|
+
let subject = fallbackSubject;
|
|
56044
|
+
if (aiSubject && aiSubject.length > 0) {
|
|
56045
|
+
const candidate = `${prefix}: ${aiSubject}`;
|
|
56046
|
+
subject = candidate.length > 72 ? candidate.slice(0, 72).trimEnd() : candidate;
|
|
55826
56047
|
}
|
|
55827
56048
|
const sections = [];
|
|
55828
56049
|
if (aiSummary && aiSummary.trim().length > 0) {
|
|
@@ -55883,11 +56104,18 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
55883
56104
|
);
|
|
55884
56105
|
return false;
|
|
55885
56106
|
}
|
|
56107
|
+
const actualContext = await computeActualMergeCommitContext({
|
|
56108
|
+
rootDir,
|
|
56109
|
+
integrationTargetSha: preAttemptHeadSha,
|
|
56110
|
+
branch
|
|
56111
|
+
});
|
|
56112
|
+
const messageCommitLog = actualContext.commitLog || commitLog;
|
|
56113
|
+
const messageDiffStat = actualContext.diffStat || diffStat;
|
|
55886
56114
|
const { subjectArg, bodyArg } = await buildDeterministicMergeMessage({
|
|
55887
56115
|
taskId,
|
|
55888
56116
|
branch,
|
|
55889
|
-
commitLog,
|
|
55890
|
-
diffStat,
|
|
56117
|
+
commitLog: messageCommitLog,
|
|
56118
|
+
diffStat: messageDiffStat,
|
|
55891
56119
|
includeTaskId,
|
|
55892
56120
|
rootDir,
|
|
55893
56121
|
settings,
|
|
@@ -56339,6 +56567,52 @@ async function collectPatchIds(rootDir, target, windowSize) {
|
|
|
56339
56567
|
}
|
|
56340
56568
|
return ids;
|
|
56341
56569
|
}
|
|
56570
|
+
async function computeActualMergeCommitContext(params) {
|
|
56571
|
+
const { rootDir, integrationTargetSha, branch } = params;
|
|
56572
|
+
const targetArg = quoteArg(integrationTargetSha);
|
|
56573
|
+
let diffStat = "";
|
|
56574
|
+
try {
|
|
56575
|
+
const { stdout: stagedStat } = await execAsync2(
|
|
56576
|
+
`git diff --cached ${targetArg} --stat`,
|
|
56577
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
56578
|
+
);
|
|
56579
|
+
diffStat = stagedStat.trim();
|
|
56580
|
+
if (diffStat.length === 0) {
|
|
56581
|
+
const { stdout: headStat } = await execAsync2(
|
|
56582
|
+
`git diff ${targetArg} HEAD --stat`,
|
|
56583
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
56584
|
+
);
|
|
56585
|
+
diffStat = headStat.trim();
|
|
56586
|
+
}
|
|
56587
|
+
} catch {
|
|
56588
|
+
}
|
|
56589
|
+
let commitLog = "";
|
|
56590
|
+
try {
|
|
56591
|
+
const targetPatchIds = await collectPatchIds(rootDir, integrationTargetSha, 200);
|
|
56592
|
+
const { stdout: branchShas } = await execAsync2(
|
|
56593
|
+
`git log ${targetArg}..${quoteArg(branch)} --format=%H`,
|
|
56594
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
56595
|
+
);
|
|
56596
|
+
const shas = branchShas.trim().split("\n").filter(Boolean);
|
|
56597
|
+
const lines = [];
|
|
56598
|
+
for (const sha of shas) {
|
|
56599
|
+
const pid = await commitPatchId(rootDir, sha);
|
|
56600
|
+
if (pid && targetPatchIds.has(pid)) continue;
|
|
56601
|
+
try {
|
|
56602
|
+
const { stdout: subj } = await execAsync2(
|
|
56603
|
+
`git log -1 ${quoteArg(sha)} --format=%s`,
|
|
56604
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
56605
|
+
);
|
|
56606
|
+
const s = subj.trim();
|
|
56607
|
+
if (s) lines.push(`- ${s}`);
|
|
56608
|
+
} catch {
|
|
56609
|
+
}
|
|
56610
|
+
}
|
|
56611
|
+
commitLog = lines.join("\n");
|
|
56612
|
+
} catch {
|
|
56613
|
+
}
|
|
56614
|
+
return { commitLog, diffStat };
|
|
56615
|
+
}
|
|
56342
56616
|
async function listBranchCommits(rootDir, target, branch) {
|
|
56343
56617
|
try {
|
|
56344
56618
|
const { stdout } = await execAsync2(
|
|
@@ -57952,11 +58226,25 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
57952
58226
|
}
|
|
57953
58227
|
try {
|
|
57954
58228
|
const authorArg = getCommitAuthorArg(params.settings);
|
|
58229
|
+
let integrationTargetSha;
|
|
58230
|
+
try {
|
|
58231
|
+
const { stdout } = await execAsync2("git rev-parse HEAD~1", {
|
|
58232
|
+
cwd: rootDir,
|
|
58233
|
+
encoding: "utf-8"
|
|
58234
|
+
});
|
|
58235
|
+
integrationTargetSha = stdout.trim() || void 0;
|
|
58236
|
+
} catch {
|
|
58237
|
+
}
|
|
58238
|
+
const actualContext = integrationTargetSha ? await computeActualMergeCommitContext({
|
|
58239
|
+
rootDir,
|
|
58240
|
+
integrationTargetSha,
|
|
58241
|
+
branch
|
|
58242
|
+
}) : { commitLog: "", diffStat: "" };
|
|
57955
58243
|
const { subjectArg, bodyArg } = await buildDeterministicMergeMessage({
|
|
57956
58244
|
taskId,
|
|
57957
58245
|
branch,
|
|
57958
|
-
commitLog,
|
|
57959
|
-
diffStat,
|
|
58246
|
+
commitLog: actualContext.commitLog || commitLog,
|
|
58247
|
+
diffStat: actualContext.diffStat || diffStat,
|
|
57960
58248
|
includeTaskId,
|
|
57961
58249
|
rootDir,
|
|
57962
58250
|
settings: params.settings,
|
|
@@ -64788,30 +65076,27 @@ var init_effective_node = __esm({
|
|
|
64788
65076
|
});
|
|
64789
65077
|
|
|
64790
65078
|
// ../engine/src/node-routing-policy.ts
|
|
64791
|
-
function applyUnavailableNodePolicy(
|
|
64792
|
-
|
|
64793
|
-
|
|
64794
|
-
|
|
64795
|
-
if (nodeStatus === void 0) {
|
|
64796
|
-
return { allowed: true, fallbackToLocal: false, reason: "unknown-health" };
|
|
65079
|
+
function applyUnavailableNodePolicy(params) {
|
|
65080
|
+
const { effectiveNode, nodeHealth, policy } = params;
|
|
65081
|
+
if (effectiveNode.source === "local") {
|
|
65082
|
+
return { allowed: true, fallbackToLocal: false };
|
|
64797
65083
|
}
|
|
64798
|
-
if (
|
|
64799
|
-
return { allowed: true, fallbackToLocal: false
|
|
65084
|
+
if (nodeHealth === "online" || nodeHealth === void 0) {
|
|
65085
|
+
return { allowed: true, fallbackToLocal: false };
|
|
64800
65086
|
}
|
|
64801
|
-
if (!UNHEALTHY_STATUSES.has(
|
|
64802
|
-
return { allowed: true, fallbackToLocal: false
|
|
65087
|
+
if (!effectiveNode.nodeId || !UNHEALTHY_STATUSES.has(nodeHealth)) {
|
|
65088
|
+
return { allowed: true, fallbackToLocal: false };
|
|
64803
65089
|
}
|
|
64804
65090
|
if (policy === "fallback-local") {
|
|
64805
65091
|
return {
|
|
64806
65092
|
allowed: true,
|
|
64807
65093
|
fallbackToLocal: true,
|
|
64808
|
-
reason: `
|
|
65094
|
+
reason: `Node ${effectiveNode.nodeId} is ${nodeHealth}; falling back to local per policy`
|
|
64809
65095
|
};
|
|
64810
65096
|
}
|
|
64811
65097
|
return {
|
|
64812
65098
|
allowed: false,
|
|
64813
|
-
|
|
64814
|
-
reason: `blocked:${nodeStatus}`
|
|
65099
|
+
reason: `Node ${effectiveNode.nodeId} is ${nodeHealth}; policy is block`
|
|
64815
65100
|
};
|
|
64816
65101
|
}
|
|
64817
65102
|
var UNHEALTHY_STATUSES;
|
|
@@ -64986,7 +65271,7 @@ var init_scheduler = __esm({
|
|
|
64986
65271
|
/** Tracks mission-linked tasks observed with status=failed before moveTask clears status/error. */
|
|
64987
65272
|
failedTaskIds = /* @__PURE__ */ new Set();
|
|
64988
65273
|
/** Tracks tasks blocked by unavailable-node policy to deduplicate block log entries. */
|
|
64989
|
-
|
|
65274
|
+
wasNodeBlocked = /* @__PURE__ */ new Set();
|
|
64990
65275
|
/**
|
|
64991
65276
|
* Validate that a task's filesystem state is intact.
|
|
64992
65277
|
* Checks that the task directory exists and PROMPT.md is present and non-empty.
|
|
@@ -65048,7 +65333,7 @@ var init_scheduler = __esm({
|
|
|
65048
65333
|
this.options.missionAutopilot.stop();
|
|
65049
65334
|
}
|
|
65050
65335
|
this.failedTaskIds.clear();
|
|
65051
|
-
this.
|
|
65336
|
+
this.wasNodeBlocked.clear();
|
|
65052
65337
|
schedulerLog.log("Stopped");
|
|
65053
65338
|
}
|
|
65054
65339
|
/**
|
|
@@ -65310,35 +65595,24 @@ var init_scheduler = __esm({
|
|
|
65310
65595
|
}
|
|
65311
65596
|
let effectiveNode = resolveEffectiveNode(freshTask, settings);
|
|
65312
65597
|
schedulerLog.log(`Task ${task.id} routed to node=${effectiveNode.nodeId ?? "local"} (source=${effectiveNode.source})`);
|
|
65313
|
-
if (effectiveNode.nodeId
|
|
65314
|
-
const
|
|
65315
|
-
const
|
|
65316
|
-
|
|
65317
|
-
|
|
65318
|
-
|
|
65319
|
-
);
|
|
65320
|
-
if (!
|
|
65321
|
-
if (!this.
|
|
65322
|
-
this.
|
|
65323
|
-
schedulerLog.
|
|
65324
|
-
|
|
65325
|
-
);
|
|
65326
|
-
await this.store.logEntry(
|
|
65327
|
-
task.id,
|
|
65328
|
-
`Routing blocked: node ${effectiveNode.nodeId} is ${nodeStatus ?? "unknown"}, policy=block`
|
|
65329
|
-
);
|
|
65598
|
+
if (effectiveNode.nodeId && this.options.nodeHealthMonitor) {
|
|
65599
|
+
const nodeHealth = this.options.nodeHealthMonitor.getNodeHealth(effectiveNode.nodeId);
|
|
65600
|
+
const decision = applyUnavailableNodePolicy({
|
|
65601
|
+
effectiveNode,
|
|
65602
|
+
nodeHealth,
|
|
65603
|
+
policy: settings.unavailableNodePolicy
|
|
65604
|
+
});
|
|
65605
|
+
if (!decision.allowed) {
|
|
65606
|
+
if (!this.wasNodeBlocked.has(task.id)) {
|
|
65607
|
+
this.wasNodeBlocked.add(task.id);
|
|
65608
|
+
schedulerLog.warn(`Task ${task.id} blocked: ${decision.reason}`);
|
|
65609
|
+
await this.store.logEntry(task.id, decision.reason);
|
|
65330
65610
|
}
|
|
65331
65611
|
continue;
|
|
65332
65612
|
}
|
|
65333
|
-
|
|
65334
|
-
|
|
65335
|
-
|
|
65336
|
-
`Task ${task.id} falling back to local \u2014 node ${effectiveNode.nodeId} is ${nodeStatus ?? "unknown"} (policy: fallback-local)`
|
|
65337
|
-
);
|
|
65338
|
-
await this.store.logEntry(
|
|
65339
|
-
task.id,
|
|
65340
|
-
`Routing fallback to local: node ${effectiveNode.nodeId} is ${nodeStatus ?? "unknown"}, policy=fallback-local`
|
|
65341
|
-
);
|
|
65613
|
+
if (decision.fallbackToLocal) {
|
|
65614
|
+
schedulerLog.log(`Task ${task.id} falling back to local: ${decision.reason}`);
|
|
65615
|
+
await this.store.logEntry(task.id, decision.reason);
|
|
65342
65616
|
effectiveNode = { nodeId: void 0, source: "local" };
|
|
65343
65617
|
}
|
|
65344
65618
|
}
|
|
@@ -65352,6 +65626,7 @@ var init_scheduler = __esm({
|
|
|
65352
65626
|
effectiveNodeSource: effectiveNode.source
|
|
65353
65627
|
});
|
|
65354
65628
|
await this.store.moveTask(task.id, "in-progress");
|
|
65629
|
+
this.wasNodeBlocked.delete(task.id);
|
|
65355
65630
|
await this.store.logEntry(task.id, `Node routing resolved: ${effectiveNode.nodeId ?? "local"} (source: ${effectiveNode.source})`);
|
|
65356
65631
|
this.options.onSchedule?.(task);
|
|
65357
65632
|
started++;
|
|
@@ -70435,6 +70710,7 @@ When sending messages:
|
|
|
70435
70710
|
}
|
|
70436
70711
|
const agentHasIdentity = hasAgentIdentity(agent);
|
|
70437
70712
|
const isAgentEphemeral = isEphemeralAgent(agent);
|
|
70713
|
+
const canRunNoTaskHeartbeat = agentHasIdentity && !isAgentEphemeral;
|
|
70438
70714
|
let taskId = explicitTaskId ?? agent.taskId;
|
|
70439
70715
|
let inboxSelection = null;
|
|
70440
70716
|
if (!taskId) {
|
|
@@ -70471,7 +70747,7 @@ When sending messages:
|
|
|
70471
70747
|
engineRunContext.taskId = taskId;
|
|
70472
70748
|
}
|
|
70473
70749
|
if (!taskId) {
|
|
70474
|
-
if (!
|
|
70750
|
+
if (!canRunNoTaskHeartbeat) {
|
|
70475
70751
|
heartbeatLog.log(`Agent ${agentId} has no task assignment \u2014 graceful exit`);
|
|
70476
70752
|
await this.completeRun(agentId, run.id, {
|
|
70477
70753
|
status: "completed",
|
|
@@ -70481,7 +70757,7 @@ When sending messages:
|
|
|
70481
70757
|
}
|
|
70482
70758
|
heartbeatLog.log(`Agent ${agentId} has no task but has identity \u2014 running no-task heartbeat`);
|
|
70483
70759
|
}
|
|
70484
|
-
|
|
70760
|
+
let isNoTaskRun = !taskId;
|
|
70485
70761
|
if (!isNoTaskRun) {
|
|
70486
70762
|
const validStates = ["active", "running", "idle"];
|
|
70487
70763
|
if (!validStates.includes(agent.state)) {
|
|
@@ -70507,53 +70783,99 @@ When sending messages:
|
|
|
70507
70783
|
});
|
|
70508
70784
|
return await this.store.getRunDetail(agentId, run.id);
|
|
70509
70785
|
}
|
|
70510
|
-
if (taskDetail.
|
|
70511
|
-
|
|
70512
|
-
|
|
70513
|
-
|
|
70514
|
-
|
|
70515
|
-
|
|
70516
|
-
|
|
70517
|
-
|
|
70518
|
-
|
|
70519
|
-
|
|
70786
|
+
if (taskDetail.column === "done" || taskDetail.column === "archived") {
|
|
70787
|
+
if (agent.taskId === resolvedTaskId2) {
|
|
70788
|
+
heartbeatLog.log(
|
|
70789
|
+
`Agent ${agentId} linked task ${resolvedTaskId2} is ${taskDetail.column} \u2014 clearing assignment and running heartbeat without task context`
|
|
70790
|
+
);
|
|
70791
|
+
try {
|
|
70792
|
+
await this.store.assignTask(agentId, void 0, runContext);
|
|
70793
|
+
} catch (clearErr) {
|
|
70794
|
+
heartbeatLog.warn(
|
|
70795
|
+
`Failed to clear terminal task assignment ${resolvedTaskId2} for ${agentId}: ${clearErr instanceof Error ? clearErr.message : String(clearErr)}`
|
|
70796
|
+
);
|
|
70520
70797
|
}
|
|
70521
|
-
|
|
70522
|
-
|
|
70798
|
+
taskId = void 0;
|
|
70799
|
+
taskDetail = void 0;
|
|
70800
|
+
isNoTaskRun = true;
|
|
70801
|
+
if (!canRunNoTaskHeartbeat) {
|
|
70802
|
+
await this.completeRun(agentId, run.id, {
|
|
70803
|
+
status: "completed",
|
|
70804
|
+
resultJson: { reason: "no_assignment" }
|
|
70805
|
+
});
|
|
70806
|
+
return await this.store.getRunDetail(agentId, run.id);
|
|
70807
|
+
}
|
|
70808
|
+
} else {
|
|
70809
|
+
heartbeatLog.log(
|
|
70810
|
+
`Heartbeat for ${agentId} targeted terminal task ${resolvedTaskId2} (${taskDetail.column}) \u2014 graceful exit`
|
|
70811
|
+
);
|
|
70812
|
+
await this.completeRun(agentId, run.id, {
|
|
70813
|
+
status: "completed",
|
|
70814
|
+
resultJson: { reason: "terminal_task", taskId: resolvedTaskId2, column: taskDetail.column }
|
|
70815
|
+
});
|
|
70816
|
+
return await this.store.getRunDetail(agentId, run.id);
|
|
70817
|
+
}
|
|
70523
70818
|
}
|
|
70524
|
-
|
|
70525
|
-
|
|
70526
|
-
|
|
70527
|
-
const
|
|
70528
|
-
|
|
70529
|
-
|
|
70530
|
-
const contextHash = Buffer.from(
|
|
70531
|
-
JSON.stringify({ commentCount, lastCommentId, lastSteeringCommentId, blockedBy })
|
|
70532
|
-
).toString("base64").slice(0, 16);
|
|
70533
|
-
const currentBlockedState = {
|
|
70534
|
-
taskId: resolvedTaskId2,
|
|
70535
|
-
blockedBy,
|
|
70536
|
-
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
70537
|
-
contextHash
|
|
70538
|
-
};
|
|
70539
|
-
const previousBlockedState = await this.store.getLastBlockedState(agentId);
|
|
70540
|
-
if (previousBlockedState && isBlockedStateDuplicate(currentBlockedState, previousBlockedState)) {
|
|
70819
|
+
if (isNoTaskRun) {
|
|
70820
|
+
heartbeatLog.log(`Agent ${agentId} terminal task assignment resolved into no-task heartbeat`);
|
|
70821
|
+
} else {
|
|
70822
|
+
const liveTaskDetail = taskDetail;
|
|
70823
|
+
if (!liveTaskDetail) {
|
|
70824
|
+
heartbeatLog.warn(`Task ${resolvedTaskId2} lost detail after terminal-assignment handling \u2014 graceful exit`);
|
|
70541
70825
|
await this.completeRun(agentId, run.id, {
|
|
70542
70826
|
status: "completed",
|
|
70543
|
-
resultJson: { reason: "
|
|
70827
|
+
resultJson: { reason: "task_not_found", taskId: resolvedTaskId2 }
|
|
70828
|
+
});
|
|
70829
|
+
return await this.store.getRunDetail(agentId, run.id);
|
|
70830
|
+
}
|
|
70831
|
+
if (liveTaskDetail.checkedOutBy && liveTaskDetail.checkedOutBy !== agentId) {
|
|
70832
|
+
heartbeatLog.warn(
|
|
70833
|
+
`Agent ${agentId} does not hold checkout for ${resolvedTaskId2} (held by ${liveTaskDetail.checkedOutBy}) \u2014 graceful exit`
|
|
70834
|
+
);
|
|
70835
|
+
await this.completeRun(agentId, run.id, {
|
|
70836
|
+
status: "completed",
|
|
70837
|
+
resultJson: {
|
|
70838
|
+
reason: "checkout_conflict",
|
|
70839
|
+
taskId: resolvedTaskId2,
|
|
70840
|
+
checkedOutBy: liveTaskDetail.checkedOutBy
|
|
70841
|
+
}
|
|
70842
|
+
});
|
|
70843
|
+
return await this.store.getRunDetail(agentId, run.id);
|
|
70844
|
+
}
|
|
70845
|
+
const blockedBy = typeof liveTaskDetail.blockedBy === "string" ? liveTaskDetail.blockedBy.trim() : "";
|
|
70846
|
+
const isBlockedTask = liveTaskDetail.status === "queued" && blockedBy.length > 0;
|
|
70847
|
+
if (isBlockedTask) {
|
|
70848
|
+
const commentCount = (liveTaskDetail.comments?.length ?? 0) + (liveTaskDetail.steeringComments?.length ?? 0);
|
|
70849
|
+
const lastCommentId = liveTaskDetail.comments?.at(-1)?.id;
|
|
70850
|
+
const lastSteeringCommentId = liveTaskDetail.steeringComments?.at(-1)?.id;
|
|
70851
|
+
const contextHash = Buffer.from(
|
|
70852
|
+
JSON.stringify({ commentCount, lastCommentId, lastSteeringCommentId, blockedBy })
|
|
70853
|
+
).toString("base64").slice(0, 16);
|
|
70854
|
+
const currentBlockedState = {
|
|
70855
|
+
taskId: resolvedTaskId2,
|
|
70856
|
+
blockedBy,
|
|
70857
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
70858
|
+
contextHash
|
|
70859
|
+
};
|
|
70860
|
+
const previousBlockedState = await this.store.getLastBlockedState(agentId);
|
|
70861
|
+
if (previousBlockedState && isBlockedStateDuplicate(currentBlockedState, previousBlockedState)) {
|
|
70862
|
+
await this.completeRun(agentId, run.id, {
|
|
70863
|
+
status: "completed",
|
|
70864
|
+
resultJson: { reason: "blocked_duplicate", taskId: resolvedTaskId2, blockedBy }
|
|
70865
|
+
});
|
|
70866
|
+
return await this.store.getRunDetail(agentId, run.id);
|
|
70867
|
+
}
|
|
70868
|
+
const blockedMessage = `Task is blocked by ${blockedBy}; waiting for dependency/context changes before retrying.`;
|
|
70869
|
+
await taskStore.addComment(resolvedTaskId2, blockedMessage, "agent", void 0, runContext);
|
|
70870
|
+
await audit.database({ type: "task:comment:add", target: resolvedTaskId2, metadata: { blockedBy } });
|
|
70871
|
+
await this.store.setLastBlockedState(agentId, currentBlockedState);
|
|
70872
|
+
heartbeatLog.log(`Task ${resolvedTaskId2} is blocked by ${blockedBy} \u2014 recorded blocked state`);
|
|
70873
|
+
await this.completeRun(agentId, run.id, {
|
|
70874
|
+
status: "completed",
|
|
70875
|
+
resultJson: { reason: "blocked", taskId: resolvedTaskId2, blockedBy }
|
|
70544
70876
|
});
|
|
70545
70877
|
return await this.store.getRunDetail(agentId, run.id);
|
|
70546
70878
|
}
|
|
70547
|
-
const blockedMessage = `Task is blocked by ${blockedBy}; waiting for dependency/context changes before retrying.`;
|
|
70548
|
-
await taskStore.addComment(resolvedTaskId2, blockedMessage, "agent", void 0, runContext);
|
|
70549
|
-
await audit.database({ type: "task:comment:add", target: resolvedTaskId2, metadata: { blockedBy } });
|
|
70550
|
-
await this.store.setLastBlockedState(agentId, currentBlockedState);
|
|
70551
|
-
heartbeatLog.log(`Task ${resolvedTaskId2} is blocked by ${blockedBy} \u2014 recorded blocked state`);
|
|
70552
|
-
await this.completeRun(agentId, run.id, {
|
|
70553
|
-
status: "completed",
|
|
70554
|
-
resultJson: { reason: "blocked", taskId: resolvedTaskId2, blockedBy }
|
|
70555
|
-
});
|
|
70556
|
-
return await this.store.getRunDetail(agentId, run.id);
|
|
70557
70879
|
}
|
|
70558
70880
|
}
|
|
70559
70881
|
if (!isNoTaskRun) {
|
|
@@ -78665,6 +78987,7 @@ __export(src_exports2, {
|
|
|
78665
78987
|
WebhookNotificationProvider: () => WebhookNotificationProvider,
|
|
78666
78988
|
WorktreePool: () => WorktreePool,
|
|
78667
78989
|
aiMergeTask: () => aiMergeTask,
|
|
78990
|
+
applyUnavailableNodePolicy: () => applyUnavailableNodePolicy,
|
|
78668
78991
|
buildAgentChatPrompt: () => buildAgentChatPrompt,
|
|
78669
78992
|
buildNtfyClickUrl: () => buildNtfyClickUrl,
|
|
78670
78993
|
buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
|
|
@@ -78748,6 +79071,7 @@ var init_src2 = __esm({
|
|
|
78748
79071
|
init_project_engine();
|
|
78749
79072
|
init_project_engine_manager();
|
|
78750
79073
|
init_node_health_monitor();
|
|
79074
|
+
init_node_routing_policy();
|
|
78751
79075
|
init_peer_exchange_service();
|
|
78752
79076
|
init_remote_access();
|
|
78753
79077
|
init_remote_node_client();
|