@alexeiled/pi-fusion 0.5.2 → 0.6.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/README.md +24 -11
- package/agents/fusion-composer.md +49 -0
- package/agents/fusion-judge.md +7 -0
- package/agents/fusion-panelist-full.md +53 -0
- package/agents/fusion-panelist-web.md +41 -0
- package/agents/fusion-panelist.md +2 -0
- package/docs/user-guide.md +208 -21
- package/package.json +1 -1
- package/skills/fusion-review/SKILL.md +69 -18
- package/src/commands.ts +1 -0
- package/src/config.ts +167 -5
- package/src/fusion-args.ts +36 -2
- package/src/index.ts +11 -2
- package/src/orchestrator.ts +146 -19
- package/src/panel-completion.ts +17 -6
- package/src/report.ts +233 -78
- package/src/result-extract.ts +15 -1
- package/src/run-builder.ts +298 -153
- package/src/run-store.ts +31 -0
- package/src/types.ts +89 -1
|
@@ -1,34 +1,85 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fusion-review
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Run a pi-fusion review: several models answer in parallel, then one synthesis
|
|
5
|
+
step returns a single report. Use when the user says "invoke fusion", "run a
|
|
6
|
+
fusion panel", "discuss this through fusion", "get multi-model opinions",
|
|
7
|
+
"panel review", "compare <model> and <model> on this", or asks to cover
|
|
8
|
+
something from several angles ("what did we miss", "audit this release").
|
|
9
|
+
Call the start_fusion_review tool with the topic. NOT for routine edits,
|
|
10
|
+
formatting, or one-step fixes. NOT for arguing one claim from both sides (use
|
|
11
|
+
dialectic) and NOT for open-ended idea generation (use brainstorming) —
|
|
12
|
+
Fusion panelists work independently and never talk to each other.
|
|
8
13
|
---
|
|
9
14
|
|
|
10
15
|
# Fusion Review
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
Call `start_fusion_review` so the user does not have to type `/fusion`.
|
|
13
18
|
|
|
14
19
|
## When to use
|
|
15
20
|
|
|
16
|
-
Use Fusion
|
|
21
|
+
Use Fusion when independent perspectives are worth the extra latency:
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
- a hard decision or a design tradeoff
|
|
24
|
+
- risk or release review
|
|
25
|
+
- tricky debugging
|
|
26
|
+
- a research-heavy question
|
|
27
|
+
- a breadth sweep, such as an audit or "what did we miss"
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- "get a panel review of …"
|
|
23
|
-
- "use multi-model review for …"
|
|
24
|
-
- "discuss this through fusion"
|
|
25
|
-
- Any prompt that implies parallel model review or multi-perspective analysis
|
|
29
|
+
Do not use Fusion for routine edits, formatting, obvious one-step fixes, or
|
|
30
|
+
simple questions. Keep those on the normal Pi path.
|
|
26
31
|
|
|
27
|
-
##
|
|
32
|
+
## Not this skill
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
- The user wants one claim argued for and against. That is `dialectic`.
|
|
35
|
+
- The user wants options generated, or a draft plan stress-tested. That is
|
|
36
|
+
`brainstorming-ideas`.
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
Fusion differs from both. Panelists run in isolation and never see the answers of
|
|
39
|
+
the others. The value is uncorrelated errors, not debate.
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
## How to call it
|
|
42
|
+
|
|
43
|
+
| Parameter | Required | Use |
|
|
44
|
+
| --------- | -------- | ------------------------------------------------------------- |
|
|
45
|
+
| `prompt` | yes | The topic, question, or code excerpt |
|
|
46
|
+
| `profile` | no | A named profile that the user already has. Omit it otherwise. |
|
|
47
|
+
| `panel` | no | Models for this run only. Use it when the user names models. |
|
|
48
|
+
|
|
49
|
+
Each `panel` entry is `<model>` or `<agent>:<model>`. For example: `opus`, or
|
|
50
|
+
`pi-fusion.fusion-panelist-web:gpt-5.5`.
|
|
51
|
+
|
|
52
|
+
Pass `panel` only when the user names the models. If the user asks for a kind of
|
|
53
|
+
review but names no model, omit `panel` and let the profile decide.
|
|
54
|
+
|
|
55
|
+
Do not pass a `profile` name that you have not seen in the config of the user.
|
|
56
|
+
An unknown name fails the run.
|
|
57
|
+
|
|
58
|
+
## Two shapes of report
|
|
59
|
+
|
|
60
|
+
The shape follows the profile. You do not select it, and there is no flag.
|
|
61
|
+
|
|
62
|
+
**Select** is the default. Every panelist answers the whole question. The judge
|
|
63
|
+
compares the answers and reports consensus, disagreements, contested claims, and
|
|
64
|
+
blind spots. This fits a decision question, where the failure mode is the bad
|
|
65
|
+
reasoning path of one model.
|
|
66
|
+
|
|
67
|
+
**Merge** happens when the profile gives its members a `question` field. Each
|
|
68
|
+
panelist then answers a different facet. A composer unions the answers into a
|
|
69
|
+
coverage map, a combined answer, and a list of gaps. This fits a breadth
|
|
70
|
+
question, where the failure mode is incomplete coverage.
|
|
71
|
+
|
|
72
|
+
If the user asks for a multi-angle audit and has no faceted profile, run the
|
|
73
|
+
default panel. Then tell them that a profile with `question` fields covers this
|
|
74
|
+
better.
|
|
75
|
+
|
|
76
|
+
## After the call
|
|
77
|
+
|
|
78
|
+
The tool returns at once. The panel and the synthesis step run in the background,
|
|
79
|
+
and Fusion posts the report when they finish.
|
|
80
|
+
|
|
81
|
+
- Do not call the tool again while a run is active. It returns a conflict with
|
|
82
|
+
the id of the active run.
|
|
83
|
+
- Do not summarize or predict the report. Wait for it.
|
|
84
|
+
- To show progress or stop a run, tell the user to type `/fusion status` or
|
|
85
|
+
`/fusion stop`. No tool does this.
|
package/src/commands.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { applyClaudeAliasShorthand } from "./claude-aliases.js";
|
|
5
5
|
import { FusionConfigError } from "./errors.js";
|
|
6
6
|
import {
|
|
7
|
+
JUDGE_AGENT,
|
|
8
|
+
PANEL_AGENT,
|
|
7
9
|
THINKING_LEVELS,
|
|
8
10
|
type FusionConfig,
|
|
9
11
|
type FusionContextMode,
|
|
@@ -11,6 +13,7 @@ import {
|
|
|
11
13
|
type JudgeConfig,
|
|
12
14
|
type PanelMemberConfig,
|
|
13
15
|
type ThinkingLevel,
|
|
16
|
+
type ToolBudget,
|
|
14
17
|
} from "./types.js";
|
|
15
18
|
import {
|
|
16
19
|
isNodeErrorCode,
|
|
@@ -21,8 +24,43 @@ import {
|
|
|
21
24
|
|
|
22
25
|
export const FUSION_CONFIG_FILE = "fusion.json";
|
|
23
26
|
export const DEFAULT_PROFILE_NAME = "quality";
|
|
24
|
-
export
|
|
25
|
-
|
|
27
|
+
export {
|
|
28
|
+
COMPOSER_AGENT,
|
|
29
|
+
JUDGE_AGENT,
|
|
30
|
+
PANEL_AGENT,
|
|
31
|
+
PANEL_AGENT_FULL,
|
|
32
|
+
PANEL_AGENT_WEB,
|
|
33
|
+
} from "./types.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tool names Fusion's bundled agents may declare: Pi core child tools plus the
|
|
37
|
+
* tools contributed by `pi-web-providers`. A name outside this set resolves to
|
|
38
|
+
* nothing at runtime, so a typo is only discovered after a panel run is spent.
|
|
39
|
+
* Single source of truth for `test/unit/agents.test.ts`.
|
|
40
|
+
*/
|
|
41
|
+
export const KNOWN_TOOL_NAMES: readonly string[] = [
|
|
42
|
+
"read",
|
|
43
|
+
"bash",
|
|
44
|
+
"edit",
|
|
45
|
+
"write",
|
|
46
|
+
"grep",
|
|
47
|
+
"find",
|
|
48
|
+
"ls",
|
|
49
|
+
"web_search",
|
|
50
|
+
"web_contents",
|
|
51
|
+
"web_answer",
|
|
52
|
+
"web_research",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Agent references are dot-separated (`<package>.<name>`, or a bare local name).
|
|
57
|
+
* This deliberately does not enforce the lowercase `IDENTIFIER_PATTERN` that
|
|
58
|
+
* pi-subagents applies to package names: that pattern does not cover the
|
|
59
|
+
* frontmatter `name`, and rejecting a config that would actually run is worse
|
|
60
|
+
* than accepting a malformed one. It catches the real typo classes only —
|
|
61
|
+
* embedded whitespace, empty segments, leading or trailing dots.
|
|
62
|
+
*/
|
|
63
|
+
const AGENT_REFERENCE_PATTERN = /^[^\s.]+(?:\.[^\s.]+)*$/;
|
|
26
64
|
|
|
27
65
|
export interface FusionConfigLoadContext {
|
|
28
66
|
cwd: string;
|
|
@@ -138,6 +176,89 @@ export function resolveProfile(
|
|
|
138
176
|
return { name, profile };
|
|
139
177
|
}
|
|
140
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Splits an inline `--panel` entry into agent and model.
|
|
181
|
+
*
|
|
182
|
+
* Two shapes collide on `:` — an agent-qualified entry
|
|
183
|
+
* (`pi-fusion.fusion-panelist-web:gpt-5.5`) and a model with a thinking suffix
|
|
184
|
+
* (`gpt-4.1:high`). A dot in the prefix is not enough to tell them apart:
|
|
185
|
+
* dotted model versions like `gpt-4.1`, `claude-3.5-haiku`, and
|
|
186
|
+
* `gemini-2.5-pro` are common. The suffix decides — if it is a thinking level,
|
|
187
|
+
* the whole entry is a model.
|
|
188
|
+
*/
|
|
189
|
+
export function splitInlinePanelEntry(entry: string): {
|
|
190
|
+
agent: string;
|
|
191
|
+
model: string;
|
|
192
|
+
} {
|
|
193
|
+
const trimmed = entry.trim();
|
|
194
|
+
const separator = trimmed.indexOf(":");
|
|
195
|
+
if (separator > 0) {
|
|
196
|
+
const prefix = trimmed.slice(0, separator);
|
|
197
|
+
const rest = trimmed.slice(separator + 1).trim();
|
|
198
|
+
if (
|
|
199
|
+
prefix.includes(".") &&
|
|
200
|
+
!prefix.includes("/") &&
|
|
201
|
+
rest &&
|
|
202
|
+
!isThinkingLevel(rest)
|
|
203
|
+
) {
|
|
204
|
+
return { agent: prefix, model: rest };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { agent: PANEL_AGENT, model: trimmed };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Builds an ephemeral profile from `--panel`. The resolved profile still
|
|
212
|
+
* supplies the judge and every other setting; only the panel is replaced.
|
|
213
|
+
*/
|
|
214
|
+
export function buildInlinePanelProfile(
|
|
215
|
+
base: FusionProfile,
|
|
216
|
+
entries: readonly string[],
|
|
217
|
+
): FusionProfile {
|
|
218
|
+
const usedIds = new Set<string>();
|
|
219
|
+
const panel = entries.map((entry, index) => {
|
|
220
|
+
const { agent, model } = splitInlinePanelEntry(entry);
|
|
221
|
+
if (!model) {
|
|
222
|
+
throw new FusionConfigError(
|
|
223
|
+
`Inline panel entry "${entry}" has no model.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (!isAgentReference(agent)) {
|
|
227
|
+
throw new FusionConfigError(
|
|
228
|
+
`Inline panel entry "${entry}" has a malformed agent reference.`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
id: uniqueInlineId(model, index, usedIds),
|
|
233
|
+
label: model,
|
|
234
|
+
agent,
|
|
235
|
+
model,
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
// Inline members have no `question`, so they all answer the whole task.
|
|
239
|
+
// Carrying `synthesis: "merge"` over from the base profile would tell the
|
|
240
|
+
// composer to merge facets that do not exist.
|
|
241
|
+
const { synthesis: _dropped, ...rest } = base;
|
|
242
|
+
return { ...rest, panel };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function uniqueInlineId(
|
|
246
|
+
model: string,
|
|
247
|
+
index: number,
|
|
248
|
+
used: Set<string>,
|
|
249
|
+
): string {
|
|
250
|
+
const base =
|
|
251
|
+
model
|
|
252
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
253
|
+
.replace(/^_+|_+$/g, "")
|
|
254
|
+
.toLowerCase() || `panel_${index + 1}`;
|
|
255
|
+
let candidate = base;
|
|
256
|
+
let suffix = 2;
|
|
257
|
+
while (used.has(candidate)) candidate = `${base}_${suffix++}`;
|
|
258
|
+
used.add(candidate);
|
|
259
|
+
return candidate;
|
|
260
|
+
}
|
|
261
|
+
|
|
141
262
|
export async function writeProjectFusionConfigTemplate(
|
|
142
263
|
cwd: string,
|
|
143
264
|
deps: FileWriteDeps = {},
|
|
@@ -209,24 +330,65 @@ function isFusionProfile(value: unknown): value is FusionProfile {
|
|
|
209
330
|
) {
|
|
210
331
|
return false;
|
|
211
332
|
}
|
|
333
|
+
if (
|
|
334
|
+
value.blindPanelLabels !== undefined &&
|
|
335
|
+
typeof value.blindPanelLabels !== "boolean"
|
|
336
|
+
) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (
|
|
340
|
+
value.judgeToolBudget !== undefined &&
|
|
341
|
+
!isToolBudget(value.judgeToolBudget)
|
|
342
|
+
) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
if (
|
|
346
|
+
value.synthesis !== undefined &&
|
|
347
|
+
value.synthesis !== "select" &&
|
|
348
|
+
value.synthesis !== "merge"
|
|
349
|
+
) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function isToolBudget(value: unknown): value is ToolBudget {
|
|
356
|
+
if (!isRecord(value)) return false;
|
|
357
|
+
if (value.soft !== undefined && !isPositiveInteger(value.soft)) return false;
|
|
358
|
+
if (value.hard !== undefined && !isPositiveInteger(value.hard)) return false;
|
|
359
|
+
if (value.soft === undefined && value.hard === undefined) return false;
|
|
360
|
+
if (
|
|
361
|
+
isPositiveInteger(value.soft) &&
|
|
362
|
+
isPositiveInteger(value.hard) &&
|
|
363
|
+
value.soft > value.hard
|
|
364
|
+
) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
212
367
|
return true;
|
|
213
368
|
}
|
|
214
369
|
|
|
370
|
+
export function isAgentReference(value: unknown): value is string {
|
|
371
|
+
return isNonEmptyString(value) && AGENT_REFERENCE_PATTERN.test(value.trim());
|
|
372
|
+
}
|
|
373
|
+
|
|
215
374
|
function isPanelMemberConfig(value: unknown): value is PanelMemberConfig {
|
|
216
375
|
if (!isRecord(value)) return false;
|
|
217
376
|
if (!isNonEmptyString(value.id)) return false;
|
|
218
|
-
if (!isNonEmptyString(value.label)) return false;
|
|
219
|
-
if (!
|
|
377
|
+
if (value.label !== undefined && !isNonEmptyString(value.label)) return false;
|
|
378
|
+
if (!isAgentReference(value.agent)) return false;
|
|
220
379
|
if (value.model !== undefined && !isNonEmptyString(value.model)) return false;
|
|
221
380
|
if (value.thinking !== undefined && !isThinkingLevel(value.thinking))
|
|
222
381
|
return false;
|
|
223
382
|
if (value.role !== undefined && typeof value.role !== "string") return false;
|
|
383
|
+
if (value.question !== undefined && !isNonEmptyString(value.question)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
224
386
|
return true;
|
|
225
387
|
}
|
|
226
388
|
|
|
227
389
|
function isJudgeConfig(value: unknown): value is JudgeConfig {
|
|
228
390
|
if (!isRecord(value)) return false;
|
|
229
|
-
if (!
|
|
391
|
+
if (!isAgentReference(value.agent)) return false;
|
|
230
392
|
if (value.model !== undefined && !isNonEmptyString(value.model)) return false;
|
|
231
393
|
if (value.thinking !== undefined && !isThinkingLevel(value.thinking))
|
|
232
394
|
return false;
|
package/src/fusion-args.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { FusionArgsError } from "./errors.js";
|
|
|
2
2
|
import type { ParsedFusionArgs } from "./types.js";
|
|
3
3
|
|
|
4
4
|
const FUSION_USAGE =
|
|
5
|
-
"Usage: /fusion <prompt> | /fusion --profile <name> <prompt> | /fusion status | /fusion stop | /fusion init.";
|
|
5
|
+
"Usage: /fusion <prompt> | /fusion --profile <name> <prompt> | /fusion --panel <models> <prompt> | /fusion status | /fusion stop | /fusion init.";
|
|
6
6
|
|
|
7
7
|
export type FusionInlineCommand = "init" | "status" | "stop";
|
|
8
8
|
|
|
@@ -27,12 +27,31 @@ export function parseFusionArgs(
|
|
|
27
27
|
if (tokens[0] === "/fusion" || tokens[0] === "fusion") tokens.shift();
|
|
28
28
|
|
|
29
29
|
let profile: string | undefined;
|
|
30
|
+
let panel: string[] | undefined;
|
|
30
31
|
const promptTokens: string[] = [];
|
|
31
32
|
|
|
32
33
|
for (let index = 0; index < tokens.length; index++) {
|
|
33
34
|
const token = tokens[index];
|
|
34
35
|
if (!token) continue;
|
|
35
36
|
|
|
37
|
+
if (promptTokens.length === 0 && token === "--panel") {
|
|
38
|
+
const value = tokens[index + 1];
|
|
39
|
+
if (!value || value.startsWith("-")) {
|
|
40
|
+
throw new FusionArgsError(`Missing value for --panel. ${FUSION_USAGE}`);
|
|
41
|
+
}
|
|
42
|
+
if (panel) throw new FusionArgsError("Panel can only be provided once.");
|
|
43
|
+
panel = parsePanelEntries(value);
|
|
44
|
+
index++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (promptTokens.length === 0 && token.startsWith("--panel=")) {
|
|
49
|
+
const value = token.slice("--panel=".length);
|
|
50
|
+
if (panel) throw new FusionArgsError("Panel can only be provided once.");
|
|
51
|
+
panel = parsePanelEntries(value);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
if (
|
|
37
56
|
promptTokens.length === 0 &&
|
|
38
57
|
(token === "--profile" || token === "-p")
|
|
@@ -72,7 +91,22 @@ export function parseFusionArgs(
|
|
|
72
91
|
|
|
73
92
|
const prompt = promptTokens.join(" ").trim();
|
|
74
93
|
if (!prompt) throw new FusionArgsError(FUSION_USAGE);
|
|
75
|
-
return
|
|
94
|
+
return {
|
|
95
|
+
prompt,
|
|
96
|
+
...(profile ? { profile } : {}),
|
|
97
|
+
...(panel ? { panel } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parsePanelEntries(value: string): string[] {
|
|
102
|
+
const entries = value
|
|
103
|
+
.split(",")
|
|
104
|
+
.map((entry) => entry.trim())
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
if (entries.length === 0) {
|
|
107
|
+
throw new FusionArgsError(`Missing value for --panel. ${FUSION_USAGE}`);
|
|
108
|
+
}
|
|
109
|
+
return entries;
|
|
76
110
|
}
|
|
77
111
|
|
|
78
112
|
export function tokenizeCommandArgs(input: string): string[] {
|
package/src/index.ts
CHANGED
|
@@ -18,22 +18,30 @@ function registerFusionTool(
|
|
|
18
18
|
name: "start_fusion_review",
|
|
19
19
|
label: "Fusion Review",
|
|
20
20
|
description:
|
|
21
|
-
"Start a pi-fusion
|
|
21
|
+
"Start a pi-fusion review. Several models answer in parallel, then one synthesis step returns a single report. Use it for a hard decision, a design tradeoff, a risk or release review, tricky debugging, a research-heavy question, or a breadth sweep such as an audit or 'what did we miss'. Do not use it for routine edits, formatting, or obvious one-step fixes.",
|
|
22
22
|
promptSnippet: "Start a fusion panel review for a topic or code",
|
|
23
23
|
promptGuidelines: [
|
|
24
|
-
"Use start_fusion_review only for hard decisions, design tradeoffs, risk review, tricky debugging,
|
|
24
|
+
"Use start_fusion_review only for hard decisions, design tradeoffs, risk review, tricky debugging, research-heavy questions, or breadth sweeps such as audits. Do not use it for routine edits, formatting, or obvious one-step fixes.",
|
|
25
|
+
"Pass panel only when the user names the models to compare. Otherwise omit it and let the profile decide.",
|
|
25
26
|
],
|
|
26
27
|
parameters: Type.Object({
|
|
27
28
|
prompt: Type.String({ description: "What to review or discuss" }),
|
|
28
29
|
profile: Type.Optional(
|
|
29
30
|
Type.String({ description: "Fusion profile name (optional)" }),
|
|
30
31
|
),
|
|
32
|
+
panel: Type.Optional(
|
|
33
|
+
Type.Array(Type.String(), {
|
|
34
|
+
description:
|
|
35
|
+
"Models to use for this run, overriding the profile panel. Each entry is <model> or <agent>:<model>. Use only when the user names specific models.",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
31
38
|
}),
|
|
32
39
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
33
40
|
const result = await orchestrator.startRun(
|
|
34
41
|
{
|
|
35
42
|
prompt: params.prompt,
|
|
36
43
|
...(params.profile ? { profile: params.profile } : {}),
|
|
44
|
+
...(params.panel?.length ? { panel: params.panel } : {}),
|
|
37
45
|
},
|
|
38
46
|
ctx,
|
|
39
47
|
);
|
|
@@ -48,6 +56,7 @@ function registerFusionTool(
|
|
|
48
56
|
details: {
|
|
49
57
|
prompt: params.prompt,
|
|
50
58
|
profile: params.profile,
|
|
59
|
+
panel: params.panel,
|
|
51
60
|
status: result.status,
|
|
52
61
|
},
|
|
53
62
|
};
|