@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
package/src/core/timer.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * `TimerScheduler` — the pure kernel behind the supervisor-armable `timer` tool.
3
3
  *
4
- * Problem it solves: a supervisor that must WAIT for a fixed wall-clock moment (a scheduled event,
5
- * a service window, a scheduled re-check) has no cheap way to "sleep and resume". The
6
- * MCP/transport layer caps a blocking tool call at ~20-60s, so a 50-minute wait degenerates into
7
- * dozens of token-burning polls. The fix is an ALARM, not a sleep: arm a timer that, when it
8
- * expires, WAKES the session by injecting a follow-up (the extension routes the fire through the
9
- * same idle-delivery notifier that surfaces async-run completions — an idle delivery always starts
10
- * a fresh turn, so the supervisor resumes on its own).
4
+ * Problem it solves: a supervisor needs a wake at a fixed wall-clock moment the world decides (a
5
+ * scheduled event, a service window, a reminder). The MCP/transport layer caps a blocking tool
6
+ * call at ~20-60s, so a blocking sleep is not an option. The fix is an ALARM: arm a timer that,
7
+ * when it expires, WAKES the session by injecting a follow-up (the extension routes the fire
8
+ * through the same idle-delivery notifier that surfaces async-run completions — an idle delivery
9
+ * always starts a fresh turn, so the supervisor resumes on its own).
11
10
  *
12
11
  * This module is pure (injected clock + setTimer/clearTimer + an onFire sink), so scheduling,
13
12
  * validation, cancellation and listing are unit-tested without a live model or real wall clock.
@@ -65,6 +64,11 @@ export interface TimerSchedulerDeps {
65
64
  clearTimer: (handle: unknown) => void;
66
65
  /** Called exactly once when a timer expires (never for a cancelled one). */
67
66
  onFire: (entry: TimerEntry) => void;
67
+ /** Called once per admitted alarm, right after `arm` accepts it (never for a rejected request).
68
+ * Every announced alarm later reaches exactly one of `onFire` / `onCancelled`. */
69
+ onArmed?: (entry: TimerEntry) => void;
70
+ /** Called once per alarm dropped by `cancel` or `cancelAll` (never for one that fired). */
71
+ onCancelled?: (entry: TimerEntry) => void;
68
72
  /** Minimum accepted delay (ms). Default 1000 — sub-second alarms are pointless here. */
69
73
  minDelayMs?: number;
70
74
  /** Maximum accepted delay (ms). Default 24h — guards against a runaway/typo alarm. */
@@ -75,12 +79,26 @@ export interface TimerSchedulerDeps {
75
79
  /** Maximum follow-up message length (chars). Default 4000 — the stored message is
76
80
  * re-injected into context at fire time, so it is a budget, not a document. */
77
81
  maxMessageChars?: number;
82
+ /** Longest accepted label (code units). Default 120 — the label lands in the tree row, `timer list`
83
+ * and the fire message, so it is a caption, not a document. A longer label is CLIPPED (ending in
84
+ * "…"), not rejected: a verbose label must never cost the alarm. */
85
+ maxLabelChars?: number;
78
86
  }
79
87
 
80
88
  const DEFAULT_MIN_DELAY_MS = 1_000;
81
89
  const DEFAULT_MAX_DELAY_MS = 24 * 60 * 60 * 1_000;
82
90
  const DEFAULT_MAX_ARMED = 32;
83
91
  const DEFAULT_MAX_MESSAGE_CHARS = 4_000;
92
+ const DEFAULT_MAX_LABEL_CHARS = 120;
93
+
94
+ /** At most `max` code units, ending in "…" when clipped; never cuts an astral character in half. */
95
+ function clipLabel(text: string, max: number): string {
96
+ if (text.length <= max) return text;
97
+ let end = Math.max(0, max - 1);
98
+ const last = text.charCodeAt(end - 1);
99
+ if (last >= 0xd800 && last <= 0xdbff) end -= 1;
100
+ return `${text.slice(0, end)}…`;
101
+ }
84
102
 
85
103
  interface Scheduled {
86
104
  entry: TimerEntry;
@@ -93,6 +111,7 @@ export class TimerScheduler {
93
111
  private readonly maxDelayMs: number;
94
112
  private readonly maxArmed: number;
95
113
  private readonly maxMessageChars: number;
114
+ private readonly maxLabelChars: number;
96
115
  private readonly timers = new Map<string, Scheduled>();
97
116
  private seq = 0;
98
117
 
@@ -102,6 +121,7 @@ export class TimerScheduler {
102
121
  this.maxDelayMs = deps.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
103
122
  this.maxArmed = deps.maxArmed ?? DEFAULT_MAX_ARMED;
104
123
  this.maxMessageChars = deps.maxMessageChars ?? DEFAULT_MAX_MESSAGE_CHARS;
124
+ this.maxLabelChars = deps.maxLabelChars ?? DEFAULT_MAX_LABEL_CHARS;
105
125
  }
106
126
 
107
127
  /** Arm a new alarm. Returns the entry on success, or a reason on rejection. */
@@ -137,13 +157,14 @@ export class TimerScheduler {
137
157
  const id = `timer-${++this.seq}`;
138
158
  const entry: TimerEntry = {
139
159
  id,
140
- label: (req.label ?? "").trim() || id,
160
+ label: clipLabel((req.label ?? "").trim(), this.maxLabelChars) || id,
141
161
  fireAtEpochMs: fireAt,
142
162
  armedAtEpochMs: now,
143
163
  message,
144
164
  };
145
165
  const handle = this.deps.setTimer(() => this.fire(id), delay);
146
166
  this.timers.set(id, { entry, handle });
167
+ this.deps.onArmed?.(entry);
147
168
  return { ok: true, entry, remainingMs: delay };
148
169
  }
149
170
 
@@ -153,13 +174,17 @@ export class TimerScheduler {
153
174
  if (!scheduled) return false;
154
175
  this.deps.clearTimer(scheduled.handle);
155
176
  this.timers.delete(id);
177
+ this.deps.onCancelled?.(scheduled.entry);
156
178
  return true;
157
179
  }
158
180
 
159
181
  /** Cancel every armed alarm (reload/dispose hygiene — never leak a timer across sessions). */
160
182
  cancelAll(): void {
161
- for (const scheduled of this.timers.values()) this.deps.clearTimer(scheduled.handle);
183
+ const dropped = [...this.timers.values()];
184
+ for (const scheduled of dropped) this.deps.clearTimer(scheduled.handle);
162
185
  this.timers.clear();
186
+ // Announced after the map is empty, so a listener that re-enters (list/cancel) sees the truth.
187
+ for (const scheduled of dropped) this.deps.onCancelled?.(scheduled.entry);
163
188
  }
164
189
 
165
190
  /** Snapshot of all armed alarms, soonest first, with live remaining time. */
@@ -12,14 +12,14 @@ import { type ContractDef, contractInstructions, parseAndValidate, pinContract,
12
12
  import { sanitizeDisplayLabel } from "../core/display-label.ts";
13
13
  import { assignedIdentityPrompt } from "../core/naming.ts";
14
14
  import { roleHint } from "../orchestration/roster.ts";
15
- import { type AgentRunSpec, isPositiveFiniteMs, type SteerFn, type StrategyEngine } from "../orchestration/sdk.ts";
15
+ import { type AgentProgress, type AgentRunSpec, isPositiveFiniteMs, type SteerFn, type StrategyEngine } from "../orchestration/sdk.ts";
16
16
  import type { AgentResult } from "../orchestration/types.ts";
17
17
  import { type ChildEngineOptions, type ChildRunSpec, runChildAgent } from "./child.ts";
18
18
  import { looksLikeProviderError } from "./errors.ts";
19
+ import { piHostModuleHint } from "./host-module.ts";
19
20
  import { nextChildHandle } from "./handles.ts";
20
21
  import { combineSignals } from "./signals.ts";
21
22
  import { toolModifierFailure, unknownAgentFailure, unknownContractFailure } from "./spec-preflight.ts";
22
- import type { ToolEvent } from "./stream.ts";
23
23
 
24
24
  /** The engine-side face of the cross-process broker (spec B3/B4/B7) — a child-engine
25
25
  * spawn's connection to the supervisor-hosted relay. The concrete implementation
@@ -106,7 +106,7 @@ export function makeEngine(deps: EngineAdapterDeps): StrategyEngine {
106
106
  return {
107
107
  async run(
108
108
  spec: AgentRunSpec,
109
- onProgress?: (p: { output: string; tokens?: number; activity?: string; toolEvent?: ToolEvent }) => void,
109
+ onProgress?: (p: AgentProgress) => void,
110
110
  callSignal?: AbortSignal,
111
111
  /** Called once with a steer handle when the broker is on (in-process parity). */
112
112
  onSteerable?: (steer: SteerFn) => void,
@@ -153,7 +153,7 @@ export function makeEngine(deps: EngineAdapterDeps): StrategyEngine {
153
153
  if (isPositiveFiniteMs(spec.timeoutMs)) childOptions.timeoutMs = spec.timeoutMs;
154
154
  if (onProgress) {
155
155
  childOptions.onProgress = (snap) =>
156
- onProgress({ output: snap.output, tokens: snap.tokens, ...(snap.activity ? { activity: snap.activity } : {}), ...(snap.toolOutputAt !== undefined ? { toolOutputAt: snap.toolOutputAt } : {}), ...(snap.toolEvent ? { toolEvent: snap.toolEvent } : {}) });
156
+ onProgress({ output: snap.output, tokens: snap.tokens, ...(snap.usage ? { usage: snap.usage } : {}), ...(snap.activity ? { activity: snap.activity } : {}), ...(snap.toolEvent ? { toolEvent: snap.toolEvent } : {}) });
157
157
  }
158
158
  // The run aborts if EITHER the whole-run signal or this agent's own (UI stop) fires.
159
159
  const signal = combineSignals(deps.signal, callSignal);
@@ -215,6 +215,8 @@ export function makeEngine(deps: EngineAdapterDeps): StrategyEngine {
215
215
  const tag = `[${spec.agent} · ${modelRef}${dyn}]`;
216
216
  if (child.errorMessage) result.error = `${tag} ${child.errorMessage}`;
217
217
  else if (!child.ok) result.error = `${tag} ${child.stderr.trim() || `agent failed (exit ${child.exitCode})`}`;
218
+ const hostModuleHint = !child.ok ? piHostModuleHint(`${child.errorMessage ?? ""}\n${child.stderr}`) : undefined;
219
+ if (hostModuleHint && result.error) result.error += ` — ${hostModuleHint}`;
218
220
  // MCP-leg diagnosis: an `mcp: true` leg is routed here (the child engine) precisely so
219
221
  // `pi-mcp-adapter` initializes in a fresh headless `pi -p`. When such a leg fails without
220
222
  // completing a single turn, the near-certain cause is the MCP adapter hanging in init —
@@ -222,7 +224,7 @@ export function makeEngine(deps: EngineAdapterDeps): StrategyEngine {
222
224
  // Keyed on turns===0 (never really started), not empty output, so an early tool/log line
223
225
  // can't mask it. Name the cause + the fix so the operator never has to guess (a real prod
224
226
  // report: a dead `mcp: true` async delegation with an opaque timeout).
225
- if (!child.ok && spec.mcp === true && child.usage.turns === 0) {
227
+ if (!child.ok && !hostModuleHint && spec.mcp === true && child.usage.turns === 0) {
226
228
  result.error =
227
229
  `${result.error ?? `${tag} agent failed`} — mcp:true leg never completed a turn: the MCP adapter likely hung initializing in the headless child ` +
228
230
  `(a server needing interactive OAuth can't authenticate without a UI). Fix: authenticate the MCP server once in a normal \`pi\` session (\`/mcp auth\`) so its token is cached — the headless child reuses it; ` +
@@ -239,9 +241,11 @@ export function makeEngine(deps: EngineAdapterDeps): StrategyEngine {
239
241
  ? "timeout"
240
242
  : child.aborted
241
243
  ? "abort"
242
- : child.stopReason === "error" || looksLikeProviderError(`${child.errorMessage ?? ""}\n${child.stderr}`)
243
- ? "provider"
244
- : "agent";
244
+ : hostModuleHint
245
+ ? "agent"
246
+ : child.stopReason === "error" || looksLikeProviderError(`${child.errorMessage ?? ""}\n${child.stderr}`)
247
+ ? "provider"
248
+ : "agent";
245
249
 
246
250
  if (contractDef) {
247
251
  // Shared parse+validate (unwraps ```json fences / prose first) — a member's