@tintinweb/pi-subagents 0.4.9 → 0.4.11
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.
- package/.github/workflows/ci.yml +21 -0
- package/CHANGELOG.md +18 -0
- package/README.md +11 -11
- package/biome.json +26 -0
- package/dist/agent-manager.d.ts +18 -4
- package/dist/agent-manager.js +111 -9
- package/dist/agent-runner.d.ts +10 -6
- package/dist/agent-runner.js +80 -26
- package/dist/agent-types.d.ts +10 -0
- package/dist/agent-types.js +23 -1
- package/dist/cross-extension-rpc.d.ts +30 -0
- package/dist/cross-extension-rpc.js +33 -0
- package/dist/custom-agents.js +36 -8
- package/dist/index.js +335 -66
- package/dist/memory.d.ts +49 -0
- package/dist/memory.js +151 -0
- package/dist/output-file.d.ts +17 -0
- package/dist/output-file.js +66 -0
- package/dist/prompts.d.ts +12 -1
- package/dist/prompts.js +15 -3
- package/dist/skill-loader.d.ts +19 -0
- package/dist/skill-loader.js +67 -0
- package/dist/types.d.ts +45 -1
- package/dist/ui/agent-widget.d.ts +21 -0
- package/dist/ui/agent-widget.js +205 -127
- package/dist/ui/conversation-viewer.d.ts +2 -2
- package/dist/ui/conversation-viewer.js +2 -2
- package/dist/ui/conversation-viewer.test.d.ts +1 -0
- package/dist/ui/conversation-viewer.test.js +254 -0
- package/dist/worktree.d.ts +36 -0
- package/dist/worktree.js +139 -0
- package/package.json +7 -2
- package/src/agent-manager.ts +7 -5
- package/src/agent-runner.ts +24 -19
- package/src/agent-types.ts +5 -5
- package/src/custom-agents.ts +4 -4
- package/src/index.ts +54 -33
- package/src/memory.ts +2 -2
- package/src/output-file.ts +1 -1
- package/src/skill-loader.ts +1 -1
- package/src/types.ts +3 -1
- package/src/ui/agent-widget.ts +18 -2
- package/src/ui/conversation-viewer.ts +4 -4
- package/src/worktree.ts +2 -2
package/src/index.ts
CHANGED
|
@@ -10,32 +10,33 @@
|
|
|
10
10
|
* /agents — Interactive agent management menu
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
import { registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
15
|
-
import { existsSync, mkdirSync, unlinkSync, readFileSync } from "node:fs";
|
|
16
|
-
import { join } from "node:path";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
17
14
|
import { homedir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
18
17
|
import { Text } from "@mariozechner/pi-tui";
|
|
19
18
|
import { Type } from "@sinclair/typebox";
|
|
20
19
|
import { AgentManager } from "./agent-manager.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { getAvailableTypes, getAllTypes, getDefaultAgentNames, getUserAgentNames, getAgentConfig, resolveType, registerAgents, BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
20
|
+
import { getAgentConversation, getDefaultMaxTurns, getGraceTurns, setDefaultMaxTurns, setGraceTurns, steerAgent } from "./agent-runner.js";
|
|
21
|
+
import { BUILTIN_TOOL_NAMES, getAgentConfig, getAllTypes, getAvailableTypes, getDefaultAgentNames, getUserAgentNames, registerAgents, resolveType } from "./agent-types.js";
|
|
22
|
+
import { registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
25
23
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
24
|
+
import { GroupJoinManager } from "./group-join.js";
|
|
25
|
+
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
26
|
+
import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
|
|
27
|
+
import { type AgentConfig, type AgentRecord, type JoinMode, type NotificationDetails, type SubagentType, type ThinkingLevel } from "./types.js";
|
|
28
28
|
import {
|
|
29
|
+
type AgentActivity,
|
|
30
|
+
type AgentDetails,
|
|
29
31
|
AgentWidget,
|
|
30
|
-
|
|
31
|
-
formatTokens,
|
|
32
|
-
formatMs,
|
|
32
|
+
describeActivity,
|
|
33
33
|
formatDuration,
|
|
34
|
+
formatMs,
|
|
35
|
+
formatTokens,
|
|
36
|
+
formatTurns,
|
|
34
37
|
getDisplayName,
|
|
35
38
|
getPromptModeLabel,
|
|
36
|
-
|
|
37
|
-
type AgentDetails,
|
|
38
|
-
type AgentActivity,
|
|
39
|
+
SPINNER,
|
|
39
40
|
type UICtx,
|
|
40
41
|
} from "./ui/agent-widget.js";
|
|
41
42
|
|
|
@@ -56,8 +57,8 @@ function safeFormatTokens(session: { getSessionStats(): { tokens: { total: numbe
|
|
|
56
57
|
* Create an AgentActivity state and spawn callbacks for tracking tool usage.
|
|
57
58
|
* Used by both foreground and background paths to avoid duplication.
|
|
58
59
|
*/
|
|
59
|
-
function createActivityTracker(onStreamUpdate?: () => void) {
|
|
60
|
-
const state: AgentActivity = { activeTools: new Map(), toolUses: 0, tokens: "", responseText: "", session: undefined };
|
|
60
|
+
function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
|
|
61
|
+
const state: AgentActivity = { activeTools: new Map(), toolUses: 0, turnCount: 1, maxTurns, tokens: "", responseText: "", session: undefined };
|
|
61
62
|
|
|
62
63
|
const callbacks = {
|
|
63
64
|
onToolActivity: (activity: { type: "start" | "end"; toolName: string }) => {
|
|
@@ -76,6 +77,10 @@ function createActivityTracker(onStreamUpdate?: () => void) {
|
|
|
76
77
|
state.responseText = fullText;
|
|
77
78
|
onStreamUpdate?.();
|
|
78
79
|
},
|
|
80
|
+
onTurnEnd: (turnCount: number) => {
|
|
81
|
+
state.turnCount = turnCount;
|
|
82
|
+
onStreamUpdate?.();
|
|
83
|
+
},
|
|
79
84
|
onSessionCreated: (session: any) => {
|
|
80
85
|
state.session = session;
|
|
81
86
|
},
|
|
@@ -145,12 +150,15 @@ function formatTaskNotification(record: AgentRecord, resultMaxLen: number): stri
|
|
|
145
150
|
function buildDetails(
|
|
146
151
|
base: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "tags">,
|
|
147
152
|
record: { toolUses: number; startedAt: number; completedAt?: number; status: string; error?: string; id?: string; session?: any },
|
|
153
|
+
activity?: AgentActivity,
|
|
148
154
|
overrides?: Partial<AgentDetails>,
|
|
149
155
|
): AgentDetails {
|
|
150
156
|
return {
|
|
151
157
|
...base,
|
|
152
158
|
toolUses: record.toolUses,
|
|
153
159
|
tokens: safeFormatTokens(record.session),
|
|
160
|
+
turnCount: activity?.turnCount,
|
|
161
|
+
maxTurns: activity?.maxTurns,
|
|
154
162
|
durationMs: (record.completedAt ?? Date.now()) - record.startedAt,
|
|
155
163
|
status: record.status as AgentDetails["status"],
|
|
156
164
|
agentId: record.id,
|
|
@@ -160,7 +168,7 @@ function buildDetails(
|
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
/** Build notification details for the custom message renderer. */
|
|
163
|
-
function buildNotificationDetails(record: AgentRecord, resultMaxLen: number): NotificationDetails {
|
|
171
|
+
function buildNotificationDetails(record: AgentRecord, resultMaxLen: number, activity?: AgentActivity): NotificationDetails {
|
|
164
172
|
let totalTokens = 0;
|
|
165
173
|
try {
|
|
166
174
|
if (record.session) totalTokens = record.session.getSessionStats().tokens?.total ?? 0;
|
|
@@ -171,6 +179,8 @@ function buildNotificationDetails(record: AgentRecord, resultMaxLen: number): No
|
|
|
171
179
|
description: record.description,
|
|
172
180
|
status: record.status,
|
|
173
181
|
toolUses: record.toolUses,
|
|
182
|
+
turnCount: activity?.turnCount ?? 0,
|
|
183
|
+
maxTurns: activity?.maxTurns,
|
|
174
184
|
totalTokens,
|
|
175
185
|
durationMs: record.completedAt ? record.completedAt - record.startedAt : 0,
|
|
176
186
|
outputFile: record.outputFile,
|
|
@@ -203,6 +213,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
203
213
|
|
|
204
214
|
// Line 2: stats
|
|
205
215
|
const parts: string[] = [];
|
|
216
|
+
if (d.turnCount > 0) parts.push(formatTurns(d.turnCount, d.maxTurns));
|
|
206
217
|
if (d.toolUses > 0) parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`);
|
|
207
218
|
if (d.totalTokens > 0) parts.push(formatTokens(d.totalTokens));
|
|
208
219
|
if (d.durationMs > 0) parts.push(formatMs(d.durationMs));
|
|
@@ -277,7 +288,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
277
288
|
customType: "subagent-notification",
|
|
278
289
|
content: notification + footer,
|
|
279
290
|
display: true,
|
|
280
|
-
details: buildNotificationDetails(record, 500),
|
|
291
|
+
details: buildNotificationDetails(record, 500, agentActivity.get(record.id)),
|
|
281
292
|
}, { deliverAs: "followUp" });
|
|
282
293
|
}
|
|
283
294
|
|
|
@@ -305,9 +316,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
305
316
|
: `${unconsumed.length} agent(s) finished`;
|
|
306
317
|
|
|
307
318
|
const [first, ...rest] = unconsumed;
|
|
308
|
-
const details = buildNotificationDetails(first, 300);
|
|
319
|
+
const details = buildNotificationDetails(first, 300, agentActivity.get(first.id));
|
|
309
320
|
if (rest.length > 0) {
|
|
310
|
-
details.others = rest.map(r => buildNotificationDetails(r, 300));
|
|
321
|
+
details.others = rest.map(r => buildNotificationDetails(r, 300, agentActivity.get(r.id)));
|
|
311
322
|
}
|
|
312
323
|
|
|
313
324
|
pi.sendMessage<NotificationDetails>({
|
|
@@ -585,7 +596,7 @@ Guidelines:
|
|
|
585
596
|
),
|
|
586
597
|
max_turns: Type.Optional(
|
|
587
598
|
Type.Number({
|
|
588
|
-
description: "Maximum number of agentic turns before stopping.",
|
|
599
|
+
description: "Maximum number of agentic turns before stopping. Omit for unlimited (default).",
|
|
589
600
|
minimum: 1,
|
|
590
601
|
}),
|
|
591
602
|
),
|
|
@@ -637,11 +648,14 @@ Guidelines:
|
|
|
637
648
|
return new Text(text, 0, 0);
|
|
638
649
|
}
|
|
639
650
|
|
|
640
|
-
// Helper: build "haiku · thinking: high · 3 tool uses · 33.8k tokens" stats string
|
|
651
|
+
// Helper: build "haiku · thinking: high · ⟳5≤30 · 3 tool uses · 33.8k tokens" stats string
|
|
641
652
|
const stats = (d: AgentDetails) => {
|
|
642
653
|
const parts: string[] = [];
|
|
643
654
|
if (d.modelName) parts.push(d.modelName);
|
|
644
655
|
if (d.tags) parts.push(...d.tags);
|
|
656
|
+
if (d.turnCount != null && d.turnCount > 0) {
|
|
657
|
+
parts.push(formatTurns(d.turnCount, d.maxTurns));
|
|
658
|
+
}
|
|
645
659
|
if (d.toolUses > 0) parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`);
|
|
646
660
|
if (d.tokens) parts.push(d.tokens);
|
|
647
661
|
return parts.map(p => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
|
|
@@ -762,6 +776,7 @@ Guidelines:
|
|
|
762
776
|
if (thinking) agentTags.push(`thinking: ${thinking}`);
|
|
763
777
|
if (isolated) agentTags.push("isolated");
|
|
764
778
|
if (isolation === "worktree") agentTags.push("worktree");
|
|
779
|
+
const effectiveMaxTurns = params.max_turns ?? customConfig?.maxTurns ?? getDefaultMaxTurns();
|
|
765
780
|
// Shared base fields for all AgentDetails in this call
|
|
766
781
|
const detailBase = {
|
|
767
782
|
displayName,
|
|
@@ -792,7 +807,7 @@ Guidelines:
|
|
|
792
807
|
|
|
793
808
|
// Background execution
|
|
794
809
|
if (runInBackground) {
|
|
795
|
-
const { state: bgState, callbacks: bgCallbacks } = createActivityTracker();
|
|
810
|
+
const { state: bgState, callbacks: bgCallbacks } = createActivityTracker(effectiveMaxTurns);
|
|
796
811
|
|
|
797
812
|
// Wrap onSessionCreated to wire output file streaming.
|
|
798
813
|
// The callback lazily reads record.outputFile (set right after spawn)
|
|
@@ -878,6 +893,8 @@ Guidelines:
|
|
|
878
893
|
...detailBase,
|
|
879
894
|
toolUses: fgState.toolUses,
|
|
880
895
|
tokens: fgState.tokens,
|
|
896
|
+
turnCount: fgState.turnCount,
|
|
897
|
+
maxTurns: fgState.maxTurns,
|
|
881
898
|
durationMs: Date.now() - startedAt,
|
|
882
899
|
status: "running",
|
|
883
900
|
activity: describeActivity(fgState.activeTools, fgState.responseText),
|
|
@@ -889,7 +906,7 @@ Guidelines:
|
|
|
889
906
|
});
|
|
890
907
|
};
|
|
891
908
|
|
|
892
|
-
const { state: fgState, callbacks: fgCallbacks } = createActivityTracker(streamUpdate);
|
|
909
|
+
const { state: fgState, callbacks: fgCallbacks } = createActivityTracker(effectiveMaxTurns, streamUpdate);
|
|
893
910
|
|
|
894
911
|
// Wire session creation to register in widget
|
|
895
912
|
const origOnSession = fgCallbacks.onSessionCreated;
|
|
@@ -935,7 +952,7 @@ Guidelines:
|
|
|
935
952
|
// Get final token count
|
|
936
953
|
const tokenText = safeFormatTokens(fgState.session);
|
|
937
954
|
|
|
938
|
-
const details = buildDetails(detailBase, record, { tokens: tokenText });
|
|
955
|
+
const details = buildDetails(detailBase, record, fgState, { tokens: tokenText });
|
|
939
956
|
|
|
940
957
|
const fallbackNote = fellBack
|
|
941
958
|
? `Note: Unknown agent type "${rawType}" — using general-purpose.\n\n`
|
|
@@ -1056,7 +1073,8 @@ Guidelines:
|
|
|
1056
1073
|
}
|
|
1057
1074
|
if (!record.session) {
|
|
1058
1075
|
// Session not ready yet — queue the steer for delivery once initialized
|
|
1059
|
-
(record.pendingSteers
|
|
1076
|
+
if (!record.pendingSteers) record.pendingSteers = [];
|
|
1077
|
+
record.pendingSteers.push(params.message);
|
|
1060
1078
|
pi.events.emit("subagents:steered", { id: record.id, message: params.message });
|
|
1061
1079
|
return textResult(`Steering message queued for agent ${record.id}. It will be delivered once the session initializes.`);
|
|
1062
1080
|
}
|
|
@@ -1461,7 +1479,7 @@ description: <one-line description shown in UI>
|
|
|
1461
1479
|
tools: <comma-separated built-in tools: read, bash, edit, write, grep, find, ls. Use "none" for no tools. Omit for all tools>
|
|
1462
1480
|
model: <optional model as "provider/modelId", e.g. "anthropic/claude-haiku-4-5-20251001". Omit to inherit parent model>
|
|
1463
1481
|
thinking: <optional thinking level: off, minimal, low, medium, high, xhigh. Omit to inherit>
|
|
1464
|
-
max_turns: <optional max agentic turns
|
|
1482
|
+
max_turns: <optional max agentic turns. 0 or omit for unlimited (default)>
|
|
1465
1483
|
prompt_mode: <"replace" (body IS the full system prompt) or "append" (body is appended to default prompt). Default: replace>
|
|
1466
1484
|
extensions: <true (inherit all MCP/extension tools), false (none), or comma-separated names. Default: true>
|
|
1467
1485
|
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
@@ -1597,7 +1615,7 @@ ${systemPrompt}
|
|
|
1597
1615
|
async function showSettings(ctx: ExtensionCommandContext) {
|
|
1598
1616
|
const choice = await ctx.ui.select("Settings", [
|
|
1599
1617
|
`Max concurrency (current: ${manager.getMaxConcurrent()})`,
|
|
1600
|
-
`Default max turns (current: ${getDefaultMaxTurns()})`,
|
|
1618
|
+
`Default max turns (current: ${getDefaultMaxTurns() ?? "unlimited"})`,
|
|
1601
1619
|
`Grace turns (current: ${getGraceTurns()})`,
|
|
1602
1620
|
`Join mode (current: ${getDefaultJoinMode()})`,
|
|
1603
1621
|
]);
|
|
@@ -1615,14 +1633,17 @@ ${systemPrompt}
|
|
|
1615
1633
|
}
|
|
1616
1634
|
}
|
|
1617
1635
|
} else if (choice.startsWith("Default max turns")) {
|
|
1618
|
-
const val = await ctx.ui.input("Default max turns before wrap-up", String(getDefaultMaxTurns()));
|
|
1636
|
+
const val = await ctx.ui.input("Default max turns before wrap-up (0 = unlimited)", String(getDefaultMaxTurns() ?? 0));
|
|
1619
1637
|
if (val) {
|
|
1620
1638
|
const n = parseInt(val, 10);
|
|
1621
|
-
if (n
|
|
1639
|
+
if (n === 0) {
|
|
1640
|
+
setDefaultMaxTurns(undefined);
|
|
1641
|
+
ctx.ui.notify("Default max turns set to unlimited", "info");
|
|
1642
|
+
} else if (n >= 1) {
|
|
1622
1643
|
setDefaultMaxTurns(n);
|
|
1623
1644
|
ctx.ui.notify(`Default max turns set to ${n}`, "info");
|
|
1624
1645
|
} else {
|
|
1625
|
-
ctx.ui.notify("Must be a positive integer.", "warning");
|
|
1646
|
+
ctx.ui.notify("Must be 0 (unlimited) or a positive integer.", "warning");
|
|
1626
1647
|
}
|
|
1627
1648
|
}
|
|
1628
1649
|
} else if (choice.startsWith("Grace turns")) {
|
package/src/memory.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* - "local" → .pi/agent-memory-local/{agent-name}/
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync,
|
|
11
|
-
import { join, resolve } from "node:path";
|
|
10
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
|
|
12
11
|
import { homedir } from "node:os";
|
|
12
|
+
import { join, } from "node:path";
|
|
13
13
|
import type { MemoryScope } from "./types.js";
|
|
14
14
|
|
|
15
15
|
/** Maximum lines to read from MEMORY.md */
|
package/src/output-file.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* matching Claude Code's task output file format.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { appendFileSync, chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { join } from "node:path";
|
|
10
|
-
import { mkdirSync, chmodSync, appendFileSync, writeFileSync } from "node:fs";
|
|
11
11
|
import type { AgentSession, AgentSessionEvent } from "@mariozechner/pi-coding-agent";
|
|
12
12
|
|
|
13
13
|
/** Create the output file path, ensuring the directory exists.
|
package/src/skill-loader.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and returns their content for injection into the agent's system prompt.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { join } from "node:path";
|
|
9
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
10
|
import { isUnsafeName, safeReadFile } from "./memory.js";
|
|
11
11
|
|
|
12
12
|
export interface PreloadedSkill {
|
package/src/types.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* types.ts — Type definitions for the subagent system.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|
6
5
|
import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
|
|
6
|
+
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|
7
7
|
|
|
8
8
|
export type { ThinkingLevel };
|
|
9
9
|
|
|
@@ -93,6 +93,8 @@ export interface NotificationDetails {
|
|
|
93
93
|
description: string;
|
|
94
94
|
status: string;
|
|
95
95
|
toolUses: number;
|
|
96
|
+
turnCount: number;
|
|
97
|
+
maxTurns?: number;
|
|
96
98
|
totalTokens: number;
|
|
97
99
|
durationMs: number;
|
|
98
100
|
outputFile?: string;
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { truncateToWidth } from "@mariozechner/pi-tui";
|
|
9
9
|
import type { AgentManager } from "../agent-manager.js";
|
|
10
|
-
import type { SubagentType } from "../types.js";
|
|
11
10
|
import { getConfig } from "../agent-types.js";
|
|
11
|
+
import type { SubagentType } from "../types.js";
|
|
12
12
|
|
|
13
13
|
// ---- Constants ----
|
|
14
14
|
|
|
@@ -55,6 +55,10 @@ export interface AgentActivity {
|
|
|
55
55
|
tokens: string;
|
|
56
56
|
responseText: string;
|
|
57
57
|
session?: { getSessionStats(): { tokens: { total: number } } };
|
|
58
|
+
/** Current turn count. */
|
|
59
|
+
turnCount: number;
|
|
60
|
+
/** Effective max turns for this agent (undefined = unlimited). */
|
|
61
|
+
maxTurns?: number;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/** Metadata attached to Agent tool results for custom rendering. */
|
|
@@ -74,6 +78,10 @@ export interface AgentDetails {
|
|
|
74
78
|
modelName?: string;
|
|
75
79
|
/** Notable config tags (e.g. ["thinking: high", "isolated"]). */
|
|
76
80
|
tags?: string[];
|
|
81
|
+
/** Current turn count. */
|
|
82
|
+
turnCount?: number;
|
|
83
|
+
/** Effective max turns (undefined = unlimited). */
|
|
84
|
+
maxTurns?: number;
|
|
77
85
|
agentId?: string;
|
|
78
86
|
error?: string;
|
|
79
87
|
}
|
|
@@ -87,6 +95,11 @@ export function formatTokens(count: number): string {
|
|
|
87
95
|
return `${count} token`;
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
/** Format turn count with optional max limit: "⟳5≤30" or "⟳5". */
|
|
99
|
+
export function formatTurns(turnCount: number, maxTurns?: number | null): string {
|
|
100
|
+
return maxTurns != null ? `⟳${turnCount}≤${maxTurns}` : `⟳${turnCount}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
/** Format milliseconds as human-readable duration. */
|
|
91
104
|
export function formatMs(ms: number): string {
|
|
92
105
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -214,7 +227,7 @@ export class AgentWidget {
|
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
/** Render a finished agent line. */
|
|
217
|
-
private renderFinishedLine(a: { type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string }, theme: Theme): string {
|
|
230
|
+
private renderFinishedLine(a: { id: string; type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string }, theme: Theme): string {
|
|
218
231
|
const name = getDisplayName(a.type);
|
|
219
232
|
const modeLabel = getPromptModeLabel(a.type);
|
|
220
233
|
const duration = formatMs((a.completedAt ?? Date.now()) - a.startedAt);
|
|
@@ -241,6 +254,8 @@ export class AgentWidget {
|
|
|
241
254
|
}
|
|
242
255
|
|
|
243
256
|
const parts: string[] = [];
|
|
257
|
+
const activity = this.agentActivity.get(a.id);
|
|
258
|
+
if (activity) parts.push(formatTurns(activity.turnCount, activity.maxTurns));
|
|
244
259
|
if (a.toolUses > 0) parts.push(`${a.toolUses} tool use${a.toolUses === 1 ? "" : "s"}`);
|
|
245
260
|
parts.push(duration);
|
|
246
261
|
|
|
@@ -296,6 +311,7 @@ export class AgentWidget {
|
|
|
296
311
|
}
|
|
297
312
|
|
|
298
313
|
const parts: string[] = [];
|
|
314
|
+
if (bg) parts.push(formatTurns(bg.turnCount, bg.maxTurns));
|
|
299
315
|
if (toolUses > 0) parts.push(`${toolUses} tool use${toolUses === 1 ? "" : "s"}`);
|
|
300
316
|
if (tokenText) parts.push(tokenText);
|
|
301
317
|
parts.push(elapsed);
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* Subscribes to session events for real-time streaming updates.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component, type TUI } from "@mariozechner/pi-tui";
|
|
9
8
|
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|
10
|
-
import type
|
|
11
|
-
import { formatTokens, formatDuration, getDisplayName, getPromptModeLabel, describeActivity, type AgentActivity } from "./agent-widget.js";
|
|
12
|
-
import type { AgentRecord } from "../types.js";
|
|
9
|
+
import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
13
10
|
import { extractText } from "../context.js";
|
|
11
|
+
import type { AgentRecord } from "../types.js";
|
|
12
|
+
import type { Theme } from "./agent-widget.js";
|
|
13
|
+
import { type AgentActivity, describeActivity, formatDuration, formatTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
|
|
14
14
|
|
|
15
15
|
/** Lines consumed by chrome: top border + header + header sep + footer sep + footer + bottom border. */
|
|
16
16
|
const CHROME_LINES = 6;
|
package/src/worktree.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
10
11
|
import { existsSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
|
-
import {
|
|
13
|
+
import { join } from "node:path";
|
|
14
14
|
|
|
15
15
|
export interface WorktreeInfo {
|
|
16
16
|
/** Absolute path to the worktree directory. */
|