@aeondave/pi-persona 1.17.1 → 1.17.4
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/AGENTS.md +103 -64
- package/CHANGELOG.md +310 -1
- package/README.md +37 -23
- package/agents/architect.md +21 -0
- package/agents/balthasar.md +17 -22
- package/agents/casper.md +17 -23
- package/agents/evidence-verifier.md +31 -16
- package/agents/melchior.md +17 -22
- package/agents/operator.md +15 -55
- package/agents/research.md +14 -48
- package/agents/reviewer.md +20 -39
- package/agents/scout.md +14 -15
- package/agents/verifier.md +15 -16
- package/docs/ARCHITECTURE.md +280 -92
- package/docs/COLLABORATION.md +110 -0
- package/docs/EXPERIENCE.md +52 -17
- package/docs/MONITORS.md +62 -3
- package/docs/README.md +12 -0
- package/docs/REFERENCE.md +139 -38
- package/docs/SPINE.md +2 -2
- package/docs/STRATEGIES.md +23 -8
- package/docs/TELEMETRY.md +1 -0
- package/flows/deep-review.flow.json +1 -1
- package/package.json +15 -8
- package/personas/audit.md +28 -17
- package/personas/dev.md +30 -32
- package/personas/elite.md +223 -221
- package/personas/judge.md +25 -19
- package/personas/magi.md +26 -29
- package/personas/planner.md +31 -24
- package/personas/researcher.md +25 -26
- package/personas/swarm.md +35 -25
- package/personas/verify.md +30 -19
- package/src/agents/agent.ts +26 -0
- package/src/bridge.ts +28 -10
- package/src/bus/broker/client.ts +119 -21
- package/src/bus/broker/host.ts +123 -8
- package/src/bus/broker/messages.ts +31 -4
- package/src/bus/contact.ts +113 -38
- package/src/bus/peers.ts +15 -0
- package/src/bus/result-reader.ts +88 -0
- package/src/core/brief.ts +436 -353
- package/src/core/capabilities.ts +1 -1
- package/src/core/config.ts +15 -10
- package/src/core/leg-thinking.ts +57 -0
- package/src/core/model-catalog.ts +171 -0
- package/src/core/model-tier.ts +89 -0
- package/src/core/models.ts +13 -4
- package/src/core/monitor-output.ts +85 -0
- package/src/core/monitor.ts +34 -2
- package/src/core/nudge.ts +176 -23
- package/src/core/ownership.ts +1 -2
- package/src/core/pi-compat.ts +5 -12
- package/src/core/seed.ts +99 -6
- package/src/core/session-runs.ts +87 -0
- package/src/core/settings.ts +1 -1
- package/src/core/timer.ts +34 -9
- package/src/core/types.ts +1 -1
- package/src/engine/adapter.ts +95 -27
- package/src/engine/async.ts +213 -155
- package/src/engine/child.ts +12 -4
- package/src/engine/host-module.ts +12 -0
- package/src/engine/inproc.ts +99 -53
- package/src/engine/result-grant.ts +62 -0
- package/src/engine/session-tools.ts +6 -0
- package/src/engine/stream.ts +10 -20
- package/src/engine/worktree.ts +17 -7
- package/src/exocom/collaboration-delivery.ts +29 -0
- package/src/exocom/collaboration.ts +148 -0
- package/src/exocom/envelope.ts +43 -4
- package/src/exocom/gate.ts +7 -2
- package/src/exocom/install.ts +47 -18
- package/src/exocom/ledger.ts +58 -8
- package/src/exocom/plane.ts +32 -1
- package/src/exocom/profile.ts +40 -0
- package/src/exocom/registry.ts +5 -0
- package/src/exocom/status.ts +11 -1
- package/src/exocom/write-guard.ts +7 -0
- package/src/extension/broker-host.ts +61 -9
- package/src/extension/engine.ts +54 -28
- package/src/extension/hooks.ts +952 -868
- package/src/extension/identity.ts +41 -75
- package/src/extension/persona-model-settings.ts +31 -13
- package/src/extension/results.ts +136 -0
- package/src/extension/shared.ts +32 -7
- package/src/extension.ts +2965 -2611
- package/src/loader.ts +11 -3
- package/src/monitor/archive.ts +235 -0
- package/src/monitor/session.ts +21 -1
- package/src/orchestration/flow-journal.ts +33 -6
- package/src/orchestration/flow-run.ts +160 -32
- package/src/orchestration/flow.ts +20 -2
- package/src/orchestration/reducers.ts +9 -5
- package/src/orchestration/render.ts +1 -1
- package/src/orchestration/roster.ts +71 -23
- package/src/orchestration/sdk.ts +61 -11
- package/src/orchestration/strategies/compete.ts +5 -1
- package/src/orchestration/strategies/council-rounds.ts +2 -0
- package/src/orchestration/strategies/critic-loop.ts +3 -0
- package/src/orchestration/strategies/debate.ts +2 -0
- package/src/orchestration/strategies/fanout.ts +3 -2
- package/src/orchestration/strategies/judge.ts +5 -1
- package/src/orchestration/strategies/magi.ts +40 -23
- package/src/orchestration/strategies/map.ts +13 -3
- package/src/orchestration/strategies/pair.ts +2 -0
- package/src/orchestration/strategies/pipeline.ts +2 -0
- package/src/orchestration/strategies/synthesize.ts +27 -7
- package/src/orchestration/strategy.ts +31 -0
- package/src/orchestration/types.ts +10 -0
- package/src/persona/model-participants.ts +20 -7
- package/src/persona/orchestrate.ts +2 -0
- package/src/persona/persona.ts +8 -4
- package/src/results/archive.ts +281 -0
- package/src/results/record.ts +166 -0
- package/src/results/retrieval.ts +142 -0
- package/src/results/types.ts +128 -0
- package/src/telemetry/contract.ts +1 -1
- package/src/tools/background-run.ts +151 -0
- package/src/tools/council.ts +108 -66
- package/src/tools/delegate-tool.ts +349 -283
- package/src/tools/delegate.ts +35 -216
- package/src/tools/exocom-collaboration-input.ts +168 -0
- package/src/tools/exocom-collaboration.ts +315 -0
- package/src/tools/exocom-profile.ts +102 -0
- package/src/tools/exocom-work.ts +18 -53
- package/src/tools/exocom.ts +35 -22
- package/src/tools/flow.ts +103 -30
- package/src/tools/intercom-tool.ts +202 -128
- package/src/tools/intercom.ts +1 -1
- package/src/tools/models.ts +34 -9
- package/src/tools/monitor.ts +58 -6
- package/src/tools/read-result.ts +52 -0
- package/src/tools/timer.ts +34 -15
- package/src/ui/agent-overlay.ts +65 -16
- package/src/ui/agent-tree.ts +127 -19
- package/src/ui/compact-widget.ts +25 -0
- package/src/ui/failure-summary.ts +55 -0
- package/src/ui/literal-tool-result.ts +24 -0
- package/src/ui/model-picker.ts +158 -81
- package/src/ui/orchestration-launch.ts +49 -0
- package/src/ui/persona-settings.ts +9 -3
- package/src/ui/presentation.ts +24 -0
- package/src/ui/report-body.ts +63 -0
- package/src/ui/session-cards.ts +366 -0
- package/src/ui/wake-rows.ts +262 -0
- package/teams.yaml +5 -1
package/src/tools/council.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import { fenceUntrusted } from "../core/fence.ts";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { resultReceiptHint } from "../results/retrieval.ts";
|
|
6
|
+
import { expandDetailHint, failureDetails, formatCouncilCallLabel, isInline, toastCause } from "../extension/shared.ts";
|
|
7
|
+
import { knownParams, missingRequiredParams, strategyCatalog } from "../orchestration/strategy.ts";
|
|
8
|
+
import { memberBaseLabel, parseRuntimeRosterMember, type RosterMember } from "../orchestration/roster.ts";
|
|
8
9
|
import { formatCouncilResult, humanizeAggregateResult } from "../orchestration/render.ts";
|
|
9
10
|
import type { FailureKind } from "../orchestration/types.ts";
|
|
10
11
|
import type { AgentResult } from "../orchestration/types.ts";
|
|
@@ -12,10 +13,12 @@ import { resolveCouncilInvocation } from "../persona/persona.ts";
|
|
|
12
13
|
import type { OrchestrationGrammar, Persona } from "../persona/persona.ts";
|
|
13
14
|
import type { PersonaController } from "../persona/controller.ts";
|
|
14
15
|
import { Text } from "@earendil-works/pi-tui";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { createReportBody } from "../ui/report-body.ts";
|
|
17
|
+
import { orchestrationLaunchLines, type OrchestrationLaunchSnapshot } from "../ui/orchestration-launch.ts";
|
|
18
|
+
import { compactInlineText, compactVisibleText, sanitizeTerminalText } from "../ui/presentation.ts";
|
|
19
|
+
import { toolUsageField } from "../ui/usage.ts";
|
|
18
20
|
import { emptyUsage } from "../engine/stream.ts";
|
|
21
|
+
import { launchReceipt, type BackgroundRuns, type TrackedRun } from "./background-run.ts";
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
export interface CouncilToolDeps {
|
|
@@ -23,16 +26,17 @@ export interface CouncilToolDeps {
|
|
|
23
26
|
set lastCtx(value: ExtensionContext | undefined);
|
|
24
27
|
controller: PersonaController;
|
|
25
28
|
personas: Persona[];
|
|
29
|
+
launchSnapshot(ctx: ExtensionContext, orch: OrchestrationGrammar): OrchestrationLaunchSnapshot;
|
|
26
30
|
runStrategyVisible(
|
|
27
31
|
ctx: ExtensionContext,
|
|
28
32
|
orch: OrchestrationGrammar,
|
|
29
33
|
task: string,
|
|
30
34
|
label: string,
|
|
31
|
-
|
|
35
|
+
tracked?: TrackedRun,
|
|
32
36
|
): Promise<AgentResult | undefined>;
|
|
37
|
+
/** Launch/join: a council is one tracked run, like a delegate leg (see background-run.ts). */
|
|
38
|
+
runs: BackgroundRuns;
|
|
33
39
|
drainBusBlock(): string;
|
|
34
|
-
childUsage: ChildUsageLedger;
|
|
35
|
-
publishPersonaCost(): void;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/** What the card names as the panel: the team, or the ad-hoc members themselves — an
|
|
@@ -55,11 +59,11 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
55
59
|
strategy: Type.Optional(
|
|
56
60
|
Type.String({ description: 'Per-call strategy override (default: the selected or active council profile\'s strategy, or built-in "magi")' }),
|
|
57
61
|
),
|
|
58
|
-
roster: Type.Optional(Type.String({ description: 'Per-call roster override (default: the selected or active profile\'s roster, then its orchestration roster, or built-in "magi")' })),
|
|
62
|
+
roster: Type.Optional(Type.String({ description: 'Per-call roster override (default: the method\'s own team when you switch method; else the selected or active profile\'s roster, then its orchestration roster, or built-in "magi")' })),
|
|
59
63
|
members: Type.Optional(
|
|
60
64
|
Type.Array(Type.Unknown(), {
|
|
61
65
|
description:
|
|
62
|
-
'Ephemeral panel members for THIS call — each an installed agent name ("scout") or an inline { agent, role?, model?, skills? } specialisation. They win `roster` and the profile\'s own members for this call only: no team file, persona file, or inherited model/tool is touched. Omit to use the named roster.',
|
|
66
|
+
'Ephemeral panel members for THIS call — each an installed agent name ("scout") or an inline { agent, role?, model?, thinking?, skills?, tools?, isolation?, mcp? } specialisation. thinking is per-leg requested effort (off/minimal/low/medium/high/xhigh/max); Pi clamps it to model capabilities, overriding the agent and shared child effort defaults. They win `roster` and the profile\'s own members for this call only: no team file, persona file, or inherited model/tool is touched. Omit to use the named roster.',
|
|
63
67
|
}),
|
|
64
68
|
),
|
|
65
69
|
params: Type.Optional(
|
|
@@ -72,30 +76,24 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
72
76
|
pi.registerTool({
|
|
73
77
|
name: "council",
|
|
74
78
|
label: "Council",
|
|
75
|
-
|
|
79
|
+
// The method catalog is generated from the strategy registry (`when`/`roster`/`required`), never hand-written.
|
|
80
|
+
description: `${[
|
|
76
81
|
"Convene a council of specialists with controlled, complementary biases to deliberate a",
|
|
77
|
-
"decision — returns the selected strategy's result,
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"deserve a whole panel. Then
|
|
81
|
-
"surfaces a NEW decision. Do NOT use it
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"(critic-loop); head-to-head argument (debate); many findings merged into ONE deliverable",
|
|
86
|
-
"(synthesize, map); rival implementations judged blind (compete); a single pair (pair).",
|
|
82
|
+
"decision or run a coordinated method over a task — returns the selected strategy's result,",
|
|
83
|
+
"preserving vote tally and dissent when produced.",
|
|
84
|
+
"Scale it to the task: convene when the method adds value — NOT before every decision, and a",
|
|
85
|
+
"routine task does not deserve a whole panel. Then act on the result yourself and re-convene only",
|
|
86
|
+
"when acting surfaces a NEW decision. Do NOT use it for plain independent parallel work: that is",
|
|
87
|
+
"`delegate`. Interactive sessions run a council in the background: you get a run id at once and ONE",
|
|
88
|
+
"follow-up carries the ruling, so end your turn and never poll it; headless sessions return the",
|
|
89
|
+
"ruling inline.",
|
|
87
90
|
'Use `persona: "magi"` to invoke an installed persona\'s declared council without switching away',
|
|
88
91
|
"from the active caller; its prompt, model, tools, and permissions are never inherited.",
|
|
89
|
-
`Strategies: ${strategyNames()
|
|
90
|
-
.map((n) => {
|
|
91
|
-
const p = knownParams(n);
|
|
92
|
-
const keys = p ? Object.keys(p) : [];
|
|
93
|
-
return keys.length > 0 ? `${n}(${keys.join(", ")})` : n;
|
|
94
|
-
})
|
|
95
|
-
.join(" · ")}.`,
|
|
96
92
|
"Pass `members` to seat an ad-hoc panel for one call, or `params` to vary the profile's default",
|
|
97
93
|
'council — e.g. { "reflect": false }.',
|
|
98
|
-
|
|
94
|
+
"Pick the strategy by problem class (`*` = required param):",
|
|
95
|
+
].join(" ")}
|
|
96
|
+
${strategyCatalog()}`,
|
|
99
97
|
parameters: CouncilParams,
|
|
100
98
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
101
99
|
d.lastCtx = ctx;
|
|
@@ -106,15 +104,19 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
106
104
|
members: params.members,
|
|
107
105
|
params: params.params as Record<string, unknown> | undefined,
|
|
108
106
|
});
|
|
109
|
-
|
|
107
|
+
// A call that cannot run (no such persona, a missing `required` param) is refused HERE, as an
|
|
108
|
+
// immediate tool error — before a run, a tree row or the model picker exists.
|
|
109
|
+
const error = resolved.ok ? missingRequiredParams(resolved.value.strategy, resolved.value.params) : resolved.error;
|
|
110
|
+
if (!resolved.ok || error) {
|
|
110
111
|
return {
|
|
111
|
-
content: [{ type: "text", text: `council failed: ${
|
|
112
|
-
details: failureDetails({ error:
|
|
112
|
+
content: [{ type: "text", text: `council failed: ${error}` }],
|
|
113
|
+
details: failureDetails({ error: error ?? "", persona: params.persona }),
|
|
113
114
|
isError: true,
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
const { strategy, roster, members, params: mergedParams, persona } = resolved.value;
|
|
117
118
|
const panel = panelLabel({ roster, members });
|
|
119
|
+
let launch: OrchestrationLaunchSnapshot | undefined;
|
|
118
120
|
try {
|
|
119
121
|
// Fully persona-driven: a persona's `council:` block picks the strategy, roster,
|
|
120
122
|
// and params — a new ensemble (more members, supermajority, multi-round) needs no
|
|
@@ -129,7 +131,9 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
129
131
|
if (unknown.length > 0) {
|
|
130
132
|
const note = `council: ignoring unknown param(s) [${unknown.join(", ")}] for "${strategy}" — known: ${Object.keys(schema).join(", ") || "(none)"}`;
|
|
131
133
|
if (process.env.PI_PERSONA_DEBUG) process.stderr.write(`[pi-persona] ${note}\n`);
|
|
132
|
-
|
|
134
|
+
// The toast is bounded/sanitized for the operator; the model-facing paramNote below
|
|
135
|
+
// keeps the raw note byte-identical (payloads are not presentation).
|
|
136
|
+
ctx.ui.notify(toastCause(note), "warning");
|
|
133
137
|
paramNote = `\n\n(${note})`;
|
|
134
138
|
}
|
|
135
139
|
}
|
|
@@ -137,8 +141,37 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
137
141
|
// Ephemeral members ride the SAME grammar path as a named team (see
|
|
138
142
|
// `resolveOrchestrationMembers`) — nothing about the profile is inherited.
|
|
139
143
|
if (members) orch.members = members;
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
try {
|
|
145
|
+
const snapshot = d.launchSnapshot(ctx, orch);
|
|
146
|
+
launch = { ...snapshot, participants: snapshot.participants.map((p) => ({ ...p })) };
|
|
147
|
+
} catch {
|
|
148
|
+
// A display lookup must not change launch admission; its failure is visible, not
|
|
149
|
+
// replaced with guessed models or an apparently successful empty panel.
|
|
150
|
+
launch = { participants: [], unavailable: true };
|
|
151
|
+
}
|
|
152
|
+
// The same launch `delegate` uses: ONE tracked run. Interactive sessions get a receipt now and
|
|
153
|
+
// the ruling as ONE completion follow-up (the idle-gated wake, fenced and bounded); headless
|
|
154
|
+
// sessions join the run before returning, since nothing would deliver a follow-up there.
|
|
155
|
+
const inline = isInline(ctx);
|
|
156
|
+
const id = d.runs.launch({ kind: "council", label: `council ${strategy}`, task: params.question, inline }, async (run) =>
|
|
157
|
+
(await d.runStrategyVisible(ctx, orch, params.question, "council", run)) ??
|
|
158
|
+
{ agent: strategy, output: "(the council returned no ruling)", usage: emptyUsage(), ok: false, error: "no ruling" });
|
|
159
|
+
if (!inline) {
|
|
160
|
+
const names = compactInlineText(`${strategy} · ${panel}`, { maxChars: 120 });
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: "text", text: `${launchReceipt("council", id, names, "with the ruling")}${paramNote}` }],
|
|
163
|
+
details: { runId: id, strategy, roster: panel, persona, launch },
|
|
164
|
+
isError: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const { runs, usage } = await d.runs.settle([id], signal);
|
|
168
|
+
const run = runs[0];
|
|
169
|
+
const result = run?.result;
|
|
170
|
+
if (!result) {
|
|
171
|
+
const message = run?.error ?? run?.status ?? "the run record is gone";
|
|
172
|
+
return { content: [{ type: "text", text: `council failed: ${message}` }], details: failureDetails({ error: message, strategy, roster: panel, ...(launch ? { launch } : {}) }), isError: true };
|
|
173
|
+
}
|
|
174
|
+
const s = (result.structured ?? {}) as {
|
|
142
175
|
headline?: string;
|
|
143
176
|
status?: string;
|
|
144
177
|
tally?: Record<string, number>;
|
|
@@ -146,54 +179,49 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
146
179
|
count?: number;
|
|
147
180
|
items?: unknown;
|
|
148
181
|
};
|
|
149
|
-
const ruling = result?.output ?? "(the council returned no ruling)";
|
|
150
|
-
const uiBody = result ? (humanizeAggregateResult(result) ?? result.output) : "";
|
|
151
182
|
const headline = s.headline ?? (typeof s.count === "number" ? `${s.count} member results` : s.status ?? "");
|
|
152
|
-
const ok = result?.ok ?? false;
|
|
153
183
|
const details = {
|
|
154
|
-
|
|
184
|
+
launch,
|
|
185
|
+
ok: result.ok,
|
|
155
186
|
headline,
|
|
156
187
|
status: s.status,
|
|
157
188
|
tally: s.tally,
|
|
158
189
|
usedFallback: s.usedFallback,
|
|
159
190
|
items: s.items,
|
|
160
|
-
body:
|
|
191
|
+
body: humanizeAggregateResult(result) ?? result.output,
|
|
161
192
|
strategy,
|
|
162
193
|
roster: panel,
|
|
163
194
|
persona,
|
|
164
|
-
...(result
|
|
165
|
-
...(result
|
|
195
|
+
...(result.error ? { error: result.error } : {}),
|
|
196
|
+
...(result.failureKind ? { failureKind: result.failureKind } : {}),
|
|
197
|
+
...(run?.resultReceipt ? { resultReceipt: { ...run.resultReceipt } } : {}),
|
|
166
198
|
};
|
|
167
|
-
const usage = result?.usage ?? emptyUsage();
|
|
168
|
-
d.childUsage.account(usage);
|
|
169
|
-
d.publishPersonaCost();
|
|
170
199
|
return {
|
|
171
200
|
// The ruling is sub-agent (council member) text — fence it like every other
|
|
172
201
|
// path that hands sub-agent output to the supervisor.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
202
|
+
// A run stopped before it convened has no ruling, only its cause: show that, not an empty fence.
|
|
203
|
+
content: [{ type: "text", text: `${fenceUntrusted(result.output || result.error || "")}${run?.resultReceipt ? `\n\n${resultReceiptHint(run.resultReceipt)}` : ""}${paramNote}${d.drainBusBlock()}` }],
|
|
204
|
+
details: result.ok ? details : failureDetails(details),
|
|
205
|
+
isError: !result.ok,
|
|
176
206
|
...toolUsageField(usage),
|
|
177
207
|
};
|
|
178
208
|
} catch (err) {
|
|
179
209
|
const message = err instanceof Error ? err.message : String(err);
|
|
180
|
-
return { content: [{ type: "text", text: `council failed: ${message}` }], details: failureDetails({ error: message, strategy, roster: panel }), isError: true };
|
|
210
|
+
return { content: [{ type: "text", text: `council failed: ${message}` }], details: failureDetails({ error: message, strategy, roster: panel, ...(launch ? { launch } : {}) }), isError: true };
|
|
181
211
|
}
|
|
182
212
|
},
|
|
183
213
|
renderCall(args, theme) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
// With inline members there is no named team to name — label the panel itself.
|
|
193
|
-
const roster = resolved.ok ? panelLabel(resolved.value) : (args.roster ?? args.persona ?? "?");
|
|
214
|
+
// Native Pi draws the call before its saved result. Keep this title args-only and
|
|
215
|
+
// neutral about omitted defaults; the receipt below carries the resolved snapshot.
|
|
216
|
+
const members = Array.isArray(args.members) ? args.members.flatMap((raw) => {
|
|
217
|
+
const parsed = parseRuntimeRosterMember(raw);
|
|
218
|
+
return parsed.ok ? [parsed.member] : [];
|
|
219
|
+
}) : [];
|
|
220
|
+
const strategy = args.strategy ?? "profile defaults";
|
|
221
|
+
const roster = members?.length ? panelLabel({ roster: "", members }) : args.roster ?? args.persona ?? "profile roster";
|
|
194
222
|
return new Text(theme.fg("toolTitle", theme.bold(formatCouncilCallLabel(strategy, roster))), 0, 0);
|
|
195
223
|
},
|
|
196
|
-
renderResult(result, { expanded }, theme) {
|
|
224
|
+
renderResult(result, { expanded }, theme, context) {
|
|
197
225
|
const d = (result.details ?? {}) as {
|
|
198
226
|
ok?: boolean;
|
|
199
227
|
headline?: string;
|
|
@@ -205,24 +233,38 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
|
|
|
205
233
|
roster?: string;
|
|
206
234
|
error?: string;
|
|
207
235
|
failureKind?: FailureKind;
|
|
236
|
+
runId?: string;
|
|
237
|
+
launch?: OrchestrationLaunchSnapshot;
|
|
208
238
|
};
|
|
209
239
|
const first = result.content[0];
|
|
240
|
+
const failed = (context?.isError ?? result.isError) || d.ok === false || !!d.error;
|
|
241
|
+
if (d.runId) {
|
|
242
|
+
// Interactive launch receipt: the ruling arrives later as the completion follow-up.
|
|
243
|
+
const launched = failed ? theme.fg("error", theme.bold("council failed"))
|
|
244
|
+
: `${theme.fg("toolTitle", theme.bold("council "))}${theme.fg("accent", compactInlineText(`launched ${d.runId} · ${d.strategy ?? "?"} · ${d.roster ?? "?"}`, { maxChars: 120 }))}`;
|
|
245
|
+
const metadata = orchestrationLaunchLines(d.launch, expanded).join("\n");
|
|
246
|
+
return new Text([launched, theme.fg("dim", metadata), expanded && first?.type === "text" ? theme.fg("toolOutput", sanitizeTerminalText(first.text)) : ""].filter(Boolean).join("\n"), 0, 0);
|
|
247
|
+
}
|
|
210
248
|
const body = sanitizeTerminalText(d.body || (first && first.type === "text" ? first.text : ""));
|
|
211
|
-
const
|
|
249
|
+
const metadata = d.launch ? `\n\nLaunch request (Pi may clamp effort):\n${orchestrationLaunchLines(d.launch, expanded).join("\n")}` : "";
|
|
212
250
|
if (failed) {
|
|
213
251
|
const cause = compactInlineText([d.failureKind, d.error].filter((part): part is string => !!part).join(" · "), { maxChars: 160 });
|
|
214
|
-
const title = theme.fg("error", theme.bold(`council failed${cause ? ` · ${cause}` : ""}`));
|
|
215
|
-
if (expanded)
|
|
252
|
+
const title = () => theme.fg("error", theme.bold(`council failed${cause ? ` · ${cause}` : ""}`));
|
|
253
|
+
if (expanded) {
|
|
254
|
+
// Only a completed council retains a ruling body; preflight/host errors are literal.
|
|
255
|
+
if (typeof d.body === "string") return createReportBody(`${body || "(no ruling)"}${metadata}`, theme, { header: title }, context?.lastComponent);
|
|
256
|
+
return new Text(`${title()}\n${theme.fg("toolOutput", body || "(no ruling)")}${theme.fg("dim", metadata)}`, 0, 0);
|
|
257
|
+
}
|
|
216
258
|
const preview = compactVisibleText(body || "(no ruling)", { maxLines: 3, maxLineChars: 100 });
|
|
217
259
|
const hint = preview.truncated ? `\n${theme.fg("dim", expandDetailHint())}` : "";
|
|
218
|
-
return new Text(`${title}\n${theme.fg("toolOutput", preview.text)}${hint}`, 0, 0);
|
|
260
|
+
return new Text(`${title()}\n${theme.fg("toolOutput", preview.text)}${hint}${theme.fg("dim", metadata)}`, 0, 0);
|
|
219
261
|
}
|
|
220
262
|
const text = formatCouncilResult(
|
|
221
263
|
{ headline: d.headline, status: d.status, tally: d.tally, usedFallback: d.usedFallback, body },
|
|
222
264
|
expanded,
|
|
223
265
|
expandDetailHint(),
|
|
224
266
|
);
|
|
225
|
-
return new Text(theme.fg(
|
|
267
|
+
return expanded ? createReportBody(`${text}${metadata}`, theme, {}, context?.lastComponent) : new Text(`${theme.fg("accent", text)}${theme.fg("dim", metadata)}`, 0, 0);
|
|
226
268
|
},
|
|
227
269
|
});
|
|
228
270
|
}
|