@nredd/pi-subagents 0.0.0-stage → 0.22.0-nredd.10

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1444 -2
  3. package/dist/chunks/chunk-MSGGFB5Q.ts +6755 -0
  4. package/dist/chunks/chunk-MSGGFB5Q.ts.map +7 -0
  5. package/dist/chunks/conversation-viewer-A7AMRDVY.ts +15 -0
  6. package/dist/chunks/conversation-viewer-A7AMRDVY.ts.map +7 -0
  7. package/dist/index.ts +9874 -0
  8. package/dist/index.ts.map +7 -0
  9. package/package.json +82 -4
  10. package/src/abortable.ts +43 -0
  11. package/src/agent-color.ts +161 -0
  12. package/src/agent-file-toggle.ts +298 -0
  13. package/src/agent-manager.ts +2431 -0
  14. package/src/agent-runner.ts +1703 -0
  15. package/src/agent-source-badge.ts +75 -0
  16. package/src/agent-types.ts +346 -0
  17. package/src/child-context.ts +33 -0
  18. package/src/clock.ts +25 -0
  19. package/src/completion-outbox.ts +144 -0
  20. package/src/context.ts +58 -0
  21. package/src/cross-extension-rpc.ts +200 -0
  22. package/src/custom-agents.ts +441 -0
  23. package/src/default-agents.ts +126 -0
  24. package/src/detached-results.ts +150 -0
  25. package/src/enabled-models.ts +181 -0
  26. package/src/enterprise-local.ts +67 -0
  27. package/src/env.ts +33 -0
  28. package/src/error-message.ts +11 -0
  29. package/src/git-path.ts +66 -0
  30. package/src/group-join.ts +142 -0
  31. package/src/home-paths.ts +27 -0
  32. package/src/index.ts +5047 -0
  33. package/src/invocation-config.ts +158 -0
  34. package/src/memory.ts +179 -0
  35. package/src/mention-clone.ts +200 -0
  36. package/src/mention.ts +141 -0
  37. package/src/model-call.ts +83 -0
  38. package/src/model-resolver.ts +333 -0
  39. package/src/model-scope.ts +71 -0
  40. package/src/model-selection.ts +170 -0
  41. package/src/model-tiers.ts +176 -0
  42. package/src/nested-tools.ts +512 -0
  43. package/src/notification-result.ts +118 -0
  44. package/src/output-file.ts +186 -0
  45. package/src/package-resources.ts +639 -0
  46. package/src/plan-mode-children.ts +176 -0
  47. package/src/process-budget.ts +346 -0
  48. package/src/prompts.ts +161 -0
  49. package/src/quota-admission.ts +121 -0
  50. package/src/resource-loader.ts +65 -0
  51. package/src/schedule-store.ts +153 -0
  52. package/src/schedule.ts +514 -0
  53. package/src/settings.ts +851 -0
  54. package/src/skill-loader.ts +288 -0
  55. package/src/stale-ctx.ts +16 -0
  56. package/src/startup-error.ts +29 -0
  57. package/src/status-note.ts +113 -0
  58. package/src/structured-output.ts +130 -0
  59. package/src/types.ts +487 -0
  60. package/src/ui/agent-mention.ts +219 -0
  61. package/src/ui/agent-row.ts +245 -0
  62. package/src/ui/agent-widget.ts +722 -0
  63. package/src/ui/conversation-viewer.ts +837 -0
  64. package/src/ui/fallback-flag.ts +114 -0
  65. package/src/ui/fleet-list.ts +1020 -0
  66. package/src/ui/fleet-state.ts +154 -0
  67. package/src/ui/notification-view.ts +139 -0
  68. package/src/ui/schedule-menu.ts +105 -0
  69. package/src/ui/select-item.ts +45 -0
  70. package/src/ui/viewer-keys.ts +39 -0
  71. package/src/ui/workflow-card.ts +459 -0
  72. package/src/ui/workflow-dialog.ts +1138 -0
  73. package/src/ui/workflow-menu.ts +193 -0
  74. package/src/usage.ts +167 -0
  75. package/src/workflow/collisions.ts +123 -0
  76. package/src/workflow/entry.ts +47 -0
  77. package/src/workflow/host.ts +398 -0
  78. package/src/workflow/journal.ts +176 -0
  79. package/src/workflow/json-schema.ts +129 -0
  80. package/src/workflow/meta.ts +329 -0
  81. package/src/workflow/progress.ts +554 -0
  82. package/src/workflow/runtime.ts +1249 -0
  83. package/src/workflow/saved.ts +259 -0
  84. package/src/workflow/task.ts +317 -0
  85. package/src/workflow/tool-description.ts +200 -0
  86. package/src/workflow/worker-source.ts +903 -0
  87. package/src/worktree.ts +520 -0
  88. package/src/xml.ts +13 -0
package/src/types.ts ADDED
@@ -0,0 +1,487 @@
1
+ /**
2
+ * types.ts — Type definitions for the subagent system.
3
+ */
4
+
5
+ import type { ThinkingLevel } from "@earendil-works/pi-ai";
6
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
7
+ import type { ModelFallback } from "./model-resolver.js";
8
+ import type { ModelSelection } from "./model-selection.js";
9
+ import type { TierDisclosure } from "./model-tiers.js";
10
+ import type { PlanGate, PlanToolPolicy } from "./plan-mode-children.js";
11
+ import type { BudgetLease } from "./process-budget.js";
12
+ import type { LifetimeUsage } from "./usage.js";
13
+
14
+ export type { ThinkingLevel };
15
+
16
+ /** Agent type: any string name (built-in defaults or user-defined). */
17
+ export type SubagentType = string;
18
+
19
+ /** Names of the three embedded default agents. */
20
+ export const DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"] as const;
21
+
22
+ /** Memory scope for persistent agent memory. */
23
+ export type MemoryScope = "user" | "project" | "local";
24
+
25
+ /**
26
+ * Isolation mode for agent execution.
27
+ *
28
+ * `"off"` exists for the caller's benefit, not the runtime's: models that fill
29
+ * every optional parameter had no legal way to decline a single-value
30
+ * `isolation` field and kept spawning worktrees they had just reasoned their
31
+ * way out of (#231, #184). It is an input spelling only —
32
+ * `resolveAgentInvocationConfig` collapses it to `undefined`, so nothing
33
+ * downstream sees a value other than `"worktree"`. In an agent file it is a
34
+ * genuine veto, since agent config outranks tool-call params.
35
+ */
36
+ export type IsolationMode = "worktree" | "off";
37
+
38
+ /** Unified agent configuration — used for both default and user-defined agents. */
39
+ export interface AgentConfig {
40
+ name: string;
41
+ /** UI name. `display_name` wins; Claude Code's `name` is accepted as a fallback. */
42
+ displayName?: string;
43
+ /** Claude Code-compatible name color (named color or #RRGGBB). */
44
+ color?: string;
45
+ description: string;
46
+ builtinToolNames?: string[];
47
+ /** Raw `ext:` selector entries from the `tools:` CSV, e.g. ["ext:foo", "ext:bar/x"].
48
+ * Presence of any entry flips extension tools to an explicit allowlist. */
49
+ extSelectors?: string[];
50
+ /** Tool denylist — these tools are removed even if `builtinToolNames` or extensions include them. */
51
+ disallowedTools?: string[];
52
+ /** true = inherit all, string[] = only listed, false = none */
53
+ extensions: true | string[] | false;
54
+ /** Extension-name denylist applied after the `extensions:` include set. Exclude wins.
55
+ * Plain canonical names only (case-insensitive); no paths, no wildcard. */
56
+ excludeExtensions?: string[];
57
+ /** true = inherit all, string[] = only listed, false = none */
58
+ skills: true | string[] | false;
59
+ model?: string;
60
+ thinking?: ThinkingLevel;
61
+ maxTurns?: number;
62
+ /** Persist this subagent as a normal pi session instead of keeping it in memory only. */
63
+ persistSession?: boolean;
64
+ /** Write the subagent's .output transcript. Defaults to true; false suppresses only that transcript. */
65
+ outputTranscript?: boolean;
66
+ /** Optional session directory used when persistSession is true. Omitted = pi's normal session location. */
67
+ sessionDir?: string;
68
+ /**
69
+ * Nested delegation, off by default: undefined = no nested tools;
70
+ * "all" = any enabled agent; string[] = only those agent types.
71
+ */
72
+ allowedSubagents?: "all" | string[];
73
+ systemPrompt: string;
74
+ promptMode: "replace" | "append";
75
+ /** Default for spawn: fork parent conversation. undefined = caller decides. */
76
+ inheritContext?: boolean;
77
+ /** Default for spawn: run in background. undefined = caller decides. */
78
+ runInBackground?: boolean;
79
+ /** Default for spawn: no extension tools. undefined = caller decides. */
80
+ isolated?: boolean;
81
+ /** Persistent memory scope — agents with memory get a persistent directory and MEMORY.md */
82
+ memory?: MemoryScope;
83
+ /**
84
+ * Isolation mode — "worktree" runs the agent in a temporary git worktree,
85
+ * "off" refuses one even when the caller asks (frontmatter outranks params).
86
+ */
87
+ isolation?: IsolationMode;
88
+ /** true = this is an embedded default agent (informational) */
89
+ isDefault?: boolean;
90
+ /** false = agent is hidden from the registry */
91
+ enabled?: boolean;
92
+ /**
93
+ * Where this agent was loaded from. `"package"` means an installed pi package
94
+ * declared it (see package-resources.ts) — those files live under pi's install
95
+ * root, so `/agents` treats them as read-only and offers Eject instead of Edit.
96
+ */
97
+ source?: "default" | "project" | "global" | "package";
98
+ /** Path of the .md it was loaded from. Unset for embedded defaults. */
99
+ sourcePath?: string;
100
+ }
101
+
102
+ export type JoinMode = 'async' | 'group' | 'smart';
103
+
104
+ /**
105
+ * Display mode for the persistent above-editor agent widget.
106
+ * - `all`: show every agent (foreground + background).
107
+ * - `background`: hide foreground agents (they already render inline as the
108
+ * Agent tool result, #118); show background/queued/scheduled/RPC.
109
+ * - `off`: hide the widget entirely.
110
+ */
111
+ export type WidgetMode = 'all' | 'background' | 'off';
112
+
113
+ /**
114
+ * How much of the conversation viewer's transcript is rendered as Markdown.
115
+ * - `off`: every line wraps as literal text, as it did before the mode existed.
116
+ * - `assistant`: assistant text renders as Markdown; tool results stay verbatim
117
+ * and dim. The default, because assistant text *is* Markdown by contract
118
+ * while a tool result is arbitrary bytes — a Markdown pass over a log or a
119
+ * diff eats `#` from shell comments, swallows a `---` line into a setext
120
+ * heading, re-fences indented output and redraws `| a | b |` as a table.
121
+ * (Ordered-list renumbering is the one such rewrite actively suppressed —
122
+ * see `MARKDOWN_OPTIONS` — because it silently changes data, not layout.)
123
+ * - `all`: tool results render as Markdown too, for tools that genuinely emit
124
+ * it (#210's `ctx_execute`), accepting the rewrites above on ones that don't.
125
+ */
126
+ export type ViewerMarkdownMode = 'off' | 'assistant' | 'all';
127
+
128
+ /**
129
+ * How `@handle message` starts an agent that is not already running.
130
+ * - `model`: inject Claude Code's `agent_mention` reminder and let the main
131
+ * model spawn it with the `Agent` tool, which is what Claude Code does.
132
+ * - `direct`: spawn it here, immediately, with the typed message as its prompt
133
+ * and no main-model turn spent.
134
+ * - `off`: `@` means only "attach a file" again.
135
+ *
136
+ * Messaging a running agent and resuming a finished one are direct in every
137
+ * mode — Claude Code only differs from us on the *new* invocation.
138
+ */
139
+ export type AgentMentionMode = 'model' | 'direct' | 'off';
140
+
141
+ /**
142
+ * What survives a record's eviction so `@handle` keeps working. The live record
143
+ * is discarded after ~10 minutes, but the pi session it wrote is still on disk,
144
+ * and this is the little that is needed to find and describe it again.
145
+ */
146
+ export interface AgentTombstone {
147
+ handle: string;
148
+ alias?: string;
149
+ id: string;
150
+ type: SubagentType;
151
+ description: string;
152
+ /** Always set — a record with no session file is never tombstoned. */
153
+ sessionFile: string;
154
+ /** The evicted run's tier and fallback, handed back when `@handle` reopens it. */
155
+ modelTier?: TierDisclosure;
156
+ modelFallback?: ModelFallback;
157
+ completedAt: number;
158
+ }
159
+
160
+ /**
161
+ * What `@handle` resolved to: an agent still in memory, or the remains of one
162
+ * whose conversation can be reopened from disk.
163
+ */
164
+ export type MentionResolution =
165
+ | { kind: "live"; record: AgentRecord }
166
+ | { kind: "tombstone"; entry: AgentTombstone };
167
+
168
+ /** Which concurrency pool a run is charged to. */
169
+ export type Pool = "background" | "foreground";
170
+
171
+ export interface AgentRecord {
172
+ id: string;
173
+ type: SubagentType;
174
+ /**
175
+ * Typeable name for the `@handle message` prompt mention, derived from the
176
+ * agent type and numbered when siblings collide (`explore`, `explore-2`).
177
+ * Top-level agents only — nested children are hidden from every top-level
178
+ * surface, so nothing can address them.
179
+ */
180
+ handle?: string;
181
+ /**
182
+ * A second, memorable handle from the spawner's `name` (`@auth-audit`), drawn
183
+ * from the same namespace as `handle` so the two can never collide. Purely
184
+ * additive: `handle` is assigned regardless, so a named agent stays reachable
185
+ * by its type and `@explore` never comes to mean "start another one".
186
+ */
187
+ alias?: string;
188
+ /**
189
+ * `handleBase(name)` before collision numbering: `auth-audit` for an alias of
190
+ * `auth-audit-2`. Announced as `nameBase` so a listener matching the spawner's
191
+ * `name` finds a retry rather than the failed first try still holding the name.
192
+ */
193
+ nameBase?: string;
194
+ description: string;
195
+ status: "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
196
+ /**
197
+ * Set when the agent that spawned this one stopped it with `stop_subagent`
198
+ * (#443). Unset on a `stopped` record means a human stopped it (a viewer's
199
+ * `x`, the stop-all chord, `/agents`, or the RPC), which the status notes
200
+ * word differently.
201
+ */
202
+ stoppedBy?: "parent";
203
+ result?: string;
204
+ error?: string;
205
+ toolUses: number;
206
+ startedAt: number;
207
+ completedAt?: number;
208
+ session?: AgentSession;
209
+ abortController?: AbortController;
210
+ promise?: Promise<string>;
211
+ /**
212
+ * A caller is awaiting this agent inline (`spawnAndWait`) — what
213
+ * `maxConcurrentForeground` bounds. Distinct from `isBackground === false`,
214
+ * which says only that the agent has an inline result surface: a detached
215
+ * cross-extension RPC spawn is foreground by that measure and yet blocks
216
+ * nobody, so it takes no slot.
217
+ *
218
+ * Cleared when the inline caller stops waiting: by `sendToBackground`, or
219
+ * when the run settles.
220
+ */
221
+ blocking?: boolean;
222
+ /**
223
+ * The concurrency pool this run's slot is charged to, set once when it
224
+ * starts. `settleRun` releases exactly this; `sendToBackground` is the only
225
+ * thing that moves it, together with the counters.
226
+ */
227
+ chargedPool?: Pool;
228
+ /**
229
+ * This run's process-wide budget lease (`process-budget.ts`), held from
230
+ * admission until the run settles. Spawns and resumes alike; cleared on release.
231
+ */
232
+ budgetLease?: BudgetLease;
233
+ /**
234
+ * Removes the listener that stops this run when its caller's signal aborts.
235
+ * At most one is armed at a time — queueing arms one, starting replaces it.
236
+ */
237
+ detachParentAbort?: () => void;
238
+ /**
239
+ * Present only while the record is "queued": resolves when it leaves the
240
+ * queue, started or aborted. `spawnAndWait` waits on this because a queued
241
+ * record has no `promise` yet. Always resolves, never rejects — a rejection
242
+ * would escape into the caller's tool `execute` and take down pi's whole
243
+ * Promise.all tool batch.
244
+ */
245
+ startGate?: Promise<void>;
246
+ groupId?: string;
247
+ joinMode?: JoinMode;
248
+ /** Set when result was already consumed via get_subagent_result — suppresses completion notification. */
249
+ resultConsumed?: boolean;
250
+ /**
251
+ * Set when the completion notice was sent to the parent, whole or clipped, failures
252
+ * included. Unlike `resultConsumed` it does not mean FleetView acknowledged it; the
253
+ * reap notice only asks "did the parent ever see this?".
254
+ */
255
+ noticeDelivered?: boolean;
256
+ /**
257
+ * The latest run actually began executing (the runner was started on a live signal).
258
+ * Positive evidence the reap notice requires: a record that was stopped in the queue,
259
+ * refused, or failed before the runner started never ran, so evicting it loses nothing.
260
+ * Reset on every resume.
261
+ */
262
+ ranThisRun?: boolean;
263
+ /** When `resultConsumed` was set; the GC keeps a consumed record for a short window from here. */
264
+ consumedAt?: number;
265
+ /** Steering messages queued before the session was ready. */
266
+ pendingSteers?: string[];
267
+ /**
268
+ * The Plan restriction its runs are under (#437): set at spawn, re-decided at
269
+ * each resume, read by the child's tool hooks on every call.
270
+ */
271
+ planGate?: PlanGate;
272
+ /** Asks the parent's pi-plan-mode again, for a resume. */
273
+ planQuery?: () => PlanToolPolicy | undefined;
274
+ /** Worktree info if the agent is running in an isolated worktree. */
275
+ worktree?: { path: string; branch: string; baseSha: string; repoRoot: string; workPath: string };
276
+ /** Worktree cleanup result after agent completion. */
277
+ worktreeResult?: { hasChanges: boolean; branch?: string; path?: string; error?: string };
278
+ /** The tool_use_id from the original Agent tool call. */
279
+ toolCallId?: string;
280
+ /** Path to the streaming output transcript file. */
281
+ outputFile?: string;
282
+ /**
283
+ * The agent's pi session file, when it was persisted (`persist_session`, or
284
+ * the `rememberAgents` default). Captured so a mention can reopen the
285
+ * conversation after the record itself has been evicted; undefined for an
286
+ * in-memory session, which leaves nothing to reopen.
287
+ */
288
+ sessionFile?: string;
289
+ /** Cleanup function for the output file stream subscription. */
290
+ outputCleanup?: () => void;
291
+ /**
292
+ * Lifetime usage breakdown, accumulated via `message_end` events. Survives
293
+ * compaction. Total = input + output + cacheWrite (cacheRead deliberately
294
+ * excluded — see issue #38). Initialized to zeros at spawn.
295
+ */
296
+ lifetimeUsage: LifetimeUsage;
297
+ /** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
298
+ compactionCount: number;
299
+ /**
300
+ * Whether this agent runs in the background. Tri-state, initialized at spawn
301
+ * from `SpawnOptions.isBackground` and flipped `false` → `true` (once, never
302
+ * back) by `sendToBackground`: `true` = background, `false` =
303
+ * foreground (has an inline Agent tool-result surface), `undefined` = the
304
+ * caller never declared it (e.g. a cross-extension RPC spawn, which is detached
305
+ * and has no inline surface). The widget's background-only filter keys off this
306
+ * — and excludes only explicit `false`, so `undefined` agents stay visible.
307
+ * Reliable across ALL spawn paths, unlike the UI-only `invocation` snapshot,
308
+ * which only the Agent-tool path populates.
309
+ */
310
+ isBackground?: boolean;
311
+ /** Resolved spawn params, captured for UI display. Fixed at spawn time. */
312
+ invocation?: AgentInvocation;
313
+ /**
314
+ * Set when a configured model did not resolve and the agent runs on the parent
315
+ * model. The single source for every disclosure surface; a resume reuses it
316
+ * as is, never recomputes it from the resuming call's config.
317
+ */
318
+ modelFallback?: ModelFallback;
319
+ /**
320
+ * This record's selection, retained across live resumes and physical relabelling.
321
+ * An evicted-session reopen selects anew; current fallback disclosure always
322
+ * comes from that selection.
323
+ */
324
+ modelSelection?: ModelSelection;
325
+ /** Actual run's tier provenance, preserved through eviction and replay. */
326
+ modelTier?: TierDisclosure;
327
+ /** Prior evicted run's outcome, never the continuation's selection or disclosure. */
328
+ previousModelTier?: TierDisclosure;
329
+ /** Prior evicted run's fallback, retained separately from the continuation's current disclosure. */
330
+ previousModelFallback?: ModelFallback;
331
+ /**
332
+ * The evicted id this record reopened (#330), announced as `previousId` on its
333
+ * lifecycle events so a listener keyed by the old id follows the conversation.
334
+ */
335
+ previousId?: string;
336
+ /**
337
+ * Order of this record's spawn among the manager's spawns and settles, set once at
338
+ * creation (before any queueing) and announced as `spawnSeq` on lifecycle events.
339
+ */
340
+ spawnSeq?: number;
341
+ /** Order of this record's latest settle on the same counter, announced as `settleSeq`. */
342
+ settleSeq?: number;
343
+ /** Nesting depth: top-level subagent = 1. */
344
+ depth: number;
345
+ /**
346
+ * The validated `StructuredOutput` payload, as canonical JSON.
347
+ *
348
+ * Set only when the spawn asked for a schema. Separate from `result` because
349
+ * `result` is prose for a reader — previewed in the widget, written to the
350
+ * transcript, and appended to with the worktree branch note — and JSON that
351
+ * has been appended to no longer parses.
352
+ */
353
+ structuredJson?: string;
354
+ /** Whether the child needed the extra structured-output prompt. */
355
+ structuredRetried?: boolean;
356
+ /** Parent agent ID for ownership-scoped nested controls. */
357
+ parentAgentId?: string;
358
+ /**
359
+ * The workflow run that owns this child, when a workflow spawned it.
360
+ *
361
+ * Owned the same way a nested child is owned by its parent: filtered out of
362
+ * every top-level surface, and outside the `maxConcurrent` pool. See
363
+ * `isTopLevelAgent`.
364
+ */
365
+ workflowId?: string;
366
+ /** Effective inherited nesting cap for this branch. */
367
+ maxSubagentDepth?: number;
368
+ /**
369
+ * Session id of the root (main) session this branch descends from. Nested
370
+ * spawns inherit it so their transcripts file under the same session
371
+ * directory as their ancestors' instead of the child session's own id.
372
+ */
373
+ rootSessionId?: string;
374
+ }
375
+
376
+ /**
377
+ * What a session reports as its level: pi's `ThinkingLevel` plus the `"off"` a
378
+ * model with thinking disabled reports. Display-only — spawning still takes a
379
+ * `ThinkingLevel`, so this widening cannot leak into an invocation.
380
+ */
381
+ export type EffectiveThinkingLevel = ThinkingLevel | "off";
382
+
383
+ export interface AgentInvocation {
384
+ /** Short display name for tight rows, e.g. "haiku 4.5". Always set once known. */
385
+ modelName?: string;
386
+ /** Canonical `provider/id`, for surfaces with room to disambiguate providers. */
387
+ modelId?: string;
388
+ /** The level actually in effect, once a session exists to report one. */
389
+ thinking?: EffectiveThinkingLevel;
390
+ /**
391
+ * What the caller asked for, kept only when they did not get it — pi clamped
392
+ * the level to the model's capabilities, or an agent file's frontmatter
393
+ * outranked the parameter (#182). The snapshot exists to answer "did the spawn
394
+ * honor my instructions?" (#62), which it cannot do if the request is lost, so
395
+ * neither `requested*` field is overwritten once set.
396
+ */
397
+ requestedThinking?: EffectiveThinkingLevel;
398
+ maxTurns?: number;
399
+ isolated?: boolean;
400
+ inheritContext?: boolean;
401
+ runInBackground?: boolean;
402
+ isolation?: IsolationMode;
403
+ /** The run is under Plan mode's policy because its parent was planning (#437). */
404
+ planMode?: boolean;
405
+ }
406
+
407
+ /** Details attached to custom notification messages for visual rendering. */
408
+ export interface NotificationDetails {
409
+ id: string;
410
+ description: string;
411
+ status: string;
412
+ toolUses: number;
413
+ turnCount: number;
414
+ maxTurns?: number;
415
+ totalTokens: number;
416
+ /**
417
+ * Estimated cost in USD, from pi's per-message `usage.cost.total`. Always
418
+ * populated (0 when the model has no pricing); the renderer decides whether
419
+ * to show it, per the `showCost` setting.
420
+ */
421
+ totalCost?: number;
422
+ durationMs: number;
423
+ outputFile?: string;
424
+ error?: string;
425
+ /** Set for a failed agent whose transcript survived: `resume agent <id> to continue` (status-note.ts `resumeHint`). */
426
+ resumeHint?: string;
427
+ /** The `Note:` line when the agent ran on the parent model because its configured one did not resolve. */
428
+ modelNote?: string;
429
+ modelTier?: TierDisclosure;
430
+ resultPreview: string;
431
+ /** Additional agents in a group notification. */
432
+ others?: NotificationDetails[];
433
+ }
434
+
435
+ export interface EnvInfo {
436
+ isGitRepo: boolean;
437
+ branch: string;
438
+ platform: string;
439
+ }
440
+
441
+ /**
442
+ * A subagent spawn registered to fire on a schedule.
443
+ *
444
+ * Stored at `<cwd>/.pi/subagent-schedules/<sessionId>.json`. Session-scoped:
445
+ * survives `/resume` but resets on `/new`, mirroring pi-chonky-tasks.
446
+ */
447
+ export interface ScheduledSubagent {
448
+ id: string;
449
+ /** Unique within store. Defaults to `description`. */
450
+ name: string;
451
+ description: string;
452
+ /** Raw user input — cron expr | "+10m" | ISO | "5m". */
453
+ schedule: string;
454
+ scheduleType: "cron" | "once" | "interval";
455
+ /** Computed at create time for interval/once. */
456
+ intervalMs?: number;
457
+
458
+ // spawn params (subset of Agent tool params; no inherit_context, no resume)
459
+ subagent_type: SubagentType;
460
+ prompt: string;
461
+ model?: string;
462
+ thinking?: ThinkingLevel;
463
+ max_turns?: number;
464
+ isolated?: boolean;
465
+ isolation?: IsolationMode;
466
+
467
+ // state
468
+ enabled: boolean;
469
+ /** ISO timestamp. */
470
+ createdAt: string;
471
+ lastRun?: string;
472
+ lastStatus?: "success" | "error" | "running";
473
+ /**
474
+ * Parked by quota admission: re-check admission at fire time and re-park at
475
+ * the new reset while the model is still blocked. See `quota-admission.ts`.
476
+ */
477
+ quotaParked?: boolean;
478
+ /** Refreshed on every fire and on store load. */
479
+ nextRun?: string;
480
+ runCount: number;
481
+ }
482
+
483
+ export interface ScheduleStoreData {
484
+ /** For future migrations. */
485
+ version: 1;
486
+ jobs: ScheduledSubagent[];
487
+ }