@leing2021/super-pi 0.22.0 → 0.23.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 (101) hide show
  1. package/README.md +38 -209
  2. package/extensions/ce-core/index.ts +190 -7
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -18
  6. package/rules/common/naming.md +154 -0
  7. package/skills/02-plan/SKILL.md +3 -2
  8. package/skills/03-work/SKILL.md +9 -6
  9. package/skills/04-review/SKILL.md +3 -2
  10. package/skills/06-next/SKILL.md +38 -10
  11. package/skills/06-next/references/recommendation-logic.md +2 -2
  12. package/skills/07-worktree/SKILL.md +9 -8
  13. package/skills/{09-help → 08-help}/SKILL.md +3 -3
  14. package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
  15. package/skills/references/language-detection.md +44 -0
  16. package/extensions/subagent/agent-management.ts +0 -595
  17. package/extensions/subagent/agent-manager-chain-detail.ts +0 -162
  18. package/extensions/subagent/agent-manager-detail.ts +0 -231
  19. package/extensions/subagent/agent-manager-edit.ts +0 -390
  20. package/extensions/subagent/agent-manager-list.ts +0 -278
  21. package/extensions/subagent/agent-manager-parallel.ts +0 -304
  22. package/extensions/subagent/agent-manager.ts +0 -705
  23. package/extensions/subagent/agent-scope.ts +0 -8
  24. package/extensions/subagent/agent-selection.ts +0 -25
  25. package/extensions/subagent/agent-serializer.ts +0 -123
  26. package/extensions/subagent/agent-templates.ts +0 -62
  27. package/extensions/subagent/agents/context-builder.md +0 -37
  28. package/extensions/subagent/agents/delegate.md +0 -9
  29. package/extensions/subagent/agents/oracle.md +0 -73
  30. package/extensions/subagent/agents/planner.md +0 -52
  31. package/extensions/subagent/agents/researcher.md +0 -50
  32. package/extensions/subagent/agents/reviewer.md +0 -38
  33. package/extensions/subagent/agents/scout.md +0 -48
  34. package/extensions/subagent/agents/worker.md +0 -52
  35. package/extensions/subagent/agents.ts +0 -761
  36. package/extensions/subagent/artifacts.ts +0 -100
  37. package/extensions/subagent/async-execution.ts +0 -520
  38. package/extensions/subagent/async-job-tracker.ts +0 -216
  39. package/extensions/subagent/async-status.ts +0 -241
  40. package/extensions/subagent/chain-clarify.ts +0 -1364
  41. package/extensions/subagent/chain-execution.ts +0 -853
  42. package/extensions/subagent/chain-serializer.ts +0 -126
  43. package/extensions/subagent/completion-dedupe.ts +0 -65
  44. package/extensions/subagent/doctor.ts +0 -200
  45. package/extensions/subagent/execution.ts +0 -738
  46. package/extensions/subagent/file-coalescer.ts +0 -42
  47. package/extensions/subagent/fork-context.ts +0 -63
  48. package/extensions/subagent/formatters.ts +0 -122
  49. package/extensions/subagent/frontmatter.ts +0 -31
  50. package/extensions/subagent/index.ts +0 -585
  51. package/extensions/subagent/intercom-bridge.ts +0 -240
  52. package/extensions/subagent/jsonl-writer.ts +0 -83
  53. package/extensions/subagent/model-fallback.ts +0 -108
  54. package/extensions/subagent/notify.ts +0 -110
  55. package/extensions/subagent/parallel-utils.ts +0 -108
  56. package/extensions/subagent/pi-args.ts +0 -138
  57. package/extensions/subagent/pi-spawn.ts +0 -100
  58. package/extensions/subagent/post-exit-stdio-guard.ts +0 -87
  59. package/extensions/subagent/prompt-template-bridge.ts +0 -399
  60. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  61. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  62. package/extensions/subagent/prompts/parallel-research.md +0 -50
  63. package/extensions/subagent/prompts/parallel-review.md +0 -40
  64. package/extensions/subagent/render-helpers.ts +0 -82
  65. package/extensions/subagent/render.ts +0 -836
  66. package/extensions/subagent/result-intercom.ts +0 -237
  67. package/extensions/subagent/result-watcher.ts +0 -171
  68. package/extensions/subagent/run-history.ts +0 -57
  69. package/extensions/subagent/run-status.ts +0 -136
  70. package/extensions/subagent/schemas.ts +0 -164
  71. package/extensions/subagent/session-tokens.ts +0 -50
  72. package/extensions/subagent/settings.ts +0 -367
  73. package/extensions/subagent/single-output.ts +0 -97
  74. package/extensions/subagent/skills.ts +0 -626
  75. package/extensions/subagent/slash-bridge.ts +0 -176
  76. package/extensions/subagent/slash-commands.ts +0 -303
  77. package/extensions/subagent/slash-live-state.ts +0 -294
  78. package/extensions/subagent/subagent-control.ts +0 -150
  79. package/extensions/subagent/subagent-executor.ts +0 -1899
  80. package/extensions/subagent/subagent-prompt-runtime.ts +0 -75
  81. package/extensions/subagent/subagent-runner.ts +0 -1470
  82. package/extensions/subagent/subagents-status.ts +0 -472
  83. package/extensions/subagent/text-editor.ts +0 -272
  84. package/extensions/subagent/top-level-async.ts +0 -15
  85. package/extensions/subagent/types.ts +0 -623
  86. package/extensions/subagent/utils.ts +0 -456
  87. package/extensions/subagent/worktree.ts +0 -579
  88. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  89. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  90. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  91. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  92. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  93. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  94. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  95. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  96. package/extensions/super-pi-extension/index.ts +0 -129
  97. package/extensions/super-pi-extension/model-sync.ts +0 -204
  98. package/skills/08-status/SKILL.md +0 -41
  99. package/skills/08-status/references/artifact-locations.md +0 -10
  100. package/skills/10-rules/SKILL.md +0 -76
  101. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,623 +0,0 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
3
- /**
4
- * Type definitions for the subagent extension
5
- */
6
-
7
- import * as os from "node:os";
8
- import * as path from "node:path";
9
- import type { Message } from "@mariozechner/pi-ai";
10
- import type { FSWatcher } from "node:fs";
11
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
12
-
13
- // ============================================================================
14
- // Basic Types
15
- // ============================================================================
16
-
17
- export interface MaxOutputConfig {
18
- bytes?: number;
19
- lines?: number;
20
- }
21
-
22
- export interface TruncationResult {
23
- text: string;
24
- truncated: boolean;
25
- originalBytes?: number;
26
- originalLines?: number;
27
- artifactPath?: string;
28
- }
29
-
30
- export interface Usage {
31
- input: number;
32
- output: number;
33
- cacheRead: number;
34
- cacheWrite: number;
35
- cost: number;
36
- turns: number;
37
- }
38
-
39
- export interface TokenUsage {
40
- input: number;
41
- output: number;
42
- total: number;
43
- }
44
-
45
- export type ActivityState = "needs_attention";
46
- export type ControlEventType = "needs_attention";
47
- export type ControlNotificationChannel = "event" | "async" | "intercom";
48
-
49
- export interface ControlConfig {
50
- enabled?: boolean;
51
- needsAttentionAfterMs?: number;
52
- notifyOn?: ControlEventType[];
53
- notifyChannels?: ControlNotificationChannel[];
54
- }
55
-
56
- export interface ResolvedControlConfig {
57
- enabled: boolean;
58
- needsAttentionAfterMs: number;
59
- notifyOn: ControlEventType[];
60
- notifyChannels: ControlNotificationChannel[];
61
- }
62
-
63
- export interface ControlEvent {
64
- type: ControlEventType;
65
- from?: ActivityState;
66
- to: ActivityState;
67
- ts: number;
68
- agent: string;
69
- index?: number;
70
- runId: string;
71
- message: string;
72
- }
73
-
74
- export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
75
-
76
- export interface SubagentResultIntercomChild {
77
- agent: string;
78
- status: SubagentResultStatus;
79
- summary: string;
80
- index?: number;
81
- artifactPath?: string;
82
- sessionPath?: string;
83
- intercomTarget?: string;
84
- }
85
-
86
- export interface SubagentResultIntercomPayload {
87
- to: string;
88
- message: string;
89
- requestId?: string;
90
- runId: string;
91
- mode: "single" | "parallel" | "chain";
92
- status: SubagentResultStatus;
93
- summary: string;
94
- source: "foreground" | "async";
95
- children: SubagentResultIntercomChild[];
96
- asyncId?: string;
97
- asyncDir?: string;
98
- chainSteps?: number;
99
- agent?: string;
100
- index?: number;
101
- artifactPath?: string;
102
- sessionPath?: string;
103
- }
104
-
105
- // ============================================================================
106
- // Progress Tracking
107
- // ============================================================================
108
-
109
- export interface AgentProgress {
110
- index: number;
111
- agent: string;
112
- status: "pending" | "running" | "completed" | "failed" | "detached";
113
- activityState?: ActivityState;
114
- task: string;
115
- skills?: string[];
116
- lastActivityAt?: number;
117
- currentTool?: string;
118
- currentToolArgs?: string;
119
- currentToolStartedAt?: number;
120
- recentTools: Array<{ tool: string; args: string; endMs: number }>;
121
- recentOutput: string[];
122
- toolCount: number;
123
- tokens: number;
124
- durationMs: number;
125
- error?: string;
126
- failedTool?: string;
127
- }
128
-
129
- export interface ToolCallSummary {
130
- text: string;
131
- expandedText: string;
132
- }
133
-
134
- export interface ProgressSummary {
135
- toolCount: number;
136
- tokens: number;
137
- durationMs: number;
138
- }
139
-
140
- // ============================================================================
141
- // Results
142
- // ============================================================================
143
-
144
- export interface ModelAttempt {
145
- model: string;
146
- success: boolean;
147
- exitCode?: number | null;
148
- error?: string;
149
- usage?: Usage;
150
- }
151
-
152
- export interface SingleResult {
153
- agent: string;
154
- task: string;
155
- exitCode: number;
156
- detached?: boolean;
157
- detachedReason?: string;
158
- interrupted?: boolean;
159
- messages?: Message[];
160
- usage: Usage;
161
- model?: string;
162
- attemptedModels?: string[];
163
- modelAttempts?: ModelAttempt[];
164
- controlEvents?: ControlEvent[];
165
- error?: string;
166
- sessionFile?: string;
167
- skills?: string[];
168
- skillsWarning?: string;
169
- progress?: AgentProgress;
170
- progressSummary?: ProgressSummary;
171
- toolCalls?: ToolCallSummary[];
172
- artifactPaths?: ArtifactPaths;
173
- truncation?: TruncationResult;
174
- finalOutput?: string;
175
- savedOutputPath?: string;
176
- outputSaveError?: string;
177
- }
178
-
179
- export interface Details {
180
- mode: "single" | "parallel" | "chain" | "management";
181
- context?: "fresh" | "fork";
182
- results: SingleResult[];
183
- controlEvents?: ControlEvent[];
184
- asyncId?: string;
185
- asyncDir?: string;
186
- progress?: AgentProgress[];
187
- progressSummary?: ProgressSummary;
188
- artifacts?: {
189
- dir: string;
190
- files: ArtifactPaths[];
191
- };
192
- truncation?: {
193
- truncated: boolean;
194
- originalBytes?: number;
195
- originalLines?: number;
196
- artifactPath?: string;
197
- };
198
- // Chain metadata for observability
199
- chainAgents?: string[]; // Agent names in order, e.g., ["scout", "planner"]
200
- totalSteps?: number; // Total steps in chain
201
- currentStepIndex?: number; // 0-indexed current step (for running chains)
202
- }
203
-
204
- // ============================================================================
205
- // Artifacts
206
- // ============================================================================
207
-
208
- export interface ArtifactPaths {
209
- inputPath: string;
210
- outputPath: string;
211
- jsonlPath: string;
212
- metadataPath: string;
213
- }
214
-
215
- export interface ArtifactConfig {
216
- enabled: boolean;
217
- includeInput: boolean;
218
- includeOutput: boolean;
219
- includeJsonl: boolean;
220
- includeMetadata: boolean;
221
- cleanupDays: number;
222
- }
223
-
224
- // ============================================================================
225
- // Async Execution
226
- // ============================================================================
227
-
228
- export interface AsyncStatus {
229
- runId: string;
230
- mode: "single" | "chain";
231
- state: "queued" | "running" | "complete" | "failed" | "paused";
232
- activityState?: ActivityState;
233
- lastActivityAt?: number;
234
- currentTool?: string;
235
- currentToolStartedAt?: number;
236
- startedAt: number;
237
- endedAt?: number;
238
- lastUpdate?: number;
239
- pid?: number;
240
- cwd?: string;
241
- currentStep?: number;
242
- steps?: Array<{
243
- agent: string;
244
- status: string;
245
- activityState?: ActivityState;
246
- lastActivityAt?: number;
247
- currentTool?: string;
248
- currentToolStartedAt?: number;
249
- startedAt?: number;
250
- endedAt?: number;
251
- durationMs?: number;
252
- tokens?: TokenUsage;
253
- skills?: string[];
254
- model?: string;
255
- attemptedModels?: string[];
256
- modelAttempts?: ModelAttempt[];
257
- error?: string;
258
- }>;
259
- sessionDir?: string;
260
- outputFile?: string;
261
- totalTokens?: TokenUsage;
262
- sessionFile?: string;
263
- }
264
-
265
- export interface AsyncJobState {
266
- asyncId: string;
267
- asyncDir: string;
268
- status: "queued" | "running" | "complete" | "failed" | "paused";
269
- activityState?: ActivityState;
270
- lastActivityAt?: number;
271
- currentTool?: string;
272
- currentToolStartedAt?: number;
273
- mode?: "single" | "chain";
274
- agents?: string[];
275
- currentStep?: number;
276
- stepsTotal?: number;
277
- startedAt?: number;
278
- updatedAt?: number;
279
- sessionDir?: string;
280
- outputFile?: string;
281
- totalTokens?: TokenUsage;
282
- sessionFile?: string;
283
- controlEventCursor?: number;
284
- }
285
-
286
- export interface SubagentState {
287
- baseCwd: string;
288
- currentSessionId: string | null;
289
- asyncJobs: Map<string, AsyncJobState>;
290
- foregroundControls: Map<string, {
291
- runId: string;
292
- mode: "single" | "parallel" | "chain";
293
- startedAt: number;
294
- updatedAt: number;
295
- currentAgent?: string;
296
- currentIndex?: number;
297
- currentActivityState?: ActivityState;
298
- lastActivityAt?: number;
299
- currentTool?: string;
300
- currentToolStartedAt?: number;
301
- interrupt?: () => boolean;
302
- }>;
303
- lastForegroundControlId: string | null;
304
- cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;
305
- lastUiContext: ExtensionContext | null;
306
- poller: NodeJS.Timeout | null;
307
- completionSeen: Map<string, number>;
308
- watcher: FSWatcher | null;
309
- watcherRestartTimer: ReturnType<typeof setTimeout> | null;
310
- resultFileCoalescer: {
311
- schedule(file: string, delayMs?: number): boolean;
312
- clear(): void;
313
- };
314
- }
315
-
316
- // ============================================================================
317
- // Display
318
- // ============================================================================
319
-
320
- export type DisplayItem =
321
- | { type: "text"; text: string }
322
- | { type: "tool"; name: string; args: Record<string, unknown> };
323
-
324
- // ============================================================================
325
- // Error Handling
326
- // ============================================================================
327
-
328
- export interface ErrorInfo {
329
- hasError: boolean;
330
- exitCode?: number;
331
- errorType?: string;
332
- details?: string;
333
- }
334
-
335
- export interface IntercomEventBus {
336
- on(channel: string, handler: (data: unknown) => void): () => void;
337
- emit(channel: string, data: unknown): void;
338
- }
339
-
340
- export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
341
- export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
342
- export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
343
- export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
344
- export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
345
- export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
346
- export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
347
- export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
348
-
349
- // ============================================================================
350
- // Execution Options
351
- // ============================================================================
352
-
353
- export interface RunSyncOptions {
354
- cwd?: string;
355
- signal?: AbortSignal;
356
- interruptSignal?: AbortSignal;
357
- allowIntercomDetach?: boolean;
358
- intercomEvents?: IntercomEventBus;
359
- onUpdate?: (r: import("@mariozechner/pi-agent-core").AgentToolResult<Details>) => void;
360
- onControlEvent?: (event: ControlEvent) => void;
361
- controlConfig?: ResolvedControlConfig;
362
- intercomSessionName?: string;
363
- maxOutput?: MaxOutputConfig;
364
- artifactsDir?: string;
365
- artifactConfig?: ArtifactConfig;
366
- runId: string;
367
- index?: number;
368
- sessionDir?: string;
369
- sessionFile?: string;
370
- share?: boolean;
371
- outputPath?: string;
372
- maxSubagentDepth?: number;
373
- /** Override the agent's default model (format: "provider/id" or just "id") */
374
- modelOverride?: string;
375
- /** Registry models available for heuristic bare-model resolution */
376
- availableModels?: Array<{ provider: string; id: string; fullId: string }>;
377
- /** Current parent-session provider to prefer for ambiguous bare model ids */
378
- preferredModelProvider?: string;
379
- /** Skills to inject (overrides agent default if provided) */
380
- skills?: string[];
381
- }
382
-
383
- export type IntercomBridgeMode = "off" | "fork-only" | "always";
384
-
385
- export interface IntercomBridgeConfig {
386
- mode?: IntercomBridgeMode;
387
- instructionFile?: string;
388
- }
389
-
390
- export interface TopLevelParallelConfig {
391
- maxTasks?: number;
392
- concurrency?: number;
393
- }
394
-
395
- export interface ExtensionConfig {
396
- asyncByDefault?: boolean;
397
- forceTopLevelAsync?: boolean;
398
- defaultSessionDir?: string;
399
- maxSubagentDepth?: number;
400
- control?: ControlConfig;
401
- parallel?: TopLevelParallelConfig;
402
- worktreeSetupHook?: string;
403
- worktreeSetupHookTimeoutMs?: number;
404
- intercomBridge?: IntercomBridgeConfig;
405
- }
406
-
407
- // ============================================================================
408
- // Constants
409
- // ============================================================================
410
-
411
- export const DEFAULT_MAX_OUTPUT: Required<MaxOutputConfig> = {
412
- bytes: 200 * 1024,
413
- lines: 5000,
414
- };
415
-
416
- export const DEFAULT_ARTIFACT_CONFIG: ArtifactConfig = {
417
- enabled: true,
418
- includeInput: true,
419
- includeOutput: true,
420
- includeJsonl: false,
421
- includeMetadata: true,
422
- cleanupDays: 7,
423
- };
424
-
425
- function sanitizeTempScopeSegment(value: string): string {
426
- const sanitized = value
427
- .trim()
428
- .replace(/[^A-Za-z0-9._-]+/g, "-")
429
- .replace(/^-+|-+$/g, "");
430
- return sanitized || "unknown";
431
- }
432
-
433
- export function resolveTempScopeId(options?: {
434
- env?: NodeJS.ProcessEnv;
435
- getuid?: (() => number) | undefined;
436
- userInfo?: (() => { username?: string | null }) | undefined;
437
- homedir?: (() => string) | undefined;
438
- }): string {
439
- const env = options?.env ?? process.env;
440
- const getuid = options && Object.hasOwn(options, "getuid")
441
- ? options.getuid
442
- : process.getuid?.bind(process);
443
- if (typeof getuid === "function") {
444
- return `uid-${getuid()}`;
445
- }
446
-
447
- for (const key of ["USERNAME", "USER", "LOGNAME"] as const) {
448
- const value = env[key];
449
- if (value) return `user-${sanitizeTempScopeSegment(value)}`;
450
- }
451
-
452
- const userInfo = options && Object.hasOwn(options, "userInfo")
453
- ? options.userInfo
454
- : os.userInfo;
455
- try {
456
- const username = userInfo?.().username;
457
- if (username) return `user-${sanitizeTempScopeSegment(username)}`;
458
- } catch {
459
- // Fall through to home-directory-based scoping.
460
- }
461
-
462
- const homedir = env.USERPROFILE ?? env.HOME;
463
- if (homedir) return `home-${sanitizeTempScopeSegment(homedir)}`;
464
-
465
- const resolveHomedir = options && Object.hasOwn(options, "homedir")
466
- ? options.homedir
467
- : os.homedir;
468
- try {
469
- const fallbackHomedir = resolveHomedir?.();
470
- if (fallbackHomedir) return `home-${sanitizeTempScopeSegment(fallbackHomedir)}`;
471
- } catch {
472
- // Fall through to the last-resort shared scope.
473
- }
474
-
475
- return "shared";
476
- }
477
-
478
- export const MAX_PARALLEL = 8;
479
- export const MAX_CONCURRENCY = 4;
480
- export const TEMP_ROOT_DIR = path.join(os.tmpdir(), `pi-subagents-${resolveTempScopeId()}`);
481
- export const RESULTS_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-results");
482
- export const ASYNC_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-runs");
483
- export const CHAIN_RUNS_DIR = path.join(TEMP_ROOT_DIR, "chain-runs");
484
- export const TEMP_ARTIFACTS_DIR = path.join(TEMP_ROOT_DIR, "artifacts");
485
- export const WIDGET_KEY = "subagent-async";
486
- export const SLASH_RESULT_TYPE = "subagent-slash-result";
487
- export const SLASH_SUBAGENT_REQUEST_EVENT = "subagent:slash:request";
488
- export const SLASH_SUBAGENT_STARTED_EVENT = "subagent:slash:started";
489
- export const SLASH_SUBAGENT_RESPONSE_EVENT = "subagent:slash:response";
490
- export const SLASH_SUBAGENT_UPDATE_EVENT = "subagent:slash:update";
491
- export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
492
- export const POLL_INTERVAL_MS = 250;
493
- export const MAX_WIDGET_JOBS = 4;
494
- export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
495
-
496
- export const DEFAULT_FORK_PREAMBLE =
497
- "You are a delegated subagent running from a fork of the parent session. " +
498
- "Treat the inherited conversation as reference-only context, not a live thread to continue. " +
499
- "Do not continue or answer prior messages as if they are waiting for a reply. " +
500
- "Your sole job is to execute the task below and return a focused result for that task using your tools.";
501
-
502
- function normalizeTopLevelParallelValue(value: unknown): number | undefined {
503
- const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
504
- if (!Number.isInteger(parsed) || parsed < 1) return undefined;
505
- return parsed;
506
- }
507
-
508
- export function resolveTopLevelParallelMaxTasks(value: unknown): number {
509
- return normalizeTopLevelParallelValue(value) ?? MAX_PARALLEL;
510
- }
511
-
512
- export function resolveTopLevelParallelConcurrency(
513
- override: unknown,
514
- configValue: unknown,
515
- ): number {
516
- return normalizeTopLevelParallelValue(override)
517
- ?? normalizeTopLevelParallelValue(configValue)
518
- ?? MAX_CONCURRENCY;
519
- }
520
-
521
- export function getAsyncConfigPath(suffix: string): string {
522
- return path.join(TEMP_ROOT_DIR, `async-cfg-${suffix}.json`);
523
- }
524
-
525
- export function wrapForkTask(task: string, preamble?: string | false): string {
526
- if (preamble === false) return task;
527
- const effectivePreamble = preamble ?? DEFAULT_FORK_PREAMBLE;
528
- const wrappedPrefix = `${effectivePreamble}\n\nTask:\n`;
529
- if (task.startsWith(wrappedPrefix)) return task;
530
- return `${wrappedPrefix}${task}`;
531
- }
532
-
533
- // ============================================================================
534
- // Recursion Depth Guard
535
- // ============================================================================
536
-
537
- export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
538
- const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
539
- if (!Number.isInteger(parsed) || parsed < 0) return undefined;
540
- return parsed;
541
- }
542
-
543
- export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number): number {
544
- return normalizeMaxSubagentDepth(process.env.PI_SUBAGENT_MAX_DEPTH)
545
- ?? normalizeMaxSubagentDepth(configMaxDepth)
546
- ?? DEFAULT_SUBAGENT_MAX_DEPTH;
547
- }
548
-
549
- export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDepth?: number): number {
550
- const normalizedParent = normalizeMaxSubagentDepth(parentMaxDepth) ?? DEFAULT_SUBAGENT_MAX_DEPTH;
551
- const normalizedAgent = normalizeMaxSubagentDepth(agentMaxDepth);
552
- return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
553
- }
554
-
555
- export function checkSubagentDepth(configMaxDepth?: number): { blocked: boolean; depth: number; maxDepth: number } {
556
- const depth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
557
- const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth);
558
- const blocked = Number.isFinite(depth) && depth >= maxDepth;
559
- return { blocked, depth, maxDepth };
560
- }
561
-
562
- export function getSubagentDepthEnv(maxDepth?: number): Record<string, string> {
563
- const parentDepth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
564
- const nextDepth = Number.isFinite(parentDepth) ? parentDepth + 1 : 1;
565
- return {
566
- PI_SUBAGENT_DEPTH: String(nextDepth),
567
- PI_SUBAGENT_MAX_DEPTH: String(normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth()),
568
- };
569
- }
570
-
571
- // ============================================================================
572
- // Utility Functions
573
- // ============================================================================
574
-
575
- export function formatBytes(bytes: number): string {
576
- if (bytes < 1024) return `${bytes}B`;
577
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
578
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
579
- }
580
-
581
- export function truncateOutput(
582
- output: string,
583
- config: Required<MaxOutputConfig>,
584
- artifactPath?: string,
585
- ): TruncationResult {
586
- const lines = output.split("\n");
587
- const bytes = Buffer.byteLength(output, "utf-8");
588
-
589
- if (bytes <= config.bytes && lines.length <= config.lines) {
590
- return { text: output, truncated: false };
591
- }
592
-
593
- let truncatedLines = lines;
594
- if (lines.length > config.lines) {
595
- truncatedLines = lines.slice(0, config.lines);
596
- }
597
-
598
- let result = truncatedLines.join("\n");
599
- if (Buffer.byteLength(result, "utf-8") > config.bytes) {
600
- let low = 0;
601
- let high = result.length;
602
- while (low < high) {
603
- const mid = Math.floor((low + high + 1) / 2);
604
- if (Buffer.byteLength(result.slice(0, mid), "utf-8") <= config.bytes) {
605
- low = mid;
606
- } else {
607
- high = mid - 1;
608
- }
609
- }
610
- result = result.slice(0, low);
611
- }
612
-
613
- const keptLines = result.split("\n").length;
614
- const marker = `[TRUNCATED: showing first ${keptLines} of ${lines.length} lines, ${formatBytes(Buffer.byteLength(result))} of ${formatBytes(bytes)}${artifactPath ? ` - full output at ${artifactPath}` : ""}]\n`;
615
-
616
- return {
617
- text: marker + result,
618
- truncated: true,
619
- originalBytes: bytes,
620
- originalLines: lines.length,
621
- artifactPath,
622
- };
623
- }