@runfusion/fusion 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2707 -1417
- package/dist/client/assets/ChatView-BomXmqar.js +1 -0
- package/dist/client/assets/{DevServerView-DIrmWI5T.js → DevServerView-yFvF4xL4.js} +1 -1
- package/dist/client/assets/DirectoryPicker-BDNodhtF.js +1 -0
- package/dist/client/assets/DocumentsView-CAWtDEaL.js +1 -0
- package/dist/client/assets/{InsightsView-DAJSq4gV.js → InsightsView-CDkiJeW1.js} +2 -2
- package/dist/client/assets/MemoryView-ZRQ9EL9H.js +2 -0
- package/dist/client/assets/NodesView-DosrOyeH.js +14 -0
- package/dist/client/assets/NodesView-sJgPLTzz.css +1 -0
- package/dist/client/assets/{PiExtensionsManager-DD0fTQNf.js → PiExtensionsManager-CzZ1LEpz.js} +3 -3
- package/dist/client/assets/PluginManager-Dp3vPsMO.js +1 -0
- package/dist/client/assets/ResearchView-PvNkdaQE.js +1 -0
- package/dist/client/assets/{RoadmapsView-DsH7Hicx.js → RoadmapsView-BUW-HJz5.js} +2 -2
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +1 -0
- package/dist/client/assets/{SettingsModal-Cn_CIPXu.js → SettingsModal-ByVl_fUi.js} +1 -1
- package/dist/client/assets/SettingsModal-oOnIed5O.css +1 -0
- package/dist/client/assets/SettingsModal-uzo470XS.js +31 -0
- package/dist/client/assets/SetupWizardModal-DH1hpyiP.js +1 -0
- package/dist/client/assets/SkillsView-B-RqQSFE.js +1 -0
- package/dist/client/assets/index-CtiRbTNv.js +1229 -0
- package/dist/client/assets/index-Dy-xC2C2.css +1 -0
- package/dist/client/assets/{users-DEicv0kj.js → users-WyHhw14V.js} +2 -2
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/index.ts +12 -7
- package/dist/droid-cli/package.json +4 -1
- package/dist/droid-cli/src/__tests__/provider.test.ts +42 -6
- package/dist/extension.js +1701 -565
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/package.json +2 -1
- package/skill/fusion/SKILL.md +2 -2
- package/skill/fusion/references/best-practices.md +33 -0
- package/skill/fusion/references/extension-tools.md +47 -2
- package/skill/fusion/references/fusion-capabilities.md +7 -2
- package/dist/client/assets/AgentDetailView-BmxnuM0D.js +0 -18
- package/dist/client/assets/AgentDetailView-yu8Xltqk.css +0 -1
- package/dist/client/assets/AgentsView-1xSqjJxs.js +0 -517
- package/dist/client/assets/AgentsView-Bs03ptrd.css +0 -1
- package/dist/client/assets/ChatView-CkWkEwXL.js +0 -1
- package/dist/client/assets/DirectoryPicker-Sqwdifcb.js +0 -1
- package/dist/client/assets/DocumentsView-Cx_02o_z.js +0 -1
- package/dist/client/assets/MemoryView-CCIBAre3.js +0 -2
- package/dist/client/assets/NodesView-D02HxGCl.js +0 -14
- package/dist/client/assets/NodesView-DuAXX_0j.css +0 -1
- package/dist/client/assets/PluginManager-Cfl0VBX9.js +0 -1
- package/dist/client/assets/ResearchView-B9RqOVbr.js +0 -1
- package/dist/client/assets/SettingsModal-D_AFkDJa.css +0 -1
- package/dist/client/assets/SettingsModal-Dq4a5KSX.css +0 -1
- package/dist/client/assets/SettingsModal-YH_rM1ZT.js +0 -31
- package/dist/client/assets/SetupWizardModal-k5vqrHZU.js +0 -1
- package/dist/client/assets/SkillsView-BIdt5cfB.js +0 -1
- package/dist/client/assets/folder-open-B3TO7t7Z.js +0 -6
- package/dist/client/assets/index-BIJgrHEn.css +0 -1
- package/dist/client/assets/index-BlkXZ4C5.js +0 -682
- package/dist/client/assets/star-DW-M-BD_.js +0 -6
- package/dist/client/assets/upload-BzG6fknr.js +0 -6
package/dist/extension.js
CHANGED
|
@@ -1389,7 +1389,14 @@ Note: Refs (@e1, @e2) are invalidated after page navigation. Re-snapshot after c
|
|
|
1389
1389
|
toolMode: "readonly",
|
|
1390
1390
|
prompt: `You are a UX design reviewer. Verify frontend changes maintain visual polish and consistency with existing UI patterns and design tokens.
|
|
1391
1391
|
|
|
1392
|
-
|
|
1392
|
+
FAST-BAIL RULE (check this FIRST):
|
|
1393
|
+
- The task harness gives you a "Diff Scope" listing the files this task actually changed.
|
|
1394
|
+
- If that list contains NO frontend/UI files (no .tsx/.jsx/.ts/.js component files, no .css/.scss/.sass/.styl, no .html/.vue/.svelte/.astro, no design-token/theme files), respond IMMEDIATELY with a single short line such as "No UI changes in scope \u2014 approved." and STOP.
|
|
1395
|
+
- Do NOT explore the worktree looking for related-looking UI code to critique. If this task didn't change a UI file, your review is a no-op by definition.
|
|
1396
|
+
|
|
1397
|
+
Otherwise, restrict your review to the UI files actually present in the diff scope.
|
|
1398
|
+
|
|
1399
|
+
Design System Review (only for UI files in the diff scope):
|
|
1393
1400
|
1. **Visual Hierarchy** \u2014 Check that the changes maintain consistent heading levels, content flow, and information architecture
|
|
1394
1401
|
2. **Spacing and Typography** \u2014 Verify consistent spacing (margins, padding, gaps) and typography scale usage
|
|
1395
1402
|
3. **Color and Token Consistency** \u2014 Check that CSS custom properties and design tokens are used correctly; no hardcoded color values that bypass the design system
|
|
@@ -1397,15 +1404,16 @@ Design System Review:
|
|
|
1397
1404
|
5. **Responsive Behavior** \u2014 Check that layouts adapt properly across viewport sizes and maintain usability on mobile
|
|
1398
1405
|
6. **Fit with Design Language** \u2014 Verify the visual style matches existing patterns (border radius, shadows, transitions, icon style, etc.)
|
|
1399
1406
|
|
|
1400
|
-
Files to Review:
|
|
1407
|
+
Files to Review (only those that appear in the Diff Scope):
|
|
1401
1408
|
- Modified UI components (React, Vue, Angular, HTML)
|
|
1402
1409
|
- CSS/SCSS/styled-component files
|
|
1403
1410
|
- Design token or theme configuration files
|
|
1404
1411
|
|
|
1405
1412
|
Output Requirements:
|
|
1406
|
-
- If design is consistent and polished
|
|
1407
|
-
- If issues found: describe each finding with specific file paths and suggested corrections
|
|
1408
|
-
- Prioritize issues by impact: layout breaks > visual inconsistency > style preferences
|
|
1413
|
+
- If design is consistent and polished (or there are no UI files in scope): respond with a brief approval line and stop.
|
|
1414
|
+
- If issues found: start your response with "REQUEST REVISION" and describe each finding with specific file paths and suggested corrections.
|
|
1415
|
+
- Prioritize issues by impact: layout breaks > visual inconsistency > style preferences.
|
|
1416
|
+
- Do NOT spend time on stylistic nits when no real issues exist.`
|
|
1409
1417
|
}
|
|
1410
1418
|
];
|
|
1411
1419
|
DOCUMENT_KEY_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
@@ -2763,7 +2771,7 @@ var init_db = __esm({
|
|
|
2763
2771
|
"use strict";
|
|
2764
2772
|
init_sqlite_adapter();
|
|
2765
2773
|
init_types();
|
|
2766
|
-
SCHEMA_VERSION =
|
|
2774
|
+
SCHEMA_VERSION = 60;
|
|
2767
2775
|
SCHEMA_SQL = `
|
|
2768
2776
|
-- Tasks table with JSON columns for nested data
|
|
2769
2777
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -2831,6 +2839,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2831
2839
|
missionId TEXT,
|
|
2832
2840
|
sliceId TEXT,
|
|
2833
2841
|
assignedAgentId TEXT,
|
|
2842
|
+
pausedByAgentId TEXT,
|
|
2834
2843
|
assigneeUserId TEXT,
|
|
2835
2844
|
sourceType TEXT,
|
|
2836
2845
|
sourceAgentId TEXT,
|
|
@@ -4628,6 +4637,12 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4628
4637
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idxInsightRunEventsRunIdSeq ON project_insight_run_events(runId, seq)`);
|
|
4629
4638
|
});
|
|
4630
4639
|
}
|
|
4640
|
+
if (version < 60) {
|
|
4641
|
+
this.applyMigration(60, () => {
|
|
4642
|
+
this.addColumnIfMissing("tasks", "pausedByAgentId", "TEXT");
|
|
4643
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idxTasksPausedByAgentId ON tasks(pausedByAgentId)`);
|
|
4644
|
+
});
|
|
4645
|
+
}
|
|
4631
4646
|
}
|
|
4632
4647
|
/**
|
|
4633
4648
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -29610,8 +29625,8 @@ var require_CronFileParser = __commonJS({
|
|
|
29610
29625
|
* @throws If file cannot be read
|
|
29611
29626
|
*/
|
|
29612
29627
|
static parseFileSync(filePath) {
|
|
29613
|
-
const { readFileSync:
|
|
29614
|
-
const data =
|
|
29628
|
+
const { readFileSync: readFileSync12 } = __require("fs");
|
|
29629
|
+
const data = readFileSync12(filePath, "utf8");
|
|
29615
29630
|
return _CronFileParser.#parseContent(data);
|
|
29616
29631
|
}
|
|
29617
29632
|
/**
|
|
@@ -32637,6 +32652,7 @@ var init_store = __esm({
|
|
|
32637
32652
|
missionId: row.missionId || void 0,
|
|
32638
32653
|
sliceId: row.sliceId || void 0,
|
|
32639
32654
|
assignedAgentId: row.assignedAgentId || void 0,
|
|
32655
|
+
pausedByAgentId: row.pausedByAgentId || void 0,
|
|
32640
32656
|
assigneeUserId: row.assigneeUserId || void 0,
|
|
32641
32657
|
nodeId: row.nodeId || void 0,
|
|
32642
32658
|
effectiveNodeId: row.effectiveNodeId || void 0,
|
|
@@ -32901,6 +32917,7 @@ ${recentText}` : void 0
|
|
|
32901
32917
|
"missionId",
|
|
32902
32918
|
"sliceId",
|
|
32903
32919
|
"assignedAgentId",
|
|
32920
|
+
"pausedByAgentId",
|
|
32904
32921
|
"assigneeUserId",
|
|
32905
32922
|
"nodeId",
|
|
32906
32923
|
"effectiveNodeId",
|
|
@@ -33013,6 +33030,7 @@ ${outcome}`;
|
|
|
33013
33030
|
"missionId",
|
|
33014
33031
|
"sliceId",
|
|
33015
33032
|
"assignedAgentId",
|
|
33033
|
+
"pausedByAgentId",
|
|
33016
33034
|
"assigneeUserId",
|
|
33017
33035
|
"nodeId",
|
|
33018
33036
|
"effectiveNodeId",
|
|
@@ -33063,9 +33081,9 @@ ${outcome}`;
|
|
|
33063
33081
|
dependencies, steps, log, attachments, steeringComments,
|
|
33064
33082
|
comments, workflowStepResults, prInfo, issueInfo,
|
|
33065
33083
|
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
33066
|
-
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33084
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33067
33085
|
) VALUES (
|
|
33068
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33086
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33069
33087
|
)
|
|
33070
33088
|
ON CONFLICT(id) DO UPDATE SET
|
|
33071
33089
|
title = excluded.title,
|
|
@@ -33134,6 +33152,7 @@ ${outcome}`;
|
|
|
33134
33152
|
missionId = excluded.missionId,
|
|
33135
33153
|
sliceId = excluded.sliceId,
|
|
33136
33154
|
assignedAgentId = excluded.assignedAgentId,
|
|
33155
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
33137
33156
|
assigneeUserId = excluded.assigneeUserId,
|
|
33138
33157
|
nodeId = excluded.nodeId,
|
|
33139
33158
|
effectiveNodeId = excluded.effectiveNodeId,
|
|
@@ -33215,6 +33234,7 @@ ${outcome}`;
|
|
|
33215
33234
|
task.missionId ?? null,
|
|
33216
33235
|
task.sliceId ?? null,
|
|
33217
33236
|
task.assignedAgentId ?? null,
|
|
33237
|
+
task.pausedByAgentId ?? null,
|
|
33218
33238
|
task.assigneeUserId ?? null,
|
|
33219
33239
|
task.nodeId ?? null,
|
|
33220
33240
|
task.effectiveNodeId ?? null,
|
|
@@ -34332,6 +34352,23 @@ ${newTask.description}
|
|
|
34332
34352
|
const matches = [...activeMatches, ...archiveMatches];
|
|
34333
34353
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
34334
34354
|
}
|
|
34355
|
+
async getTasksByAssignedAgent(agentId, options) {
|
|
34356
|
+
const whereClauses = ["assignedAgentId = ?"];
|
|
34357
|
+
const params = [agentId];
|
|
34358
|
+
if (options?.pausedOnly) {
|
|
34359
|
+
whereClauses.push("paused = 1");
|
|
34360
|
+
}
|
|
34361
|
+
if (options?.excludeArchived) {
|
|
34362
|
+
whereClauses.push(`"column" != 'archived'`);
|
|
34363
|
+
}
|
|
34364
|
+
const selectClause = this.getTaskSelectClause(false);
|
|
34365
|
+
const rows = this.db.prepare(`
|
|
34366
|
+
SELECT ${selectClause} FROM tasks
|
|
34367
|
+
WHERE ${whereClauses.join(" AND ")}
|
|
34368
|
+
ORDER BY createdAt ASC
|
|
34369
|
+
`).all(...params);
|
|
34370
|
+
return rows.map((row) => this.rowToTask(row));
|
|
34371
|
+
}
|
|
34335
34372
|
async selectNextTaskForAgent(agentId) {
|
|
34336
34373
|
const tasks = await this.listTasks({ slim: true });
|
|
34337
34374
|
if (tasks.length === 0) {
|
|
@@ -34569,6 +34606,11 @@ ${newTask.description}
|
|
|
34569
34606
|
} else if (updates.assignedAgentId !== void 0) {
|
|
34570
34607
|
task.assignedAgentId = updates.assignedAgentId;
|
|
34571
34608
|
}
|
|
34609
|
+
if (updates.pausedByAgentId === null) {
|
|
34610
|
+
task.pausedByAgentId = void 0;
|
|
34611
|
+
} else if (updates.pausedByAgentId !== void 0) {
|
|
34612
|
+
task.pausedByAgentId = updates.pausedByAgentId;
|
|
34613
|
+
}
|
|
34572
34614
|
if (updates.assigneeUserId === null) {
|
|
34573
34615
|
task.assigneeUserId = void 0;
|
|
34574
34616
|
} else if (updates.assigneeUserId !== void 0) {
|
|
@@ -34807,14 +34849,21 @@ ${newTask.description}
|
|
|
34807
34849
|
* Pause or unpause a task. Paused tasks are excluded from all automated
|
|
34808
34850
|
* agent and scheduler interaction. Logs the action and emits `task:updated`.
|
|
34809
34851
|
*/
|
|
34810
|
-
async pauseTask(id, paused, runContext) {
|
|
34852
|
+
async pauseTask(id, paused, runContext, agentOptions) {
|
|
34811
34853
|
return this.withTaskLock(id, async () => {
|
|
34812
34854
|
const dir = this.taskDir(id);
|
|
34813
34855
|
const task = await this.readTaskJson(dir);
|
|
34814
34856
|
if (!task.log) {
|
|
34815
34857
|
task.log = [];
|
|
34816
34858
|
}
|
|
34859
|
+
const previousPausedByAgentId = task.pausedByAgentId;
|
|
34817
34860
|
task.paused = paused || void 0;
|
|
34861
|
+
if (paused && agentOptions?.pausedByAgentId) {
|
|
34862
|
+
task.pausedByAgentId = agentOptions.pausedByAgentId;
|
|
34863
|
+
}
|
|
34864
|
+
if (!paused) {
|
|
34865
|
+
task.pausedByAgentId = void 0;
|
|
34866
|
+
}
|
|
34818
34867
|
if (task.column === "in-progress" || task.column === "in-review") {
|
|
34819
34868
|
task.status = paused ? "paused" : void 0;
|
|
34820
34869
|
}
|
|
@@ -34822,7 +34871,7 @@ ${newTask.description}
|
|
|
34822
34871
|
task.updatedAt = now;
|
|
34823
34872
|
const logEntry = {
|
|
34824
34873
|
timestamp: now,
|
|
34825
|
-
action: paused ? "Task paused" : "Task unpaused"
|
|
34874
|
+
action: paused ? agentOptions?.pausedByAgentId ? `Task paused (agent ${agentOptions.pausedByAgentId} paused)` : "Task paused" : previousPausedByAgentId ? `Task unpaused (agent ${previousPausedByAgentId} resumed)` : "Task unpaused"
|
|
34826
34875
|
};
|
|
34827
34876
|
if (runContext) {
|
|
34828
34877
|
logEntry.runContext = runContext;
|
|
@@ -39973,10 +40022,17 @@ var init_docker_client = __esm({
|
|
|
39973
40022
|
}
|
|
39974
40023
|
return this.createDockerInstance(hostConfig);
|
|
39975
40024
|
}
|
|
39976
|
-
async getContainerInfo(containerId) {
|
|
40025
|
+
async getContainerInfo(containerId, hostConfig) {
|
|
39977
40026
|
try {
|
|
39978
|
-
const docker = await this.
|
|
40027
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
39979
40028
|
const inspect = await docker.getContainer(containerId).inspect();
|
|
40029
|
+
const ports = Object.entries(inspect.NetworkSettings?.Ports ?? {}).reduce((acc, [key, value]) => {
|
|
40030
|
+
const binding = Array.isArray(value) && value.length > 0 ? value[0] : void 0;
|
|
40031
|
+
if (binding?.HostPort) {
|
|
40032
|
+
acc[key] = binding.HostPort;
|
|
40033
|
+
}
|
|
40034
|
+
return acc;
|
|
40035
|
+
}, {});
|
|
39980
40036
|
return {
|
|
39981
40037
|
id: inspect.Id,
|
|
39982
40038
|
name: (inspect.Name ?? "").replace(/^\//, ""),
|
|
@@ -39988,8 +40044,12 @@ var init_docker_client = __esm({
|
|
|
39988
40044
|
paused: Boolean(inspect.State?.Paused),
|
|
39989
40045
|
restarting: Boolean(inspect.State?.Restarting),
|
|
39990
40046
|
dead: Boolean(inspect.State?.Dead),
|
|
39991
|
-
error: inspect.State?.Error || void 0
|
|
39992
|
-
|
|
40047
|
+
error: inspect.State?.Error || void 0,
|
|
40048
|
+
exitCode: typeof inspect.State?.ExitCode === "number" ? inspect.State.ExitCode : void 0,
|
|
40049
|
+
startedAt: inspect.State?.StartedAt || void 0,
|
|
40050
|
+
finishedAt: inspect.State?.FinishedAt || void 0
|
|
40051
|
+
},
|
|
40052
|
+
ports
|
|
39993
40053
|
};
|
|
39994
40054
|
} catch (error) {
|
|
39995
40055
|
const message = toErrorMessage(error);
|
|
@@ -39999,6 +40059,18 @@ var init_docker_client = __esm({
|
|
|
39999
40059
|
throw error;
|
|
40000
40060
|
}
|
|
40001
40061
|
}
|
|
40062
|
+
async getContainerLogs(containerId, hostConfig, options) {
|
|
40063
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
40064
|
+
const stream = await docker.getContainer(containerId).logs({
|
|
40065
|
+
stdout: true,
|
|
40066
|
+
stderr: true,
|
|
40067
|
+
tail: options?.tail ?? 100
|
|
40068
|
+
});
|
|
40069
|
+
if (Buffer.isBuffer(stream)) {
|
|
40070
|
+
return stream.toString("utf8");
|
|
40071
|
+
}
|
|
40072
|
+
return String(stream ?? "");
|
|
40073
|
+
}
|
|
40002
40074
|
async getInstance() {
|
|
40003
40075
|
if (!this.dockerInstance) {
|
|
40004
40076
|
this.dockerInstance = await this.createDockerInstance(this.defaultHostConfig);
|
|
@@ -52657,6 +52729,193 @@ var init_chat_store = __esm({
|
|
|
52657
52729
|
}
|
|
52658
52730
|
});
|
|
52659
52731
|
|
|
52732
|
+
// ../core/src/oauth-credential-interop.ts
|
|
52733
|
+
import { existsSync as existsSync19, readFileSync as readFileSync6 } from "node:fs";
|
|
52734
|
+
import { homedir as homedir4 } from "node:os";
|
|
52735
|
+
import { join as join22 } from "node:path";
|
|
52736
|
+
function getHomeDir4() {
|
|
52737
|
+
return process.env.HOME || process.env.USERPROFILE || homedir4();
|
|
52738
|
+
}
|
|
52739
|
+
function getCodexCliAuthPath(home = getHomeDir4()) {
|
|
52740
|
+
return join22(home, ".codex", "auth.json");
|
|
52741
|
+
}
|
|
52742
|
+
function parseJwtPayload(token) {
|
|
52743
|
+
try {
|
|
52744
|
+
const [, payload = ""] = token.split(".", 3);
|
|
52745
|
+
if (!payload) {
|
|
52746
|
+
return null;
|
|
52747
|
+
}
|
|
52748
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
52749
|
+
} catch {
|
|
52750
|
+
return null;
|
|
52751
|
+
}
|
|
52752
|
+
}
|
|
52753
|
+
function getJwtExpiryMs(token) {
|
|
52754
|
+
if (!token) {
|
|
52755
|
+
return void 0;
|
|
52756
|
+
}
|
|
52757
|
+
const payload = parseJwtPayload(token);
|
|
52758
|
+
const exp = payload?.exp;
|
|
52759
|
+
if (typeof exp !== "number" || !Number.isFinite(exp)) {
|
|
52760
|
+
return void 0;
|
|
52761
|
+
}
|
|
52762
|
+
return exp * 1e3;
|
|
52763
|
+
}
|
|
52764
|
+
function getCodexAccountId(accessToken, fallbackAccountId) {
|
|
52765
|
+
const payload = parseJwtPayload(accessToken);
|
|
52766
|
+
const authClaim = payload?.[OPENAI_AUTH_CLAIM];
|
|
52767
|
+
const claimAccountId = authClaim && typeof authClaim === "object" ? authClaim.chatgpt_account_id : void 0;
|
|
52768
|
+
if (typeof claimAccountId === "string" && claimAccountId.trim().length > 0) {
|
|
52769
|
+
return claimAccountId;
|
|
52770
|
+
}
|
|
52771
|
+
if (typeof fallbackAccountId === "string" && fallbackAccountId.trim().length > 0) {
|
|
52772
|
+
return fallbackAccountId;
|
|
52773
|
+
}
|
|
52774
|
+
return void 0;
|
|
52775
|
+
}
|
|
52776
|
+
function getLastRefreshFallbackExpiryMs(lastRefresh) {
|
|
52777
|
+
if (typeof lastRefresh !== "string" || lastRefresh.trim().length === 0) {
|
|
52778
|
+
return void 0;
|
|
52779
|
+
}
|
|
52780
|
+
const parsed = Date.parse(lastRefresh);
|
|
52781
|
+
if (!Number.isFinite(parsed)) {
|
|
52782
|
+
return void 0;
|
|
52783
|
+
}
|
|
52784
|
+
return parsed + CODEX_REFRESH_FALLBACK_WINDOW_MS;
|
|
52785
|
+
}
|
|
52786
|
+
function isStoredAuthCredential(value) {
|
|
52787
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
52788
|
+
return false;
|
|
52789
|
+
}
|
|
52790
|
+
const record = value;
|
|
52791
|
+
return record.type === "oauth" || record.type === "api_key";
|
|
52792
|
+
}
|
|
52793
|
+
function isValidOauthCredential(credential) {
|
|
52794
|
+
return credential?.type === "oauth" && typeof credential.access === "string" && credential.access.length > 0 && typeof credential.refresh === "string" && credential.refresh.length > 0 && typeof credential.expires === "number" && Number.isFinite(credential.expires) && Date.now() < credential.expires;
|
|
52795
|
+
}
|
|
52796
|
+
function isRefreshableOauthCredential(credential) {
|
|
52797
|
+
return credential?.type === "oauth" && typeof credential.refresh === "string" && credential.refresh.length > 0 && typeof credential.expires === "number" && Number.isFinite(credential.expires);
|
|
52798
|
+
}
|
|
52799
|
+
function compareStoredCredentials(left, right) {
|
|
52800
|
+
if (!left && !right) {
|
|
52801
|
+
return 0;
|
|
52802
|
+
}
|
|
52803
|
+
if (left && !right) {
|
|
52804
|
+
return 1;
|
|
52805
|
+
}
|
|
52806
|
+
if (!left && right) {
|
|
52807
|
+
return -1;
|
|
52808
|
+
}
|
|
52809
|
+
if (left?.type === "api_key" && right?.type !== "api_key") {
|
|
52810
|
+
return 1;
|
|
52811
|
+
}
|
|
52812
|
+
if (right?.type === "api_key" && left?.type !== "api_key") {
|
|
52813
|
+
return -1;
|
|
52814
|
+
}
|
|
52815
|
+
if (left?.type === "oauth" && right?.type === "oauth") {
|
|
52816
|
+
const leftValid = isValidOauthCredential(left);
|
|
52817
|
+
const rightValid = isValidOauthCredential(right);
|
|
52818
|
+
if (leftValid !== rightValid) {
|
|
52819
|
+
return leftValid ? 1 : -1;
|
|
52820
|
+
}
|
|
52821
|
+
const leftRefreshable = isRefreshableOauthCredential(left);
|
|
52822
|
+
const rightRefreshable = isRefreshableOauthCredential(right);
|
|
52823
|
+
if (leftRefreshable !== rightRefreshable) {
|
|
52824
|
+
return leftRefreshable ? 1 : -1;
|
|
52825
|
+
}
|
|
52826
|
+
const leftExpiry = typeof left.expires === "number" && Number.isFinite(left.expires) ? left.expires : -Infinity;
|
|
52827
|
+
const rightExpiry = typeof right.expires === "number" && Number.isFinite(right.expires) ? right.expires : -Infinity;
|
|
52828
|
+
if (leftExpiry !== rightExpiry) {
|
|
52829
|
+
return leftExpiry > rightExpiry ? 1 : -1;
|
|
52830
|
+
}
|
|
52831
|
+
const leftAccessLength = typeof left.access === "string" ? left.access.length : 0;
|
|
52832
|
+
const rightAccessLength = typeof right.access === "string" ? right.access.length : 0;
|
|
52833
|
+
if (leftAccessLength !== rightAccessLength) {
|
|
52834
|
+
return leftAccessLength > rightAccessLength ? 1 : -1;
|
|
52835
|
+
}
|
|
52836
|
+
}
|
|
52837
|
+
return 0;
|
|
52838
|
+
}
|
|
52839
|
+
function choosePreferredStoredCredential(...credentials) {
|
|
52840
|
+
let best;
|
|
52841
|
+
for (const credential of credentials) {
|
|
52842
|
+
if (compareStoredCredentials(credential, best) > 0) {
|
|
52843
|
+
best = credential;
|
|
52844
|
+
}
|
|
52845
|
+
}
|
|
52846
|
+
return best;
|
|
52847
|
+
}
|
|
52848
|
+
function shouldHydrateStoredCredential(current, candidate) {
|
|
52849
|
+
if (!candidate || candidate.type !== "oauth") {
|
|
52850
|
+
return false;
|
|
52851
|
+
}
|
|
52852
|
+
if (current?.type === "api_key") {
|
|
52853
|
+
return false;
|
|
52854
|
+
}
|
|
52855
|
+
return compareStoredCredentials(candidate, current) > 0;
|
|
52856
|
+
}
|
|
52857
|
+
function extractCodexCliStoredCredential(raw) {
|
|
52858
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
52859
|
+
return void 0;
|
|
52860
|
+
}
|
|
52861
|
+
const record = raw;
|
|
52862
|
+
const tokens = record.tokens;
|
|
52863
|
+
if (!tokens || typeof tokens !== "object" || Array.isArray(tokens)) {
|
|
52864
|
+
return void 0;
|
|
52865
|
+
}
|
|
52866
|
+
const tokenRecord = tokens;
|
|
52867
|
+
const access7 = typeof tokenRecord.access_token === "string" ? tokenRecord.access_token : void 0;
|
|
52868
|
+
const refresh = typeof tokenRecord.refresh_token === "string" ? tokenRecord.refresh_token : void 0;
|
|
52869
|
+
if (!access7 || !refresh) {
|
|
52870
|
+
return void 0;
|
|
52871
|
+
}
|
|
52872
|
+
const expires = getJwtExpiryMs(access7) ?? getJwtExpiryMs(typeof tokenRecord.id_token === "string" ? tokenRecord.id_token : void 0) ?? getLastRefreshFallbackExpiryMs(record.last_refresh);
|
|
52873
|
+
if (typeof expires !== "number" || !Number.isFinite(expires)) {
|
|
52874
|
+
return void 0;
|
|
52875
|
+
}
|
|
52876
|
+
const accountId = getCodexAccountId(access7, tokenRecord.account_id);
|
|
52877
|
+
return {
|
|
52878
|
+
type: "oauth",
|
|
52879
|
+
access: access7,
|
|
52880
|
+
refresh,
|
|
52881
|
+
expires,
|
|
52882
|
+
...accountId ? { accountId } : {}
|
|
52883
|
+
};
|
|
52884
|
+
}
|
|
52885
|
+
function readStoredCredentialsFromAuthFile(authPath) {
|
|
52886
|
+
if (!existsSync19(authPath)) {
|
|
52887
|
+
return {};
|
|
52888
|
+
}
|
|
52889
|
+
try {
|
|
52890
|
+
const parsed = JSON.parse(readFileSync6(authPath, "utf-8"));
|
|
52891
|
+
const codexCliCredential = extractCodexCliStoredCredential(parsed);
|
|
52892
|
+
if (codexCliCredential) {
|
|
52893
|
+
return { "openai-codex": codexCliCredential };
|
|
52894
|
+
}
|
|
52895
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
52896
|
+
return {};
|
|
52897
|
+
}
|
|
52898
|
+
const credentials = {};
|
|
52899
|
+
for (const [providerId, value] of Object.entries(parsed)) {
|
|
52900
|
+
if (!isStoredAuthCredential(value)) {
|
|
52901
|
+
continue;
|
|
52902
|
+
}
|
|
52903
|
+
credentials[providerId] = value;
|
|
52904
|
+
}
|
|
52905
|
+
return credentials;
|
|
52906
|
+
} catch {
|
|
52907
|
+
return {};
|
|
52908
|
+
}
|
|
52909
|
+
}
|
|
52910
|
+
var OPENAI_AUTH_CLAIM, CODEX_REFRESH_FALLBACK_WINDOW_MS;
|
|
52911
|
+
var init_oauth_credential_interop = __esm({
|
|
52912
|
+
"../core/src/oauth-credential-interop.ts"() {
|
|
52913
|
+
"use strict";
|
|
52914
|
+
OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
52915
|
+
CODEX_REFRESH_FALLBACK_WINDOW_MS = 55 * 60 * 1e3;
|
|
52916
|
+
}
|
|
52917
|
+
});
|
|
52918
|
+
|
|
52660
52919
|
// ../core/src/index.ts
|
|
52661
52920
|
var src_exports = {};
|
|
52662
52921
|
__export(src_exports, {
|
|
@@ -52813,6 +53072,7 @@ __export(src_exports, {
|
|
|
52813
53072
|
buildTriageMemoryInstructions: () => buildTriageMemoryInstructions,
|
|
52814
53073
|
canTransition: () => canTransition,
|
|
52815
53074
|
checkRateLimit: () => checkRateLimit,
|
|
53075
|
+
choosePreferredStoredCredential: () => choosePreferredStoredCredential,
|
|
52816
53076
|
classifyInsightRunError: () => classifyInsightRunError,
|
|
52817
53077
|
clearOverrides: () => clearOverrides,
|
|
52818
53078
|
collectSystemMetrics: () => collectSystemMetrics,
|
|
@@ -52843,6 +53103,7 @@ __export(src_exports, {
|
|
|
52843
53103
|
executeInsightRunLifecycle: () => executeInsightRunLifecycle,
|
|
52844
53104
|
exportAgentsToDirectory: () => exportAgentsToDirectory,
|
|
52845
53105
|
exportSettings: () => exportSettings,
|
|
53106
|
+
extractCodexCliStoredCredential: () => extractCodexCliStoredCredential,
|
|
52846
53107
|
extractDreamProcessorResult: () => extractDreamProcessorResult,
|
|
52847
53108
|
formatPiExtensionSource: () => formatPiExtensionSource,
|
|
52848
53109
|
fromJson: () => fromJson,
|
|
@@ -52853,6 +53114,7 @@ __export(src_exports, {
|
|
|
52853
53114
|
generateMemoryAudit: () => generateMemoryAudit,
|
|
52854
53115
|
getAppVersion: () => getAppVersion,
|
|
52855
53116
|
getAvailableTemplates: () => getAvailableTemplates,
|
|
53117
|
+
getCodexCliAuthPath: () => getCodexCliAuthPath,
|
|
52856
53118
|
getCurrentRepo: () => getCurrentRepo,
|
|
52857
53119
|
getDefaultDailyMemoryScaffold: () => getDefaultDailyMemoryScaffold,
|
|
52858
53120
|
getDefaultDreamsScaffold: () => getDefaultDreamsScaffold,
|
|
@@ -52950,6 +53212,7 @@ __export(src_exports, {
|
|
|
52950
53212
|
readProjectMemoryFile: () => readProjectMemoryFile,
|
|
52951
53213
|
readProjectMemoryFileContent: () => readProjectMemoryFileContent,
|
|
52952
53214
|
readProjectMemoryWithBackend: () => readProjectMemoryWithBackend,
|
|
53215
|
+
readStoredCredentialsFromAuthFile: () => readStoredCredentialsFromAuthFile,
|
|
52953
53216
|
readWorkingMemory: () => readWorkingMemory,
|
|
52954
53217
|
reconcileClaudeCliPaths: () => reconcileClaudeCliPaths,
|
|
52955
53218
|
reconcileDroidCliPaths: () => reconcileDroidCliPaths,
|
|
@@ -52985,6 +53248,7 @@ __export(src_exports, {
|
|
|
52985
53248
|
scheduleQmdProjectMemoryRefresh: () => scheduleQmdProjectMemoryRefresh,
|
|
52986
53249
|
searchProjectMemory: () => searchProjectMemory,
|
|
52987
53250
|
setCreateFnAgent: () => setCreateFnAgent,
|
|
53251
|
+
shouldHydrateStoredCredential: () => shouldHydrateStoredCredential,
|
|
52988
53252
|
shouldSkipBackgroundQmdRefresh: () => shouldSkipBackgroundQmdRefresh,
|
|
52989
53253
|
shouldTriggerExtraction: () => shouldTriggerExtraction,
|
|
52990
53254
|
slugify: () => slugify,
|
|
@@ -53089,6 +53353,7 @@ var init_src = __esm({
|
|
|
53089
53353
|
init_agent_companies_parser();
|
|
53090
53354
|
init_agent_companies_exporter();
|
|
53091
53355
|
init_chat_store();
|
|
53356
|
+
init_oauth_credential_interop();
|
|
53092
53357
|
init_error_message();
|
|
53093
53358
|
}
|
|
53094
53359
|
});
|
|
@@ -54278,12 +54543,12 @@ var init_github_provider = __esm({
|
|
|
54278
54543
|
});
|
|
54279
54544
|
|
|
54280
54545
|
// ../engine/src/skill-resolver.ts
|
|
54281
|
-
import { existsSync as
|
|
54282
|
-
import { dirname as dirname8, join as
|
|
54546
|
+
import { existsSync as existsSync20, readFileSync as readFileSync7 } from "node:fs";
|
|
54547
|
+
import { dirname as dirname8, join as join23, resolve as resolve11 } from "node:path";
|
|
54283
54548
|
function resolveProjectRoot(cwd) {
|
|
54284
54549
|
let current = resolve11(cwd);
|
|
54285
54550
|
while (true) {
|
|
54286
|
-
if (
|
|
54551
|
+
if (existsSync20(join23(current, ".fusion"))) {
|
|
54287
54552
|
return current;
|
|
54288
54553
|
}
|
|
54289
54554
|
const parent = dirname8(current);
|
|
@@ -54294,19 +54559,19 @@ function resolveProjectRoot(cwd) {
|
|
|
54294
54559
|
}
|
|
54295
54560
|
}
|
|
54296
54561
|
function readJsonObject(path2) {
|
|
54297
|
-
if (!
|
|
54562
|
+
if (!existsSync20(path2)) {
|
|
54298
54563
|
return {};
|
|
54299
54564
|
}
|
|
54300
54565
|
try {
|
|
54301
|
-
const parsed = JSON.parse(
|
|
54566
|
+
const parsed = JSON.parse(readFileSync7(path2, "utf-8"));
|
|
54302
54567
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
54303
54568
|
} catch {
|
|
54304
54569
|
return {};
|
|
54305
54570
|
}
|
|
54306
54571
|
}
|
|
54307
54572
|
function readProjectSettings(projectRootDir) {
|
|
54308
|
-
const fusionSettings =
|
|
54309
|
-
if (
|
|
54573
|
+
const fusionSettings = join23(projectRootDir, ".fusion", "settings.json");
|
|
54574
|
+
if (existsSync20(fusionSettings)) {
|
|
54310
54575
|
const parsed = readJsonObject(fusionSettings);
|
|
54311
54576
|
return {
|
|
54312
54577
|
skills: Array.isArray(parsed.skills) ? parsed.skills : void 0,
|
|
@@ -54324,6 +54589,9 @@ function normalizePattern(pattern) {
|
|
|
54324
54589
|
function isExclusionPattern(pattern) {
|
|
54325
54590
|
return pattern.startsWith("-");
|
|
54326
54591
|
}
|
|
54592
|
+
function bareSkillName(name) {
|
|
54593
|
+
return name.replace(/\/SKILL\.md$/i, "");
|
|
54594
|
+
}
|
|
54327
54595
|
function resolveSessionSkills(context) {
|
|
54328
54596
|
const { requestedSkillNames } = context;
|
|
54329
54597
|
const projectRootDir = resolveProjectRoot(context.projectRootDir);
|
|
@@ -54417,25 +54685,40 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54417
54685
|
const hasRequestedNames = Boolean(requestedSkillNames && requestedSkillNames.length > 0);
|
|
54418
54686
|
const hasExcluded = excludedSkillPaths.size > 0;
|
|
54419
54687
|
let filteredSkills;
|
|
54688
|
+
const skillNameMatches = (skill, pattern) => bareSkillName(skill.name).toLowerCase() === bareSkillName(pattern).toLowerCase() || skill.filePath === pattern;
|
|
54689
|
+
const isExcluded = (skill) => {
|
|
54690
|
+
for (const ep of excludedSkillPaths) {
|
|
54691
|
+
if (skillNameMatches(skill, ep)) return true;
|
|
54692
|
+
}
|
|
54693
|
+
return false;
|
|
54694
|
+
};
|
|
54695
|
+
const isAllowed = (skill) => {
|
|
54696
|
+
for (const ap of allowedSkillPaths) {
|
|
54697
|
+
if (skillNameMatches(skill, ap)) return true;
|
|
54698
|
+
}
|
|
54699
|
+
return false;
|
|
54700
|
+
};
|
|
54420
54701
|
if (hasRequestedNames) {
|
|
54421
|
-
const
|
|
54702
|
+
const requestedBareNamesLower = new Set(requestedSkillNames.map((n) => bareSkillName(n).toLowerCase()));
|
|
54422
54703
|
filteredSkills = base.skills.filter(
|
|
54423
|
-
(skill) =>
|
|
54704
|
+
(skill) => requestedBareNamesLower.has(bareSkillName(skill.name).toLowerCase()) && !isExcluded(skill)
|
|
54424
54705
|
);
|
|
54425
54706
|
} else if (hasPatterns) {
|
|
54426
54707
|
filteredSkills = base.skills.filter(
|
|
54427
|
-
(skill) =>
|
|
54708
|
+
(skill) => isAllowed(skill) && !isExcluded(skill)
|
|
54428
54709
|
);
|
|
54429
54710
|
} else if (hasExcluded) {
|
|
54430
|
-
filteredSkills = base.skills.filter((skill) => !
|
|
54711
|
+
filteredSkills = base.skills.filter((skill) => !isExcluded(skill));
|
|
54431
54712
|
} else {
|
|
54432
54713
|
filteredSkills = base.skills;
|
|
54433
54714
|
}
|
|
54434
54715
|
const newDiagnostics = [];
|
|
54435
54716
|
const purpose = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
54436
|
-
const
|
|
54717
|
+
const discoveredBareNames = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
54718
|
+
const discoveredFilePaths = new Set(base.skills.map((s) => s.filePath));
|
|
54719
|
+
const hasDiscoveredMatch = (pattern) => discoveredBareNames.has(bareSkillName(pattern).toLowerCase()) || discoveredFilePaths.has(pattern);
|
|
54437
54720
|
for (const excludedPath of excludedSkillPaths) {
|
|
54438
|
-
if (
|
|
54721
|
+
if (hasDiscoveredMatch(excludedPath)) {
|
|
54439
54722
|
newDiagnostics.push({
|
|
54440
54723
|
type: "warning",
|
|
54441
54724
|
message: `Skill at '${excludedPath}' exists but is disabled by project execution settings${purpose}`,
|
|
@@ -54444,7 +54727,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54444
54727
|
}
|
|
54445
54728
|
}
|
|
54446
54729
|
for (const allowedPath of allowedSkillPaths) {
|
|
54447
|
-
if (!
|
|
54730
|
+
if (!hasDiscoveredMatch(allowedPath)) {
|
|
54448
54731
|
newDiagnostics.push({
|
|
54449
54732
|
type: "warning",
|
|
54450
54733
|
message: `Configured skill pattern '${allowedPath}' not found in discovered skills${purpose}`,
|
|
@@ -54453,9 +54736,9 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54453
54736
|
}
|
|
54454
54737
|
}
|
|
54455
54738
|
if (requestedSkillNames) {
|
|
54456
|
-
const
|
|
54739
|
+
const discoveredBareNamesLower = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
54457
54740
|
for (const requestedName of requestedSkillNames) {
|
|
54458
|
-
if (!
|
|
54741
|
+
if (!discoveredBareNamesLower.has(bareSkillName(requestedName).toLowerCase()) && !isBuiltInFallbackRequest(requestedName)) {
|
|
54459
54742
|
const purpose2 = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
54460
54743
|
newDiagnostics.push({
|
|
54461
54744
|
type: "warning",
|
|
@@ -54531,51 +54814,51 @@ var init_context_limit_detector = __esm({
|
|
|
54531
54814
|
});
|
|
54532
54815
|
|
|
54533
54816
|
// ../engine/src/auth-storage.ts
|
|
54534
|
-
import { existsSync as
|
|
54535
|
-
import { homedir as
|
|
54536
|
-
import { join as
|
|
54817
|
+
import { existsSync as existsSync21, readFileSync as readFileSync8 } from "node:fs";
|
|
54818
|
+
import { homedir as homedir5 } from "node:os";
|
|
54819
|
+
import { join as join24 } from "node:path";
|
|
54537
54820
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
54538
54821
|
import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
54539
|
-
function
|
|
54540
|
-
return process.env.HOME || process.env.USERPROFILE ||
|
|
54822
|
+
function getHomeDir5() {
|
|
54823
|
+
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
54541
54824
|
}
|
|
54542
|
-
function getFusionAuthPath(home =
|
|
54543
|
-
return
|
|
54825
|
+
function getFusionAuthPath(home = getHomeDir5()) {
|
|
54826
|
+
return join24(home, ".fusion", "agent", "auth.json");
|
|
54544
54827
|
}
|
|
54545
|
-
function getFusionModelsPath(home =
|
|
54546
|
-
return
|
|
54828
|
+
function getFusionModelsPath(home = getHomeDir5()) {
|
|
54829
|
+
return join24(home, ".fusion", "agent", "models.json");
|
|
54830
|
+
}
|
|
54831
|
+
function getLegacyAuthPaths(home = getHomeDir5()) {
|
|
54832
|
+
return [
|
|
54833
|
+
join24(home, ".pi", "agent", "auth.json"),
|
|
54834
|
+
join24(home, ".pi", "auth.json")
|
|
54835
|
+
];
|
|
54547
54836
|
}
|
|
54548
|
-
function
|
|
54837
|
+
function getSupplementalAuthPaths(home = getHomeDir5()) {
|
|
54549
54838
|
return [
|
|
54550
|
-
|
|
54551
|
-
|
|
54839
|
+
...getLegacyAuthPaths(home),
|
|
54840
|
+
getCodexCliAuthPath(home)
|
|
54552
54841
|
];
|
|
54553
54842
|
}
|
|
54554
|
-
function getLegacyModelsPaths(home =
|
|
54843
|
+
function getLegacyModelsPaths(home = getHomeDir5()) {
|
|
54555
54844
|
return [
|
|
54556
|
-
|
|
54557
|
-
|
|
54845
|
+
join24(home, ".pi", "agent", "models.json"),
|
|
54846
|
+
join24(home, ".pi", "models.json")
|
|
54558
54847
|
];
|
|
54559
54848
|
}
|
|
54560
|
-
function getModelRegistryModelsPath(home =
|
|
54849
|
+
function getModelRegistryModelsPath(home = getHomeDir5()) {
|
|
54561
54850
|
const fusionModelsPath = getFusionModelsPath(home);
|
|
54562
|
-
if (
|
|
54851
|
+
if (existsSync21(fusionModelsPath)) {
|
|
54563
54852
|
return fusionModelsPath;
|
|
54564
54853
|
}
|
|
54565
|
-
return getLegacyModelsPaths(home).find((modelsPath) =>
|
|
54854
|
+
return getLegacyModelsPaths(home).find((modelsPath) => existsSync21(modelsPath)) ?? fusionModelsPath;
|
|
54566
54855
|
}
|
|
54567
|
-
function
|
|
54856
|
+
function readSupplementalCredentials(authPaths = getSupplementalAuthPaths()) {
|
|
54568
54857
|
const credentials = {};
|
|
54569
54858
|
for (const authPath of authPaths) {
|
|
54570
|
-
|
|
54571
|
-
|
|
54572
|
-
|
|
54573
|
-
try {
|
|
54574
|
-
const parsed = JSON.parse(readFileSync7(authPath, "utf-8"));
|
|
54575
|
-
for (const [provider, credential] of Object.entries(parsed)) {
|
|
54576
|
-
credentials[provider] ??= credential;
|
|
54577
|
-
}
|
|
54578
|
-
} catch {
|
|
54859
|
+
const parsed = readStoredCredentialsFromAuthFile(authPath);
|
|
54860
|
+
for (const [provider, credential] of Object.entries(parsed)) {
|
|
54861
|
+
credentials[provider] = choosePreferredStoredCredential(credentials[provider], credential) ?? credential;
|
|
54579
54862
|
}
|
|
54580
54863
|
}
|
|
54581
54864
|
return credentials;
|
|
@@ -54599,14 +54882,14 @@ function resolveStoredCredentialApiKey(providerId, credential) {
|
|
|
54599
54882
|
}
|
|
54600
54883
|
return void 0;
|
|
54601
54884
|
}
|
|
54602
|
-
function readModelsJsonApiKeys(home =
|
|
54885
|
+
function readModelsJsonApiKeys(home = getHomeDir5()) {
|
|
54603
54886
|
const apiKeys = /* @__PURE__ */ new Map();
|
|
54604
54887
|
const modelsPath = getModelRegistryModelsPath(home);
|
|
54605
|
-
if (!
|
|
54888
|
+
if (!existsSync21(modelsPath)) {
|
|
54606
54889
|
return apiKeys;
|
|
54607
54890
|
}
|
|
54608
54891
|
try {
|
|
54609
|
-
const parsed = JSON.parse(
|
|
54892
|
+
const parsed = JSON.parse(readFileSync8(modelsPath, "utf-8"));
|
|
54610
54893
|
const providers = parsed?.providers;
|
|
54611
54894
|
if (providers) {
|
|
54612
54895
|
for (const [providerId, config] of Object.entries(providers)) {
|
|
@@ -54621,8 +54904,20 @@ function readModelsJsonApiKeys(home = getHomeDir4()) {
|
|
|
54621
54904
|
}
|
|
54622
54905
|
function createFusionAuthStorage() {
|
|
54623
54906
|
const primary = AuthStorage.create(getFusionAuthPath());
|
|
54624
|
-
let
|
|
54907
|
+
let supplementalCredentials = readSupplementalCredentials();
|
|
54625
54908
|
let modelsJsonApiKeys = readModelsJsonApiKeys();
|
|
54909
|
+
const syncSupplementalOauthCredentials = () => {
|
|
54910
|
+
for (const [provider, credential] of Object.entries(supplementalCredentials)) {
|
|
54911
|
+
const current = primary.get(provider);
|
|
54912
|
+
if (!shouldHydrateStoredCredential(current, credential)) {
|
|
54913
|
+
continue;
|
|
54914
|
+
}
|
|
54915
|
+
if (credential.type === "oauth" || credential.type === "api_key") {
|
|
54916
|
+
primary.set(provider, credential);
|
|
54917
|
+
}
|
|
54918
|
+
}
|
|
54919
|
+
};
|
|
54920
|
+
syncSupplementalOauthCredentials();
|
|
54626
54921
|
return new Proxy(primary, {
|
|
54627
54922
|
// Forward property writes to the target so that methods like
|
|
54628
54923
|
// `setFallbackResolver` (called by ModelRegistry) correctly update the
|
|
@@ -54636,31 +54931,51 @@ function createFusionAuthStorage() {
|
|
|
54636
54931
|
if (prop === "reload") {
|
|
54637
54932
|
return () => {
|
|
54638
54933
|
target.reload();
|
|
54639
|
-
|
|
54934
|
+
supplementalCredentials = readSupplementalCredentials();
|
|
54935
|
+
syncSupplementalOauthCredentials();
|
|
54640
54936
|
modelsJsonApiKeys = readModelsJsonApiKeys();
|
|
54641
54937
|
};
|
|
54642
54938
|
}
|
|
54643
54939
|
if (prop === "get") {
|
|
54644
|
-
return (provider) =>
|
|
54940
|
+
return (provider) => choosePreferredStoredCredential(
|
|
54941
|
+
target.get(provider),
|
|
54942
|
+
supplementalCredentials[provider]
|
|
54943
|
+
);
|
|
54645
54944
|
}
|
|
54646
54945
|
if (prop === "has") {
|
|
54647
|
-
return (provider) => target.has(provider) || provider in
|
|
54946
|
+
return (provider) => target.has(provider) || provider in supplementalCredentials || modelsJsonApiKeys.has(provider);
|
|
54648
54947
|
}
|
|
54649
54948
|
if (prop === "hasAuth") {
|
|
54650
|
-
return (provider) => target.hasAuth(provider) || Boolean(
|
|
54949
|
+
return (provider) => target.hasAuth(provider) || Boolean(supplementalCredentials[provider]) || modelsJsonApiKeys.has(provider);
|
|
54651
54950
|
}
|
|
54652
54951
|
if (prop === "getAll") {
|
|
54653
|
-
return () =>
|
|
54952
|
+
return () => {
|
|
54953
|
+
const providerIds = /* @__PURE__ */ new Set([
|
|
54954
|
+
...Object.keys(supplementalCredentials),
|
|
54955
|
+
...Object.keys(target.getAll())
|
|
54956
|
+
]);
|
|
54957
|
+
const merged = {};
|
|
54958
|
+
for (const providerId of providerIds) {
|
|
54959
|
+
const credential = choosePreferredStoredCredential(
|
|
54960
|
+
target.get(providerId),
|
|
54961
|
+
supplementalCredentials[providerId]
|
|
54962
|
+
);
|
|
54963
|
+
if (credential) {
|
|
54964
|
+
merged[providerId] = credential;
|
|
54965
|
+
}
|
|
54966
|
+
}
|
|
54967
|
+
return merged;
|
|
54968
|
+
};
|
|
54654
54969
|
}
|
|
54655
54970
|
if (prop === "list") {
|
|
54656
|
-
return () => Array.from(/* @__PURE__ */ new Set([...Object.keys(
|
|
54971
|
+
return () => Array.from(/* @__PURE__ */ new Set([...Object.keys(supplementalCredentials), ...target.list(), ...modelsJsonApiKeys.keys()]));
|
|
54657
54972
|
}
|
|
54658
54973
|
if (prop === "getApiKey") {
|
|
54659
54974
|
return async (provider) => {
|
|
54660
54975
|
const primaryKey = await target.getApiKey(provider);
|
|
54661
54976
|
if (primaryKey) return primaryKey;
|
|
54662
|
-
const
|
|
54663
|
-
if (
|
|
54977
|
+
const supplementalKey = resolveStoredCredentialApiKey(provider, supplementalCredentials[provider]);
|
|
54978
|
+
if (supplementalKey) return supplementalKey;
|
|
54664
54979
|
return modelsJsonApiKeys.get(provider);
|
|
54665
54980
|
};
|
|
54666
54981
|
}
|
|
@@ -54671,17 +54986,18 @@ function createFusionAuthStorage() {
|
|
|
54671
54986
|
var init_auth_storage = __esm({
|
|
54672
54987
|
"../engine/src/auth-storage.ts"() {
|
|
54673
54988
|
"use strict";
|
|
54989
|
+
init_src();
|
|
54674
54990
|
}
|
|
54675
54991
|
});
|
|
54676
54992
|
|
|
54677
54993
|
// ../engine/src/custom-providers.ts
|
|
54678
|
-
import { readFileSync as
|
|
54679
|
-
import { homedir as
|
|
54680
|
-
import { join as
|
|
54994
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
54995
|
+
import { homedir as homedir6 } from "node:os";
|
|
54996
|
+
import { join as join25 } from "node:path";
|
|
54681
54997
|
function readCustomProviders() {
|
|
54682
54998
|
try {
|
|
54683
|
-
const settingsPath =
|
|
54684
|
-
const raw =
|
|
54999
|
+
const settingsPath = join25(homedir6(), ".fusion", "settings.json");
|
|
55000
|
+
const raw = readFileSync9(settingsPath, "utf-8");
|
|
54685
55001
|
const parsed = JSON.parse(raw);
|
|
54686
55002
|
return Array.isArray(parsed.customProviders) ? parsed.customProviders : [];
|
|
54687
55003
|
} catch {
|
|
@@ -54695,11 +55011,11 @@ var init_custom_providers = __esm({
|
|
|
54695
55011
|
});
|
|
54696
55012
|
|
|
54697
55013
|
// ../engine/src/pi.ts
|
|
54698
|
-
import { existsSync as
|
|
55014
|
+
import { existsSync as existsSync22, readFileSync as readFileSync10 } from "node:fs";
|
|
54699
55015
|
import { exec as exec2 } from "node:child_process";
|
|
54700
55016
|
import { promisify as promisify3 } from "node:util";
|
|
54701
55017
|
import { createRequire as createRequire2 } from "node:module";
|
|
54702
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
55018
|
+
import { basename as basename7, dirname as dirname9, join as join26, relative as relative3, isAbsolute as isAbsolute7, resolve as resolve12 } from "node:path";
|
|
54703
55019
|
import {
|
|
54704
55020
|
createAgentSession,
|
|
54705
55021
|
createBashTool,
|
|
@@ -54776,6 +55092,11 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
54776
55092
|
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
54777
55093
|
}
|
|
54778
55094
|
}
|
|
55095
|
+
if (/Provider finish_reason:\s*repeat\b/i.test(stateError)) {
|
|
55096
|
+
piLog.warn(`pi state error \u2014 treating provider finish_reason=repeat as soft stop: ${stateError}`);
|
|
55097
|
+
clearSessionStateError(session);
|
|
55098
|
+
return;
|
|
55099
|
+
}
|
|
54779
55100
|
throw new Error(stateError);
|
|
54780
55101
|
}
|
|
54781
55102
|
}
|
|
@@ -54953,11 +55274,11 @@ function isRetryableModelSelectionError(message) {
|
|
|
54953
55274
|
return normalized.includes("rate limit") || normalized.includes("too many requests") || normalized.includes("429") || normalized.includes("401") || normalized.includes("403") || normalized.includes("unauthorized") || normalized.includes("forbidden") || normalized.includes("authentication") || normalized.includes("invalid api key") || normalized.includes("invalid key") || normalized.includes("api key") || normalized.includes("overloaded") || normalized.includes("quota") || normalized.includes("capacity") || normalized.includes("temporarily unavailable") || normalized.includes("invalid temperature");
|
|
54954
55275
|
}
|
|
54955
55276
|
function readJsonObject2(path2) {
|
|
54956
|
-
if (!
|
|
55277
|
+
if (!existsSync22(path2)) {
|
|
54957
55278
|
return {};
|
|
54958
55279
|
}
|
|
54959
55280
|
try {
|
|
54960
|
-
const parsed = JSON.parse(
|
|
55281
|
+
const parsed = JSON.parse(readFileSync10(path2, "utf-8"));
|
|
54961
55282
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
54962
55283
|
} catch {
|
|
54963
55284
|
return {};
|
|
@@ -55094,17 +55415,17 @@ function siblingAgentDir(agentDir, siblingRoot) {
|
|
|
55094
55415
|
if (basename7(agentDir) !== "agent") {
|
|
55095
55416
|
return void 0;
|
|
55096
55417
|
}
|
|
55097
|
-
return
|
|
55418
|
+
return join26(dirname9(dirname9(agentDir)), siblingRoot, "agent");
|
|
55098
55419
|
}
|
|
55099
55420
|
function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
55100
55421
|
const projectRoot = resolvePiExtensionProjectRoot(cwd);
|
|
55101
|
-
const fusionAgentDir = agentDir.includes(`${
|
|
55102
|
-
const legacyAgentDir = agentDir.includes(`${
|
|
55103
|
-
const legacyGlobalSettings = legacyAgentDir ? readJsonObject2(
|
|
55104
|
-
const fusionGlobalSettings = fusionAgentDir ? readJsonObject2(
|
|
55105
|
-
const directGlobalSettings = readJsonObject2(
|
|
55422
|
+
const fusionAgentDir = agentDir.includes(`${join26(".fusion", "agent")}`) ? agentDir : siblingAgentDir(agentDir, ".fusion");
|
|
55423
|
+
const legacyAgentDir = agentDir.includes(`${join26(".pi", "agent")}`) ? agentDir : siblingAgentDir(agentDir, ".pi");
|
|
55424
|
+
const legacyGlobalSettings = legacyAgentDir ? readJsonObject2(join26(legacyAgentDir, "settings.json")) : {};
|
|
55425
|
+
const fusionGlobalSettings = fusionAgentDir ? readJsonObject2(join26(fusionAgentDir, "settings.json")) : {};
|
|
55426
|
+
const directGlobalSettings = readJsonObject2(join26(agentDir, "settings.json"));
|
|
55106
55427
|
const globalSettings = { ...legacyGlobalSettings, ...directGlobalSettings, ...fusionGlobalSettings };
|
|
55107
|
-
const fusionProjectSettings = readJsonObject2(
|
|
55428
|
+
const fusionProjectSettings = readJsonObject2(join26(projectRoot, ".fusion", "settings.json"));
|
|
55108
55429
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
55109
55430
|
return {
|
|
55110
55431
|
getGlobalSettings: () => structuredClone(globalSettings),
|
|
@@ -55115,27 +55436,27 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
|
55115
55436
|
function getPackageManagerAgentDir() {
|
|
55116
55437
|
const fusionAgentDir = getFusionAgentDir();
|
|
55117
55438
|
const legacyAgentDir = getLegacyPiAgentDir();
|
|
55118
|
-
const fusionSettings = readJsonObject2(
|
|
55119
|
-
const legacySettings = readJsonObject2(
|
|
55120
|
-
if (hasPackageManagerSettings(fusionSettings) || !
|
|
55439
|
+
const fusionSettings = readJsonObject2(join26(fusionAgentDir, "settings.json"));
|
|
55440
|
+
const legacySettings = readJsonObject2(join26(legacyAgentDir, "settings.json"));
|
|
55441
|
+
if (hasPackageManagerSettings(fusionSettings) || !existsSync22(legacyAgentDir)) {
|
|
55121
55442
|
return fusionAgentDir;
|
|
55122
55443
|
}
|
|
55123
55444
|
if (hasPackageManagerSettings(legacySettings)) {
|
|
55124
55445
|
return legacyAgentDir;
|
|
55125
55446
|
}
|
|
55126
|
-
return
|
|
55447
|
+
return existsSync22(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
|
|
55127
55448
|
}
|
|
55128
55449
|
function resolveVendoredClaudeCliEntry() {
|
|
55129
55450
|
try {
|
|
55130
55451
|
const require_ = createRequire2(import.meta.url);
|
|
55131
55452
|
const pkgJsonPath = require_.resolve("@fusion/pi-claude-cli/package.json");
|
|
55132
|
-
const pkgJson = JSON.parse(
|
|
55453
|
+
const pkgJson = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
|
|
55133
55454
|
const extensions = pkgJson.pi?.extensions;
|
|
55134
55455
|
if (!Array.isArray(extensions) || extensions.length === 0) return null;
|
|
55135
55456
|
const entry = extensions[0];
|
|
55136
55457
|
if (typeof entry !== "string" || entry.length === 0) return null;
|
|
55137
55458
|
const path2 = resolve12(dirname9(pkgJsonPath), entry);
|
|
55138
|
-
return
|
|
55459
|
+
return existsSync22(path2) ? path2 : null;
|
|
55139
55460
|
} catch {
|
|
55140
55461
|
return null;
|
|
55141
55462
|
}
|
|
@@ -55144,13 +55465,13 @@ function resolveVendoredDroidCliEntry() {
|
|
|
55144
55465
|
try {
|
|
55145
55466
|
const require_ = createRequire2(import.meta.url);
|
|
55146
55467
|
const pkgJsonPath = require_.resolve("@fusion/droid-cli/package.json");
|
|
55147
|
-
const pkgJson = JSON.parse(
|
|
55468
|
+
const pkgJson = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
|
|
55148
55469
|
const extensions = pkgJson.pi?.extensions;
|
|
55149
55470
|
if (!Array.isArray(extensions) || extensions.length === 0) return null;
|
|
55150
55471
|
const entry = extensions[0];
|
|
55151
55472
|
if (typeof entry !== "string" || entry.length === 0) return null;
|
|
55152
55473
|
const path2 = resolve12(dirname9(pkgJsonPath), entry);
|
|
55153
|
-
return
|
|
55474
|
+
return existsSync22(path2) ? path2 : null;
|
|
55154
55475
|
} catch {
|
|
55155
55476
|
return null;
|
|
55156
55477
|
}
|
|
@@ -55178,7 +55499,7 @@ async function registerExtensionProviders(cwd, modelRegistry) {
|
|
|
55178
55499
|
const extensionsResult = await discoverAndLoadExtensions(
|
|
55179
55500
|
doubleReconciledPaths,
|
|
55180
55501
|
cwd,
|
|
55181
|
-
|
|
55502
|
+
join26(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery")
|
|
55182
55503
|
);
|
|
55183
55504
|
for (const { path: path2, error } of extensionsResult.errors) {
|
|
55184
55505
|
extensionsLog.warn(`Failed to load ${path2}: ${error}`);
|
|
@@ -55233,10 +55554,10 @@ async function isCompleteGitWorktree(worktreePath) {
|
|
|
55233
55554
|
}
|
|
55234
55555
|
}
|
|
55235
55556
|
async function assertValidWorktreeSession(cwd, projectRoot) {
|
|
55236
|
-
if (!
|
|
55557
|
+
if (!existsSync22(cwd)) {
|
|
55237
55558
|
throw new Error(`Refusing to start coding agent in missing worktree: ${cwd}`);
|
|
55238
55559
|
}
|
|
55239
|
-
if (!
|
|
55560
|
+
if (!existsSync22(join26(cwd, ".git")) || !await isCompleteGitWorktree(cwd)) {
|
|
55240
55561
|
throw new Error(`Refusing to start coding agent in incomplete worktree: ${cwd}`);
|
|
55241
55562
|
}
|
|
55242
55563
|
if (!await isRegisteredGitWorktree(projectRoot, cwd)) {
|
|
@@ -55817,7 +56138,7 @@ ${source.content ?? ""}`;
|
|
|
55817
56138
|
|
|
55818
56139
|
// ../engine/src/research/providers/local-docs-provider.ts
|
|
55819
56140
|
import { promises as fs } from "node:fs";
|
|
55820
|
-
import { extname as extname2, join as
|
|
56141
|
+
import { extname as extname2, join as join27, relative as relative4, resolve as resolve13 } from "node:path";
|
|
55821
56142
|
function buildExcerpt(content, terms) {
|
|
55822
56143
|
const lower = content.toLowerCase();
|
|
55823
56144
|
const first = terms.find((term) => lower.includes(term));
|
|
@@ -55946,7 +56267,7 @@ var init_local_docs_provider = __esm({
|
|
|
55946
56267
|
const rootEntries = await fs.readdir(this.projectRoot, { withFileTypes: true });
|
|
55947
56268
|
for (const entry of rootEntries) {
|
|
55948
56269
|
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
55949
|
-
files.push(
|
|
56270
|
+
files.push(join27(this.projectRoot, entry.name));
|
|
55950
56271
|
}
|
|
55951
56272
|
}
|
|
55952
56273
|
return [...new Set(files)];
|
|
@@ -55956,7 +56277,7 @@ var init_local_docs_provider = __esm({
|
|
|
55956
56277
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
55957
56278
|
for (const entry of entries) {
|
|
55958
56279
|
this.throwIfAborted(signal);
|
|
55959
|
-
const fullPath =
|
|
56280
|
+
const fullPath = join27(dir, entry.name);
|
|
55960
56281
|
const relPath = relative4(this.projectRoot, fullPath).replace(/\\/g, "/");
|
|
55961
56282
|
if (matchesGitignore(relPath, ignorePatterns)) continue;
|
|
55962
56283
|
if (entry.isDirectory()) {
|
|
@@ -55981,7 +56302,7 @@ var init_local_docs_provider = __esm({
|
|
|
55981
56302
|
}
|
|
55982
56303
|
async readGitignore() {
|
|
55983
56304
|
try {
|
|
55984
|
-
const content = await fs.readFile(
|
|
56305
|
+
const content = await fs.readFile(join27(this.projectRoot, ".gitignore"), "utf-8");
|
|
55985
56306
|
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
55986
56307
|
} catch {
|
|
55987
56308
|
return [];
|
|
@@ -56591,9 +56912,9 @@ var init_research_step_runner = __esm({
|
|
|
56591
56912
|
|
|
56592
56913
|
// ../engine/src/agent-tools.ts
|
|
56593
56914
|
import { appendFile as appendFile3, mkdir as mkdir11, readFile as readFile12, readdir as readdir7, stat as stat4, writeFile as writeFile10 } from "node:fs/promises";
|
|
56594
|
-
import { existsSync as
|
|
56915
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
56595
56916
|
import { createHash as createHash4 } from "node:crypto";
|
|
56596
|
-
import { join as
|
|
56917
|
+
import { join as join28 } from "node:path";
|
|
56597
56918
|
import { Type } from "@mariozechner/pi-ai";
|
|
56598
56919
|
function sanitizeAgentMemoryId(agentId) {
|
|
56599
56920
|
return agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
@@ -56605,16 +56926,16 @@ function agentDreamsDisplayPath(agentId) {
|
|
|
56605
56926
|
return `${AGENT_MEMORY_ROOT2}/${sanitizeAgentMemoryId(agentId)}/${AGENT_DREAMS_FILENAME2}`;
|
|
56606
56927
|
}
|
|
56607
56928
|
function agentMemoryDirectory(rootDir, agentId) {
|
|
56608
|
-
return
|
|
56929
|
+
return join28(rootDir, AGENT_MEMORY_ROOT2, sanitizeAgentMemoryId(agentId));
|
|
56609
56930
|
}
|
|
56610
56931
|
function agentMemoryFilePath(rootDir, agentId) {
|
|
56611
|
-
return
|
|
56932
|
+
return join28(agentMemoryDirectory(rootDir, agentId), AGENT_MEMORY_FILENAME2);
|
|
56612
56933
|
}
|
|
56613
56934
|
function agentDreamsFilePath(rootDir, agentId) {
|
|
56614
|
-
return
|
|
56935
|
+
return join28(agentMemoryDirectory(rootDir, agentId), AGENT_DREAMS_FILENAME2);
|
|
56615
56936
|
}
|
|
56616
56937
|
function agentDailyFilePath(rootDir, agentId, date = /* @__PURE__ */ new Date()) {
|
|
56617
|
-
return
|
|
56938
|
+
return join28(agentMemoryDirectory(rootDir, agentId), `${date.toISOString().slice(0, 10)}.md`);
|
|
56618
56939
|
}
|
|
56619
56940
|
function qmdAgentMemoryCollectionName(rootDir, agentId) {
|
|
56620
56941
|
const hash = createHash4("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
|
|
@@ -56650,7 +56971,7 @@ async function syncAgentMemoryFile(rootDir, agentMemory) {
|
|
|
56650
56971
|
const dir = agentMemoryDirectory(rootDir, agentMemory.agentId);
|
|
56651
56972
|
await mkdir11(dir, { recursive: true });
|
|
56652
56973
|
const longTermPath = agentMemoryFilePath(rootDir, agentMemory.agentId);
|
|
56653
|
-
if (!
|
|
56974
|
+
if (!existsSync23(longTermPath)) {
|
|
56654
56975
|
const title = agentMemory.agentName?.trim() ? `# Agent Memory: ${agentMemory.agentName.trim()}` : "# Agent Memory";
|
|
56655
56976
|
const fileContent = `${title}
|
|
56656
56977
|
|
|
@@ -56661,11 +56982,11 @@ ${content || ""}
|
|
|
56661
56982
|
await writeFile10(longTermPath, fileContent, "utf-8");
|
|
56662
56983
|
}
|
|
56663
56984
|
const dreamsPath = agentDreamsFilePath(rootDir, agentMemory.agentId);
|
|
56664
|
-
if (!
|
|
56985
|
+
if (!existsSync23(dreamsPath)) {
|
|
56665
56986
|
await writeFile10(dreamsPath, "# Agent Memory Dreams\n\n<!-- Synthesized patterns from this agent's daily notes. -->\n", "utf-8");
|
|
56666
56987
|
}
|
|
56667
56988
|
const dailyPath = agentDailyFilePath(rootDir, agentMemory.agentId);
|
|
56668
|
-
if (!
|
|
56989
|
+
if (!existsSync23(dailyPath)) {
|
|
56669
56990
|
await writeFile10(dailyPath, `# Agent Daily Memory ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
|
|
56670
56991
|
|
|
56671
56992
|
<!-- Running observations for this agent. -->
|
|
@@ -56689,7 +57010,7 @@ async function listAgentMemoryFiles2(rootDir, agentMemory) {
|
|
|
56689
57010
|
}
|
|
56690
57011
|
for (const entry of entries) {
|
|
56691
57012
|
if (!DAILY_AGENT_MEMORY_RE2.test(entry)) continue;
|
|
56692
|
-
const absPath =
|
|
57013
|
+
const absPath = join28(dir, entry);
|
|
56693
57014
|
const fileStat = await stat4(absPath);
|
|
56694
57015
|
if (fileStat.isFile()) {
|
|
56695
57016
|
files.push({
|
|
@@ -56851,7 +57172,7 @@ function resolveAgentMemoryPath(rootDir, agentId, path2) {
|
|
|
56851
57172
|
return null;
|
|
56852
57173
|
}
|
|
56853
57174
|
return {
|
|
56854
|
-
absPath:
|
|
57175
|
+
absPath: join28(agentMemoryDirectory(rootDir, agentId), filename),
|
|
56855
57176
|
displayPath: `${prefix}${filename}`
|
|
56856
57177
|
};
|
|
56857
57178
|
}
|
|
@@ -57957,9 +58278,18 @@ function normalizeAgentSkills(metadataSkills) {
|
|
|
57957
58278
|
name = namedEntry.trim();
|
|
57958
58279
|
}
|
|
57959
58280
|
}
|
|
57960
|
-
if (name && name.length > 0
|
|
57961
|
-
|
|
57962
|
-
|
|
58281
|
+
if (name && name.length > 0) {
|
|
58282
|
+
if (name.includes("::")) {
|
|
58283
|
+
const idPath = name.split("::").pop();
|
|
58284
|
+
const parts = idPath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
58285
|
+
if (parts.length >= 2) {
|
|
58286
|
+
name = parts.slice(-2).join("/");
|
|
58287
|
+
}
|
|
58288
|
+
}
|
|
58289
|
+
if (!seen.has(name)) {
|
|
58290
|
+
seen.add(name);
|
|
58291
|
+
result.push(name);
|
|
58292
|
+
}
|
|
57963
58293
|
}
|
|
57964
58294
|
}
|
|
57965
58295
|
return result;
|
|
@@ -59148,6 +59478,38 @@ var init_notifier = __esm({
|
|
|
59148
59478
|
}
|
|
59149
59479
|
});
|
|
59150
59480
|
|
|
59481
|
+
// ../engine/src/fallback-model-observer.ts
|
|
59482
|
+
function buildFallbackLogMessage(label, payload) {
|
|
59483
|
+
return `[fallback] ${label} switched from ${payload.primaryModel} to ${payload.fallbackModel} (${payload.triggerPoint})`;
|
|
59484
|
+
}
|
|
59485
|
+
function createFallbackModelObserver(options) {
|
|
59486
|
+
return async (payload) => {
|
|
59487
|
+
const taskId = options.taskId ?? payload.taskId;
|
|
59488
|
+
const taskTitle = options.taskTitle ?? payload.taskTitle;
|
|
59489
|
+
const message = buildFallbackLogMessage(options.label, payload);
|
|
59490
|
+
if (taskId && options.store?.logEntry) {
|
|
59491
|
+
await options.store.logEntry(taskId, message).catch(() => void 0);
|
|
59492
|
+
}
|
|
59493
|
+
if (taskId && options.store?.appendAgentLog) {
|
|
59494
|
+
await options.store.appendAgentLog(taskId, message, "text", void 0, options.agent).catch(() => void 0);
|
|
59495
|
+
}
|
|
59496
|
+
await notifyFallbackUsed({
|
|
59497
|
+
primaryModel: payload.primaryModel,
|
|
59498
|
+
fallbackModel: payload.fallbackModel,
|
|
59499
|
+
triggerPoint: payload.triggerPoint,
|
|
59500
|
+
taskId,
|
|
59501
|
+
taskTitle,
|
|
59502
|
+
timestamp: payload.timestamp
|
|
59503
|
+
});
|
|
59504
|
+
};
|
|
59505
|
+
}
|
|
59506
|
+
var init_fallback_model_observer = __esm({
|
|
59507
|
+
"../engine/src/fallback-model-observer.ts"() {
|
|
59508
|
+
"use strict";
|
|
59509
|
+
init_notifier();
|
|
59510
|
+
}
|
|
59511
|
+
});
|
|
59512
|
+
|
|
59151
59513
|
// ../engine/src/reviewer.ts
|
|
59152
59514
|
async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptContent, baseline, options = {}) {
|
|
59153
59515
|
let liveSettings = options.settings;
|
|
@@ -59278,7 +59640,13 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
59278
59640
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
59279
59641
|
taskId: options.taskId,
|
|
59280
59642
|
taskTitle: options.taskTitle,
|
|
59281
|
-
onFallbackModelUsed:
|
|
59643
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
59644
|
+
agent: "reviewer",
|
|
59645
|
+
label: "reviewer",
|
|
59646
|
+
store: options.store,
|
|
59647
|
+
taskId: options.taskId,
|
|
59648
|
+
taskTitle: options.taskTitle
|
|
59649
|
+
}),
|
|
59282
59650
|
beforeSpawnSession: async () => {
|
|
59283
59651
|
if (!options.store) return;
|
|
59284
59652
|
let finalSettings;
|
|
@@ -59465,7 +59833,7 @@ var init_reviewer = __esm({
|
|
|
59465
59833
|
init_logger2();
|
|
59466
59834
|
init_usage_limit_detector();
|
|
59467
59835
|
init_agent_instructions();
|
|
59468
|
-
|
|
59836
|
+
init_fallback_model_observer();
|
|
59469
59837
|
init_agent_tools();
|
|
59470
59838
|
REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
|
59471
59839
|
|
|
@@ -59826,7 +60194,7 @@ var init_recovery_policy = __esm({
|
|
|
59826
60194
|
// ../engine/src/triage.ts
|
|
59827
60195
|
import { Type as Type2 } from "@mariozechner/pi-ai";
|
|
59828
60196
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
59829
|
-
import { join as
|
|
60197
|
+
import { join as join29 } from "node:path";
|
|
59830
60198
|
function extractPromptDeclaredTitle(prompt, taskId) {
|
|
59831
60199
|
const headingMatch = prompt.match(/^#\s+Task:\s+([A-Z]+-\d+)\s+-\s+(.+)$/m);
|
|
59832
60200
|
if (!headingMatch) return null;
|
|
@@ -59855,9 +60223,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
59855
60223
|
return { attachmentContents, imageContents };
|
|
59856
60224
|
}
|
|
59857
60225
|
const { readFile: readFile20 } = await import("node:fs/promises");
|
|
59858
|
-
const { join:
|
|
60226
|
+
const { join: join43 } = await import("node:path");
|
|
59859
60227
|
for (const att of attachments) {
|
|
59860
|
-
const filePath =
|
|
60228
|
+
const filePath = join43(
|
|
59861
60229
|
rootDir,
|
|
59862
60230
|
".fusion",
|
|
59863
60231
|
"tasks",
|
|
@@ -60082,7 +60450,7 @@ var init_triage = __esm({
|
|
|
60082
60450
|
init_concurrency();
|
|
60083
60451
|
init_agent_logger();
|
|
60084
60452
|
init_agent_instructions();
|
|
60085
|
-
|
|
60453
|
+
init_fallback_model_observer();
|
|
60086
60454
|
init_logger2();
|
|
60087
60455
|
init_usage_limit_detector();
|
|
60088
60456
|
init_transient_error_detector();
|
|
@@ -60692,7 +61060,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
60692
61060
|
return false;
|
|
60693
61061
|
}
|
|
60694
61062
|
const settings = await this.store.getSettings();
|
|
60695
|
-
const promptPath =
|
|
61063
|
+
const promptPath = join29(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
60696
61064
|
const written = await readFile14(promptPath, "utf-8").catch((err) => {
|
|
60697
61065
|
const msg = err instanceof Error ? err.message : String(err);
|
|
60698
61066
|
planLog.warn(`${task.id}: failed to read PROMPT.md during approved-spec recovery (${promptPath}): ${msg}`);
|
|
@@ -60922,7 +61290,13 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
60922
61290
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
60923
61291
|
taskId: task.id,
|
|
60924
61292
|
taskTitle: task.title,
|
|
60925
|
-
onFallbackModelUsed:
|
|
61293
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
61294
|
+
agent: "triage",
|
|
61295
|
+
label: "triage",
|
|
61296
|
+
store: this.store,
|
|
61297
|
+
taskId: task.id,
|
|
61298
|
+
taskTitle: task.title
|
|
61299
|
+
})
|
|
60926
61300
|
});
|
|
60927
61301
|
const modelDesc = describeModel(session);
|
|
60928
61302
|
planLog.log(`${task.id}: using model ${modelDesc}`);
|
|
@@ -61065,7 +61439,13 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
61065
61439
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
61066
61440
|
taskId: task.id,
|
|
61067
61441
|
taskTitle: task.title,
|
|
61068
|
-
onFallbackModelUsed:
|
|
61442
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
61443
|
+
agent: "triage",
|
|
61444
|
+
label: "triage",
|
|
61445
|
+
store: this.store,
|
|
61446
|
+
taskId: task.id,
|
|
61447
|
+
taskTitle: task.title
|
|
61448
|
+
})
|
|
61069
61449
|
});
|
|
61070
61450
|
session = fallbackResult.session;
|
|
61071
61451
|
const fallbackModelDesc = describeModel(session);
|
|
@@ -61150,7 +61530,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
61150
61530
|
return;
|
|
61151
61531
|
}
|
|
61152
61532
|
const written = await readFile14(
|
|
61153
|
-
|
|
61533
|
+
join29(this.rootDir, promptPath),
|
|
61154
61534
|
"utf-8"
|
|
61155
61535
|
).catch((err) => {
|
|
61156
61536
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61469,9 +61849,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
61469
61849
|
}
|
|
61470
61850
|
try {
|
|
61471
61851
|
const { readFile: readFile20 } = await import("node:fs/promises");
|
|
61472
|
-
const { join:
|
|
61852
|
+
const { join: join43 } = await import("node:path");
|
|
61473
61853
|
const promptContent = await readFile20(
|
|
61474
|
-
|
|
61854
|
+
join43(rootDir, promptPath),
|
|
61475
61855
|
"utf-8"
|
|
61476
61856
|
).catch((err) => {
|
|
61477
61857
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61699,160 +62079,8 @@ Take a completely different approach to writing this specification. Do NOT repea
|
|
|
61699
62079
|
}
|
|
61700
62080
|
});
|
|
61701
62081
|
|
|
61702
|
-
// ../engine/src/
|
|
61703
|
-
|
|
61704
|
-
__export(session_token_usage_exports, {
|
|
61705
|
-
accumulateSessionTokenUsage: () => accumulateSessionTokenUsage
|
|
61706
|
-
});
|
|
61707
|
-
function readSessionStats(session) {
|
|
61708
|
-
const accessor = session.getSessionStats;
|
|
61709
|
-
if (typeof accessor !== "function") return void 0;
|
|
61710
|
-
try {
|
|
61711
|
-
return accessor.call(session);
|
|
61712
|
-
} catch {
|
|
61713
|
-
return void 0;
|
|
61714
|
-
}
|
|
61715
|
-
}
|
|
61716
|
-
async function accumulateSessionTokenUsage(store, taskId, session) {
|
|
61717
|
-
try {
|
|
61718
|
-
const stats = readSessionStats(session);
|
|
61719
|
-
const tokens = stats?.tokens;
|
|
61720
|
-
if (!tokens) return;
|
|
61721
|
-
const currentInput = (tokens.input ?? 0) + (tokens.cacheWrite ?? 0);
|
|
61722
|
-
const currentOutput = tokens.output ?? 0;
|
|
61723
|
-
const currentCached = tokens.cacheRead ?? 0;
|
|
61724
|
-
const baseline = sessionBaselines.get(session) ?? { input: 0, output: 0, cached: 0 };
|
|
61725
|
-
const inputDelta = Math.max(0, currentInput - baseline.input);
|
|
61726
|
-
const outputDelta = Math.max(0, currentOutput - baseline.output);
|
|
61727
|
-
const cachedDelta = Math.max(0, currentCached - baseline.cached);
|
|
61728
|
-
sessionBaselines.set(session, {
|
|
61729
|
-
input: currentInput,
|
|
61730
|
-
output: currentOutput,
|
|
61731
|
-
cached: currentCached
|
|
61732
|
-
});
|
|
61733
|
-
if (inputDelta === 0 && outputDelta === 0 && cachedDelta === 0) return;
|
|
61734
|
-
const task = await store.getTask(taskId);
|
|
61735
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
61736
|
-
const newInput = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
|
|
61737
|
-
const newOutput = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
|
|
61738
|
-
const newCached = (task.tokenUsage?.cachedTokens ?? 0) + cachedDelta;
|
|
61739
|
-
await store.updateTask(taskId, {
|
|
61740
|
-
tokenUsage: {
|
|
61741
|
-
inputTokens: newInput,
|
|
61742
|
-
outputTokens: newOutput,
|
|
61743
|
-
cachedTokens: newCached,
|
|
61744
|
-
totalTokens: newInput + newOutput + newCached,
|
|
61745
|
-
firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
|
|
61746
|
-
lastUsedAt: now
|
|
61747
|
-
}
|
|
61748
|
-
});
|
|
61749
|
-
} catch (err) {
|
|
61750
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
61751
|
-
log14.warn(`${taskId}: session token usage accumulate failed: ${message}`);
|
|
61752
|
-
}
|
|
61753
|
-
}
|
|
61754
|
-
var log14, sessionBaselines;
|
|
61755
|
-
var init_session_token_usage = __esm({
|
|
61756
|
-
"../engine/src/session-token-usage.ts"() {
|
|
61757
|
-
"use strict";
|
|
61758
|
-
init_logger2();
|
|
61759
|
-
log14 = createLogger2("session-token-usage");
|
|
61760
|
-
sessionBaselines = /* @__PURE__ */ new WeakMap();
|
|
61761
|
-
}
|
|
61762
|
-
});
|
|
61763
|
-
|
|
61764
|
-
// ../engine/src/run-audit.ts
|
|
61765
|
-
function createRunAuditor(store, context) {
|
|
61766
|
-
if (!context) {
|
|
61767
|
-
return {
|
|
61768
|
-
git: async () => {
|
|
61769
|
-
},
|
|
61770
|
-
database: async () => {
|
|
61771
|
-
},
|
|
61772
|
-
filesystem: async () => {
|
|
61773
|
-
}
|
|
61774
|
-
};
|
|
61775
|
-
}
|
|
61776
|
-
const hasRecordAuditEvent = typeof store.recordRunAuditEvent === "function";
|
|
61777
|
-
if (!hasRecordAuditEvent) {
|
|
61778
|
-
return {
|
|
61779
|
-
git: async () => {
|
|
61780
|
-
},
|
|
61781
|
-
database: async () => {
|
|
61782
|
-
},
|
|
61783
|
-
filesystem: async () => {
|
|
61784
|
-
}
|
|
61785
|
-
};
|
|
61786
|
-
}
|
|
61787
|
-
return {
|
|
61788
|
-
git: async (input) => {
|
|
61789
|
-
const eventInput = {
|
|
61790
|
-
taskId: context.taskId,
|
|
61791
|
-
agentId: context.agentId,
|
|
61792
|
-
runId: context.runId,
|
|
61793
|
-
domain: "git",
|
|
61794
|
-
mutationType: input.type,
|
|
61795
|
-
target: input.target,
|
|
61796
|
-
metadata: {
|
|
61797
|
-
phase: context.phase,
|
|
61798
|
-
...context.source ? { source: context.source } : {},
|
|
61799
|
-
...input.metadata
|
|
61800
|
-
}
|
|
61801
|
-
};
|
|
61802
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61803
|
-
},
|
|
61804
|
-
database: async (input) => {
|
|
61805
|
-
const inferredTaskId = input.target.startsWith("FN-") || input.target.startsWith("KB-") ? input.target : context.taskId;
|
|
61806
|
-
const eventInput = {
|
|
61807
|
-
taskId: inferredTaskId,
|
|
61808
|
-
agentId: context.agentId,
|
|
61809
|
-
runId: context.runId,
|
|
61810
|
-
domain: "database",
|
|
61811
|
-
mutationType: input.type,
|
|
61812
|
-
target: input.target,
|
|
61813
|
-
metadata: {
|
|
61814
|
-
phase: context.phase,
|
|
61815
|
-
...context.source ? { source: context.source } : {},
|
|
61816
|
-
...input.metadata
|
|
61817
|
-
}
|
|
61818
|
-
};
|
|
61819
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61820
|
-
},
|
|
61821
|
-
filesystem: async (input) => {
|
|
61822
|
-
const eventInput = {
|
|
61823
|
-
taskId: context.taskId,
|
|
61824
|
-
agentId: context.agentId,
|
|
61825
|
-
runId: context.runId,
|
|
61826
|
-
domain: "filesystem",
|
|
61827
|
-
mutationType: input.type,
|
|
61828
|
-
target: input.target,
|
|
61829
|
-
metadata: {
|
|
61830
|
-
phase: context.phase,
|
|
61831
|
-
...context.source ? { source: context.source } : {},
|
|
61832
|
-
...input.metadata
|
|
61833
|
-
}
|
|
61834
|
-
};
|
|
61835
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61836
|
-
}
|
|
61837
|
-
};
|
|
61838
|
-
}
|
|
61839
|
-
function generateSyntheticRunId(prefix, taskId) {
|
|
61840
|
-
const timestamp = Date.now();
|
|
61841
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
61842
|
-
return `${prefix}-${taskId}-${timestamp}-${random}`;
|
|
61843
|
-
}
|
|
61844
|
-
var init_run_audit = __esm({
|
|
61845
|
-
"../engine/src/run-audit.ts"() {
|
|
61846
|
-
"use strict";
|
|
61847
|
-
}
|
|
61848
|
-
});
|
|
61849
|
-
|
|
61850
|
-
// ../engine/src/merger.ts
|
|
61851
|
-
import { execSync, exec as exec3, spawn as spawn3 } from "node:child_process";
|
|
61852
|
-
import { promisify as promisify4 } from "node:util";
|
|
61853
|
-
import { existsSync as existsSync23 } from "node:fs";
|
|
61854
|
-
import { join as join29 } from "node:path";
|
|
61855
|
-
import { Type as Type3 } from "typebox";
|
|
62082
|
+
// ../engine/src/verification-utils.ts
|
|
62083
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
61856
62084
|
async function execWithProcessGroup(command, options) {
|
|
61857
62085
|
return new Promise((resolve20, reject) => {
|
|
61858
62086
|
if (options.signal?.aborted) {
|
|
@@ -61964,6 +62192,11 @@ function truncateWithEllipsis(text, maxChars) {
|
|
|
61964
62192
|
return `${text.slice(0, maxChars)}
|
|
61965
62193
|
... (truncated)`;
|
|
61966
62194
|
}
|
|
62195
|
+
function truncateOutput(output) {
|
|
62196
|
+
if (output.length <= VERIFICATION_LOG_MAX_CHARS) return output;
|
|
62197
|
+
return `... output truncated to last ${VERIFICATION_LOG_MAX_CHARS} characters ...
|
|
62198
|
+
${output.slice(-VERIFICATION_LOG_MAX_CHARS)}`;
|
|
62199
|
+
}
|
|
61967
62200
|
function summarizeVerificationOutput(output, type) {
|
|
61968
62201
|
const lines = output.split("\n");
|
|
61969
62202
|
let summaryLine = null;
|
|
@@ -62029,6 +62262,13 @@ function summarizeVerificationOutput(output, type) {
|
|
|
62029
62262
|
failureNames.add(truncated);
|
|
62030
62263
|
}
|
|
62031
62264
|
const footer = "(full output available in engine logs)";
|
|
62265
|
+
if (type === "build") {
|
|
62266
|
+
const buildError = output.length > 500 ? `${output.slice(0, 500)}
|
|
62267
|
+
... (truncated)` : output;
|
|
62268
|
+
return `Build output:
|
|
62269
|
+
${buildError}
|
|
62270
|
+
${footer}`;
|
|
62271
|
+
}
|
|
62032
62272
|
const parts = [];
|
|
62033
62273
|
if (summaryLine) {
|
|
62034
62274
|
parts.push(summaryLine);
|
|
@@ -62046,29 +62286,292 @@ function summarizeVerificationOutput(output, type) {
|
|
|
62046
62286
|
parts.push(` \u2022 ... and ${names.length - 5} more failures`);
|
|
62047
62287
|
}
|
|
62048
62288
|
}
|
|
62049
|
-
if (parts.length
|
|
62050
|
-
|
|
62051
|
-
|
|
62052
|
-
|
|
62053
|
-
|
|
62054
|
-
|
|
62055
|
-
return `Verification command failed with no output
|
|
62289
|
+
if (parts.length === 0) {
|
|
62290
|
+
if (output.trim().length === 0) {
|
|
62291
|
+
return `no output
|
|
62292
|
+
${footer}`;
|
|
62293
|
+
}
|
|
62294
|
+
return `${truncateOutput(output)}
|
|
62056
62295
|
${footer}`;
|
|
62057
62296
|
}
|
|
62058
|
-
|
|
62059
|
-
return `${trimmed}
|
|
62297
|
+
return parts.join("\n") + `
|
|
62060
62298
|
${footer}`;
|
|
62299
|
+
}
|
|
62300
|
+
async function runVerificationCommand(store, rootDir, taskId, command, type, signal, log18, agentLabel) {
|
|
62301
|
+
const logger2 = log18 ?? { log: console.log, error: console.error, warn: console.warn };
|
|
62302
|
+
const label = agentLabel ?? "merger";
|
|
62303
|
+
if (signal?.aborted) {
|
|
62304
|
+
throw Object.assign(
|
|
62305
|
+
new Error(`Command aborted before start: ${command}`),
|
|
62306
|
+
{ code: "ABORT_ERR", aborted: true }
|
|
62307
|
+
);
|
|
62061
62308
|
}
|
|
62062
|
-
|
|
62063
|
-
|
|
62064
|
-
|
|
62065
|
-
|
|
62066
|
-
|
|
62309
|
+
logger2.log(`${taskId}: running ${type} command: ${command}`);
|
|
62310
|
+
await store.logEntry(taskId, `[verification] Running ${type} command: ${command}`);
|
|
62311
|
+
await store.appendAgentLog(taskId, `Running ${type} command`, "tool", command, label);
|
|
62312
|
+
const result = {
|
|
62313
|
+
command,
|
|
62314
|
+
exitCode: null,
|
|
62315
|
+
stdout: "",
|
|
62316
|
+
stderr: "",
|
|
62317
|
+
success: false
|
|
62318
|
+
};
|
|
62319
|
+
const verificationStartedAt = Date.now();
|
|
62320
|
+
try {
|
|
62321
|
+
const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(command, {
|
|
62322
|
+
cwd: rootDir,
|
|
62323
|
+
timeout: VERIFICATION_COMMAND_TIMEOUT_MS,
|
|
62324
|
+
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
|
|
62325
|
+
signal
|
|
62326
|
+
});
|
|
62327
|
+
if (signal?.aborted) {
|
|
62328
|
+
throw Object.assign(
|
|
62329
|
+
new Error(`Command aborted: ${command}`),
|
|
62330
|
+
{ code: "ABORT_ERR", aborted: true }
|
|
62331
|
+
);
|
|
62332
|
+
}
|
|
62333
|
+
result.stdout = stdout?.toString?.() || "";
|
|
62334
|
+
result.stderr = stderr?.toString?.() || "";
|
|
62335
|
+
result.exitCode = 0;
|
|
62336
|
+
result.success = true;
|
|
62337
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62338
|
+
const timingDetail = `${verificationDurationMs}ms`;
|
|
62339
|
+
if (bufferOverflow) {
|
|
62340
|
+
logger2.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62341
|
+
await store.logEntry(
|
|
62342
|
+
taskId,
|
|
62343
|
+
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62344
|
+
);
|
|
62345
|
+
await store.appendAgentLog(
|
|
62346
|
+
taskId,
|
|
62347
|
+
`${type} command succeeded (exit 0)`,
|
|
62348
|
+
"tool_result",
|
|
62349
|
+
timingDetail,
|
|
62350
|
+
label
|
|
62351
|
+
);
|
|
62352
|
+
} else {
|
|
62353
|
+
logger2.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
62354
|
+
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
62355
|
+
await store.appendAgentLog(
|
|
62356
|
+
taskId,
|
|
62357
|
+
`${type} command succeeded (exit 0)`,
|
|
62358
|
+
"tool_result",
|
|
62359
|
+
timingDetail,
|
|
62360
|
+
label
|
|
62361
|
+
);
|
|
62362
|
+
}
|
|
62363
|
+
return result;
|
|
62364
|
+
} catch (error) {
|
|
62365
|
+
if (signal?.aborted) {
|
|
62366
|
+
throw Object.assign(
|
|
62367
|
+
new Error(`Command aborted: ${command}`),
|
|
62368
|
+
{ code: "ABORT_ERR", aborted: true }
|
|
62369
|
+
);
|
|
62370
|
+
}
|
|
62371
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62372
|
+
const err = error;
|
|
62373
|
+
result.stdout = err?.stdout?.toString?.() || "";
|
|
62374
|
+
result.stderr = err?.stderr?.toString?.() || "";
|
|
62375
|
+
result.exitCode = typeof err?.status === "number" ? err.status : typeof err?.code === "number" ? err.code : null;
|
|
62376
|
+
const maxBufferExceeded = err?.code === "ENOBUFS" || err?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(err?.message ?? "").includes("maxBuffer");
|
|
62377
|
+
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
62378
|
+
if (result.success) {
|
|
62379
|
+
logger2.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62380
|
+
await store.logEntry(
|
|
62381
|
+
taskId,
|
|
62382
|
+
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62383
|
+
);
|
|
62384
|
+
await store.appendAgentLog(
|
|
62385
|
+
taskId,
|
|
62386
|
+
`${type} command succeeded (exit 0)`,
|
|
62387
|
+
"tool_result",
|
|
62388
|
+
`${verificationDurationMs}ms`,
|
|
62389
|
+
label
|
|
62390
|
+
);
|
|
62391
|
+
return result;
|
|
62067
62392
|
}
|
|
62393
|
+
const output = result.stderr || result.stdout || err?.message || "Unknown error";
|
|
62394
|
+
const summary = summarizeVerificationOutput(output, type);
|
|
62395
|
+
logger2.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
62396
|
+
await store.logEntry(
|
|
62397
|
+
taskId,
|
|
62398
|
+
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
62399
|
+
${summary}`
|
|
62400
|
+
);
|
|
62401
|
+
await store.appendAgentLog(
|
|
62402
|
+
taskId,
|
|
62403
|
+
`${type} command failed (exit ${result.exitCode})`,
|
|
62404
|
+
"tool_error",
|
|
62405
|
+
summary,
|
|
62406
|
+
label
|
|
62407
|
+
);
|
|
62068
62408
|
}
|
|
62069
|
-
return
|
|
62070
|
-
|
|
62409
|
+
return result;
|
|
62410
|
+
}
|
|
62411
|
+
var VERIFICATION_COMMAND_MAX_BUFFER, VERIFICATION_COMMAND_TIMEOUT_MS, VERIFICATION_LOG_MAX_CHARS;
|
|
62412
|
+
var init_verification_utils = __esm({
|
|
62413
|
+
"../engine/src/verification-utils.ts"() {
|
|
62414
|
+
"use strict";
|
|
62415
|
+
VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024;
|
|
62416
|
+
VERIFICATION_COMMAND_TIMEOUT_MS = 6e5;
|
|
62417
|
+
VERIFICATION_LOG_MAX_CHARS = 2e4;
|
|
62418
|
+
}
|
|
62419
|
+
});
|
|
62420
|
+
|
|
62421
|
+
// ../engine/src/session-token-usage.ts
|
|
62422
|
+
var session_token_usage_exports = {};
|
|
62423
|
+
__export(session_token_usage_exports, {
|
|
62424
|
+
accumulateSessionTokenUsage: () => accumulateSessionTokenUsage
|
|
62425
|
+
});
|
|
62426
|
+
function readSessionStats(session) {
|
|
62427
|
+
const accessor = session.getSessionStats;
|
|
62428
|
+
if (typeof accessor !== "function") return void 0;
|
|
62429
|
+
try {
|
|
62430
|
+
return accessor.call(session);
|
|
62431
|
+
} catch {
|
|
62432
|
+
return void 0;
|
|
62433
|
+
}
|
|
62434
|
+
}
|
|
62435
|
+
async function accumulateSessionTokenUsage(store, taskId, session) {
|
|
62436
|
+
try {
|
|
62437
|
+
const stats = readSessionStats(session);
|
|
62438
|
+
const tokens = stats?.tokens;
|
|
62439
|
+
if (!tokens) return;
|
|
62440
|
+
const currentInput = (tokens.input ?? 0) + (tokens.cacheWrite ?? 0);
|
|
62441
|
+
const currentOutput = tokens.output ?? 0;
|
|
62442
|
+
const currentCached = tokens.cacheRead ?? 0;
|
|
62443
|
+
const baseline = sessionBaselines.get(session) ?? { input: 0, output: 0, cached: 0 };
|
|
62444
|
+
const inputDelta = Math.max(0, currentInput - baseline.input);
|
|
62445
|
+
const outputDelta = Math.max(0, currentOutput - baseline.output);
|
|
62446
|
+
const cachedDelta = Math.max(0, currentCached - baseline.cached);
|
|
62447
|
+
sessionBaselines.set(session, {
|
|
62448
|
+
input: currentInput,
|
|
62449
|
+
output: currentOutput,
|
|
62450
|
+
cached: currentCached
|
|
62451
|
+
});
|
|
62452
|
+
if (inputDelta === 0 && outputDelta === 0 && cachedDelta === 0) return;
|
|
62453
|
+
const task = await store.getTask(taskId);
|
|
62454
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
62455
|
+
const newInput = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
|
|
62456
|
+
const newOutput = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
|
|
62457
|
+
const newCached = (task.tokenUsage?.cachedTokens ?? 0) + cachedDelta;
|
|
62458
|
+
await store.updateTask(taskId, {
|
|
62459
|
+
tokenUsage: {
|
|
62460
|
+
inputTokens: newInput,
|
|
62461
|
+
outputTokens: newOutput,
|
|
62462
|
+
cachedTokens: newCached,
|
|
62463
|
+
totalTokens: newInput + newOutput + newCached,
|
|
62464
|
+
firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
|
|
62465
|
+
lastUsedAt: now
|
|
62466
|
+
}
|
|
62467
|
+
});
|
|
62468
|
+
} catch (err) {
|
|
62469
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62470
|
+
log14.warn(`${taskId}: session token usage accumulate failed: ${message}`);
|
|
62471
|
+
}
|
|
62472
|
+
}
|
|
62473
|
+
var log14, sessionBaselines;
|
|
62474
|
+
var init_session_token_usage = __esm({
|
|
62475
|
+
"../engine/src/session-token-usage.ts"() {
|
|
62476
|
+
"use strict";
|
|
62477
|
+
init_logger2();
|
|
62478
|
+
log14 = createLogger2("session-token-usage");
|
|
62479
|
+
sessionBaselines = /* @__PURE__ */ new WeakMap();
|
|
62480
|
+
}
|
|
62481
|
+
});
|
|
62482
|
+
|
|
62483
|
+
// ../engine/src/run-audit.ts
|
|
62484
|
+
function createRunAuditor(store, context) {
|
|
62485
|
+
if (!context) {
|
|
62486
|
+
return {
|
|
62487
|
+
git: async () => {
|
|
62488
|
+
},
|
|
62489
|
+
database: async () => {
|
|
62490
|
+
},
|
|
62491
|
+
filesystem: async () => {
|
|
62492
|
+
}
|
|
62493
|
+
};
|
|
62494
|
+
}
|
|
62495
|
+
const hasRecordAuditEvent = typeof store.recordRunAuditEvent === "function";
|
|
62496
|
+
if (!hasRecordAuditEvent) {
|
|
62497
|
+
return {
|
|
62498
|
+
git: async () => {
|
|
62499
|
+
},
|
|
62500
|
+
database: async () => {
|
|
62501
|
+
},
|
|
62502
|
+
filesystem: async () => {
|
|
62503
|
+
}
|
|
62504
|
+
};
|
|
62505
|
+
}
|
|
62506
|
+
return {
|
|
62507
|
+
git: async (input) => {
|
|
62508
|
+
const eventInput = {
|
|
62509
|
+
taskId: context.taskId,
|
|
62510
|
+
agentId: context.agentId,
|
|
62511
|
+
runId: context.runId,
|
|
62512
|
+
domain: "git",
|
|
62513
|
+
mutationType: input.type,
|
|
62514
|
+
target: input.target,
|
|
62515
|
+
metadata: {
|
|
62516
|
+
phase: context.phase,
|
|
62517
|
+
...context.source ? { source: context.source } : {},
|
|
62518
|
+
...input.metadata
|
|
62519
|
+
}
|
|
62520
|
+
};
|
|
62521
|
+
await store.recordRunAuditEvent(eventInput);
|
|
62522
|
+
},
|
|
62523
|
+
database: async (input) => {
|
|
62524
|
+
const inferredTaskId = input.target.startsWith("FN-") || input.target.startsWith("KB-") ? input.target : context.taskId;
|
|
62525
|
+
const eventInput = {
|
|
62526
|
+
taskId: inferredTaskId,
|
|
62527
|
+
agentId: context.agentId,
|
|
62528
|
+
runId: context.runId,
|
|
62529
|
+
domain: "database",
|
|
62530
|
+
mutationType: input.type,
|
|
62531
|
+
target: input.target,
|
|
62532
|
+
metadata: {
|
|
62533
|
+
phase: context.phase,
|
|
62534
|
+
...context.source ? { source: context.source } : {},
|
|
62535
|
+
...input.metadata
|
|
62536
|
+
}
|
|
62537
|
+
};
|
|
62538
|
+
await store.recordRunAuditEvent(eventInput);
|
|
62539
|
+
},
|
|
62540
|
+
filesystem: async (input) => {
|
|
62541
|
+
const eventInput = {
|
|
62542
|
+
taskId: context.taskId,
|
|
62543
|
+
agentId: context.agentId,
|
|
62544
|
+
runId: context.runId,
|
|
62545
|
+
domain: "filesystem",
|
|
62546
|
+
mutationType: input.type,
|
|
62547
|
+
target: input.target,
|
|
62548
|
+
metadata: {
|
|
62549
|
+
phase: context.phase,
|
|
62550
|
+
...context.source ? { source: context.source } : {},
|
|
62551
|
+
...input.metadata
|
|
62552
|
+
}
|
|
62553
|
+
};
|
|
62554
|
+
await store.recordRunAuditEvent(eventInput);
|
|
62555
|
+
}
|
|
62556
|
+
};
|
|
62071
62557
|
}
|
|
62558
|
+
function generateSyntheticRunId(prefix, taskId) {
|
|
62559
|
+
const timestamp = Date.now();
|
|
62560
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
62561
|
+
return `${prefix}-${taskId}-${timestamp}-${random}`;
|
|
62562
|
+
}
|
|
62563
|
+
var init_run_audit = __esm({
|
|
62564
|
+
"../engine/src/run-audit.ts"() {
|
|
62565
|
+
"use strict";
|
|
62566
|
+
}
|
|
62567
|
+
});
|
|
62568
|
+
|
|
62569
|
+
// ../engine/src/merger.ts
|
|
62570
|
+
import { execSync, exec as exec3 } from "node:child_process";
|
|
62571
|
+
import { promisify as promisify4 } from "node:util";
|
|
62572
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
62573
|
+
import { join as join30 } from "node:path";
|
|
62574
|
+
import { Type as Type3 } from "typebox";
|
|
62072
62575
|
function truncateWorkflowScriptOutput(output) {
|
|
62073
62576
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS) return output;
|
|
62074
62577
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS} characters ...
|
|
@@ -62112,7 +62615,7 @@ async function getStagedFiles(cwd) {
|
|
|
62112
62615
|
}
|
|
62113
62616
|
}
|
|
62114
62617
|
function hasInstallState(rootDir) {
|
|
62115
|
-
return
|
|
62618
|
+
return existsSync24(join30(rootDir, "node_modules")) || existsSync24(join30(rootDir, ".pnp.cjs"));
|
|
62116
62619
|
}
|
|
62117
62620
|
function shouldSyncDependenciesForMerge(stagedFiles, installStatePresent) {
|
|
62118
62621
|
if (!installStatePresent) return true;
|
|
@@ -62121,10 +62624,10 @@ function shouldSyncDependenciesForMerge(stagedFiles, installStatePresent) {
|
|
|
62121
62624
|
);
|
|
62122
62625
|
}
|
|
62123
62626
|
function getDependencySyncCommand(rootDir) {
|
|
62124
|
-
if (
|
|
62125
|
-
if (
|
|
62126
|
-
if (
|
|
62127
|
-
if (
|
|
62627
|
+
if (existsSync24(join30(rootDir, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
|
|
62628
|
+
if (existsSync24(join30(rootDir, "package-lock.json"))) return "npm install";
|
|
62629
|
+
if (existsSync24(join30(rootDir, "yarn.lock"))) return "yarn install --frozen-lockfile";
|
|
62630
|
+
if (existsSync24(join30(rootDir, "bun.lock")) || existsSync24(join30(rootDir, "bun.lockb"))) {
|
|
62128
62631
|
return "bun install --frozen-lockfile";
|
|
62129
62632
|
}
|
|
62130
62633
|
return null;
|
|
@@ -62157,8 +62660,8 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
62157
62660
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62158
62661
|
};
|
|
62159
62662
|
}
|
|
62160
|
-
if (
|
|
62161
|
-
if (
|
|
62663
|
+
if (existsSync24(join30(rootDir, "pnpm-lock.yaml"))) {
|
|
62664
|
+
if (existsSync24(join30(rootDir, "pnpm-workspace.yaml"))) {
|
|
62162
62665
|
mergerLog.warn(
|
|
62163
62666
|
`Inferred test command "pnpm test" in a pnpm workspace (${rootDir}). This runs the full monorepo suite on every merge. Consider setting an explicit scoped testCommand in project settings, e.g. \`pnpm -r --filter "...[main]" test\`.`
|
|
62164
62667
|
);
|
|
@@ -62169,21 +62672,21 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
62169
62672
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62170
62673
|
};
|
|
62171
62674
|
}
|
|
62172
|
-
if (
|
|
62675
|
+
if (existsSync24(join30(rootDir, "yarn.lock"))) {
|
|
62173
62676
|
return {
|
|
62174
62677
|
command: "yarn test",
|
|
62175
62678
|
testSource: "inferred",
|
|
62176
62679
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62177
62680
|
};
|
|
62178
62681
|
}
|
|
62179
|
-
if (
|
|
62682
|
+
if (existsSync24(join30(rootDir, "bun.lock")) || existsSync24(join30(rootDir, "bun.lockb"))) {
|
|
62180
62683
|
return {
|
|
62181
62684
|
command: "bun test",
|
|
62182
62685
|
testSource: "inferred",
|
|
62183
62686
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62184
62687
|
};
|
|
62185
62688
|
}
|
|
62186
|
-
if (
|
|
62689
|
+
if (existsSync24(join30(rootDir, "package-lock.json"))) {
|
|
62187
62690
|
return {
|
|
62188
62691
|
command: "npm test",
|
|
62189
62692
|
testSource: "inferred",
|
|
@@ -62220,7 +62723,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62220
62723
|
await store.logEntry(taskId, deterministicVerificationMessage);
|
|
62221
62724
|
await store.appendAgentLog(taskId, deterministicVerificationMessage, "text", void 0, "merger");
|
|
62222
62725
|
if (hasTestCommand) {
|
|
62223
|
-
const testResult = await
|
|
62726
|
+
const testResult = await runVerificationCommand2(
|
|
62224
62727
|
store,
|
|
62225
62728
|
rootDir,
|
|
62226
62729
|
taskId,
|
|
@@ -62251,7 +62754,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62251
62754
|
}
|
|
62252
62755
|
}
|
|
62253
62756
|
if (hasBuildCommand) {
|
|
62254
|
-
const buildResult = await
|
|
62757
|
+
const buildResult = await runVerificationCommand2(
|
|
62255
62758
|
store,
|
|
62256
62759
|
rootDir,
|
|
62257
62760
|
taskId,
|
|
@@ -62286,98 +62789,9 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62286
62789
|
await store.appendAgentLog(taskId, "Deterministic merge verification passed", "text", void 0, "merger");
|
|
62287
62790
|
return result;
|
|
62288
62791
|
}
|
|
62289
|
-
async function
|
|
62792
|
+
async function runVerificationCommand2(store, rootDir, taskId, command, type, signal) {
|
|
62290
62793
|
throwIfAborted(signal, taskId);
|
|
62291
|
-
|
|
62292
|
-
await store.logEntry(taskId, `[verification] Running ${type} command: ${command}`);
|
|
62293
|
-
await store.appendAgentLog(taskId, `Running ${type} command`, "tool", command, "merger");
|
|
62294
|
-
const result = {
|
|
62295
|
-
command,
|
|
62296
|
-
exitCode: null,
|
|
62297
|
-
stdout: "",
|
|
62298
|
-
stderr: "",
|
|
62299
|
-
success: false
|
|
62300
|
-
};
|
|
62301
|
-
const verificationStartedAt = Date.now();
|
|
62302
|
-
try {
|
|
62303
|
-
const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(command, {
|
|
62304
|
-
cwd: rootDir,
|
|
62305
|
-
timeout: VERIFICATION_COMMAND_TIMEOUT_MS,
|
|
62306
|
-
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
|
|
62307
|
-
signal
|
|
62308
|
-
});
|
|
62309
|
-
throwIfAborted(signal, taskId);
|
|
62310
|
-
result.stdout = stdout?.toString?.() || "";
|
|
62311
|
-
result.stderr = stderr?.toString?.() || "";
|
|
62312
|
-
result.exitCode = 0;
|
|
62313
|
-
result.success = true;
|
|
62314
|
-
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62315
|
-
const timingDetail = `${verificationDurationMs}ms`;
|
|
62316
|
-
if (bufferOverflow) {
|
|
62317
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62318
|
-
await store.logEntry(
|
|
62319
|
-
taskId,
|
|
62320
|
-
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62321
|
-
);
|
|
62322
|
-
await store.appendAgentLog(
|
|
62323
|
-
taskId,
|
|
62324
|
-
`${type} command succeeded (exit 0)`,
|
|
62325
|
-
"tool_result",
|
|
62326
|
-
timingDetail,
|
|
62327
|
-
"merger"
|
|
62328
|
-
);
|
|
62329
|
-
} else {
|
|
62330
|
-
mergerLog.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
62331
|
-
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
62332
|
-
await store.appendAgentLog(
|
|
62333
|
-
taskId,
|
|
62334
|
-
`${type} command succeeded (exit 0)`,
|
|
62335
|
-
"tool_result",
|
|
62336
|
-
timingDetail,
|
|
62337
|
-
"merger"
|
|
62338
|
-
);
|
|
62339
|
-
}
|
|
62340
|
-
return result;
|
|
62341
|
-
} catch (error) {
|
|
62342
|
-
throwIfAborted(signal, taskId);
|
|
62343
|
-
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62344
|
-
result.stdout = error?.stdout?.toString?.() || "";
|
|
62345
|
-
result.stderr = error?.stderr?.toString?.() || "";
|
|
62346
|
-
result.exitCode = typeof error?.status === "number" ? error.status : typeof error?.code === "number" ? error.code : null;
|
|
62347
|
-
const maxBufferExceeded = error?.code === "ENOBUFS" || error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(error?.message ?? "").includes("maxBuffer");
|
|
62348
|
-
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
62349
|
-
if (result.success) {
|
|
62350
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62351
|
-
await store.logEntry(
|
|
62352
|
-
taskId,
|
|
62353
|
-
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62354
|
-
);
|
|
62355
|
-
await store.appendAgentLog(
|
|
62356
|
-
taskId,
|
|
62357
|
-
`${type} command succeeded (exit 0)`,
|
|
62358
|
-
"tool_result",
|
|
62359
|
-
`${verificationDurationMs}ms`,
|
|
62360
|
-
"merger"
|
|
62361
|
-
);
|
|
62362
|
-
return result;
|
|
62363
|
-
}
|
|
62364
|
-
const output = result.stderr || result.stdout || error?.message || "Unknown error";
|
|
62365
|
-
const summary = summarizeVerificationOutput(output, type);
|
|
62366
|
-
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
62367
|
-
await store.logEntry(
|
|
62368
|
-
taskId,
|
|
62369
|
-
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
62370
|
-
${summary}`
|
|
62371
|
-
);
|
|
62372
|
-
await store.appendAgentLog(
|
|
62373
|
-
taskId,
|
|
62374
|
-
`${type} command failed (exit ${result.exitCode})`,
|
|
62375
|
-
"tool_error",
|
|
62376
|
-
summary,
|
|
62377
|
-
"merger"
|
|
62378
|
-
);
|
|
62379
|
-
}
|
|
62380
|
-
return result;
|
|
62794
|
+
return runVerificationCommand(store, rootDir, taskId, command, type, signal, mergerLog, "merger");
|
|
62381
62795
|
}
|
|
62382
62796
|
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, mergeRunContext, fixAttemptNumber, _testCommand, _buildCommand) {
|
|
62383
62797
|
try {
|
|
@@ -62440,9 +62854,20 @@ Do not refactor, rename broadly, or make opportunistic improvements.
|
|
|
62440
62854
|
onToolEnd: logger2.onToolEnd,
|
|
62441
62855
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
62442
62856
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
62857
|
+
fallbackProvider: settings.fallbackProvider,
|
|
62858
|
+
fallbackModelId: settings.fallbackModelId,
|
|
62443
62859
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
62444
62860
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
62445
|
-
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
62861
|
+
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
62862
|
+
taskId,
|
|
62863
|
+
taskTitle: taskForSkillContext?.title,
|
|
62864
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
62865
|
+
agent: "merger",
|
|
62866
|
+
label: "merge verification fix agent",
|
|
62867
|
+
store,
|
|
62868
|
+
taskId,
|
|
62869
|
+
taskTitle: taskForSkillContext?.title
|
|
62870
|
+
})
|
|
62446
62871
|
});
|
|
62447
62872
|
const runId = mergeRunContext?.runId;
|
|
62448
62873
|
const agentId = mergeRunContext?.agentId ?? "merger";
|
|
@@ -62495,7 +62920,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
62495
62920
|
void 0,
|
|
62496
62921
|
"merger"
|
|
62497
62922
|
);
|
|
62498
|
-
const reRunResult = await
|
|
62923
|
+
const reRunResult = await runVerificationCommand2(
|
|
62499
62924
|
store,
|
|
62500
62925
|
rootDir,
|
|
62501
62926
|
taskId,
|
|
@@ -63237,9 +63662,16 @@ You are assisting with a paused \`git pull --rebase\`.
|
|
|
63237
63662
|
onToolEnd: agentLogger.onToolEnd,
|
|
63238
63663
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
63239
63664
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
63665
|
+
fallbackProvider: settings.fallbackProvider,
|
|
63666
|
+
fallbackModelId: settings.fallbackModelId,
|
|
63240
63667
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
63241
63668
|
taskId,
|
|
63242
|
-
onFallbackModelUsed:
|
|
63669
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
63670
|
+
agent: "merger",
|
|
63671
|
+
label: "rebase conflict resolver",
|
|
63672
|
+
store,
|
|
63673
|
+
taskId
|
|
63674
|
+
})
|
|
63243
63675
|
});
|
|
63244
63676
|
const prompt = [
|
|
63245
63677
|
`Resolve rebase conflicts for task ${taskId}.`,
|
|
@@ -63431,7 +63863,7 @@ async function pushToRemoteAfterMerge(store, rootDir, taskId, settings, options)
|
|
|
63431
63863
|
}
|
|
63432
63864
|
async function createPostMergeWorktree(rootDir, taskId) {
|
|
63433
63865
|
const randomSuffix = Math.random().toString(36).slice(2, 10);
|
|
63434
|
-
const postMergeWorktree =
|
|
63866
|
+
const postMergeWorktree = join30(rootDir, ".worktrees", `post-merge-${taskId}-${randomSuffix}`);
|
|
63435
63867
|
try {
|
|
63436
63868
|
await execAsync2(`git worktree add ${quoteArg(postMergeWorktree)} HEAD`, { cwd: rootDir });
|
|
63437
63869
|
return postMergeWorktree;
|
|
@@ -64372,7 +64804,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
64372
64804
|
}
|
|
64373
64805
|
}
|
|
64374
64806
|
throwIfAborted(options.signal, taskId);
|
|
64375
|
-
if (worktreePath &&
|
|
64807
|
+
if (worktreePath && existsSync24(worktreePath)) {
|
|
64376
64808
|
const otherUser = await findWorktreeUser(store, worktreePath, taskId);
|
|
64377
64809
|
if (otherUser) {
|
|
64378
64810
|
mergerLog.log(`Worktree retained \u2014 still needed by ${otherUser}`);
|
|
@@ -65026,9 +65458,20 @@ async function runAiAgentForCommit(params) {
|
|
|
65026
65458
|
onToolEnd: agentLogger.onToolEnd,
|
|
65027
65459
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
65028
65460
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
65461
|
+
fallbackProvider: settings.fallbackProvider,
|
|
65462
|
+
fallbackModelId: settings.fallbackModelId,
|
|
65029
65463
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
65030
65464
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
65031
|
-
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
65465
|
+
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
65466
|
+
taskId,
|
|
65467
|
+
taskTitle: taskForSkillContext?.title,
|
|
65468
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
65469
|
+
agent: "merger",
|
|
65470
|
+
label: "merge agent",
|
|
65471
|
+
store,
|
|
65472
|
+
taskId,
|
|
65473
|
+
taskTitle: taskForSkillContext?.title
|
|
65474
|
+
})
|
|
65032
65475
|
});
|
|
65033
65476
|
options.onSession?.(session);
|
|
65034
65477
|
try {
|
|
@@ -65459,7 +65902,14 @@ If issues are found that need attention, describe them clearly and include concr
|
|
|
65459
65902
|
fallbackModelId: settings.fallbackModelId,
|
|
65460
65903
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
65461
65904
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
65462
|
-
...postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {}
|
|
65905
|
+
...postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {},
|
|
65906
|
+
taskId,
|
|
65907
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
65908
|
+
agent: "merger",
|
|
65909
|
+
label: `post-merge workflow step '${workflowStep.name}'`,
|
|
65910
|
+
store,
|
|
65911
|
+
taskId
|
|
65912
|
+
})
|
|
65463
65913
|
});
|
|
65464
65914
|
mergerLog.log(`${taskId}: [post-merge] workflow step '${workflowStep.name}' using model ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);
|
|
65465
65915
|
await store.logEntry(taskId, `[post-merge] Workflow step '${workflowStep.name}' using model: ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);
|
|
@@ -65495,15 +65945,17 @@ async function completeTask(store, taskId, result) {
|
|
|
65495
65945
|
result.task = task;
|
|
65496
65946
|
store.emit("task:merged", result);
|
|
65497
65947
|
}
|
|
65498
|
-
var execAsync2, LOCKFILE_PATTERNS, GENERATED_PATTERNS, DEPENDENCY_SYNC_TRIGGER_PATTERNS,
|
|
65948
|
+
var execAsync2, LOCKFILE_PATTERNS, GENERATED_PATTERNS, DEPENDENCY_SYNC_TRIGGER_PATTERNS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, PULL_REBASE_TIMEOUT_MS, PUSH_TIMEOUT_MS, MERGE_COMMIT_LOG_MAX_CHARS, MERGE_DIFF_STAT_MAX_CHARS, VerificationError, MergeAbortedError, FUSION_TASK_ID_TRAILER_KEY;
|
|
65499
65949
|
var init_merger = __esm({
|
|
65500
65950
|
"../engine/src/merger.ts"() {
|
|
65501
65951
|
"use strict";
|
|
65952
|
+
init_verification_utils();
|
|
65953
|
+
init_verification_utils();
|
|
65502
65954
|
init_src();
|
|
65503
65955
|
init_pi();
|
|
65504
65956
|
init_session_token_usage();
|
|
65505
65957
|
init_agent_session_helpers();
|
|
65506
|
-
|
|
65958
|
+
init_fallback_model_observer();
|
|
65507
65959
|
init_session_skill_context();
|
|
65508
65960
|
init_agent_logger();
|
|
65509
65961
|
init_logger2();
|
|
@@ -65549,9 +66001,6 @@ var init_merger = __esm({
|
|
|
65549
66001
|
"bun.lock",
|
|
65550
66002
|
"packages/*/package.json"
|
|
65551
66003
|
];
|
|
65552
|
-
VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024;
|
|
65553
|
-
VERIFICATION_COMMAND_TIMEOUT_MS = 6e5;
|
|
65554
|
-
VERIFICATION_LOG_MAX_CHARS = 2e4;
|
|
65555
66004
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS = 4e3;
|
|
65556
66005
|
PULL_REBASE_TIMEOUT_MS = 12e4;
|
|
65557
66006
|
PUSH_TIMEOUT_MS = 6e4;
|
|
@@ -65576,8 +66025,8 @@ var init_merger = __esm({
|
|
|
65576
66025
|
|
|
65577
66026
|
// ../engine/src/worktree-names.ts
|
|
65578
66027
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
65579
|
-
import { join as
|
|
65580
|
-
import { existsSync as
|
|
66028
|
+
import { join as join31 } from "node:path";
|
|
66029
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
65581
66030
|
function slugify2(str) {
|
|
65582
66031
|
return str.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
65583
66032
|
}
|
|
@@ -65588,7 +66037,7 @@ function generateReservedWorktreeName(rootDir, reservedNames = /* @__PURE__ */ n
|
|
|
65588
66037
|
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
65589
66038
|
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
65590
66039
|
const baseName = `${adjective}-${noun}`;
|
|
65591
|
-
const worktreesDir =
|
|
66040
|
+
const worktreesDir = join31(rootDir, ".worktrees");
|
|
65592
66041
|
const existing = getExistingWorktreeNames(worktreesDir);
|
|
65593
66042
|
for (const reserved of reservedNames) {
|
|
65594
66043
|
existing.add(reserved);
|
|
@@ -65603,7 +66052,7 @@ function generateReservedWorktreeName(rootDir, reservedNames = /* @__PURE__ */ n
|
|
|
65603
66052
|
return `${baseName}-${suffix}`;
|
|
65604
66053
|
}
|
|
65605
66054
|
function getExistingWorktreeNames(worktreesDir) {
|
|
65606
|
-
if (!
|
|
66055
|
+
if (!existsSync25(worktreesDir)) {
|
|
65607
66056
|
return /* @__PURE__ */ new Set();
|
|
65608
66057
|
}
|
|
65609
66058
|
try {
|
|
@@ -65740,8 +66189,8 @@ __export(worktree_pool_exports, {
|
|
|
65740
66189
|
});
|
|
65741
66190
|
import { exec as exec4 } from "node:child_process";
|
|
65742
66191
|
import { promisify as promisify5 } from "node:util";
|
|
65743
|
-
import { existsSync as
|
|
65744
|
-
import { join as
|
|
66192
|
+
import { existsSync as existsSync26, lstatSync, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
66193
|
+
import { join as join32, relative as relative6, resolve as resolve15, isAbsolute as isAbsolute9 } from "node:path";
|
|
65745
66194
|
function getExecStdout(result) {
|
|
65746
66195
|
if (typeof result === "string") return result;
|
|
65747
66196
|
if (result && typeof result === "object" && "stdout" in result) {
|
|
@@ -65787,10 +66236,10 @@ async function isRegisteredGitWorktree2(rootDir, worktreePath) {
|
|
|
65787
66236
|
return (await getRegisteredWorktreePaths(rootDir)).has(resolve15(worktreePath));
|
|
65788
66237
|
}
|
|
65789
66238
|
function hasRequiredWorktreeFiles(worktreePath) {
|
|
65790
|
-
return
|
|
66239
|
+
return existsSync26(join32(worktreePath, ".git")) && existsSync26(join32(worktreePath, "package.json"));
|
|
65791
66240
|
}
|
|
65792
66241
|
async function isUsableTaskWorktree(rootDir, worktreePath) {
|
|
65793
|
-
return
|
|
66242
|
+
return existsSync26(worktreePath) && await isRegisteredGitWorktree2(rootDir, worktreePath) && hasRequiredWorktreeFiles(worktreePath);
|
|
65794
66243
|
}
|
|
65795
66244
|
function isInsideWorktreesDir(rootDir, worktreePath) {
|
|
65796
66245
|
const worktreesDir = resolve15(rootDir, ".worktrees");
|
|
@@ -65799,14 +66248,14 @@ function isInsideWorktreesDir(rootDir, worktreePath) {
|
|
|
65799
66248
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute9(rel);
|
|
65800
66249
|
}
|
|
65801
66250
|
async function scanIdleWorktrees(rootDir, store) {
|
|
65802
|
-
const worktreesDir =
|
|
65803
|
-
if (!
|
|
66251
|
+
const worktreesDir = join32(rootDir, ".worktrees");
|
|
66252
|
+
if (!existsSync26(worktreesDir)) {
|
|
65804
66253
|
return [];
|
|
65805
66254
|
}
|
|
65806
66255
|
let dirs;
|
|
65807
66256
|
try {
|
|
65808
66257
|
const entries = readdirSync4(worktreesDir, { withFileTypes: true });
|
|
65809
|
-
dirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
66258
|
+
dirs = entries.filter((e) => e.isDirectory()).map((e) => join32(worktreesDir, e.name));
|
|
65810
66259
|
} catch (err) {
|
|
65811
66260
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65812
66261
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory: ${errorMessage}`);
|
|
@@ -65829,16 +66278,16 @@ async function scanIdleWorktrees(rootDir, store) {
|
|
|
65829
66278
|
return registeredDirs.filter((dir) => !activeWorktrees.has(resolve15(dir)));
|
|
65830
66279
|
}
|
|
65831
66280
|
async function cleanupOrphanedWorktrees(rootDir, store) {
|
|
65832
|
-
const worktreesDir =
|
|
65833
|
-
if (!
|
|
66281
|
+
const worktreesDir = join32(rootDir, ".worktrees");
|
|
66282
|
+
if (!existsSync26(worktreesDir)) {
|
|
65834
66283
|
return 0;
|
|
65835
66284
|
}
|
|
65836
66285
|
const orphaned = await scanIdleWorktrees(rootDir, store);
|
|
65837
66286
|
const registeredWorktrees = await getRegisteredWorktreePaths(rootDir);
|
|
65838
66287
|
let dirs = [];
|
|
65839
|
-
if (
|
|
66288
|
+
if (existsSync26(worktreesDir)) {
|
|
65840
66289
|
try {
|
|
65841
|
-
dirs = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
66290
|
+
dirs = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join32(worktreesDir, e.name));
|
|
65842
66291
|
} catch (err) {
|
|
65843
66292
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65844
66293
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory for cleanup: ${errorMessage}`);
|
|
@@ -65870,8 +66319,8 @@ async function cleanupOrphanedWorktrees(rootDir, store) {
|
|
|
65870
66319
|
return cleaned;
|
|
65871
66320
|
}
|
|
65872
66321
|
async function reapOrphanWorktrees(projectRoot) {
|
|
65873
|
-
const worktreesDir =
|
|
65874
|
-
if (!
|
|
66322
|
+
const worktreesDir = join32(projectRoot, ".worktrees");
|
|
66323
|
+
if (!existsSync26(worktreesDir)) {
|
|
65875
66324
|
return 0;
|
|
65876
66325
|
}
|
|
65877
66326
|
let entries;
|
|
@@ -65879,11 +66328,11 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
65879
66328
|
entries = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => {
|
|
65880
66329
|
if (!e.isDirectory()) return false;
|
|
65881
66330
|
try {
|
|
65882
|
-
return lstatSync(
|
|
66331
|
+
return lstatSync(join32(worktreesDir, e.name)).isDirectory() && !lstatSync(join32(worktreesDir, e.name)).isSymbolicLink();
|
|
65883
66332
|
} catch {
|
|
65884
66333
|
return false;
|
|
65885
66334
|
}
|
|
65886
|
-
}).map((e) => ({ name: e.name, fullPath:
|
|
66335
|
+
}).map((e) => ({ name: e.name, fullPath: join32(worktreesDir, e.name) }));
|
|
65887
66336
|
} catch (err) {
|
|
65888
66337
|
const msg = err instanceof Error ? err.message : String(err);
|
|
65889
66338
|
worktreePoolLog.warn(`reapOrphanWorktrees: failed to read .worktrees/ \u2014 ${msg}`);
|
|
@@ -65902,8 +66351,8 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
65902
66351
|
if (registered.has(resolvedFull)) {
|
|
65903
66352
|
continue;
|
|
65904
66353
|
}
|
|
65905
|
-
const dotGit =
|
|
65906
|
-
if (
|
|
66354
|
+
const dotGit = join32(resolvedFull, ".git");
|
|
66355
|
+
if (existsSync26(dotGit)) {
|
|
65907
66356
|
worktreePoolLog.log(`reapOrphanWorktrees: skipping ${name} (has .git entry but not in registered list \u2014 may be partially registered)`);
|
|
65908
66357
|
continue;
|
|
65909
66358
|
}
|
|
@@ -65963,7 +66412,7 @@ var init_worktree_pool = __esm({
|
|
|
65963
66412
|
acquire() {
|
|
65964
66413
|
for (const path2 of this.idle) {
|
|
65965
66414
|
this.idle.delete(path2);
|
|
65966
|
-
if (
|
|
66415
|
+
if (existsSync26(path2)) {
|
|
65967
66416
|
return path2;
|
|
65968
66417
|
}
|
|
65969
66418
|
worktreePoolLog.log(`Pruned stale entry: ${path2}`);
|
|
@@ -66010,7 +66459,7 @@ var init_worktree_pool = __esm({
|
|
|
66010
66459
|
*/
|
|
66011
66460
|
rehydrate(idlePaths) {
|
|
66012
66461
|
for (const path2 of idlePaths) {
|
|
66013
|
-
if (
|
|
66462
|
+
if (existsSync26(path2)) {
|
|
66014
66463
|
this.idle.add(path2);
|
|
66015
66464
|
} else {
|
|
66016
66465
|
worktreePoolLog.log(`Rehydrate skipped (not on disk): ${path2}`);
|
|
@@ -66066,7 +66515,7 @@ var init_worktree_pool = __esm({
|
|
|
66066
66515
|
throw err;
|
|
66067
66516
|
}
|
|
66068
66517
|
const conflictingPath = match[1];
|
|
66069
|
-
if (!
|
|
66518
|
+
if (!existsSync26(conflictingPath)) {
|
|
66070
66519
|
await execAsync3("git worktree prune", { cwd: worktreePath });
|
|
66071
66520
|
await execAsync3(checkoutCmd, { cwd: worktreePath });
|
|
66072
66521
|
return branchName;
|
|
@@ -66143,8 +66592,8 @@ var init_token_cap_detector = __esm({
|
|
|
66143
66592
|
// ../engine/src/step-session-executor.ts
|
|
66144
66593
|
import { exec as exec5 } from "node:child_process";
|
|
66145
66594
|
import { promisify as promisify6 } from "node:util";
|
|
66146
|
-
import { existsSync as
|
|
66147
|
-
import { join as
|
|
66595
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
66596
|
+
import { join as join33 } from "node:path";
|
|
66148
66597
|
function parseStepFileScopes(prompt) {
|
|
66149
66598
|
const result = /* @__PURE__ */ new Map();
|
|
66150
66599
|
if (!prompt) return result;
|
|
@@ -66431,7 +66880,7 @@ var init_step_session_executor = __esm({
|
|
|
66431
66880
|
init_worktree_names();
|
|
66432
66881
|
init_agent_logger();
|
|
66433
66882
|
init_logger2();
|
|
66434
|
-
|
|
66883
|
+
init_fallback_model_observer();
|
|
66435
66884
|
init_context_limit_detector();
|
|
66436
66885
|
init_usage_limit_detector();
|
|
66437
66886
|
init_agent_tools();
|
|
@@ -66548,7 +66997,7 @@ var init_step_session_executor = __esm({
|
|
|
66548
66997
|
}
|
|
66549
66998
|
for (const [stepIdx, worktreePath] of this.parallelWorktrees) {
|
|
66550
66999
|
try {
|
|
66551
|
-
if (
|
|
67000
|
+
if (existsSync27(worktreePath)) {
|
|
66552
67001
|
await execAsync4(`git worktree remove "${worktreePath}" --force`, {
|
|
66553
67002
|
cwd: this.options.rootDir
|
|
66554
67003
|
});
|
|
@@ -66713,7 +67162,13 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66713
67162
|
...this.options.skillSelection ? { skillSelection: this.options.skillSelection } : {},
|
|
66714
67163
|
taskId: taskDetail.id,
|
|
66715
67164
|
taskTitle: taskDetail.title,
|
|
66716
|
-
onFallbackModelUsed:
|
|
67165
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
67166
|
+
agent: "executor",
|
|
67167
|
+
label: "workflow step agent",
|
|
67168
|
+
store: this.store,
|
|
67169
|
+
taskId: taskDetail.id,
|
|
67170
|
+
taskTitle: taskDetail.title
|
|
67171
|
+
})
|
|
66717
67172
|
});
|
|
66718
67173
|
session = createResult.session;
|
|
66719
67174
|
const handle = {
|
|
@@ -66893,7 +67348,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66893
67348
|
for (const [stepIdx, worktreePath] of worktreePaths) {
|
|
66894
67349
|
if (worktreePath !== this.options.worktreePath) {
|
|
66895
67350
|
try {
|
|
66896
|
-
if (
|
|
67351
|
+
if (existsSync27(worktreePath)) {
|
|
66897
67352
|
await execAsync4(`git worktree remove "${worktreePath}" --force`, {
|
|
66898
67353
|
cwd: this.options.rootDir
|
|
66899
67354
|
});
|
|
@@ -66923,7 +67378,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66923
67378
|
async createStepWorktree(stepIndex) {
|
|
66924
67379
|
const { rootDir } = this.options;
|
|
66925
67380
|
const name = generateWorktreeName(rootDir);
|
|
66926
|
-
const worktreePath =
|
|
67381
|
+
const worktreePath = join33(rootDir, ".worktrees", name);
|
|
66927
67382
|
const branchName = `fusion/step-${stepIndex}-${name}`;
|
|
66928
67383
|
stepExecLog.log(`Creating worktree for step ${stepIndex}: ${worktreePath} (branch: ${branchName})`);
|
|
66929
67384
|
try {
|
|
@@ -66998,7 +67453,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66998
67453
|
|
|
66999
67454
|
// ../engine/src/spec-staleness.ts
|
|
67000
67455
|
import { stat as stat5 } from "node:fs/promises";
|
|
67001
|
-
import { join as
|
|
67456
|
+
import { join as join34 } from "node:path";
|
|
67002
67457
|
async function evaluateSpecStaleness(options) {
|
|
67003
67458
|
const { settings, promptPath, nowMs } = options;
|
|
67004
67459
|
if (settings.specStalenessEnabled !== true) {
|
|
@@ -67038,7 +67493,7 @@ async function evaluateSpecStaleness(options) {
|
|
|
67038
67493
|
};
|
|
67039
67494
|
}
|
|
67040
67495
|
function getPromptPath(tasksDir, taskId) {
|
|
67041
|
-
return
|
|
67496
|
+
return join34(tasksDir, taskId, "PROMPT.md");
|
|
67042
67497
|
}
|
|
67043
67498
|
var DEFAULT_SPEC_STALENESS_MAX_AGE_MS;
|
|
67044
67499
|
var init_spec_staleness = __esm({
|
|
@@ -67069,8 +67524,8 @@ var init_task_completion = __esm({
|
|
|
67069
67524
|
|
|
67070
67525
|
// ../engine/src/run-verification-tool.ts
|
|
67071
67526
|
import { spawn as spawn4 } from "node:child_process";
|
|
67072
|
-
import { existsSync as
|
|
67073
|
-
import { isAbsolute as isAbsolute10, join as
|
|
67527
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
67528
|
+
import { isAbsolute as isAbsolute10, join as join35 } from "node:path";
|
|
67074
67529
|
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
67075
67530
|
function createBuffer() {
|
|
67076
67531
|
return { headChunks: [], headBytes: 0, tailChunks: [], tailBytes: 0, totalBytes: 0 };
|
|
@@ -67103,7 +67558,7 @@ function flattenBuffer(buf) {
|
|
|
67103
67558
|
|
|
67104
67559
|
` + tail;
|
|
67105
67560
|
}
|
|
67106
|
-
async function
|
|
67561
|
+
async function runVerificationCommand3(opts) {
|
|
67107
67562
|
const { command, cwd, timeoutMs, expectFailure = false, onHeartbeat, onLine } = opts;
|
|
67108
67563
|
const startMs = Date.now();
|
|
67109
67564
|
const warnings = [];
|
|
@@ -67243,7 +67698,7 @@ function createRunVerificationTool(opts) {
|
|
|
67243
67698
|
if (params.cwd && isAbsolute10(params.cwd)) {
|
|
67244
67699
|
resolvedCwd = params.cwd;
|
|
67245
67700
|
} else if (params.cwd) {
|
|
67246
|
-
resolvedCwd =
|
|
67701
|
+
resolvedCwd = join35(worktreePath, params.cwd);
|
|
67247
67702
|
} else {
|
|
67248
67703
|
resolvedCwd = worktreePath;
|
|
67249
67704
|
}
|
|
@@ -67258,8 +67713,8 @@ function createRunVerificationTool(opts) {
|
|
|
67258
67713
|
}
|
|
67259
67714
|
let effectiveCommand = command;
|
|
67260
67715
|
if (command.trimStart().startsWith("pnpm --filter")) {
|
|
67261
|
-
const modulesYaml =
|
|
67262
|
-
if (!
|
|
67716
|
+
const modulesYaml = join35(rootDir, "node_modules", ".modules.yaml");
|
|
67717
|
+
if (!existsSync28(modulesYaml)) {
|
|
67263
67718
|
const installCmd = "pnpm install --prefer-offline";
|
|
67264
67719
|
const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
|
|
67265
67720
|
warnings.push(msg);
|
|
@@ -67270,7 +67725,7 @@ function createRunVerificationTool(opts) {
|
|
|
67270
67725
|
log18.info(
|
|
67271
67726
|
`[fn_run_verification] ${taskId}: scope=${scope} timeout=${timeoutSec}s cwd=${resolvedCwd} cmd=${effectiveCommand}`
|
|
67272
67727
|
);
|
|
67273
|
-
const result = await
|
|
67728
|
+
const result = await runVerificationCommand3({
|
|
67274
67729
|
command: effectiveCommand,
|
|
67275
67730
|
cwd: resolvedCwd,
|
|
67276
67731
|
timeoutMs,
|
|
@@ -67370,8 +67825,8 @@ var init_run_verification_tool = __esm({
|
|
|
67370
67825
|
// ../engine/src/executor.ts
|
|
67371
67826
|
import { exec as exec6 } from "node:child_process";
|
|
67372
67827
|
import { promisify as promisify7 } from "node:util";
|
|
67373
|
-
import { isAbsolute as isAbsolute11, join as
|
|
67374
|
-
import { existsSync as
|
|
67828
|
+
import { isAbsolute as isAbsolute11, join as join36, relative as relative7, resolve as resolvePath } from "node:path";
|
|
67829
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
67375
67830
|
import { readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
67376
67831
|
import { Type as Type5 } from "@mariozechner/pi-ai";
|
|
67377
67832
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
@@ -67665,6 +68120,7 @@ var init_executor = __esm({
|
|
|
67665
68120
|
"use strict";
|
|
67666
68121
|
init_src();
|
|
67667
68122
|
init_merger();
|
|
68123
|
+
init_verification_utils();
|
|
67668
68124
|
init_worktree_names();
|
|
67669
68125
|
init_pi();
|
|
67670
68126
|
init_session_token_usage();
|
|
@@ -67689,7 +68145,7 @@ var init_executor = __esm({
|
|
|
67689
68145
|
init_task_completion();
|
|
67690
68146
|
init_auth_storage();
|
|
67691
68147
|
init_run_verification_tool();
|
|
67692
|
-
|
|
68148
|
+
init_fallback_model_observer();
|
|
67693
68149
|
init_agent_logger();
|
|
67694
68150
|
init_agent_tools();
|
|
67695
68151
|
execAsync5 = promisify7(exec6);
|
|
@@ -68456,20 +68912,26 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68456
68912
|
if (from !== "in-review" && from !== "done") {
|
|
68457
68913
|
return task;
|
|
68458
68914
|
}
|
|
68459
|
-
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr";
|
|
68915
|
+
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr" || task.status === "merging-fix";
|
|
68460
68916
|
if (!hasMergeEvidence) {
|
|
68461
68917
|
return task;
|
|
68462
68918
|
}
|
|
68463
68919
|
return this.cleanupMergeStateForReverification(
|
|
68464
68920
|
task,
|
|
68465
|
-
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification
|
|
68921
|
+
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification`,
|
|
68922
|
+
{
|
|
68923
|
+
// Keep deterministic merge-verification bounce budget across remediation
|
|
68924
|
+
// cycles. Status may be cleared by intermediate paths, so the counter is
|
|
68925
|
+
// the canonical signal once a bounce has started.
|
|
68926
|
+
preserveVerificationFailureCount: (task.verificationFailureCount ?? 0) > 0
|
|
68927
|
+
}
|
|
68466
68928
|
);
|
|
68467
68929
|
}
|
|
68468
|
-
async cleanupMergeStateForReverification(task, logMessage) {
|
|
68930
|
+
async cleanupMergeStateForReverification(task, logMessage, options) {
|
|
68469
68931
|
await this.store.updateTask(task.id, {
|
|
68470
68932
|
mergeDetails: null,
|
|
68471
68933
|
mergeRetries: 0,
|
|
68472
|
-
verificationFailureCount: 0,
|
|
68934
|
+
verificationFailureCount: options?.preserveVerificationFailureCount ? task.verificationFailureCount ?? 0 : 0,
|
|
68473
68935
|
workflowStepResults: []
|
|
68474
68936
|
});
|
|
68475
68937
|
const refreshedTask = await this.store.getTask(task.id);
|
|
@@ -68836,7 +69298,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68836
69298
|
);
|
|
68837
69299
|
return false;
|
|
68838
69300
|
}
|
|
68839
|
-
if (task.worktree &&
|
|
69301
|
+
if (task.worktree && existsSync29(task.worktree)) {
|
|
68840
69302
|
const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
|
|
68841
69303
|
if (modifiedFiles.length > 0) {
|
|
68842
69304
|
await this.store.updateTask(task.id, { modifiedFiles });
|
|
@@ -69016,7 +69478,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69016
69478
|
if (task.dependencies.length === 0) return null;
|
|
69017
69479
|
for (const depId of task.dependencies) {
|
|
69018
69480
|
const dep = allTasks.find((t) => t.id === depId);
|
|
69019
|
-
if (dep && dep.worktree && (dep.column === "done" || dep.column === "in-review") &&
|
|
69481
|
+
if (dep && dep.worktree && (dep.column === "done" || dep.column === "in-review") && existsSync29(dep.worktree)) {
|
|
69020
69482
|
return dep.worktree;
|
|
69021
69483
|
}
|
|
69022
69484
|
}
|
|
@@ -69064,10 +69526,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69064
69526
|
};
|
|
69065
69527
|
const audit = createRunAuditor(this.store, engineRunContext);
|
|
69066
69528
|
const activeColumns = /* @__PURE__ */ new Set(["in-progress", "in-review", "done"]);
|
|
69067
|
-
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
69529
|
+
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
69068
69530
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
69069
69531
|
if (!isActiveTask) {
|
|
69070
|
-
const tasksDir =
|
|
69532
|
+
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
69071
69533
|
const promptPath = getPromptPath(tasksDir, task.id);
|
|
69072
69534
|
const staleness = await evaluateSpecStaleness({ settings, promptPath });
|
|
69073
69535
|
if (staleness.isStale) {
|
|
@@ -69114,7 +69576,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69114
69576
|
worktreeName = generateWorktreeName(this.rootDir);
|
|
69115
69577
|
break;
|
|
69116
69578
|
}
|
|
69117
|
-
worktreePath =
|
|
69579
|
+
worktreePath = join36(this.rootDir, ".worktrees", worktreeName);
|
|
69118
69580
|
}
|
|
69119
69581
|
let stuckRequeue = null;
|
|
69120
69582
|
let taskDone = false;
|
|
@@ -69138,7 +69600,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69138
69600
|
);
|
|
69139
69601
|
}
|
|
69140
69602
|
const branchName = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
69141
|
-
let isResume =
|
|
69603
|
+
let isResume = existsSync29(worktreePath);
|
|
69142
69604
|
let acquiredFromPool = false;
|
|
69143
69605
|
const baseBranch = task.baseBranch || null;
|
|
69144
69606
|
if (task.worktree && isResume && !await isUsableTaskWorktree(this.rootDir, worktreePath)) {
|
|
@@ -69151,8 +69613,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69151
69613
|
this.currentRunContext
|
|
69152
69614
|
);
|
|
69153
69615
|
await this.store.updateTask(task.id, { worktree: null, branch: null });
|
|
69154
|
-
worktreePath =
|
|
69155
|
-
isResume =
|
|
69616
|
+
worktreePath = join36(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
69617
|
+
isResume = existsSync29(worktreePath);
|
|
69156
69618
|
}
|
|
69157
69619
|
if (!isResume) {
|
|
69158
69620
|
if (this.options.pool && settings.recycleWorktrees) {
|
|
@@ -69378,6 +69840,88 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69378
69840
|
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after step-session completion")) {
|
|
69379
69841
|
return;
|
|
69380
69842
|
}
|
|
69843
|
+
if (executionMode !== "fast") {
|
|
69844
|
+
if (settings.testCommand?.trim() || settings.buildCommand?.trim()) {
|
|
69845
|
+
const verificationResult = await this.runExecutorDeterministicVerification(task, worktreePath, settings);
|
|
69846
|
+
if (!verificationResult.allPassed) {
|
|
69847
|
+
const failedType = verificationResult.failedCommand === "testCommand" ? "test" : "build";
|
|
69848
|
+
const failedResult = failedType === "test" ? verificationResult.testResult : verificationResult.buildResult;
|
|
69849
|
+
const failedCommand = failedResult.command;
|
|
69850
|
+
const failureOutput = failedResult.stderr || failedResult.stdout || "Unknown error";
|
|
69851
|
+
const summary = summarizeVerificationOutput(failureOutput, failedType);
|
|
69852
|
+
executorLog.log(`${task.id}: [verification] ${failedType} failed \u2014 attempting fix agent`);
|
|
69853
|
+
await this.store.logEntry(
|
|
69854
|
+
task.id,
|
|
69855
|
+
`[verification] ${failedType} command failed (exit ${failedResult.exitCode}). Attempting fix agent...`,
|
|
69856
|
+
summary,
|
|
69857
|
+
this.currentRunContext
|
|
69858
|
+
);
|
|
69859
|
+
const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
|
|
69860
|
+
if (maxFixRetries === 0) {
|
|
69861
|
+
executorLog.log(`${task.id}: [verification] fix retries set to 0 \u2014 sending task back immediately`);
|
|
69862
|
+
await this.sendTaskBackForFix(
|
|
69863
|
+
task,
|
|
69864
|
+
worktreePath,
|
|
69865
|
+
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}):
|
|
69866
|
+
${summary}`,
|
|
69867
|
+
`Verification (${failedType})`,
|
|
69868
|
+
`Deterministic verification failed (${failedType})`,
|
|
69869
|
+
true,
|
|
69870
|
+
true
|
|
69871
|
+
);
|
|
69872
|
+
return;
|
|
69873
|
+
}
|
|
69874
|
+
let fixSucceeded = false;
|
|
69875
|
+
for (let attempt = 1; attempt <= maxFixRetries; attempt++) {
|
|
69876
|
+
const fixed = await this.attemptExecutorVerificationFix(
|
|
69877
|
+
task,
|
|
69878
|
+
worktreePath,
|
|
69879
|
+
{
|
|
69880
|
+
command: failedCommand,
|
|
69881
|
+
exitCode: failedResult.exitCode,
|
|
69882
|
+
output: failureOutput,
|
|
69883
|
+
type: failedType
|
|
69884
|
+
},
|
|
69885
|
+
settings,
|
|
69886
|
+
attempt,
|
|
69887
|
+
maxFixRetries
|
|
69888
|
+
);
|
|
69889
|
+
if (fixed) {
|
|
69890
|
+
fixSucceeded = true;
|
|
69891
|
+
executorLog.log(`${task.id}: [verification] fix agent succeeded on attempt ${attempt}/${maxFixRetries}`);
|
|
69892
|
+
await this.store.logEntry(
|
|
69893
|
+
task.id,
|
|
69894
|
+
`[verification] Fix agent succeeded on attempt ${attempt}/${maxFixRetries}. Verification now passing.`,
|
|
69895
|
+
void 0,
|
|
69896
|
+
this.currentRunContext
|
|
69897
|
+
);
|
|
69898
|
+
break;
|
|
69899
|
+
}
|
|
69900
|
+
executorLog.log(`${task.id}: [verification] fix agent attempt ${attempt}/${maxFixRetries} failed`);
|
|
69901
|
+
await this.store.logEntry(
|
|
69902
|
+
task.id,
|
|
69903
|
+
`[verification] Fix agent attempt ${attempt}/${maxFixRetries} failed`,
|
|
69904
|
+
void 0,
|
|
69905
|
+
this.currentRunContext
|
|
69906
|
+
);
|
|
69907
|
+
}
|
|
69908
|
+
if (!fixSucceeded) {
|
|
69909
|
+
executorLog.log(`${task.id}: [verification] all fix attempts exhausted (${maxFixRetries}/${maxFixRetries}) \u2014 sending task back`);
|
|
69910
|
+
await this.sendTaskBackForFix(
|
|
69911
|
+
task,
|
|
69912
|
+
worktreePath,
|
|
69913
|
+
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}) after ${maxFixRetries} fix attempts:
|
|
69914
|
+
${summary}`,
|
|
69915
|
+
`Verification (${failedType})`,
|
|
69916
|
+
`Deterministic verification failed after ${maxFixRetries} fix attempts`,
|
|
69917
|
+
true,
|
|
69918
|
+
true
|
|
69919
|
+
);
|
|
69920
|
+
return;
|
|
69921
|
+
}
|
|
69922
|
+
}
|
|
69923
|
+
}
|
|
69924
|
+
}
|
|
69381
69925
|
if (executionMode !== "fast") {
|
|
69382
69926
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
69383
69927
|
if (workflowResult === "deferred-paused") {
|
|
@@ -69466,7 +70010,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69466
70010
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}: ${errorMessage}`);
|
|
69467
70011
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
69468
70012
|
}
|
|
69469
|
-
if (worktreePath &&
|
|
70013
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
69470
70014
|
try {
|
|
69471
70015
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
69472
70016
|
await audit.git({ type: "worktree:remove", target: worktreePath });
|
|
@@ -69525,7 +70069,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69525
70069
|
try {
|
|
69526
70070
|
const latestTask = await this.store.getTask(task.id);
|
|
69527
70071
|
await this.resetStepsIfWorkLost(latestTask);
|
|
69528
|
-
if (worktreePath &&
|
|
70072
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
69529
70073
|
try {
|
|
69530
70074
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
69531
70075
|
} catch (wtErr) {
|
|
@@ -69637,7 +70181,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69637
70181
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
69638
70182
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
69639
70183
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
69640
|
-
const isResuming = !!task.sessionFile &&
|
|
70184
|
+
const isResuming = !!task.sessionFile && existsSync29(task.sessionFile);
|
|
69641
70185
|
const sessionManager = isResuming ? SessionManager2.open(task.sessionFile) : SessionManager2.create(worktreePath);
|
|
69642
70186
|
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
|
|
69643
70187
|
const executorInstructions = await this.resolveInstructionsForRole("executor");
|
|
@@ -69667,7 +70211,13 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69667
70211
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
69668
70212
|
taskId: task.id,
|
|
69669
70213
|
taskTitle: detail.title,
|
|
69670
|
-
onFallbackModelUsed:
|
|
70214
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
70215
|
+
agent: "executor",
|
|
70216
|
+
label: "executor",
|
|
70217
|
+
store: this.store,
|
|
70218
|
+
taskId: task.id,
|
|
70219
|
+
taskTitle: detail.title
|
|
70220
|
+
})
|
|
69671
70221
|
});
|
|
69672
70222
|
if (isResuming) {
|
|
69673
70223
|
executorLog.log(`${task.id}: resumed session from ${task.sessionFile}`);
|
|
@@ -70101,7 +70651,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70101
70651
|
this.options.onComplete?.(task);
|
|
70102
70652
|
} else {
|
|
70103
70653
|
executorLog.log(`${task.id} paused \u2014 moving to todo`);
|
|
70104
|
-
if (worktreePath &&
|
|
70654
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70105
70655
|
try {
|
|
70106
70656
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70107
70657
|
executorLog.log(`Removed old worktree for paused task: ${worktreePath}`);
|
|
@@ -70197,7 +70747,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70197
70747
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}: ${errorMessage}`);
|
|
70198
70748
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
70199
70749
|
}
|
|
70200
|
-
if (worktreePath &&
|
|
70750
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70201
70751
|
try {
|
|
70202
70752
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70203
70753
|
executorLog.log(`Removed old worktree for transient retry: ${worktreePath}`);
|
|
@@ -70252,7 +70802,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70252
70802
|
try {
|
|
70253
70803
|
const latestTask = await this.store.getTask(task.id);
|
|
70254
70804
|
await this.resetStepsIfWorkLost(latestTask);
|
|
70255
|
-
if (worktreePath &&
|
|
70805
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70256
70806
|
try {
|
|
70257
70807
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70258
70808
|
executorLog.log(`Removed old worktree for stuck-killed retry: ${worktreePath}`);
|
|
@@ -70757,7 +71307,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
70757
71307
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
70758
71308
|
*/
|
|
70759
71309
|
async injectWorkflowRevisionInstructions(task, feedback) {
|
|
70760
|
-
const promptPath =
|
|
71310
|
+
const promptPath = join36(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
70761
71311
|
let content;
|
|
70762
71312
|
try {
|
|
70763
71313
|
content = await readFile15(promptPath, "utf-8");
|
|
@@ -70811,6 +71361,217 @@ ${feedback}
|
|
|
70811
71361
|
*
|
|
70812
71362
|
* @returns true if a retry was scheduled, false if retries are exhausted
|
|
70813
71363
|
*/
|
|
71364
|
+
/**
|
|
71365
|
+
* Run deterministic verification (test + build commands) in the task's worktree.
|
|
71366
|
+
* Returns a structured result indicating whether all commands passed.
|
|
71367
|
+
*/
|
|
71368
|
+
async runExecutorDeterministicVerification(task, worktreePath, settings) {
|
|
71369
|
+
const testCommand = settings.testCommand?.trim();
|
|
71370
|
+
const buildCommand2 = settings.buildCommand?.trim();
|
|
71371
|
+
if (!testCommand && !buildCommand2) {
|
|
71372
|
+
executorLog.log(`${task.id}: no test/build commands configured \u2014 skipping verification`);
|
|
71373
|
+
return { allPassed: true };
|
|
71374
|
+
}
|
|
71375
|
+
const parts = [];
|
|
71376
|
+
if (testCommand) parts.push(`test: ${testCommand}`);
|
|
71377
|
+
if (buildCommand2) parts.push(`build: ${buildCommand2}`);
|
|
71378
|
+
executorLog.log(`${task.id}: [verification] running deterministic verification (${parts.join(", ")})`);
|
|
71379
|
+
await this.store.logEntry(
|
|
71380
|
+
task.id,
|
|
71381
|
+
`[verification] Running deterministic verification (${parts.join(", ")})`,
|
|
71382
|
+
void 0,
|
|
71383
|
+
this.currentRunContext
|
|
71384
|
+
);
|
|
71385
|
+
const result = { allPassed: true };
|
|
71386
|
+
if (testCommand) {
|
|
71387
|
+
const testResult = await runVerificationCommand(
|
|
71388
|
+
this.store,
|
|
71389
|
+
worktreePath,
|
|
71390
|
+
task.id,
|
|
71391
|
+
testCommand,
|
|
71392
|
+
"test",
|
|
71393
|
+
void 0,
|
|
71394
|
+
executorLog,
|
|
71395
|
+
"executor"
|
|
71396
|
+
);
|
|
71397
|
+
result.testResult = testResult;
|
|
71398
|
+
if (!testResult.success) {
|
|
71399
|
+
result.allPassed = false;
|
|
71400
|
+
result.failedCommand = "testCommand";
|
|
71401
|
+
executorLog.log(`${task.id}: [verification] test failed (exit ${testResult.exitCode})`);
|
|
71402
|
+
return result;
|
|
71403
|
+
}
|
|
71404
|
+
}
|
|
71405
|
+
if (buildCommand2) {
|
|
71406
|
+
const buildResult = await runVerificationCommand(
|
|
71407
|
+
this.store,
|
|
71408
|
+
worktreePath,
|
|
71409
|
+
task.id,
|
|
71410
|
+
buildCommand2,
|
|
71411
|
+
"build",
|
|
71412
|
+
void 0,
|
|
71413
|
+
executorLog,
|
|
71414
|
+
"executor"
|
|
71415
|
+
);
|
|
71416
|
+
result.buildResult = buildResult;
|
|
71417
|
+
if (!buildResult.success) {
|
|
71418
|
+
result.allPassed = false;
|
|
71419
|
+
result.failedCommand = "buildCommand";
|
|
71420
|
+
executorLog.log(`${task.id}: [verification] build failed (exit ${buildResult.exitCode})`);
|
|
71421
|
+
return result;
|
|
71422
|
+
}
|
|
71423
|
+
}
|
|
71424
|
+
executorLog.log(`${task.id}: [verification] passed`);
|
|
71425
|
+
await this.store.logEntry(
|
|
71426
|
+
task.id,
|
|
71427
|
+
`[verification] Deterministic verification passed`,
|
|
71428
|
+
void 0,
|
|
71429
|
+
this.currentRunContext
|
|
71430
|
+
);
|
|
71431
|
+
return result;
|
|
71432
|
+
}
|
|
71433
|
+
/**
|
|
71434
|
+
* Attempt to fix verification failures by spawning a dedicated AI fix agent.
|
|
71435
|
+
* Follows the pattern established by the merger's attemptInMergeVerificationFix.
|
|
71436
|
+
* Returns true if verification passes after the fix attempt, false otherwise.
|
|
71437
|
+
*/
|
|
71438
|
+
async attemptExecutorVerificationFix(task, worktreePath, failureContext, settings, retryNumber, maxRetries) {
|
|
71439
|
+
try {
|
|
71440
|
+
executorLog.log(`${task.id}: spawning executor verification fix agent (attempt ${retryNumber}/${maxRetries})`);
|
|
71441
|
+
const logger2 = new AgentLogger({
|
|
71442
|
+
store: this.store,
|
|
71443
|
+
taskId: task.id,
|
|
71444
|
+
agent: "executor",
|
|
71445
|
+
persistAgentToolOutput: settings.persistAgentToolOutput,
|
|
71446
|
+
onAgentText: this.options.onAgentText,
|
|
71447
|
+
onAgentTool: this.options.onAgentTool
|
|
71448
|
+
});
|
|
71449
|
+
let skillContext;
|
|
71450
|
+
if (this.options.agentStore) {
|
|
71451
|
+
try {
|
|
71452
|
+
skillContext = await buildSessionSkillContext({
|
|
71453
|
+
agentStore: this.options.agentStore,
|
|
71454
|
+
task,
|
|
71455
|
+
sessionPurpose: "executor",
|
|
71456
|
+
projectRootDir: worktreePath,
|
|
71457
|
+
pluginRunner: this.options.pluginRunner
|
|
71458
|
+
});
|
|
71459
|
+
} catch {
|
|
71460
|
+
}
|
|
71461
|
+
}
|
|
71462
|
+
const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair2(
|
|
71463
|
+
task.modelProvider,
|
|
71464
|
+
task.modelId,
|
|
71465
|
+
settings
|
|
71466
|
+
);
|
|
71467
|
+
const { session } = await createResolvedAgentSession({
|
|
71468
|
+
sessionPurpose: "executor",
|
|
71469
|
+
pluginRunner: this.options.pluginRunner,
|
|
71470
|
+
cwd: worktreePath,
|
|
71471
|
+
// Run in the task's worktree
|
|
71472
|
+
systemPrompt: `You are a verification fix agent running during task execution in a worktree.
|
|
71473
|
+
|
|
71474
|
+
All step-session steps completed successfully but the deterministic verification command failed. Your job is to fix the failing code directly in the working directory.
|
|
71475
|
+
|
|
71476
|
+
## Scope
|
|
71477
|
+
Only fix what is required to make the failing verification pass.
|
|
71478
|
+
Do not refactor, rename broadly, or make opportunistic improvements.
|
|
71479
|
+
|
|
71480
|
+
## Rules
|
|
71481
|
+
1. Read the error output carefully to understand what is failing before editing anything
|
|
71482
|
+
2. Before assuming a code fix is needed, check whether the failure is caused by stale/missing build artifacts in a sibling workspace package \u2014 typical signatures: \`Failed to resolve import "./X.js"\` pointing into another package's \`dist/\`, \`Cannot find module\`, or \`ERR_MODULE_NOT_FOUND\` referencing a workspace-internal path. In that case, rebuild the affected package(s) (e.g. \`pnpm --filter <pkg> build\`, or \`pnpm --filter "<scope>/*" build\` for a group) and re-run verification before editing source files.
|
|
71483
|
+
3. Make targeted fixes to the failing code path
|
|
71484
|
+
4. After fixing, run the verification command to confirm the fix works
|
|
71485
|
+
5. Do NOT make any git commits \u2014 just fix the code
|
|
71486
|
+
6. You MAY modify any files needed to make the verification pass, including files unrelated to this task's original change. Pre-existing build/test breakage is in scope: fix it. Prefer the smallest change that makes verification green.
|
|
71487
|
+
7. If you cannot fix the issue within scope, explain why and what evidence indicates a deeper/root problem`,
|
|
71488
|
+
tools: "coding",
|
|
71489
|
+
onText: logger2.onText,
|
|
71490
|
+
onThinking: logger2.onThinking,
|
|
71491
|
+
onToolStart: logger2.onToolStart,
|
|
71492
|
+
onToolEnd: logger2.onToolEnd,
|
|
71493
|
+
defaultProvider: executorProvider,
|
|
71494
|
+
defaultModelId: executorModelId,
|
|
71495
|
+
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
71496
|
+
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
71497
|
+
});
|
|
71498
|
+
await this.store.logEntry(
|
|
71499
|
+
task.id,
|
|
71500
|
+
`Executor verification fix agent started (model: ${describeModel(session)}, attempt ${retryNumber}/${maxRetries})`,
|
|
71501
|
+
void 0,
|
|
71502
|
+
this.currentRunContext
|
|
71503
|
+
);
|
|
71504
|
+
await this.store.appendAgentLog(
|
|
71505
|
+
task.id,
|
|
71506
|
+
`Fix agent started (model: ${describeModel(session)}, attempt ${retryNumber}/${maxRetries})`,
|
|
71507
|
+
"text",
|
|
71508
|
+
void 0,
|
|
71509
|
+
"executor"
|
|
71510
|
+
);
|
|
71511
|
+
try {
|
|
71512
|
+
const fixPrompt = `Fix the failing ${failureContext.type} verification for task ${task.id}.
|
|
71513
|
+
|
|
71514
|
+
## Failed command
|
|
71515
|
+
Command: \`${failureContext.command}\`
|
|
71516
|
+
Exit code: ${failureContext.exitCode}
|
|
71517
|
+
|
|
71518
|
+
## Error output
|
|
71519
|
+
${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
71520
|
+
|
|
71521
|
+
## Instructions
|
|
71522
|
+
1. Read the error output and identify the root cause
|
|
71523
|
+
2. Make targeted fixes to resolve the failure
|
|
71524
|
+
3. Run the verification command \`${failureContext.command}\` to confirm your fix works
|
|
71525
|
+
4. If the fix doesn't work, try a different approach
|
|
71526
|
+
5. Do NOT make any git commits`;
|
|
71527
|
+
await withRateLimitRetry(async () => {
|
|
71528
|
+
await promptWithFallback(session, fixPrompt);
|
|
71529
|
+
}, {
|
|
71530
|
+
onRetry: (attempt, delayMs, error) => {
|
|
71531
|
+
const delaySec = Math.round(delayMs / 1e3);
|
|
71532
|
+
executorLog.warn(`\u23F3 ${task.id} executor fix agent rate limited \u2014 retry ${attempt} in ${delaySec}s: ${error.message}`);
|
|
71533
|
+
}
|
|
71534
|
+
});
|
|
71535
|
+
await accumulateSessionTokenUsage(this.store, task.id, session);
|
|
71536
|
+
executorLog.log(`${task.id}: re-running deterministic verification after fix attempt ${retryNumber}/${maxRetries}`);
|
|
71537
|
+
await this.store.logEntry(
|
|
71538
|
+
task.id,
|
|
71539
|
+
`Re-running deterministic verification (attempt ${retryNumber}/${maxRetries})`,
|
|
71540
|
+
void 0,
|
|
71541
|
+
this.currentRunContext
|
|
71542
|
+
);
|
|
71543
|
+
await this.store.appendAgentLog(
|
|
71544
|
+
task.id,
|
|
71545
|
+
`Re-running verification (attempt ${retryNumber}/${maxRetries})`,
|
|
71546
|
+
"text",
|
|
71547
|
+
void 0,
|
|
71548
|
+
"executor"
|
|
71549
|
+
);
|
|
71550
|
+
const reRunResult = await this.runExecutorDeterministicVerification(task, worktreePath, settings);
|
|
71551
|
+
return reRunResult.allPassed;
|
|
71552
|
+
} finally {
|
|
71553
|
+
await logger2.flush();
|
|
71554
|
+
await session.dispose();
|
|
71555
|
+
}
|
|
71556
|
+
} catch (err) {
|
|
71557
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
71558
|
+
executorLog.warn(`${task.id}: executor verification fix agent error: ${errorMessage}`);
|
|
71559
|
+
await this.store.logEntry(
|
|
71560
|
+
task.id,
|
|
71561
|
+
`Executor verification fix agent encountered an error`,
|
|
71562
|
+
errorMessage,
|
|
71563
|
+
this.currentRunContext
|
|
71564
|
+
);
|
|
71565
|
+
await this.store.appendAgentLog(
|
|
71566
|
+
task.id,
|
|
71567
|
+
"Fix agent encountered an error",
|
|
71568
|
+
"tool_error",
|
|
71569
|
+
errorMessage,
|
|
71570
|
+
"executor"
|
|
71571
|
+
);
|
|
71572
|
+
return false;
|
|
71573
|
+
}
|
|
71574
|
+
}
|
|
70814
71575
|
async handleWorkflowStepFailure(task, worktreePath, failureFeedback, stepName) {
|
|
70815
71576
|
this.clearCompletedTaskWatchdog(task.id);
|
|
70816
71577
|
const currentRetries = task.workflowStepRetries ?? 0;
|
|
@@ -70843,7 +71604,7 @@ ${feedback}
|
|
|
70843
71604
|
* Injects failure feedback into PROMPT.md, resets steps, clears session,
|
|
70844
71605
|
* and schedules a move to todo → in-progress after the executing guard clears.
|
|
70845
71606
|
*/
|
|
70846
|
-
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true) {
|
|
71607
|
+
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false) {
|
|
70847
71608
|
const taskId = task.id;
|
|
70848
71609
|
this.clearCompletedTaskWatchdog(taskId);
|
|
70849
71610
|
await this.store.addTaskComment(
|
|
@@ -70862,7 +71623,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
70862
71623
|
const updatedTask = await this.store.getTask(taskId);
|
|
70863
71624
|
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
70864
71625
|
await this.store.updateTask(taskId, {
|
|
70865
|
-
status: null,
|
|
71626
|
+
status: mergeVerificationFailure ? "merging-fix" : null,
|
|
70866
71627
|
error: null,
|
|
70867
71628
|
sessionFile: null,
|
|
70868
71629
|
workflowStepRetries: 0
|
|
@@ -70880,7 +71641,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
70880
71641
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
70881
71642
|
*/
|
|
70882
71643
|
async injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount) {
|
|
70883
|
-
const promptPath =
|
|
71644
|
+
const promptPath = join36(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
70884
71645
|
let content;
|
|
70885
71646
|
try {
|
|
70886
71647
|
content = await readFile15(promptPath, "utf-8");
|
|
@@ -70945,31 +71706,33 @@ ${failureFeedback}
|
|
|
70945
71706
|
* Uses git diff against the stored baseCommitSha to determine what changed.
|
|
70946
71707
|
* Returns an empty array if no changes or if git commands fail.
|
|
70947
71708
|
*/
|
|
71709
|
+
async resolveDiffBaseRef(worktreePath, baseCommitSha) {
|
|
71710
|
+
if (baseCommitSha) return baseCommitSha;
|
|
71711
|
+
try {
|
|
71712
|
+
const { stdout } = await execAsync5(
|
|
71713
|
+
"git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main",
|
|
71714
|
+
{ cwd: worktreePath, encoding: "utf-8" }
|
|
71715
|
+
);
|
|
71716
|
+
const ref = stdout.trim();
|
|
71717
|
+
if (ref) return ref;
|
|
71718
|
+
} catch (mergeBaseErr) {
|
|
71719
|
+
const mergeBaseMsg = mergeBaseErr instanceof Error ? mergeBaseErr.message : String(mergeBaseErr);
|
|
71720
|
+
executorLog.warn(`Failed merge-base lookup for diff base in ${worktreePath}, trying HEAD~1 fallback: ${mergeBaseMsg}`);
|
|
71721
|
+
}
|
|
71722
|
+
try {
|
|
71723
|
+
const { stdout } = await execAsync5("git rev-parse HEAD~1", {
|
|
71724
|
+
cwd: worktreePath,
|
|
71725
|
+
encoding: "utf-8"
|
|
71726
|
+
});
|
|
71727
|
+
return stdout.trim() || void 0;
|
|
71728
|
+
} catch {
|
|
71729
|
+
executorLog.log(`Could not determine base commit for diff in ${worktreePath}`);
|
|
71730
|
+
return void 0;
|
|
71731
|
+
}
|
|
71732
|
+
}
|
|
70948
71733
|
async captureModifiedFiles(worktreePath, baseCommitSha) {
|
|
70949
71734
|
try {
|
|
70950
|
-
|
|
70951
|
-
if (!baseRef) {
|
|
70952
|
-
try {
|
|
70953
|
-
const { stdout: stdout2 } = await execAsync5("git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main", {
|
|
70954
|
-
cwd: worktreePath,
|
|
70955
|
-
encoding: "utf-8"
|
|
70956
|
-
});
|
|
70957
|
-
baseRef = stdout2.trim();
|
|
70958
|
-
} catch (mergeBaseErr) {
|
|
70959
|
-
const mergeBaseMsg = mergeBaseErr instanceof Error ? mergeBaseErr.message : String(mergeBaseErr);
|
|
70960
|
-
executorLog.warn(`Failed merge-base lookup for diff base in ${worktreePath}, trying HEAD~1 fallback: ${mergeBaseMsg}`);
|
|
70961
|
-
try {
|
|
70962
|
-
const { stdout: stdout2 } = await execAsync5("git rev-parse HEAD~1", {
|
|
70963
|
-
cwd: worktreePath,
|
|
70964
|
-
encoding: "utf-8"
|
|
70965
|
-
});
|
|
70966
|
-
baseRef = stdout2.trim();
|
|
70967
|
-
} catch {
|
|
70968
|
-
executorLog.log(`Could not determine base commit for diff in ${worktreePath}`);
|
|
70969
|
-
return [];
|
|
70970
|
-
}
|
|
70971
|
-
}
|
|
70972
|
-
}
|
|
71735
|
+
const baseRef = await this.resolveDiffBaseRef(worktreePath, baseCommitSha);
|
|
70973
71736
|
if (!baseRef) {
|
|
70974
71737
|
return [];
|
|
70975
71738
|
}
|
|
@@ -71205,6 +71968,30 @@ ${failureFeedback}
|
|
|
71205
71968
|
*/
|
|
71206
71969
|
async executeWorkflowStep(task, workflowStep, worktreePath, settings) {
|
|
71207
71970
|
const toolMode = workflowStep.toolMode || "readonly";
|
|
71971
|
+
const scopedFiles = await this.captureModifiedFiles(worktreePath, task.baseCommitSha);
|
|
71972
|
+
let diffShortstat;
|
|
71973
|
+
try {
|
|
71974
|
+
const baseRef = await this.resolveDiffBaseRef(worktreePath, task.baseCommitSha);
|
|
71975
|
+
if (baseRef) {
|
|
71976
|
+
const { stdout } = await execAsync5(`git diff --shortstat ${baseRef}..HEAD`, {
|
|
71977
|
+
cwd: worktreePath,
|
|
71978
|
+
encoding: "utf-8"
|
|
71979
|
+
});
|
|
71980
|
+
diffShortstat = stdout.trim() || void 0;
|
|
71981
|
+
}
|
|
71982
|
+
} catch {
|
|
71983
|
+
}
|
|
71984
|
+
const MAX_SCOPE_FILES = 100;
|
|
71985
|
+
const scopeFileBlock = scopedFiles.length === 0 ? "(no modified files detected for this task \u2014 review the worktree directly, but do NOT browse unrelated files)" : scopedFiles.length > MAX_SCOPE_FILES ? `${scopedFiles.slice(0, MAX_SCOPE_FILES).map((f) => `- ${f}`).join("\n")}
|
|
71986
|
+
- ... (${scopedFiles.length - MAX_SCOPE_FILES} more files truncated)` : scopedFiles.map((f) => `- ${f}`).join("\n");
|
|
71987
|
+
const scopeBlock = `Diff Scope (files changed by THIS task vs base):
|
|
71988
|
+
${scopeFileBlock}${diffShortstat ? `
|
|
71989
|
+
Diff stat: ${diffShortstat}` : ""}
|
|
71990
|
+
|
|
71991
|
+
CRITICAL SCOPING RULES \u2014 read before doing anything else:
|
|
71992
|
+
- Review ONLY the files listed above. Do NOT analyze unmodified files or unrelated parts of the codebase.
|
|
71993
|
+
- If NONE of the files in the diff scope are relevant to your review category (e.g. a UX/design reviewer with no UI/CSS/component files in scope, a security reviewer with no auth/network code in scope, an a11y reviewer with no markup changes), respond IMMEDIATELY with a single short approval line such as "No relevant changes in scope \u2014 approved." and STOP. Do not start exploring the codebase.
|
|
71994
|
+
- Your wall-clock budget is short. Spending it browsing unmodified files will cause this step to time out and block merge.`;
|
|
71208
71995
|
const systemPrompt = `You are a workflow step agent executing: ${workflowStep.name}
|
|
71209
71996
|
|
|
71210
71997
|
Task Context:
|
|
@@ -71212,6 +71999,8 @@ Task Context:
|
|
|
71212
71999
|
- Task Description: ${task.description}
|
|
71213
72000
|
- Worktree: ${worktreePath}
|
|
71214
72001
|
|
|
72002
|
+
${scopeBlock}
|
|
72003
|
+
|
|
71215
72004
|
Your role:
|
|
71216
72005
|
- Execute this workflow step exactly as scoped.
|
|
71217
72006
|
- Prioritize high-impact correctness/risk findings over stylistic nits.
|
|
@@ -71680,7 +72469,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71680
72469
|
* rather than fail the task permanently.
|
|
71681
72470
|
*/
|
|
71682
72471
|
async resolveWorktreeStartPoint(startPoint, taskId) {
|
|
71683
|
-
const command = isAbsolute11(startPoint) &&
|
|
72472
|
+
const command = isAbsolute11(startPoint) && existsSync29(startPoint) ? `git -C "${startPoint}" rev-parse --verify HEAD^{commit}` : `git rev-parse --verify "${startPoint}^{commit}"`;
|
|
71684
72473
|
try {
|
|
71685
72474
|
const { stdout } = await execAsync5(command, { cwd: this.rootDir });
|
|
71686
72475
|
return stdout.trim() || startPoint;
|
|
@@ -71700,7 +72489,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71700
72489
|
*/
|
|
71701
72490
|
async tryCreateWorktree(branch, path2, taskId, startPoint, attemptNumber = 0, recoveryDepth = 0) {
|
|
71702
72491
|
await this.assertWorktreePathNotNested(path2, taskId);
|
|
71703
|
-
if (
|
|
72492
|
+
if (existsSync29(path2)) {
|
|
71704
72493
|
const isRegistered = await this.isRegisteredWorktree(path2);
|
|
71705
72494
|
if (!isRegistered) {
|
|
71706
72495
|
await this.store.logEntry(
|
|
@@ -71851,7 +72640,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71851
72640
|
);
|
|
71852
72641
|
if (shouldGenerateNewName) {
|
|
71853
72642
|
const conflictStartPoint = branch;
|
|
71854
|
-
const newPath =
|
|
72643
|
+
const newPath = join36(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
71855
72644
|
for (let suffix = 2; suffix <= 6; suffix++) {
|
|
71856
72645
|
const suffixedBranch = `${branch}-${suffix}`;
|
|
71857
72646
|
try {
|
|
@@ -72451,7 +73240,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
72451
73240
|
metadata: { type: "spawned", parentTaskId: taskId }
|
|
72452
73241
|
});
|
|
72453
73242
|
const childWorktreeName = generateWorktreeName(this.rootDir);
|
|
72454
|
-
const childWorktreePath =
|
|
73243
|
+
const childWorktreePath = join36(this.rootDir, ".worktrees", childWorktreeName);
|
|
72455
73244
|
const childBranch = `fusion/spawn-${agent.id}`;
|
|
72456
73245
|
await this.createWorktree(childBranch, childWorktreePath, taskId, worktreePath);
|
|
72457
73246
|
await this.options.agentStore.updateAgentState(agent.id, "active");
|
|
@@ -72640,9 +73429,9 @@ var init_node_routing_policy = __esm({
|
|
|
72640
73429
|
});
|
|
72641
73430
|
|
|
72642
73431
|
// ../engine/src/scheduler.ts
|
|
72643
|
-
import { existsSync as
|
|
73432
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
72644
73433
|
import { readFile as readFile16 } from "node:fs/promises";
|
|
72645
|
-
import { basename as basename8, join as
|
|
73434
|
+
import { basename as basename8, join as join37 } from "node:path";
|
|
72646
73435
|
function pathsOverlap2(a, b) {
|
|
72647
73436
|
for (const pa of a) {
|
|
72648
73437
|
const prefixA = pa.endsWith("/*") ? pa.slice(0, -1) : null;
|
|
@@ -72812,12 +73601,12 @@ var init_scheduler = __esm({
|
|
|
72812
73601
|
* @returns Object with `valid: true` if checks pass, or `valid: false` with a `reason` string if they fail
|
|
72813
73602
|
*/
|
|
72814
73603
|
async validateTaskFilesystem(id) {
|
|
72815
|
-
const taskDir =
|
|
72816
|
-
if (!
|
|
73604
|
+
const taskDir = join37(this.store.getTasksDir(), id);
|
|
73605
|
+
if (!existsSync30(taskDir)) {
|
|
72817
73606
|
return { valid: false, reason: "missing directory" };
|
|
72818
73607
|
}
|
|
72819
|
-
const promptPath =
|
|
72820
|
-
if (!
|
|
73608
|
+
const promptPath = join37(taskDir, "PROMPT.md");
|
|
73609
|
+
if (!existsSync30(promptPath)) {
|
|
72821
73610
|
return { valid: false, reason: "missing or empty PROMPT.md" };
|
|
72822
73611
|
}
|
|
72823
73612
|
try {
|
|
@@ -72956,7 +73745,7 @@ var init_scheduler = __esm({
|
|
|
72956
73745
|
break;
|
|
72957
73746
|
}
|
|
72958
73747
|
reservedNames.add(worktreeName);
|
|
72959
|
-
return
|
|
73748
|
+
return join37(this.store.getRootDir(), ".worktrees", worktreeName);
|
|
72960
73749
|
}
|
|
72961
73750
|
/**
|
|
72962
73751
|
* Run one scheduling pass.
|
|
@@ -74233,7 +75022,7 @@ var init_mission_execution_loop = __esm({
|
|
|
74233
75022
|
init_pi();
|
|
74234
75023
|
init_agent_session_helpers();
|
|
74235
75024
|
init_logger2();
|
|
74236
|
-
|
|
75025
|
+
init_fallback_model_observer();
|
|
74237
75026
|
loopLog = createLogger2("mission-loop");
|
|
74238
75027
|
VALIDATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
74239
75028
|
MissionExecutionLoop = class extends EventEmitter17 {
|
|
@@ -74467,7 +75256,13 @@ Assertions: ${assertions.map((a) => a.title).join(", ")}`,
|
|
|
74467
75256
|
},
|
|
74468
75257
|
taskId: task?.id,
|
|
74469
75258
|
taskTitle: task?.title,
|
|
74470
|
-
onFallbackModelUsed:
|
|
75259
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
75260
|
+
agent: "reviewer",
|
|
75261
|
+
label: "mission validator",
|
|
75262
|
+
store: this.taskStore,
|
|
75263
|
+
taskId: task?.id,
|
|
75264
|
+
taskTitle: task?.title
|
|
75265
|
+
})
|
|
74471
75266
|
});
|
|
74472
75267
|
session = { session: sessionResult.session, sessionFile: sessionResult.sessionFile };
|
|
74473
75268
|
loopLog.log(`Validation session created for feature ${feature.id}`);
|
|
@@ -77732,7 +78527,7 @@ async function createAiPromptExecutor(cwd) {
|
|
|
77732
78527
|
}
|
|
77733
78528
|
};
|
|
77734
78529
|
}
|
|
77735
|
-
function
|
|
78530
|
+
function truncateOutput2(stdout, stderr) {
|
|
77736
78531
|
const out = stdout ?? "";
|
|
77737
78532
|
const err = stderr ?? "";
|
|
77738
78533
|
let combined = out;
|
|
@@ -77912,7 +78707,7 @@ var init_cron_runner = __esm({
|
|
|
77912
78707
|
maxBuffer: MAX_BUFFER,
|
|
77913
78708
|
shell: defaultShell
|
|
77914
78709
|
});
|
|
77915
|
-
const output =
|
|
78710
|
+
const output = truncateOutput2(stdout, stderr);
|
|
77916
78711
|
log15.log(`\u2713 ${schedule.name} completed (${output.length} bytes output)`);
|
|
77917
78712
|
return {
|
|
77918
78713
|
success: true,
|
|
@@ -77923,7 +78718,7 @@ var init_cron_runner = __esm({
|
|
|
77923
78718
|
} catch (err) {
|
|
77924
78719
|
const stdout = err.stdout ?? "";
|
|
77925
78720
|
const stderr = err.stderr ?? "";
|
|
77926
|
-
const output =
|
|
78721
|
+
const output = truncateOutput2(stdout, stderr);
|
|
77927
78722
|
const errorMessage = err.killed ? `Command timed out after ${(schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6) / 1e3}s` : err.message ?? String(err);
|
|
77928
78723
|
log15.warn(`\u2717 ${schedule.name} failed: ${errorMessage}`);
|
|
77929
78724
|
return {
|
|
@@ -77968,7 +78763,7 @@ var init_cron_runner = __esm({
|
|
|
77968
78763
|
const result = await runBackupCommand2(fusionDir, settings);
|
|
77969
78764
|
return {
|
|
77970
78765
|
success: result.success,
|
|
77971
|
-
output:
|
|
78766
|
+
output: truncateOutput2(result.output ?? "", ""),
|
|
77972
78767
|
error: result.success ? void 0 : result.output
|
|
77973
78768
|
};
|
|
77974
78769
|
} catch (err) {
|
|
@@ -78009,7 +78804,7 @@ var init_cron_runner = __esm({
|
|
|
78009
78804
|
if (sr.output) outputParts.push(sr.output);
|
|
78010
78805
|
if (sr.error) outputParts.push(`Error: ${sr.error}`);
|
|
78011
78806
|
}
|
|
78012
|
-
const output =
|
|
78807
|
+
const output = truncateOutput2(outputParts.join("\n"), "");
|
|
78013
78808
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
78014
78809
|
const error = failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0;
|
|
78015
78810
|
const status = overallSuccess ? "\u2713" : "\u2717";
|
|
@@ -78088,7 +78883,7 @@ var init_cron_runner = __esm({
|
|
|
78088
78883
|
stepName: step.name,
|
|
78089
78884
|
stepIndex,
|
|
78090
78885
|
success: true,
|
|
78091
|
-
output:
|
|
78886
|
+
output: truncateOutput2(stdout, stderr),
|
|
78092
78887
|
startedAt,
|
|
78093
78888
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78094
78889
|
};
|
|
@@ -78101,7 +78896,7 @@ var init_cron_runner = __esm({
|
|
|
78101
78896
|
stepName: step.name,
|
|
78102
78897
|
stepIndex,
|
|
78103
78898
|
success: false,
|
|
78104
|
-
output:
|
|
78899
|
+
output: truncateOutput2(stdout, stderr),
|
|
78105
78900
|
error: errorMessage,
|
|
78106
78901
|
startedAt,
|
|
78107
78902
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78251,7 +79046,7 @@ var init_cron_runner = __esm({
|
|
|
78251
79046
|
// ../engine/src/routine-runner.ts
|
|
78252
79047
|
import { exec as exec8 } from "node:child_process";
|
|
78253
79048
|
import { promisify as promisify8 } from "node:util";
|
|
78254
|
-
function
|
|
79049
|
+
function truncateOutput3(stdout, stderr) {
|
|
78255
79050
|
let output = stdout;
|
|
78256
79051
|
if (stderr) {
|
|
78257
79052
|
output += stdout ? "\n--- stderr ---\n" : "";
|
|
@@ -78434,7 +79229,7 @@ var init_routine_runner = __esm({
|
|
|
78434
79229
|
const result = await runBackupCommand2(fusionDir, settings);
|
|
78435
79230
|
return {
|
|
78436
79231
|
success: result.success,
|
|
78437
|
-
output:
|
|
79232
|
+
output: truncateOutput3(result.output ?? "", ""),
|
|
78438
79233
|
error: result.success ? void 0 : result.output,
|
|
78439
79234
|
startedAt,
|
|
78440
79235
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78458,7 +79253,7 @@ var init_routine_runner = __esm({
|
|
|
78458
79253
|
});
|
|
78459
79254
|
return {
|
|
78460
79255
|
success: true,
|
|
78461
|
-
output:
|
|
79256
|
+
output: truncateOutput3(stdout, stderr),
|
|
78462
79257
|
startedAt,
|
|
78463
79258
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78464
79259
|
};
|
|
@@ -78469,7 +79264,7 @@ var init_routine_runner = __esm({
|
|
|
78469
79264
|
const error = errObj.killed === true ? `Command timed out after ${(timeoutMs ?? DEFAULT_TIMEOUT_MS7) / 1e3}s` : (err instanceof Error ? err.message : null) ?? String(err);
|
|
78470
79265
|
return {
|
|
78471
79266
|
success: false,
|
|
78472
|
-
output:
|
|
79267
|
+
output: truncateOutput3(stdout, stderr),
|
|
78473
79268
|
error,
|
|
78474
79269
|
startedAt,
|
|
78475
79270
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78502,7 +79297,7 @@ var init_routine_runner = __esm({
|
|
|
78502
79297
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
78503
79298
|
return {
|
|
78504
79299
|
success: overallSuccess,
|
|
78505
|
-
output:
|
|
79300
|
+
output: truncateOutput3(outputParts.join("\n"), ""),
|
|
78506
79301
|
error: failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0,
|
|
78507
79302
|
startedAt,
|
|
78508
79303
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -78537,7 +79332,7 @@ var init_routine_runner = __esm({
|
|
|
78537
79332
|
this.options.aiPromptExecutor(step.prompt, step.modelProvider, step.modelId),
|
|
78538
79333
|
new Promise((_resolve, reject) => setTimeout(() => reject(new Error(`AI prompt step timed out after ${timeoutMs / 1e3}s`)), timeoutMs))
|
|
78539
79334
|
]);
|
|
78540
|
-
return { stepId: step.id, stepName: step.name, stepIndex, success: true, output:
|
|
79335
|
+
return { stepId: step.id, stepName: step.name, stepIndex, success: true, output: truncateOutput3(output, ""), startedAt, completedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
78541
79336
|
} catch (err) {
|
|
78542
79337
|
return { stepId: step.id, stepName: step.name, stepIndex, success: false, output: "", error: err instanceof Error ? err.message : String(err), startedAt, completedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
78543
79338
|
}
|
|
@@ -79151,8 +79946,8 @@ var init_stuck_task_detector = __esm({
|
|
|
79151
79946
|
// ../engine/src/self-healing.ts
|
|
79152
79947
|
import { exec as exec9 } from "node:child_process";
|
|
79153
79948
|
import { promisify as promisify9 } from "node:util";
|
|
79154
|
-
import { existsSync as
|
|
79155
|
-
import { isAbsolute as isAbsolute13, join as
|
|
79949
|
+
import { existsSync as existsSync31, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
|
|
79950
|
+
import { isAbsolute as isAbsolute13, join as join38, relative as relative8, resolve as resolve17 } from "node:path";
|
|
79156
79951
|
function shellQuote(value) {
|
|
79157
79952
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
79158
79953
|
}
|
|
@@ -79197,14 +79992,15 @@ var init_self_healing = __esm({
|
|
|
79197
79992
|
execAsync7 = promisify9(exec9);
|
|
79198
79993
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
79199
79994
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
79200
|
-
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
79995
|
+
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
79201
79996
|
NON_TERMINAL_STEP_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in-progress"]);
|
|
79202
79997
|
GHOST_REVIEW_PRESERVED_STATUSES = /* @__PURE__ */ new Set([
|
|
79203
79998
|
"failed",
|
|
79204
79999
|
"awaiting-user-review",
|
|
79205
80000
|
"awaiting-approval",
|
|
79206
80001
|
"merging",
|
|
79207
|
-
"merging-pr"
|
|
80002
|
+
"merging-pr",
|
|
80003
|
+
"merging-fix"
|
|
79208
80004
|
]);
|
|
79209
80005
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
79210
80006
|
MAX_TASK_DONE_RETRIES = 3;
|
|
@@ -79548,7 +80344,7 @@ var init_self_healing = __esm({
|
|
|
79548
80344
|
return commit;
|
|
79549
80345
|
}
|
|
79550
80346
|
async cleanupInterruptedMergeArtifacts(task) {
|
|
79551
|
-
if (task.worktree &&
|
|
80347
|
+
if (task.worktree && existsSync31(task.worktree)) {
|
|
79552
80348
|
try {
|
|
79553
80349
|
await execAsync7(`git worktree remove ${shellQuote(task.worktree)} --force`, {
|
|
79554
80350
|
cwd: this.options.rootDir,
|
|
@@ -79927,7 +80723,7 @@ var init_self_healing = __esm({
|
|
|
79927
80723
|
*
|
|
79928
80724
|
* Preserved statuses (skipped):
|
|
79929
80725
|
* - `awaiting-user-review`, `awaiting-approval`: explicit human handoff
|
|
79930
|
-
* - `merging`, `merging-pr`: handled by `recoverInterruptedMergingTasks`
|
|
80726
|
+
* - `merging`, `merging-pr`, `merging-fix`: handled by `recoverInterruptedMergingTasks`
|
|
79931
80727
|
*
|
|
79932
80728
|
* Rate-limiting comes from the `updatedAt >= taskStuckTimeoutMs` gate —
|
|
79933
80729
|
* each kick refreshes `updatedAt`, so a task that re-enters review and gets
|
|
@@ -80169,7 +80965,7 @@ var init_self_healing = __esm({
|
|
|
80169
80965
|
return false;
|
|
80170
80966
|
}
|
|
80171
80967
|
const staleness = now - new Date(t.updatedAt).getTime();
|
|
80172
|
-
const hasWorktree = t.worktree &&
|
|
80968
|
+
const hasWorktree = t.worktree && existsSync31(t.worktree);
|
|
80173
80969
|
const graceMs = hasWorktree ? ORPHANED_WITH_WORKTREE_GRACE_MS : ORPHANED_EXECUTION_RECOVERY_GRACE_MS;
|
|
80174
80970
|
return staleness >= graceMs;
|
|
80175
80971
|
});
|
|
@@ -80178,7 +80974,7 @@ var init_self_healing = __esm({
|
|
|
80178
80974
|
let recovered = 0;
|
|
80179
80975
|
for (const task of orphaned) {
|
|
80180
80976
|
try {
|
|
80181
|
-
const hadWorktree = task.worktree &&
|
|
80977
|
+
const hadWorktree = task.worktree && existsSync31(task.worktree);
|
|
80182
80978
|
const reason = hadWorktree ? "worktree exists but no active session" : "missing worktree/session";
|
|
80183
80979
|
await this.resetStepsIfWorkLost(task);
|
|
80184
80980
|
await this.store.updateTask(task.id, {
|
|
@@ -80324,7 +81120,7 @@ var init_self_healing = __esm({
|
|
|
80324
81120
|
}
|
|
80325
81121
|
}
|
|
80326
81122
|
async hasRecoverableGitWork(task) {
|
|
80327
|
-
if (task.worktree &&
|
|
81123
|
+
if (task.worktree && existsSync31(task.worktree)) {
|
|
80328
81124
|
try {
|
|
80329
81125
|
const { stdout: status } = await execAsync7("git status --porcelain", {
|
|
80330
81126
|
cwd: task.worktree,
|
|
@@ -80509,11 +81305,11 @@ var init_self_healing = __esm({
|
|
|
80509
81305
|
* tracks registered idle worktrees, never these orphans.
|
|
80510
81306
|
*/
|
|
80511
81307
|
async reapUnregisteredOrphans() {
|
|
80512
|
-
const worktreesDir =
|
|
80513
|
-
if (!
|
|
81308
|
+
const worktreesDir = join38(this.options.rootDir, ".worktrees");
|
|
81309
|
+
if (!existsSync31(worktreesDir)) return 0;
|
|
80514
81310
|
let dirs;
|
|
80515
81311
|
try {
|
|
80516
|
-
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
81312
|
+
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join38(worktreesDir, e.name));
|
|
80517
81313
|
} catch (err) {
|
|
80518
81314
|
log17.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
|
|
80519
81315
|
return 0;
|
|
@@ -80618,8 +81414,8 @@ var init_self_healing = __esm({
|
|
|
80618
81414
|
}
|
|
80619
81415
|
/** Remove oldest idle worktrees if total count exceeds 2× maxWorktrees. */
|
|
80620
81416
|
async enforceWorktreeCap() {
|
|
80621
|
-
const worktreesDir =
|
|
80622
|
-
if (!
|
|
81417
|
+
const worktreesDir = join38(this.options.rootDir, ".worktrees");
|
|
81418
|
+
if (!existsSync31(worktreesDir)) return;
|
|
80623
81419
|
try {
|
|
80624
81420
|
const settings = await this.store.getSettings();
|
|
80625
81421
|
const cap = (settings.maxWorktrees ?? 4) * 2;
|
|
@@ -82457,7 +83253,7 @@ var init_ipc_host = __esm({
|
|
|
82457
83253
|
import { EventEmitter as EventEmitter20 } from "node:events";
|
|
82458
83254
|
import { fork } from "node:child_process";
|
|
82459
83255
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
82460
|
-
import { dirname as dirname11, join as
|
|
83256
|
+
import { dirname as dirname11, join as join39 } from "node:path";
|
|
82461
83257
|
var HealthMonitor, ChildProcessRuntime;
|
|
82462
83258
|
var init_child_process_runtime = __esm({
|
|
82463
83259
|
"../engine/src/runtimes/child-process-runtime.ts"() {
|
|
@@ -82619,7 +83415,7 @@ var init_child_process_runtime = __esm({
|
|
|
82619
83415
|
const isCompiled = !import.meta.url.endsWith(".ts");
|
|
82620
83416
|
const currentDir = dirname11(fileURLToPath3(import.meta.url));
|
|
82621
83417
|
const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
|
|
82622
|
-
return
|
|
83418
|
+
return join39(currentDir, workerFile);
|
|
82623
83419
|
}
|
|
82624
83420
|
/**
|
|
82625
83421
|
* Set up event forwarding from IPC host to runtime listeners.
|
|
@@ -85543,7 +86339,7 @@ ${detail}`
|
|
|
85543
86339
|
"agent"
|
|
85544
86340
|
);
|
|
85545
86341
|
await store.updateTask(taskId, {
|
|
85546
|
-
status:
|
|
86342
|
+
status: "merging-fix",
|
|
85547
86343
|
mergeRetries: 0,
|
|
85548
86344
|
error: null,
|
|
85549
86345
|
verificationFailureCount: nextBounces
|
|
@@ -85551,10 +86347,10 @@ ${detail}`
|
|
|
85551
86347
|
await store.moveTask(taskId, "in-progress");
|
|
85552
86348
|
await store.logEntry(
|
|
85553
86349
|
taskId,
|
|
85554
|
-
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress for remediation`
|
|
86350
|
+
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress with status=merging-fix for remediation`
|
|
85555
86351
|
);
|
|
85556
86352
|
runtimeLog.log(
|
|
85557
|
-
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress`
|
|
86353
|
+
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress with status=merging-fix`
|
|
85558
86354
|
);
|
|
85559
86355
|
} catch {
|
|
85560
86356
|
runtimeLog.error(
|
|
@@ -86872,6 +87668,8 @@ __export(src_exports2, {
|
|
|
86872
87668
|
describeAgentModel: () => describeAgentModel,
|
|
86873
87669
|
describeModel: () => describeModel,
|
|
86874
87670
|
ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
|
|
87671
|
+
extractRuntimeHint: () => extractRuntimeHint,
|
|
87672
|
+
extractRuntimeModel: () => extractRuntimeModel,
|
|
86875
87673
|
formatTaskIdentifier: () => formatTaskIdentifier,
|
|
86876
87674
|
getDefaultPiRuntime: () => getDefaultPiRuntime,
|
|
86877
87675
|
getHostExtensionPaths: () => getHostExtensionPaths,
|
|
@@ -96539,10 +97337,18 @@ var init_claude_cli_probe = __esm({
|
|
|
96539
97337
|
}
|
|
96540
97338
|
});
|
|
96541
97339
|
|
|
97340
|
+
// ../../plugins/fusion-plugin-droid-runtime/dist/probe.js
|
|
97341
|
+
var init_probe3 = __esm({
|
|
97342
|
+
"../../plugins/fusion-plugin-droid-runtime/dist/probe.js"() {
|
|
97343
|
+
"use strict";
|
|
97344
|
+
}
|
|
97345
|
+
});
|
|
97346
|
+
|
|
96542
97347
|
// ../dashboard/src/droid-cli-probe.ts
|
|
96543
97348
|
var init_droid_cli_probe = __esm({
|
|
96544
97349
|
"../dashboard/src/droid-cli-probe.ts"() {
|
|
96545
97350
|
"use strict";
|
|
97351
|
+
init_probe3();
|
|
96546
97352
|
}
|
|
96547
97353
|
});
|
|
96548
97354
|
|
|
@@ -101430,7 +102236,7 @@ var init_auth_middleware = __esm({
|
|
|
101430
102236
|
|
|
101431
102237
|
// ../dashboard/src/server.ts
|
|
101432
102238
|
import express from "express";
|
|
101433
|
-
import { join as
|
|
102239
|
+
import { join as join40, dirname as dirname12 } from "node:path";
|
|
101434
102240
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
101435
102241
|
function clearAiSessionCleanupInterval() {
|
|
101436
102242
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -101722,8 +102528,8 @@ __export(task_exports, {
|
|
|
101722
102528
|
runTaskUpdate: () => runTaskUpdate
|
|
101723
102529
|
});
|
|
101724
102530
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
101725
|
-
import { watchFile, unwatchFile, statSync as statSync6, existsSync as
|
|
101726
|
-
import { basename as basename10, join as
|
|
102531
|
+
import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync32, readFileSync as readFileSync11 } from "node:fs";
|
|
102532
|
+
import { basename as basename10, join as join41 } from "node:path";
|
|
101727
102533
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
101728
102534
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
101729
102535
|
const issueUrl = sourceMetadata.issueUrl;
|
|
@@ -102036,8 +102842,8 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
102036
102842
|
printEntries(filteredEntries);
|
|
102037
102843
|
if (options.follow) {
|
|
102038
102844
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
102039
|
-
const logPath =
|
|
102040
|
-
if (!
|
|
102845
|
+
const logPath = join41(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
102846
|
+
if (!existsSync32(logPath)) {
|
|
102041
102847
|
console.log(`
|
|
102042
102848
|
Waiting for log file to be created...`);
|
|
102043
102849
|
}
|
|
@@ -102066,7 +102872,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
102066
102872
|
lastPosition = 0;
|
|
102067
102873
|
}
|
|
102068
102874
|
if (stats.size > lastPosition) {
|
|
102069
|
-
const content =
|
|
102875
|
+
const content = readFileSync11(logPath, "utf-8");
|
|
102070
102876
|
const lines = content.slice(lastPosition).split("\n");
|
|
102071
102877
|
for (const line of lines) {
|
|
102072
102878
|
if (!line.trim()) continue;
|
|
@@ -103179,9 +103985,9 @@ init_src();
|
|
|
103179
103985
|
init_gh_cli();
|
|
103180
103986
|
import { Type as Type8 } from "typebox";
|
|
103181
103987
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
103182
|
-
import { resolve as resolve19, basename as basename11, extname as extname3, join as
|
|
103988
|
+
import { resolve as resolve19, basename as basename11, extname as extname3, join as join42 } from "node:path";
|
|
103183
103989
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
103184
|
-
import { existsSync as
|
|
103990
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
103185
103991
|
import { spawn as spawn11 } from "node:child_process";
|
|
103186
103992
|
var MIME_TYPES2 = {
|
|
103187
103993
|
".png": "image/png",
|
|
@@ -103201,7 +104007,7 @@ var MIME_TYPES2 = {
|
|
|
103201
104007
|
function resolveProjectRoot2(cwd) {
|
|
103202
104008
|
let current = resolve19(cwd);
|
|
103203
104009
|
while (true) {
|
|
103204
|
-
if (
|
|
104010
|
+
if (existsSync33(join42(current, ".fusion"))) {
|
|
103205
104011
|
return current;
|
|
103206
104012
|
}
|
|
103207
104013
|
const parent = resolve19(current, "..");
|
|
@@ -103222,7 +104028,7 @@ async function getStore2(cwd) {
|
|
|
103222
104028
|
return store;
|
|
103223
104029
|
}
|
|
103224
104030
|
function getFusionDir(cwd) {
|
|
103225
|
-
return
|
|
104031
|
+
return join42(resolveProjectRoot2(cwd), ".fusion");
|
|
103226
104032
|
}
|
|
103227
104033
|
async function validateAssignableAgentId(cwd, agentId) {
|
|
103228
104034
|
const { AgentStore: AgentStore2, isEphemeralAgent: isEphemeralAgent2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -103271,9 +104077,12 @@ async function getResearchAvailability(store) {
|
|
|
103271
104077
|
}
|
|
103272
104078
|
const backend = resolved.searchProvider ?? settings.researchWebSearchProvider;
|
|
103273
104079
|
const configured = backend === "searxng" ? Boolean(settings.researchSearxngUrl) : backend === "brave" ? Boolean(settings.researchBraveApiKey) : backend === "google" ? Boolean(settings.researchGoogleSearchApiKey && settings.researchGoogleSearchCx) : backend === "tavily" ? Boolean(settings.researchTavilyApiKey) : false;
|
|
103274
|
-
if (!
|
|
104080
|
+
if (!backend) {
|
|
103275
104081
|
return { ok: false, code: "provider-unavailable", message: "Research provider is not configured. Set research provider credentials in Settings." };
|
|
103276
104082
|
}
|
|
104083
|
+
if (!configured) {
|
|
104084
|
+
return { ok: false, code: "missing-credentials", message: `Missing credentials for ${backend}. Add provider keys in Authentication and verify Research defaults.` };
|
|
104085
|
+
}
|
|
103277
104086
|
return { ok: true };
|
|
103278
104087
|
}
|
|
103279
104088
|
function toResearchRunDetails(run) {
|
|
@@ -103664,12 +104473,13 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
103664
104473
|
pi.registerTool({
|
|
103665
104474
|
name: "fn_task_retry",
|
|
103666
104475
|
label: "fn: Retry Task",
|
|
103667
|
-
description: "Retry a failed task \u2014 clears the error state
|
|
103668
|
-
promptSnippet: "Retry a failed Fusion task (clears error, moves to todo)",
|
|
104476
|
+
description: "Retry a failed task \u2014 clears the error state. Tasks in other columns move to todo; tasks in in-review stay in-place for auto-merge retry.",
|
|
104477
|
+
promptSnippet: "Retry a failed Fusion task (clears error, moves to todo or stays in in-review)",
|
|
103669
104478
|
promptGuidelines: [
|
|
103670
|
-
"Use when a task has failed and needs to be retried
|
|
103671
|
-
"Only tasks in 'failed' state can be retried",
|
|
103672
|
-
"
|
|
104479
|
+
"Use when a task has failed and needs to be retried",
|
|
104480
|
+
"Only tasks in 'failed' or 'stuck-killed' state can be retried",
|
|
104481
|
+
"Tasks in 'in-review' stay in in-review \u2014 only the error/retry state is cleared, and the auto-merge system re-attempts",
|
|
104482
|
+
"Tasks in other columns are moved to the todo column with error state cleared"
|
|
103673
104483
|
],
|
|
103674
104484
|
parameters: Type8.Object({
|
|
103675
104485
|
id: Type8.String({ description: "Task ID to retry (e.g. FN-001). Must be in 'failed' state." })
|
|
@@ -103693,6 +104503,14 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
103693
104503
|
details: { taskId: params.id, currentStatus: task.status }
|
|
103694
104504
|
};
|
|
103695
104505
|
}
|
|
104506
|
+
if (task.column === "in-review") {
|
|
104507
|
+
await store.updateTask(params.id, { status: null, error: null, stuckKillCount: 0, mergeRetries: 0 });
|
|
104508
|
+
await store.logEntry(params.id, "Retry requested via Fusion extension (in-review retry, mergeRetries reset)");
|
|
104509
|
+
return {
|
|
104510
|
+
content: [{ type: "text", text: `Retried ${params.id} \u2192 in-review (merge retry state cleared, task stays in in-review)` }],
|
|
104511
|
+
details: { taskId: params.id, newColumn: "in-review" }
|
|
104512
|
+
};
|
|
104513
|
+
}
|
|
103696
104514
|
await store.updateTask(params.id, { status: null, error: null });
|
|
103697
104515
|
await store.moveTask(params.id, "todo");
|
|
103698
104516
|
await store.logEntry(params.id, "Retry requested via Fusion extension", "Task reset to todo for retry");
|
|
@@ -104184,7 +105002,7 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104184
105002
|
pi.registerTool({
|
|
104185
105003
|
name: "fn_research_cancel",
|
|
104186
105004
|
label: "fn: Cancel Research Run",
|
|
104187
|
-
description: "Cancel
|
|
105005
|
+
description: "Cancel an in-flight research run. Terminal runs return INVALID_TRANSITION.",
|
|
104188
105006
|
parameters: Type8.Object({ id: Type8.String({ description: "Research run ID" }) }),
|
|
104189
105007
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
104190
105008
|
const store = await getStore2(ctx.cwd);
|
|
@@ -104196,6 +105014,17 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104196
105014
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
104197
105015
|
};
|
|
104198
105016
|
}
|
|
105017
|
+
if (!["queued", "running", "cancelling", "retry_waiting"].includes(run.status)) {
|
|
105018
|
+
return {
|
|
105019
|
+
content: [{ type: "text", text: `Research run ${params.id} cannot be cancelled from status ${run.status}.` }],
|
|
105020
|
+
isError: true,
|
|
105021
|
+
details: {
|
|
105022
|
+
...toResearchRunDetails(run),
|
|
105023
|
+
error: "invalid transition",
|
|
105024
|
+
setup: { code: "INVALID_TRANSITION", message: "Cancel is only available for queued/running/cancelling/retry_waiting runs." }
|
|
105025
|
+
}
|
|
105026
|
+
};
|
|
105027
|
+
}
|
|
104199
105028
|
const updated = researchStore.requestCancellation(params.id);
|
|
104200
105029
|
return {
|
|
104201
105030
|
content: [{ type: "text", text: `Requested cancellation for research run ${params.id} (status: ${updated.status}).` }],
|
|
@@ -104203,6 +105032,41 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104203
105032
|
};
|
|
104204
105033
|
}
|
|
104205
105034
|
});
|
|
105035
|
+
pi.registerTool({
|
|
105036
|
+
name: "fn_research_retry",
|
|
105037
|
+
label: "fn: Retry Research Run",
|
|
105038
|
+
description: "Retry a failed research run when lifecycle marks it retryable.",
|
|
105039
|
+
parameters: Type8.Object({ id: Type8.String({ description: "Research run ID" }) }),
|
|
105040
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105041
|
+
const store = await getStore2(ctx.cwd);
|
|
105042
|
+
const researchStore = store.getResearchStore();
|
|
105043
|
+
const run = researchStore.getRun(params.id);
|
|
105044
|
+
if (!run) {
|
|
105045
|
+
return {
|
|
105046
|
+
content: [{ type: "text", text: `Research run ${params.id} not found.` }],
|
|
105047
|
+
isError: true,
|
|
105048
|
+
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
105049
|
+
};
|
|
105050
|
+
}
|
|
105051
|
+
const isRetryExhausted = run.status === "retry_exhausted" || run.lifecycle?.errorCode === "RETRY_EXHAUSTED";
|
|
105052
|
+
if (run.status !== "failed" && run.status !== "timed_out" || run.lifecycle?.retryable === false || isRetryExhausted) {
|
|
105053
|
+
return {
|
|
105054
|
+
content: [{ type: "text", text: `Research run ${params.id} is not retryable from status ${run.status}.` }],
|
|
105055
|
+
isError: true,
|
|
105056
|
+
details: {
|
|
105057
|
+
...toResearchRunDetails(run),
|
|
105058
|
+
error: "not retryable",
|
|
105059
|
+
setup: { code: isRetryExhausted ? "RETRY_EXHAUSTED" : "INVALID_TRANSITION", message: "Retry is only available for failed/timed_out retryable runs." }
|
|
105060
|
+
}
|
|
105061
|
+
};
|
|
105062
|
+
}
|
|
105063
|
+
const retryRun = researchStore.createRetryRun(params.id);
|
|
105064
|
+
return {
|
|
105065
|
+
content: [{ type: "text", text: `Created retry run ${retryRun.id} from ${params.id}.` }],
|
|
105066
|
+
details: toResearchRunDetails(retryRun)
|
|
105067
|
+
};
|
|
105068
|
+
}
|
|
105069
|
+
});
|
|
104206
105070
|
pi.registerTool({
|
|
104207
105071
|
name: "fn_insight_list",
|
|
104208
105072
|
label: "fn: List Insights",
|
|
@@ -104899,6 +105763,278 @@ Status: ${updated.status}`
|
|
|
104899
105763
|
};
|
|
104900
105764
|
}
|
|
104901
105765
|
});
|
|
105766
|
+
pi.registerTool({
|
|
105767
|
+
name: "fn_list_agents",
|
|
105768
|
+
label: "fn: List Agents",
|
|
105769
|
+
description: "List all available agents in the system. Shows each agent's name, role, state, personality (soul), and current assignment. Use this to discover which agents exist and what they specialize in before delegating work.",
|
|
105770
|
+
promptSnippet: "List all available Fusion agents",
|
|
105771
|
+
promptGuidelines: [
|
|
105772
|
+
"Use fn_list_agents to discover which agents exist before delegating work",
|
|
105773
|
+
"Filter by role or state to narrow results",
|
|
105774
|
+
"Ephemeral/runtime agents are excluded by default"
|
|
105775
|
+
],
|
|
105776
|
+
parameters: Type8.Object({
|
|
105777
|
+
role: Type8.Optional(
|
|
105778
|
+
Type8.String({ description: "Filter by agent role/capability (e.g., 'executor', 'reviewer', 'qa')" })
|
|
105779
|
+
),
|
|
105780
|
+
state: Type8.Optional(
|
|
105781
|
+
Type8.String({ description: "Filter by agent state (e.g., 'idle', 'active', 'running')" })
|
|
105782
|
+
),
|
|
105783
|
+
includeEphemeral: Type8.Optional(
|
|
105784
|
+
Type8.Boolean({ description: "Include ephemeral/runtime agents (default: false)" })
|
|
105785
|
+
)
|
|
105786
|
+
}),
|
|
105787
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105788
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105789
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105790
|
+
await agentStore.init();
|
|
105791
|
+
const filter = {};
|
|
105792
|
+
if (params.role) filter.role = params.role;
|
|
105793
|
+
if (params.state) filter.state = params.state;
|
|
105794
|
+
if (params.includeEphemeral !== void 0) filter.includeEphemeral = params.includeEphemeral;
|
|
105795
|
+
const agents = await agentStore.listAgents(filter);
|
|
105796
|
+
if (agents.length === 0) {
|
|
105797
|
+
return {
|
|
105798
|
+
content: [{ type: "text", text: "No agents found matching the specified filters." }],
|
|
105799
|
+
details: { agents: [], count: 0 }
|
|
105800
|
+
};
|
|
105801
|
+
}
|
|
105802
|
+
const lines = agents.map((agent) => {
|
|
105803
|
+
const parts = [
|
|
105804
|
+
`ID: ${agent.id}`,
|
|
105805
|
+
`Name: ${agent.name}`,
|
|
105806
|
+
`Role: ${agent.role}`,
|
|
105807
|
+
`State: ${agent.state}`
|
|
105808
|
+
];
|
|
105809
|
+
if (agent.title) parts.push(`Title: ${agent.title}`);
|
|
105810
|
+
if (agent.soul) parts.push(`Soul: ${agent.soul.slice(0, 200)}`);
|
|
105811
|
+
if (agent.instructionsText) {
|
|
105812
|
+
const snippet = agent.instructionsText.slice(0, 100);
|
|
105813
|
+
parts.push(`Custom Instructions: ${snippet}${agent.instructionsText.length > 100 ? "\u2026" : ""}`);
|
|
105814
|
+
}
|
|
105815
|
+
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
|
105816
|
+
return parts.join("\n");
|
|
105817
|
+
});
|
|
105818
|
+
return {
|
|
105819
|
+
content: [{ type: "text", text: `Available agents (${agents.length}):
|
|
105820
|
+
|
|
105821
|
+
${lines.join("\n\n")}` }],
|
|
105822
|
+
details: { agents, count: agents.length }
|
|
105823
|
+
};
|
|
105824
|
+
}
|
|
105825
|
+
});
|
|
105826
|
+
pi.registerTool({
|
|
105827
|
+
name: "fn_delegate_task",
|
|
105828
|
+
label: "fn: Delegate Task",
|
|
105829
|
+
description: "Create a new task and assign it to a specific agent for execution. The task goes to 'todo' and will be picked up by the target agent on their next heartbeat cycle. Use fn_list_agents first to find available agents and their capabilities.",
|
|
105830
|
+
promptSnippet: "Delegate a task to a specific Fusion agent",
|
|
105831
|
+
promptGuidelines: [
|
|
105832
|
+
"Use fn_list_agents first to find available agents and their capabilities",
|
|
105833
|
+
"The task is created in 'todo' and assigned to the target agent",
|
|
105834
|
+
"Cannot delegate to ephemeral/runtime agents",
|
|
105835
|
+
"Optionally specify dependencies on other tasks"
|
|
105836
|
+
],
|
|
105837
|
+
parameters: Type8.Object({
|
|
105838
|
+
agent_id: Type8.String({ description: "The agent ID to delegate work to" }),
|
|
105839
|
+
description: Type8.String({ description: "What needs to be done" }),
|
|
105840
|
+
dependencies: Type8.Optional(
|
|
105841
|
+
Type8.Array(Type8.String(), { description: 'Task IDs this new task depends on (e.g. ["KB-001"]' })
|
|
105842
|
+
)
|
|
105843
|
+
}),
|
|
105844
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105845
|
+
const agentError = await validateAssignableAgentId(ctx.cwd, params.agent_id);
|
|
105846
|
+
if (agentError) {
|
|
105847
|
+
return {
|
|
105848
|
+
content: [{ type: "text", text: `ERROR: ${agentError}` }],
|
|
105849
|
+
isError: true,
|
|
105850
|
+
details: { error: agentError }
|
|
105851
|
+
};
|
|
105852
|
+
}
|
|
105853
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105854
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105855
|
+
await agentStore.init();
|
|
105856
|
+
const agent = await agentStore.getAgent(params.agent_id);
|
|
105857
|
+
const store = await getStore2(ctx.cwd);
|
|
105858
|
+
const task = await store.createTask({
|
|
105859
|
+
description: params.description,
|
|
105860
|
+
dependencies: params.dependencies,
|
|
105861
|
+
column: "todo",
|
|
105862
|
+
assignedAgentId: params.agent_id,
|
|
105863
|
+
source: { sourceType: "api" }
|
|
105864
|
+
});
|
|
105865
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
105866
|
+
return {
|
|
105867
|
+
content: [{
|
|
105868
|
+
type: "text",
|
|
105869
|
+
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. The task will be picked up by ${agent.name} on their next heartbeat cycle.`
|
|
105870
|
+
}],
|
|
105871
|
+
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
105872
|
+
};
|
|
105873
|
+
}
|
|
105874
|
+
});
|
|
105875
|
+
pi.registerTool({
|
|
105876
|
+
name: "fn_agent_show",
|
|
105877
|
+
label: "fn: Show Agent",
|
|
105878
|
+
description: "Show detailed information about a single agent, including their role, state, position in the org hierarchy (reports-to, direct reports), skills, and current assignment.",
|
|
105879
|
+
promptSnippet: "Show details of a specific Fusion agent",
|
|
105880
|
+
promptGuidelines: [
|
|
105881
|
+
"Use to get full details about a specific agent",
|
|
105882
|
+
"Provide agent ID or a resolvable name",
|
|
105883
|
+
"Shows the agent's position in the org hierarchy"
|
|
105884
|
+
],
|
|
105885
|
+
parameters: Type8.Object({
|
|
105886
|
+
id: Type8.String({ description: "Agent ID or resolvable name" })
|
|
105887
|
+
}),
|
|
105888
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105889
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105890
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105891
|
+
await agentStore.init();
|
|
105892
|
+
const agent = await agentStore.resolveAgent(params.id);
|
|
105893
|
+
if (!agent) {
|
|
105894
|
+
return {
|
|
105895
|
+
content: [{ type: "text", text: `Agent '${params.id}' not found` }],
|
|
105896
|
+
isError: true,
|
|
105897
|
+
details: { error: "Agent not found" }
|
|
105898
|
+
};
|
|
105899
|
+
}
|
|
105900
|
+
const directReports = await agentStore.getAgentsByReportsTo(agent.id);
|
|
105901
|
+
const parts = [
|
|
105902
|
+
`ID: ${agent.id}`,
|
|
105903
|
+
`Name: ${agent.name}`,
|
|
105904
|
+
`Role: ${agent.role}`,
|
|
105905
|
+
`State: ${agent.state}`
|
|
105906
|
+
];
|
|
105907
|
+
if (agent.title) parts.push(`Title: ${agent.title}`);
|
|
105908
|
+
if (agent.icon) parts.push(`Icon: ${agent.icon}`);
|
|
105909
|
+
if (agent.reportsTo) {
|
|
105910
|
+
const manager = await agentStore.getAgent(agent.reportsTo);
|
|
105911
|
+
if (manager) {
|
|
105912
|
+
parts.push(`Reports To: ${manager.name} (${manager.id})`);
|
|
105913
|
+
} else {
|
|
105914
|
+
parts.push(`Reports To: ${agent.reportsTo}`);
|
|
105915
|
+
}
|
|
105916
|
+
}
|
|
105917
|
+
if (directReports.length > 0) {
|
|
105918
|
+
parts.push(`Direct Reports: ${directReports.map((r) => `${r.name} (${r.id})`).join(", ")}`);
|
|
105919
|
+
}
|
|
105920
|
+
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
|
105921
|
+
if (agent.instructionsText) {
|
|
105922
|
+
const snippet = agent.instructionsText.slice(0, 100);
|
|
105923
|
+
parts.push(`Custom Instructions: ${snippet}${agent.instructionsText.length > 100 ? "\u2026" : ""}`);
|
|
105924
|
+
}
|
|
105925
|
+
if (agent.soul) {
|
|
105926
|
+
const snippet = agent.soul.slice(0, 200);
|
|
105927
|
+
parts.push(`Soul: ${snippet}${agent.soul.length > 200 ? "\u2026" : ""}`);
|
|
105928
|
+
}
|
|
105929
|
+
if (agent.metadata?.skills) {
|
|
105930
|
+
parts.push(`Skills: ${JSON.stringify(agent.metadata.skills)}`);
|
|
105931
|
+
}
|
|
105932
|
+
return {
|
|
105933
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
105934
|
+
details: {
|
|
105935
|
+
agent,
|
|
105936
|
+
directReports: directReports.map((r) => ({ id: r.id, name: r.name, role: r.role }))
|
|
105937
|
+
}
|
|
105938
|
+
};
|
|
105939
|
+
}
|
|
105940
|
+
});
|
|
105941
|
+
pi.registerTool({
|
|
105942
|
+
name: "fn_agent_org_chart",
|
|
105943
|
+
label: "fn: Agent Org Chart",
|
|
105944
|
+
description: "Show the organizational tree of agents, displaying the role hierarchy. Optionally filter to a subtree rooted at a specific agent.",
|
|
105945
|
+
promptSnippet: "Show the Fusion agent org chart",
|
|
105946
|
+
promptGuidelines: [
|
|
105947
|
+
"Use to understand the team structure and reporting hierarchy",
|
|
105948
|
+
"Optionally specify a root agent to see only their subtree",
|
|
105949
|
+
"Ephemeral/runtime agents are excluded by default"
|
|
105950
|
+
],
|
|
105951
|
+
parameters: Type8.Object({
|
|
105952
|
+
root_agent_id: Type8.Optional(
|
|
105953
|
+
Type8.String({ description: "If provided, show only the subtree rooted at this agent" })
|
|
105954
|
+
),
|
|
105955
|
+
include_ephemeral: Type8.Optional(
|
|
105956
|
+
Type8.Boolean({ description: "Include ephemeral/runtime agents (default: false)" })
|
|
105957
|
+
)
|
|
105958
|
+
}),
|
|
105959
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105960
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105961
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105962
|
+
await agentStore.init();
|
|
105963
|
+
const includeEphemeral = params.include_ephemeral ?? false;
|
|
105964
|
+
if (params.root_agent_id) {
|
|
105965
|
+
const rootAgent = await agentStore.resolveAgent(params.root_agent_id);
|
|
105966
|
+
if (!rootAgent) {
|
|
105967
|
+
return {
|
|
105968
|
+
content: [{ type: "text", text: `Agent '${params.root_agent_id}' not found` }],
|
|
105969
|
+
isError: true,
|
|
105970
|
+
details: { error: "Root agent not found" }
|
|
105971
|
+
};
|
|
105972
|
+
}
|
|
105973
|
+
const fullTree = await agentStore.getOrgTree({ includeEphemeral });
|
|
105974
|
+
const findSubtree = (nodes) => {
|
|
105975
|
+
for (const node of nodes) {
|
|
105976
|
+
if (node.agent.id === rootAgent.id) return node;
|
|
105977
|
+
const found = findSubtree(node.children);
|
|
105978
|
+
if (found) return found;
|
|
105979
|
+
}
|
|
105980
|
+
return null;
|
|
105981
|
+
};
|
|
105982
|
+
const subtree = findSubtree(fullTree);
|
|
105983
|
+
if (!subtree) {
|
|
105984
|
+
return {
|
|
105985
|
+
content: [{
|
|
105986
|
+
type: "text",
|
|
105987
|
+
text: `${rootAgent.icon ?? "\u{1F916}"} ${rootAgent.name} (${rootAgent.role}) \u2014 ${rootAgent.state}${rootAgent.taskId ? ` [${rootAgent.taskId}]` : ""}`
|
|
105988
|
+
}],
|
|
105989
|
+
details: { tree: [{ agent: rootAgent, children: [] }] }
|
|
105990
|
+
};
|
|
105991
|
+
}
|
|
105992
|
+
const lines2 = [];
|
|
105993
|
+
const renderNode2 = (node, indent) => {
|
|
105994
|
+
const a = node.agent;
|
|
105995
|
+
lines2.push(
|
|
105996
|
+
`${indent}${a.icon ?? "\u{1F916}"} ${a.name} (${a.role}) \u2014 ${a.state}${a.taskId ? ` [${a.taskId}]` : ""}`
|
|
105997
|
+
);
|
|
105998
|
+
for (const child of node.children) {
|
|
105999
|
+
renderNode2(child, indent + " ");
|
|
106000
|
+
}
|
|
106001
|
+
};
|
|
106002
|
+
renderNode2(subtree, "");
|
|
106003
|
+
return {
|
|
106004
|
+
content: [{ type: "text", text: `Agent Org Tree (subtree: ${rootAgent.name}):
|
|
106005
|
+
${lines2.join("\n")}` }],
|
|
106006
|
+
details: { tree: [subtree] }
|
|
106007
|
+
};
|
|
106008
|
+
}
|
|
106009
|
+
const tree = await agentStore.getOrgTree({ includeEphemeral });
|
|
106010
|
+
if (tree.length === 0) {
|
|
106011
|
+
return {
|
|
106012
|
+
content: [{ type: "text", text: "No agents found." }],
|
|
106013
|
+
details: { tree: [], count: 0 }
|
|
106014
|
+
};
|
|
106015
|
+
}
|
|
106016
|
+
const lines = [];
|
|
106017
|
+
let count = 0;
|
|
106018
|
+
const renderNode = (node, indent) => {
|
|
106019
|
+
const a = node.agent;
|
|
106020
|
+
lines.push(
|
|
106021
|
+
`${indent}${a.icon ?? "\u{1F916}"} ${a.name} (${a.role}) \u2014 ${a.state}${a.taskId ? ` [${a.taskId}]` : ""}`
|
|
106022
|
+
);
|
|
106023
|
+
count++;
|
|
106024
|
+
for (const child of node.children) {
|
|
106025
|
+
renderNode(child, indent + " ");
|
|
106026
|
+
}
|
|
106027
|
+
};
|
|
106028
|
+
for (const root of tree) {
|
|
106029
|
+
renderNode(root, "");
|
|
106030
|
+
}
|
|
106031
|
+
return {
|
|
106032
|
+
content: [{ type: "text", text: `Agent Org Tree (${count} agents):
|
|
106033
|
+
${lines.join("\n")}` }],
|
|
106034
|
+
details: { tree, count }
|
|
106035
|
+
};
|
|
106036
|
+
}
|
|
106037
|
+
});
|
|
104902
106038
|
pi.registerTool({
|
|
104903
106039
|
name: "fn_skills_search",
|
|
104904
106040
|
label: "FN: Search Skills",
|