@tintinweb/pi-subagents 0.4.8 → 0.4.10
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 +22 -0
- package/README.md +11 -11
- package/biome.json +26 -0
- package/package.json +8 -5
- 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 +5 -5
- package/src/worktree.ts +2 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: 20
|
|
17
|
+
cache: npm
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm run lint
|
|
20
|
+
- run: npm run typecheck
|
|
21
|
+
- run: npm run test
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.10] - 2026-03-18
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Default max turns is now unlimited** — subagents no longer have a 50-turn default cap. The default is unlimited (no turn limit), matching Claude Code's main loop behavior. Users can still set explicit limits per-agent via `max_turns` frontmatter or the Agent tool parameter, or globally via `/agents` → Settings (`0` = unlimited).
|
|
12
|
+
- **Live turn counter** — all agents now show a live turn count in the widget, inline result, and completion notification. With a turn limit: `⟳5≤30` (5 of 30 turns). Without: `⟳5`. Updates in real time as turns progress.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Biome linting** — added [Biome](https://biomejs.dev/) for correctness linting (unused imports, suspicious patterns). Style rules disabled. Run `npm run lint` to check, `npm run lint:fix` to auto-fix.
|
|
16
|
+
- **CI workflow** — GitHub Actions runs lint, typecheck, and tests on push to master and PRs.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Env test CI failure** — `detectEnv` test assumed a branch name exists, but CI checks out detached HEAD. Split into separate tests for repo detection and branch detection with a controlled temp repo.
|
|
20
|
+
|
|
21
|
+
## [0.4.9] - 2026-03-18
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Conversation viewer crash in narrow terminals** ([#7](https://github.com/tintinweb/pi-subagents/issues/7)) — `buildContentLines()` in the live conversation viewer could return lines wider than the terminal when `wrapTextWithAnsi()` misjudged visible width on ANSI-heavy input (e.g. tool output with embedded escape codes, long URLs, wide tables). All content lines are now clamped with `truncateToWidth()` before returning. Same class of bug as the widget fix in v0.2.7, different component.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Conversation viewer width-safety tests** — 17 tests covering `render()` and `buildContentLines()` across varied content (plain text, ANSI codes, unicode, tables, long URLs, narrow terminals). Includes mock-based regression tests that simulate upstream `wrapTextWithAnsi` returning overwidth lines, ensuring the safety net catches them.
|
|
28
|
+
|
|
8
29
|
## [0.4.8] - 2026-03-18
|
|
9
30
|
|
|
10
31
|
### Added
|
|
@@ -299,6 +320,7 @@ Initial release.
|
|
|
299
320
|
- **Thinking level** — per-agent extended thinking control
|
|
300
321
|
- **`/agent` and `/agents` commands**
|
|
301
322
|
|
|
323
|
+
[0.4.9]: https://github.com/tintinweb/pi-subagents/compare/v0.4.8...v0.4.9
|
|
302
324
|
[0.4.8]: https://github.com/tintinweb/pi-subagents/compare/v0.4.7...v0.4.8
|
|
303
325
|
[0.4.7]: https://github.com/tintinweb/pi-subagents/compare/v0.4.6...v0.4.7
|
|
304
326
|
[0.4.6]: https://github.com/tintinweb/pi-subagents/compare/v0.4.5...v0.4.6
|
package/README.md
CHANGED
|
@@ -64,9 +64,9 @@ The extension renders a persistent widget above the editor showing all active ag
|
|
|
64
64
|
|
|
65
65
|
```
|
|
66
66
|
● Agents
|
|
67
|
-
├─ ⠹ Agent Refactor auth module · 5 tool uses · 33.8k token · 12.3s
|
|
67
|
+
├─ ⠹ Agent Refactor auth module · ⟳5≤30 · 5 tool uses · 33.8k token · 12.3s
|
|
68
68
|
│ ⎿ editing 2 files…
|
|
69
|
-
├─ ⠹ Explore Find auth files · 3 tool uses · 12.4k token · 4.1s
|
|
69
|
+
├─ ⠹ Explore Find auth files · ⟳3 · 3 tool uses · 12.4k token · 4.1s
|
|
70
70
|
│ ⎿ searching…
|
|
71
71
|
└─ 2 queued
|
|
72
72
|
```
|
|
@@ -75,12 +75,12 @@ Individual agent results render Claude Code-style in the conversation:
|
|
|
75
75
|
|
|
76
76
|
| State | Example |
|
|
77
77
|
|-------|---------|
|
|
78
|
-
| **Running** | `⠹ 3 tool uses · 12.4k token` / `⎿ searching, reading 3 files…` |
|
|
79
|
-
| **Completed** | `✓ 5 tool uses · 33.8k token · 12.3s` / `⎿ Done` |
|
|
80
|
-
| **Wrapped up** | `✓ 50 tool uses · 89.1k token · 45.2s` / `⎿ Wrapped up (turn limit)` |
|
|
81
|
-
| **Stopped** | `■ 3 tool uses · 12.4k token` / `⎿ Stopped` |
|
|
82
|
-
| **Error** | `✗ 3 tool uses · 12.4k token` / `⎿ Error: timeout` |
|
|
83
|
-
| **Aborted** | `✗ 55 tool uses · 102.3k token` / `⎿ Aborted (max turns exceeded)` |
|
|
78
|
+
| **Running** | `⠹ ⟳3≤30 · 3 tool uses · 12.4k token` / `⎿ searching, reading 3 files…` |
|
|
79
|
+
| **Completed** | `✓ ⟳8 · 5 tool uses · 33.8k token · 12.3s` / `⎿ Done` |
|
|
80
|
+
| **Wrapped up** | `✓ ⟳50≤50 · 50 tool uses · 89.1k token · 45.2s` / `⎿ Wrapped up (turn limit)` |
|
|
81
|
+
| **Stopped** | `■ ⟳3 · 3 tool uses · 12.4k token` / `⎿ Stopped` |
|
|
82
|
+
| **Error** | `✗ ⟳3 · 3 tool uses · 12.4k token` / `⎿ Error: timeout` |
|
|
83
|
+
| **Aborted** | `✗ ⟳55≤50 · 55 tool uses · 102.3k token` / `⎿ Aborted (max turns exceeded)` |
|
|
84
84
|
|
|
85
85
|
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
|
|
86
86
|
|
|
@@ -88,7 +88,7 @@ Background agent completion notifications render as styled boxes:
|
|
|
88
88
|
|
|
89
89
|
```
|
|
90
90
|
✓ Find auth files completed
|
|
91
|
-
3 tool uses · 12.4k token · 4.1s
|
|
91
|
+
⟳3 · 3 tool uses · 12.4k token · 4.1s
|
|
92
92
|
⎿ Found 5 files related to authentication...
|
|
93
93
|
transcript: .pi/output/agent-abc123.jsonl
|
|
94
94
|
```
|
|
@@ -162,7 +162,7 @@ All fields are optional — sensible defaults for everything.
|
|
|
162
162
|
| `isolation` | — | Set to `worktree` to run in an isolated git worktree |
|
|
163
163
|
| `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
|
|
164
164
|
| `thinking` | inherit | off, minimal, low, medium, high, xhigh |
|
|
165
|
-
| `max_turns` |
|
|
165
|
+
| `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
|
|
166
166
|
| `prompt_mode` | `replace` | `replace`: body is the full system prompt. `append`: body appended to parent's prompt (agent acts as a "parent twin" with optional extra instructions) |
|
|
167
167
|
| `inherit_context` | `false` | Fork parent conversation into agent |
|
|
168
168
|
| `run_in_background` | `false` | Run in background by default |
|
|
@@ -185,7 +185,7 @@ Launch a sub-agent.
|
|
|
185
185
|
| `subagent_type` | string | yes | Agent type (built-in or custom) |
|
|
186
186
|
| `model` | string | no | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
|
|
187
187
|
| `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh |
|
|
188
|
-
| `max_turns` | number | no | Max agentic turns (default
|
|
188
|
+
| `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
|
|
189
189
|
| `run_in_background` | boolean | no | Run without blocking |
|
|
190
190
|
| `resume` | string | no | Agent ID to resume a previous session |
|
|
191
191
|
| `isolated` | boolean | no | No extension/MCP tools |
|
package/biome.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
|
|
3
|
+
"linter": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"rules": {
|
|
6
|
+
"recommended": true,
|
|
7
|
+
"style": {
|
|
8
|
+
"recommended": false
|
|
9
|
+
},
|
|
10
|
+
"suspicious": {
|
|
11
|
+
"noExplicitAny": "off",
|
|
12
|
+
"noControlCharactersInRegex": "off",
|
|
13
|
+
"noEmptyInterface": "off"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"formatter": {
|
|
18
|
+
"enabled": false
|
|
19
|
+
},
|
|
20
|
+
"files": {
|
|
21
|
+
"includes": [
|
|
22
|
+
"src/**/*.ts",
|
|
23
|
+
"test/**/*.ts"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tintinweb/pi-subagents",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "A pi extension extension that brings smart Claude Code-style autonomous sub-agents to pi.",
|
|
5
5
|
"author": "tintinweb",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,19 +21,22 @@
|
|
|
21
21
|
"autonomous"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mariozechner/pi-ai": "^0.
|
|
25
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
26
|
-
"@mariozechner/pi-tui": "^0.
|
|
24
|
+
"@mariozechner/pi-ai": "^0.60.0",
|
|
25
|
+
"@mariozechner/pi-coding-agent": "^0.60.0",
|
|
26
|
+
"@mariozechner/pi-tui": "^0.60.0",
|
|
27
27
|
"@sinclair/typebox": "latest"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "vitest run",
|
|
31
31
|
"test:watch": "vitest",
|
|
32
|
-
"typecheck": "tsc --noEmit"
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"lint": "biome check src/ test/",
|
|
34
|
+
"lint:fix": "biome check --fix src/ test/"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^20.0.0",
|
|
36
38
|
"typescript": "^5.0.0",
|
|
39
|
+
"@biomejs/biome": "^2.3.5",
|
|
37
40
|
"vitest": "^4.0.18"
|
|
38
41
|
},
|
|
39
42
|
"pi": {
|
package/src/agent-manager.ts
CHANGED
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
|
-
import type { ExtensionContext, ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
10
|
import type { Model } from "@mariozechner/pi-ai";
|
|
12
|
-
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
11
|
+
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
|
|
13
|
+
import type { AgentRecord, IsolationMode, SubagentType, ThinkingLevel } from "./types.js";
|
|
14
|
+
import { cleanupWorktree, createWorktree, pruneWorktrees, } from "./worktree.js";
|
|
16
15
|
|
|
17
16
|
export type OnAgentComplete = (record: AgentRecord) => void;
|
|
18
17
|
export type OnAgentStart = (record: AgentRecord) => void;
|
|
@@ -44,6 +43,8 @@ interface SpawnOptions {
|
|
|
44
43
|
onTextDelta?: (delta: string, fullText: string) => void;
|
|
45
44
|
/** Called when the agent session is created (for accessing session stats). */
|
|
46
45
|
onSessionCreated?: (session: AgentSession) => void;
|
|
46
|
+
/** Called at the end of each agentic turn with the cumulative count. */
|
|
47
|
+
onTurnEnd?: (turnCount: number) => void;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export class AgentManager {
|
|
@@ -149,6 +150,7 @@ export class AgentManager {
|
|
|
149
150
|
if (activity.type === "end") record.toolUses++;
|
|
150
151
|
options.onToolActivity?.(activity);
|
|
151
152
|
},
|
|
153
|
+
onTurnEnd: options.onTurnEnd,
|
|
152
154
|
onTextDelta: options.onTextDelta,
|
|
153
155
|
onSessionCreated: (session) => {
|
|
154
156
|
record.session = session;
|
package/src/agent-runner.ts
CHANGED
|
@@ -2,35 +2,35 @@
|
|
|
2
2
|
* agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { Model } from "@mariozechner/pi-ai";
|
|
6
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
5
7
|
import {
|
|
8
|
+
type AgentSession,
|
|
9
|
+
type AgentSessionEvent,
|
|
6
10
|
createAgentSession,
|
|
7
11
|
DefaultResourceLoader,
|
|
12
|
+
type ExtensionAPI,
|
|
8
13
|
SessionManager,
|
|
9
14
|
SettingsManager,
|
|
10
|
-
type AgentSession,
|
|
11
|
-
type AgentSessionEvent,
|
|
12
|
-
type ExtensionAPI,
|
|
13
15
|
} from "@mariozechner/pi-coding-agent";
|
|
14
|
-
import
|
|
15
|
-
import type { Model } from "@mariozechner/pi-ai";
|
|
16
|
-
import { getToolsForType, getConfig, getAgentConfig, getMemoryTools, getReadOnlyMemoryTools } from "./agent-types.js";
|
|
17
|
-
import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
|
|
16
|
+
import { getAgentConfig, getConfig, getMemoryTools, getReadOnlyMemoryTools, getToolsForType } from "./agent-types.js";
|
|
18
17
|
import { buildParentContext, extractText } from "./context.js";
|
|
19
18
|
import { detectEnv } from "./env.js";
|
|
20
19
|
import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
|
|
20
|
+
import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
|
|
21
21
|
import { preloadSkills } from "./skill-loader.js";
|
|
22
22
|
import type { SubagentType, ThinkingLevel } from "./types.js";
|
|
23
23
|
|
|
24
24
|
/** Names of tools registered by this extension that subagents must NOT inherit. */
|
|
25
25
|
const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
|
|
26
26
|
|
|
27
|
-
/** Default max turns
|
|
28
|
-
let defaultMaxTurns
|
|
27
|
+
/** Default max turns. undefined = unlimited (no turn limit). */
|
|
28
|
+
let defaultMaxTurns: number | undefined;
|
|
29
29
|
|
|
30
|
-
/** Get the default max turns value. */
|
|
31
|
-
export function getDefaultMaxTurns(): number { return defaultMaxTurns; }
|
|
32
|
-
/** Set the default max turns value
|
|
33
|
-
export function setDefaultMaxTurns(n: number): void { defaultMaxTurns = Math.max(1, n); }
|
|
30
|
+
/** Get the default max turns value. undefined = unlimited. */
|
|
31
|
+
export function getDefaultMaxTurns(): number | undefined { return defaultMaxTurns; }
|
|
32
|
+
/** Set the default max turns value. undefined = unlimited, otherwise minimum 1. */
|
|
33
|
+
export function setDefaultMaxTurns(n: number | undefined): void { defaultMaxTurns = n != null ? Math.max(1, n) : undefined; }
|
|
34
34
|
|
|
35
35
|
/** Additional turns allowed after the soft limit steer message. */
|
|
36
36
|
let graceTurns = 5;
|
|
@@ -93,6 +93,8 @@ export interface RunOptions {
|
|
|
93
93
|
/** Called on streaming text deltas from the assistant response. */
|
|
94
94
|
onTextDelta?: (delta: string, fullText: string) => void;
|
|
95
95
|
onSessionCreated?: (session: AgentSession) => void;
|
|
96
|
+
/** Called at the end of each agentic turn with the cumulative count. */
|
|
97
|
+
onTurnEnd?: (turnCount: number) => void;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
export interface RunResult {
|
|
@@ -285,12 +287,15 @@ export async function runAgent(
|
|
|
285
287
|
const unsubTurns = session.subscribe((event: AgentSessionEvent) => {
|
|
286
288
|
if (event.type === "turn_end") {
|
|
287
289
|
turnCount++;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
290
|
+
options.onTurnEnd?.(turnCount);
|
|
291
|
+
if (maxTurns != null) {
|
|
292
|
+
if (!softLimitReached && turnCount >= maxTurns) {
|
|
293
|
+
softLimitReached = true;
|
|
294
|
+
session.steer("You have reached your turn limit. Wrap up immediately — provide your final answer now.");
|
|
295
|
+
} else if (softLimitReached && turnCount >= maxTurns + graceTurns) {
|
|
296
|
+
aborted = true;
|
|
297
|
+
session.abort();
|
|
298
|
+
}
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
if (event.type === "message_start") {
|
package/src/agent-types.ts
CHANGED
|
@@ -5,18 +5,18 @@
|
|
|
5
5
|
* User agents override defaults with the same name. Disabled agents are kept but excluded from spawning.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
8
9
|
import {
|
|
9
|
-
createReadTool,
|
|
10
10
|
createBashTool,
|
|
11
11
|
createEditTool,
|
|
12
|
-
createWriteTool,
|
|
13
|
-
createGrepTool,
|
|
14
12
|
createFindTool,
|
|
13
|
+
createGrepTool,
|
|
15
14
|
createLsTool,
|
|
15
|
+
createReadTool,
|
|
16
|
+
createWriteTool,
|
|
16
17
|
} from "@mariozechner/pi-coding-agent";
|
|
17
|
-
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
18
|
-
import type { AgentConfig } from "./types.js";
|
|
19
18
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
19
|
+
import type { AgentConfig } from "./types.js";
|
|
20
20
|
|
|
21
21
|
type ToolFactory = (cwd: string) => AgentTool<any>;
|
|
22
22
|
|
package/src/custom-agents.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* custom-agents.ts — Load user-defined agents from project (.pi/agents/) and global (~/.pi/agent/agents/) locations.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
7
|
-
import { join, basename } from "node:path";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
6
|
import { homedir } from "node:os";
|
|
9
|
-
import
|
|
7
|
+
import { basename, join } from "node:path";
|
|
8
|
+
import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
10
9
|
import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
10
|
+
import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Scan for custom agent .md files from multiple locations.
|
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;
|
|
@@ -238,6 +238,6 @@ export class ConversationViewer implements Component {
|
|
|
238
238
|
lines.push(truncateToWidth(th.fg("accent", "▍ ") + th.fg("dim", act), width));
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
return lines;
|
|
241
|
+
return lines.map(l => truncateToWidth(l, width));
|
|
242
242
|
}
|
|
243
243
|
}
|
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. */
|