@aeondave/pi-persona 1.17.1 → 1.17.3

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.
Files changed (103) hide show
  1. package/AGENTS.md +59 -28
  2. package/CHANGELOG.md +234 -0
  3. package/README.md +16 -12
  4. package/agents/architect.md +21 -0
  5. package/agents/balthasar.md +17 -22
  6. package/agents/casper.md +17 -23
  7. package/agents/evidence-verifier.md +31 -16
  8. package/agents/melchior.md +17 -22
  9. package/agents/operator.md +15 -55
  10. package/agents/research.md +14 -48
  11. package/agents/reviewer.md +20 -39
  12. package/agents/scout.md +14 -15
  13. package/agents/verifier.md +15 -16
  14. package/docs/ARCHITECTURE.md +183 -55
  15. package/docs/EXPERIENCE.md +19 -9
  16. package/docs/MONITORS.md +62 -3
  17. package/docs/REFERENCE.md +82 -32
  18. package/docs/SPINE.md +2 -2
  19. package/docs/STRATEGIES.md +23 -8
  20. package/docs/TELEMETRY.md +1 -0
  21. package/flows/deep-review.flow.json +1 -1
  22. package/package.json +9 -8
  23. package/personas/audit.md +28 -17
  24. package/personas/dev.md +30 -32
  25. package/personas/elite.md +223 -221
  26. package/personas/judge.md +25 -19
  27. package/personas/magi.md +26 -29
  28. package/personas/planner.md +31 -24
  29. package/personas/researcher.md +25 -26
  30. package/personas/swarm.md +35 -25
  31. package/personas/verify.md +30 -19
  32. package/src/agents/agent.ts +20 -0
  33. package/src/bridge.ts +3 -2
  34. package/src/core/brief.ts +429 -356
  35. package/src/core/config.ts +15 -10
  36. package/src/core/model-catalog.ts +171 -0
  37. package/src/core/model-tier.ts +89 -0
  38. package/src/core/models.ts +13 -4
  39. package/src/core/monitor-output.ts +85 -0
  40. package/src/core/monitor.ts +34 -2
  41. package/src/core/nudge.ts +176 -23
  42. package/src/core/seed.ts +99 -6
  43. package/src/core/session-runs.ts +87 -0
  44. package/src/core/settings.ts +1 -1
  45. package/src/core/timer.ts +34 -9
  46. package/src/engine/adapter.ts +12 -8
  47. package/src/engine/async.ts +165 -146
  48. package/src/engine/child.ts +2 -2
  49. package/src/engine/host-module.ts +12 -0
  50. package/src/engine/inproc.ts +12 -7
  51. package/src/engine/stream.ts +4 -20
  52. package/src/exocom/install.ts +2 -1
  53. package/src/extension/engine.ts +43 -16
  54. package/src/extension/hooks.ts +106 -36
  55. package/src/extension/persona-model-settings.ts +31 -13
  56. package/src/extension/shared.ts +15 -7
  57. package/src/extension.ts +505 -241
  58. package/src/monitor/archive.ts +235 -0
  59. package/src/monitor/session.ts +21 -1
  60. package/src/orchestration/flow-journal.ts +26 -5
  61. package/src/orchestration/flow-run.ts +38 -13
  62. package/src/orchestration/flow.ts +9 -2
  63. package/src/orchestration/reducers.ts +9 -5
  64. package/src/orchestration/render.ts +1 -1
  65. package/src/orchestration/sdk.ts +17 -5
  66. package/src/orchestration/strategies/compete.ts +5 -1
  67. package/src/orchestration/strategies/council-rounds.ts +2 -0
  68. package/src/orchestration/strategies/critic-loop.ts +3 -0
  69. package/src/orchestration/strategies/debate.ts +2 -0
  70. package/src/orchestration/strategies/fanout.ts +3 -2
  71. package/src/orchestration/strategies/judge.ts +5 -1
  72. package/src/orchestration/strategies/magi.ts +40 -23
  73. package/src/orchestration/strategies/map.ts +13 -3
  74. package/src/orchestration/strategies/pair.ts +2 -0
  75. package/src/orchestration/strategies/pipeline.ts +2 -0
  76. package/src/orchestration/strategies/synthesize.ts +27 -7
  77. package/src/orchestration/strategy.ts +31 -0
  78. package/src/persona/persona.ts +7 -3
  79. package/src/telemetry/contract.ts +1 -1
  80. package/src/tools/background-run.ts +151 -0
  81. package/src/tools/council.ts +77 -52
  82. package/src/tools/delegate-tool.ts +310 -280
  83. package/src/tools/delegate.ts +22 -210
  84. package/src/tools/exocom-work.ts +18 -53
  85. package/src/tools/exocom.ts +15 -19
  86. package/src/tools/flow.ts +85 -28
  87. package/src/tools/intercom-tool.ts +108 -118
  88. package/src/tools/intercom.ts +1 -1
  89. package/src/tools/models.ts +19 -9
  90. package/src/tools/monitor.ts +39 -6
  91. package/src/tools/timer.ts +11 -13
  92. package/src/ui/agent-overlay.ts +55 -14
  93. package/src/ui/agent-tree.ts +84 -18
  94. package/src/ui/compact-widget.ts +25 -0
  95. package/src/ui/failure-summary.ts +55 -0
  96. package/src/ui/literal-tool-result.ts +24 -0
  97. package/src/ui/model-picker.ts +158 -81
  98. package/src/ui/persona-settings.ts +9 -3
  99. package/src/ui/presentation.ts +24 -0
  100. package/src/ui/report-body.ts +63 -0
  101. package/src/ui/session-cards.ts +189 -0
  102. package/src/ui/wake-rows.ts +262 -0
  103. package/teams.yaml +5 -1
@@ -2,8 +2,8 @@
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 { expandDetailHint, failureDetails, formatCouncilCallLabel } from "../extension/shared.ts";
6
- import { knownParams, strategyNames } from "../orchestration/strategy.ts";
5
+ import { expandDetailHint, failureDetails, formatCouncilCallLabel, isInline } from "../extension/shared.ts";
6
+ import { knownParams, missingRequiredParams, strategyCatalog } from "../orchestration/strategy.ts";
7
7
  import { memberBaseLabel, type RosterMember } from "../orchestration/roster.ts";
8
8
  import { formatCouncilResult, humanizeAggregateResult } from "../orchestration/render.ts";
9
9
  import type { FailureKind } from "../orchestration/types.ts";
@@ -12,10 +12,11 @@ import { resolveCouncilInvocation } from "../persona/persona.ts";
12
12
  import type { OrchestrationGrammar, Persona } from "../persona/persona.ts";
13
13
  import type { PersonaController } from "../persona/controller.ts";
14
14
  import { Text } from "@earendil-works/pi-tui";
15
- import { compactInlineText, sanitizeTerminalText } from "../ui/presentation.ts";
16
- import { compactVisibleText } from "../ui/presentation.ts";
17
- import { toolUsageField, type ChildUsageLedger } from "../ui/usage.ts";
15
+ import { createReportBody } from "../ui/report-body.ts";
16
+ import { compactInlineText, compactVisibleText, sanitizeTerminalText } from "../ui/presentation.ts";
17
+ import { toolUsageField } from "../ui/usage.ts";
18
18
  import { emptyUsage } from "../engine/stream.ts";
19
+ import { launchReceipt, type BackgroundRuns, type TrackedRun } from "./background-run.ts";
19
20
 
20
21
 
21
22
  export interface CouncilToolDeps {
@@ -28,11 +29,11 @@ export interface CouncilToolDeps {
28
29
  orch: OrchestrationGrammar,
29
30
  task: string,
30
31
  label: string,
31
- signal?: AbortSignal,
32
+ tracked?: TrackedRun,
32
33
  ): Promise<AgentResult | undefined>;
34
+ /** Launch/join: a council is one tracked run, like a delegate leg (see background-run.ts). */
35
+ runs: BackgroundRuns;
33
36
  drainBusBlock(): string;
34
- childUsage: ChildUsageLedger;
35
- publishPersonaCost(): void;
36
37
  }
37
38
 
38
39
  /** What the card names as the panel: the team, or the ad-hoc members themselves — an
@@ -55,7 +56,7 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
55
56
  strategy: Type.Optional(
56
57
  Type.String({ description: 'Per-call strategy override (default: the selected or active council profile\'s strategy, or built-in "magi")' }),
57
58
  ),
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")' })),
59
+ 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
60
  members: Type.Optional(
60
61
  Type.Array(Type.Unknown(), {
61
62
  description:
@@ -72,30 +73,24 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
72
73
  pi.registerTool({
73
74
  name: "council",
74
75
  label: "Council",
75
- description: [
76
+ // The method catalog is generated from the strategy registry (`when`/`roster`/`required`), never hand-written.
77
+ description: `${[
76
78
  "Convene a council of specialists with controlled, complementary biases to deliberate a",
77
- "decision — returns the selected strategy's result, preserving vote tally and dissent when produced.",
78
- "Scale it to the decision: convene for a genuine fork, a contested or high-stakes call, or a",
79
- "pick between rival answers — NOT before every decision, and a routine task does not",
80
- "deserve a whole panel. Then EXECUTE the ruling yourself and re-convene only when execution",
81
- "surfaces a NEW decision. Do NOT use it to get work done: `delegate` (and your own tools)",
82
- "produce the work; the council decides, the executor acts.",
83
- "Patterns: adversarial vote with dissent (magi, council-rounds); a reasoned pick between rival",
84
- "COMPLETE answers by an impartial arbiter (judge); adversarial critique then revise",
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).",
79
+ "decision or run a coordinated method over a task — returns the selected strategy's result,",
80
+ "preserving vote tally and dissent when produced.",
81
+ "Scale it to the task: convene when the method adds value — NOT before every decision, and a",
82
+ "routine task does not deserve a whole panel. Then act on the result yourself and re-convene only",
83
+ "when acting surfaces a NEW decision. Do NOT use it for plain independent parallel work: that is",
84
+ "`delegate`. Interactive sessions run a council in the background: you get a run id at once and ONE",
85
+ "follow-up carries the ruling, so end your turn and never poll it; headless sessions return the",
86
+ "ruling inline.",
87
87
  'Use `persona: "magi"` to invoke an installed persona\'s declared council without switching away',
88
88
  "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
89
  "Pass `members` to seat an ad-hoc panel for one call, or `params` to vary the profile's default",
97
90
  'council — e.g. { "reflect": false }.',
98
- ].join(" "),
91
+ "Pick the strategy by problem class (`*` = required param):",
92
+ ].join(" ")}
93
+ ${strategyCatalog()}`,
99
94
  parameters: CouncilParams,
100
95
  async execute(_id, params, signal, _onUpdate, ctx) {
101
96
  d.lastCtx = ctx;
@@ -106,10 +101,13 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
106
101
  members: params.members,
107
102
  params: params.params as Record<string, unknown> | undefined,
108
103
  });
109
- if (!resolved.ok) {
104
+ // A call that cannot run (no such persona, a missing `required` param) is refused HERE, as an
105
+ // immediate tool error — before a run, a tree row or the model picker exists.
106
+ const error = resolved.ok ? missingRequiredParams(resolved.value.strategy, resolved.value.params) : resolved.error;
107
+ if (!resolved.ok || error) {
110
108
  return {
111
- content: [{ type: "text", text: `council failed: ${resolved.error}` }],
112
- details: failureDetails({ error: resolved.error, persona: params.persona }),
109
+ content: [{ type: "text", text: `council failed: ${error}` }],
110
+ details: failureDetails({ error: error ?? "", persona: params.persona }),
113
111
  isError: true,
114
112
  };
115
113
  }
@@ -137,8 +135,29 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
137
135
  // Ephemeral members ride the SAME grammar path as a named team (see
138
136
  // `resolveOrchestrationMembers`) — nothing about the profile is inherited.
139
137
  if (members) orch.members = members;
140
- const result = await d.runStrategyVisible(ctx, orch, params.question, `council:${_id}`, signal);
141
- const s = (result?.structured ?? {}) as {
138
+ // The same launch `delegate` uses: ONE tracked run. Interactive sessions get a receipt now and
139
+ // the ruling as ONE completion follow-up (the idle-gated wake, fenced and bounded); headless
140
+ // sessions join the run before returning, since nothing would deliver a follow-up there.
141
+ const inline = isInline(ctx);
142
+ const id = d.runs.launch({ kind: "council", label: `council ${strategy}`, task: params.question, inline }, async (run) =>
143
+ (await d.runStrategyVisible(ctx, orch, params.question, "council", run)) ??
144
+ { agent: strategy, output: "(the council returned no ruling)", usage: emptyUsage(), ok: false, error: "no ruling" });
145
+ if (!inline) {
146
+ const names = compactInlineText(`${strategy} · ${panel}`, { maxChars: 120 });
147
+ return {
148
+ content: [{ type: "text", text: `${launchReceipt("council", id, names, "with the ruling")}${paramNote}` }],
149
+ details: { runId: id, strategy, roster: panel, persona },
150
+ isError: false,
151
+ };
152
+ }
153
+ const { runs, usage } = await d.runs.settle([id], signal);
154
+ const run = runs[0];
155
+ const result = run?.result;
156
+ if (!result) {
157
+ const message = run?.error ?? run?.status ?? "the run record is gone";
158
+ return { content: [{ type: "text", text: `council failed: ${message}` }], details: failureDetails({ error: message, strategy, roster: panel }), isError: true };
159
+ }
160
+ const s = (result.structured ?? {}) as {
142
161
  headline?: string;
143
162
  status?: string;
144
163
  tally?: Record<string, number>;
@@ -146,33 +165,28 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
146
165
  count?: number;
147
166
  items?: unknown;
148
167
  };
149
- const ruling = result?.output ?? "(the council returned no ruling)";
150
- const uiBody = result ? (humanizeAggregateResult(result) ?? result.output) : "";
151
168
  const headline = s.headline ?? (typeof s.count === "number" ? `${s.count} member results` : s.status ?? "");
152
- const ok = result?.ok ?? false;
153
169
  const details = {
154
- ok,
170
+ ok: result.ok,
155
171
  headline,
156
172
  status: s.status,
157
173
  tally: s.tally,
158
174
  usedFallback: s.usedFallback,
159
175
  items: s.items,
160
- body: uiBody,
176
+ body: humanizeAggregateResult(result) ?? result.output,
161
177
  strategy,
162
178
  roster: panel,
163
179
  persona,
164
- ...(result?.error ? { error: result.error } : {}),
165
- ...(result?.failureKind ? { failureKind: result.failureKind } : {}),
180
+ ...(result.error ? { error: result.error } : {}),
181
+ ...(result.failureKind ? { failureKind: result.failureKind } : {}),
166
182
  };
167
- const usage = result?.usage ?? emptyUsage();
168
- d.childUsage.account(usage);
169
- d.publishPersonaCost();
170
183
  return {
171
184
  // The ruling is sub-agent (council member) text — fence it like every other
172
185
  // path that hands sub-agent output to the supervisor.
173
- content: [{ type: "text", text: `${fenceUntrusted(ruling)}${paramNote}${d.drainBusBlock()}` }],
174
- details: ok ? details : failureDetails(details),
175
- isError: !ok,
186
+ // A run stopped before it convened has no ruling, only its cause: show that, not an empty fence.
187
+ content: [{ type: "text", text: `${fenceUntrusted(result.output || result.error || "")}${paramNote}${d.drainBusBlock()}` }],
188
+ details: result.ok ? details : failureDetails(details),
189
+ isError: !result.ok,
176
190
  ...toolUsageField(usage),
177
191
  };
178
192
  } catch (err) {
@@ -193,7 +207,7 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
193
207
  const roster = resolved.ok ? panelLabel(resolved.value) : (args.roster ?? args.persona ?? "?");
194
208
  return new Text(theme.fg("toolTitle", theme.bold(formatCouncilCallLabel(strategy, roster))), 0, 0);
195
209
  },
196
- renderResult(result, { expanded }, theme) {
210
+ renderResult(result, { expanded }, theme, context) {
197
211
  const d = (result.details ?? {}) as {
198
212
  ok?: boolean;
199
213
  headline?: string;
@@ -205,24 +219,35 @@ export function registerCouncilTool(pi: ExtensionAPI, d: CouncilToolDeps): void
205
219
  roster?: string;
206
220
  error?: string;
207
221
  failureKind?: FailureKind;
222
+ runId?: string;
208
223
  };
209
224
  const first = result.content[0];
225
+ const failed = (context?.isError ?? result.isError) || d.ok === false || !!d.error;
226
+ if (d.runId) {
227
+ // Interactive launch receipt: the ruling arrives later as the completion follow-up.
228
+ const launched = failed ? theme.fg("error", theme.bold("council failed"))
229
+ : `${theme.fg("toolTitle", theme.bold("council "))}${theme.fg("accent", compactInlineText(`launched ${d.runId} · ${d.strategy ?? "?"} · ${d.roster ?? "?"}`, { maxChars: 120 }))}`;
230
+ return new Text(expanded && first?.type === "text" ? `${launched}\n${theme.fg("toolOutput", sanitizeTerminalText(first.text))}` : launched, 0, 0);
231
+ }
210
232
  const body = sanitizeTerminalText(d.body || (first && first.type === "text" ? first.text : ""));
211
- const failed = d.ok === false || !!d.error;
212
233
  if (failed) {
213
234
  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) return new Text(`${title}\n${theme.fg("toolOutput", body || "(no ruling)")}`, 0, 0);
235
+ const title = () => theme.fg("error", theme.bold(`council failed${cause ? ` · ${cause}` : ""}`));
236
+ if (expanded) {
237
+ // Only a completed council retains a ruling body; preflight/host errors are literal.
238
+ if (typeof d.body === "string") return createReportBody(body || "(no ruling)", theme, { header: title }, context?.lastComponent);
239
+ return new Text(`${title()}\n${theme.fg("toolOutput", body || "(no ruling)")}`, 0, 0);
240
+ }
216
241
  const preview = compactVisibleText(body || "(no ruling)", { maxLines: 3, maxLineChars: 100 });
217
242
  const hint = preview.truncated ? `\n${theme.fg("dim", expandDetailHint())}` : "";
218
- return new Text(`${title}\n${theme.fg("toolOutput", preview.text)}${hint}`, 0, 0);
243
+ return new Text(`${title()}\n${theme.fg("toolOutput", preview.text)}${hint}`, 0, 0);
219
244
  }
220
245
  const text = formatCouncilResult(
221
246
  { headline: d.headline, status: d.status, tally: d.tally, usedFallback: d.usedFallback, body },
222
247
  expanded,
223
248
  expandDetailHint(),
224
249
  );
225
- return new Text(theme.fg(expanded ? "toolOutput" : "accent", text), 0, 0);
250
+ return expanded ? createReportBody(text, theme, {}, context?.lastComponent) : new Text(theme.fg("accent", text), 0, 0);
226
251
  },
227
252
  });
228
253
  }