pi-crew 0.1.30 → 0.1.32

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 (159) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +80 -27
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/architecture.md +173 -164
  14. package/docs/refactor-tasks-phase3.md +394 -394
  15. package/docs/refactor-tasks-phase4.md +564 -564
  16. package/docs/refactor-tasks-phase5.md +402 -402
  17. package/docs/refactor-tasks-phase6.md +662 -662
  18. package/docs/research-extension-examples.md +297 -0
  19. package/docs/research-extension-system.md +324 -0
  20. package/docs/research-optimization-plan.md +548 -0
  21. package/docs/research-pi-coding-agent.md +357 -0
  22. package/docs/resource-formats.md +4 -3
  23. package/docs/runtime-flow.md +148 -148
  24. package/docs/source-runtime-refactor-map.md +83 -83
  25. package/docs/usage.md +3 -3
  26. package/index.ts +6 -6
  27. package/package.json +1 -1
  28. package/schema.json +53 -1
  29. package/skills/git-master/SKILL.md +24 -19
  30. package/skills/read-only-explorer/SKILL.md +26 -21
  31. package/skills/safe-bash/SKILL.md +21 -16
  32. package/skills/task-packet/SKILL.md +28 -23
  33. package/skills/verify-evidence/SKILL.md +27 -22
  34. package/src/agents/agent-serializer.ts +34 -34
  35. package/src/agents/discover-agents.ts +102 -102
  36. package/src/config/config.ts +14 -1
  37. package/src/config/defaults.ts +3 -2
  38. package/src/extension/cross-extension-rpc.ts +82 -82
  39. package/src/extension/import-index.ts +4 -3
  40. package/src/extension/management.ts +2 -2
  41. package/src/extension/project-init.ts +9 -7
  42. package/src/extension/register.ts +63 -0
  43. package/src/extension/registration/artifact-cleanup.ts +15 -14
  44. package/src/extension/registration/commands.ts +208 -208
  45. package/src/extension/registration/compaction-guard.ts +125 -0
  46. package/src/extension/registration/subagent-tools.ts +27 -8
  47. package/src/extension/registration/team-tool.ts +61 -44
  48. package/src/extension/result-watcher.ts +98 -98
  49. package/src/extension/run-import.ts +4 -4
  50. package/src/extension/run-index.ts +14 -14
  51. package/src/extension/run-maintenance.ts +24 -24
  52. package/src/extension/team-tool/api.ts +3 -0
  53. package/src/extension/team-tool/cancel.ts +31 -31
  54. package/src/extension/team-tool/doctor.ts +179 -178
  55. package/src/extension/team-tool/inspect.ts +41 -41
  56. package/src/extension/team-tool/lifecycle-actions.ts +79 -79
  57. package/src/extension/team-tool/plan.ts +19 -19
  58. package/src/extension/team-tool/status.ts +73 -73
  59. package/src/prompt/prompt-runtime.ts +68 -68
  60. package/src/runtime/agent-control.ts +64 -64
  61. package/src/runtime/agent-memory.ts +72 -72
  62. package/src/runtime/agent-observability.ts +113 -113
  63. package/src/runtime/async-marker.ts +26 -26
  64. package/src/runtime/background-runner.ts +53 -53
  65. package/src/runtime/crew-agent-runtime.ts +58 -58
  66. package/src/runtime/direct-run.ts +35 -35
  67. package/src/runtime/foreground-control.ts +82 -82
  68. package/src/runtime/green-contract.ts +46 -46
  69. package/src/runtime/group-join.ts +88 -88
  70. package/src/runtime/live-agent-control.ts +78 -78
  71. package/src/runtime/live-agent-manager.ts +85 -85
  72. package/src/runtime/live-control-realtime.ts +36 -36
  73. package/src/runtime/live-session-runtime.ts +299 -299
  74. package/src/runtime/manifest-cache.ts +212 -214
  75. package/src/runtime/model-fallback.ts +261 -261
  76. package/src/runtime/parallel-research.ts +44 -44
  77. package/src/runtime/parallel-utils.ts +99 -99
  78. package/src/runtime/pi-json-output.ts +111 -111
  79. package/src/runtime/pi-spawn.ts +96 -96
  80. package/src/runtime/policy-engine.ts +78 -78
  81. package/src/runtime/post-exit-stdio-guard.ts +86 -86
  82. package/src/runtime/process-status.ts +56 -56
  83. package/src/runtime/progress-event-coalescer.ts +43 -43
  84. package/src/runtime/recovery-recipes.ts +74 -74
  85. package/src/runtime/role-permission.ts +39 -39
  86. package/src/runtime/session-usage.ts +79 -79
  87. package/src/runtime/sidechain-output.ts +28 -28
  88. package/src/runtime/subagent-manager.ts +29 -2
  89. package/src/runtime/task-display.ts +38 -38
  90. package/src/runtime/task-output-context.ts +106 -106
  91. package/src/runtime/task-packet.ts +84 -84
  92. package/src/runtime/task-runner/live-executor.ts +98 -98
  93. package/src/runtime/task-runner/progress.ts +111 -111
  94. package/src/runtime/task-runner/prompt-builder.ts +72 -72
  95. package/src/runtime/task-runner/result-utils.ts +14 -14
  96. package/src/runtime/task-runner/state-helpers.ts +22 -22
  97. package/src/runtime/worker-heartbeat.ts +21 -21
  98. package/src/runtime/worker-startup.ts +57 -57
  99. package/src/schema/config-schema.ts +12 -0
  100. package/src/schema/team-tool-schema.ts +100 -100
  101. package/src/state/artifact-store.ts +108 -108
  102. package/src/state/contracts.ts +105 -105
  103. package/src/state/jsonl-writer.ts +77 -77
  104. package/src/state/state-store.ts +8 -9
  105. package/src/state/task-claims.ts +42 -42
  106. package/src/state/types.ts +180 -180
  107. package/src/state/usage.ts +29 -29
  108. package/src/subagents/async-entry.ts +1 -1
  109. package/src/subagents/index.ts +3 -3
  110. package/src/subagents/live/control.ts +1 -1
  111. package/src/subagents/live/manager.ts +1 -1
  112. package/src/subagents/live/realtime.ts +1 -1
  113. package/src/subagents/live/session-runtime.ts +1 -1
  114. package/src/subagents/manager.ts +1 -1
  115. package/src/subagents/spawn.ts +1 -1
  116. package/src/teams/discover-teams.ts +2 -2
  117. package/src/teams/team-serializer.ts +36 -36
  118. package/src/types/diff.d.ts +18 -0
  119. package/src/ui/crew-footer.ts +101 -101
  120. package/src/ui/crew-select-list.ts +111 -111
  121. package/src/ui/crew-widget.ts +285 -285
  122. package/src/ui/dynamic-border.ts +25 -25
  123. package/src/ui/layout-primitives.ts +106 -106
  124. package/src/ui/loaders.ts +158 -158
  125. package/src/ui/mascot.ts +441 -441
  126. package/src/ui/powerbar-publisher.ts +94 -94
  127. package/src/ui/render-diff.ts +119 -119
  128. package/src/ui/run-dashboard.ts +372 -372
  129. package/src/ui/status-colors.ts +54 -54
  130. package/src/ui/syntax-highlight.ts +116 -116
  131. package/src/ui/transcript-viewer.ts +302 -302
  132. package/src/utils/completion-dedupe.ts +63 -63
  133. package/src/utils/file-coalescer.ts +84 -84
  134. package/src/utils/frontmatter.ts +36 -36
  135. package/src/utils/fs-watch.ts +31 -31
  136. package/src/utils/git.ts +262 -262
  137. package/src/utils/ids.ts +12 -12
  138. package/src/utils/names.ts +26 -26
  139. package/src/utils/paths.ts +34 -7
  140. package/src/utils/sleep.ts +32 -32
  141. package/src/utils/timings.ts +31 -31
  142. package/src/utils/visual.ts +159 -159
  143. package/src/workflows/discover-workflows.ts +2 -2
  144. package/src/workflows/validate-workflow.ts +40 -40
  145. package/src/worktree/branch-freshness.ts +45 -45
  146. package/src/worktree/cleanup.ts +71 -69
  147. package/src/worktree/worktree-manager.ts +3 -1
  148. package/teams/default.team.md +12 -12
  149. package/teams/fast-fix.team.md +11 -11
  150. package/teams/implementation.team.md +18 -18
  151. package/teams/parallel-research.team.md +14 -14
  152. package/teams/research.team.md +11 -11
  153. package/teams/review.team.md +12 -12
  154. package/workflows/default.workflow.md +29 -29
  155. package/workflows/fast-fix.workflow.md +22 -22
  156. package/workflows/implementation.workflow.md +38 -38
  157. package/workflows/parallel-research.workflow.md +46 -46
  158. package/workflows/research.workflow.md +22 -22
  159. package/workflows/review.workflow.md +30 -30
@@ -1,99 +1,99 @@
1
- export interface RunnerSubagentStep {
2
- agent: string;
3
- task: string;
4
- cwd?: string;
5
- model?: string;
6
- modelCandidates?: string[];
7
- tools?: string[];
8
- extensions?: string[];
9
- mcpDirectTools?: string[];
10
- systemPrompt?: string | null;
11
- systemPromptMode?: "append" | "replace";
12
- inheritProjectContext: boolean;
13
- inheritSkills: boolean;
14
- skills?: string[];
15
- outputPath?: string;
16
- sessionFile?: string;
17
- maxSubagentDepth?: number;
18
- }
19
-
20
- export interface ParallelStepGroup {
21
- parallel: RunnerSubagentStep[];
22
- concurrency?: number;
23
- failFast?: boolean;
24
- worktree?: boolean;
25
- }
26
-
27
- export type RunnerStep = RunnerSubagentStep | ParallelStepGroup;
28
-
29
- export function isParallelGroup(step: RunnerStep): step is ParallelStepGroup {
30
- return "parallel" in step && Array.isArray(step.parallel);
31
- }
32
-
33
- export function flattenSteps(steps: RunnerStep[]): RunnerSubagentStep[] {
34
- const flat: RunnerSubagentStep[] = [];
35
- for (const step of steps) {
36
- if (isParallelGroup(step)) {
37
- for (const task of step.parallel) flat.push(task);
38
- } else {
39
- flat.push(step);
40
- }
41
- }
42
- return flat;
43
- }
44
-
45
- export async function mapConcurrent<T, R>(items: T[], limit: number, fn: (item: T, i: number) => Promise<R>): Promise<R[]> {
46
- const safeLimit = Math.max(1, Math.floor(limit) || 1);
47
- const results: R[] = new Array(items.length);
48
- let next = 0;
49
-
50
- const worker = async (_workerIndex: number): Promise<void> => {
51
- while (next < items.length) {
52
- const i = next++;
53
- results[i] = await fn(items[i], i);
54
- }
55
- };
56
-
57
- await Promise.all(Array.from({ length: Math.min(safeLimit, items.length) }, (_, workerIndex) => worker(workerIndex)));
58
- return results;
59
- }
60
-
61
- export interface ParallelTaskResult {
62
- agent: string;
63
- taskIndex?: number;
64
- output: string;
65
- exitCode: number | null;
66
- error?: string;
67
- model?: string;
68
- attemptedModels?: string[];
69
- outputTargetPath?: string;
70
- outputTargetExists?: boolean;
71
- }
72
-
73
- export function aggregateParallelOutputs(
74
- results: ParallelTaskResult[],
75
- headerFormat: (index: number, agent: string) => string = (i, agent) => `=== Parallel Task ${i + 1} (${agent}) ===`,
76
- ): string {
77
- return results
78
- .map((r, i) => {
79
- const header = headerFormat(r.taskIndex ?? i, r.agent);
80
- const hasOutput = Boolean(r.output?.trim());
81
- const status =
82
- r.exitCode === -1
83
- ? "SKIPPED"
84
- : r.exitCode !== 0 && r.exitCode !== null
85
- ? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
86
- : r.error
87
- ? `WARNING: ${r.error}`
88
- : !hasOutput && r.outputTargetPath && r.outputTargetExists === false
89
- ? `EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
90
- : !hasOutput && !r.outputTargetPath
91
- ? "EMPTY OUTPUT (no textual response returned)"
92
- : "";
93
- const body = status ? (hasOutput ? `${status}\n${r.output}` : status) : r.output;
94
- return `${header}\n${body}`;
95
- })
96
- .join("\n\n");
97
- }
98
-
99
- export const MAX_PARALLEL_CONCURRENCY = 4;
1
+ export interface RunnerSubagentStep {
2
+ agent: string;
3
+ task: string;
4
+ cwd?: string;
5
+ model?: string;
6
+ modelCandidates?: string[];
7
+ tools?: string[];
8
+ extensions?: string[];
9
+ mcpDirectTools?: string[];
10
+ systemPrompt?: string | null;
11
+ systemPromptMode?: "append" | "replace";
12
+ inheritProjectContext: boolean;
13
+ inheritSkills: boolean;
14
+ skills?: string[];
15
+ outputPath?: string;
16
+ sessionFile?: string;
17
+ maxSubagentDepth?: number;
18
+ }
19
+
20
+ export interface ParallelStepGroup {
21
+ parallel: RunnerSubagentStep[];
22
+ concurrency?: number;
23
+ failFast?: boolean;
24
+ worktree?: boolean;
25
+ }
26
+
27
+ export type RunnerStep = RunnerSubagentStep | ParallelStepGroup;
28
+
29
+ export function isParallelGroup(step: RunnerStep): step is ParallelStepGroup {
30
+ return "parallel" in step && Array.isArray(step.parallel);
31
+ }
32
+
33
+ export function flattenSteps(steps: RunnerStep[]): RunnerSubagentStep[] {
34
+ const flat: RunnerSubagentStep[] = [];
35
+ for (const step of steps) {
36
+ if (isParallelGroup(step)) {
37
+ for (const task of step.parallel) flat.push(task);
38
+ } else {
39
+ flat.push(step);
40
+ }
41
+ }
42
+ return flat;
43
+ }
44
+
45
+ export async function mapConcurrent<T, R>(items: T[], limit: number, fn: (item: T, i: number) => Promise<R>): Promise<R[]> {
46
+ const safeLimit = Math.max(1, Math.floor(limit) || 1);
47
+ const results: R[] = new Array(items.length);
48
+ let next = 0;
49
+
50
+ const worker = async (_workerIndex: number): Promise<void> => {
51
+ while (next < items.length) {
52
+ const i = next++;
53
+ results[i] = await fn(items[i], i);
54
+ }
55
+ };
56
+
57
+ await Promise.all(Array.from({ length: Math.min(safeLimit, items.length) }, (_, workerIndex) => worker(workerIndex)));
58
+ return results;
59
+ }
60
+
61
+ export interface ParallelTaskResult {
62
+ agent: string;
63
+ taskIndex?: number;
64
+ output: string;
65
+ exitCode: number | null;
66
+ error?: string;
67
+ model?: string;
68
+ attemptedModels?: string[];
69
+ outputTargetPath?: string;
70
+ outputTargetExists?: boolean;
71
+ }
72
+
73
+ export function aggregateParallelOutputs(
74
+ results: ParallelTaskResult[],
75
+ headerFormat: (index: number, agent: string) => string = (i, agent) => `=== Parallel Task ${i + 1} (${agent}) ===`,
76
+ ): string {
77
+ return results
78
+ .map((r, i) => {
79
+ const header = headerFormat(r.taskIndex ?? i, r.agent);
80
+ const hasOutput = Boolean(r.output?.trim());
81
+ const status =
82
+ r.exitCode === -1
83
+ ? "SKIPPED"
84
+ : r.exitCode !== 0 && r.exitCode !== null
85
+ ? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
86
+ : r.error
87
+ ? `WARNING: ${r.error}`
88
+ : !hasOutput && r.outputTargetPath && r.outputTargetExists === false
89
+ ? `EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
90
+ : !hasOutput && !r.outputTargetPath
91
+ ? "EMPTY OUTPUT (no textual response returned)"
92
+ : "";
93
+ const body = status ? (hasOutput ? `${status}\n${r.output}` : status) : r.output;
94
+ return `${header}\n${body}`;
95
+ })
96
+ .join("\n\n");
97
+ }
98
+
99
+ export const MAX_PARALLEL_CONCURRENCY = 4;
@@ -1,111 +1,111 @@
1
- export interface ParsedPiUsage {
2
- input?: number;
3
- output?: number;
4
- cacheRead?: number;
5
- cacheWrite?: number;
6
- cost?: number;
7
- turns?: number;
8
- }
9
-
10
- export interface ParsedPiJsonOutput {
11
- jsonEvents: number;
12
- textEvents: string[];
13
- finalText?: string;
14
- usage?: ParsedPiUsage;
15
- }
16
-
17
- function asRecord(value: unknown): Record<string, unknown> | undefined {
18
- return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
19
- }
20
-
21
- function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
22
- for (const key of keys) {
23
- const value = obj[key];
24
- if (typeof value === "number" && Number.isFinite(value)) return value;
25
- }
26
- return undefined;
27
- }
28
-
29
- function mergeUsage(target: ParsedPiUsage, source: ParsedPiUsage): ParsedPiUsage {
30
- return {
31
- input: source.input ?? target.input,
32
- output: source.output ?? target.output,
33
- cacheRead: source.cacheRead ?? target.cacheRead,
34
- cacheWrite: source.cacheWrite ?? target.cacheWrite,
35
- cost: source.cost ?? target.cost,
36
- turns: source.turns ?? target.turns,
37
- };
38
- }
39
-
40
- function extractUsage(value: unknown): ParsedPiUsage | undefined {
41
- const obj = asRecord(value);
42
- if (!obj) return undefined;
43
- const direct: ParsedPiUsage = {
44
- input: numberField(obj, ["input", "inputTokens", "input_tokens"]),
45
- output: numberField(obj, ["output", "outputTokens", "output_tokens"]),
46
- cacheRead: numberField(obj, ["cacheRead", "cache_read", "cacheReadTokens", "cache_read_tokens"]),
47
- cacheWrite: numberField(obj, ["cacheWrite", "cache_write", "cacheWriteTokens", "cache_write_tokens"]),
48
- cost: numberField(obj, ["cost", "costUsd", "cost_usd"]),
49
- turns: numberField(obj, ["turns", "turnCount", "turn_count"]),
50
- };
51
- if (Object.values(direct).some((entry) => entry !== undefined)) return direct;
52
- for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
53
- const nested = extractUsage(obj[key]);
54
- if (nested) return nested;
55
- }
56
- return undefined;
57
- }
58
-
59
- function textFromContent(content: unknown): string[] {
60
- if (typeof content === "string") return [content];
61
- if (!Array.isArray(content)) return [];
62
- const text: string[] = [];
63
- for (const part of content) {
64
- const obj = asRecord(part);
65
- if (!obj) continue;
66
- if (obj.type === "text" && typeof obj.text === "string") text.push(obj.text);
67
- else if (typeof obj.content === "string") text.push(obj.content);
68
- }
69
- return text;
70
- }
71
-
72
- function extractText(value: unknown): string[] {
73
- const obj = asRecord(value);
74
- if (!obj) return [];
75
- const message = asRecord(obj.message);
76
- if (message?.role !== undefined && message.role !== "assistant") return [];
77
- const text: string[] = [];
78
- if (typeof obj.text === "string") text.push(obj.text);
79
- if (typeof obj.output === "string") text.push(obj.output);
80
- if (typeof obj.finalOutput === "string") text.push(obj.finalOutput);
81
- if (typeof obj.final_output === "string") text.push(obj.final_output);
82
- if (!message) text.push(...textFromContent(obj.content));
83
- if (message) text.push(...textFromContent(message.content));
84
- return text.filter((entry) => entry.trim().length > 0);
85
- }
86
-
87
- export function parsePiJsonOutput(stdout: string): ParsedPiJsonOutput {
88
- let jsonEvents = 0;
89
- const textEvents: string[] = [];
90
- let usage: ParsedPiUsage | undefined;
91
- for (const line of stdout.split("\n")) {
92
- const trimmed = line.trim();
93
- if (!trimmed) continue;
94
- let event: unknown;
95
- try {
96
- event = JSON.parse(trimmed) as unknown;
97
- } catch {
98
- continue;
99
- }
100
- jsonEvents++;
101
- textEvents.push(...extractText(event));
102
- const eventUsage = extractUsage(event);
103
- if (eventUsage) usage = mergeUsage(usage ?? {}, eventUsage);
104
- }
105
- return {
106
- jsonEvents,
107
- textEvents,
108
- finalText: textEvents.length > 0 ? textEvents[textEvents.length - 1] : undefined,
109
- usage,
110
- };
111
- }
1
+ export interface ParsedPiUsage {
2
+ input?: number;
3
+ output?: number;
4
+ cacheRead?: number;
5
+ cacheWrite?: number;
6
+ cost?: number;
7
+ turns?: number;
8
+ }
9
+
10
+ export interface ParsedPiJsonOutput {
11
+ jsonEvents: number;
12
+ textEvents: string[];
13
+ finalText?: string;
14
+ usage?: ParsedPiUsage;
15
+ }
16
+
17
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
18
+ return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
19
+ }
20
+
21
+ function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
22
+ for (const key of keys) {
23
+ const value = obj[key];
24
+ if (typeof value === "number" && Number.isFinite(value)) return value;
25
+ }
26
+ return undefined;
27
+ }
28
+
29
+ function mergeUsage(target: ParsedPiUsage, source: ParsedPiUsage): ParsedPiUsage {
30
+ return {
31
+ input: source.input ?? target.input,
32
+ output: source.output ?? target.output,
33
+ cacheRead: source.cacheRead ?? target.cacheRead,
34
+ cacheWrite: source.cacheWrite ?? target.cacheWrite,
35
+ cost: source.cost ?? target.cost,
36
+ turns: source.turns ?? target.turns,
37
+ };
38
+ }
39
+
40
+ function extractUsage(value: unknown): ParsedPiUsage | undefined {
41
+ const obj = asRecord(value);
42
+ if (!obj) return undefined;
43
+ const direct: ParsedPiUsage = {
44
+ input: numberField(obj, ["input", "inputTokens", "input_tokens"]),
45
+ output: numberField(obj, ["output", "outputTokens", "output_tokens"]),
46
+ cacheRead: numberField(obj, ["cacheRead", "cache_read", "cacheReadTokens", "cache_read_tokens"]),
47
+ cacheWrite: numberField(obj, ["cacheWrite", "cache_write", "cacheWriteTokens", "cache_write_tokens"]),
48
+ cost: numberField(obj, ["cost", "costUsd", "cost_usd"]),
49
+ turns: numberField(obj, ["turns", "turnCount", "turn_count"]),
50
+ };
51
+ if (Object.values(direct).some((entry) => entry !== undefined)) return direct;
52
+ for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
53
+ const nested = extractUsage(obj[key]);
54
+ if (nested) return nested;
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ function textFromContent(content: unknown): string[] {
60
+ if (typeof content === "string") return [content];
61
+ if (!Array.isArray(content)) return [];
62
+ const text: string[] = [];
63
+ for (const part of content) {
64
+ const obj = asRecord(part);
65
+ if (!obj) continue;
66
+ if (obj.type === "text" && typeof obj.text === "string") text.push(obj.text);
67
+ else if (typeof obj.content === "string") text.push(obj.content);
68
+ }
69
+ return text;
70
+ }
71
+
72
+ function extractText(value: unknown): string[] {
73
+ const obj = asRecord(value);
74
+ if (!obj) return [];
75
+ const message = asRecord(obj.message);
76
+ if (message?.role !== undefined && message.role !== "assistant") return [];
77
+ const text: string[] = [];
78
+ if (typeof obj.text === "string") text.push(obj.text);
79
+ if (typeof obj.output === "string") text.push(obj.output);
80
+ if (typeof obj.finalOutput === "string") text.push(obj.finalOutput);
81
+ if (typeof obj.final_output === "string") text.push(obj.final_output);
82
+ if (!message) text.push(...textFromContent(obj.content));
83
+ if (message) text.push(...textFromContent(message.content));
84
+ return text.filter((entry) => entry.trim().length > 0);
85
+ }
86
+
87
+ export function parsePiJsonOutput(stdout: string): ParsedPiJsonOutput {
88
+ let jsonEvents = 0;
89
+ const textEvents: string[] = [];
90
+ let usage: ParsedPiUsage | undefined;
91
+ for (const line of stdout.split("\n")) {
92
+ const trimmed = line.trim();
93
+ if (!trimmed) continue;
94
+ let event: unknown;
95
+ try {
96
+ event = JSON.parse(trimmed) as unknown;
97
+ } catch {
98
+ continue;
99
+ }
100
+ jsonEvents++;
101
+ textEvents.push(...extractText(event));
102
+ const eventUsage = extractUsage(event);
103
+ if (eventUsage) usage = mergeUsage(usage ?? {}, eventUsage);
104
+ }
105
+ return {
106
+ jsonEvents,
107
+ textEvents,
108
+ finalText: textEvents.length > 0 ? textEvents[textEvents.length - 1] : undefined,
109
+ usage,
110
+ };
111
+ }
@@ -1,96 +1,96 @@
1
- import * as fs from "node:fs";
2
- import { fileURLToPath } from "node:url";
3
- import * as path from "node:path";
4
-
5
- export interface PiSpawnCommand {
6
- command: string;
7
- args: string[];
8
- }
9
-
10
- function isRunnableNodeScript(filePath: string): boolean {
11
- return fs.existsSync(filePath) && /\.(?:mjs|cjs|js)$/i.test(filePath);
12
- }
13
-
14
- function resolvePiPackageRoot(): string | undefined {
15
- try {
16
- const entry = process.argv[1];
17
- if (!entry) return undefined;
18
- let dir = path.dirname(fs.realpathSync(entry));
19
- while (dir !== path.dirname(dir)) {
20
- try {
21
- const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8")) as { name?: string };
22
- if (pkg.name === "@mariozechner/pi-coding-agent") return dir;
23
- } catch {
24
- // Continue walking upward.
25
- }
26
- dir = path.dirname(dir);
27
- }
28
- } catch {
29
- return undefined;
30
- }
31
- return undefined;
32
- }
33
-
34
- function packageBinScript(packageJsonPath: string): string | undefined {
35
- try {
36
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { bin?: string | Record<string, string> };
37
- const binPath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.pi ?? Object.values(pkg.bin ?? {})[0];
38
- if (!binPath) return undefined;
39
- const candidate = path.resolve(path.dirname(packageJsonPath), binPath);
40
- return isRunnableNodeScript(candidate) ? candidate : undefined;
41
- } catch {
42
- return undefined;
43
- }
44
- }
45
-
46
- function findPiPackageJsonFrom(startDir: string): string | undefined {
47
- let dir = startDir;
48
- while (dir !== path.dirname(dir)) {
49
- const direct = path.join(dir, "package.json");
50
- try {
51
- const pkg = JSON.parse(fs.readFileSync(direct, "utf-8")) as { name?: string };
52
- if (pkg.name === "@mariozechner/pi-coding-agent") return direct;
53
- } catch {
54
- // Continue searching upward and in node_modules.
55
- }
56
- const dependency = path.join(dir, "node_modules", "@mariozechner", "pi-coding-agent", "package.json");
57
- if (fs.existsSync(dependency)) return dependency;
58
- dir = path.dirname(dir);
59
- }
60
- return undefined;
61
- }
62
-
63
- function resolvePiCliScript(): string | undefined {
64
- const explicit = process.env.PI_TEAMS_PI_BIN?.trim();
65
- if (explicit && isRunnableNodeScript(explicit)) return explicit;
66
-
67
- const argv1 = process.argv[1];
68
- if (argv1) {
69
- const argvPath = path.isAbsolute(argv1) ? argv1 : path.resolve(argv1);
70
- if (isRunnableNodeScript(argvPath)) return argvPath;
71
- }
72
-
73
- const roots = [
74
- resolvePiPackageRoot(),
75
- process.env.APPDATA ? path.join(process.env.APPDATA, "npm", "node_modules", "@mariozechner", "pi-coding-agent") : undefined,
76
- path.dirname(fileURLToPath(import.meta.url)),
77
- process.cwd(),
78
- ].filter((entry): entry is string => Boolean(entry));
79
-
80
- for (const root of roots) {
81
- const packageJsonPath = root.endsWith("package.json") ? root : findPiPackageJsonFrom(root) ?? path.join(root, "package.json");
82
- const script = packageBinScript(packageJsonPath);
83
- if (script) return script;
84
- }
85
- return undefined;
86
- }
87
-
88
- export function getPiSpawnCommand(args: string[]): PiSpawnCommand {
89
- const explicit = process.env.PI_TEAMS_PI_BIN?.trim();
90
- if (explicit && fs.existsSync(explicit) && !isRunnableNodeScript(explicit)) return { command: explicit, args };
91
- if (process.platform === "win32") {
92
- const script = resolvePiCliScript();
93
- if (script) return { command: process.execPath, args: [script, ...args] };
94
- }
95
- return { command: "pi", args };
96
- }
1
+ import * as fs from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import * as path from "node:path";
4
+
5
+ export interface PiSpawnCommand {
6
+ command: string;
7
+ args: string[];
8
+ }
9
+
10
+ function isRunnableNodeScript(filePath: string): boolean {
11
+ return fs.existsSync(filePath) && /\.(?:mjs|cjs|js)$/i.test(filePath);
12
+ }
13
+
14
+ function resolvePiPackageRoot(): string | undefined {
15
+ try {
16
+ const entry = process.argv[1];
17
+ if (!entry) return undefined;
18
+ let dir = path.dirname(fs.realpathSync(entry));
19
+ while (dir !== path.dirname(dir)) {
20
+ try {
21
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8")) as { name?: string };
22
+ if (pkg.name === "@mariozechner/pi-coding-agent") return dir;
23
+ } catch {
24
+ // Continue walking upward.
25
+ }
26
+ dir = path.dirname(dir);
27
+ }
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ return undefined;
32
+ }
33
+
34
+ function packageBinScript(packageJsonPath: string): string | undefined {
35
+ try {
36
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { bin?: string | Record<string, string> };
37
+ const binPath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.pi ?? Object.values(pkg.bin ?? {})[0];
38
+ if (!binPath) return undefined;
39
+ const candidate = path.resolve(path.dirname(packageJsonPath), binPath);
40
+ return isRunnableNodeScript(candidate) ? candidate : undefined;
41
+ } catch {
42
+ return undefined;
43
+ }
44
+ }
45
+
46
+ function findPiPackageJsonFrom(startDir: string): string | undefined {
47
+ let dir = startDir;
48
+ while (dir !== path.dirname(dir)) {
49
+ const direct = path.join(dir, "package.json");
50
+ try {
51
+ const pkg = JSON.parse(fs.readFileSync(direct, "utf-8")) as { name?: string };
52
+ if (pkg.name === "@mariozechner/pi-coding-agent") return direct;
53
+ } catch {
54
+ // Continue searching upward and in node_modules.
55
+ }
56
+ const dependency = path.join(dir, "node_modules", "@mariozechner", "pi-coding-agent", "package.json");
57
+ if (fs.existsSync(dependency)) return dependency;
58
+ dir = path.dirname(dir);
59
+ }
60
+ return undefined;
61
+ }
62
+
63
+ function resolvePiCliScript(): string | undefined {
64
+ const explicit = process.env.PI_TEAMS_PI_BIN?.trim();
65
+ if (explicit && isRunnableNodeScript(explicit)) return explicit;
66
+
67
+ const argv1 = process.argv[1];
68
+ if (argv1) {
69
+ const argvPath = path.isAbsolute(argv1) ? argv1 : path.resolve(argv1);
70
+ if (isRunnableNodeScript(argvPath)) return argvPath;
71
+ }
72
+
73
+ const roots = [
74
+ resolvePiPackageRoot(),
75
+ process.env.APPDATA ? path.join(process.env.APPDATA, "npm", "node_modules", "@mariozechner", "pi-coding-agent") : undefined,
76
+ path.dirname(fileURLToPath(import.meta.url)),
77
+ process.cwd(),
78
+ ].filter((entry): entry is string => Boolean(entry));
79
+
80
+ for (const root of roots) {
81
+ const packageJsonPath = root.endsWith("package.json") ? root : findPiPackageJsonFrom(root) ?? path.join(root, "package.json");
82
+ const script = packageBinScript(packageJsonPath);
83
+ if (script) return script;
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ export function getPiSpawnCommand(args: string[]): PiSpawnCommand {
89
+ const explicit = process.env.PI_TEAMS_PI_BIN?.trim();
90
+ if (explicit && fs.existsSync(explicit) && !isRunnableNodeScript(explicit)) return { command: explicit, args };
91
+ if (process.platform === "win32") {
92
+ const script = resolvePiCliScript();
93
+ if (script) return { command: process.execPath, args: [script, ...args] };
94
+ }
95
+ return { command: "pi", args };
96
+ }