@runfusion/fusion 0.17.2 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1035 -660
- package/dist/client/assets/ChatView-3Sqm6teN.js +1 -0
- package/dist/client/assets/{DevServerView-GFFVXHVP.js → DevServerView-r6V3FqkY.js} +1 -1
- package/dist/client/assets/DirectoryPicker-CTZE95Fk.js +1 -0
- package/dist/client/assets/DocumentsView-DSEf1Lmg.js +1 -0
- package/dist/client/assets/{InsightsView-Bxu0TJkt.js → InsightsView-F5PZsX5u.js} +2 -2
- package/dist/client/assets/MemoryView-DicXjec9.js +2 -0
- package/dist/client/assets/NodesView-DddCS7zB.js +14 -0
- package/dist/client/assets/NodesView-sJgPLTzz.css +1 -0
- package/dist/client/assets/{PiExtensionsManager-4e3MlD62.js → PiExtensionsManager-Ch7si-v8.js} +3 -3
- package/dist/client/assets/PluginManager-LcTh_fHP.js +1 -0
- package/dist/client/assets/ResearchView-D0TY1VcX.js +1 -0
- package/dist/client/assets/{RoadmapsView-jHTOK0RQ.js → RoadmapsView-DfEF3mql.js} +2 -2
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +1 -0
- package/dist/client/assets/SettingsModal-SOADcCNJ.js +31 -0
- package/dist/client/assets/{SettingsModal-4Z8ZJMzD.js → SettingsModal-YcScdFiG.js} +1 -1
- package/dist/client/assets/SettingsModal-oOnIed5O.css +1 -0
- package/dist/client/assets/SetupWizardModal-EDYuf9Yc.js +1 -0
- package/dist/client/assets/SkillsView-Dkq2CQla.js +1 -0
- package/dist/client/assets/index-4hC8zoTD.css +1 -0
- package/dist/client/assets/index-DNzA4aZ7.js +1229 -0
- package/dist/client/assets/{users-D3u6f2Rz.js → users-Cp5TSxVm.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 +505 -70
- 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-17J-F0Rl.js +0 -18
- package/dist/client/assets/AgentDetailView-yu8Xltqk.css +0 -1
- package/dist/client/assets/AgentsView-Bs03ptrd.css +0 -1
- package/dist/client/assets/AgentsView-sbBkb7Wd.js +0 -517
- package/dist/client/assets/ChatView-BR5cvK_B.js +0 -1
- package/dist/client/assets/DirectoryPicker-WPDSBdT6.js +0 -1
- package/dist/client/assets/DocumentsView-BHpDsIIt.js +0 -1
- package/dist/client/assets/MemoryView-CmnzZorw.js +0 -2
- package/dist/client/assets/NodesView-CO9_4hCr.js +0 -14
- package/dist/client/assets/NodesView-DuAXX_0j.css +0 -1
- package/dist/client/assets/PluginManager-DGN2rvOY.js +0 -1
- package/dist/client/assets/ResearchView-Dsa6Gykl.js +0 -1
- package/dist/client/assets/SettingsModal-D0kuJpBA.js +0 -31
- package/dist/client/assets/SettingsModal-D_AFkDJa.css +0 -1
- package/dist/client/assets/SettingsModal-Dq4a5KSX.css +0 -1
- package/dist/client/assets/SetupWizardModal-Bhumd4Rf.js +0 -1
- package/dist/client/assets/SkillsView-MHweJTz4.js +0 -1
- package/dist/client/assets/folder-open-BNQW9dE9.js +0 -6
- package/dist/client/assets/index-DEVBHvyW.css +0 -1
- package/dist/client/assets/index-k_85J1DS.js +0 -682
- package/dist/client/assets/star-7L86NZrT.js +0 -6
- package/dist/client/assets/upload-DsAS6tno.js +0 -6
package/dist/extension.js
CHANGED
|
@@ -2771,7 +2771,7 @@ var init_db = __esm({
|
|
|
2771
2771
|
"use strict";
|
|
2772
2772
|
init_sqlite_adapter();
|
|
2773
2773
|
init_types();
|
|
2774
|
-
SCHEMA_VERSION =
|
|
2774
|
+
SCHEMA_VERSION = 60;
|
|
2775
2775
|
SCHEMA_SQL = `
|
|
2776
2776
|
-- Tasks table with JSON columns for nested data
|
|
2777
2777
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -2839,6 +2839,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2839
2839
|
missionId TEXT,
|
|
2840
2840
|
sliceId TEXT,
|
|
2841
2841
|
assignedAgentId TEXT,
|
|
2842
|
+
pausedByAgentId TEXT,
|
|
2842
2843
|
assigneeUserId TEXT,
|
|
2843
2844
|
sourceType TEXT,
|
|
2844
2845
|
sourceAgentId TEXT,
|
|
@@ -4636,6 +4637,12 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4636
4637
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idxInsightRunEventsRunIdSeq ON project_insight_run_events(runId, seq)`);
|
|
4637
4638
|
});
|
|
4638
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
|
+
}
|
|
4639
4646
|
}
|
|
4640
4647
|
/**
|
|
4641
4648
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -10542,50 +10549,40 @@ ${feature.acceptanceCriteria}`);
|
|
|
10542
10549
|
}
|
|
10543
10550
|
}
|
|
10544
10551
|
// ── ID Generators ───────────────────────────────────────────────────
|
|
10545
|
-
|
|
10546
|
-
|
|
10552
|
+
idSequence = 0;
|
|
10553
|
+
generateId(prefix) {
|
|
10554
|
+
const timestamp = Date.now().toString(36).toUpperCase();
|
|
10555
|
+
this.idSequence += 1;
|
|
10556
|
+
const sequence = this.idSequence.toString(36).toUpperCase().padStart(4, "0");
|
|
10547
10557
|
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10548
|
-
return
|
|
10558
|
+
return `${prefix}-${timestamp}-${sequence}-${random}`;
|
|
10559
|
+
}
|
|
10560
|
+
generateMissionId() {
|
|
10561
|
+
return this.generateId("M");
|
|
10549
10562
|
}
|
|
10550
10563
|
generateMilestoneId() {
|
|
10551
|
-
|
|
10552
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10553
|
-
return `MS-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10564
|
+
return this.generateId("MS");
|
|
10554
10565
|
}
|
|
10555
10566
|
generateSliceId() {
|
|
10556
|
-
|
|
10557
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10558
|
-
return `SL-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10567
|
+
return this.generateId("SL");
|
|
10559
10568
|
}
|
|
10560
10569
|
generateFeatureId() {
|
|
10561
|
-
|
|
10562
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10563
|
-
return `F-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10570
|
+
return this.generateId("F");
|
|
10564
10571
|
}
|
|
10565
10572
|
generateMissionEventId() {
|
|
10566
|
-
|
|
10567
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10568
|
-
return `ME-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10573
|
+
return this.generateId("ME");
|
|
10569
10574
|
}
|
|
10570
10575
|
generateAssertionId() {
|
|
10571
|
-
|
|
10572
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10573
|
-
return `CA-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10576
|
+
return this.generateId("CA");
|
|
10574
10577
|
}
|
|
10575
10578
|
generateValidatorRunId() {
|
|
10576
|
-
|
|
10577
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10578
|
-
return `VR-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10579
|
+
return this.generateId("VR");
|
|
10579
10580
|
}
|
|
10580
10581
|
generateFailureId() {
|
|
10581
|
-
|
|
10582
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10583
|
-
return `VF-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10582
|
+
return this.generateId("VF");
|
|
10584
10583
|
}
|
|
10585
10584
|
generateLineageId() {
|
|
10586
|
-
|
|
10587
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10588
|
-
return `FL-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10585
|
+
return this.generateId("FL");
|
|
10589
10586
|
}
|
|
10590
10587
|
};
|
|
10591
10588
|
}
|
|
@@ -32645,6 +32642,7 @@ var init_store = __esm({
|
|
|
32645
32642
|
missionId: row.missionId || void 0,
|
|
32646
32643
|
sliceId: row.sliceId || void 0,
|
|
32647
32644
|
assignedAgentId: row.assignedAgentId || void 0,
|
|
32645
|
+
pausedByAgentId: row.pausedByAgentId || void 0,
|
|
32648
32646
|
assigneeUserId: row.assigneeUserId || void 0,
|
|
32649
32647
|
nodeId: row.nodeId || void 0,
|
|
32650
32648
|
effectiveNodeId: row.effectiveNodeId || void 0,
|
|
@@ -32909,6 +32907,7 @@ ${recentText}` : void 0
|
|
|
32909
32907
|
"missionId",
|
|
32910
32908
|
"sliceId",
|
|
32911
32909
|
"assignedAgentId",
|
|
32910
|
+
"pausedByAgentId",
|
|
32912
32911
|
"assigneeUserId",
|
|
32913
32912
|
"nodeId",
|
|
32914
32913
|
"effectiveNodeId",
|
|
@@ -33021,6 +33020,7 @@ ${outcome}`;
|
|
|
33021
33020
|
"missionId",
|
|
33022
33021
|
"sliceId",
|
|
33023
33022
|
"assignedAgentId",
|
|
33023
|
+
"pausedByAgentId",
|
|
33024
33024
|
"assigneeUserId",
|
|
33025
33025
|
"nodeId",
|
|
33026
33026
|
"effectiveNodeId",
|
|
@@ -33071,9 +33071,9 @@ ${outcome}`;
|
|
|
33071
33071
|
dependencies, steps, log, attachments, steeringComments,
|
|
33072
33072
|
comments, workflowStepResults, prInfo, issueInfo,
|
|
33073
33073
|
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
33074
|
-
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33074
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33075
33075
|
) VALUES (
|
|
33076
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33076
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33077
33077
|
)
|
|
33078
33078
|
ON CONFLICT(id) DO UPDATE SET
|
|
33079
33079
|
title = excluded.title,
|
|
@@ -33142,6 +33142,7 @@ ${outcome}`;
|
|
|
33142
33142
|
missionId = excluded.missionId,
|
|
33143
33143
|
sliceId = excluded.sliceId,
|
|
33144
33144
|
assignedAgentId = excluded.assignedAgentId,
|
|
33145
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
33145
33146
|
assigneeUserId = excluded.assigneeUserId,
|
|
33146
33147
|
nodeId = excluded.nodeId,
|
|
33147
33148
|
effectiveNodeId = excluded.effectiveNodeId,
|
|
@@ -33223,6 +33224,7 @@ ${outcome}`;
|
|
|
33223
33224
|
task.missionId ?? null,
|
|
33224
33225
|
task.sliceId ?? null,
|
|
33225
33226
|
task.assignedAgentId ?? null,
|
|
33227
|
+
task.pausedByAgentId ?? null,
|
|
33226
33228
|
task.assigneeUserId ?? null,
|
|
33227
33229
|
task.nodeId ?? null,
|
|
33228
33230
|
task.effectiveNodeId ?? null,
|
|
@@ -34340,6 +34342,23 @@ ${newTask.description}
|
|
|
34340
34342
|
const matches = [...activeMatches, ...archiveMatches];
|
|
34341
34343
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
34342
34344
|
}
|
|
34345
|
+
async getTasksByAssignedAgent(agentId, options) {
|
|
34346
|
+
const whereClauses = ["assignedAgentId = ?"];
|
|
34347
|
+
const params = [agentId];
|
|
34348
|
+
if (options?.pausedOnly) {
|
|
34349
|
+
whereClauses.push("paused = 1");
|
|
34350
|
+
}
|
|
34351
|
+
if (options?.excludeArchived) {
|
|
34352
|
+
whereClauses.push(`"column" != 'archived'`);
|
|
34353
|
+
}
|
|
34354
|
+
const selectClause = this.getTaskSelectClause(false);
|
|
34355
|
+
const rows = this.db.prepare(`
|
|
34356
|
+
SELECT ${selectClause} FROM tasks
|
|
34357
|
+
WHERE ${whereClauses.join(" AND ")}
|
|
34358
|
+
ORDER BY createdAt ASC
|
|
34359
|
+
`).all(...params);
|
|
34360
|
+
return rows.map((row) => this.rowToTask(row));
|
|
34361
|
+
}
|
|
34343
34362
|
async selectNextTaskForAgent(agentId) {
|
|
34344
34363
|
const tasks = await this.listTasks({ slim: true });
|
|
34345
34364
|
if (tasks.length === 0) {
|
|
@@ -34577,6 +34596,11 @@ ${newTask.description}
|
|
|
34577
34596
|
} else if (updates.assignedAgentId !== void 0) {
|
|
34578
34597
|
task.assignedAgentId = updates.assignedAgentId;
|
|
34579
34598
|
}
|
|
34599
|
+
if (updates.pausedByAgentId === null) {
|
|
34600
|
+
task.pausedByAgentId = void 0;
|
|
34601
|
+
} else if (updates.pausedByAgentId !== void 0) {
|
|
34602
|
+
task.pausedByAgentId = updates.pausedByAgentId;
|
|
34603
|
+
}
|
|
34580
34604
|
if (updates.assigneeUserId === null) {
|
|
34581
34605
|
task.assigneeUserId = void 0;
|
|
34582
34606
|
} else if (updates.assigneeUserId !== void 0) {
|
|
@@ -34815,14 +34839,21 @@ ${newTask.description}
|
|
|
34815
34839
|
* Pause or unpause a task. Paused tasks are excluded from all automated
|
|
34816
34840
|
* agent and scheduler interaction. Logs the action and emits `task:updated`.
|
|
34817
34841
|
*/
|
|
34818
|
-
async pauseTask(id, paused, runContext) {
|
|
34842
|
+
async pauseTask(id, paused, runContext, agentOptions) {
|
|
34819
34843
|
return this.withTaskLock(id, async () => {
|
|
34820
34844
|
const dir = this.taskDir(id);
|
|
34821
34845
|
const task = await this.readTaskJson(dir);
|
|
34822
34846
|
if (!task.log) {
|
|
34823
34847
|
task.log = [];
|
|
34824
34848
|
}
|
|
34849
|
+
const previousPausedByAgentId = task.pausedByAgentId;
|
|
34825
34850
|
task.paused = paused || void 0;
|
|
34851
|
+
if (paused && agentOptions?.pausedByAgentId) {
|
|
34852
|
+
task.pausedByAgentId = agentOptions.pausedByAgentId;
|
|
34853
|
+
}
|
|
34854
|
+
if (!paused) {
|
|
34855
|
+
task.pausedByAgentId = void 0;
|
|
34856
|
+
}
|
|
34826
34857
|
if (task.column === "in-progress" || task.column === "in-review") {
|
|
34827
34858
|
task.status = paused ? "paused" : void 0;
|
|
34828
34859
|
}
|
|
@@ -34830,7 +34861,7 @@ ${newTask.description}
|
|
|
34830
34861
|
task.updatedAt = now;
|
|
34831
34862
|
const logEntry = {
|
|
34832
34863
|
timestamp: now,
|
|
34833
|
-
action: paused ? "Task paused" : "Task unpaused"
|
|
34864
|
+
action: paused ? agentOptions?.pausedByAgentId ? `Task paused (agent ${agentOptions.pausedByAgentId} paused)` : "Task paused" : previousPausedByAgentId ? `Task unpaused (agent ${previousPausedByAgentId} resumed)` : "Task unpaused"
|
|
34834
34865
|
};
|
|
34835
34866
|
if (runContext) {
|
|
34836
34867
|
logEntry.runContext = runContext;
|
|
@@ -39981,10 +40012,17 @@ var init_docker_client = __esm({
|
|
|
39981
40012
|
}
|
|
39982
40013
|
return this.createDockerInstance(hostConfig);
|
|
39983
40014
|
}
|
|
39984
|
-
async getContainerInfo(containerId) {
|
|
40015
|
+
async getContainerInfo(containerId, hostConfig) {
|
|
39985
40016
|
try {
|
|
39986
|
-
const docker = await this.
|
|
40017
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
39987
40018
|
const inspect = await docker.getContainer(containerId).inspect();
|
|
40019
|
+
const ports = Object.entries(inspect.NetworkSettings?.Ports ?? {}).reduce((acc, [key, value]) => {
|
|
40020
|
+
const binding = Array.isArray(value) && value.length > 0 ? value[0] : void 0;
|
|
40021
|
+
if (binding?.HostPort) {
|
|
40022
|
+
acc[key] = binding.HostPort;
|
|
40023
|
+
}
|
|
40024
|
+
return acc;
|
|
40025
|
+
}, {});
|
|
39988
40026
|
return {
|
|
39989
40027
|
id: inspect.Id,
|
|
39990
40028
|
name: (inspect.Name ?? "").replace(/^\//, ""),
|
|
@@ -39996,8 +40034,12 @@ var init_docker_client = __esm({
|
|
|
39996
40034
|
paused: Boolean(inspect.State?.Paused),
|
|
39997
40035
|
restarting: Boolean(inspect.State?.Restarting),
|
|
39998
40036
|
dead: Boolean(inspect.State?.Dead),
|
|
39999
|
-
error: inspect.State?.Error || void 0
|
|
40000
|
-
|
|
40037
|
+
error: inspect.State?.Error || void 0,
|
|
40038
|
+
exitCode: typeof inspect.State?.ExitCode === "number" ? inspect.State.ExitCode : void 0,
|
|
40039
|
+
startedAt: inspect.State?.StartedAt || void 0,
|
|
40040
|
+
finishedAt: inspect.State?.FinishedAt || void 0
|
|
40041
|
+
},
|
|
40042
|
+
ports
|
|
40001
40043
|
};
|
|
40002
40044
|
} catch (error) {
|
|
40003
40045
|
const message = toErrorMessage(error);
|
|
@@ -40007,6 +40049,18 @@ var init_docker_client = __esm({
|
|
|
40007
40049
|
throw error;
|
|
40008
40050
|
}
|
|
40009
40051
|
}
|
|
40052
|
+
async getContainerLogs(containerId, hostConfig, options) {
|
|
40053
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
40054
|
+
const stream = await docker.getContainer(containerId).logs({
|
|
40055
|
+
stdout: true,
|
|
40056
|
+
stderr: true,
|
|
40057
|
+
tail: options?.tail ?? 100
|
|
40058
|
+
});
|
|
40059
|
+
if (Buffer.isBuffer(stream)) {
|
|
40060
|
+
return stream.toString("utf8");
|
|
40061
|
+
}
|
|
40062
|
+
return String(stream ?? "");
|
|
40063
|
+
}
|
|
40010
40064
|
async getInstance() {
|
|
40011
40065
|
if (!this.dockerInstance) {
|
|
40012
40066
|
this.dockerInstance = await this.createDockerInstance(this.defaultHostConfig);
|
|
@@ -54525,6 +54579,9 @@ function normalizePattern(pattern) {
|
|
|
54525
54579
|
function isExclusionPattern(pattern) {
|
|
54526
54580
|
return pattern.startsWith("-");
|
|
54527
54581
|
}
|
|
54582
|
+
function bareSkillName(name) {
|
|
54583
|
+
return name.replace(/\/SKILL\.md$/i, "");
|
|
54584
|
+
}
|
|
54528
54585
|
function resolveSessionSkills(context) {
|
|
54529
54586
|
const { requestedSkillNames } = context;
|
|
54530
54587
|
const projectRootDir = resolveProjectRoot(context.projectRootDir);
|
|
@@ -54618,25 +54675,40 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54618
54675
|
const hasRequestedNames = Boolean(requestedSkillNames && requestedSkillNames.length > 0);
|
|
54619
54676
|
const hasExcluded = excludedSkillPaths.size > 0;
|
|
54620
54677
|
let filteredSkills;
|
|
54678
|
+
const skillNameMatches = (skill, pattern) => bareSkillName(skill.name).toLowerCase() === bareSkillName(pattern).toLowerCase() || skill.filePath === pattern;
|
|
54679
|
+
const isExcluded = (skill) => {
|
|
54680
|
+
for (const ep of excludedSkillPaths) {
|
|
54681
|
+
if (skillNameMatches(skill, ep)) return true;
|
|
54682
|
+
}
|
|
54683
|
+
return false;
|
|
54684
|
+
};
|
|
54685
|
+
const isAllowed = (skill) => {
|
|
54686
|
+
for (const ap of allowedSkillPaths) {
|
|
54687
|
+
if (skillNameMatches(skill, ap)) return true;
|
|
54688
|
+
}
|
|
54689
|
+
return false;
|
|
54690
|
+
};
|
|
54621
54691
|
if (hasRequestedNames) {
|
|
54622
|
-
const
|
|
54692
|
+
const requestedBareNamesLower = new Set(requestedSkillNames.map((n) => bareSkillName(n).toLowerCase()));
|
|
54623
54693
|
filteredSkills = base.skills.filter(
|
|
54624
|
-
(skill) =>
|
|
54694
|
+
(skill) => requestedBareNamesLower.has(bareSkillName(skill.name).toLowerCase()) && !isExcluded(skill)
|
|
54625
54695
|
);
|
|
54626
54696
|
} else if (hasPatterns) {
|
|
54627
54697
|
filteredSkills = base.skills.filter(
|
|
54628
|
-
(skill) =>
|
|
54698
|
+
(skill) => isAllowed(skill) && !isExcluded(skill)
|
|
54629
54699
|
);
|
|
54630
54700
|
} else if (hasExcluded) {
|
|
54631
|
-
filteredSkills = base.skills.filter((skill) => !
|
|
54701
|
+
filteredSkills = base.skills.filter((skill) => !isExcluded(skill));
|
|
54632
54702
|
} else {
|
|
54633
54703
|
filteredSkills = base.skills;
|
|
54634
54704
|
}
|
|
54635
54705
|
const newDiagnostics = [];
|
|
54636
54706
|
const purpose = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
54637
|
-
const
|
|
54707
|
+
const discoveredBareNames = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
54708
|
+
const discoveredFilePaths = new Set(base.skills.map((s) => s.filePath));
|
|
54709
|
+
const hasDiscoveredMatch = (pattern) => discoveredBareNames.has(bareSkillName(pattern).toLowerCase()) || discoveredFilePaths.has(pattern);
|
|
54638
54710
|
for (const excludedPath of excludedSkillPaths) {
|
|
54639
|
-
if (
|
|
54711
|
+
if (hasDiscoveredMatch(excludedPath)) {
|
|
54640
54712
|
newDiagnostics.push({
|
|
54641
54713
|
type: "warning",
|
|
54642
54714
|
message: `Skill at '${excludedPath}' exists but is disabled by project execution settings${purpose}`,
|
|
@@ -54645,7 +54717,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54645
54717
|
}
|
|
54646
54718
|
}
|
|
54647
54719
|
for (const allowedPath of allowedSkillPaths) {
|
|
54648
|
-
if (!
|
|
54720
|
+
if (!hasDiscoveredMatch(allowedPath)) {
|
|
54649
54721
|
newDiagnostics.push({
|
|
54650
54722
|
type: "warning",
|
|
54651
54723
|
message: `Configured skill pattern '${allowedPath}' not found in discovered skills${purpose}`,
|
|
@@ -54654,9 +54726,9 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54654
54726
|
}
|
|
54655
54727
|
}
|
|
54656
54728
|
if (requestedSkillNames) {
|
|
54657
|
-
const
|
|
54729
|
+
const discoveredBareNamesLower = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
54658
54730
|
for (const requestedName of requestedSkillNames) {
|
|
54659
|
-
if (!
|
|
54731
|
+
if (!discoveredBareNamesLower.has(bareSkillName(requestedName).toLowerCase()) && !isBuiltInFallbackRequest(requestedName)) {
|
|
54660
54732
|
const purpose2 = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
54661
54733
|
newDiagnostics.push({
|
|
54662
54734
|
type: "warning",
|
|
@@ -55010,6 +55082,11 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
55010
55082
|
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
55011
55083
|
}
|
|
55012
55084
|
}
|
|
55085
|
+
if (/Provider finish_reason:\s*repeat\b/i.test(stateError)) {
|
|
55086
|
+
piLog.warn(`pi state error \u2014 treating provider finish_reason=repeat as soft stop: ${stateError}`);
|
|
55087
|
+
clearSessionStateError(session);
|
|
55088
|
+
return;
|
|
55089
|
+
}
|
|
55013
55090
|
throw new Error(stateError);
|
|
55014
55091
|
}
|
|
55015
55092
|
}
|
|
@@ -58191,9 +58268,18 @@ function normalizeAgentSkills(metadataSkills) {
|
|
|
58191
58268
|
name = namedEntry.trim();
|
|
58192
58269
|
}
|
|
58193
58270
|
}
|
|
58194
|
-
if (name && name.length > 0
|
|
58195
|
-
|
|
58196
|
-
|
|
58271
|
+
if (name && name.length > 0) {
|
|
58272
|
+
if (name.includes("::")) {
|
|
58273
|
+
const idPath = name.split("::").pop();
|
|
58274
|
+
const parts = idPath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
58275
|
+
if (parts.length >= 2) {
|
|
58276
|
+
name = parts.slice(-2).join("/");
|
|
58277
|
+
}
|
|
58278
|
+
}
|
|
58279
|
+
if (!seen.has(name)) {
|
|
58280
|
+
seen.add(name);
|
|
58281
|
+
result.push(name);
|
|
58282
|
+
}
|
|
58197
58283
|
}
|
|
58198
58284
|
}
|
|
58199
58285
|
return result;
|
|
@@ -68816,20 +68902,26 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68816
68902
|
if (from !== "in-review" && from !== "done") {
|
|
68817
68903
|
return task;
|
|
68818
68904
|
}
|
|
68819
|
-
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr";
|
|
68905
|
+
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr" || task.status === "merging-fix";
|
|
68820
68906
|
if (!hasMergeEvidence) {
|
|
68821
68907
|
return task;
|
|
68822
68908
|
}
|
|
68823
68909
|
return this.cleanupMergeStateForReverification(
|
|
68824
68910
|
task,
|
|
68825
|
-
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification
|
|
68911
|
+
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification`,
|
|
68912
|
+
{
|
|
68913
|
+
// Keep deterministic merge-verification bounce budget across remediation
|
|
68914
|
+
// cycles. Status may be cleared by intermediate paths, so the counter is
|
|
68915
|
+
// the canonical signal once a bounce has started.
|
|
68916
|
+
preserveVerificationFailureCount: (task.verificationFailureCount ?? 0) > 0
|
|
68917
|
+
}
|
|
68826
68918
|
);
|
|
68827
68919
|
}
|
|
68828
|
-
async cleanupMergeStateForReverification(task, logMessage) {
|
|
68920
|
+
async cleanupMergeStateForReverification(task, logMessage, options) {
|
|
68829
68921
|
await this.store.updateTask(task.id, {
|
|
68830
68922
|
mergeDetails: null,
|
|
68831
68923
|
mergeRetries: 0,
|
|
68832
|
-
verificationFailureCount: 0,
|
|
68924
|
+
verificationFailureCount: options?.preserveVerificationFailureCount ? task.verificationFailureCount ?? 0 : 0,
|
|
68833
68925
|
workflowStepResults: []
|
|
68834
68926
|
});
|
|
68835
68927
|
const refreshedTask = await this.store.getTask(task.id);
|
|
@@ -69424,7 +69516,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69424
69516
|
};
|
|
69425
69517
|
const audit = createRunAuditor(this.store, engineRunContext);
|
|
69426
69518
|
const activeColumns = /* @__PURE__ */ new Set(["in-progress", "in-review", "done"]);
|
|
69427
|
-
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
69519
|
+
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
69428
69520
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
69429
69521
|
if (!isActiveTask) {
|
|
69430
69522
|
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
@@ -69763,7 +69855,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69763
69855
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}):
|
|
69764
69856
|
${summary}`,
|
|
69765
69857
|
`Verification (${failedType})`,
|
|
69766
|
-
`Deterministic verification failed (${failedType})
|
|
69858
|
+
`Deterministic verification failed (${failedType})`,
|
|
69859
|
+
true,
|
|
69860
|
+
true
|
|
69767
69861
|
);
|
|
69768
69862
|
return;
|
|
69769
69863
|
}
|
|
@@ -69809,7 +69903,9 @@ ${summary}`,
|
|
|
69809
69903
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}) after ${maxFixRetries} fix attempts:
|
|
69810
69904
|
${summary}`,
|
|
69811
69905
|
`Verification (${failedType})`,
|
|
69812
|
-
`Deterministic verification failed after ${maxFixRetries} fix attempts
|
|
69906
|
+
`Deterministic verification failed after ${maxFixRetries} fix attempts`,
|
|
69907
|
+
true,
|
|
69908
|
+
true
|
|
69813
69909
|
);
|
|
69814
69910
|
return;
|
|
69815
69911
|
}
|
|
@@ -71498,7 +71594,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
71498
71594
|
* Injects failure feedback into PROMPT.md, resets steps, clears session,
|
|
71499
71595
|
* and schedules a move to todo → in-progress after the executing guard clears.
|
|
71500
71596
|
*/
|
|
71501
|
-
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true) {
|
|
71597
|
+
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false) {
|
|
71502
71598
|
const taskId = task.id;
|
|
71503
71599
|
this.clearCompletedTaskWatchdog(taskId);
|
|
71504
71600
|
await this.store.addTaskComment(
|
|
@@ -71517,7 +71613,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
71517
71613
|
const updatedTask = await this.store.getTask(taskId);
|
|
71518
71614
|
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
71519
71615
|
await this.store.updateTask(taskId, {
|
|
71520
|
-
status: null,
|
|
71616
|
+
status: mergeVerificationFailure ? "merging-fix" : null,
|
|
71521
71617
|
error: null,
|
|
71522
71618
|
sessionFile: null,
|
|
71523
71619
|
workflowStepRetries: 0
|
|
@@ -79886,14 +79982,15 @@ var init_self_healing = __esm({
|
|
|
79886
79982
|
execAsync7 = promisify9(exec9);
|
|
79887
79983
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
79888
79984
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
79889
|
-
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
79985
|
+
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
79890
79986
|
NON_TERMINAL_STEP_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in-progress"]);
|
|
79891
79987
|
GHOST_REVIEW_PRESERVED_STATUSES = /* @__PURE__ */ new Set([
|
|
79892
79988
|
"failed",
|
|
79893
79989
|
"awaiting-user-review",
|
|
79894
79990
|
"awaiting-approval",
|
|
79895
79991
|
"merging",
|
|
79896
|
-
"merging-pr"
|
|
79992
|
+
"merging-pr",
|
|
79993
|
+
"merging-fix"
|
|
79897
79994
|
]);
|
|
79898
79995
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
79899
79996
|
MAX_TASK_DONE_RETRIES = 3;
|
|
@@ -80616,7 +80713,7 @@ var init_self_healing = __esm({
|
|
|
80616
80713
|
*
|
|
80617
80714
|
* Preserved statuses (skipped):
|
|
80618
80715
|
* - `awaiting-user-review`, `awaiting-approval`: explicit human handoff
|
|
80619
|
-
* - `merging`, `merging-pr`: handled by `recoverInterruptedMergingTasks`
|
|
80716
|
+
* - `merging`, `merging-pr`, `merging-fix`: handled by `recoverInterruptedMergingTasks`
|
|
80620
80717
|
*
|
|
80621
80718
|
* Rate-limiting comes from the `updatedAt >= taskStuckTimeoutMs` gate —
|
|
80622
80719
|
* each kick refreshes `updatedAt`, so a task that re-enters review and gets
|
|
@@ -86232,7 +86329,7 @@ ${detail}`
|
|
|
86232
86329
|
"agent"
|
|
86233
86330
|
);
|
|
86234
86331
|
await store.updateTask(taskId, {
|
|
86235
|
-
status:
|
|
86332
|
+
status: "merging-fix",
|
|
86236
86333
|
mergeRetries: 0,
|
|
86237
86334
|
error: null,
|
|
86238
86335
|
verificationFailureCount: nextBounces
|
|
@@ -86240,10 +86337,10 @@ ${detail}`
|
|
|
86240
86337
|
await store.moveTask(taskId, "in-progress");
|
|
86241
86338
|
await store.logEntry(
|
|
86242
86339
|
taskId,
|
|
86243
|
-
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress for remediation`
|
|
86340
|
+
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress with status=merging-fix for remediation`
|
|
86244
86341
|
);
|
|
86245
86342
|
runtimeLog.log(
|
|
86246
|
-
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress`
|
|
86343
|
+
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress with status=merging-fix`
|
|
86247
86344
|
);
|
|
86248
86345
|
} catch {
|
|
86249
86346
|
runtimeLog.error(
|
|
@@ -97230,10 +97327,18 @@ var init_claude_cli_probe = __esm({
|
|
|
97230
97327
|
}
|
|
97231
97328
|
});
|
|
97232
97329
|
|
|
97330
|
+
// ../../plugins/fusion-plugin-droid-runtime/dist/probe.js
|
|
97331
|
+
var init_probe3 = __esm({
|
|
97332
|
+
"../../plugins/fusion-plugin-droid-runtime/dist/probe.js"() {
|
|
97333
|
+
"use strict";
|
|
97334
|
+
}
|
|
97335
|
+
});
|
|
97336
|
+
|
|
97233
97337
|
// ../dashboard/src/droid-cli-probe.ts
|
|
97234
97338
|
var init_droid_cli_probe = __esm({
|
|
97235
97339
|
"../dashboard/src/droid-cli-probe.ts"() {
|
|
97236
97340
|
"use strict";
|
|
97341
|
+
init_probe3();
|
|
97237
97342
|
}
|
|
97238
97343
|
});
|
|
97239
97344
|
|
|
@@ -103962,9 +104067,12 @@ async function getResearchAvailability(store) {
|
|
|
103962
104067
|
}
|
|
103963
104068
|
const backend = resolved.searchProvider ?? settings.researchWebSearchProvider;
|
|
103964
104069
|
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;
|
|
103965
|
-
if (!
|
|
104070
|
+
if (!backend) {
|
|
103966
104071
|
return { ok: false, code: "provider-unavailable", message: "Research provider is not configured. Set research provider credentials in Settings." };
|
|
103967
104072
|
}
|
|
104073
|
+
if (!configured) {
|
|
104074
|
+
return { ok: false, code: "missing-credentials", message: `Missing credentials for ${backend}. Add provider keys in Authentication and verify Research defaults.` };
|
|
104075
|
+
}
|
|
103968
104076
|
return { ok: true };
|
|
103969
104077
|
}
|
|
103970
104078
|
function toResearchRunDetails(run) {
|
|
@@ -104355,12 +104463,13 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
104355
104463
|
pi.registerTool({
|
|
104356
104464
|
name: "fn_task_retry",
|
|
104357
104465
|
label: "fn: Retry Task",
|
|
104358
|
-
description: "Retry a failed task \u2014 clears the error state
|
|
104359
|
-
promptSnippet: "Retry a failed Fusion task (clears error, moves to todo)",
|
|
104466
|
+
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.",
|
|
104467
|
+
promptSnippet: "Retry a failed Fusion task (clears error, moves to todo or stays in in-review)",
|
|
104360
104468
|
promptGuidelines: [
|
|
104361
|
-
"Use when a task has failed and needs to be retried
|
|
104362
|
-
"Only tasks in 'failed' state can be retried",
|
|
104363
|
-
"
|
|
104469
|
+
"Use when a task has failed and needs to be retried",
|
|
104470
|
+
"Only tasks in 'failed' or 'stuck-killed' state can be retried",
|
|
104471
|
+
"Tasks in 'in-review' stay in in-review \u2014 only the error/retry state is cleared, and the auto-merge system re-attempts",
|
|
104472
|
+
"Tasks in other columns are moved to the todo column with error state cleared"
|
|
104364
104473
|
],
|
|
104365
104474
|
parameters: Type8.Object({
|
|
104366
104475
|
id: Type8.String({ description: "Task ID to retry (e.g. FN-001). Must be in 'failed' state." })
|
|
@@ -104384,6 +104493,14 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
104384
104493
|
details: { taskId: params.id, currentStatus: task.status }
|
|
104385
104494
|
};
|
|
104386
104495
|
}
|
|
104496
|
+
if (task.column === "in-review") {
|
|
104497
|
+
await store.updateTask(params.id, { status: null, error: null, stuckKillCount: 0, mergeRetries: 0 });
|
|
104498
|
+
await store.logEntry(params.id, "Retry requested via Fusion extension (in-review retry, mergeRetries reset)");
|
|
104499
|
+
return {
|
|
104500
|
+
content: [{ type: "text", text: `Retried ${params.id} \u2192 in-review (merge retry state cleared, task stays in in-review)` }],
|
|
104501
|
+
details: { taskId: params.id, newColumn: "in-review" }
|
|
104502
|
+
};
|
|
104503
|
+
}
|
|
104387
104504
|
await store.updateTask(params.id, { status: null, error: null });
|
|
104388
104505
|
await store.moveTask(params.id, "todo");
|
|
104389
104506
|
await store.logEntry(params.id, "Retry requested via Fusion extension", "Task reset to todo for retry");
|
|
@@ -104875,7 +104992,7 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104875
104992
|
pi.registerTool({
|
|
104876
104993
|
name: "fn_research_cancel",
|
|
104877
104994
|
label: "fn: Cancel Research Run",
|
|
104878
|
-
description: "Cancel
|
|
104995
|
+
description: "Cancel an in-flight research run. Terminal runs return INVALID_TRANSITION.",
|
|
104879
104996
|
parameters: Type8.Object({ id: Type8.String({ description: "Research run ID" }) }),
|
|
104880
104997
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
104881
104998
|
const store = await getStore2(ctx.cwd);
|
|
@@ -104887,6 +105004,17 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104887
105004
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
104888
105005
|
};
|
|
104889
105006
|
}
|
|
105007
|
+
if (!["queued", "running", "cancelling", "retry_waiting"].includes(run.status)) {
|
|
105008
|
+
return {
|
|
105009
|
+
content: [{ type: "text", text: `Research run ${params.id} cannot be cancelled from status ${run.status}.` }],
|
|
105010
|
+
isError: true,
|
|
105011
|
+
details: {
|
|
105012
|
+
...toResearchRunDetails(run),
|
|
105013
|
+
error: "invalid transition",
|
|
105014
|
+
setup: { code: "INVALID_TRANSITION", message: "Cancel is only available for queued/running/cancelling/retry_waiting runs." }
|
|
105015
|
+
}
|
|
105016
|
+
};
|
|
105017
|
+
}
|
|
104890
105018
|
const updated = researchStore.requestCancellation(params.id);
|
|
104891
105019
|
return {
|
|
104892
105020
|
content: [{ type: "text", text: `Requested cancellation for research run ${params.id} (status: ${updated.status}).` }],
|
|
@@ -104894,6 +105022,41 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104894
105022
|
};
|
|
104895
105023
|
}
|
|
104896
105024
|
});
|
|
105025
|
+
pi.registerTool({
|
|
105026
|
+
name: "fn_research_retry",
|
|
105027
|
+
label: "fn: Retry Research Run",
|
|
105028
|
+
description: "Retry a failed research run when lifecycle marks it retryable.",
|
|
105029
|
+
parameters: Type8.Object({ id: Type8.String({ description: "Research run ID" }) }),
|
|
105030
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105031
|
+
const store = await getStore2(ctx.cwd);
|
|
105032
|
+
const researchStore = store.getResearchStore();
|
|
105033
|
+
const run = researchStore.getRun(params.id);
|
|
105034
|
+
if (!run) {
|
|
105035
|
+
return {
|
|
105036
|
+
content: [{ type: "text", text: `Research run ${params.id} not found.` }],
|
|
105037
|
+
isError: true,
|
|
105038
|
+
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
105039
|
+
};
|
|
105040
|
+
}
|
|
105041
|
+
const isRetryExhausted = run.status === "retry_exhausted" || run.lifecycle?.errorCode === "RETRY_EXHAUSTED";
|
|
105042
|
+
if (run.status !== "failed" && run.status !== "timed_out" || run.lifecycle?.retryable === false || isRetryExhausted) {
|
|
105043
|
+
return {
|
|
105044
|
+
content: [{ type: "text", text: `Research run ${params.id} is not retryable from status ${run.status}.` }],
|
|
105045
|
+
isError: true,
|
|
105046
|
+
details: {
|
|
105047
|
+
...toResearchRunDetails(run),
|
|
105048
|
+
error: "not retryable",
|
|
105049
|
+
setup: { code: isRetryExhausted ? "RETRY_EXHAUSTED" : "INVALID_TRANSITION", message: "Retry is only available for failed/timed_out retryable runs." }
|
|
105050
|
+
}
|
|
105051
|
+
};
|
|
105052
|
+
}
|
|
105053
|
+
const retryRun = researchStore.createRetryRun(params.id);
|
|
105054
|
+
return {
|
|
105055
|
+
content: [{ type: "text", text: `Created retry run ${retryRun.id} from ${params.id}.` }],
|
|
105056
|
+
details: toResearchRunDetails(retryRun)
|
|
105057
|
+
};
|
|
105058
|
+
}
|
|
105059
|
+
});
|
|
104897
105060
|
pi.registerTool({
|
|
104898
105061
|
name: "fn_insight_list",
|
|
104899
105062
|
label: "fn: List Insights",
|
|
@@ -105590,6 +105753,278 @@ Status: ${updated.status}`
|
|
|
105590
105753
|
};
|
|
105591
105754
|
}
|
|
105592
105755
|
});
|
|
105756
|
+
pi.registerTool({
|
|
105757
|
+
name: "fn_list_agents",
|
|
105758
|
+
label: "fn: List Agents",
|
|
105759
|
+
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.",
|
|
105760
|
+
promptSnippet: "List all available Fusion agents",
|
|
105761
|
+
promptGuidelines: [
|
|
105762
|
+
"Use fn_list_agents to discover which agents exist before delegating work",
|
|
105763
|
+
"Filter by role or state to narrow results",
|
|
105764
|
+
"Ephemeral/runtime agents are excluded by default"
|
|
105765
|
+
],
|
|
105766
|
+
parameters: Type8.Object({
|
|
105767
|
+
role: Type8.Optional(
|
|
105768
|
+
Type8.String({ description: "Filter by agent role/capability (e.g., 'executor', 'reviewer', 'qa')" })
|
|
105769
|
+
),
|
|
105770
|
+
state: Type8.Optional(
|
|
105771
|
+
Type8.String({ description: "Filter by agent state (e.g., 'idle', 'active', 'running')" })
|
|
105772
|
+
),
|
|
105773
|
+
includeEphemeral: Type8.Optional(
|
|
105774
|
+
Type8.Boolean({ description: "Include ephemeral/runtime agents (default: false)" })
|
|
105775
|
+
)
|
|
105776
|
+
}),
|
|
105777
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105778
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105779
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105780
|
+
await agentStore.init();
|
|
105781
|
+
const filter = {};
|
|
105782
|
+
if (params.role) filter.role = params.role;
|
|
105783
|
+
if (params.state) filter.state = params.state;
|
|
105784
|
+
if (params.includeEphemeral !== void 0) filter.includeEphemeral = params.includeEphemeral;
|
|
105785
|
+
const agents = await agentStore.listAgents(filter);
|
|
105786
|
+
if (agents.length === 0) {
|
|
105787
|
+
return {
|
|
105788
|
+
content: [{ type: "text", text: "No agents found matching the specified filters." }],
|
|
105789
|
+
details: { agents: [], count: 0 }
|
|
105790
|
+
};
|
|
105791
|
+
}
|
|
105792
|
+
const lines = agents.map((agent) => {
|
|
105793
|
+
const parts = [
|
|
105794
|
+
`ID: ${agent.id}`,
|
|
105795
|
+
`Name: ${agent.name}`,
|
|
105796
|
+
`Role: ${agent.role}`,
|
|
105797
|
+
`State: ${agent.state}`
|
|
105798
|
+
];
|
|
105799
|
+
if (agent.title) parts.push(`Title: ${agent.title}`);
|
|
105800
|
+
if (agent.soul) parts.push(`Soul: ${agent.soul.slice(0, 200)}`);
|
|
105801
|
+
if (agent.instructionsText) {
|
|
105802
|
+
const snippet = agent.instructionsText.slice(0, 100);
|
|
105803
|
+
parts.push(`Custom Instructions: ${snippet}${agent.instructionsText.length > 100 ? "\u2026" : ""}`);
|
|
105804
|
+
}
|
|
105805
|
+
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
|
105806
|
+
return parts.join("\n");
|
|
105807
|
+
});
|
|
105808
|
+
return {
|
|
105809
|
+
content: [{ type: "text", text: `Available agents (${agents.length}):
|
|
105810
|
+
|
|
105811
|
+
${lines.join("\n\n")}` }],
|
|
105812
|
+
details: { agents, count: agents.length }
|
|
105813
|
+
};
|
|
105814
|
+
}
|
|
105815
|
+
});
|
|
105816
|
+
pi.registerTool({
|
|
105817
|
+
name: "fn_delegate_task",
|
|
105818
|
+
label: "fn: Delegate Task",
|
|
105819
|
+
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.",
|
|
105820
|
+
promptSnippet: "Delegate a task to a specific Fusion agent",
|
|
105821
|
+
promptGuidelines: [
|
|
105822
|
+
"Use fn_list_agents first to find available agents and their capabilities",
|
|
105823
|
+
"The task is created in 'todo' and assigned to the target agent",
|
|
105824
|
+
"Cannot delegate to ephemeral/runtime agents",
|
|
105825
|
+
"Optionally specify dependencies on other tasks"
|
|
105826
|
+
],
|
|
105827
|
+
parameters: Type8.Object({
|
|
105828
|
+
agent_id: Type8.String({ description: "The agent ID to delegate work to" }),
|
|
105829
|
+
description: Type8.String({ description: "What needs to be done" }),
|
|
105830
|
+
dependencies: Type8.Optional(
|
|
105831
|
+
Type8.Array(Type8.String(), { description: 'Task IDs this new task depends on (e.g. ["KB-001"]' })
|
|
105832
|
+
)
|
|
105833
|
+
}),
|
|
105834
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105835
|
+
const agentError = await validateAssignableAgentId(ctx.cwd, params.agent_id);
|
|
105836
|
+
if (agentError) {
|
|
105837
|
+
return {
|
|
105838
|
+
content: [{ type: "text", text: `ERROR: ${agentError}` }],
|
|
105839
|
+
isError: true,
|
|
105840
|
+
details: { error: agentError }
|
|
105841
|
+
};
|
|
105842
|
+
}
|
|
105843
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105844
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105845
|
+
await agentStore.init();
|
|
105846
|
+
const agent = await agentStore.getAgent(params.agent_id);
|
|
105847
|
+
const store = await getStore2(ctx.cwd);
|
|
105848
|
+
const task = await store.createTask({
|
|
105849
|
+
description: params.description,
|
|
105850
|
+
dependencies: params.dependencies,
|
|
105851
|
+
column: "todo",
|
|
105852
|
+
assignedAgentId: params.agent_id,
|
|
105853
|
+
source: { sourceType: "api" }
|
|
105854
|
+
});
|
|
105855
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
105856
|
+
return {
|
|
105857
|
+
content: [{
|
|
105858
|
+
type: "text",
|
|
105859
|
+
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.`
|
|
105860
|
+
}],
|
|
105861
|
+
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
105862
|
+
};
|
|
105863
|
+
}
|
|
105864
|
+
});
|
|
105865
|
+
pi.registerTool({
|
|
105866
|
+
name: "fn_agent_show",
|
|
105867
|
+
label: "fn: Show Agent",
|
|
105868
|
+
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.",
|
|
105869
|
+
promptSnippet: "Show details of a specific Fusion agent",
|
|
105870
|
+
promptGuidelines: [
|
|
105871
|
+
"Use to get full details about a specific agent",
|
|
105872
|
+
"Provide agent ID or a resolvable name",
|
|
105873
|
+
"Shows the agent's position in the org hierarchy"
|
|
105874
|
+
],
|
|
105875
|
+
parameters: Type8.Object({
|
|
105876
|
+
id: Type8.String({ description: "Agent ID or resolvable name" })
|
|
105877
|
+
}),
|
|
105878
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105879
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105880
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105881
|
+
await agentStore.init();
|
|
105882
|
+
const agent = await agentStore.resolveAgent(params.id);
|
|
105883
|
+
if (!agent) {
|
|
105884
|
+
return {
|
|
105885
|
+
content: [{ type: "text", text: `Agent '${params.id}' not found` }],
|
|
105886
|
+
isError: true,
|
|
105887
|
+
details: { error: "Agent not found" }
|
|
105888
|
+
};
|
|
105889
|
+
}
|
|
105890
|
+
const directReports = await agentStore.getAgentsByReportsTo(agent.id);
|
|
105891
|
+
const parts = [
|
|
105892
|
+
`ID: ${agent.id}`,
|
|
105893
|
+
`Name: ${agent.name}`,
|
|
105894
|
+
`Role: ${agent.role}`,
|
|
105895
|
+
`State: ${agent.state}`
|
|
105896
|
+
];
|
|
105897
|
+
if (agent.title) parts.push(`Title: ${agent.title}`);
|
|
105898
|
+
if (agent.icon) parts.push(`Icon: ${agent.icon}`);
|
|
105899
|
+
if (agent.reportsTo) {
|
|
105900
|
+
const manager = await agentStore.getAgent(agent.reportsTo);
|
|
105901
|
+
if (manager) {
|
|
105902
|
+
parts.push(`Reports To: ${manager.name} (${manager.id})`);
|
|
105903
|
+
} else {
|
|
105904
|
+
parts.push(`Reports To: ${agent.reportsTo}`);
|
|
105905
|
+
}
|
|
105906
|
+
}
|
|
105907
|
+
if (directReports.length > 0) {
|
|
105908
|
+
parts.push(`Direct Reports: ${directReports.map((r) => `${r.name} (${r.id})`).join(", ")}`);
|
|
105909
|
+
}
|
|
105910
|
+
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
|
105911
|
+
if (agent.instructionsText) {
|
|
105912
|
+
const snippet = agent.instructionsText.slice(0, 100);
|
|
105913
|
+
parts.push(`Custom Instructions: ${snippet}${agent.instructionsText.length > 100 ? "\u2026" : ""}`);
|
|
105914
|
+
}
|
|
105915
|
+
if (agent.soul) {
|
|
105916
|
+
const snippet = agent.soul.slice(0, 200);
|
|
105917
|
+
parts.push(`Soul: ${snippet}${agent.soul.length > 200 ? "\u2026" : ""}`);
|
|
105918
|
+
}
|
|
105919
|
+
if (agent.metadata?.skills) {
|
|
105920
|
+
parts.push(`Skills: ${JSON.stringify(agent.metadata.skills)}`);
|
|
105921
|
+
}
|
|
105922
|
+
return {
|
|
105923
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
105924
|
+
details: {
|
|
105925
|
+
agent,
|
|
105926
|
+
directReports: directReports.map((r) => ({ id: r.id, name: r.name, role: r.role }))
|
|
105927
|
+
}
|
|
105928
|
+
};
|
|
105929
|
+
}
|
|
105930
|
+
});
|
|
105931
|
+
pi.registerTool({
|
|
105932
|
+
name: "fn_agent_org_chart",
|
|
105933
|
+
label: "fn: Agent Org Chart",
|
|
105934
|
+
description: "Show the organizational tree of agents, displaying the role hierarchy. Optionally filter to a subtree rooted at a specific agent.",
|
|
105935
|
+
promptSnippet: "Show the Fusion agent org chart",
|
|
105936
|
+
promptGuidelines: [
|
|
105937
|
+
"Use to understand the team structure and reporting hierarchy",
|
|
105938
|
+
"Optionally specify a root agent to see only their subtree",
|
|
105939
|
+
"Ephemeral/runtime agents are excluded by default"
|
|
105940
|
+
],
|
|
105941
|
+
parameters: Type8.Object({
|
|
105942
|
+
root_agent_id: Type8.Optional(
|
|
105943
|
+
Type8.String({ description: "If provided, show only the subtree rooted at this agent" })
|
|
105944
|
+
),
|
|
105945
|
+
include_ephemeral: Type8.Optional(
|
|
105946
|
+
Type8.Boolean({ description: "Include ephemeral/runtime agents (default: false)" })
|
|
105947
|
+
)
|
|
105948
|
+
}),
|
|
105949
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
105950
|
+
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
105951
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
105952
|
+
await agentStore.init();
|
|
105953
|
+
const includeEphemeral = params.include_ephemeral ?? false;
|
|
105954
|
+
if (params.root_agent_id) {
|
|
105955
|
+
const rootAgent = await agentStore.resolveAgent(params.root_agent_id);
|
|
105956
|
+
if (!rootAgent) {
|
|
105957
|
+
return {
|
|
105958
|
+
content: [{ type: "text", text: `Agent '${params.root_agent_id}' not found` }],
|
|
105959
|
+
isError: true,
|
|
105960
|
+
details: { error: "Root agent not found" }
|
|
105961
|
+
};
|
|
105962
|
+
}
|
|
105963
|
+
const fullTree = await agentStore.getOrgTree({ includeEphemeral });
|
|
105964
|
+
const findSubtree = (nodes) => {
|
|
105965
|
+
for (const node of nodes) {
|
|
105966
|
+
if (node.agent.id === rootAgent.id) return node;
|
|
105967
|
+
const found = findSubtree(node.children);
|
|
105968
|
+
if (found) return found;
|
|
105969
|
+
}
|
|
105970
|
+
return null;
|
|
105971
|
+
};
|
|
105972
|
+
const subtree = findSubtree(fullTree);
|
|
105973
|
+
if (!subtree) {
|
|
105974
|
+
return {
|
|
105975
|
+
content: [{
|
|
105976
|
+
type: "text",
|
|
105977
|
+
text: `${rootAgent.icon ?? "\u{1F916}"} ${rootAgent.name} (${rootAgent.role}) \u2014 ${rootAgent.state}${rootAgent.taskId ? ` [${rootAgent.taskId}]` : ""}`
|
|
105978
|
+
}],
|
|
105979
|
+
details: { tree: [{ agent: rootAgent, children: [] }] }
|
|
105980
|
+
};
|
|
105981
|
+
}
|
|
105982
|
+
const lines2 = [];
|
|
105983
|
+
const renderNode2 = (node, indent) => {
|
|
105984
|
+
const a = node.agent;
|
|
105985
|
+
lines2.push(
|
|
105986
|
+
`${indent}${a.icon ?? "\u{1F916}"} ${a.name} (${a.role}) \u2014 ${a.state}${a.taskId ? ` [${a.taskId}]` : ""}`
|
|
105987
|
+
);
|
|
105988
|
+
for (const child of node.children) {
|
|
105989
|
+
renderNode2(child, indent + " ");
|
|
105990
|
+
}
|
|
105991
|
+
};
|
|
105992
|
+
renderNode2(subtree, "");
|
|
105993
|
+
return {
|
|
105994
|
+
content: [{ type: "text", text: `Agent Org Tree (subtree: ${rootAgent.name}):
|
|
105995
|
+
${lines2.join("\n")}` }],
|
|
105996
|
+
details: { tree: [subtree] }
|
|
105997
|
+
};
|
|
105998
|
+
}
|
|
105999
|
+
const tree = await agentStore.getOrgTree({ includeEphemeral });
|
|
106000
|
+
if (tree.length === 0) {
|
|
106001
|
+
return {
|
|
106002
|
+
content: [{ type: "text", text: "No agents found." }],
|
|
106003
|
+
details: { tree: [], count: 0 }
|
|
106004
|
+
};
|
|
106005
|
+
}
|
|
106006
|
+
const lines = [];
|
|
106007
|
+
let count = 0;
|
|
106008
|
+
const renderNode = (node, indent) => {
|
|
106009
|
+
const a = node.agent;
|
|
106010
|
+
lines.push(
|
|
106011
|
+
`${indent}${a.icon ?? "\u{1F916}"} ${a.name} (${a.role}) \u2014 ${a.state}${a.taskId ? ` [${a.taskId}]` : ""}`
|
|
106012
|
+
);
|
|
106013
|
+
count++;
|
|
106014
|
+
for (const child of node.children) {
|
|
106015
|
+
renderNode(child, indent + " ");
|
|
106016
|
+
}
|
|
106017
|
+
};
|
|
106018
|
+
for (const root of tree) {
|
|
106019
|
+
renderNode(root, "");
|
|
106020
|
+
}
|
|
106021
|
+
return {
|
|
106022
|
+
content: [{ type: "text", text: `Agent Org Tree (${count} agents):
|
|
106023
|
+
${lines.join("\n")}` }],
|
|
106024
|
+
details: { tree, count }
|
|
106025
|
+
};
|
|
106026
|
+
}
|
|
106027
|
+
});
|
|
105593
106028
|
pi.registerTool({
|
|
105594
106029
|
name: "fn_skills_search",
|
|
105595
106030
|
label: "FN: Search Skills",
|