pi-subagents 0.40.0 → 0.41.0

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 (119) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +246 -525
  3. package/agents/oracle.md +1 -0
  4. package/package.json +12 -4
  5. package/prompts/parallel-context-build.md +1 -1
  6. package/prompts/parallel-handoff-plan.md +1 -1
  7. package/prompts/review-loop.md +1 -1
  8. package/skills/pi-subagents/SKILL.md +6 -6
  9. package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
  10. package/skills/pi-subagents/references/execution-controls.md +98 -97
  11. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  12. package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
  13. package/src/agents/agent-management.ts +155 -65
  14. package/src/agents/agent-serializer.ts +19 -0
  15. package/src/agents/agents.ts +154 -71
  16. package/src/agents/chain-serializer.ts +10 -7
  17. package/src/agents/frontmatter.ts +5 -3
  18. package/src/agents/identity.ts +1 -1
  19. package/src/agents/proactive-skills.ts +13 -10
  20. package/src/agents/skills.ts +23 -6
  21. package/src/api/control-channel.ts +4 -0
  22. package/src/api/delegation.ts +26 -194
  23. package/src/api/external-runs.ts +129 -0
  24. package/src/api/intercom-bridge.ts +3 -0
  25. package/src/api/pi-args.ts +5 -0
  26. package/src/api/preflight.ts +3 -3
  27. package/src/api/shared-types.ts +19 -0
  28. package/src/extension/config.ts +10 -0
  29. package/src/extension/control-notices.ts +5 -39
  30. package/src/extension/doctor.ts +10 -9
  31. package/src/extension/fanout-child.ts +7 -4
  32. package/src/extension/index.ts +232 -68
  33. package/src/extension/rpc.ts +18 -12
  34. package/src/extension/schemas.ts +48 -37
  35. package/src/extension/tool-description.ts +36 -86
  36. package/src/inspectors/herdr/actions.ts +229 -0
  37. package/src/inspectors/herdr/client.ts +130 -0
  38. package/src/inspectors/herdr/inspector-runner.ts +141 -0
  39. package/src/inspectors/herdr/project-panes.ts +154 -0
  40. package/src/integrations/herdr-status.ts +330 -0
  41. package/src/intercom/intercom-bridge.ts +3 -2
  42. package/src/intercom/result-intercom.ts +5 -1
  43. package/src/missions/actions.ts +372 -0
  44. package/src/missions/lifecycle.ts +314 -0
  45. package/src/missions/store.ts +442 -0
  46. package/src/missions/types.ts +135 -0
  47. package/src/policy/authority.ts +46 -0
  48. package/src/profiles/profiles.ts +29 -3
  49. package/src/runs/background/async-execution.ts +98 -49
  50. package/src/runs/background/async-job-tracker.ts +10 -2
  51. package/src/runs/background/async-resume.ts +6 -6
  52. package/src/runs/background/async-status.ts +29 -1
  53. package/src/runs/background/auto-drain.ts +3 -3
  54. package/src/runs/background/chain-append.ts +3 -2
  55. package/src/runs/background/control-channel.ts +9 -7
  56. package/src/runs/background/fleet-view.ts +3 -4
  57. package/src/runs/background/notify.ts +2 -1
  58. package/src/runs/background/process-terminal.ts +5 -5
  59. package/src/runs/background/result-watcher.ts +13 -5
  60. package/src/runs/background/run-id-resolver.ts +3 -3
  61. package/src/runs/background/run-status.ts +35 -8
  62. package/src/runs/background/scheduled-runs.ts +602 -375
  63. package/src/runs/background/stale-run-reconciler.ts +3 -3
  64. package/src/runs/background/subagent-runner.ts +608 -445
  65. package/src/runs/background/subagent-wait.ts +50 -9
  66. package/src/runs/background/wait-subscriptions.ts +253 -0
  67. package/src/runs/background/wait-tool.ts +12 -4
  68. package/src/runs/foreground/async-steering-action.ts +3 -3
  69. package/src/runs/foreground/chain-clarify.ts +8 -4
  70. package/src/runs/foreground/chain-execution.ts +56 -30
  71. package/src/runs/foreground/execution.ts +15 -2
  72. package/src/runs/foreground/subagent-executor.ts +1023 -273
  73. package/src/runs/shared/acceptance.ts +28 -6
  74. package/src/runs/shared/child-protocol.ts +302 -22
  75. package/src/runs/shared/dynamic-fanout.ts +1 -1
  76. package/src/runs/shared/external-cli-runner.ts +130 -0
  77. package/src/runs/shared/long-running-guard.ts +42 -1
  78. package/src/runs/shared/nested-events.ts +59 -5
  79. package/src/runs/shared/nested-render.ts +9 -4
  80. package/src/runs/shared/parallel-handoff.ts +86 -2
  81. package/src/runs/shared/parallel-utils.ts +11 -2
  82. package/src/runs/shared/permissions.ts +95 -0
  83. package/src/runs/shared/pi-args.ts +11 -1
  84. package/src/runs/shared/pi-spawn.ts +11 -1
  85. package/src/runs/shared/run-history.ts +1 -1
  86. package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
  87. package/src/runs/shared/subagent-startup-retry.ts +5 -2
  88. package/src/runs/shared/turn-budget.ts +6 -6
  89. package/src/runs/shared/worktree.ts +122 -12
  90. package/src/shared/accessible-dir.ts +29 -7
  91. package/src/shared/artifacts.ts +18 -1
  92. package/src/shared/fork-context.ts +3 -2
  93. package/src/shared/launch-contract.ts +1 -0
  94. package/src/shared/settings.ts +10 -0
  95. package/src/shared/types.ts +156 -14
  96. package/src/shared/utils.ts +8 -6
  97. package/src/slash/delegation-adapters.ts +32 -194
  98. package/src/slash/delegation-request.ts +43 -126
  99. package/src/slash/prompt-template-bridge.ts +158 -205
  100. package/src/slash/prompt-workflows.ts +21 -57
  101. package/src/slash/slash-bridge.ts +14 -0
  102. package/src/slash/slash-commands.ts +31 -632
  103. package/src/slash/subagents-admin.ts +18 -14
  104. package/src/tui/fleet-status.ts +156 -21
  105. package/src/tui/fleet-transcript.ts +110 -5
  106. package/src/tui/fleet.ts +56 -24
  107. package/src/tui/render.ts +291 -109
  108. package/src/types/pi-runtime-compat.d.ts +14 -0
  109. package/src/watchdog/lsp-diagnostics.ts +12 -7
  110. package/src/watchdog/model-selection.ts +2 -2
  111. package/src/watchdog/permission-arbiter.ts +145 -0
  112. package/src/watchdog/register-child.ts +1 -1
  113. package/src/watchdog/register-main.ts +1 -1
  114. package/src/watchdog/review.ts +4 -1
  115. package/src/watchdog/runtime.ts +3 -2
  116. package/src/workflows/chat-progress.ts +140 -0
  117. package/src/workflows/scripted-workflow.ts +415 -0
  118. package/agents/advisor.md +0 -73
  119. package/src/extension/chain-validation.ts +0 -181
@@ -0,0 +1,330 @@
1
+ import {
2
+ SUBAGENT_ASYNC_COMPLETE_EVENT,
3
+ SUBAGENT_ASYNC_STARTED_EVENT,
4
+ SUBAGENT_CONTROL_EVENT,
5
+ } from "../shared/types.ts";
6
+
7
+ const DEFAULT_SOURCE = "pi-subagents:herdr";
8
+ const DEFAULT_TTL_MS = 120_000;
9
+ const DEFAULT_REFRESH_MS = 45_000;
10
+
11
+ let metadataReportSeq = Date.now() * 1000;
12
+
13
+ function nextMetadataReportSeq(): number {
14
+ metadataReportSeq = Math.max(metadataReportSeq + 1, Date.now() * 1000);
15
+ return metadataReportSeq;
16
+ }
17
+
18
+ export interface HerdrStatusBridgeEvents {
19
+ on(event: string, handler: (data: unknown) => void): (() => void) | void;
20
+ emit(event: string, data: unknown): void;
21
+ }
22
+
23
+ export interface HerdrStatusRun {
24
+ id: string;
25
+ agent?: string;
26
+ agents?: string[];
27
+ needsAttention?: boolean;
28
+ attentionLabel?: string;
29
+ }
30
+
31
+ export interface HerdrStatusBridgeOptions {
32
+ events: HerdrStatusBridgeEvents;
33
+ env?: Record<string, string | undefined>;
34
+ /** Current authoritative active-run projection, used before TTL refresh. */
35
+ getRuns?: () => Iterable<HerdrStatusRun>;
36
+ runHerdr: (args: readonly string[]) => void | Promise<void>;
37
+ ttlMs?: number;
38
+ refreshMs?: number;
39
+ timers?: {
40
+ setInterval: typeof setInterval;
41
+ clearInterval: typeof clearInterval;
42
+ };
43
+ }
44
+
45
+ export interface HerdrStatusBridge {
46
+ /**
47
+ * Binds the pane owner. Only the root interactive session may publish pane
48
+ * metadata: headless parents (print/json), non-UI harnesses, and child
49
+ * runtimes must never fight the pane's lifecycle authority over display
50
+ * state. Also re-syncs runs that survived a reload/resume.
51
+ */
52
+ sessionStarted(input: { hasUI: boolean; runs: Iterable<HerdrStatusRun> }): void;
53
+ agentStarted(): void;
54
+ flush(): Promise<void>;
55
+ dispose(): void;
56
+ }
57
+
58
+ function isRecord(value: unknown): value is Record<string, unknown> {
59
+ return typeof value === "object" && value !== null && !Array.isArray(value);
60
+ }
61
+
62
+ function startedRun(data: unknown): HerdrStatusRun | undefined {
63
+ if (!isRecord(data) || typeof data.id !== "string" || !data.id) return undefined;
64
+ return {
65
+ id: data.id,
66
+ ...(typeof data.agent === "string" ? { agent: data.agent } : {}),
67
+ ...(Array.isArray(data.agents) && data.agents.every((agent) => typeof agent === "string") ? { agents: data.agents as string[] } : {}),
68
+ };
69
+ }
70
+
71
+ function completedRunId(data: unknown): string | undefined {
72
+ if (!isRecord(data)) return undefined;
73
+ const id = typeof data.runId === "string" ? data.runId : data.id;
74
+ return typeof id === "string" && id.length > 0 ? id : undefined;
75
+ }
76
+
77
+ function attentionNotice(data: unknown): { runId: string; label: string } | undefined {
78
+ if (!isRecord(data) || data.source !== "async" || !isRecord(data.event)) return undefined;
79
+ if (data.event.type !== "needs_attention" || typeof data.event.runId !== "string" || !data.event.runId) return undefined;
80
+ const label = typeof data.noticeText === "string" && data.noticeText
81
+ ? data.noticeText
82
+ : typeof data.event.message === "string" && data.event.message
83
+ ? data.event.message
84
+ : "subagent needs attention";
85
+ return { runId: data.event.runId, label };
86
+ }
87
+
88
+ export function registerHerdrStatusBridge(options: HerdrStatusBridgeOptions): HerdrStatusBridge {
89
+ const env = options.env ?? process.env;
90
+ const paneId = env.HERDR_PANE_ID;
91
+ const enabled = env.HERDR_ENV === "1" && typeof paneId === "string" && paneId.length > 0;
92
+ const runHerdr = options.runHerdr;
93
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
94
+ const refreshMs = options.refreshMs ?? DEFAULT_REFRESH_MS;
95
+ const timers = options.timers ?? { setInterval, clearInterval };
96
+ const runs = new Map<string, HerdrStatusRun>();
97
+ const attentionLabels = new Map<string, string>();
98
+ const acknowledgedAttention = new Set<string>();
99
+ const subscriptions: Array<() => void> = [];
100
+ let rootSession = false;
101
+ let published = false;
102
+ let busyRaised = false;
103
+ let busyLabel: string | undefined;
104
+ let blockedRaised = false;
105
+ let blockedLabel: string | undefined;
106
+ let disposed = false;
107
+ let pendingReport: readonly string[] | undefined;
108
+ let draining = false;
109
+ let drainPromise = Promise.resolve();
110
+ let refreshTimer: ReturnType<typeof setInterval> | undefined;
111
+
112
+ const label = (): string => {
113
+ const agents = [...new Set([...runs.values()].flatMap((run) => run.agents?.length ? run.agents : run.agent ? [run.agent] : []))];
114
+ const who = agents.length > 0
115
+ ? ` (${agents.slice(0, 3).join(", ")}${agents.length > 3 ? ", …" : ""})`
116
+ : "";
117
+ return `⏳ ${runs.size} subagent${runs.size === 1 ? "" : "s"}${who}`;
118
+ };
119
+
120
+ const enqueue = (args: readonly string[]): void => {
121
+ pendingReport = args;
122
+ if (draining) return;
123
+ draining = true;
124
+ drainPromise = (async () => {
125
+ while (pendingReport) {
126
+ const next = pendingReport;
127
+ pendingReport = undefined;
128
+ try {
129
+ await runHerdr(next);
130
+ } catch {
131
+ // Herdr integration is best effort; a later state transition or TTL
132
+ // refresh retries with the newest desired snapshot.
133
+ }
134
+ }
135
+ })().finally(() => {
136
+ draining = false;
137
+ });
138
+ };
139
+
140
+ const publish = (): void => {
141
+ if (!enabled || !rootSession || disposed || !paneId) return;
142
+ if (runs.size === 0 && !published) return;
143
+ const seq = String(nextMetadataReportSeq());
144
+ if (runs.size === 0) {
145
+ published = false;
146
+ enqueue([
147
+ "pane", "report-metadata", paneId,
148
+ "--source", DEFAULT_SOURCE,
149
+ "--agent", "pi",
150
+ "--applies-to-source", "herdr:pi",
151
+ "--clear-state-labels",
152
+ "--clear-token", "summary",
153
+ "--seq", seq,
154
+ ]);
155
+ return;
156
+ }
157
+ const text = label();
158
+ published = true;
159
+ enqueue([
160
+ "pane", "report-metadata", paneId,
161
+ "--source", DEFAULT_SOURCE,
162
+ "--agent", "pi",
163
+ "--applies-to-source", "herdr:pi",
164
+ "--state-label", `idle=${text}`,
165
+ "--state-label", `done=${text}`,
166
+ "--state-label", `working=${text}`,
167
+ "--token", `summary=${text}`,
168
+ "--ttl-ms", String(ttlMs),
169
+ "--seq", seq,
170
+ ]);
171
+ };
172
+
173
+ const syncRefreshTimer = (): void => {
174
+ if (runs.size > 0 && refreshMs > 0 && !refreshTimer) {
175
+ refreshTimer = timers.setInterval(() => refresh(), refreshMs);
176
+ refreshTimer.unref?.();
177
+ } else if ((runs.size === 0 || refreshMs <= 0) && refreshTimer) {
178
+ timers.clearInterval(refreshTimer);
179
+ refreshTimer = undefined;
180
+ }
181
+ };
182
+
183
+ const syncBusy = (): void => {
184
+ if (!enabled || !rootSession || disposed) return;
185
+ if (runs.size > 0) {
186
+ const text = label();
187
+ if (busyRaised && busyLabel === text) return;
188
+ if (busyRaised) options.events.emit("herdr:busy", { active: false });
189
+ busyRaised = true;
190
+ busyLabel = text;
191
+ options.events.emit("herdr:busy", { active: true, label: text });
192
+ return;
193
+ }
194
+ if (busyRaised) {
195
+ busyRaised = false;
196
+ busyLabel = undefined;
197
+ options.events.emit("herdr:busy", { active: false });
198
+ }
199
+ };
200
+
201
+ const syncBlocked = (): void => {
202
+ if (!enabled || !rootSession || disposed) return;
203
+ const nextLabel = [...attentionLabels.values()].at(-1);
204
+ if (nextLabel !== undefined) {
205
+ if (blockedRaised && blockedLabel === nextLabel) return;
206
+ // Herdr's sibling overlay contract is counted. Lower before changing
207
+ // the active label so this bridge continues to own exactly one count.
208
+ if (blockedRaised) options.events.emit("herdr:blocked", { active: false });
209
+ blockedRaised = true;
210
+ blockedLabel = nextLabel;
211
+ options.events.emit("herdr:blocked", { active: true, label: nextLabel });
212
+ return;
213
+ }
214
+ if (blockedRaised) {
215
+ blockedRaised = false;
216
+ blockedLabel = undefined;
217
+ options.events.emit("herdr:blocked", { active: false });
218
+ }
219
+ };
220
+
221
+ const clearAttention = (): void => {
222
+ attentionLabels.clear();
223
+ syncBlocked();
224
+ };
225
+
226
+ const raiseAttention = (runId: string, labelText: string): void => {
227
+ if (!rootSession || attentionLabels.has(runId)) return;
228
+ acknowledgedAttention.delete(runId);
229
+ attentionLabels.set(runId, labelText);
230
+ syncBlocked();
231
+ };
232
+
233
+ const replaceRuns = (nextRuns: Iterable<HerdrStatusRun>): void => {
234
+ const nextAttention = new Map<string, string>();
235
+ const activeIds = new Set<string>();
236
+ runs.clear();
237
+ for (const run of nextRuns) {
238
+ if (!run || typeof run.id !== "string" || !run.id) continue;
239
+ activeIds.add(run.id);
240
+ runs.set(run.id, { ...run });
241
+ if (!run.needsAttention) {
242
+ acknowledgedAttention.delete(run.id);
243
+ } else if (!acknowledgedAttention.has(run.id)) {
244
+ nextAttention.set(run.id, run.attentionLabel || attentionLabels.get(run.id) || "subagent needs attention");
245
+ }
246
+ }
247
+ for (const id of acknowledgedAttention) {
248
+ if (!activeIds.has(id)) acknowledgedAttention.delete(id);
249
+ }
250
+ attentionLabels.clear();
251
+ for (const [id, labelText] of nextAttention) attentionLabels.set(id, labelText);
252
+ syncBusy();
253
+ syncRefreshTimer();
254
+ syncBlocked();
255
+ publish();
256
+ };
257
+
258
+ const refresh = (): void => {
259
+ if (!options.getRuns) {
260
+ publish();
261
+ return;
262
+ }
263
+ try {
264
+ replaceRuns(options.getRuns());
265
+ } catch {
266
+ // Keep the last known active projection and retry on the next refresh.
267
+ publish();
268
+ }
269
+ };
270
+
271
+ const subscribe = (event: string, handler: (data: unknown) => void): void => {
272
+ const unsubscribe = options.events.on(event, handler);
273
+ if (typeof unsubscribe === "function") subscriptions.push(unsubscribe);
274
+ };
275
+
276
+ if (enabled) {
277
+ subscribe(SUBAGENT_ASYNC_STARTED_EVENT, (data) => {
278
+ if (!rootSession) return;
279
+ const run = startedRun(data);
280
+ if (!run) return;
281
+ acknowledgedAttention.delete(run.id);
282
+ runs.set(run.id, run);
283
+ syncBusy();
284
+ syncRefreshTimer();
285
+ publish();
286
+ });
287
+ subscribe(SUBAGENT_ASYNC_COMPLETE_EVENT, (data) => {
288
+ if (!rootSession) return;
289
+ const id = completedRunId(data);
290
+ if (!id || !runs.delete(id)) return;
291
+ acknowledgedAttention.delete(id);
292
+ if (attentionLabels.delete(id)) syncBlocked();
293
+ syncBusy();
294
+ syncRefreshTimer();
295
+ publish();
296
+ });
297
+ subscribe(SUBAGENT_CONTROL_EVENT, (data) => {
298
+ if (!rootSession) return;
299
+ const notice = attentionNotice(data);
300
+ if (!notice || !runs.has(notice.runId)) return;
301
+ raiseAttention(notice.runId, notice.label);
302
+ });
303
+ }
304
+
305
+ return {
306
+ agentStarted() {
307
+ for (const id of attentionLabels.keys()) acknowledgedAttention.add(id);
308
+ clearAttention();
309
+ },
310
+ sessionStarted({ hasUI, runs: restoredRuns }) {
311
+ if (!enabled || disposed || hasUI !== true) return;
312
+ rootSession = true;
313
+ replaceRuns(restoredRuns);
314
+ },
315
+ async flush() {
316
+ while (draining || pendingReport) await drainPromise;
317
+ },
318
+ dispose() {
319
+ if (disposed) return;
320
+ clearAttention();
321
+ acknowledgedAttention.clear();
322
+ runs.clear();
323
+ syncBusy();
324
+ syncRefreshTimer();
325
+ publish();
326
+ for (const unsubscribe of subscriptions) unsubscribe();
327
+ disposed = true;
328
+ },
329
+ };
330
+ }
@@ -53,6 +53,7 @@ interface ResolveIntercomBridgeInput {
53
53
  config: ExtensionConfig["intercomBridge"];
54
54
  context: "fresh" | "fork" | undefined;
55
55
  orchestratorTarget?: string;
56
+ cwd?: string;
56
57
  settingsDir?: string;
57
58
  agentDir?: string;
58
59
  }
@@ -81,12 +82,12 @@ export function resolveIntercomBridgeMode(value: unknown): IntercomBridgeMode {
81
82
 
82
83
  function resolveIntercomBridgeConfig(value: ExtensionConfig["intercomBridge"]): Required<IntercomBridgeConfig> {
83
84
  if (!value || typeof value !== "object" || Array.isArray(value)) {
84
- return { mode: "always", instructionFile: "", resultDelivery: true };
85
+ return { mode: "always", instructionFile: "", resultDelivery: false };
85
86
  }
86
87
  return {
87
88
  mode: resolveIntercomBridgeMode(value.mode),
88
89
  instructionFile: typeof value.instructionFile === "string" ? value.instructionFile : "",
89
- resultDelivery: value.resultDelivery !== false,
90
+ resultDelivery: value.resultDelivery === true,
90
91
  };
91
92
  }
92
93
 
@@ -113,6 +113,8 @@ function compactNestedRun(run: NestedRunSummary | PublicNestedRunSummary, depth
113
113
  state: run.state,
114
114
  ...(run.agent ? { agent: run.agent } : {}),
115
115
  ...(run.agents?.length ? { agents: run.agents.slice(0, 12) } : {}),
116
+ ...(run.model ? { model: run.model } : {}),
117
+ ...(run.thinking ? { thinking: run.thinking } : {}),
116
118
  ...(run.currentStep !== undefined ? { currentStep: run.currentStep } : {}),
117
119
  ...(run.chainStepCount !== undefined ? { chainStepCount: run.chainStepCount } : {}),
118
120
  ...(run.parallelGroups?.length ? { parallelGroups: run.parallelGroups.slice(0, 8) } : {}),
@@ -131,6 +133,8 @@ function compactNestedRun(run: NestedRunSummary | PublicNestedRunSummary, depth
131
133
  ...(run.steps?.length ? { steps: run.steps.slice(0, 12).map((step) => ({
132
134
  agent: step.agent,
133
135
  status: step.status,
136
+ ...(step.model ? { model: step.model } : {}),
137
+ ...(step.thinking ? { thinking: step.thinking } : {}),
134
138
  ...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
135
139
  ...(step.activityState ? { activityState: step.activityState } : {}),
136
140
  ...(step.lastActivityAt !== undefined ? { lastActivityAt: step.lastActivityAt } : {}),
@@ -315,7 +319,7 @@ export async function deliverSubagentResultIntercomEvent(
315
319
  payload: SubagentResultIntercomPayload,
316
320
  timeoutMs = 500,
317
321
  ): Promise<boolean> {
318
- return deliverSubagentIntercomMessageEvent(events, payload.to, payload.message, timeoutMs, payload);
322
+ return deliverSubagentIntercomMessageEvent(events, payload.to, payload.message, timeoutMs, payload as unknown as Record<string, unknown>);
319
323
  }
320
324
 
321
325
  export async function deliverSubagentIntercomMessageEvent(