@gavin-hu/my-pi-agent 0.21.0 → 0.22.0
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/README.md +46 -31
- package/README.zh-CN.md +40 -26
- package/assets/screens/board.png +0 -0
- package/assets/screens/chrome.png +0 -0
- package/assets/screens/file-browse.png +0 -0
- package/assets/screens/plan-review.png +0 -0
- package/assets/screens/plan.png +0 -0
- package/docs/extensions/README.md +22 -24
- package/docs/extensions/board.md +361 -147
- package/docs/extensions/bytefly.md +152 -111
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +30 -31
- package/docs/extensions/file-open.md +9 -11
- package/docs/extensions/file-read.md +12 -14
- package/docs/extensions/goal.md +15 -17
- package/docs/extensions/job.md +54 -42
- package/docs/extensions/memory.md +17 -19
- package/docs/extensions/plan.md +166 -126
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +34 -18
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +72 -33
- package/docs/extensions/todo.md +18 -18
- package/docs/extensions/watch.md +31 -31
- package/docs/extensions/web-access.md +2 -4
- package/docs/extensions/wechat.md +34 -35
- package/docs/extensions/worktree.md +56 -50
- package/extensions/board/commands.ts +56 -15
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +5 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +4 -40
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +13 -11
- package/extensions/board/result.ts +30 -16
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +42 -10
- package/extensions/board/snapshot.ts +2 -1
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +131 -44
- package/extensions/board/types.ts +36 -32
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +33 -15
- package/extensions/board/view/dialog.ts +169 -6
- package/extensions/board/view/empty.ts +47 -0
- package/extensions/board/view/keys.ts +13 -2
- package/extensions/board/view/live.ts +89 -21
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +23 -9
- package/extensions/board/view/render.ts +208 -271
- package/extensions/board/view/styles.ts +123 -31
- package/extensions/board/view/team.ts +297 -76
- package/extensions/bytefly/block/fallback.ts +1 -1
- package/extensions/bytefly/doctor/runtime.ts +5 -2
- package/extensions/bytefly/footer/collect/label.ts +60 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
- package/extensions/bytefly/footer/config.ts +4 -2
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/lines.ts +7 -4
- package/extensions/bytefly/footer/render/segments.ts +37 -10
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/config.ts +1 -1
- package/extensions/bytefly/index.ts +32 -6
- package/extensions/bytefly/mode/editor.ts +115 -0
- package/extensions/bytefly/panel/commands.ts +210 -120
- package/extensions/bytefly/panel/format.ts +4 -4
- package/extensions/bytefly/panel/settings.ts +73 -6
- package/extensions/bytefly/panel/tui/keys.ts +2 -6
- package/extensions/bytefly/update/cache.ts +1 -1
- package/extensions/bytefly/update/register.ts +4 -4
- package/extensions/bytefly/working/config.ts +5 -7
- package/extensions/bytefly/working/format.ts +10 -13
- package/extensions/bytefly/working/register.ts +22 -27
- package/extensions/check/commands.ts +3 -2
- package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
- package/extensions/context/commands.ts +2 -2
- package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
- package/extensions/file-browse/index.ts +67 -36
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/job/child.ts +527 -0
- package/extensions/job/clock.ts +142 -0
- package/extensions/job/commands.ts +4 -2
- package/extensions/job/config.ts +8 -1
- package/extensions/job/format.ts +22 -1
- package/extensions/job/log-writer.ts +13 -2
- package/extensions/job/registry.ts +63 -7
- package/extensions/job/render.ts +34 -0
- package/extensions/job/runtime.ts +296 -655
- package/extensions/job/session.ts +132 -19
- package/extensions/job/store.ts +7 -7
- package/extensions/job/tools.ts +3 -3
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
- package/extensions/job/waiters.ts +44 -1
- package/extensions/memory/commands.ts +4 -2
- package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
- package/extensions/plan/commands.ts +12 -9
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +53 -52
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plans.ts +5 -54
- package/extensions/plan/policy.ts +8 -10
- package/extensions/plan/review-lazy.ts +85 -0
- package/extensions/plan/review.ts +2 -2
- package/extensions/plan/runtime.ts +49 -39
- package/extensions/plan/sections.ts +1 -1
- package/extensions/plan/steps.ts +3 -3
- package/extensions/plan/todos.ts +1 -1
- package/extensions/plan/tools/cancel.ts +27 -62
- package/extensions/plan/tools/enter.ts +55 -81
- package/extensions/plan/tools/exit.ts +221 -238
- package/extensions/plan/tools/refusal.ts +18 -0
- package/extensions/plan/tools/steps.ts +75 -92
- package/extensions/plan/tools/write.ts +77 -120
- package/extensions/plan/tools.ts +188 -30
- package/extensions/plan/tui.ts +5 -5
- package/extensions/plan/types.ts +48 -5
- package/extensions/plan/web-client.ts +3 -0
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +9 -67
- package/extensions/plan/web-payload.ts +3 -3
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web.ts +1 -12
- package/extensions/question/tools.ts +6 -2
- package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
- package/extensions/rewind/commands/commands.ts +6 -3
- package/extensions/rewind/state/runtime.ts +1 -1
- package/extensions/rewind/{view/status.ts → status.ts} +4 -4
- package/extensions/rewind/view/review-tui.ts +4 -4
- package/extensions/serve/config.ts +10 -8
- package/extensions/serve/index.ts +17 -3
- package/extensions/serve/registry.ts +26 -17
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +2 -1
- package/extensions/session-bus/mail-watch.ts +26 -17
- package/extensions/session-bus/protocol.ts +15 -3
- package/extensions/session-bus/resolve.ts +5 -7
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/types.ts +19 -0
- package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
- package/extensions/subagent/agents.ts +1 -1
- package/extensions/subagent/commands.ts +4 -2
- package/extensions/subagent/format.ts +15 -15
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +3 -3
- package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
- package/extensions/team/commands.ts +24 -7
- package/extensions/team/dispatch.ts +18 -4
- package/extensions/team/format.ts +17 -2
- package/extensions/team/index.ts +9 -8
- package/extensions/team/schema.ts +6 -5
- package/extensions/team/tools.ts +107 -45
- package/extensions/team/types.ts +3 -2
- package/extensions/todo/commands.ts +4 -2
- package/extensions/todo/format.ts +11 -0
- package/extensions/todo/render.ts +57 -0
- package/extensions/todo/runtime.ts +1 -1
- package/extensions/todo/tools.ts +1 -1
- package/extensions/todo/view/todo-screen.ts +122 -0
- package/extensions/todo/widget.ts +47 -0
- package/extensions/watch/commands.ts +4 -2
- package/extensions/watch/format.ts +36 -7
- package/extensions/watch/render.ts +57 -0
- package/extensions/watch/runtime.ts +27 -22
- package/extensions/watch/tools.ts +4 -4
- package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
- package/extensions/web-access/fetch/tool.ts +12 -4
- package/extensions/wechat/commands/commands.ts +18 -14
- package/extensions/wechat/index.ts +72 -37
- package/extensions/wechat/state/credentials.ts +1 -1
- package/extensions/wechat/state/state.ts +1 -1
- package/extensions/wechat/text/status.ts +112 -0
- package/extensions/wechat/types.ts +2 -2
- package/extensions/wechat/view/status-screen.ts +4 -103
- package/extensions/worktree/commands.ts +11 -9
- package/extensions/worktree/enter.ts +2 -2
- package/extensions/worktree/exit.ts +2 -2
- package/extensions/worktree/index.ts +5 -5
- package/extensions/worktree/screen.ts +3 -3
- package/extensions/worktree/status.ts +2 -2
- package/extensions/worktree/tools.ts +113 -116
- package/lib/README.md +27 -13
- package/lib/board/actions.ts +111 -0
- package/lib/board/artifact.ts +149 -0
- package/lib/board/card.ts +163 -52
- package/lib/board/config.ts +58 -0
- package/lib/board/contract.ts +113 -0
- package/lib/board/join.ts +38 -0
- package/lib/board/lanes.ts +182 -0
- package/lib/board/lead.ts +130 -0
- package/lib/board/limits.ts +78 -0
- package/lib/board/links.ts +31 -10
- package/lib/board/page-pointer.ts +1 -1
- package/lib/board/read.ts +83 -0
- package/lib/board/reason.ts +102 -0
- package/lib/board/round-text.ts +68 -6
- package/lib/board/round.ts +31 -15
- package/lib/board/rules.ts +151 -307
- package/lib/board/seat.ts +35 -0
- package/lib/board/session.ts +136 -54
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +87 -30
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +70 -58
- package/{extensions/watch → lib}/child.ts +40 -21
- package/lib/env.ts +65 -7
- package/lib/format.ts +13 -0
- package/lib/http/index.ts +14 -12
- package/lib/http/port.ts +15 -10
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +39 -10
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +8 -8
- package/lib/session-label.ts +111 -5
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +5 -4
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +23 -8
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +9 -7
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +3 -3
- package/lib/web/README.md +21 -0
- package/lib/web/components.ts +5 -1
- package/lib/web/index.ts +1 -0
- package/lib/web/markdown.ts +83 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +9 -10
- package/package.json +6 -1
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/seat.ts +0 -28
- package/extensions/todo/tui.ts +0 -212
- package/lib/tui/index.ts +0 -34
- package/skills/project-docs/SKILL.md +0 -133
- package/skills/project-docs/references/audit.md +0 -131
- package/skills/project-docs/references/changelog.md +0 -105
- package/skills/project-docs/references/discovery.md +0 -127
- package/skills/project-docs/references/readme.md +0 -80
- package/skills/project-docs/references/release.md +0 -84
- package/skills/project-docs/references/roadmap.md +0 -93
- package/skills/project-docs/scripts/state.ts +0 -316
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure text for the live working line.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A plain count while the run has started nothing (`working · 42s`), then how
|
|
5
|
+
* many top-level tools this run has started and the elapsed time
|
|
6
|
+
* (`working · 3 tools · 42s`). The line no longer names the tool: it is one word
|
|
7
|
+
* a reader can skim, and the transcript already draws each call as its own block.
|
|
8
|
+
* Whole seconds, because the tick advances once a second; negative and
|
|
9
|
+
* non-finite input reads as `0s`.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
|
-
import {
|
|
12
|
+
import { formatToolCount } from "../../../lib/format.ts";
|
|
11
13
|
import { SEPARATORS } from "../../../lib/tui/glyphs.ts";
|
|
12
14
|
import { WORKING_CONFIG } from "./config.ts";
|
|
13
15
|
|
|
14
16
|
/** What the current run has done so far, for the live line. */
|
|
15
17
|
export interface WorkingRun {
|
|
16
|
-
/** The last top-level tool started, or `null` before the first one. */
|
|
17
|
-
readonly tool: string | null;
|
|
18
18
|
/** Top-level tool calls started in this run. */
|
|
19
19
|
readonly tools: number;
|
|
20
20
|
}
|
|
@@ -30,13 +30,10 @@ export function formatElapsed(ms: number): string {
|
|
|
30
30
|
return `${seconds}s`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** The working
|
|
33
|
+
/** The working line's message: `working · 4s`, then `working · 3 tools · 4s`. */
|
|
34
34
|
export function workingCounter(ms: number, run?: WorkingRun): string {
|
|
35
35
|
const elapsed = formatElapsed(ms);
|
|
36
36
|
const tools = run?.tools ?? 0;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
return [tool, formatToolCount(tools), elapsed].filter((part) => part !== "").join(SEPARATORS.item);
|
|
40
|
-
}
|
|
41
|
-
return `${WORKING_CONFIG.working}${WORKING_CONFIG.ellipsis} ${elapsed}`;
|
|
37
|
+
const count = tools > 0 ? formatToolCount(tools) : "";
|
|
38
|
+
return [WORKING_CONFIG.working, count, elapsed].filter((part) => part !== "").join(SEPARATORS.item);
|
|
42
39
|
}
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* bytefly's live working line.
|
|
3
3
|
*
|
|
4
|
-
* Pi's working
|
|
5
|
-
* short one. This surface advances the message once a second while the
|
|
6
|
-
* up, and hands it back to Pi when
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Pi's working indicator shows a static `Working`, so a long run reads the same as
|
|
5
|
+
* a short one. This surface advances the message once a second while the indicator
|
|
6
|
+
* is up, and hands it back to Pi when it comes down. Once a top-level tool starts,
|
|
7
|
+
* the message also counts this run's tools, turning the line on the composer's top
|
|
8
|
+
* border into `working · 3 tools · 12s`.
|
|
9
9
|
*
|
|
10
|
-
* The timer is tied to the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* turns, and `start` is a no-op while it is counting.
|
|
10
|
+
* The timer is tied to the indicator's visibility. The host shows it in its own
|
|
11
|
+
* `turn_start` handler and clears it at `agent_end` (`interactive-mode.js`), so the
|
|
12
|
+
* tick starts at `turn_start` and stops at `agent_end` — the two moments it appears
|
|
13
|
+
* and disappears. Turns inside one run do not restart it: the host leaves the same
|
|
14
|
+
* indicator up across turns, and `start` is a no-op while it is counting.
|
|
16
15
|
*
|
|
17
16
|
* A nested tool call (one carrying `parentToolCallId`, such as a codemode
|
|
18
|
-
* script's inner call) is not counted
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* script's inner call) is not counted, so the number matches the tool blocks the
|
|
18
|
+
* transcript shows. The tool events repaint the line immediately; the one-second
|
|
19
|
+
* tick is still the only timer.
|
|
21
20
|
*
|
|
22
|
-
* Compaction and auto-retry also replace the
|
|
21
|
+
* Compaction and auto-retry also replace the indicator, but both run outside this
|
|
23
22
|
* window (`auto_retry_start` follows `agent_end`; the before-settle compaction
|
|
24
23
|
* runs after it), and neither is an extension event, so there is nothing to
|
|
25
24
|
* hook and nothing to undo.
|
|
26
25
|
*
|
|
27
|
-
* The handle is global (`setWorkingMessage` drives the single
|
|
28
|
-
*
|
|
26
|
+
* The handle is global (`setWorkingMessage` drives the single indicator), so the
|
|
27
|
+
* tick is cancelled and the message reset on stop and, belt and braces, on
|
|
29
28
|
* `session_shutdown` — the factory itself only registers.
|
|
30
29
|
*/
|
|
31
30
|
|
|
@@ -50,14 +49,13 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
50
49
|
let startedAt = 0;
|
|
51
50
|
// A boolean, not `startedAt === 0`: an injected clock is allowed to start at 0.
|
|
52
51
|
let active = false;
|
|
53
|
-
//
|
|
54
|
-
let tool: string | null = null;
|
|
52
|
+
// How many top-level tools this run has started. Reset with the run.
|
|
55
53
|
let tools = 0;
|
|
56
54
|
|
|
57
|
-
/** The current
|
|
58
|
-
const message = (): string => workingCounter(now() - startedAt, {
|
|
55
|
+
/** The current line's text, from the run state and the elapsed time. */
|
|
56
|
+
const message = (): string => workingCounter(now() - startedAt, { tools });
|
|
59
57
|
|
|
60
|
-
/** Repaint an active
|
|
58
|
+
/** Repaint an active line now, so a tool start does not wait for the next tick. */
|
|
61
59
|
const refresh = (ctx: ExtensionContext): void => {
|
|
62
60
|
if (active) ctx.ui.setWorkingMessage(message());
|
|
63
61
|
};
|
|
@@ -68,7 +66,6 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
68
66
|
cancel = undefined;
|
|
69
67
|
if (!active) return;
|
|
70
68
|
active = false;
|
|
71
|
-
tool = null;
|
|
72
69
|
tools = 0;
|
|
73
70
|
ctx.ui.setWorkingMessage(undefined);
|
|
74
71
|
};
|
|
@@ -78,7 +75,6 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
78
75
|
if (active) return;
|
|
79
76
|
startedAt = now();
|
|
80
77
|
active = true;
|
|
81
|
-
tool = null;
|
|
82
78
|
tools = 0;
|
|
83
79
|
const tick = (): void => {
|
|
84
80
|
ctx.ui.setWorkingMessage(message());
|
|
@@ -88,17 +84,16 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
88
84
|
};
|
|
89
85
|
|
|
90
86
|
// The host shows the working indicator on its own `turn_start` handler, so
|
|
91
|
-
// this is the first frame the
|
|
87
|
+
// this is the first frame the indicator is up.
|
|
92
88
|
pi.on("turn_start", (_event, ctx) => {
|
|
93
89
|
if (ctx.mode !== "tui") return;
|
|
94
90
|
start(ctx);
|
|
95
91
|
});
|
|
96
92
|
|
|
97
93
|
// A nested tool is an implementation detail of its parent run, not a
|
|
98
|
-
// transcript block, so it
|
|
94
|
+
// transcript block, so it does not count toward the line.
|
|
99
95
|
pi.on("tool_execution_start", (event, ctx) => {
|
|
100
96
|
if (ctx.mode !== "tui" || !active || event.parentToolCallId) return;
|
|
101
|
-
tool = event.toolName;
|
|
102
97
|
tools += 1;
|
|
103
98
|
refresh(ctx);
|
|
104
99
|
});
|
|
@@ -12,10 +12,8 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
|
|
|
12
12
|
import { resolveEffectiveCwd } from "../../lib/env.ts";
|
|
13
13
|
import { planModeRefusal } from "../../lib/plan-entry.ts";
|
|
14
14
|
import { isPlanModeEnabled } from "../../lib/plan-state.ts";
|
|
15
|
-
import { withRailsSuppressed } from "../../lib/rails.ts";
|
|
16
15
|
import { formatCheckReport, formatCheckSummary } from "./format.ts";
|
|
17
16
|
import { CheckError, type CheckRuntime } from "./runtime.ts";
|
|
18
|
-
import { CheckResultComponent, CheckRunComponent } from "./tui.ts";
|
|
19
17
|
import type { CheckResult, CheckTarget } from "./types.ts";
|
|
20
18
|
|
|
21
19
|
/** The accepted targets and their completion descriptions. */
|
|
@@ -42,6 +40,8 @@ function runLabel(commands: readonly string[]): string {
|
|
|
42
40
|
|
|
43
41
|
/** The TUI loop: run, then list/detail, with rerun. */
|
|
44
42
|
async function runTui(runtime: CheckRuntime, ctx: ExtensionCommandContext, target: CheckTarget): Promise<void> {
|
|
43
|
+
// The screens are the TUI's: a text run above and registration load neither.
|
|
44
|
+
const { CheckResultComponent, CheckRunComponent } = await import("./view/check-screens.ts");
|
|
45
45
|
while (true) {
|
|
46
46
|
let active: ReturnType<CheckRuntime["start"]>;
|
|
47
47
|
try {
|
|
@@ -123,6 +123,7 @@ export function registerCheckCommand(pi: ExtensionAPI, runtime: CheckRuntime): v
|
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
const { withRailsSuppressed } = await import("../../lib/rails.ts");
|
|
126
127
|
await withRailsSuppressed(pi, () => runTui(runtime, ctx, target));
|
|
127
128
|
},
|
|
128
129
|
});
|
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
|
|
12
12
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
14
|
-
import { formatDuration, sanitizeLine } from "
|
|
15
|
-
import { dockListFrame, screenHint, type ScreenHint, type ViewportRowsSource } from "
|
|
16
|
-
import { clip } from "
|
|
17
|
-
import { fitRows, formatRange, isCloseKey, wheelDelta } from "
|
|
18
|
-
import { DockListScreen, type DetailContents } from "
|
|
19
|
-
import { SEPARATORS } from "
|
|
20
|
-
import { LinePane } from "
|
|
21
|
-
import { emptyState, listRow } from "
|
|
22
|
-
import { titleRule } from "
|
|
23
|
-
import { paint } from "
|
|
24
|
-
import { formatFailureDetail, formatFailureLocation, formatResultSummary } from "
|
|
25
|
-
import { readSourceExcerpt } from "
|
|
26
|
-
import type { CheckResult, Failure } from "
|
|
14
|
+
import { formatDuration, sanitizeLine } from "../../../lib/format.ts";
|
|
15
|
+
import { dockListFrame, screenHint, type ScreenHint, type ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
16
|
+
import { clip } from "../../../lib/tui/columns.ts";
|
|
17
|
+
import { fitRows, formatRange, isCloseKey, wheelDelta } from "../../../lib/tui/cursor.ts";
|
|
18
|
+
import { DockListScreen, type DetailContents } from "../../../lib/tui/dock-screen.ts";
|
|
19
|
+
import { SEPARATORS } from "../../../lib/tui/glyphs.ts";
|
|
20
|
+
import { LinePane } from "../../../lib/tui/line-pane.ts";
|
|
21
|
+
import { emptyState, listRow } from "../../../lib/tui/rows.ts";
|
|
22
|
+
import { titleRule } from "../../../lib/tui/rules.ts";
|
|
23
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
24
|
+
import { formatFailureDetail, formatFailureLocation, formatResultSummary } from "../format.ts";
|
|
25
|
+
import { readSourceExcerpt } from "../source.ts";
|
|
26
|
+
import type { CheckResult, Failure } from "../types.ts";
|
|
27
27
|
|
|
28
28
|
const RUN_DEFAULT_ROWS = 20;
|
|
29
29
|
const RUN_CHROME_ROWS = 5;
|
|
@@ -166,12 +166,7 @@ export class CheckResultComponent extends DockListScreen<Failure> {
|
|
|
166
166
|
protected listHints(rows: readonly Failure[]): ScreenHint[] {
|
|
167
167
|
// An empty list has nothing to open, so it offers only the two ways out.
|
|
168
168
|
if (rows.length === 0) return ["Esc close", { text: "r rerun", color: "accent" }];
|
|
169
|
-
return [
|
|
170
|
-
"Esc close",
|
|
171
|
-
{ text: "Enter detail", color: "accent" },
|
|
172
|
-
{ text: "r rerun", color: "warning" },
|
|
173
|
-
"↑/↓ select",
|
|
174
|
-
];
|
|
169
|
+
return ["Esc close", { text: "Enter detail", color: "accent" }, { text: "r rerun", color: "warn" }, "↑/↓ select"];
|
|
175
170
|
}
|
|
176
171
|
|
|
177
172
|
protected detailHints(): ScreenHint[] {
|
|
@@ -10,12 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { formatTokens } from "../../lib/format.ts";
|
|
13
|
-
import { withRailsSuppressed } from "../../lib/rails.ts";
|
|
14
13
|
import { messageOf } from "../../lib/errors.ts";
|
|
15
14
|
import { collectInputs } from "./collect.ts";
|
|
16
15
|
import { formatReport } from "./format.ts";
|
|
17
16
|
import { buildReport } from "./report.ts";
|
|
18
|
-
import { ContextComponent } from "./tui.ts";
|
|
19
17
|
|
|
20
18
|
/** The command's argument surface, shared by the description and the usage warning. */
|
|
21
19
|
const CONTEXT_USAGE = "/context [compact [instructions]]";
|
|
@@ -37,6 +35,8 @@ async function openReport(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promi
|
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
// The dock screen owns the editor slot; hide the rails for its lifetime.
|
|
38
|
+
const { withRailsSuppressed } = await import("../../lib/rails.ts");
|
|
39
|
+
const { ContextComponent } = await import("./view/context-report.ts");
|
|
40
40
|
await withRailsSuppressed(pi, () =>
|
|
41
41
|
ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
42
42
|
return new ContextComponent({
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
|
|
15
15
|
import type { CompactionResult, Theme } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import { truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
17
|
-
import { messageOf } from "
|
|
18
|
-
import { formatTokens } from "
|
|
19
|
-
import { screenFooter, screenHint, type ViewportRowsSource } from "
|
|
20
|
-
import { ConfirmState } from "
|
|
21
|
-
import { clampScroll, fitRows, formatRange, isCloseKey, navigateScroll } from "
|
|
22
|
-
import { gaugeBar, gaugeRole } from "
|
|
23
|
-
import { BODY_INDENT } from "
|
|
24
|
-
import { titleRule } from "
|
|
25
|
-
import { paint, type TuiRole } from "
|
|
26
|
-
import { formatBudget, formatCompaction, formatDrift } from "
|
|
27
|
-
import type { ContextBucket, ContextReport } from "
|
|
17
|
+
import { messageOf } from "../../../lib/errors.ts";
|
|
18
|
+
import { formatTokens } from "../../../lib/format.ts";
|
|
19
|
+
import { screenFooter, screenHint, type ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
20
|
+
import { ConfirmState } from "../../../lib/tui/confirm.ts";
|
|
21
|
+
import { clampScroll, fitRows, formatRange, isCloseKey, navigateScroll } from "../../../lib/tui/cursor.ts";
|
|
22
|
+
import { gaugeBar, gaugeRole } from "../../../lib/tui/gauge.ts";
|
|
23
|
+
import { BODY_INDENT } from "../../../lib/tui/indent.ts";
|
|
24
|
+
import { titleRule } from "../../../lib/tui/rules.ts";
|
|
25
|
+
import { paint, type TuiRole } from "../../../lib/tui/tokens.ts";
|
|
26
|
+
import { formatBudget, formatCompaction, formatDrift } from "../format.ts";
|
|
27
|
+
import type { ContextBucket, ContextReport } from "../types.ts";
|
|
28
28
|
|
|
29
29
|
/** Fixed rows around the scrollable body: title, gauge, budget, blank, blank, footer, closing rule. */
|
|
30
30
|
const CHROME_ROWS = 7;
|
|
@@ -71,6 +71,34 @@ function shareCell(share: number): string {
|
|
|
71
71
|
return `${share.toFixed(1)}%`.padStart(6);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The hue that identifies a context category, keyed by the bucket ids
|
|
76
|
+
* `report.ts` builds.
|
|
77
|
+
*
|
|
78
|
+
* Categories are what the screen tells apart at a glance; a bucket with no
|
|
79
|
+
* entry here — injected context and its children, summaries, shell output, a
|
|
80
|
+
* type this build has never seen — reads neutral, so the catch-all rows stay
|
|
81
|
+
* quiet and the coloured ones mean something. The same table answers for the
|
|
82
|
+
* largest-message list, so a tool result is one colour in both sections.
|
|
83
|
+
*/
|
|
84
|
+
const CATEGORY_HUES: Readonly<Record<string, TuiRole>> = {
|
|
85
|
+
system: "hue1",
|
|
86
|
+
tools: "hue2",
|
|
87
|
+
user: "hue3",
|
|
88
|
+
assistant: "hue4",
|
|
89
|
+
toolResults: "hue5",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** The hue for a bucket id, or `undefined` when the category is neutral. */
|
|
93
|
+
export function hueOf(id: string): TuiRole | undefined {
|
|
94
|
+
return CATEGORY_HUES[id];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The hue for a message role word; `tool result` is the bucket's own category. */
|
|
98
|
+
export function hueForRole(role: string): TuiRole | undefined {
|
|
99
|
+
return CATEGORY_HUES[role === "tool result" ? "toolResults" : role];
|
|
100
|
+
}
|
|
101
|
+
|
|
74
102
|
/** Pad a label to its column with spaces, measured in terminal columns. */
|
|
75
103
|
function padCell(text: string, width: number): string {
|
|
76
104
|
const clipped = truncateToWidth(text, width, "…");
|
|
@@ -151,7 +179,7 @@ export class ContextComponent implements Component {
|
|
|
151
179
|
private compositionLine(width: number): string {
|
|
152
180
|
const label = paint(this.theme, "label", "Composition");
|
|
153
181
|
const total = paint(this.theme, "meta", `${formatTokens(this.report.estimate)} estimate`);
|
|
154
|
-
const full = ` ${label} · ${total}`;
|
|
182
|
+
const full = ` ${label}${paint(this.theme, "faint", " · ")}${total}`;
|
|
155
183
|
return visibleWidth(full) <= width ? full : truncateToWidth(` ${label}`, width);
|
|
156
184
|
}
|
|
157
185
|
|
|
@@ -161,12 +189,15 @@ export class ContextComponent implements Component {
|
|
|
161
189
|
const labelWidth = bucket.depth > 0 ? 20 : 22;
|
|
162
190
|
const label = padCell(bucket.label, labelWidth);
|
|
163
191
|
const tokens = formatTokens(bucket.tokens).padStart(6);
|
|
164
|
-
|
|
165
|
-
|
|
192
|
+
// The row's hue carries the category; the label, the count, and the printed
|
|
193
|
+
// share still say everything the colour repeats.
|
|
194
|
+
const hue = hueOf(bucket.id) ?? "meta";
|
|
195
|
+
const share = paint(this.theme, hue, shareCell(bucket.share));
|
|
196
|
+
const note = bucket.note ? paint(this.theme, "faint", ` ${bucket.note}`) : "";
|
|
166
197
|
const bar = gaugeBar(this.theme, {
|
|
167
198
|
percent: bucket.share,
|
|
168
199
|
blocks: BUCKET_BLOCKS,
|
|
169
|
-
role:
|
|
200
|
+
role: hue,
|
|
170
201
|
emptyRole: "faint",
|
|
171
202
|
});
|
|
172
203
|
const head = `${indent}${paint(this.theme, "label", label)}${paint(this.theme, "meta", tokens)}`;
|
|
@@ -204,7 +235,8 @@ export class ContextComponent implements Component {
|
|
|
204
235
|
for (const block of report.largest) {
|
|
205
236
|
const tokens = formatTokens(block.tokens).padStart(6);
|
|
206
237
|
const role = padCell(block.role, 12);
|
|
207
|
-
const
|
|
238
|
+
const hue = hueForRole(block.role) ?? "meta";
|
|
239
|
+
const candidate = ` ${paint(this.theme, "meta", tokens)} ${paint(this.theme, hue, role)}${paint(this.theme, "label", block.label)}`;
|
|
208
240
|
lines.push(truncateToWidth(candidate, width));
|
|
209
241
|
}
|
|
210
242
|
}
|
|
@@ -221,9 +253,9 @@ export class ContextComponent implements Component {
|
|
|
221
253
|
width,
|
|
222
254
|
[
|
|
223
255
|
{ text: "c compact", color: "accent" },
|
|
224
|
-
{ text: "r refresh", color: "
|
|
225
|
-
{ text: "↑↓ scroll", color: "
|
|
226
|
-
{ text: "Esc close", color: "
|
|
256
|
+
{ text: "r refresh", color: "faint" },
|
|
257
|
+
{ text: "↑↓ scroll", color: "faint" },
|
|
258
|
+
{ text: "Esc close", color: "faint" },
|
|
227
259
|
],
|
|
228
260
|
BODY_INDENT,
|
|
229
261
|
);
|
|
@@ -14,16 +14,13 @@ import { realpathSync } from "node:fs";
|
|
|
14
14
|
import { stat } from "node:fs/promises";
|
|
15
15
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import { isExtensionEnabled, resolveEffectiveCwd } from "../../lib/env.ts";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { openInBrowser } from "../../lib/browser.ts";
|
|
21
|
-
import { HttpError, resolveRequestPath } from "./files/paths.ts";
|
|
22
|
-
import { createFileServer, type FileServer } from "./transport/server.ts";
|
|
17
|
+
import type { openInBrowser } from "../../lib/browser.ts";
|
|
18
|
+
import type { ServeConfig } from "./config/config.ts";
|
|
19
|
+
import type { FileServer } from "./transport/server.ts";
|
|
23
20
|
|
|
24
21
|
export interface FileBrowserDeps {
|
|
25
22
|
/** Override the server factory (tests). */
|
|
26
|
-
createServer?: typeof createFileServer;
|
|
23
|
+
createServer?: typeof import("./transport/server.ts").createFileServer;
|
|
27
24
|
/** Override the browser opener (tests). */
|
|
28
25
|
open?: typeof openInBrowser;
|
|
29
26
|
}
|
|
@@ -58,51 +55,49 @@ export default function fileBrowser(pi: ExtensionAPI, deps: FileBrowserDeps = {}
|
|
|
58
55
|
if (await stop()) current.ctx.ui.notify("browse: stopped — the last page was closed", "info");
|
|
59
56
|
};
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
const target = args.trim();
|
|
63
|
-
if (target === "status") {
|
|
64
|
-
ctx.ui.notify(
|
|
65
|
-
running ? `browse: ${running.server.root} at ${running.server.url}` : "browse: not running",
|
|
66
|
-
"info",
|
|
67
|
-
);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (target === "stop") {
|
|
71
|
-
ctx.ui.notify((await stop()) ? "browse: stopped" : "browse: not running", "info");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
58
|
+
type ResolvedRoot = { root: string; config: ServeConfig };
|
|
74
59
|
|
|
75
|
-
|
|
60
|
+
/** The folder `/browse [path]` names, or undefined after telling the user why. */
|
|
61
|
+
const resolveRoot = async (target: string, cwd: string, ctx: ExtensionContext): Promise<ResolvedRoot | undefined> => {
|
|
62
|
+
const { loadConfig } = await import("./config/config.ts");
|
|
76
63
|
const config = loadConfig(cwd);
|
|
77
64
|
let root = cwd;
|
|
78
65
|
if (target) {
|
|
66
|
+
const { HttpError, resolveRequestPath } = await import("./files/paths.ts");
|
|
79
67
|
try {
|
|
80
68
|
root = await resolveRequestPath(cwd, target);
|
|
81
69
|
} catch (error) {
|
|
82
70
|
const message = error instanceof HttpError ? error.message : String(error);
|
|
83
71
|
ctx.ui.notify(`browse: ${message}`, "error");
|
|
84
|
-
return;
|
|
72
|
+
return undefined;
|
|
85
73
|
}
|
|
86
74
|
const info = await stat(root).catch(() => undefined);
|
|
87
75
|
if (!info?.isDirectory()) {
|
|
88
76
|
ctx.ui.notify(`browse: ${target} is not a folder inside the working directory`, "error");
|
|
89
|
-
return;
|
|
77
|
+
return undefined;
|
|
90
78
|
}
|
|
91
79
|
}
|
|
80
|
+
return { root, config };
|
|
81
|
+
};
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Put the page in front of the user, unless the project asked to stay headless.
|
|
85
|
+
*
|
|
86
|
+
* Every `/browse` opens it, including one that finds the page already served: the
|
|
87
|
+
* command's job is showing the page, and a URL printed over a running server leaves
|
|
88
|
+
* the user to paste it.
|
|
89
|
+
*/
|
|
90
|
+
const openPage = async (url: string, config: ServeConfig): Promise<void> => {
|
|
91
|
+
if (!config.autoOpen) return;
|
|
92
|
+
const open = deps.open ?? (await import("../../lib/browser.ts")).openInBrowser;
|
|
93
|
+
open(url);
|
|
94
|
+
};
|
|
103
95
|
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
/** Open the page's server; the server, git, HTTP, and browser modules live here. */
|
|
97
|
+
const openServer = async (root: string, config: ServeConfig, ctx: ExtensionContext): Promise<void> => {
|
|
98
|
+
const create = deps.createServer ?? (await import("./transport/server.ts")).createFileServer;
|
|
99
|
+
const { createGitDiff, createGitStatus } = await import("./git/git.ts");
|
|
100
|
+
const { requestMount } = await import("../../lib/http/index.ts");
|
|
106
101
|
try {
|
|
107
102
|
const outcome = await create({
|
|
108
103
|
root,
|
|
@@ -125,12 +120,48 @@ export default function fileBrowser(pi: ExtensionAPI, deps: FileBrowserDeps = {}
|
|
|
125
120
|
const server = outcome.server;
|
|
126
121
|
running = { server, ctx };
|
|
127
122
|
ctx.ui.notify(`browse: ${server.root} at ${server.url}`, "info");
|
|
128
|
-
|
|
123
|
+
await openPage(server.url, config);
|
|
129
124
|
} catch (error) {
|
|
130
125
|
ctx.ui.notify(`browse: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
131
126
|
}
|
|
132
127
|
};
|
|
133
128
|
|
|
129
|
+
const start = async (args: string, ctx: ExtensionContext): Promise<void> => {
|
|
130
|
+
const target = args.trim();
|
|
131
|
+
if (target === "status") {
|
|
132
|
+
ctx.ui.notify(
|
|
133
|
+
running ? `browse: ${running.server.root} at ${running.server.url}` : "browse: not running",
|
|
134
|
+
"info",
|
|
135
|
+
);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (target === "stop") {
|
|
139
|
+
ctx.ui.notify((await stop()) ? "browse: stopped" : "browse: not running", "info");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const cwd = resolveEffectiveCwd(ctx.cwd);
|
|
144
|
+
const resolved = await resolveRoot(target, cwd, ctx);
|
|
145
|
+
if (resolved === undefined) return;
|
|
146
|
+
const { root, config } = resolved;
|
|
147
|
+
|
|
148
|
+
let realRoot = root;
|
|
149
|
+
try {
|
|
150
|
+
realRoot = realpathSync.native(root);
|
|
151
|
+
} catch {
|
|
152
|
+
// Keep the unresolved root; the server factory reports a real failure.
|
|
153
|
+
}
|
|
154
|
+
if (running && running.server.root === realRoot) {
|
|
155
|
+
ctx.ui.notify(`browse: ${running.server.url} (already running)`, "info");
|
|
156
|
+
// The page is already served, so this is an open and not a restart.
|
|
157
|
+
await openPage(running.server.url, config);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await stop();
|
|
162
|
+
await openServer(root, config, ctx);
|
|
163
|
+
};
|
|
164
|
+
|
|
134
165
|
pi.registerCommand("browse", {
|
|
135
166
|
description: "Browse the working directory in a local browser (read-only): /browse [path | status | stop]",
|
|
136
167
|
getArgumentCompletions: (prefix) => {
|
|
@@ -25,10 +25,10 @@ export const PREVIEW_LINES = 20;
|
|
|
25
25
|
|
|
26
26
|
/** Render the argument path: body colour, `~`-shortened, OSC-8 linked when supported. */
|
|
27
27
|
export function renderReadPath(rawPath: string | undefined, theme: Theme, cwd: string, argsComplete = true): string {
|
|
28
|
-
if (rawPath === undefined && !argsComplete) return theme
|
|
28
|
+
if (rawPath === undefined && !argsComplete) return paint(theme, "toolOutput", "...");
|
|
29
29
|
if (typeof rawPath !== "string") return paint(theme, "bad", "[invalid arg]");
|
|
30
30
|
const value = sanitizeLine(rawPath);
|
|
31
|
-
if (!value) return theme
|
|
31
|
+
if (!value) return paint(theme, "toolOutput", "...");
|
|
32
32
|
const styled = paint(theme, "label", shortenPath(value));
|
|
33
33
|
if (!getCapabilities().hyperlinks) return styled;
|
|
34
34
|
const abs = isAbsolute(value) ? value : resolve(cwd, value);
|
|
@@ -93,14 +93,14 @@ export function formatReadResult(result: ReadRenderResult, options: ReadRenderOp
|
|
|
93
93
|
|
|
94
94
|
const summary = summarizeRead(details);
|
|
95
95
|
const sep = paint(theme, "faint", SEPARATORS.item);
|
|
96
|
-
let text = paint(theme, "meta", summary.format) + sep + theme
|
|
96
|
+
let text = paint(theme, "meta", summary.format) + sep + paint(theme, "toolOutput", summary.detail);
|
|
97
97
|
if (summary.note) text += paint(theme, summary.truncated ? "accent" : "faint", SEPARATORS.item + summary.note);
|
|
98
98
|
|
|
99
99
|
if (details.text.length > 0) {
|
|
100
100
|
if (options.expanded) {
|
|
101
101
|
const { body, moreLines } = previewText(details.text, PREVIEW_LINES);
|
|
102
102
|
// Blank line before the body, matching the built-ins' header/output gap.
|
|
103
|
-
text += `\n\n${theme
|
|
103
|
+
text += `\n\n${paint(theme, "toolOutput", body)}`;
|
|
104
104
|
if (moreLines > 0) text += `\n${paint(theme, "meta", `... (${moreLines} more lines)`)}`;
|
|
105
105
|
} else {
|
|
106
106
|
const hint = expandKey(theme);
|
|
@@ -22,7 +22,7 @@ import { isPursuedGoal } from "../../lib/goal-state.ts";
|
|
|
22
22
|
import { isRemoteTurn, remoteLanguage } from "../../lib/interaction.ts";
|
|
23
23
|
import { PLAN_CONSENT, PLAN_CONSENT_SHORT, PLAN_CONSENT_SHORT_ZH, requestPlanMode } from "../../lib/plan-entry.ts";
|
|
24
24
|
import { isPlanModeEnabled } from "../../lib/plan-state.ts";
|
|
25
|
-
import {
|
|
25
|
+
import { PLAN_TOOL } from "../../lib/tool-names.ts";
|
|
26
26
|
import type { Goal, GoalStatus } from "./types.ts";
|
|
27
27
|
|
|
28
28
|
/** Dialog title: the question the phase answers. */
|
|
@@ -45,7 +45,7 @@ export async function goalPhase(
|
|
|
45
45
|
if (isPlanModeEnabled(ctx.sessionManager.getBranch())) return "planning";
|
|
46
46
|
if (!options.ask || !ctx.hasUI) return "active";
|
|
47
47
|
// Do not ask about a phase this session cannot enter.
|
|
48
|
-
if (!pi.getAllTools().some((tool) => tool.name ===
|
|
48
|
+
if (!pi.getAllTools().some((tool) => tool.name === PLAN_TOOL)) return "active";
|
|
49
49
|
const consent = isRemoteTurn(ctx)
|
|
50
50
|
? remoteLanguage(ctx) === "zh"
|
|
51
51
|
? PLAN_CONSENT_SHORT_ZH
|