@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.
Files changed (44) hide show
  1. package/.github/workflows/ci.yml +21 -0
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +11 -11
  4. package/biome.json +26 -0
  5. package/dist/agent-manager.d.ts +18 -4
  6. package/dist/agent-manager.js +111 -9
  7. package/dist/agent-runner.d.ts +10 -6
  8. package/dist/agent-runner.js +80 -26
  9. package/dist/agent-types.d.ts +10 -0
  10. package/dist/agent-types.js +23 -1
  11. package/dist/cross-extension-rpc.d.ts +30 -0
  12. package/dist/cross-extension-rpc.js +33 -0
  13. package/dist/custom-agents.js +36 -8
  14. package/dist/index.js +335 -66
  15. package/dist/memory.d.ts +49 -0
  16. package/dist/memory.js +151 -0
  17. package/dist/output-file.d.ts +17 -0
  18. package/dist/output-file.js +66 -0
  19. package/dist/prompts.d.ts +12 -1
  20. package/dist/prompts.js +15 -3
  21. package/dist/skill-loader.d.ts +19 -0
  22. package/dist/skill-loader.js +67 -0
  23. package/dist/types.d.ts +45 -1
  24. package/dist/ui/agent-widget.d.ts +21 -0
  25. package/dist/ui/agent-widget.js +205 -127
  26. package/dist/ui/conversation-viewer.d.ts +2 -2
  27. package/dist/ui/conversation-viewer.js +2 -2
  28. package/dist/ui/conversation-viewer.test.d.ts +1 -0
  29. package/dist/ui/conversation-viewer.test.js +254 -0
  30. package/dist/worktree.d.ts +36 -0
  31. package/dist/worktree.js +139 -0
  32. package/package.json +7 -2
  33. package/src/agent-manager.ts +7 -5
  34. package/src/agent-runner.ts +24 -19
  35. package/src/agent-types.ts +5 -5
  36. package/src/custom-agents.ts +4 -4
  37. package/src/index.ts +54 -33
  38. package/src/memory.ts +2 -2
  39. package/src/output-file.ts +1 -1
  40. package/src/skill-loader.ts +1 -1
  41. package/src/types.ts +3 -1
  42. package/src/ui/agent-widget.ts +18 -2
  43. package/src/ui/conversation-viewer.ts +4 -4
  44. 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 type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
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 { steerAgent, getAgentConversation, getDefaultMaxTurns, setDefaultMaxTurns, getGraceTurns, setGraceTurns } from "./agent-runner.js";
22
- import { DEFAULT_AGENT_NAMES, type SubagentType, type ThinkingLevel, type AgentConfig, type JoinMode, type AgentRecord, type NotificationDetails } from "./types.js";
23
- import { GroupJoinManager } from "./group-join.js";
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 { resolveModel, type ModelRegistry } from "./model-resolver.js";
27
- import { createOutputFilePath, writeInitialEntry, streamToOutputFile } from "./output-file.js";
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
- SPINNER,
31
- formatTokens,
32
- formatMs,
32
+ describeActivity,
33
33
  formatDuration,
34
+ formatMs,
35
+ formatTokens,
36
+ formatTurns,
34
37
  getDisplayName,
35
38
  getPromptModeLabel,
36
- describeActivity,
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 ??= []).push(params.message);
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, default 50. Omit for default>
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 >= 1) {
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, readFileSync, mkdirSync, lstatSync } from "node:fs";
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 */
@@ -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.
@@ -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;
@@ -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 { Theme } from "./agent-widget.js";
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 { randomUUID } from "node:crypto";
13
+ import { join } from "node:path";
14
14
 
15
15
  export interface WorktreeInfo {
16
16
  /** Absolute path to the worktree directory. */