@dyyz1993/pi-coding-agent 0.74.24 → 0.74.27

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 (157) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +3 -0
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/session-manager.d.ts +5 -0
  6. package/dist/core/session-manager.d.ts.map +1 -1
  7. package/dist/core/session-manager.js +8 -0
  8. package/dist/core/session-manager.js.map +1 -1
  9. package/dist/extensions/agent-permissions/index.ts +235 -0
  10. package/dist/extensions/ask-tools/index.ts +115 -0
  11. package/dist/extensions/auto-memory/contract.d.ts +51 -0
  12. package/dist/extensions/auto-memory/contract.d.ts.map +1 -0
  13. package/dist/extensions/auto-memory/contract.js +2 -0
  14. package/dist/extensions/auto-memory/contract.js.map +1 -0
  15. package/dist/extensions/auto-memory/contract.ts +56 -0
  16. package/dist/extensions/auto-memory/index.ts +969 -0
  17. package/dist/extensions/auto-memory/prompts.ts +202 -0
  18. package/dist/extensions/auto-memory/skip-rules.ts +297 -0
  19. package/dist/extensions/auto-memory/utils.ts +208 -0
  20. package/dist/extensions/auto-session-title/index.ts +83 -0
  21. package/dist/extensions/bash-ext/contract.d.ts +79 -0
  22. package/dist/extensions/bash-ext/contract.d.ts.map +1 -0
  23. package/dist/extensions/bash-ext/contract.js +2 -0
  24. package/dist/extensions/bash-ext/contract.js.map +1 -0
  25. package/dist/extensions/bash-ext/contract.ts +69 -0
  26. package/dist/extensions/bash-ext/index.ts +858 -0
  27. package/dist/extensions/claude-hooks-compat/config-loader.ts +49 -0
  28. package/dist/extensions/claude-hooks-compat/handler-runner.ts +377 -0
  29. package/dist/extensions/claude-hooks-compat/if-parser.ts +53 -0
  30. package/dist/extensions/claude-hooks-compat/index.ts +178 -0
  31. package/dist/extensions/claude-hooks-compat/matcher.ts +17 -0
  32. package/dist/extensions/claude-hooks-compat/stdin-builder.ts +27 -0
  33. package/dist/extensions/claude-hooks-compat/types.ts +77 -0
  34. package/dist/extensions/compaction-manager/config.ts +47 -0
  35. package/dist/extensions/compaction-manager/context-fold.ts +63 -0
  36. package/dist/extensions/compaction-manager/index.ts +151 -0
  37. package/dist/extensions/compaction-manager/microcompact.ts +49 -0
  38. package/dist/extensions/compaction-manager/reactive.ts +9 -0
  39. package/dist/extensions/compaction-manager/session-memory.ts +48 -0
  40. package/dist/extensions/coordinator/INTEGRATION.md +376 -0
  41. package/dist/extensions/coordinator/handler.test.ts +277 -0
  42. package/dist/extensions/coordinator/handler.ts +189 -0
  43. package/dist/extensions/coordinator/index.ts +261 -0
  44. package/dist/extensions/coordinator/types.d.ts +100 -0
  45. package/dist/extensions/coordinator/types.d.ts.map +1 -0
  46. package/dist/extensions/coordinator/types.js +2 -0
  47. package/dist/extensions/coordinator/types.js.map +1 -0
  48. package/dist/extensions/coordinator/types.ts +72 -0
  49. package/dist/extensions/file-snapshot/index.ts +131 -0
  50. package/dist/extensions/file-time-guard/README.md +133 -0
  51. package/dist/extensions/file-time-guard/config.ts +13 -0
  52. package/dist/extensions/file-time-guard/index.ts +171 -0
  53. package/dist/extensions/hooks-engine/index.ts +117 -0
  54. package/dist/extensions/lsp/lsp/client/file-tracker.ts +70 -0
  55. package/dist/extensions/lsp/lsp/client/registry.ts +305 -0
  56. package/dist/extensions/lsp/lsp/client/runtime.ts +832 -0
  57. package/dist/extensions/lsp/lsp/config/resolver.ts +573 -0
  58. package/dist/extensions/lsp/lsp/contract.d.ts +101 -0
  59. package/dist/extensions/lsp/lsp/contract.d.ts.map +1 -0
  60. package/dist/extensions/lsp/lsp/contract.js +2 -0
  61. package/dist/extensions/lsp/lsp/contract.js.map +1 -0
  62. package/dist/extensions/lsp/lsp/contract.ts +103 -0
  63. package/dist/extensions/lsp/lsp/hooks/agent-end.ts +169 -0
  64. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts +10 -0
  65. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts.map +1 -0
  66. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js +30 -0
  67. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js.map +1 -0
  68. package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.ts +41 -0
  69. package/dist/extensions/lsp/lsp/hooks/writethrough.ts +342 -0
  70. package/dist/extensions/lsp/lsp/index.ts +310 -0
  71. package/dist/extensions/lsp/lsp/lsp.test.ts +684 -0
  72. package/dist/extensions/lsp/lsp/monitoring/server-metrics.ts +176 -0
  73. package/dist/extensions/lsp/lsp/tools/lsp-tool.ts +402 -0
  74. package/dist/extensions/lsp/lsp/utils/dependency-resolver.ts +147 -0
  75. package/dist/extensions/lsp/lsp/utils/diagnostics-wait.ts +41 -0
  76. package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts +20 -0
  77. package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts.map +1 -0
  78. package/dist/extensions/lsp/lsp/utils/lsp-helpers.js +64 -0
  79. package/dist/extensions/lsp/lsp/utils/lsp-helpers.js.map +1 -0
  80. package/dist/extensions/lsp/lsp/utils/lsp-helpers.ts +76 -0
  81. package/dist/extensions/message-bridge/GUIDE.md +210 -0
  82. package/dist/extensions/message-bridge/index.ts +222 -0
  83. package/dist/extensions/output-guard/index.ts +446 -0
  84. package/dist/extensions/preview/index.ts +278 -0
  85. package/dist/extensions/rules-engine/MATCH_HISTORY_RECONCILIATION.md +111 -0
  86. package/dist/extensions/rules-engine/RULES-ENGINE-GUIDE.md +470 -0
  87. package/dist/extensions/rules-engine/cache.js +232 -0
  88. package/dist/extensions/rules-engine/cache.ts +38 -0
  89. package/dist/extensions/rules-engine/config.js +63 -0
  90. package/dist/extensions/rules-engine/config.ts +70 -0
  91. package/dist/extensions/rules-engine/index.js +1530 -0
  92. package/dist/extensions/rules-engine/index.ts +552 -0
  93. package/dist/extensions/rules-engine/injector.js +68 -0
  94. package/dist/extensions/rules-engine/injector.ts +74 -0
  95. package/dist/extensions/rules-engine/loader.js +179 -0
  96. package/dist/extensions/rules-engine/loader.ts +205 -0
  97. package/dist/extensions/rules-engine/matcher.js +534 -0
  98. package/dist/extensions/rules-engine/matcher.ts +52 -0
  99. package/dist/extensions/rules-engine/types.d.ts +156 -0
  100. package/dist/extensions/rules-engine/types.d.ts.map +1 -0
  101. package/dist/extensions/rules-engine/types.js +2 -0
  102. package/dist/extensions/rules-engine/types.js.map +1 -0
  103. package/dist/extensions/rules-engine/types.ts +169 -0
  104. package/dist/extensions/session-supervisor/checker.ts +116 -0
  105. package/dist/extensions/session-supervisor/config.ts +45 -0
  106. package/dist/extensions/session-supervisor/index.ts +726 -0
  107. package/dist/extensions/session-supervisor/prompts.ts +132 -0
  108. package/dist/extensions/session-supervisor/scheduler.ts +69 -0
  109. package/dist/extensions/session-supervisor/types.ts +215 -0
  110. package/dist/extensions/subagent/README.md +172 -0
  111. package/dist/extensions/subagent/agents/explorer.md +25 -0
  112. package/dist/extensions/subagent/agents/guide.md +27 -0
  113. package/dist/extensions/subagent/agents/planner.md +37 -0
  114. package/dist/extensions/subagent/agents/reviewer.md +35 -0
  115. package/dist/extensions/subagent/agents/scout.md +50 -0
  116. package/dist/extensions/subagent/agents/verification.md +35 -0
  117. package/dist/extensions/subagent/agents/worker.md +24 -0
  118. package/dist/extensions/subagent/agents.ts +25 -0
  119. package/dist/extensions/subagent/index.ts +987 -0
  120. package/dist/extensions/subagent/prompts/implement-and-review.md +10 -0
  121. package/dist/extensions/subagent/prompts/implement.md +10 -0
  122. package/dist/extensions/subagent/prompts/scout-and-plan.md +9 -0
  123. package/dist/extensions/subagent-ext/contract.d.ts +2 -0
  124. package/dist/extensions/subagent-ext/contract.d.ts.map +1 -0
  125. package/dist/extensions/subagent-ext/contract.js +2 -0
  126. package/dist/extensions/subagent-ext/contract.js.map +1 -0
  127. package/dist/extensions/subagent-ext/contract.ts +1 -0
  128. package/dist/extensions/subagent-ext/index.ts +347 -0
  129. package/dist/extensions/subagent-shared/contract.d.ts +25 -0
  130. package/dist/extensions/subagent-shared/contract.d.ts.map +1 -0
  131. package/dist/extensions/subagent-shared/contract.js +2 -0
  132. package/dist/extensions/subagent-shared/contract.js.map +1 -0
  133. package/dist/extensions/subagent-shared/contract.ts +28 -0
  134. package/dist/extensions/subagent-shared/index.ts +4 -0
  135. package/dist/extensions/subagent-shared/render.ts +166 -0
  136. package/dist/extensions/subagent-shared/types.ts +35 -0
  137. package/dist/extensions/subagent-shared/utils.ts +112 -0
  138. package/dist/extensions/subagent-v2/contract.d.ts +2 -0
  139. package/dist/extensions/subagent-v2/contract.d.ts.map +1 -0
  140. package/dist/extensions/subagent-v2/contract.js +2 -0
  141. package/dist/extensions/subagent-v2/contract.js.map +1 -0
  142. package/dist/extensions/subagent-v2/contract.ts +1 -0
  143. package/dist/extensions/subagent-v2/index.ts +599 -0
  144. package/dist/extensions/todo-ext/contract.d.ts +27 -0
  145. package/dist/extensions/todo-ext/contract.d.ts.map +1 -0
  146. package/dist/extensions/todo-ext/contract.js +2 -0
  147. package/dist/extensions/todo-ext/contract.js.map +1 -0
  148. package/dist/extensions/todo-ext/contract.ts +30 -0
  149. package/dist/extensions/todo-ext/index.ts +419 -0
  150. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  151. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  152. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  153. package/examples/extensions/sandbox/package-lock.json +2 -2
  154. package/examples/extensions/sandbox/package.json +1 -1
  155. package/examples/extensions/with-deps/package-lock.json +2 -2
  156. package/examples/extensions/with-deps/package.json +1 -1
  157. package/package.json +6 -5
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Worker implements, reviewer reviews, worker applies feedback
3
+ ---
4
+ Use the subagent tool with the chain parameter to execute this workflow:
5
+
6
+ 1. First, use the "worker" agent to implement: $@
7
+ 2. Then, use the "reviewer" agent to review the implementation from the previous step (use {previous} placeholder)
8
+ 3. Finally, use the "worker" agent to apply the feedback from the review (use {previous} placeholder)
9
+
10
+ Execute this as a chain, passing output between steps via {previous}.
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Full implementation workflow - scout gathers context, planner creates plan, worker implements
3
+ ---
4
+ Use the subagent tool with the chain parameter to execute this workflow:
5
+
6
+ 1. First, use the "scout" agent to find all code relevant to: $@
7
+ 2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder)
8
+ 3. Finally, use the "worker" agent to implement the plan from the previous step (use {previous} placeholder)
9
+
10
+ Execute this as a chain, passing output between steps via {previous}.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Scout gathers context, planner creates implementation plan (no implementation)
3
+ ---
4
+ Use the subagent tool with the chain parameter to execute this workflow:
5
+
6
+ 1. First, use the "scout" agent to find all code relevant to: $@
7
+ 2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder)
8
+
9
+ Execute this as a chain, passing output between steps via {previous}. Do NOT implement - just return the plan.
@@ -0,0 +1,2 @@
1
+ export { SUBAGENT_CHANNEL_NAME as SUBAGENT_EXT_CHANNEL_NAME, type SubagentChannelContract as SubagentExtChannelContract, type SubagentEventPayload as SubagentEventPayload, type SubagentStartPayload } from "../subagent-shared/contract.js";
2
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,IAAI,yBAAyB,EAAE,KAAK,uBAAuB,IAAI,0BAA0B,EAAE,KAAK,oBAAoB,IAAI,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gCAAgC,CAAC","sourcesContent":["export { SUBAGENT_CHANNEL_NAME as SUBAGENT_EXT_CHANNEL_NAME, type SubagentChannelContract as SubagentExtChannelContract, type SubagentEventPayload as SubagentEventPayload, type SubagentStartPayload } from \"../subagent-shared/contract.js\";\n"]}
@@ -0,0 +1,2 @@
1
+ export { SUBAGENT_CHANNEL_NAME as SUBAGENT_EXT_CHANNEL_NAME } from "../subagent-shared/contract.js";
2
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,IAAI,yBAAyB,EAA4I,MAAM,gCAAgC,CAAC","sourcesContent":["export { SUBAGENT_CHANNEL_NAME as SUBAGENT_EXT_CHANNEL_NAME, type SubagentChannelContract as SubagentExtChannelContract, type SubagentEventPayload as SubagentEventPayload, type SubagentStartPayload } from \"../subagent-shared/contract.js\";\n"]}
@@ -0,0 +1 @@
1
+ export { SUBAGENT_CHANNEL_NAME as SUBAGENT_EXT_CHANNEL_NAME, type SubagentChannelContract as SubagentExtChannelContract, type SubagentEventPayload as SubagentEventPayload, type SubagentStartPayload } from "../subagent-shared/contract.js";
@@ -0,0 +1,347 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import type { AgentToolResult, AgentToolUpdateCallback } from "@dyyz1993/pi-agent-core";
7
+ import { Type } from "typebox";
8
+ import type { ExtensionAPI, ExtensionContext } from "@dyyz1993/pi-coding-agent";
9
+ import { createTypedChannel, getAgentDir } from "@dyyz1993/pi-coding-agent";
10
+ import type { Message } from "@dyyz1993/pi-ai";
11
+ import {
12
+ SUBAGENT_CHANNEL_NAME,
13
+ type SubagentChannelContract,
14
+ type SubagentEventPayload,
15
+ cleanupTempFiles,
16
+ getFinalOutput,
17
+ writePromptToTempFile,
18
+ } from "../subagent-shared/index.js";
19
+
20
+ function getSubagentDir(): string {
21
+ return path.join(getAgentDir(), "memory", "subagent");
22
+ }
23
+
24
+ export interface SubagentParams {
25
+ systemPrompt?: string;
26
+ description: string;
27
+ instruction: string;
28
+ cwd?: string;
29
+ model?: string;
30
+ maxTurns?: number;
31
+ thinkingLevel?: string;
32
+ extensions?: string[];
33
+ permissionMode?: string;
34
+ tools?: string[];
35
+ }
36
+
37
+ export interface SubagentSessionInfo {
38
+ toolCallId: string;
39
+ sessionId: string;
40
+ sessionPath: string;
41
+ description: string;
42
+ instruction: string;
43
+ systemPrompt?: string;
44
+ startedAt: number;
45
+ completedAt?: number;
46
+ exitCode?: number;
47
+ finalText?: string;
48
+ error?: string;
49
+ }
50
+
51
+ export interface SubagentDetails {
52
+ sessionId: string;
53
+ sessionPath: string;
54
+ description: string;
55
+ instruction: string;
56
+ events: unknown[];
57
+ messageCount: number;
58
+ startedAt: number;
59
+ completedAt?: number;
60
+ exitCode?: number;
61
+ }
62
+
63
+ const SubagentParamsSchema = Type.Object({
64
+ systemPrompt: Type.Optional(
65
+ Type.String({ description: "System prompt defining the sub-agent's role and behavior" }),
66
+ ),
67
+ description: Type.String({ description: "Brief description of the sub-agent's purpose" }),
68
+ instruction: Type.String({ description: "The task instruction to execute" }),
69
+ cwd: Type.Optional(Type.String({ description: "Working directory for the sub-agent process" })),
70
+ model: Type.Optional(Type.String({ description: "Model to use (e.g. 'sonnet', 'gpt-4o')" })),
71
+ maxTurns: Type.Optional(Type.Number({ description: "Maximum number of agent turns" })),
72
+ thinkingLevel: Type.Optional(Type.String({ description: "Thinking level: off, minimal, low, medium, high, xhigh" })),
73
+ extensions: Type.Optional(Type.Array(Type.String(), { description: "Extension paths to load in the child process" })),
74
+ permissionMode: Type.Optional(
75
+ Type.String({ description: "Permission mode: auto, acceptEdits, plan, dontAsk, always-allow, always-deny" }),
76
+ ),
77
+ tools: Type.Optional(Type.Array(Type.String(), { description: "Tool names to enable" })),
78
+ });
79
+
80
+ export function parseJsonLine(line: string): unknown | null {
81
+ if (!line.trim()) return null;
82
+ try {
83
+ return JSON.parse(line);
84
+ } catch (err) {
85
+ console.debug("[subagent-ext] JSON line parse failed:", err instanceof Error ? err.message : err);
86
+ return null;
87
+ }
88
+ }
89
+
90
+ export function extractTextFromEvent(event: Record<string, unknown>): string {
91
+ const tryExtract = (content: unknown): string => {
92
+ if (!Array.isArray(content)) return "";
93
+ for (const c of content) {
94
+ if (
95
+ c &&
96
+ typeof c === "object" &&
97
+ "type" in c &&
98
+ c.type === "text" &&
99
+ "text" in c &&
100
+ typeof c.text === "string" &&
101
+ c.text
102
+ ) {
103
+ return c.text;
104
+ }
105
+ }
106
+ return "";
107
+ };
108
+ let text = tryExtract(event.content);
109
+ if (text) return text;
110
+ if (event.message && typeof event.message === "object" && event.message !== null) {
111
+ text = tryExtract((event.message as Record<string, unknown>).content);
112
+ }
113
+ return text;
114
+ }
115
+
116
+ export interface SubagentResult {
117
+ finalText: string;
118
+ events: unknown[];
119
+ exitCode: number;
120
+ }
121
+
122
+ export function runSubagent(
123
+ args: string[],
124
+ cwd: string,
125
+ channel: { send: (data: unknown) => void },
126
+ sessionId: string,
127
+ signal?: AbortSignal,
128
+ timeoutMs = 300_000,
129
+ ): Promise<SubagentResult> {
130
+ return new Promise((resolve, reject) => {
131
+ let settled = false;
132
+ let stdoutBuffer = "";
133
+ let finalText = "";
134
+ const events: unknown[] = [];
135
+
136
+ const child = spawn("pi", args, {
137
+ cwd,
138
+ stdio: ["ignore", "pipe", "pipe"],
139
+ });
140
+
141
+ const finish = (result: SubagentResult) => {
142
+ if (settled) return;
143
+ settled = true;
144
+ clearTimeout(timeout);
145
+ cleanupAbort?.();
146
+ resolve(result);
147
+ };
148
+
149
+ const fail = (err: Error) => {
150
+ if (settled) return;
151
+ settled = true;
152
+ clearTimeout(timeout);
153
+ cleanupAbort?.();
154
+ if (!child.killed) child.kill("SIGTERM");
155
+ reject(err);
156
+ };
157
+
158
+ const timeout = setTimeout(() => {
159
+ fail(new Error(`SubAgent timed out (${timeoutMs / 1000}s)`));
160
+ }, timeoutMs);
161
+
162
+ let cleanupAbort: (() => void) | undefined;
163
+
164
+ child.stdout.on("data", (data: Buffer) => {
165
+ stdoutBuffer += data.toString();
166
+ const lines = stdoutBuffer.split("\n");
167
+ stdoutBuffer = lines.pop() || "";
168
+
169
+ for (const line of lines) {
170
+ const obj = parseJsonLine(line);
171
+ if (!obj) continue;
172
+ events.push(obj);
173
+
174
+ const text = extractTextFromEvent(obj as Record<string, unknown>);
175
+ if (text) finalText = text;
176
+
177
+ channel.send({ event: obj, sessionId });
178
+ }
179
+ });
180
+
181
+ child.on("error", (err) => {
182
+ fail(err);
183
+ });
184
+
185
+ child.on("exit", (code) => {
186
+ finish({
187
+ finalText: finalText || "(no output)",
188
+ events,
189
+ exitCode: code ?? 1,
190
+ });
191
+ });
192
+
193
+ if (signal) {
194
+ const killChild = () => {
195
+ if (!child.killed) child.kill("SIGTERM");
196
+ };
197
+ if (signal.aborted) {
198
+ killChild();
199
+ } else {
200
+ signal.addEventListener("abort", killChild, { once: true });
201
+ cleanupAbort = () => signal.removeEventListener("abort", killChild);
202
+ }
203
+ }
204
+ });
205
+ }
206
+
207
+ export default function subagentExtension(pi: ExtensionAPI): void {
208
+ const rawChannel = pi.registerChannel(SUBAGENT_CHANNEL_NAME);
209
+ const channel = createTypedChannel<SubagentChannelContract>(rawChannel).server;
210
+
211
+ pi.registerTool({
212
+ name: "subagent",
213
+ label: "SubAgent",
214
+ description: [
215
+ "Spawn a sub-agent with isolated context via IPC (JSON mode).",
216
+ "The sub-agent runs in a separate pi process with its own session file.",
217
+ "Real-time events are forwarded through the 'subagent' channel.",
218
+ "Session metadata is stored for later history retrieval.",
219
+ ].join(" "),
220
+ promptSnippet: "subagent(description, instruction) — delegate a task to a sub-agent",
221
+ parameters: SubagentParamsSchema,
222
+
223
+ async execute(
224
+ toolCallId: string,
225
+ params: SubagentParams,
226
+ signal: AbortSignal | undefined,
227
+ _onUpdate: AgentToolUpdateCallback<SubagentDetails> | undefined,
228
+ ctx: ExtensionContext,
229
+ ): Promise<AgentToolResult<SubagentDetails>> {
230
+ const { systemPrompt, description, instruction, cwd, model, maxTurns, thinkingLevel, extensions, tools } = params;
231
+ const sessionId = randomUUID().slice(0, 8);
232
+ const effectiveCwd = cwd ?? ctx.cwd;
233
+ const sessionDir = getSubagentDir();
234
+ const sessionPath = path.join(sessionDir, `subagent-${sessionId}.jsonl`);
235
+ const startedAt = Date.now();
236
+
237
+ const details: SubagentDetails = {
238
+ sessionId,
239
+ sessionPath,
240
+ description,
241
+ instruction,
242
+ events: [],
243
+ messageCount: 0,
244
+ startedAt,
245
+ };
246
+
247
+ const onUpdate = _onUpdate;
248
+ let tmpPromptPath: string | null = null;
249
+ let tmpPromptDir: string | null = null;
250
+
251
+ const wrappedChannel = {
252
+ send: (data: unknown) => {
253
+ channel.emit("event", data as SubagentEventPayload);
254
+ const payload = data as { event: Record<string, unknown> };
255
+ if (payload.event) {
256
+ details.events.push(payload.event);
257
+ if (payload.event.type === "message_end") {
258
+ details.messageCount++;
259
+ }
260
+ if (onUpdate) {
261
+ const text = extractTextFromEvent(payload.event) || "(running...)";
262
+ onUpdate({
263
+ content: [{ type: "text", text }],
264
+ details: { ...details },
265
+ });
266
+ }
267
+ }
268
+ },
269
+ };
270
+
271
+ try {
272
+ channel.emit("subagent_start", {
273
+ event: { type: "subagent_start", toolCallId, description, instruction },
274
+ sessionId,
275
+ });
276
+
277
+ const effectiveModel = model ?? (ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined);
278
+ const useExtensions = extensions && extensions.length > 0;
279
+ const baseArgs: string[] = ["--mode", "json", "-p"];
280
+ if (!useExtensions) baseArgs.push("--no-extensions");
281
+ baseArgs.push("--session", sessionPath);
282
+ if (effectiveModel) baseArgs.push("--model", effectiveModel);
283
+ if (maxTurns && maxTurns > 0) baseArgs.push("--max-turns", String(maxTurns));
284
+ if (thinkingLevel) baseArgs.push("--thinking", thinkingLevel);
285
+ if (tools && tools.length > 0) baseArgs.push("--tools", tools.join(","));
286
+ if (useExtensions) {
287
+ for (const ext of extensions!) baseArgs.push("-e", ext);
288
+ }
289
+ const args = baseArgs;
290
+ if (systemPrompt?.trim()) {
291
+ const tmp = await writePromptToTempFile("subagent", systemPrompt, "pi-subagent-prompt-");
292
+ tmpPromptDir = tmp.dir;
293
+ tmpPromptPath = tmp.filePath;
294
+ args.push("--append-system-prompt", tmpPromptPath);
295
+ }
296
+ args.push(instruction);
297
+
298
+ const result = await runSubagent(args, effectiveCwd, wrappedChannel, sessionId, signal);
299
+
300
+ details.events = result.events;
301
+ details.exitCode = result.exitCode;
302
+ details.completedAt = Date.now();
303
+
304
+ pi.appendEntry("subagent", {
305
+ toolCallId,
306
+ sessionId,
307
+ sessionPath,
308
+ description,
309
+ instruction,
310
+ systemPrompt: systemPrompt ? "(provided)" : undefined,
311
+ startedAt,
312
+ completedAt: details.completedAt,
313
+ exitCode: result.exitCode,
314
+ finalText: result.finalText,
315
+ } satisfies SubagentSessionInfo);
316
+
317
+ return {
318
+ content: [{ type: "text", text: result.finalText }],
319
+ details,
320
+ };
321
+ } catch (err) {
322
+ details.completedAt = Date.now();
323
+ details.exitCode = 1;
324
+ const errorMessage = err instanceof Error ? err.message : String(err);
325
+
326
+ pi.appendEntry("subagent", {
327
+ toolCallId,
328
+ sessionId,
329
+ sessionPath,
330
+ description,
331
+ instruction,
332
+ startedAt,
333
+ completedAt: details.completedAt,
334
+ exitCode: 1,
335
+ error: errorMessage,
336
+ } satisfies SubagentSessionInfo);
337
+
338
+ return {
339
+ content: [{ type: "text", text: `SubAgent failed: ${errorMessage}` }],
340
+ details,
341
+ };
342
+ } finally {
343
+ cleanupTempFiles(tmpPromptPath, tmpPromptDir, "subagent-ext");
344
+ }
345
+ },
346
+ });
347
+ }
@@ -0,0 +1,25 @@
1
+ import type { ChannelContract } from "@dyyz1993/pi-coding-agent";
2
+ export declare const SUBAGENT_CHANNEL_NAME = "subagent";
3
+ export interface SubagentEventPayload {
4
+ event: unknown;
5
+ sessionId: string;
6
+ taskId?: string;
7
+ [key: string]: unknown;
8
+ }
9
+ export interface SubagentStartPayload {
10
+ event: {
11
+ type: "subagent_start";
12
+ toolCallId: string;
13
+ description: string;
14
+ instruction: string;
15
+ };
16
+ sessionId: string;
17
+ }
18
+ export interface SubagentChannelContract extends ChannelContract {
19
+ methods?: Record<string, never>;
20
+ events: {
21
+ event: SubagentEventPayload;
22
+ subagent_start: SubagentStartPayload;
23
+ };
24
+ }
25
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE;QACN,IAAI,EAAE,gBAAgB,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChC,MAAM,EAAE;QACP,KAAK,EAAE,oBAAoB,CAAC;QAC5B,cAAc,EAAE,oBAAoB,CAAC;KACrC,CAAC;CACF","sourcesContent":["import type { ChannelContract } from \"@dyyz1993/pi-coding-agent\";\n\nexport const SUBAGENT_CHANNEL_NAME = \"subagent\";\n\nexport interface SubagentEventPayload {\n\tevent: unknown;\n\tsessionId: string;\n\ttaskId?: string;\n\t[key: string]: unknown;\n}\n\nexport interface SubagentStartPayload {\n\tevent: {\n\t\ttype: \"subagent_start\";\n\t\ttoolCallId: string;\n\t\tdescription: string;\n\t\tinstruction: string;\n\t};\n\tsessionId: string;\n}\n\nexport interface SubagentChannelContract extends ChannelContract {\n\tmethods?: Record<string, never>;\n\tevents: {\n\t\tevent: SubagentEventPayload;\n\t\tsubagent_start: SubagentStartPayload;\n\t};\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export const SUBAGENT_CHANNEL_NAME = "subagent";
2
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["contract.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC","sourcesContent":["import type { ChannelContract } from \"@dyyz1993/pi-coding-agent\";\n\nexport const SUBAGENT_CHANNEL_NAME = \"subagent\";\n\nexport interface SubagentEventPayload {\n\tevent: unknown;\n\tsessionId: string;\n\ttaskId?: string;\n\t[key: string]: unknown;\n}\n\nexport interface SubagentStartPayload {\n\tevent: {\n\t\ttype: \"subagent_start\";\n\t\ttoolCallId: string;\n\t\tdescription: string;\n\t\tinstruction: string;\n\t};\n\tsessionId: string;\n}\n\nexport interface SubagentChannelContract extends ChannelContract {\n\tmethods?: Record<string, never>;\n\tevents: {\n\t\tevent: SubagentEventPayload;\n\t\tsubagent_start: SubagentStartPayload;\n\t};\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import type { ChannelContract } from "@dyyz1993/pi-coding-agent";
2
+
3
+ export const SUBAGENT_CHANNEL_NAME = "subagent";
4
+
5
+ export interface SubagentEventPayload {
6
+ event: unknown;
7
+ sessionId: string;
8
+ taskId?: string;
9
+ [key: string]: unknown;
10
+ }
11
+
12
+ export interface SubagentStartPayload {
13
+ event: {
14
+ type: "subagent_start";
15
+ toolCallId: string;
16
+ description: string;
17
+ instruction: string;
18
+ };
19
+ sessionId: string;
20
+ }
21
+
22
+ export interface SubagentChannelContract extends ChannelContract {
23
+ methods?: Record<string, never>;
24
+ events: {
25
+ event: SubagentEventPayload;
26
+ subagent_start: SubagentStartPayload;
27
+ };
28
+ }
@@ -0,0 +1,4 @@
1
+ export { SUBAGENT_CHANNEL_NAME, type SubagentChannelContract, type SubagentEventPayload, type SubagentStartPayload } from "./contract.js";
2
+ export { type DisplayItem, type SingleResult, type SubagentDetailsBase, type UsageStats } from "./types.js";
3
+ export { accumulateUsage, cleanupTempFiles, formatTokens, formatUsageStats, getDisplayItems, getFinalOutput, makeUsage, writePromptToTempFile } from "./utils.js";
4
+ export { aggregateUsage, COLLAPSED_ITEM_COUNT, formatToolCall, renderDisplayItems, renderSingleResult } from "./render.js";
@@ -0,0 +1,166 @@
1
+ import * as os from "node:os";
2
+ import type { Theme, ThemeColor } from "@dyyz1993/pi-coding-agent";
3
+ import { getMarkdownTheme } from "@dyyz1993/pi-coding-agent";
4
+ import { type Component, Container, Markdown, Spacer, Text } from "@dyyz1993/pi-tui";
5
+ import type { DisplayItem, SingleResult, UsageStats } from "./types.js";
6
+ import { formatUsageStats, getDisplayItems, getFinalOutput } from "./utils.js";
7
+
8
+ const COLLAPSED_ITEM_COUNT = 10;
9
+
10
+ export function formatToolCall(
11
+ toolName: string,
12
+ args: Record<string, unknown>,
13
+ themeFg: (color: ThemeColor, text: string) => string,
14
+ ): string {
15
+ const shortenPath = (p: string) => {
16
+ const home = os.homedir();
17
+ return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
18
+ };
19
+
20
+ switch (toolName) {
21
+ case "bash": {
22
+ const command = (args.command as string) || "...";
23
+ const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
24
+ return themeFg("muted", "$ ") + themeFg("toolOutput", preview);
25
+ }
26
+ case "read": {
27
+ const rawPath = (args.file_path || args.path || "...") as string;
28
+ const filePath = shortenPath(rawPath);
29
+ const offset = args.offset as number | undefined;
30
+ const limit = args.limit as number | undefined;
31
+ let text = themeFg("accent", filePath);
32
+ if (offset !== undefined || limit !== undefined) {
33
+ const startLine = offset ?? 1;
34
+ const endLine = limit !== undefined ? startLine + limit - 1 : "";
35
+ text += themeFg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
36
+ }
37
+ return themeFg("muted", "read ") + text;
38
+ }
39
+ case "write": {
40
+ const rawPath = (args.file_path || args.path || "...") as string;
41
+ const filePath = shortenPath(rawPath);
42
+ const content = (args.content || "") as string;
43
+ const lines = content.split("\n").length;
44
+ let text = themeFg("muted", "write ") + themeFg("accent", filePath);
45
+ if (lines > 1) text += themeFg("dim", ` (${lines} lines)`);
46
+ return text;
47
+ }
48
+ case "edit": {
49
+ const rawPath = (args.file_path || args.path || "...") as string;
50
+ return themeFg("muted", "edit ") + themeFg("accent", shortenPath(rawPath));
51
+ }
52
+ case "ls": {
53
+ const rawPath = (args.path || ".") as string;
54
+ return themeFg("muted", "ls ") + themeFg("accent", shortenPath(rawPath));
55
+ }
56
+ case "find": {
57
+ const pattern = (args.pattern || "*") as string;
58
+ const rawPath = (args.path || ".") as string;
59
+ return themeFg("muted", "find ") + themeFg("accent", pattern) + themeFg("dim", ` in ${shortenPath(rawPath)}`);
60
+ }
61
+ case "grep": {
62
+ const pattern = (args.pattern || "") as string;
63
+ const rawPath = (args.path || ".") as string;
64
+ return (
65
+ themeFg("muted", "grep ") +
66
+ themeFg("accent", `/${pattern}/`) +
67
+ themeFg("dim", ` in ${shortenPath(rawPath)}`)
68
+ );
69
+ }
70
+ default: {
71
+ const argsStr = JSON.stringify(args);
72
+ const preview = argsStr.length > 50 ? `${argsStr.slice(0, 50)}...` : argsStr;
73
+ return themeFg("accent", toolName) + themeFg("dim", ` ${preview}`);
74
+ }
75
+ }
76
+ }
77
+
78
+ function renderDisplayItemsInner(
79
+ items: DisplayItem[],
80
+ expanded: boolean,
81
+ theme: Theme,
82
+ limit?: number,
83
+ ): string {
84
+ const fg = (c: string, t: string) => theme.fg(c as ThemeColor, t);
85
+ const toShow = limit ? items.slice(-limit) : items;
86
+ const skipped = limit && items.length > limit ? items.length - limit : 0;
87
+ let text = "";
88
+ if (skipped > 0) text += theme.fg("muted", `... ${skipped} earlier items\n`);
89
+ for (const item of toShow) {
90
+ if (item.type === "text") {
91
+ const preview = expanded ? item.text : item.text.split("\n").slice(0, 3).join("\n");
92
+ text += `${theme.fg("toolOutput", preview)}\n`;
93
+ } else {
94
+ text += `${theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, fg)}\n`;
95
+ }
96
+ }
97
+ return text.trimEnd();
98
+ }
99
+
100
+ export function renderSingleResult(r: SingleResult, expanded: boolean, theme: Theme): Component {
101
+ const mdTheme = getMarkdownTheme();
102
+ const fg = (c: string, t: string) => theme.fg(c as ThemeColor, t);
103
+ const isError =
104
+ r.exitCode !== 0 || r.stopReason === "error" || r.stopReason === "aborted" || r.stopReason === "timeout";
105
+ const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
106
+ const displayItems = getDisplayItems(r.messages);
107
+ const finalOutput = getFinalOutput(r.messages);
108
+
109
+ if (expanded) {
110
+ const container = new Container();
111
+ let header = `${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${theme.fg("muted", ` (${r.agentSource})`)}`;
112
+ if (isError && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
113
+ container.addChild(new Text(header, 0, 0));
114
+ if (isError && r.errorMessage) container.addChild(new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0));
115
+ container.addChild(new Spacer(1));
116
+ container.addChild(new Text(theme.fg("muted", "─── Task ───"), 0, 0));
117
+ container.addChild(new Text(theme.fg("dim", r.task), 0, 0));
118
+ container.addChild(new Spacer(1));
119
+ container.addChild(new Text(theme.fg("muted", "─── Output ───"), 0, 0));
120
+ if (displayItems.length === 0 && !finalOutput) {
121
+ container.addChild(new Text(theme.fg("muted", "(no output)"), 0, 0));
122
+ } else {
123
+ for (const item of displayItems) {
124
+ if (item.type === "toolCall")
125
+ container.addChild(new Text(theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, fg), 0, 0));
126
+ }
127
+ if (finalOutput) {
128
+ container.addChild(new Spacer(1));
129
+ container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
130
+ }
131
+ }
132
+ const usageStr = formatUsageStats(r.usage, r.model);
133
+ if (usageStr) {
134
+ container.addChild(new Spacer(1));
135
+ container.addChild(new Text(theme.fg("dim", usageStr), 0, 0));
136
+ }
137
+ return container;
138
+ }
139
+
140
+ let text = `${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${theme.fg("muted", ` (${r.agentSource})`)}`;
141
+ if (isError && r.stopReason) text += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
142
+ if (isError && r.errorMessage) text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`;
143
+ else if (displayItems.length === 0) text += `\n${theme.fg("muted", "(no output)")}`;
144
+ else {
145
+ text += `\n${renderDisplayItemsInner(displayItems, expanded, theme, COLLAPSED_ITEM_COUNT)}`;
146
+ if (displayItems.length > COLLAPSED_ITEM_COUNT) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
147
+ }
148
+ const usageStr = formatUsageStats(r.usage, r.model);
149
+ if (usageStr) text += `\n${theme.fg("dim", usageStr)}`;
150
+ return new Text(text, 0, 0);
151
+ }
152
+
153
+ export function aggregateUsage(results: SingleResult[]): UsageStats {
154
+ const total: UsageStats = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
155
+ for (const r of results) {
156
+ total.input += r.usage.input;
157
+ total.output += r.usage.output;
158
+ total.cacheRead += r.usage.cacheRead;
159
+ total.cacheWrite += r.usage.cacheWrite;
160
+ total.cost += r.usage.cost;
161
+ total.turns += r.usage.turns;
162
+ }
163
+ return total;
164
+ }
165
+
166
+ export { renderDisplayItemsInner as renderDisplayItems, COLLAPSED_ITEM_COUNT };
@@ -0,0 +1,35 @@
1
+ import type { Message } from "@dyyz1993/pi-ai";
2
+
3
+ export interface UsageStats {
4
+ input: number;
5
+ output: number;
6
+ cacheRead: number;
7
+ cacheWrite: number;
8
+ cost: number;
9
+ contextTokens: number;
10
+ turns: number;
11
+ }
12
+
13
+ export interface SingleResult {
14
+ agent: string;
15
+ agentSource: "user" | "project" | "unknown" | "builtin" | "plugin" | "flag" | "policy";
16
+ task: string;
17
+ exitCode: number;
18
+ messages: Message[];
19
+ stderr: string;
20
+ usage: UsageStats;
21
+ model?: string;
22
+ stopReason?: string;
23
+ errorMessage?: string;
24
+ step?: number;
25
+ sessionPath?: string;
26
+ }
27
+
28
+ export type DisplayItem =
29
+ | { type: "text"; text: string }
30
+ | { type: "toolCall"; name: string; args: Record<string, unknown> };
31
+
32
+ export interface SubagentDetailsBase {
33
+ agentScope: string;
34
+ projectAgentsDir: string | null;
35
+ }