@pi-agents/orchid 0.1.0-beta.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 (163) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +21 -0
  3. package/README.md +246 -0
  4. package/agents/AGENTS-MANIFEST.md +42 -0
  5. package/agents/brain.md +42 -0
  6. package/agents/context-builder.md +46 -0
  7. package/agents/delegate.md +12 -0
  8. package/agents/dev-1.md +42 -0
  9. package/agents/oracle.md +73 -0
  10. package/agents/planner.md +55 -0
  11. package/agents/researcher.md +52 -0
  12. package/agents/reviewer.md +79 -0
  13. package/agents/scout.md +50 -0
  14. package/agents/tester.md +45 -0
  15. package/agents/worker.md +55 -0
  16. package/extensions/ralph.ts +1 -0
  17. package/extensions/reviewer-extension.ts +125 -0
  18. package/extensions/task-orchestrator.ts +28 -0
  19. package/package.json +63 -0
  20. package/prompts/gather-context-and-clarify.md +13 -0
  21. package/prompts/parallel-cleanup.md +59 -0
  22. package/prompts/parallel-context-build.md +53 -0
  23. package/prompts/parallel-handoff-plan.md +59 -0
  24. package/prompts/parallel-research.md +50 -0
  25. package/prompts/parallel-review.md +54 -0
  26. package/prompts/review-loop.md +41 -0
  27. package/skills/orchid/SKILL.md +214 -0
  28. package/skills/orchid/orchid-cleanup/SKILL.md +122 -0
  29. package/skills/orchid/orchid-converge/SKILL.md +124 -0
  30. package/skills/orchid/orchid-decompose/SKILL.md +201 -0
  31. package/skills/orchid/orchid-doctor/SKILL.md +162 -0
  32. package/skills/orchid/orchid-investigate/SKILL.md +102 -0
  33. package/skills/orchid/orchid-launch/SKILL.md +147 -0
  34. package/skills/ralph/SKILL.md +73 -0
  35. package/skills/subagents/pi-subagents/SKILL.md +813 -0
  36. package/src/index.ts +7 -0
  37. package/src/orchestrator/abort.ts +534 -0
  38. package/src/orchestrator/agent-bridge-extension.ts +1020 -0
  39. package/src/orchestrator/agent-host.ts +954 -0
  40. package/src/orchestrator/cleanup.ts +776 -0
  41. package/src/orchestrator/config-loader.ts +1412 -0
  42. package/src/orchestrator/config-schema.ts +690 -0
  43. package/src/orchestrator/config.ts +81 -0
  44. package/src/orchestrator/context-window.ts +66 -0
  45. package/src/orchestrator/diagnostic-reports.ts +475 -0
  46. package/src/orchestrator/diagnostics.ts +394 -0
  47. package/src/orchestrator/discovery.ts +1833 -0
  48. package/src/orchestrator/engine-worker.ts +415 -0
  49. package/src/orchestrator/engine.ts +5940 -0
  50. package/src/orchestrator/execution.ts +3104 -0
  51. package/src/orchestrator/extension.ts +5934 -0
  52. package/src/orchestrator/formatting.ts +785 -0
  53. package/src/orchestrator/git.ts +88 -0
  54. package/src/orchestrator/index.ts +28 -0
  55. package/src/orchestrator/lane-runner.ts +1787 -0
  56. package/src/orchestrator/mailbox.ts +780 -0
  57. package/src/orchestrator/merge.ts +3414 -0
  58. package/src/orchestrator/messages.ts +1062 -0
  59. package/src/orchestrator/migrations.ts +278 -0
  60. package/src/orchestrator/naming.ts +117 -0
  61. package/src/orchestrator/path-resolver.ts +275 -0
  62. package/src/orchestrator/persistence.ts +2625 -0
  63. package/src/orchestrator/process-registry.ts +452 -0
  64. package/src/orchestrator/quality-gate.ts +1085 -0
  65. package/src/orchestrator/resume.ts +3488 -0
  66. package/src/orchestrator/sessions.ts +57 -0
  67. package/src/orchestrator/settings-loader.ts +136 -0
  68. package/src/orchestrator/settings-tui.ts +2208 -0
  69. package/src/orchestrator/sidecar-telemetry.ts +267 -0
  70. package/src/orchestrator/supervisor.ts +4548 -0
  71. package/src/orchestrator/task-executor-core.ts +675 -0
  72. package/src/orchestrator/tmux-compat.ts +37 -0
  73. package/src/orchestrator/tool-allowlist-constants.ts +37 -0
  74. package/src/orchestrator/types.ts +4465 -0
  75. package/src/orchestrator/verification.ts +547 -0
  76. package/src/orchestrator/waves.ts +1564 -0
  77. package/src/orchestrator/workspace.ts +707 -0
  78. package/src/orchestrator/worktree.ts +2725 -0
  79. package/src/ralph/index.ts +825 -0
  80. package/src/subagents/agents/agent-management.ts +648 -0
  81. package/src/subagents/agents/agent-scope.ts +6 -0
  82. package/src/subagents/agents/agent-selection.ts +23 -0
  83. package/src/subagents/agents/agent-serializer.ts +86 -0
  84. package/src/subagents/agents/agents.ts +832 -0
  85. package/src/subagents/agents/chain-serializer.ts +137 -0
  86. package/src/subagents/agents/frontmatter.ts +29 -0
  87. package/src/subagents/agents/identity.ts +30 -0
  88. package/src/subagents/agents/skills.ts +632 -0
  89. package/src/subagents/extension/config.ts +16 -0
  90. package/src/subagents/extension/control-notices.ts +92 -0
  91. package/src/subagents/extension/doctor.ts +199 -0
  92. package/src/subagents/extension/fanout-child.ts +170 -0
  93. package/src/subagents/extension/index.ts +573 -0
  94. package/src/subagents/extension/schemas.ts +168 -0
  95. package/src/subagents/intercom/intercom-bridge.ts +379 -0
  96. package/src/subagents/intercom/result-intercom.ts +377 -0
  97. package/src/subagents/runs/background/async-execution.ts +712 -0
  98. package/src/subagents/runs/background/async-job-tracker.ts +310 -0
  99. package/src/subagents/runs/background/async-resume.ts +345 -0
  100. package/src/subagents/runs/background/async-status.ts +325 -0
  101. package/src/subagents/runs/background/completion-dedupe.ts +63 -0
  102. package/src/subagents/runs/background/notify.ts +108 -0
  103. package/src/subagents/runs/background/parallel-groups.ts +45 -0
  104. package/src/subagents/runs/background/result-watcher.ts +307 -0
  105. package/src/subagents/runs/background/run-id-resolver.ts +83 -0
  106. package/src/subagents/runs/background/run-status.ts +269 -0
  107. package/src/subagents/runs/background/stale-run-reconciler.ts +336 -0
  108. package/src/subagents/runs/background/subagent-runner.ts +1808 -0
  109. package/src/subagents/runs/background/top-level-async.ts +13 -0
  110. package/src/subagents/runs/foreground/chain-clarify.ts +1333 -0
  111. package/src/subagents/runs/foreground/chain-execution.ts +938 -0
  112. package/src/subagents/runs/foreground/execution.ts +918 -0
  113. package/src/subagents/runs/foreground/subagent-executor.ts +2527 -0
  114. package/src/subagents/runs/shared/completion-guard.ts +147 -0
  115. package/src/subagents/runs/shared/long-running-guard.ts +175 -0
  116. package/src/subagents/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
  117. package/src/subagents/runs/shared/model-fallback.ts +103 -0
  118. package/src/subagents/runs/shared/nested-events.ts +819 -0
  119. package/src/subagents/runs/shared/nested-path.ts +52 -0
  120. package/src/subagents/runs/shared/nested-render.ts +115 -0
  121. package/src/subagents/runs/shared/parallel-utils.ts +109 -0
  122. package/src/subagents/runs/shared/pi-args.ts +220 -0
  123. package/src/subagents/runs/shared/pi-spawn.ts +115 -0
  124. package/src/subagents/runs/shared/run-history.ts +60 -0
  125. package/src/subagents/runs/shared/single-output.ts +164 -0
  126. package/src/subagents/runs/shared/subagent-control.ts +226 -0
  127. package/src/subagents/runs/shared/subagent-prompt-runtime.ts +170 -0
  128. package/src/subagents/runs/shared/worktree.ts +577 -0
  129. package/src/subagents/shared/artifacts.ts +98 -0
  130. package/src/subagents/shared/atomic-json.ts +16 -0
  131. package/src/subagents/shared/file-coalescer.ts +40 -0
  132. package/src/subagents/shared/fork-context.ts +76 -0
  133. package/src/subagents/shared/formatters.ts +133 -0
  134. package/src/subagents/shared/jsonl-writer.ts +81 -0
  135. package/src/subagents/shared/model-info.ts +78 -0
  136. package/src/subagents/shared/post-exit-stdio-guard.ts +85 -0
  137. package/src/subagents/shared/session-identity.ts +10 -0
  138. package/src/subagents/shared/session-tokens.ts +44 -0
  139. package/src/subagents/shared/settings.ts +397 -0
  140. package/src/subagents/shared/status-format.ts +49 -0
  141. package/src/subagents/shared/types.ts +822 -0
  142. package/src/subagents/shared/utils.ts +450 -0
  143. package/src/subagents/slash/prompt-template-bridge.ts +397 -0
  144. package/src/subagents/slash/slash-bridge.ts +174 -0
  145. package/src/subagents/slash/slash-commands.ts +528 -0
  146. package/src/subagents/slash/slash-live-state.ts +292 -0
  147. package/src/subagents/tui/render-helpers.ts +80 -0
  148. package/src/subagents/tui/render.ts +1358 -0
  149. package/templates/agents/local/supervisor.md +33 -0
  150. package/templates/agents/local/task-merger.md +27 -0
  151. package/templates/agents/local/task-reviewer.md +30 -0
  152. package/templates/agents/local/task-worker.md +34 -0
  153. package/templates/agents/supervisor-routing.md +92 -0
  154. package/templates/agents/supervisor.md +229 -0
  155. package/templates/agents/task-merger.md +214 -0
  156. package/templates/agents/task-reviewer.md +260 -0
  157. package/templates/agents/task-worker-segment.md +44 -0
  158. package/templates/agents/task-worker.md +557 -0
  159. package/templates/tasks/CONTEXT.md +30 -0
  160. package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -0
  161. package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -0
  162. package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -0
  163. package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -0
@@ -0,0 +1,822 @@
1
+ /**
2
+ * Type definitions for the subagent extension
3
+ */
4
+
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import type { Message } from "@earendil-works/pi-ai";
8
+ import type { FSWatcher } from "node:fs";
9
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
10
+
11
+ // ============================================================================
12
+ // Basic Types
13
+ // ============================================================================
14
+
15
+ export interface MaxOutputConfig {
16
+ bytes?: number;
17
+ lines?: number;
18
+ }
19
+
20
+ export type OutputMode = "inline" | "file-only";
21
+
22
+ export interface SavedOutputReference {
23
+ path: string;
24
+ bytes: number;
25
+ lines: number;
26
+ message: string;
27
+ }
28
+
29
+ interface TruncationResult {
30
+ text: string;
31
+ truncated: boolean;
32
+ originalBytes?: number;
33
+ originalLines?: number;
34
+ artifactPath?: string;
35
+ }
36
+
37
+ export interface Usage {
38
+ input: number;
39
+ output: number;
40
+ cacheRead: number;
41
+ cacheWrite: number;
42
+ cost: number;
43
+ turns: number;
44
+ }
45
+
46
+ export interface TokenUsage {
47
+ input: number;
48
+ output: number;
49
+ total: number;
50
+ }
51
+
52
+ export type ActivityState = "active_long_running" | "needs_attention";
53
+ export type ControlEventType = "active_long_running" | "needs_attention";
54
+ export type ControlNotificationChannel = "event" | "async" | "intercom";
55
+
56
+ export interface ControlConfig {
57
+ enabled?: boolean;
58
+ needsAttentionAfterMs?: number;
59
+ activeNoticeAfterMs?: number;
60
+ activeNoticeAfterTurns?: number;
61
+ activeNoticeAfterTokens?: number;
62
+ failedToolAttemptsBeforeAttention?: number;
63
+ notifyOn?: ControlEventType[];
64
+ notifyChannels?: ControlNotificationChannel[];
65
+ }
66
+
67
+ export interface ResolvedControlConfig {
68
+ enabled: boolean;
69
+ needsAttentionAfterMs: number;
70
+ activeNoticeAfterMs: number;
71
+ activeNoticeAfterTurns?: number;
72
+ activeNoticeAfterTokens?: number;
73
+ failedToolAttemptsBeforeAttention: number;
74
+ notifyOn: ControlEventType[];
75
+ notifyChannels: ControlNotificationChannel[];
76
+ }
77
+
78
+ export interface ControlEvent {
79
+ type: ControlEventType;
80
+ from?: ActivityState;
81
+ to: ActivityState;
82
+ ts: number;
83
+ agent: string;
84
+ index?: number;
85
+ runId: string;
86
+ nestedRunId?: string;
87
+ nestingPath?: NestedRunAddress["path"];
88
+ message: string;
89
+ reason?: "idle" | "completion_guard" | "active_long_running" | "tool_failures" | "time_threshold" | "turn_threshold" | "token_threshold";
90
+ turns?: number;
91
+ tokens?: number;
92
+ toolCount?: number;
93
+ currentTool?: string;
94
+ currentToolDurationMs?: number;
95
+ currentPath?: string;
96
+ elapsedMs?: number;
97
+ recentFailureSummary?: string;
98
+ }
99
+
100
+ export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
101
+ export type SubagentRunMode = "single" | "parallel" | "chain";
102
+
103
+ export type PublicNestedStepSummary = Pick<
104
+ NestedStepSummary,
105
+ "agent" | "status" | "sessionFile" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "startedAt" | "endedAt" | "error"
106
+ > & {
107
+ children?: PublicNestedRunSummary[];
108
+ };
109
+
110
+ export type PublicNestedRunSummary = Pick<
111
+ NestedRunSummary,
112
+ "id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "totalTokens" | "startedAt" | "endedAt" | "lastUpdate" | "error"
113
+ > & {
114
+ steps?: PublicNestedStepSummary[];
115
+ children?: PublicNestedRunSummary[];
116
+ };
117
+
118
+ export interface SubagentResultIntercomChild {
119
+ agent: string;
120
+ status: SubagentResultStatus;
121
+ summary: string;
122
+ index?: number;
123
+ artifactPath?: string;
124
+ sessionPath?: string;
125
+ intercomTarget?: string;
126
+ children?: PublicNestedRunSummary[];
127
+ }
128
+
129
+ export interface SubagentResultIntercomPayload {
130
+ to: string;
131
+ message: string;
132
+ requestId?: string;
133
+ runId: string;
134
+ mode: SubagentRunMode;
135
+ status: SubagentResultStatus;
136
+ summary: string;
137
+ source: "foreground" | "async";
138
+ children: SubagentResultIntercomChild[];
139
+ asyncId?: string;
140
+ asyncDir?: string;
141
+ chainSteps?: number;
142
+ agent?: string;
143
+ index?: number;
144
+ artifactPath?: string;
145
+ sessionPath?: string;
146
+ }
147
+
148
+ // ============================================================================
149
+ // Progress Tracking
150
+ // ============================================================================
151
+
152
+ export interface AgentProgress {
153
+ index: number;
154
+ agent: string;
155
+ status: "pending" | "running" | "completed" | "failed" | "detached";
156
+ activityState?: ActivityState;
157
+ task: string;
158
+ skills?: string[];
159
+ lastActivityAt?: number;
160
+ currentTool?: string;
161
+ currentToolArgs?: string;
162
+ currentToolStartedAt?: number;
163
+ currentPath?: string;
164
+ recentTools: Array<{ tool: string; args: string; endMs: number }>;
165
+ recentOutput: string[];
166
+ toolCount: number;
167
+ turnCount?: number;
168
+ tokens: number;
169
+ durationMs: number;
170
+ error?: string;
171
+ failedTool?: string;
172
+ }
173
+
174
+ export interface ToolCallSummary {
175
+ text: string;
176
+ expandedText: string;
177
+ }
178
+
179
+ interface ProgressSummary {
180
+ toolCount: number;
181
+ tokens: number;
182
+ durationMs: number;
183
+ }
184
+
185
+ // ============================================================================
186
+ // Results
187
+ // ============================================================================
188
+
189
+ export interface ModelAttempt {
190
+ model: string;
191
+ success: boolean;
192
+ exitCode?: number | null;
193
+ error?: string;
194
+ usage?: Usage;
195
+ }
196
+
197
+ export interface SingleResult {
198
+ agent: string;
199
+ task: string;
200
+ exitCode: number;
201
+ detached?: boolean;
202
+ detachedReason?: string;
203
+ interrupted?: boolean;
204
+ messages?: Message[];
205
+ usage: Usage;
206
+ model?: string;
207
+ attemptedModels?: string[];
208
+ modelAttempts?: ModelAttempt[];
209
+ controlEvents?: ControlEvent[];
210
+ error?: string;
211
+ sessionFile?: string;
212
+ skills?: string[];
213
+ skillsWarning?: string;
214
+ progress?: AgentProgress;
215
+ progressSummary?: ProgressSummary;
216
+ toolCalls?: ToolCallSummary[];
217
+ artifactPaths?: ArtifactPaths;
218
+ truncation?: TruncationResult;
219
+ finalOutput?: string;
220
+ outputMode?: OutputMode;
221
+ savedOutputPath?: string;
222
+ outputReference?: SavedOutputReference;
223
+ outputSaveError?: string;
224
+ }
225
+
226
+ export interface Details {
227
+ mode: SubagentRunMode | "management";
228
+ runId?: string;
229
+ context?: "fresh" | "fork";
230
+ results: SingleResult[];
231
+ controlEvents?: ControlEvent[];
232
+ asyncId?: string;
233
+ asyncDir?: string;
234
+ progress?: AgentProgress[];
235
+ progressSummary?: ProgressSummary;
236
+ artifacts?: {
237
+ dir: string;
238
+ files: ArtifactPaths[];
239
+ };
240
+ truncation?: {
241
+ truncated: boolean;
242
+ originalBytes?: number;
243
+ originalLines?: number;
244
+ artifactPath?: string;
245
+ };
246
+ // Chain metadata for observability
247
+ chainAgents?: string[]; // Agent names in order, e.g., ["scout", "planner"]
248
+ totalSteps?: number; // Total steps in chain
249
+ currentStepIndex?: number; // 0-indexed current step (for running chains)
250
+ }
251
+
252
+ // ============================================================================
253
+ // Artifacts
254
+ // ============================================================================
255
+
256
+ export interface ArtifactPaths {
257
+ inputPath: string;
258
+ outputPath: string;
259
+ jsonlPath: string;
260
+ metadataPath: string;
261
+ }
262
+
263
+ export interface ArtifactConfig {
264
+ enabled: boolean;
265
+ includeInput: boolean;
266
+ includeOutput: boolean;
267
+ includeJsonl: boolean;
268
+ includeMetadata: boolean;
269
+ cleanupDays: number;
270
+ }
271
+
272
+ // ============================================================================
273
+ // Async Execution
274
+ // ============================================================================
275
+
276
+ export interface AsyncParallelGroupStatus {
277
+ start: number;
278
+ count: number;
279
+ stepIndex: number;
280
+ }
281
+
282
+ export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused";
283
+ export type NestedOwnerState = "live" | "gone" | "unknown";
284
+
285
+ export interface NestedRunAddress {
286
+ id: string;
287
+ parentRunId: string;
288
+ parentStepIndex?: number;
289
+ parentAgent?: string;
290
+ depth: number;
291
+ path: Array<{ runId: string; stepIndex?: number; agent?: string }>;
292
+ }
293
+
294
+ export interface NestedStepSummary {
295
+ agent: string;
296
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
297
+ sessionFile?: string;
298
+ activityState?: ActivityState;
299
+ lastActivityAt?: number;
300
+ currentTool?: string;
301
+ currentToolStartedAt?: number;
302
+ currentPath?: string;
303
+ turnCount?: number;
304
+ toolCount?: number;
305
+ startedAt?: number;
306
+ endedAt?: number;
307
+ error?: string;
308
+ children?: NestedRunSummary[];
309
+ }
310
+
311
+ export interface NestedRunSummary extends NestedRunAddress {
312
+ asyncDir?: string;
313
+ pid?: number;
314
+ sessionId?: string;
315
+ sessionFile?: string;
316
+ intercomTarget?: string;
317
+ ownerIntercomTarget?: string;
318
+ leafIntercomTarget?: string;
319
+ ownerState?: NestedOwnerState;
320
+ controlInbox?: string;
321
+ capabilityToken?: string;
322
+ mode?: SubagentRunMode;
323
+ state: NestedRunState;
324
+ agent?: string;
325
+ agents?: string[];
326
+ currentStep?: number;
327
+ chainStepCount?: number;
328
+ parallelGroups?: AsyncParallelGroupStatus[];
329
+ steps?: NestedStepSummary[];
330
+ children?: NestedRunSummary[];
331
+ activityState?: ActivityState;
332
+ lastActivityAt?: number;
333
+ currentTool?: string;
334
+ currentToolStartedAt?: number;
335
+ currentPath?: string;
336
+ turnCount?: number;
337
+ toolCount?: number;
338
+ totalTokens?: TokenUsage;
339
+ startedAt?: number;
340
+ endedAt?: number;
341
+ lastUpdate?: number;
342
+ error?: string;
343
+ }
344
+
345
+ export interface NestedRouteInfo {
346
+ rootRunId: string;
347
+ eventSink: string;
348
+ controlInbox: string;
349
+ capabilityToken: string;
350
+ }
351
+
352
+ export interface AsyncStartedEvent {
353
+ id?: string;
354
+ asyncDir?: string;
355
+ pid?: number;
356
+ sessionId?: string;
357
+ mode?: SubagentRunMode;
358
+ agent?: string;
359
+ agents?: string[];
360
+ chain?: string[];
361
+ chainStepCount?: number;
362
+ parallelGroups?: AsyncParallelGroupStatus[];
363
+ nestedRoute?: NestedRouteInfo;
364
+ }
365
+
366
+ export interface AsyncStatus {
367
+ runId: string;
368
+ sessionId?: string;
369
+ mode: SubagentRunMode;
370
+ state: "queued" | "running" | "complete" | "failed" | "paused";
371
+ activityState?: ActivityState;
372
+ lastActivityAt?: number;
373
+ currentTool?: string;
374
+ currentToolStartedAt?: number;
375
+ currentPath?: string;
376
+ turnCount?: number;
377
+ toolCount?: number;
378
+ startedAt: number;
379
+ endedAt?: number;
380
+ lastUpdate?: number;
381
+ pid?: number;
382
+ cwd?: string;
383
+ currentStep?: number;
384
+ chainStepCount?: number;
385
+ parallelGroups?: AsyncParallelGroupStatus[];
386
+ steps?: Array<{
387
+ agent: string;
388
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
389
+ children?: NestedRunSummary[];
390
+ sessionFile?: string;
391
+ activityState?: ActivityState;
392
+ lastActivityAt?: number;
393
+ currentTool?: string;
394
+ currentToolArgs?: string;
395
+ currentToolStartedAt?: number;
396
+ currentPath?: string;
397
+ recentTools?: Array<{ tool: string; args: string; endMs: number }>;
398
+ recentOutput?: string[];
399
+ turnCount?: number;
400
+ toolCount?: number;
401
+ startedAt?: number;
402
+ endedAt?: number;
403
+ durationMs?: number;
404
+ exitCode?: number | null;
405
+ tokens?: TokenUsage;
406
+ skills?: string[];
407
+ model?: string;
408
+ thinking?: string;
409
+ attemptedModels?: string[];
410
+ modelAttempts?: ModelAttempt[];
411
+ error?: string;
412
+ }>;
413
+ sessionDir?: string;
414
+ outputFile?: string;
415
+ totalTokens?: TokenUsage;
416
+ sessionFile?: string;
417
+ }
418
+
419
+ export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
420
+ index?: number;
421
+ };
422
+
423
+ export interface AsyncJobState {
424
+ asyncId: string;
425
+ asyncDir: string;
426
+ status: "queued" | "running" | "complete" | "failed" | "paused";
427
+ pid?: number;
428
+ sessionId?: string;
429
+ activityState?: ActivityState;
430
+ lastActivityAt?: number;
431
+ currentTool?: string;
432
+ currentToolStartedAt?: number;
433
+ currentPath?: string;
434
+ turnCount?: number;
435
+ toolCount?: number;
436
+ mode?: SubagentRunMode;
437
+ agents?: string[];
438
+ currentStep?: number;
439
+ chainStepCount?: number;
440
+ parallelGroups?: AsyncParallelGroupStatus[];
441
+ steps?: AsyncJobStep[];
442
+ stepsTotal?: number;
443
+ runningSteps?: number;
444
+ completedSteps?: number;
445
+ hasParallelGroups?: boolean;
446
+ activeParallelGroup?: boolean;
447
+ startedAt?: number;
448
+ updatedAt?: number;
449
+ sessionDir?: string;
450
+ outputFile?: string;
451
+ totalTokens?: TokenUsage;
452
+ sessionFile?: string;
453
+ controlEventCursor?: number;
454
+ nestedRoute?: NestedRouteInfo;
455
+ nestedChildren?: NestedRunSummary[];
456
+ }
457
+
458
+ export interface ForegroundResumeChild {
459
+ agent: string;
460
+ index: number;
461
+ sessionFile?: string;
462
+ status: SubagentResultStatus;
463
+ }
464
+
465
+ export interface ForegroundResumeRun {
466
+ runId: string;
467
+ mode: SubagentRunMode;
468
+ cwd: string;
469
+ updatedAt: number;
470
+ children: ForegroundResumeChild[];
471
+ }
472
+
473
+ export interface SubagentState {
474
+ baseCwd: string;
475
+ currentSessionId: string | null;
476
+ asyncJobs: Map<string, AsyncJobState>;
477
+ foregroundRuns?: Map<string, ForegroundResumeRun>;
478
+ foregroundControls: Map<string, {
479
+ runId: string;
480
+ mode: SubagentRunMode;
481
+ startedAt: number;
482
+ updatedAt: number;
483
+ currentAgent?: string;
484
+ currentIndex?: number;
485
+ currentActivityState?: ActivityState;
486
+ lastActivityAt?: number;
487
+ currentTool?: string;
488
+ currentToolStartedAt?: number;
489
+ currentPath?: string;
490
+ turnCount?: number;
491
+ tokens?: number;
492
+ toolCount?: number;
493
+ nestedRoute?: NestedRouteInfo;
494
+ nestedChildren?: NestedRunSummary[];
495
+ interrupt?: () => boolean;
496
+ }>;
497
+ lastForegroundControlId: string | null;
498
+ pendingForegroundControlNotices?: Map<string, ReturnType<typeof setTimeout>>;
499
+ cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;
500
+ lastUiContext: ExtensionContext | null;
501
+ poller: NodeJS.Timeout | null;
502
+ completionSeen: Map<string, number>;
503
+ watcher: FSWatcher | null;
504
+ watcherRestartTimer: ReturnType<typeof setTimeout> | null;
505
+ resultFileCoalescer: {
506
+ schedule(file: string, delayMs?: number): boolean;
507
+ clear(): void;
508
+ };
509
+ }
510
+
511
+ // ============================================================================
512
+ // Display
513
+ // ============================================================================
514
+
515
+ export type DisplayItem =
516
+ | { type: "text"; text: string }
517
+ | { type: "tool"; name: string; args: Record<string, unknown> };
518
+
519
+ // ============================================================================
520
+ // Error Handling
521
+ // ============================================================================
522
+
523
+ export interface ErrorInfo {
524
+ hasError: boolean;
525
+ exitCode?: number;
526
+ errorType?: string;
527
+ details?: string;
528
+ }
529
+
530
+ export interface IntercomEventBus {
531
+ on(channel: string, handler: (data: unknown) => void): () => void;
532
+ emit(channel: string, data: unknown): void;
533
+ }
534
+
535
+ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
536
+ export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
537
+ export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
538
+ export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
539
+ export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
540
+ export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
541
+ export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
542
+ export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
543
+
544
+ // ============================================================================
545
+ // Execution Options
546
+ // ============================================================================
547
+
548
+ export interface RunSyncOptions {
549
+ cwd?: string;
550
+ signal?: AbortSignal;
551
+ interruptSignal?: AbortSignal;
552
+ allowIntercomDetach?: boolean;
553
+ intercomEvents?: IntercomEventBus;
554
+ onUpdate?: (r: import("@earendil-works/pi-agent-core").AgentToolResult<Details>) => void;
555
+ onControlEvent?: (event: ControlEvent) => void;
556
+ controlConfig?: ResolvedControlConfig;
557
+ intercomSessionName?: string;
558
+ orchestratorIntercomTarget?: string;
559
+ maxOutput?: MaxOutputConfig;
560
+ artifactsDir?: string;
561
+ artifactConfig?: ArtifactConfig;
562
+ runId: string;
563
+ index?: number;
564
+ sessionDir?: string;
565
+ sessionFile?: string;
566
+ share?: boolean;
567
+ outputPath?: string;
568
+ outputMode?: OutputMode;
569
+ maxSubagentDepth?: number;
570
+ nestedRoute?: NestedRouteInfo;
571
+ /** Override the agent's default model (format: "provider/id" or just "id") */
572
+ modelOverride?: string;
573
+ /** Registry models available for heuristic bare-model resolution */
574
+ availableModels?: Array<{ provider: string; id: string; fullId: string }>;
575
+ /** Current parent-session provider to prefer for ambiguous bare model ids */
576
+ preferredModelProvider?: string;
577
+ /** Skills to inject (overrides agent default if provided) */
578
+ skills?: string[];
579
+ }
580
+
581
+ export type IntercomBridgeMode = "off" | "fork-only" | "always";
582
+
583
+ export interface IntercomBridgeConfig {
584
+ mode?: IntercomBridgeMode;
585
+ instructionFile?: string;
586
+ }
587
+
588
+ interface TopLevelParallelConfig {
589
+ maxTasks?: number;
590
+ concurrency?: number;
591
+ }
592
+
593
+ export interface ExtensionConfig {
594
+ asyncByDefault?: boolean;
595
+ forceTopLevelAsync?: boolean;
596
+ defaultSessionDir?: string;
597
+ maxSubagentDepth?: number;
598
+ control?: ControlConfig;
599
+ parallel?: TopLevelParallelConfig;
600
+ worktreeSetupHook?: string;
601
+ worktreeSetupHookTimeoutMs?: number;
602
+ intercomBridge?: IntercomBridgeConfig;
603
+ }
604
+
605
+ // ============================================================================
606
+ // Constants
607
+ // ============================================================================
608
+
609
+ export const DEFAULT_MAX_OUTPUT: Required<MaxOutputConfig> = {
610
+ bytes: 200 * 1024,
611
+ lines: 5000,
612
+ };
613
+
614
+ export const DEFAULT_ARTIFACT_CONFIG: ArtifactConfig = {
615
+ enabled: true,
616
+ includeInput: true,
617
+ includeOutput: true,
618
+ includeJsonl: false,
619
+ includeMetadata: true,
620
+ cleanupDays: 7,
621
+ };
622
+
623
+ function sanitizeTempScopeSegment(value: string): string {
624
+ const sanitized = value
625
+ .trim()
626
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
627
+ .replace(/^-+|-+$/g, "");
628
+ return sanitized || "unknown";
629
+ }
630
+
631
+ export function resolveTempScopeId(options?: {
632
+ env?: NodeJS.ProcessEnv;
633
+ getuid?: (() => number) | undefined;
634
+ userInfo?: (() => { username?: string | null }) | undefined;
635
+ homedir?: (() => string) | undefined;
636
+ }): string {
637
+ const env = options?.env ?? process.env;
638
+ const getuid = options && Object.hasOwn(options, "getuid")
639
+ ? options.getuid
640
+ : process.getuid?.bind(process);
641
+ if (typeof getuid === "function") {
642
+ return `uid-${getuid()}`;
643
+ }
644
+
645
+ for (const key of ["USERNAME", "USER", "LOGNAME"] as const) {
646
+ const value = env[key];
647
+ if (value) return `user-${sanitizeTempScopeSegment(value)}`;
648
+ }
649
+
650
+ const userInfo = options && Object.hasOwn(options, "userInfo")
651
+ ? options.userInfo
652
+ : os.userInfo;
653
+ try {
654
+ const username = userInfo?.().username;
655
+ if (username) return `user-${sanitizeTempScopeSegment(username)}`;
656
+ } catch {
657
+ // Fall through to home-directory-based scoping.
658
+ }
659
+
660
+ const homedir = env.USERPROFILE ?? env.HOME;
661
+ if (homedir) return `home-${sanitizeTempScopeSegment(homedir)}`;
662
+
663
+ const resolveHomedir = options && Object.hasOwn(options, "homedir")
664
+ ? options.homedir
665
+ : os.homedir;
666
+ try {
667
+ const fallbackHomedir = resolveHomedir?.();
668
+ if (fallbackHomedir) return `home-${sanitizeTempScopeSegment(fallbackHomedir)}`;
669
+ } catch {
670
+ // Fall through to the last-resort shared scope.
671
+ }
672
+
673
+ return "shared";
674
+ }
675
+
676
+ const MAX_PARALLEL = 8;
677
+ export const MAX_CONCURRENCY = 4;
678
+ export const TEMP_ROOT_DIR = path.join(os.tmpdir(), `pi-subagents-${resolveTempScopeId()}`);
679
+ export const RESULTS_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-results");
680
+ export const ASYNC_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-runs");
681
+ export const CHAIN_RUNS_DIR = path.join(TEMP_ROOT_DIR, "chain-runs");
682
+ export const TEMP_ARTIFACTS_DIR = path.join(TEMP_ROOT_DIR, "artifacts");
683
+ export const WIDGET_KEY = "subagent-async";
684
+ export const SLASH_RESULT_TYPE = "subagent-slash-result";
685
+ export const SLASH_SUBAGENT_REQUEST_EVENT = "subagent:slash:request";
686
+ export const SLASH_SUBAGENT_STARTED_EVENT = "subagent:slash:started";
687
+ export const SLASH_SUBAGENT_RESPONSE_EVENT = "subagent:slash:response";
688
+ export const SLASH_SUBAGENT_UPDATE_EVENT = "subagent:slash:update";
689
+ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
690
+ export const POLL_INTERVAL_MS = 250;
691
+ export const MAX_WIDGET_JOBS = 4;
692
+ export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
693
+ export const SUBAGENT_ACTIONS = ["list", "get", "create", "update", "delete", "status", "interrupt", "resume", "doctor"] as const;
694
+
695
+ export const DEFAULT_FORK_PREAMBLE =
696
+ "You are a delegated subagent running from a fork of the parent session. " +
697
+ "Treat the inherited conversation as reference-only context, not a live thread to continue. " +
698
+ "Do not continue or answer prior messages as if they are waiting for a reply. " +
699
+ "Your sole job is to execute the task below and return a focused result for that task using your tools.";
700
+
701
+ function normalizeTopLevelParallelValue(value: unknown): number | undefined {
702
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
703
+ if (!Number.isInteger(parsed) || parsed < 1) return undefined;
704
+ return parsed;
705
+ }
706
+
707
+ export function resolveTopLevelParallelMaxTasks(value: unknown): number {
708
+ return normalizeTopLevelParallelValue(value) ?? MAX_PARALLEL;
709
+ }
710
+
711
+ export function resolveTopLevelParallelConcurrency(
712
+ override: unknown,
713
+ configValue: unknown,
714
+ ): number {
715
+ return normalizeTopLevelParallelValue(override)
716
+ ?? normalizeTopLevelParallelValue(configValue)
717
+ ?? MAX_CONCURRENCY;
718
+ }
719
+
720
+ export function getAsyncConfigPath(suffix: string): string {
721
+ return path.join(TEMP_ROOT_DIR, `async-cfg-${suffix}.json`);
722
+ }
723
+
724
+ export function wrapForkTask(task: string, preamble?: string | false): string {
725
+ if (preamble === false) return task;
726
+ const effectivePreamble = preamble ?? DEFAULT_FORK_PREAMBLE;
727
+ const wrappedPrefix = `${effectivePreamble}\n\nTask:\n`;
728
+ if (task.startsWith(wrappedPrefix)) return task;
729
+ return `${wrappedPrefix}${task}`;
730
+ }
731
+
732
+ // ============================================================================
733
+ // Recursion Depth Guard
734
+ // ============================================================================
735
+
736
+ export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
737
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
738
+ if (!Number.isInteger(parsed) || parsed < 0) return undefined;
739
+ return parsed;
740
+ }
741
+
742
+ export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number): number {
743
+ return normalizeMaxSubagentDepth(process.env.PI_SUBAGENT_MAX_DEPTH)
744
+ ?? normalizeMaxSubagentDepth(configMaxDepth)
745
+ ?? DEFAULT_SUBAGENT_MAX_DEPTH;
746
+ }
747
+
748
+ export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDepth?: number): number {
749
+ const normalizedParent = normalizeMaxSubagentDepth(parentMaxDepth) ?? DEFAULT_SUBAGENT_MAX_DEPTH;
750
+ const normalizedAgent = normalizeMaxSubagentDepth(agentMaxDepth);
751
+ return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
752
+ }
753
+
754
+ export function checkSubagentDepth(configMaxDepth?: number): { blocked: boolean; depth: number; maxDepth: number } {
755
+ const depth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
756
+ const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth);
757
+ const blocked = Number.isFinite(depth) && depth >= maxDepth;
758
+ return { blocked, depth, maxDepth };
759
+ }
760
+
761
+ export function getSubagentDepthEnv(maxDepth?: number): Record<string, string> {
762
+ const parentDepth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
763
+ const nextDepth = Number.isFinite(parentDepth) ? parentDepth + 1 : 1;
764
+ return {
765
+ PI_SUBAGENT_DEPTH: String(nextDepth),
766
+ PI_SUBAGENT_MAX_DEPTH: String(normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth()),
767
+ };
768
+ }
769
+
770
+ // ============================================================================
771
+ // Utility Functions
772
+ // ============================================================================
773
+
774
+ function formatBytes(bytes: number): string {
775
+ if (bytes < 1024) return `${bytes}B`;
776
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
777
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
778
+ }
779
+
780
+ export function truncateOutput(
781
+ output: string,
782
+ config: Required<MaxOutputConfig>,
783
+ artifactPath?: string,
784
+ ): TruncationResult {
785
+ const lines = output.split("\n");
786
+ const bytes = Buffer.byteLength(output, "utf-8");
787
+
788
+ if (bytes <= config.bytes && lines.length <= config.lines) {
789
+ return { text: output, truncated: false };
790
+ }
791
+
792
+ let truncatedLines = lines;
793
+ if (lines.length > config.lines) {
794
+ truncatedLines = lines.slice(0, config.lines);
795
+ }
796
+
797
+ let result = truncatedLines.join("\n");
798
+ if (Buffer.byteLength(result, "utf-8") > config.bytes) {
799
+ let low = 0;
800
+ let high = result.length;
801
+ while (low < high) {
802
+ const mid = Math.floor((low + high + 1) / 2);
803
+ if (Buffer.byteLength(result.slice(0, mid), "utf-8") <= config.bytes) {
804
+ low = mid;
805
+ } else {
806
+ high = mid - 1;
807
+ }
808
+ }
809
+ result = result.slice(0, low);
810
+ }
811
+
812
+ const keptLines = result.split("\n").length;
813
+ const marker = `[TRUNCATED: showing first ${keptLines} of ${lines.length} lines, ${formatBytes(Buffer.byteLength(result))} of ${formatBytes(bytes)}${artifactPath ? ` - full output at ${artifactPath}` : ""}]\n`;
814
+
815
+ return {
816
+ text: marker + result,
817
+ truncated: true,
818
+ originalBytes: bytes,
819
+ originalLines: lines.length,
820
+ artifactPath,
821
+ };
822
+ }