@gavin-hu/my-pi-agent 0.20.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 -30
- package/README.zh-CN.md +40 -25
- 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 -23
- package/docs/extensions/board.md +397 -168
- package/docs/extensions/bytefly.md +234 -197
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +57 -63
- 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 +197 -140
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +145 -0
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +83 -44
- 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 +79 -25
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +17 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +5 -20
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +65 -93
- package/extensions/board/result.ts +30 -39
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +49 -11
- package/extensions/board/snapshot.ts +8 -6
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +154 -55
- package/extensions/board/transport/router.ts +3 -5
- package/extensions/board/transport/server.ts +59 -28
- package/extensions/board/types.ts +36 -42
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +35 -16
- 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 +90 -28
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +38 -13
- package/extensions/board/view/render.ts +219 -277
- package/extensions/board/view/styles.ts +130 -32
- 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 +7 -8
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/footer.ts +18 -4
- package/extensions/bytefly/footer/render/key.ts +46 -0
- package/extensions/bytefly/footer/render/layout.ts +13 -3
- package/extensions/bytefly/footer/render/lines.ts +10 -7
- package/extensions/bytefly/footer/render/segments.ts +41 -38
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/component.ts +4 -20
- 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/render.ts +10 -1
- 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/config/config.ts +4 -19
- package/extensions/file-browse/index.ts +89 -79
- package/extensions/file-browse/live/live.ts +1 -1
- package/extensions/file-browse/transport/router.ts +12 -4
- package/extensions/file-browse/transport/server.ts +61 -28
- package/extensions/file-browse/view/chrome.ts +4 -4
- package/extensions/file-browse/view/client.ts +3 -1
- package/extensions/file-browse/view/html.ts +73 -40
- package/extensions/file-browse/view/live-client.ts +2 -2
- package/extensions/file-browse/view/markdown.ts +13 -14
- package/extensions/file-browse/view/palette.ts +2 -2
- package/extensions/file-browse/view/raw-html.ts +9 -9
- package/extensions/file-browse/view/tree.ts +1 -1
- package/extensions/file-browse/view/urls.ts +20 -6
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/goal/tui.ts +12 -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 +21 -9
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -99
- 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/feedback.ts +62 -16
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +58 -51
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plan-text.ts +114 -0
- 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 +61 -41
- 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-blocks.ts +76 -5
- package/extensions/plan/web-chrome.ts +4 -0
- package/extensions/plan/web-client.ts +102 -2
- package/extensions/plan/web-collab.ts +4 -4
- package/extensions/plan/web-comments.ts +39 -8
- package/extensions/plan/web-composer.ts +14 -3
- package/extensions/plan/web-keys.ts +149 -25
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +10 -68
- package/extensions/plan/web-outline.ts +22 -2
- package/extensions/plan/web-page.ts +56 -86
- package/extensions/plan/web-payload.ts +188 -0
- package/extensions/plan/web-plan.ts +23 -13
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web-settle.ts +14 -7
- package/extensions/plan/web-share-session.ts +24 -55
- package/extensions/plan/web-share.ts +19 -37
- package/extensions/plan/web-write.ts +112 -0
- package/extensions/plan/web.ts +67 -131
- 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 +39 -0
- package/extensions/serve/index.ts +85 -0
- package/extensions/serve/registry.ts +260 -0
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +17 -9
- 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/result.ts +1 -27
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/tools.ts +5 -4
- 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 -5
- 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/{tui.ts → view/todo-screen.ts} +18 -90
- 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 +28 -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 +30 -5
- 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 +199 -51
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +119 -36
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +81 -63
- package/lib/bus-request.ts +83 -0
- 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 +17 -11
- package/lib/http/mount.ts +142 -0
- package/lib/http/port.ts +30 -0
- package/lib/http/serve.ts +170 -46
- package/lib/http/server.ts +3 -3
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +46 -21
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +10 -10
- package/lib/session-label.ts +129 -7
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +39 -22
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +29 -11
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +11 -11
- package/lib/tui/render-cache.ts +29 -0
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +33 -4
- package/lib/web/README.md +21 -0
- package/lib/web/client-runtime.ts +27 -1
- 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 +20 -2
- package/lib/web/runtime.ts +9 -10
- package/package.json +8 -2
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/config.ts +0 -36
- package/extensions/board/seat.ts +0 -28
- 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
|
@@ -16,7 +16,7 @@ import { lastTodos } from "../../lib/todo-state.ts";
|
|
|
16
16
|
import { DEFAULT_TODO_CONFIG, type TodoConfig } from "./config.ts";
|
|
17
17
|
import { shouldNudge } from "./nudge.ts";
|
|
18
18
|
import { hasOpenTodos } from "./state.ts";
|
|
19
|
-
import { TodoWidget, WIDGET_KEY } from "./
|
|
19
|
+
import { TodoWidget, WIDGET_KEY } from "./widget.ts";
|
|
20
20
|
import type { Todo } from "./types.ts";
|
|
21
21
|
|
|
22
22
|
export interface TodoRuntime {
|
package/extensions/todo/tools.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { paint } from "../../lib/tui/tokens.ts";
|
|
|
22
22
|
import { compareByActivity, formatCallText, formatTodoText, type TodoRailInput } from "./format.ts";
|
|
23
23
|
import type { TodoRuntime } from "./runtime.ts";
|
|
24
24
|
import { TodoParams, TodoResult, type TodoArgs } from "./schema.ts";
|
|
25
|
-
import { TodoResult as TodoResultView } from "./
|
|
25
|
+
import { TodoResult as TodoResultView } from "./render.ts";
|
|
26
26
|
import type { Todo, TodoDetails } from "./types.ts";
|
|
27
27
|
|
|
28
28
|
export const TOOL_NAME = TODO_TOOL;
|
|
@@ -1,102 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `/todos` screen: a scrollable, dismissible list with its own frame.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The frame, the hints, the range line and the empty state come from the
|
|
5
|
+
* `lib/tui/` kit (`dockListFrame`, `screenHint`, `formatRange`, `emptyState`),
|
|
6
|
+
* and the rows from `todoRow` in `format.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Full-screen surfaces live under `view/` and load on first use: the `/todos`
|
|
9
|
+
* command imports this module behind its TUI branch, so registration never pays
|
|
10
|
+
* for it. The widget and the transcript block, which Pi draws synchronously,
|
|
11
|
+
* stay outside this directory on purpose.
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { emptyState
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
progressCount,
|
|
20
|
-
progressSummary,
|
|
21
|
-
todoGlyphChar,
|
|
22
|
-
todoGlyphRole,
|
|
23
|
-
todoLabel,
|
|
24
|
-
todoLabelRole,
|
|
25
|
-
todoLabelText,
|
|
26
|
-
todoRailLines,
|
|
27
|
-
type TodoRailInput,
|
|
28
|
-
} from "./format.ts";
|
|
29
|
-
import { currentTodo } from "./state.ts";
|
|
30
|
-
import type { Todo } from "./types.ts";
|
|
31
|
-
|
|
32
|
-
/** Widget key used with `ctx.ui.setWidget()`. */
|
|
33
|
-
export const WIDGET_KEY = "todo-widget";
|
|
15
|
+
import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
16
|
+
import { dockListFrame, screenHint, type ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
17
|
+
import { clip } from "../../../lib/tui/columns.ts";
|
|
18
|
+
import { clampScroll, fitRows, formatRange, isCloseKey, navigateScroll, wheelDelta } from "../../../lib/tui/cursor.ts";
|
|
19
|
+
import { emptyState } from "../../../lib/tui/rows.ts";
|
|
20
|
+
import { titleRule } from "../../../lib/tui/rules.ts";
|
|
21
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
22
|
+
import { progressSummary, todoRow } from "../format.ts";
|
|
23
|
+
import type { Todo } from "../types.ts";
|
|
34
24
|
|
|
35
25
|
/** Items the `/todos` screen shows when the terminal height is unknown. */
|
|
36
26
|
const SCREEN_DEFAULT_ITEMS = 12;
|
|
37
27
|
|
|
38
|
-
/** One indented item line, clipped to `width`. */
|
|
39
|
-
function todoRow(todo: Todo, theme: Theme, width: number): string {
|
|
40
|
-
return listRow(theme, width, {
|
|
41
|
-
glyph: todoGlyphChar(todo),
|
|
42
|
-
glyphRole: todoGlyphRole(todo),
|
|
43
|
-
label: todoLabelText(todo),
|
|
44
|
-
labelRole: todoLabelRole(todo),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* One-line widget summary: progress plus the current item, or a completion note
|
|
50
|
-
* when nothing is open. Label-first and glyph-free, matching the `goal` and
|
|
51
|
-
* `jobs` widgets. Callers clip it to the available width.
|
|
52
|
-
*/
|
|
53
|
-
function todoLine(todos: Todo[], theme: Theme): string {
|
|
54
|
-
const head = `${paint(theme, "accent", "Todos")}${paint(theme, "faint", SEPARATORS.item)}${paint(theme, "faint", progressCount(todos))}`;
|
|
55
|
-
const current = currentTodo(todos);
|
|
56
|
-
if (!current) return `${head} ${paint(theme, "faint", "completed")}`;
|
|
57
|
-
return `${head}${paint(theme, "faint", SEPARATORS.item)}${todoLabel(current, theme)}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Transcript result block: the indented glyph rail, wrapping at the render width. */
|
|
61
|
-
export class TodoResult implements Component {
|
|
62
|
-
private input: TodoRailInput;
|
|
63
|
-
private theme: Theme;
|
|
64
|
-
|
|
65
|
-
constructor(input: TodoRailInput, theme: Theme) {
|
|
66
|
-
this.input = input;
|
|
67
|
-
this.theme = theme;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Update in place so the transcript can reuse this component across renders. */
|
|
71
|
-
setInput(input: TodoRailInput, theme: Theme): void {
|
|
72
|
-
this.input = input;
|
|
73
|
-
this.theme = theme;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
invalidate(): void {}
|
|
77
|
-
|
|
78
|
-
render(width: number): string[] {
|
|
79
|
-
// The leading blank line separates the rail from the call header, matching
|
|
80
|
-
// the built-ins' header/output gap.
|
|
81
|
-
return ["", ...todoRailLines(this.input, this.theme, Math.max(1, width))];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Persistent one-line widget shown above the editor while the list has work. */
|
|
86
|
-
export class TodoWidget implements Component {
|
|
87
|
-
constructor(
|
|
88
|
-
private readonly todos: Todo[],
|
|
89
|
-
private readonly theme: Theme,
|
|
90
|
-
) {}
|
|
91
|
-
|
|
92
|
-
invalidate(): void {}
|
|
93
|
-
|
|
94
|
-
render(width: number): string[] {
|
|
95
|
-
if (this.todos.length === 0) return [];
|
|
96
|
-
return [truncateToWidth(todoLine(this.todos, this.theme), Math.max(1, width), "…")];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
28
|
/** Dismissible, scrollable list opened by `/todos`; reads the live list on each render. */
|
|
101
29
|
export class TodoListComponent implements Component {
|
|
102
30
|
private scrollTop = 0;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The persistent one-line todo widget, shown above the editor while the list
|
|
3
|
+
* has work.
|
|
4
|
+
*
|
|
5
|
+
* It is a rail, not a screen: `runtime.syncWidget` sets it whenever the list
|
|
6
|
+
* changes, through a synchronous factory Pi calls at paint time, so this module
|
|
7
|
+
* stays on the registration path. `WIDGET_KEY` lives here for the same reason —
|
|
8
|
+
* the runtime and the tests read it without touching a component.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { truncateToWidth, type Component } from "@earendil-works/pi-tui";
|
|
13
|
+
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
14
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
15
|
+
import { progressCount, todoLabel } from "./format.ts";
|
|
16
|
+
import { currentTodo } from "./state.ts";
|
|
17
|
+
import type { Todo } from "./types.ts";
|
|
18
|
+
|
|
19
|
+
/** Widget key used with `ctx.ui.setWidget()`. */
|
|
20
|
+
export const WIDGET_KEY = "todo-widget";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One-line widget summary: progress plus the current item, or a completion note
|
|
24
|
+
* when nothing is open. Label-first and glyph-free, matching the `goal` and
|
|
25
|
+
* `jobs` widgets. Callers clip it to the available width.
|
|
26
|
+
*/
|
|
27
|
+
function todoLine(todos: Todo[], theme: Theme): string {
|
|
28
|
+
const head = `${paint(theme, "accent", "Todos")}${paint(theme, "faint", SEPARATORS.item)}${paint(theme, "faint", progressCount(todos))}`;
|
|
29
|
+
const current = currentTodo(todos);
|
|
30
|
+
if (!current) return `${head} ${paint(theme, "faint", "completed")}`;
|
|
31
|
+
return `${head}${paint(theme, "faint", SEPARATORS.item)}${todoLabel(current, theme)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Persistent one-line widget shown above the editor while the list has work. */
|
|
35
|
+
export class TodoWidget implements Component {
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly todos: Todo[],
|
|
38
|
+
private readonly theme: Theme,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
invalidate(): void {}
|
|
42
|
+
|
|
43
|
+
render(width: number): string[] {
|
|
44
|
+
if (this.todos.length === 0) return [];
|
|
45
|
+
return [truncateToWidth(todoLine(this.todos, this.theme), Math.max(1, width), "…")];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import { withPolledScreen } from "../../lib/screen-session.ts";
|
|
12
11
|
import { formatWatchList, shortLabel } from "./format.ts";
|
|
13
12
|
import type { WatchRuntime } from "./runtime.ts";
|
|
14
|
-
import { WatchListComponent } from "./tui.ts";
|
|
15
13
|
|
|
16
14
|
/** How often the open screen re-reads live state. */
|
|
17
15
|
const REFRESH_MS = 500;
|
|
@@ -92,6 +90,10 @@ export function registerCommands(pi: ExtensionAPI, runtime: WatchRuntime): void
|
|
|
92
90
|
return;
|
|
93
91
|
}
|
|
94
92
|
|
|
93
|
+
// The dock screen and its poll loop are the TUI's: the listing above and
|
|
94
|
+
// registration load neither.
|
|
95
|
+
const { withPolledScreen } = await import("../../lib/screen-session.ts");
|
|
96
|
+
const { WatchListComponent } = await import("./view/watch-screen.ts");
|
|
95
97
|
await withPolledScreen(
|
|
96
98
|
pi,
|
|
97
99
|
ctx,
|
|
@@ -14,7 +14,7 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
14
14
|
import { boundedTail, sanitizeLine } from "../../lib/format.ts";
|
|
15
15
|
import { clip, clipLabel, labelLine } from "../../lib/tui/columns.ts";
|
|
16
16
|
import { GLYPHS, SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
17
|
-
import { byRunningFirst } from "../../lib/tui/rows.ts";
|
|
17
|
+
import { byRunningFirst, statusRow, type StatusRow } from "../../lib/tui/rows.ts";
|
|
18
18
|
import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
|
|
19
19
|
import type { PendingMatch, WatchRecord, WatchStatus } from "./types.ts";
|
|
20
20
|
|
|
@@ -23,12 +23,39 @@ export function compileRegex(pattern: string, caseSensitive: boolean): RegExp {
|
|
|
23
23
|
return new RegExp(pattern, caseSensitive ? "" : "i");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* A watcher's pattern as `/<pattern>/<flags>`, with the wake badge appended:
|
|
28
|
+
* `/pat/`, `/pat/i`, `/pat/ · wake`, `/pat/i · wake`.
|
|
29
|
+
*/
|
|
30
|
+
export function formatPattern(watch: Pick<WatchRecord, "pattern" | "caseSensitive" | "wake">): string {
|
|
31
|
+
const flags = watch.caseSensitive ? "" : "i";
|
|
32
|
+
const badge = watch.wake ? `${SEPARATORS.item}wake` : "";
|
|
33
|
+
return `/${sanitizeLine(watch.pattern)}/${flags}${badge}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
/** What a watcher reads: a path or a command, clipped for display. */
|
|
27
37
|
export function sourceLabel(watch: WatchRecord): string {
|
|
28
38
|
if (watch.source.kind === "file") return clipLabel(watch.source.path ?? "");
|
|
29
39
|
return clipLabel(`$ ${watch.source.command ?? ""}`);
|
|
30
40
|
}
|
|
31
41
|
|
|
42
|
+
/** One watcher row: marker, status glyph, id, label, and a right-aligned count. */
|
|
43
|
+
export function watchRow(
|
|
44
|
+
watch: WatchRecord,
|
|
45
|
+
theme: Theme,
|
|
46
|
+
width: number,
|
|
47
|
+
options: Pick<StatusRow, "selected" | "marker"> = {},
|
|
48
|
+
): string {
|
|
49
|
+
return statusRow(theme, width, {
|
|
50
|
+
...options,
|
|
51
|
+
glyph: statusGlyphChar(watch.status),
|
|
52
|
+
glyphRole: statusRole(watch.status),
|
|
53
|
+
label: `${watch.id} ${shortLabel(watch)}`,
|
|
54
|
+
labelRole: options.selected ? "accent" : "label",
|
|
55
|
+
trailing: `${watch.matchCount}`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
/** A watcher's label, falling back to its source. */
|
|
33
60
|
export function shortLabel(watch: WatchRecord): string {
|
|
34
61
|
return clipLabel(watch.label || sourceLabel(watch));
|
|
@@ -62,11 +89,14 @@ export function statusGlyphChar(status: WatchStatus): string {
|
|
|
62
89
|
}
|
|
63
90
|
}
|
|
64
91
|
|
|
65
|
-
/** Short status word, including an exit code or error when present. */
|
|
92
|
+
/** Short status word, including an exit code, the signal that ended it, or an error when present. */
|
|
66
93
|
export function statusWord(watch: WatchRecord): string {
|
|
67
94
|
if (watch.status === "error" && watch.error) return `error (${watch.error})`;
|
|
68
|
-
if (watch.status === "exited"
|
|
69
|
-
return `exited (${watch.exitCode})`;
|
|
95
|
+
if (watch.status === "exited") {
|
|
96
|
+
if (typeof watch.exitCode === "number") return `exited (${watch.exitCode})`;
|
|
97
|
+
// Node reports a signalled child with a null code and the signal that ended it, so the
|
|
98
|
+
// code alone would leave a killed command looking like a clean exit.
|
|
99
|
+
if (watch.signal) return `exited (${watch.signal})`;
|
|
70
100
|
}
|
|
71
101
|
return watch.status;
|
|
72
102
|
}
|
|
@@ -96,7 +126,7 @@ export function formatWatchStatus(watch: WatchRecord, now = Date.now()): string
|
|
|
96
126
|
const lines = [
|
|
97
127
|
`${watch.id}: ${statusWord(watch)}`,
|
|
98
128
|
`source: ${sourceLabel(watch)}`,
|
|
99
|
-
`pattern:
|
|
129
|
+
`pattern: ${formatPattern(watch)}`,
|
|
100
130
|
];
|
|
101
131
|
if (watch.exclude.length > 0)
|
|
102
132
|
lines.push(`exclude: ${watch.exclude.map((item) => `/${sanitizeLine(item)}/`).join(" ")}`);
|
|
@@ -174,9 +204,8 @@ export function formatWatchOutcomeLine(watch: WatchRecord, theme: Theme, now = D
|
|
|
174
204
|
/** Unstyled, sanitized detail lines for the `/watch` focus pane. */
|
|
175
205
|
export function formatWatchDetail(watch: WatchRecord, now = Date.now()): string[] {
|
|
176
206
|
const age = Math.max(0, (watch.endedAt ?? now) - watch.startedAt);
|
|
177
|
-
const flags = `${watch.caseSensitive ? "" : "i"}${watch.wake ? " · wake" : ""}`;
|
|
178
207
|
return [
|
|
179
|
-
sanitizeLine(`${sourceLabel(watch)} ·
|
|
208
|
+
sanitizeLine(`${sourceLabel(watch)} · ${formatPattern(watch)}`),
|
|
180
209
|
sanitizeLine(
|
|
181
210
|
`${statusWord(watch)} · ${watch.linesRead} lines · ${watch.matchCount} matches · ${Math.round(age / 1000)}s`,
|
|
182
211
|
),
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `WatchResult` transcript block: the `watch` tool's list-or-matches result.
|
|
3
|
+
*
|
|
4
|
+
* It wraps the shared `TailResult` kit over the `/watch`-style row rail, so the
|
|
5
|
+
* tool's `renderResult` stays a few lines and every row is drawn by `watchRow`
|
|
6
|
+
* in `format.ts`. The `/watch` screen draws the same rows through that helper;
|
|
7
|
+
* this module is on the registration path, and the screen is not.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { Component } from "@earendil-works/pi-tui";
|
|
12
|
+
import { TailResult, type TailResultInput } from "../../lib/tui/tool.ts";
|
|
13
|
+
import { watchRow } from "./format.ts";
|
|
14
|
+
import type { WatchRecord } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
/** Input for the transcript result: a themed watcher list or a matched-line tail. */
|
|
17
|
+
export type WatchResultInput =
|
|
18
|
+
| { kind: "list"; watches: WatchRecord[]; more: number }
|
|
19
|
+
| { kind: "lines"; label: string; lines: string[]; earlier: number };
|
|
20
|
+
|
|
21
|
+
/** Map the watch vocabulary onto the shared transcript tail block. */
|
|
22
|
+
function watchTail(input: WatchResultInput): TailResultInput<WatchRecord> {
|
|
23
|
+
if (input.kind === "lines") {
|
|
24
|
+
return { kind: "lines", lines: input.lines, earlier: input.earlier, earlierLabel: input.label };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
kind: "list",
|
|
28
|
+
rows: input.watches,
|
|
29
|
+
more: input.more,
|
|
30
|
+
rowLine: (watch, width, theme) => watchRow(watch, theme, width, { marker: false }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Transcript result block for the `watch` tool: the shared tail block over the
|
|
36
|
+
* `/watch`-style row rail or the themed matched-line tail. Reused across renders.
|
|
37
|
+
*/
|
|
38
|
+
export class WatchResult implements Component {
|
|
39
|
+
private readonly view: TailResult<WatchRecord>;
|
|
40
|
+
|
|
41
|
+
constructor(input: WatchResultInput, theme: Theme) {
|
|
42
|
+
this.view = new TailResult(watchTail(input), theme);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Update in place so the transcript can reuse this component across renders. */
|
|
46
|
+
setInput(input: WatchResultInput, theme: Theme): void {
|
|
47
|
+
this.view.setInput(watchTail(input), theme);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
invalidate(): void {
|
|
51
|
+
this.view.invalidate();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render(width: number): string[] {
|
|
55
|
+
return this.view.render(width);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -14,7 +14,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
14
14
|
import { resolveEffectiveCwd } from "../../lib/env.ts";
|
|
15
15
|
import { LineSplitter } from "../../lib/lines.ts";
|
|
16
16
|
import type { KillTreeFn, SpawnFn, SpawnedProcess } from "../../lib/spawn.ts";
|
|
17
|
-
import { createChildSupervisor } from "
|
|
17
|
+
import { createChildSupervisor, KILL_GRACE_MS } from "../../lib/child.ts";
|
|
18
18
|
import { DEFAULT_CONFIG, loadConfig, type WatchConfig } from "./config.ts";
|
|
19
19
|
import { sanitizeUntrustedLine } from "../../lib/format.ts";
|
|
20
20
|
import { defaultInterval } from "../../lib/timer.ts";
|
|
@@ -24,8 +24,6 @@ import type { PendingMatch, WatchMatch, WatchRecord, WatchSource } from "./types
|
|
|
24
24
|
|
|
25
25
|
/** Largest slice read from a file per tick. */
|
|
26
26
|
const MAX_CHUNK = 1024 * 1024;
|
|
27
|
-
/** Grace before a stopped command escalates to SIGKILL; `child.ts` defaults the same. */
|
|
28
|
-
const KILL_GRACE_MS = 2000;
|
|
29
27
|
|
|
30
28
|
/** Internal watcher state: the public record plus non-serializable fields. */
|
|
31
29
|
interface Internal {
|
|
@@ -277,24 +275,29 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
|
|
|
277
275
|
};
|
|
278
276
|
|
|
279
277
|
const startCommand = (w: Internal): void => {
|
|
280
|
-
const error = children.start(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
w.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
278
|
+
const error = children.start(
|
|
279
|
+
w,
|
|
280
|
+
w.source.command ?? "",
|
|
281
|
+
{ cwd: w.source.cwd ?? process.cwd() },
|
|
282
|
+
{
|
|
283
|
+
onData: (chunk) => consume(w, chunk, now()),
|
|
284
|
+
onSettled: (outcome) => {
|
|
285
|
+
if (w.status !== "running") return;
|
|
286
|
+
if (outcome.kind === "closed") {
|
|
287
|
+
flushPending(w, now());
|
|
288
|
+
w.status = "exited";
|
|
289
|
+
w.exitCode = outcome.code;
|
|
290
|
+
w.signal = outcome.signal;
|
|
291
|
+
} else {
|
|
292
|
+
w.status = "error";
|
|
293
|
+
w.error = outcome.message;
|
|
294
|
+
}
|
|
295
|
+
w.endedAt = now();
|
|
296
|
+
ui.paint();
|
|
297
|
+
if (runningCount() === 0) stopClock();
|
|
298
|
+
},
|
|
296
299
|
},
|
|
297
|
-
|
|
300
|
+
);
|
|
298
301
|
if (error === undefined) return;
|
|
299
302
|
w.status = "error";
|
|
300
303
|
w.error = error;
|
|
@@ -313,8 +316,10 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
|
|
|
313
316
|
|
|
314
317
|
const add = (opts: AddWatchOptions, ctx: ExtensionContext): WatchRecord => {
|
|
315
318
|
if (!config.enabled) throw new Error("watch is disabled.");
|
|
316
|
-
if (
|
|
317
|
-
throw new Error(
|
|
319
|
+
if (runningCount() >= config.maxWatchers) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`at most ${config.maxWatchers} live watcher${config.maxWatchers === 1 ? "" : "s"}; stop one first.`,
|
|
322
|
+
);
|
|
318
323
|
}
|
|
319
324
|
const base = resolveEffectiveCwd(ctx.cwd);
|
|
320
325
|
const source: WatchSource = opts.path
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import { sanitizeLine } from "../../lib/format.ts";
|
|
12
11
|
import { errorResult, textResult } from "../../lib/tool-result.ts";
|
|
13
12
|
import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
|
|
14
13
|
import { callLine, detailsOrLine, rejectionLine } from "../../lib/tui/tool.ts";
|
|
@@ -17,6 +16,7 @@ import { paint } from "../../lib/tui/tokens.ts";
|
|
|
17
16
|
import {
|
|
18
17
|
compareWatches,
|
|
19
18
|
formatCallText,
|
|
19
|
+
formatPattern,
|
|
20
20
|
formatTail,
|
|
21
21
|
formatWatchList,
|
|
22
22
|
formatWatchOutcomeLine,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./format.ts";
|
|
26
26
|
import type { WatchRuntime } from "./runtime.ts";
|
|
27
27
|
import { WatchParams, normalizeCall, type WatchArgs, type WatchCall } from "./schema.ts";
|
|
28
|
-
import { WatchResult, type WatchResultInput } from "./
|
|
28
|
+
import { WatchResult, type WatchResultInput } from "./render.ts";
|
|
29
29
|
import type { WatchDetails } from "./types.ts";
|
|
30
30
|
|
|
31
31
|
export const TOOL_NAME = "watch";
|
|
@@ -81,9 +81,9 @@ export function registerTools(pi: ExtensionAPI, runtime: WatchRuntime): void {
|
|
|
81
81
|
},
|
|
82
82
|
ctx as ExtensionContext,
|
|
83
83
|
);
|
|
84
|
-
const
|
|
84
|
+
const pattern = formatPattern(watch);
|
|
85
85
|
return textResult(
|
|
86
|
-
`Watching ${watch.id}: ${sourceLabel(watch)} →
|
|
86
|
+
`Watching ${watch.id}: ${sourceLabel(watch)} → ${pattern}\nRead with \`watch matches ${watch.id}\` or \`watch logs ${watch.id}\`.`,
|
|
87
87
|
{ action: "add", watch } satisfies WatchDetails,
|
|
88
88
|
);
|
|
89
89
|
}
|
|
@@ -1,87 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `/watch` screen: a dismissible list with a focused detail pane and a
|
|
3
|
+
* matches view, drawn as a `DockListScreen`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the shared `
|
|
5
|
+
* It reads live state through injected closures and tracks the selection by id
|
|
6
|
+
* so a live re-sort cannot move the cursor, through the shared `IdCursor` and
|
|
7
|
+
* `LinePane`. The frame, the navigation keys, the y/N confirmation, and the
|
|
8
|
+
* matches pane come from the shared `DockListScreen`, so this module owns only
|
|
9
|
+
* the watch vocabulary: the rows, the focus block, the counts, the hints, and
|
|
10
|
+
* what the verbs do. Matched lines arrive already sanitized by the runtime.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* verbs do. Matched lines arrive already sanitized by the runtime, so no
|
|
13
|
-
* untrusted escape sequence reaches the terminal here.
|
|
12
|
+
* Full-screen surfaces live under `view/` and load on first use: the `/watch`
|
|
13
|
+
* command imports this module behind its TUI branch, so registration never
|
|
14
|
+
* pays for it.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import type {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { paint } from "../../lib/tui/tokens.ts";
|
|
23
|
-
import { compareWatches, formatWatchDetail, shortLabel, statusGlyphChar, statusRole } from "./format.ts";
|
|
24
|
-
import type { WatchRecord } from "./types.ts";
|
|
25
|
-
|
|
26
|
-
/** One watcher row: marker, status glyph, id, label, and a right-aligned count. */
|
|
27
|
-
function watchRow(
|
|
28
|
-
watch: WatchRecord,
|
|
29
|
-
theme: Theme,
|
|
30
|
-
width: number,
|
|
31
|
-
options: Pick<StatusRow, "selected" | "marker"> = {},
|
|
32
|
-
): string {
|
|
33
|
-
return statusRow(theme, width, {
|
|
34
|
-
...options,
|
|
35
|
-
glyph: statusGlyphChar(watch.status),
|
|
36
|
-
glyphRole: statusRole(watch.status),
|
|
37
|
-
label: `${watch.id} ${shortLabel(watch)}`,
|
|
38
|
-
labelRole: options.selected ? "accent" : "label",
|
|
39
|
-
trailing: `${watch.matchCount}`,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Input for the transcript result: a themed watcher list or a matched-line tail. */
|
|
44
|
-
export type WatchResultInput =
|
|
45
|
-
| { kind: "list"; watches: WatchRecord[]; more: number }
|
|
46
|
-
| { kind: "lines"; label: string; lines: string[]; earlier: number };
|
|
47
|
-
|
|
48
|
-
/** Map the watch vocabulary onto the shared transcript tail block. */
|
|
49
|
-
function watchTail(input: WatchResultInput): TailResultInput<WatchRecord> {
|
|
50
|
-
if (input.kind === "lines") {
|
|
51
|
-
return { kind: "lines", lines: input.lines, earlier: input.earlier, earlierLabel: input.label };
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
kind: "list",
|
|
55
|
-
rows: input.watches,
|
|
56
|
-
more: input.more,
|
|
57
|
-
rowLine: (watch, width, theme) => watchRow(watch, theme, width, { marker: false }),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Transcript result block for the `watch` tool: the shared tail block over the
|
|
63
|
-
* `/watch`-style row rail or the themed matched-line tail. Reused across renders.
|
|
64
|
-
*/
|
|
65
|
-
export class WatchResult implements Component {
|
|
66
|
-
private readonly view: TailResult<WatchRecord>;
|
|
67
|
-
|
|
68
|
-
constructor(input: WatchResultInput, theme: Theme) {
|
|
69
|
-
this.view = new TailResult(watchTail(input), theme);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Update in place so the transcript can reuse this component across renders. */
|
|
73
|
-
setInput(input: WatchResultInput, theme: Theme): void {
|
|
74
|
-
this.view.setInput(watchTail(input), theme);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
invalidate(): void {
|
|
78
|
-
this.view.invalidate();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
render(width: number): string[] {
|
|
82
|
-
return this.view.render(width);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
18
|
+
import type { ScreenHint, ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
19
|
+
import { DockListScreen, type DetailContents } from "../../../lib/tui/dock-screen.ts";
|
|
20
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
21
|
+
import { compareWatches, formatWatchDetail, shortLabel, watchRow } from "../format.ts";
|
|
22
|
+
import type { WatchRecord } from "../types.ts";
|
|
85
23
|
|
|
86
24
|
export interface WatchListCallbacks {
|
|
87
25
|
/** Current matched lines for a watcher, or undefined when unknown. */
|
|
@@ -157,8 +95,8 @@ export class WatchListComponent extends DockListScreen<WatchRecord> {
|
|
|
157
95
|
"Esc close",
|
|
158
96
|
"↑/↓ or j/k select",
|
|
159
97
|
{ text: "Enter or l matches", color: "accent" },
|
|
160
|
-
{ text: "x or K stop", color: "
|
|
161
|
-
{ text: "d clear stopped", color: "
|
|
98
|
+
{ text: "x or K stop", color: "warn" },
|
|
99
|
+
{ text: "d clear stopped", color: "warn" },
|
|
162
100
|
"PgUp/PgDn",
|
|
163
101
|
];
|
|
164
102
|
}
|
|
@@ -28,9 +28,9 @@ import { charLength } from "../text.ts";
|
|
|
28
28
|
import { oneLine } from "../transcript.ts";
|
|
29
29
|
import { createPageCache, type PageCache } from "./cache.ts";
|
|
30
30
|
import type { WebFetchConfig } from "./config.ts";
|
|
31
|
-
import {
|
|
31
|
+
import type { Passage } from "./find.ts";
|
|
32
32
|
import { formatMatches, formatPage } from "./format.ts";
|
|
33
|
-
import {
|
|
33
|
+
import type { FetchDeps, PageResult } from "./page.ts";
|
|
34
34
|
import { resolveRequest, WebFetchOutput, WebFetchParams, type FetchResponse, type WebFetchArgs } from "./schema.ts";
|
|
35
35
|
import type { FetchRequest } from "./types.ts";
|
|
36
36
|
import { loadFetchConfig } from "../config.ts";
|
|
@@ -66,6 +66,10 @@ async function loadPage(
|
|
|
66
66
|
deps: FetchDeps,
|
|
67
67
|
cache: PageCache,
|
|
68
68
|
): Promise<LoadedPage> {
|
|
69
|
+
// The page loader is the call's, not registration's: the schema and the
|
|
70
|
+
// renderers above never need it, so a listing loads neither it nor its
|
|
71
|
+
// extract/ssrf/PDF subtree.
|
|
72
|
+
const { runFetch } = await import("./page.ts");
|
|
69
73
|
const cacheable = config.cacheEnabled && request.cacheable && !request.refresh;
|
|
70
74
|
const cachedEntry = cacheable ? cache.get(url, config.cacheTtlMs) : undefined;
|
|
71
75
|
if (cachedEntry) return { page: cachedEntry, cached: true, fetchedAt: cachedEntry.fetchedAt };
|
|
@@ -83,7 +87,10 @@ async function loadPage(
|
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/** Build the response entry and model-facing text for one fetched page. */
|
|
86
|
-
function pageResult(
|
|
90
|
+
async function pageResult(
|
|
91
|
+
loaded: LoadedPage,
|
|
92
|
+
request: FetchRequest,
|
|
93
|
+
): Promise<{ response: FetchResponse; text: string }> {
|
|
87
94
|
const { page, cached, fetchedAt } = loaded;
|
|
88
95
|
const finding = request.find.length > 0;
|
|
89
96
|
|
|
@@ -93,6 +100,7 @@ function pageResult(loaded: LoadedPage, request: FetchRequest): { response: Fetc
|
|
|
93
100
|
let matchesTruncated = false;
|
|
94
101
|
|
|
95
102
|
if (finding) {
|
|
103
|
+
const { findPassages } = await import("./find.ts");
|
|
96
104
|
// Request one extra so a full list proves more hits exist; only the cap is shown.
|
|
97
105
|
const found = findPassages(page.text, request.find, {
|
|
98
106
|
mode: request.mode,
|
|
@@ -211,7 +219,7 @@ export function registerFetchTool(pi: ExtensionAPI, deps: FetchToolDeps = {}): v
|
|
|
211
219
|
let text: string;
|
|
212
220
|
try {
|
|
213
221
|
const loaded = await loadPage(request.url, request, config, signal, fetchDeps, cache);
|
|
214
|
-
({ response: page, text } = pageResult(loaded, request));
|
|
222
|
+
({ response: page, text } = await pageResult(loaded, request));
|
|
215
223
|
} catch (error) {
|
|
216
224
|
const message = error instanceof Error ? error.message : String(error);
|
|
217
225
|
({ response: page, text } = fetchErrorResult(request.url, request, message));
|