@runfusion/fusion 0.17.2 → 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 +1004 -633
- package/dist/client/assets/ChatView-BomXmqar.js +1 -0
- package/dist/client/assets/{DevServerView-GFFVXHVP.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-Bxu0TJkt.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-4e3MlD62.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-jHTOK0RQ.js → RoadmapsView-BUW-HJz5.js} +2 -2
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +1 -0
- package/dist/client/assets/{SettingsModal-4Z8ZJMzD.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-D3u6f2Rz.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 +488 -43
- 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.
|
|
@@ -32645,6 +32652,7 @@ var init_store = __esm({
|
|
|
32645
32652
|
missionId: row.missionId || void 0,
|
|
32646
32653
|
sliceId: row.sliceId || void 0,
|
|
32647
32654
|
assignedAgentId: row.assignedAgentId || void 0,
|
|
32655
|
+
pausedByAgentId: row.pausedByAgentId || void 0,
|
|
32648
32656
|
assigneeUserId: row.assigneeUserId || void 0,
|
|
32649
32657
|
nodeId: row.nodeId || void 0,
|
|
32650
32658
|
effectiveNodeId: row.effectiveNodeId || void 0,
|
|
@@ -32909,6 +32917,7 @@ ${recentText}` : void 0
|
|
|
32909
32917
|
"missionId",
|
|
32910
32918
|
"sliceId",
|
|
32911
32919
|
"assignedAgentId",
|
|
32920
|
+
"pausedByAgentId",
|
|
32912
32921
|
"assigneeUserId",
|
|
32913
32922
|
"nodeId",
|
|
32914
32923
|
"effectiveNodeId",
|
|
@@ -33021,6 +33030,7 @@ ${outcome}`;
|
|
|
33021
33030
|
"missionId",
|
|
33022
33031
|
"sliceId",
|
|
33023
33032
|
"assignedAgentId",
|
|
33033
|
+
"pausedByAgentId",
|
|
33024
33034
|
"assigneeUserId",
|
|
33025
33035
|
"nodeId",
|
|
33026
33036
|
"effectiveNodeId",
|
|
@@ -33071,9 +33081,9 @@ ${outcome}`;
|
|
|
33071
33081
|
dependencies, steps, log, attachments, steeringComments,
|
|
33072
33082
|
comments, workflowStepResults, prInfo, issueInfo,
|
|
33073
33083
|
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
|
|
33084
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33075
33085
|
) VALUES (
|
|
33076
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33086
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33077
33087
|
)
|
|
33078
33088
|
ON CONFLICT(id) DO UPDATE SET
|
|
33079
33089
|
title = excluded.title,
|
|
@@ -33142,6 +33152,7 @@ ${outcome}`;
|
|
|
33142
33152
|
missionId = excluded.missionId,
|
|
33143
33153
|
sliceId = excluded.sliceId,
|
|
33144
33154
|
assignedAgentId = excluded.assignedAgentId,
|
|
33155
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
33145
33156
|
assigneeUserId = excluded.assigneeUserId,
|
|
33146
33157
|
nodeId = excluded.nodeId,
|
|
33147
33158
|
effectiveNodeId = excluded.effectiveNodeId,
|
|
@@ -33223,6 +33234,7 @@ ${outcome}`;
|
|
|
33223
33234
|
task.missionId ?? null,
|
|
33224
33235
|
task.sliceId ?? null,
|
|
33225
33236
|
task.assignedAgentId ?? null,
|
|
33237
|
+
task.pausedByAgentId ?? null,
|
|
33226
33238
|
task.assigneeUserId ?? null,
|
|
33227
33239
|
task.nodeId ?? null,
|
|
33228
33240
|
task.effectiveNodeId ?? null,
|
|
@@ -34340,6 +34352,23 @@ ${newTask.description}
|
|
|
34340
34352
|
const matches = [...activeMatches, ...archiveMatches];
|
|
34341
34353
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
34342
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
|
+
}
|
|
34343
34372
|
async selectNextTaskForAgent(agentId) {
|
|
34344
34373
|
const tasks = await this.listTasks({ slim: true });
|
|
34345
34374
|
if (tasks.length === 0) {
|
|
@@ -34577,6 +34606,11 @@ ${newTask.description}
|
|
|
34577
34606
|
} else if (updates.assignedAgentId !== void 0) {
|
|
34578
34607
|
task.assignedAgentId = updates.assignedAgentId;
|
|
34579
34608
|
}
|
|
34609
|
+
if (updates.pausedByAgentId === null) {
|
|
34610
|
+
task.pausedByAgentId = void 0;
|
|
34611
|
+
} else if (updates.pausedByAgentId !== void 0) {
|
|
34612
|
+
task.pausedByAgentId = updates.pausedByAgentId;
|
|
34613
|
+
}
|
|
34580
34614
|
if (updates.assigneeUserId === null) {
|
|
34581
34615
|
task.assigneeUserId = void 0;
|
|
34582
34616
|
} else if (updates.assigneeUserId !== void 0) {
|
|
@@ -34815,14 +34849,21 @@ ${newTask.description}
|
|
|
34815
34849
|
* Pause or unpause a task. Paused tasks are excluded from all automated
|
|
34816
34850
|
* agent and scheduler interaction. Logs the action and emits `task:updated`.
|
|
34817
34851
|
*/
|
|
34818
|
-
async pauseTask(id, paused, runContext) {
|
|
34852
|
+
async pauseTask(id, paused, runContext, agentOptions) {
|
|
34819
34853
|
return this.withTaskLock(id, async () => {
|
|
34820
34854
|
const dir = this.taskDir(id);
|
|
34821
34855
|
const task = await this.readTaskJson(dir);
|
|
34822
34856
|
if (!task.log) {
|
|
34823
34857
|
task.log = [];
|
|
34824
34858
|
}
|
|
34859
|
+
const previousPausedByAgentId = task.pausedByAgentId;
|
|
34825
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
|
+
}
|
|
34826
34867
|
if (task.column === "in-progress" || task.column === "in-review") {
|
|
34827
34868
|
task.status = paused ? "paused" : void 0;
|
|
34828
34869
|
}
|
|
@@ -34830,7 +34871,7 @@ ${newTask.description}
|
|
|
34830
34871
|
task.updatedAt = now;
|
|
34831
34872
|
const logEntry = {
|
|
34832
34873
|
timestamp: now,
|
|
34833
|
-
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"
|
|
34834
34875
|
};
|
|
34835
34876
|
if (runContext) {
|
|
34836
34877
|
logEntry.runContext = runContext;
|
|
@@ -39981,10 +40022,17 @@ var init_docker_client = __esm({
|
|
|
39981
40022
|
}
|
|
39982
40023
|
return this.createDockerInstance(hostConfig);
|
|
39983
40024
|
}
|
|
39984
|
-
async getContainerInfo(containerId) {
|
|
40025
|
+
async getContainerInfo(containerId, hostConfig) {
|
|
39985
40026
|
try {
|
|
39986
|
-
const docker = await this.
|
|
40027
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
39987
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
|
+
}, {});
|
|
39988
40036
|
return {
|
|
39989
40037
|
id: inspect.Id,
|
|
39990
40038
|
name: (inspect.Name ?? "").replace(/^\//, ""),
|
|
@@ -39996,8 +40044,12 @@ var init_docker_client = __esm({
|
|
|
39996
40044
|
paused: Boolean(inspect.State?.Paused),
|
|
39997
40045
|
restarting: Boolean(inspect.State?.Restarting),
|
|
39998
40046
|
dead: Boolean(inspect.State?.Dead),
|
|
39999
|
-
error: inspect.State?.Error || void 0
|
|
40000
|
-
|
|
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
|
|
40001
40053
|
};
|
|
40002
40054
|
} catch (error) {
|
|
40003
40055
|
const message = toErrorMessage(error);
|
|
@@ -40007,6 +40059,18 @@ var init_docker_client = __esm({
|
|
|
40007
40059
|
throw error;
|
|
40008
40060
|
}
|
|
40009
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
|
+
}
|
|
40010
40074
|
async getInstance() {
|
|
40011
40075
|
if (!this.dockerInstance) {
|
|
40012
40076
|
this.dockerInstance = await this.createDockerInstance(this.defaultHostConfig);
|
|
@@ -54525,6 +54589,9 @@ function normalizePattern(pattern) {
|
|
|
54525
54589
|
function isExclusionPattern(pattern) {
|
|
54526
54590
|
return pattern.startsWith("-");
|
|
54527
54591
|
}
|
|
54592
|
+
function bareSkillName(name) {
|
|
54593
|
+
return name.replace(/\/SKILL\.md$/i, "");
|
|
54594
|
+
}
|
|
54528
54595
|
function resolveSessionSkills(context) {
|
|
54529
54596
|
const { requestedSkillNames } = context;
|
|
54530
54597
|
const projectRootDir = resolveProjectRoot(context.projectRootDir);
|
|
@@ -54618,25 +54685,40 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54618
54685
|
const hasRequestedNames = Boolean(requestedSkillNames && requestedSkillNames.length > 0);
|
|
54619
54686
|
const hasExcluded = excludedSkillPaths.size > 0;
|
|
54620
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
|
+
};
|
|
54621
54701
|
if (hasRequestedNames) {
|
|
54622
|
-
const
|
|
54702
|
+
const requestedBareNamesLower = new Set(requestedSkillNames.map((n) => bareSkillName(n).toLowerCase()));
|
|
54623
54703
|
filteredSkills = base.skills.filter(
|
|
54624
|
-
(skill) =>
|
|
54704
|
+
(skill) => requestedBareNamesLower.has(bareSkillName(skill.name).toLowerCase()) && !isExcluded(skill)
|
|
54625
54705
|
);
|
|
54626
54706
|
} else if (hasPatterns) {
|
|
54627
54707
|
filteredSkills = base.skills.filter(
|
|
54628
|
-
(skill) =>
|
|
54708
|
+
(skill) => isAllowed(skill) && !isExcluded(skill)
|
|
54629
54709
|
);
|
|
54630
54710
|
} else if (hasExcluded) {
|
|
54631
|
-
filteredSkills = base.skills.filter((skill) => !
|
|
54711
|
+
filteredSkills = base.skills.filter((skill) => !isExcluded(skill));
|
|
54632
54712
|
} else {
|
|
54633
54713
|
filteredSkills = base.skills;
|
|
54634
54714
|
}
|
|
54635
54715
|
const newDiagnostics = [];
|
|
54636
54716
|
const purpose = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
54637
|
-
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);
|
|
54638
54720
|
for (const excludedPath of excludedSkillPaths) {
|
|
54639
|
-
if (
|
|
54721
|
+
if (hasDiscoveredMatch(excludedPath)) {
|
|
54640
54722
|
newDiagnostics.push({
|
|
54641
54723
|
type: "warning",
|
|
54642
54724
|
message: `Skill at '${excludedPath}' exists but is disabled by project execution settings${purpose}`,
|
|
@@ -54645,7 +54727,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54645
54727
|
}
|
|
54646
54728
|
}
|
|
54647
54729
|
for (const allowedPath of allowedSkillPaths) {
|
|
54648
|
-
if (!
|
|
54730
|
+
if (!hasDiscoveredMatch(allowedPath)) {
|
|
54649
54731
|
newDiagnostics.push({
|
|
54650
54732
|
type: "warning",
|
|
54651
54733
|
message: `Configured skill pattern '${allowedPath}' not found in discovered skills${purpose}`,
|
|
@@ -54654,9 +54736,9 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
54654
54736
|
}
|
|
54655
54737
|
}
|
|
54656
54738
|
if (requestedSkillNames) {
|
|
54657
|
-
const
|
|
54739
|
+
const discoveredBareNamesLower = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
54658
54740
|
for (const requestedName of requestedSkillNames) {
|
|
54659
|
-
if (!
|
|
54741
|
+
if (!discoveredBareNamesLower.has(bareSkillName(requestedName).toLowerCase()) && !isBuiltInFallbackRequest(requestedName)) {
|
|
54660
54742
|
const purpose2 = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
54661
54743
|
newDiagnostics.push({
|
|
54662
54744
|
type: "warning",
|
|
@@ -55010,6 +55092,11 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
55010
55092
|
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
55011
55093
|
}
|
|
55012
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
|
+
}
|
|
55013
55100
|
throw new Error(stateError);
|
|
55014
55101
|
}
|
|
55015
55102
|
}
|
|
@@ -58191,9 +58278,18 @@ function normalizeAgentSkills(metadataSkills) {
|
|
|
58191
58278
|
name = namedEntry.trim();
|
|
58192
58279
|
}
|
|
58193
58280
|
}
|
|
58194
|
-
if (name && name.length > 0
|
|
58195
|
-
|
|
58196
|
-
|
|
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
|
+
}
|
|
58197
58293
|
}
|
|
58198
58294
|
}
|
|
58199
58295
|
return result;
|
|
@@ -68816,20 +68912,26 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68816
68912
|
if (from !== "in-review" && from !== "done") {
|
|
68817
68913
|
return task;
|
|
68818
68914
|
}
|
|
68819
|
-
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";
|
|
68820
68916
|
if (!hasMergeEvidence) {
|
|
68821
68917
|
return task;
|
|
68822
68918
|
}
|
|
68823
68919
|
return this.cleanupMergeStateForReverification(
|
|
68824
68920
|
task,
|
|
68825
|
-
`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
|
+
}
|
|
68826
68928
|
);
|
|
68827
68929
|
}
|
|
68828
|
-
async cleanupMergeStateForReverification(task, logMessage) {
|
|
68930
|
+
async cleanupMergeStateForReverification(task, logMessage, options) {
|
|
68829
68931
|
await this.store.updateTask(task.id, {
|
|
68830
68932
|
mergeDetails: null,
|
|
68831
68933
|
mergeRetries: 0,
|
|
68832
|
-
verificationFailureCount: 0,
|
|
68934
|
+
verificationFailureCount: options?.preserveVerificationFailureCount ? task.verificationFailureCount ?? 0 : 0,
|
|
68833
68935
|
workflowStepResults: []
|
|
68834
68936
|
});
|
|
68835
68937
|
const refreshedTask = await this.store.getTask(task.id);
|
|
@@ -69424,7 +69526,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69424
69526
|
};
|
|
69425
69527
|
const audit = createRunAuditor(this.store, engineRunContext);
|
|
69426
69528
|
const activeColumns = /* @__PURE__ */ new Set(["in-progress", "in-review", "done"]);
|
|
69427
|
-
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
69529
|
+
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
69428
69530
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
69429
69531
|
if (!isActiveTask) {
|
|
69430
69532
|
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
@@ -69763,7 +69865,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69763
69865
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}):
|
|
69764
69866
|
${summary}`,
|
|
69765
69867
|
`Verification (${failedType})`,
|
|
69766
|
-
`Deterministic verification failed (${failedType})
|
|
69868
|
+
`Deterministic verification failed (${failedType})`,
|
|
69869
|
+
true,
|
|
69870
|
+
true
|
|
69767
69871
|
);
|
|
69768
69872
|
return;
|
|
69769
69873
|
}
|
|
@@ -69809,7 +69913,9 @@ ${summary}`,
|
|
|
69809
69913
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}) after ${maxFixRetries} fix attempts:
|
|
69810
69914
|
${summary}`,
|
|
69811
69915
|
`Verification (${failedType})`,
|
|
69812
|
-
`Deterministic verification failed after ${maxFixRetries} fix attempts
|
|
69916
|
+
`Deterministic verification failed after ${maxFixRetries} fix attempts`,
|
|
69917
|
+
true,
|
|
69918
|
+
true
|
|
69813
69919
|
);
|
|
69814
69920
|
return;
|
|
69815
69921
|
}
|
|
@@ -71498,7 +71604,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
71498
71604
|
* Injects failure feedback into PROMPT.md, resets steps, clears session,
|
|
71499
71605
|
* and schedules a move to todo → in-progress after the executing guard clears.
|
|
71500
71606
|
*/
|
|
71501
|
-
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true) {
|
|
71607
|
+
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false) {
|
|
71502
71608
|
const taskId = task.id;
|
|
71503
71609
|
this.clearCompletedTaskWatchdog(taskId);
|
|
71504
71610
|
await this.store.addTaskComment(
|
|
@@ -71517,7 +71623,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
71517
71623
|
const updatedTask = await this.store.getTask(taskId);
|
|
71518
71624
|
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
71519
71625
|
await this.store.updateTask(taskId, {
|
|
71520
|
-
status: null,
|
|
71626
|
+
status: mergeVerificationFailure ? "merging-fix" : null,
|
|
71521
71627
|
error: null,
|
|
71522
71628
|
sessionFile: null,
|
|
71523
71629
|
workflowStepRetries: 0
|
|
@@ -79886,14 +79992,15 @@ var init_self_healing = __esm({
|
|
|
79886
79992
|
execAsync7 = promisify9(exec9);
|
|
79887
79993
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
79888
79994
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
79889
|
-
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
79995
|
+
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
79890
79996
|
NON_TERMINAL_STEP_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in-progress"]);
|
|
79891
79997
|
GHOST_REVIEW_PRESERVED_STATUSES = /* @__PURE__ */ new Set([
|
|
79892
79998
|
"failed",
|
|
79893
79999
|
"awaiting-user-review",
|
|
79894
80000
|
"awaiting-approval",
|
|
79895
80001
|
"merging",
|
|
79896
|
-
"merging-pr"
|
|
80002
|
+
"merging-pr",
|
|
80003
|
+
"merging-fix"
|
|
79897
80004
|
]);
|
|
79898
80005
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
79899
80006
|
MAX_TASK_DONE_RETRIES = 3;
|
|
@@ -80616,7 +80723,7 @@ var init_self_healing = __esm({
|
|
|
80616
80723
|
*
|
|
80617
80724
|
* Preserved statuses (skipped):
|
|
80618
80725
|
* - `awaiting-user-review`, `awaiting-approval`: explicit human handoff
|
|
80619
|
-
* - `merging`, `merging-pr`: handled by `recoverInterruptedMergingTasks`
|
|
80726
|
+
* - `merging`, `merging-pr`, `merging-fix`: handled by `recoverInterruptedMergingTasks`
|
|
80620
80727
|
*
|
|
80621
80728
|
* Rate-limiting comes from the `updatedAt >= taskStuckTimeoutMs` gate —
|
|
80622
80729
|
* each kick refreshes `updatedAt`, so a task that re-enters review and gets
|
|
@@ -86232,7 +86339,7 @@ ${detail}`
|
|
|
86232
86339
|
"agent"
|
|
86233
86340
|
);
|
|
86234
86341
|
await store.updateTask(taskId, {
|
|
86235
|
-
status:
|
|
86342
|
+
status: "merging-fix",
|
|
86236
86343
|
mergeRetries: 0,
|
|
86237
86344
|
error: null,
|
|
86238
86345
|
verificationFailureCount: nextBounces
|
|
@@ -86240,10 +86347,10 @@ ${detail}`
|
|
|
86240
86347
|
await store.moveTask(taskId, "in-progress");
|
|
86241
86348
|
await store.logEntry(
|
|
86242
86349
|
taskId,
|
|
86243
|
-
`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`
|
|
86244
86351
|
);
|
|
86245
86352
|
runtimeLog.log(
|
|
86246
|
-
`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`
|
|
86247
86354
|
);
|
|
86248
86355
|
} catch {
|
|
86249
86356
|
runtimeLog.error(
|
|
@@ -97230,10 +97337,18 @@ var init_claude_cli_probe = __esm({
|
|
|
97230
97337
|
}
|
|
97231
97338
|
});
|
|
97232
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
|
+
|
|
97233
97347
|
// ../dashboard/src/droid-cli-probe.ts
|
|
97234
97348
|
var init_droid_cli_probe = __esm({
|
|
97235
97349
|
"../dashboard/src/droid-cli-probe.ts"() {
|
|
97236
97350
|
"use strict";
|
|
97351
|
+
init_probe3();
|
|
97237
97352
|
}
|
|
97238
97353
|
});
|
|
97239
97354
|
|
|
@@ -103962,9 +104077,12 @@ async function getResearchAvailability(store) {
|
|
|
103962
104077
|
}
|
|
103963
104078
|
const backend = resolved.searchProvider ?? settings.researchWebSearchProvider;
|
|
103964
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;
|
|
103965
|
-
if (!
|
|
104080
|
+
if (!backend) {
|
|
103966
104081
|
return { ok: false, code: "provider-unavailable", message: "Research provider is not configured. Set research provider credentials in Settings." };
|
|
103967
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
|
+
}
|
|
103968
104086
|
return { ok: true };
|
|
103969
104087
|
}
|
|
103970
104088
|
function toResearchRunDetails(run) {
|
|
@@ -104355,12 +104473,13 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
104355
104473
|
pi.registerTool({
|
|
104356
104474
|
name: "fn_task_retry",
|
|
104357
104475
|
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)",
|
|
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)",
|
|
104360
104478
|
promptGuidelines: [
|
|
104361
|
-
"Use when a task has failed and needs to be retried
|
|
104362
|
-
"Only tasks in 'failed' state can be retried",
|
|
104363
|
-
"
|
|
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"
|
|
104364
104483
|
],
|
|
104365
104484
|
parameters: Type8.Object({
|
|
104366
104485
|
id: Type8.String({ description: "Task ID to retry (e.g. FN-001). Must be in 'failed' state." })
|
|
@@ -104384,6 +104503,14 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
104384
104503
|
details: { taskId: params.id, currentStatus: task.status }
|
|
104385
104504
|
};
|
|
104386
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
|
+
}
|
|
104387
104514
|
await store.updateTask(params.id, { status: null, error: null });
|
|
104388
104515
|
await store.moveTask(params.id, "todo");
|
|
104389
104516
|
await store.logEntry(params.id, "Retry requested via Fusion extension", "Task reset to todo for retry");
|
|
@@ -104875,7 +105002,7 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104875
105002
|
pi.registerTool({
|
|
104876
105003
|
name: "fn_research_cancel",
|
|
104877
105004
|
label: "fn: Cancel Research Run",
|
|
104878
|
-
description: "Cancel
|
|
105005
|
+
description: "Cancel an in-flight research run. Terminal runs return INVALID_TRANSITION.",
|
|
104879
105006
|
parameters: Type8.Object({ id: Type8.String({ description: "Research run ID" }) }),
|
|
104880
105007
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
104881
105008
|
const store = await getStore2(ctx.cwd);
|
|
@@ -104887,6 +105014,17 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104887
105014
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
104888
105015
|
};
|
|
104889
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
|
+
}
|
|
104890
105028
|
const updated = researchStore.requestCancellation(params.id);
|
|
104891
105029
|
return {
|
|
104892
105030
|
content: [{ type: "text", text: `Requested cancellation for research run ${params.id} (status: ${updated.status}).` }],
|
|
@@ -104894,6 +105032,41 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
|
|
|
104894
105032
|
};
|
|
104895
105033
|
}
|
|
104896
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
|
+
});
|
|
104897
105070
|
pi.registerTool({
|
|
104898
105071
|
name: "fn_insight_list",
|
|
104899
105072
|
label: "fn: List Insights",
|
|
@@ -105590,6 +105763,278 @@ Status: ${updated.status}`
|
|
|
105590
105763
|
};
|
|
105591
105764
|
}
|
|
105592
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
|
+
});
|
|
105593
106038
|
pi.registerTool({
|
|
105594
106039
|
name: "fn_skills_search",
|
|
105595
106040
|
label: "FN: Search Skills",
|