@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
|
@@ -4,18 +4,30 @@
|
|
|
4
4
|
* Each session owns one registry file per project, but a job is still owned by
|
|
5
5
|
* the session that started it. To tell a live owner from a dead one — so a new
|
|
6
6
|
* session can adopt a dead peer's records instead of reaping them blindly —
|
|
7
|
-
* each session writes a small heartbeat marker (its session id, pid,
|
|
8
|
-
* last-seen time) beside the registry
|
|
7
|
+
* each session writes a small heartbeat marker (its session id, pid, the process's
|
|
8
|
+
* start time, and last-seen time) beside the registry. Adoption reads those
|
|
9
9
|
* markers, so a live peer's jobs are left alone and a dead peer's are taken
|
|
10
10
|
* over.
|
|
11
11
|
*
|
|
12
|
+
* The heartbeat only ticks while a job runs, because that is when the repaint
|
|
13
|
+
* clock runs: a session that has finished its work stops writing, and its marker
|
|
14
|
+
* ages. That is why age alone is not death — the rule is the roster's own
|
|
15
|
+
* (`lib/session-roster.ts`'s `live` / `quiet` / `gone`), read here over a
|
|
16
|
+
* marker, and *only* `gone` lets another session take anything over. A marker
|
|
17
|
+
* whose process is alive is evidence of an owner, not litter, so pruning keeps
|
|
18
|
+
* it; the start token is what tells an idle session apart from a reused pid.
|
|
19
|
+
*
|
|
12
20
|
* Markers are best-effort: a missing directory or an unwritable file degrades
|
|
13
|
-
* to "
|
|
14
|
-
* (`
|
|
21
|
+
* to "no marker", which reads as an owner that is gone — the conservative
|
|
22
|
+
* default for adoption. The rule is pure (`sessionStateOf`) so it is unit-tested
|
|
23
|
+
* without real processes, and `createOwnership` is that rule wired to one
|
|
24
|
+
* runtime's directory, clock, and seams: the state of a peer, this session's own
|
|
25
|
+
* marker, and the pruning of the markers that are left.
|
|
15
26
|
*/
|
|
16
27
|
|
|
17
28
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
29
|
import { join } from "node:path";
|
|
30
|
+
import { type RosterSeams, type RosterState, rosterStateOf } from "../../lib/session-roster.ts";
|
|
19
31
|
import { sessionMarkerPath } from "./paths.ts";
|
|
20
32
|
|
|
21
33
|
/** Prefix shared by every marker file, so log/registry files are never parsed. */
|
|
@@ -27,6 +39,14 @@ export interface SessionMarker {
|
|
|
27
39
|
sessionId: string;
|
|
28
40
|
/** Host process pid, checked for liveness before trusting the timestamp. */
|
|
29
41
|
pid: number;
|
|
42
|
+
/**
|
|
43
|
+
* That process's start time, when the platform can say.
|
|
44
|
+
*
|
|
45
|
+
* A pid that is alive may be a different program than the one the marker named, and
|
|
46
|
+
* nothing cheaper than the start time tells them apart — so this is what keeps a
|
|
47
|
+
* reused pid from reading as an idle session forever.
|
|
48
|
+
*/
|
|
49
|
+
startToken?: string;
|
|
30
50
|
/** Millisecond timestamp of the last heartbeat. */
|
|
31
51
|
updatedAt: number;
|
|
32
52
|
}
|
|
@@ -39,16 +59,33 @@ function parseMarker(raw: unknown): SessionMarker | undefined {
|
|
|
39
59
|
if (typeof marker.sessionId !== "string" || typeof marker.pid !== "number" || typeof marker.updatedAt !== "number") {
|
|
40
60
|
return undefined;
|
|
41
61
|
}
|
|
42
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
sessionId: marker.sessionId,
|
|
64
|
+
pid: marker.pid,
|
|
65
|
+
...(typeof marker.startToken === "string" && marker.startToken !== "" ? { startToken: marker.startToken } : {}),
|
|
66
|
+
updatedAt: marker.updatedAt,
|
|
67
|
+
};
|
|
43
68
|
}
|
|
44
69
|
|
|
45
70
|
/** Write (or refresh) a session's marker. Failures are non-fatal for the caller. */
|
|
46
|
-
export function touchSessionMarker(
|
|
71
|
+
export function touchSessionMarker(
|
|
72
|
+
dir: string,
|
|
73
|
+
sessionId: string,
|
|
74
|
+
pid: number,
|
|
75
|
+
now: number,
|
|
76
|
+
startToken?: string,
|
|
77
|
+
): void {
|
|
47
78
|
try {
|
|
48
79
|
mkdirSync(dir, { recursive: true });
|
|
49
80
|
const path = sessionMarkerPath(dir, sessionId);
|
|
50
81
|
const tmp = `${path}.tmp`;
|
|
51
|
-
|
|
82
|
+
const marker: SessionMarker = {
|
|
83
|
+
sessionId,
|
|
84
|
+
pid,
|
|
85
|
+
...(startToken === undefined ? {} : { startToken }),
|
|
86
|
+
updatedAt: now,
|
|
87
|
+
};
|
|
88
|
+
writeFileSync(tmp, JSON.stringify(marker), "utf-8");
|
|
52
89
|
renameSync(tmp, path);
|
|
53
90
|
} catch {
|
|
54
91
|
// A marker is best-effort; a missing one reads as a dead owner.
|
|
@@ -70,7 +107,7 @@ export function removeSessionMarker(dir: string, sessionId: string): void {
|
|
|
70
107
|
const path = sessionMarkerPath(dir, sessionId);
|
|
71
108
|
if (existsSync(path)) unlinkSync(path);
|
|
72
109
|
} catch {
|
|
73
|
-
// Best-effort; a leftover marker
|
|
110
|
+
// Best-effort; a leftover marker is reaped when its session is gone.
|
|
74
111
|
}
|
|
75
112
|
}
|
|
76
113
|
|
|
@@ -94,23 +131,99 @@ export function listSessionMarkers(dir: string): SessionMarker[] {
|
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
134
|
+
* What a marker says about its session: `live`, `quiet`, or `gone`.
|
|
135
|
+
*
|
|
136
|
+
* The rule is the roster's, asked over a marker, so `session-bus`, the board's page, and
|
|
137
|
+
* this extension cannot answer "is that session around" three ways. A dead pid is `gone`
|
|
138
|
+
* however fresh the heartbeat; a live pid with an aged heartbeat is `quiet`, which is the
|
|
139
|
+
* ordinary state of a session that has finished its jobs; and a pid that has since been
|
|
140
|
+
* reused is `gone` once the start token disagrees. A missing marker is `gone`: no file is
|
|
141
|
+
* no evidence of an owner.
|
|
99
142
|
*/
|
|
100
|
-
export function
|
|
143
|
+
export function sessionStateOf(
|
|
101
144
|
marker: SessionMarker | undefined,
|
|
102
145
|
now: number,
|
|
103
146
|
ttlMs: number,
|
|
104
|
-
|
|
105
|
-
):
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
147
|
+
seams: RosterSeams = {},
|
|
148
|
+
): RosterState {
|
|
149
|
+
if (marker === undefined) return "gone";
|
|
150
|
+
return rosterStateOf(
|
|
151
|
+
{
|
|
152
|
+
lastSeen: marker.updatedAt,
|
|
153
|
+
// A marker naming no usable process is judged by its heartbeat alone, which is the
|
|
154
|
+
// roster's answer for a record that cannot say which process owns it.
|
|
155
|
+
...(marker.pid > 0 ? { pid: marker.pid } : {}),
|
|
156
|
+
...(marker.startToken === undefined ? {} : { startToken: marker.startToken }),
|
|
157
|
+
},
|
|
158
|
+
now,
|
|
159
|
+
ttlMs,
|
|
160
|
+
seams,
|
|
161
|
+
);
|
|
109
162
|
}
|
|
110
163
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Delete the markers of sessions that are gone.
|
|
166
|
+
*
|
|
167
|
+
* A marker whose process is alive is kept, including one whose heartbeat has aged: that is
|
|
168
|
+
* what every idle session's marker looks like, and removing it would let a peer adopt a
|
|
169
|
+
* session that is still there. Only a session that has really gone — a dead pid, or a
|
|
170
|
+
* reused one — loses its marker.
|
|
171
|
+
*/
|
|
172
|
+
export function pruneSessionMarkers(dir: string, now: number, ttlMs: number, seams: RosterSeams = {}): void {
|
|
113
173
|
for (const marker of listSessionMarkers(dir)) {
|
|
114
|
-
if (
|
|
174
|
+
if (sessionStateOf(marker, now, ttlMs, seams) === "gone") removeSessionMarker(dir, marker.sessionId);
|
|
115
175
|
}
|
|
116
176
|
}
|
|
177
|
+
|
|
178
|
+
/** What the ownership rule needs from the runtime it serves. */
|
|
179
|
+
export interface OwnershipOptions {
|
|
180
|
+
/** The directory the markers sit in. */
|
|
181
|
+
directory(): string;
|
|
182
|
+
/** This session's id; it is never `gone`. */
|
|
183
|
+
sessionId(): string;
|
|
184
|
+
/** The clock the rule is read against. */
|
|
185
|
+
now(): number;
|
|
186
|
+
/** How old a beat may be before the marker's pid is re-checked for reuse. */
|
|
187
|
+
ttlMs(): number;
|
|
188
|
+
/** Whether a pid is still running. */
|
|
189
|
+
alive(pid: number): boolean;
|
|
190
|
+
/** That pid's start time, when the platform can say. */
|
|
191
|
+
startToken(pid: number): string | undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** One runtime's view of the sessions that own jobs. */
|
|
195
|
+
export interface Ownership {
|
|
196
|
+
/** `live`, `quiet`, or `gone` for the session that owns a job. */
|
|
197
|
+
state(owner: string): RosterState;
|
|
198
|
+
/** Write this session's marker, naming its process and that process's start time. */
|
|
199
|
+
touch(): void;
|
|
200
|
+
/** Drop the markers of sessions that are gone, keeping a live or a quiet one. */
|
|
201
|
+
prune(): void;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Wire the rule to one runtime: what a peer's session is, this session's own marker, and
|
|
206
|
+
* the pruning of the markers nobody owns any more.
|
|
207
|
+
*/
|
|
208
|
+
export function createOwnership(options: OwnershipOptions): Ownership {
|
|
209
|
+
const seams = (): RosterSeams => ({ alive: options.alive, token: options.startToken });
|
|
210
|
+
// This process's token cannot change while the session runs, and on a platform without
|
|
211
|
+
// `ps` the ask costs a failed spawn, so it is asked once and kept, missing or not.
|
|
212
|
+
let token: string | undefined;
|
|
213
|
+
let asked = false;
|
|
214
|
+
const ownToken = (): string | undefined => {
|
|
215
|
+
if (!asked) {
|
|
216
|
+
asked = true;
|
|
217
|
+
token = options.startToken(process.pid);
|
|
218
|
+
}
|
|
219
|
+
return token;
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
state: (owner) =>
|
|
223
|
+
owner === options.sessionId()
|
|
224
|
+
? "live"
|
|
225
|
+
: sessionStateOf(readSessionMarker(options.directory(), owner), options.now(), options.ttlMs(), seams()),
|
|
226
|
+
touch: () => touchSessionMarker(options.directory(), options.sessionId(), process.pid, options.now(), ownToken()),
|
|
227
|
+
prune: () => pruneSessionMarkers(options.directory(), options.now(), options.ttlMs(), seams()),
|
|
228
|
+
};
|
|
229
|
+
}
|
package/extensions/job/store.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Durable job storage: the on-disk registry that outlives a session.
|
|
3
3
|
*
|
|
4
|
-
* The live
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The live table (the `Job` map) stays in the runtime and the handles stay in
|
|
5
|
+
* `child.ts`, because process events mutate both; this owns the parts that
|
|
6
|
+
* survive a session — the registry directory, the id counter, the atomic write of
|
|
7
|
+
* this session's own registry file, and the trusted artifact-path rules. Each
|
|
8
|
+
* session writes only its own file, so peers never race.
|
|
9
9
|
*
|
|
10
10
|
* Persistence is best-effort: an unwritable registry must never break a job that
|
|
11
11
|
* is otherwise running fine.
|
|
@@ -101,8 +101,8 @@ export function createJobStore(initialDir = registryDirFor(process.cwd())): JobS
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/** Strip runtime-only fields for persistence. */
|
|
105
|
-
export function toRecord(job:
|
|
104
|
+
/** Strip runtime-only fields for persistence: the record, with or without a live handle. */
|
|
105
|
+
export function toRecord(job: JobRecord & { owned?: boolean }): JobRecord {
|
|
106
106
|
const { owned: _owned, ...record } = job;
|
|
107
107
|
return record;
|
|
108
108
|
}
|
package/extensions/job/tools.ts
CHANGED
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { errorResult, textResult } from "../../lib/tool-result.ts";
|
|
13
13
|
import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
callLine,
|
|
16
|
+
detailsOrLine,
|
|
17
|
+
elapsedSuffix,
|
|
18
|
+
isTailResult,
|
|
19
|
+
rejectionLine,
|
|
20
|
+
syncElapsedTimer,
|
|
21
|
+
TailResult,
|
|
22
|
+
} from "../../lib/tui/tool.ts";
|
|
15
23
|
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
16
24
|
import { paint } from "../../lib/tui/tokens.ts";
|
|
17
25
|
import {
|
|
@@ -26,8 +34,8 @@ import {
|
|
|
26
34
|
import type { JobsRuntime } from "./runtime.ts";
|
|
27
35
|
import { JobParams, normalizeCall, type JobArgs, type JobCall } from "./schema.ts";
|
|
28
36
|
import { toRecord } from "./store.ts";
|
|
29
|
-
import {
|
|
30
|
-
import type { JobDetails } from "./types.ts";
|
|
37
|
+
import { jobTail, type JobResultInput } from "./render.ts";
|
|
38
|
+
import type { JobDetails, JobRecord } from "./types.ts";
|
|
31
39
|
|
|
32
40
|
export const TOOL_NAME = "job";
|
|
33
41
|
|
|
@@ -65,7 +73,7 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
|
|
|
65
73
|
try {
|
|
66
74
|
switch (call.action) {
|
|
67
75
|
case "start": {
|
|
68
|
-
const job = runtime.start(
|
|
76
|
+
const job = await runtime.start(
|
|
69
77
|
{
|
|
70
78
|
command: call.command,
|
|
71
79
|
cwd: call.cwd,
|
|
@@ -137,7 +145,7 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
|
|
|
137
145
|
case "kill": {
|
|
138
146
|
const before = runtime.get(call.id);
|
|
139
147
|
if (!before) return errorResult("kill", `no job "${call.id}".`);
|
|
140
|
-
const job = runtime.kill(call.id, call.signal);
|
|
148
|
+
const job = await runtime.kill(call.id, call.signal);
|
|
141
149
|
if (!job) return errorResult("kill", `no job "${call.id}".`);
|
|
142
150
|
const wasRunning = before.status === "running";
|
|
143
151
|
return textResult(
|
|
@@ -224,8 +232,10 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
|
|
|
224
232
|
}
|
|
225
233
|
const shown = options.expanded ? all : all.slice(Math.max(0, all.length - RESULT_ROWS));
|
|
226
234
|
const input: JobResultInput = { kind: "logs", lines: shown, earlier: all.length - shown.length };
|
|
227
|
-
const
|
|
228
|
-
|
|
235
|
+
const previous = context.lastComponent;
|
|
236
|
+
const tail = jobTail(input);
|
|
237
|
+
const view = isTailResult<JobRecord>(previous) ? previous : new TailResult<JobRecord>(tail, theme);
|
|
238
|
+
view.setInput(tail, theme);
|
|
229
239
|
return view;
|
|
230
240
|
}
|
|
231
241
|
|
|
@@ -242,8 +252,10 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
|
|
|
242
252
|
more: ordered.length - shown.length,
|
|
243
253
|
sessionId: details.sessionId,
|
|
244
254
|
};
|
|
245
|
-
const
|
|
246
|
-
|
|
255
|
+
const previous = context.lastComponent;
|
|
256
|
+
const tail = jobTail(input);
|
|
257
|
+
const view = isTailResult<JobRecord>(previous) ? previous : new TailResult<JobRecord>(tail, theme);
|
|
258
|
+
view.setInput(tail, theme);
|
|
247
259
|
return view;
|
|
248
260
|
}
|
|
249
261
|
|
package/extensions/job/types.ts
CHANGED
|
@@ -59,6 +59,53 @@ export interface Job extends JobRecord {
|
|
|
59
59
|
owned: boolean;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export interface StartOptions {
|
|
63
|
+
command: string;
|
|
64
|
+
cwd?: string;
|
|
65
|
+
label?: string;
|
|
66
|
+
wake?: boolean;
|
|
67
|
+
detached?: boolean;
|
|
68
|
+
/** Give the child a stdin pipe, so `send` can write to it while it runs here. */
|
|
69
|
+
stdin?: boolean;
|
|
70
|
+
/** Auto-kill the job after this many milliseconds (SIGTERM, escalating to SIGKILL). */
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface WaitResult {
|
|
75
|
+
job: Job;
|
|
76
|
+
timedOut: boolean;
|
|
77
|
+
cancelled: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A job's log as `logs` returns it: the model-facing render plus the raw tail. */
|
|
81
|
+
export interface LogResult {
|
|
82
|
+
job: Job;
|
|
83
|
+
/** Model-facing render of the tail, including status header/truncation note. */
|
|
84
|
+
text: string;
|
|
85
|
+
/** Sanitized tail lines, for the `/jobs` log pane. */
|
|
86
|
+
lines: string[];
|
|
87
|
+
truncated: boolean;
|
|
88
|
+
/** True when earlier log lines exist beyond the returned window. */
|
|
89
|
+
more: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface SendOptions {
|
|
93
|
+
/** Text written to the job's stdin. */
|
|
94
|
+
input?: string;
|
|
95
|
+
/** Append a newline to `input`; defaults to true. */
|
|
96
|
+
newline?: boolean;
|
|
97
|
+
/** End the job's stdin after writing, so the child sees EOF. */
|
|
98
|
+
eof?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SendResult {
|
|
102
|
+
job: Job;
|
|
103
|
+
/** Characters of `input` written, excluding any newline this call added. */
|
|
104
|
+
sent: number;
|
|
105
|
+
/** True when this call ended the job's stdin. */
|
|
106
|
+
closed: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
62
109
|
/** Registry file contents. One file exists per session. */
|
|
63
110
|
export interface RegistryFile {
|
|
64
111
|
version: 1;
|
|
@@ -1,106 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `/jobs` screen: a dismissible list with a focused detail pane and a log
|
|
3
|
+
* view, drawn as a `DockListScreen`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* It is stateless with respect to time: elapsed values are computed from
|
|
6
|
+
* `Date.now()` at render, so the command can refresh the screen without
|
|
7
|
+
* rebuilding the component. The frame, the navigation keys, the y/N
|
|
8
|
+
* confirmation, and the log pane come from the shared `DockListScreen`, so this
|
|
9
|
+
* module owns only the job vocabulary: the rows, the focus block, the counts,
|
|
10
|
+
* the hints, and what the verbs do.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Log text arrives already sanitized by the runtime, so no untrusted escape
|
|
14
|
-
* sequence reaches the terminal through here.
|
|
12
|
+
* Full-screen surfaces live under `view/` and load on first use: the `/jobs`
|
|
13
|
+
* command imports this module behind its TUI branch, so registration never
|
|
14
|
+
* pays for it. See `docs/extensions/job.md`.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { TailResult, type TailResultInput } from "../../lib/tui/tool.ts";
|
|
25
|
-
import { paint } from "../../lib/tui/tokens.ts";
|
|
26
|
-
import {
|
|
27
|
-
compareJobs,
|
|
28
|
-
elapsedMs,
|
|
29
|
-
formatJobDetail,
|
|
30
|
-
jobCounts,
|
|
31
|
-
shortLabel,
|
|
32
|
-
statusGlyphChar,
|
|
33
|
-
statusRole,
|
|
34
|
-
type JobCounts,
|
|
35
|
-
} from "./format.ts";
|
|
36
|
-
import type { JobRecord } from "./types.ts";
|
|
37
|
-
|
|
38
|
-
/** One job row: selection marker, status glyph, id, label, and a right-aligned
|
|
39
|
-
* elapsed value, tagged `peer` when another session owns it. `statusRow` owns
|
|
40
|
-
* the clipping and the alignment. */
|
|
41
|
-
function jobRow(
|
|
42
|
-
job: JobRecord,
|
|
43
|
-
theme: Theme,
|
|
44
|
-
width: number,
|
|
45
|
-
options: Pick<StatusRow, "selected" | "marker"> & { foreign?: boolean } = {},
|
|
46
|
-
): string {
|
|
47
|
-
const elapsed = formatDuration(elapsedMs(job, Date.now()));
|
|
48
|
-
return statusRow(theme, width, {
|
|
49
|
-
...options,
|
|
50
|
-
glyph: statusGlyphChar(job.status),
|
|
51
|
-
glyphRole: statusRole(job.status),
|
|
52
|
-
label: `${job.id} ${shortLabel(job)}`,
|
|
53
|
-
labelRole: options.selected ? "accent" : "label",
|
|
54
|
-
// The tag rides the right-aligned cell, which survives a narrow terminal.
|
|
55
|
-
trailing: options.foreign ? `${elapsed}${SEPARATORS.item}peer` : elapsed,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Input for the transcript result: a themed job list or a log tail. */
|
|
60
|
-
export type JobResultInput =
|
|
61
|
-
| { kind: "list"; jobs: JobRecord[]; more: number; sessionId?: string }
|
|
62
|
-
| { kind: "logs"; lines: string[]; earlier: number };
|
|
63
|
-
|
|
64
|
-
/** Map the job vocabulary onto the shared transcript tail block. */
|
|
65
|
-
function jobTail(input: JobResultInput): TailResultInput<JobRecord> {
|
|
66
|
-
if (input.kind === "logs") {
|
|
67
|
-
return { kind: "lines", lines: input.lines, earlier: input.earlier, earlierLabel: "lines" };
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
kind: "list",
|
|
71
|
-
rows: input.jobs,
|
|
72
|
-
more: input.more,
|
|
73
|
-
rowLine: (job, width, theme) => {
|
|
74
|
-
const foreign = input.sessionId !== undefined && job.sessionId !== input.sessionId;
|
|
75
|
-
return jobRow(job, theme, width, { marker: false, foreign });
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Transcript result block for the `job` tool: the shared tail block over the
|
|
82
|
-
* `/jobs`-style row rail or the themed log tail. Reused across renders.
|
|
83
|
-
*/
|
|
84
|
-
export class JobResult implements Component {
|
|
85
|
-
private readonly view: TailResult<JobRecord>;
|
|
86
|
-
|
|
87
|
-
constructor(input: JobResultInput, theme: Theme) {
|
|
88
|
-
this.view = new TailResult(jobTail(input), theme);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Update in place so the transcript can reuse this component across renders. */
|
|
92
|
-
setInput(input: JobResultInput, theme: Theme): void {
|
|
93
|
-
this.view.setInput(jobTail(input), theme);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
invalidate(): void {
|
|
97
|
-
this.view.invalidate();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
render(width: number): string[] {
|
|
101
|
-
return this.view.render(width);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
18
|
+
import { sanitizeUntrustedLine } from "../../../lib/format.ts";
|
|
19
|
+
import type { ScreenHint, ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
20
|
+
import { DockListScreen, type DetailContents } from "../../../lib/tui/dock-screen.ts";
|
|
21
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
22
|
+
import { compareJobs, formatJobDetail, jobCounts, type JobCounts, jobRow, shortLabel } from "../format.ts";
|
|
23
|
+
import type { JobRecord } from "../types.ts";
|
|
104
24
|
|
|
105
25
|
export interface JobListCallbacks {
|
|
106
26
|
/** This session's id: a record with another id belongs to a peer session. */
|
|
@@ -187,8 +107,8 @@ export class JobListComponent extends DockListScreen<JobRecord> {
|
|
|
187
107
|
"Esc close",
|
|
188
108
|
"↑/↓ or j/k select",
|
|
189
109
|
{ text: "Enter or l logs", color: "accent" },
|
|
190
|
-
{ text: "d or K kill", color: "
|
|
191
|
-
{ text: "x clear finished", color: "
|
|
110
|
+
{ text: "d or K kill", color: "warn" },
|
|
111
|
+
{ text: "x clear finished", color: "warn" },
|
|
192
112
|
"PgUp/PgDn",
|
|
193
113
|
];
|
|
194
114
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `wait` action: waiter bookkeeping, and the promise a caller waits on.
|
|
3
3
|
*
|
|
4
4
|
* `wait` registers a resolver per job; a job's completion (or a kill/clear)
|
|
5
5
|
* wakes every waiter for that id. Kept separate from the runtime so the map and
|
|
6
6
|
* its cleanup rules are testable on their own and the runtime holds one field.
|
|
7
|
+
* The race the caller sees — the record, the timeout, or an abort — is here for
|
|
8
|
+
* the same reason: it is bookkeeping, not a decision about a record.
|
|
7
9
|
*/
|
|
8
10
|
|
|
11
|
+
/** Default `wait` timeout, for a caller that names none. */
|
|
12
|
+
export const DEFAULT_WAIT_MS = 30_000;
|
|
13
|
+
|
|
14
|
+
/** Why a `wait` stopped waiting; the caller reads the record itself. */
|
|
15
|
+
export interface WaitOutcome {
|
|
16
|
+
timedOut: boolean;
|
|
17
|
+
cancelled: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
/** Resolvers waiting on a job id. */
|
|
10
21
|
export interface Waiters {
|
|
11
22
|
/** Register `resolve` for `id`; returns a function that removes it again. */
|
|
@@ -44,3 +55,35 @@ export function createWaiters(): Waiters {
|
|
|
44
55
|
},
|
|
45
56
|
};
|
|
46
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve when `id` settles, or when the timeout or the abort signal wins.
|
|
61
|
+
*
|
|
62
|
+
* One promise, one cleanup: the timer is cleared, the abort listener removed, and
|
|
63
|
+
* the waiter dropped, whichever of the three settles it.
|
|
64
|
+
*/
|
|
65
|
+
export function waitFor(
|
|
66
|
+
waiters: Waiters,
|
|
67
|
+
id: string,
|
|
68
|
+
options: { timeoutMs?: number; signal?: AbortSignal } = {},
|
|
69
|
+
): Promise<WaitOutcome> {
|
|
70
|
+
return new Promise<WaitOutcome>((resolve) => {
|
|
71
|
+
let settled = false;
|
|
72
|
+
let remove = (): void => {};
|
|
73
|
+
const finish = (timedOut: boolean, cancelled: boolean): void => {
|
|
74
|
+
if (settled) return;
|
|
75
|
+
settled = true;
|
|
76
|
+
if (timer) clearTimeout(timer);
|
|
77
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
78
|
+
remove();
|
|
79
|
+
resolve({ timedOut, cancelled });
|
|
80
|
+
};
|
|
81
|
+
const resolveWaiter = (): void => finish(false, false);
|
|
82
|
+
const onAbort = (): void => finish(false, true);
|
|
83
|
+
const timer = setTimeout(() => finish(true, false), options.timeoutMs ?? DEFAULT_WAIT_MS);
|
|
84
|
+
|
|
85
|
+
remove = waiters.add(id, resolveWaiter);
|
|
86
|
+
if (options.signal?.aborted) onAbort();
|
|
87
|
+
else options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -10,12 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { withPolledScreen } from "../../lib/screen-session.ts";
|
|
14
13
|
import { messageOf } from "./errors.ts";
|
|
15
14
|
import { formatHealth, memoryHealth } from "./doctor.ts";
|
|
16
15
|
import { formatNotice, scopeLabel } from "./format.ts";
|
|
17
16
|
import type { MemoryRuntime } from "./runtime.ts";
|
|
18
|
-
import { MemoryPickerComponent } from "./tui.ts";
|
|
19
17
|
import { MEMORY_SCOPES, type MemoryScope } from "./types.ts";
|
|
20
18
|
|
|
21
19
|
/** How often the open picker re-checks the stores for an external edit. */
|
|
@@ -55,6 +53,10 @@ async function openPicker(pi: ExtensionAPI, runtime: MemoryRuntime, ctx: Extensi
|
|
|
55
53
|
return;
|
|
56
54
|
}
|
|
57
55
|
|
|
56
|
+
// The dock screen and its poll loop are the TUI's: the plain listing above and
|
|
57
|
+
// registration load neither.
|
|
58
|
+
const { withPolledScreen } = await import("../../lib/screen-session.ts");
|
|
59
|
+
const { MemoryPickerComponent } = await import("./view/memory-picker.ts");
|
|
58
60
|
await withPolledScreen(
|
|
59
61
|
pi,
|
|
60
62
|
ctx,
|
|
@@ -25,17 +25,17 @@ import {
|
|
|
25
25
|
type TuiMouseEvent,
|
|
26
26
|
type TuiMouseEventResult,
|
|
27
27
|
} from "@earendil-works/pi-tui";
|
|
28
|
-
import { sanitizeLine } from "
|
|
29
|
-
import { ConfirmState } from "
|
|
30
|
-
import { fitRows, formatRange, isCloseKey, ListCursor, navigateList, scrollList } from "
|
|
31
|
-
import { dockListFrame, editorTheme, screenHint, type ViewportRowsSource } from "
|
|
32
|
-
import { clip } from "
|
|
33
|
-
import { emptyState, listRow } from "
|
|
34
|
-
import { titleRule } from "
|
|
35
|
-
import { paint } from "
|
|
36
|
-
import { messageOf } from "
|
|
37
|
-
import { scopeLabel, type MemoryEntries } from "
|
|
38
|
-
import type { MemoryScope } from "
|
|
28
|
+
import { sanitizeLine } from "../../../lib/format.ts";
|
|
29
|
+
import { ConfirmState } from "../../../lib/tui/confirm.ts";
|
|
30
|
+
import { fitRows, formatRange, isCloseKey, ListCursor, navigateList, scrollList } from "../../../lib/tui/cursor.ts";
|
|
31
|
+
import { dockListFrame, editorTheme, screenHint, type ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
32
|
+
import { clip } from "../../../lib/tui/columns.ts";
|
|
33
|
+
import { emptyState, listRow } from "../../../lib/tui/rows.ts";
|
|
34
|
+
import { titleRule } from "../../../lib/tui/rules.ts";
|
|
35
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
36
|
+
import { messageOf } from "../errors.ts";
|
|
37
|
+
import { scopeLabel, type MemoryEntries } from "../format.ts";
|
|
38
|
+
import type { MemoryScope } from "../types.ts";
|
|
39
39
|
|
|
40
40
|
/** Note limit enforced by the `memory` tool; the picker mirrors it so a save is never rejected late. */
|
|
41
41
|
const MAX_NOTE_CHARS = 500;
|
|
@@ -521,7 +521,7 @@ export class MemoryPickerComponent implements Component {
|
|
|
521
521
|
"Esc close",
|
|
522
522
|
{ text: "Enter/e edit", color: "accent" },
|
|
523
523
|
{ text: "a add", color: "accent" },
|
|
524
|
-
{ text: "d forget", color: "
|
|
524
|
+
{ text: "d forget", color: "warn" },
|
|
525
525
|
"m merge",
|
|
526
526
|
"p move",
|
|
527
527
|
"s/Tab scope",
|