@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
package/lib/board/session.ts
CHANGED
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One store per working directory, opened on first use and kept for the session:
|
|
5
5
|
* a tool call is frequent and the repository lookup shells out to git, so the
|
|
6
|
-
* answer is remembered per `cwd`.
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
6
|
+
* answer is remembered per `cwd`. A failed open is not: one bad read must not
|
|
7
|
+
* outlive the call that hit it, so the next call asks again. The identity is the
|
|
8
|
+
* same pair a card records — the session id Pi knows, and the name the team
|
|
9
|
+
* addresses it by — so the id is what keeps a card held across a rename, and the
|
|
10
|
+
* name is read from the bus record where it is.
|
|
11
|
+
*
|
|
12
|
+
* One session per *host*, not one per extension: Pi loads `board` and `team` with
|
|
13
|
+
* separate module caches, so two sessions would mean the seat `/board` sets is
|
|
14
|
+
* invisible to every write `team` makes, and one session would name its own cards
|
|
15
|
+
* two ways.
|
|
9
16
|
*
|
|
10
17
|
* The store and its cache live in this closure, not in a module variable: Pi loads
|
|
11
18
|
* each extension with an isolated module cache, so module state would be one copy
|
|
@@ -14,92 +21,233 @@
|
|
|
14
21
|
|
|
15
22
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
16
23
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { resolveEffectiveCwd } from "../env.ts";
|
|
26
|
+
import { plansDirFor } from "../plans/paths.ts";
|
|
27
|
+
import { repoKeyFor } from "../repo-key.ts";
|
|
28
|
+
import {
|
|
29
|
+
agentLabel,
|
|
30
|
+
defaultLabel,
|
|
31
|
+
normalizeLabel,
|
|
32
|
+
presetNameFor,
|
|
33
|
+
sessionIdOf,
|
|
34
|
+
sessionNameOf,
|
|
35
|
+
} from "../session-label.ts";
|
|
36
|
+
import { BUS_DIR_NAME, readRoster, type RosterSession, rosterStateOf } from "../session-roster.ts";
|
|
37
|
+
import type { JoinResult } from "../session-tool.ts";
|
|
20
38
|
import type { Actor } from "./rules.ts";
|
|
21
39
|
import { type BoardStore, openBoard } from "./store.ts";
|
|
22
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Where this session stands on the team, as the one place that keeps it.
|
|
43
|
+
*
|
|
44
|
+
* Two surfaces ask: `board` when it joins on the page or on a claim, and `team` for
|
|
45
|
+
* `/team join`. It is a standing rather than a flag because both need to *say* what
|
|
46
|
+
* happened — `/board status` names the label and whether mail is listened for — and
|
|
47
|
+
* because a failure has to be asked again while a success must not be. A failure is
|
|
48
|
+
* kept so a line can say why, and `ok` is what a caller reads to decide.
|
|
49
|
+
*/
|
|
50
|
+
export interface TeamStanding {
|
|
51
|
+
/** What the last join did, or undefined when this session has not asked. */
|
|
52
|
+
last(): JoinResult | undefined;
|
|
53
|
+
/** Record what an attempt did; a failed one is kept, and asked again. */
|
|
54
|
+
record(result: JoinResult): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
23
57
|
/** What a tool, a page, and a round need from the session, without any of them owning it. */
|
|
24
58
|
export interface BoardSession {
|
|
25
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* The board for `cwd`'s repository.
|
|
61
|
+
*
|
|
62
|
+
* Opened once and kept, unless the open failed: a rejection is dropped so the next call
|
|
63
|
+
* asks again, the rule `lib/lazy.ts` states for a load it owns.
|
|
64
|
+
*/
|
|
26
65
|
store(cwd: string): Promise<BoardStore>;
|
|
27
|
-
/**
|
|
28
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The session a write is made as: its team name, and the id a card keeps.
|
|
68
|
+
*
|
|
69
|
+
* Three answers, in order: the seat while this session serves the page; else the name
|
|
70
|
+
* the bus already records for it, so `/team join core-claims` is what its cards and
|
|
71
|
+
* its notes carry and a claim does not rename it back; else a preset teammate name
|
|
72
|
+
* drawn from the id, or `team-agent-<id>` once every preset name is held by a live
|
|
73
|
+
* peer. A host that reports no id has no record to read, so it keeps the readable
|
|
74
|
+
* chain instead. One name on every surface is the point: the card, the bus record,
|
|
75
|
+
* and the roster say the same word.
|
|
76
|
+
*
|
|
77
|
+
* Read rather than remembered, because a join is not this session's event alone: the
|
|
78
|
+
* label can arrive through `/team join`, through the bus's own `join`, or on the record
|
|
79
|
+
* a `/resume` inherits, and a name cached at `session_start` would miss all three.
|
|
80
|
+
*/
|
|
81
|
+
who(ctx: ExtensionContext, cwd: string): Promise<Actor>;
|
|
29
82
|
/** The clock, so a test drives a deadline instead of waiting on one. */
|
|
30
83
|
now(): number;
|
|
31
84
|
/**
|
|
32
|
-
* Where this repository's session bus files its records, for the
|
|
85
|
+
* Where this repository's session bus files its records, for the roster.
|
|
33
86
|
*
|
|
34
87
|
* A path, not a dependency: the board reads those files when they are there and
|
|
35
88
|
* behaves exactly as before when they are not, which is what keeps the page working
|
|
36
|
-
* with `session-bus` unloaded.
|
|
89
|
+
* with `session-bus` unloaded. Three callers read it — the page's snapshot, `/team`'s
|
|
90
|
+
* round, and the tool's rollup, which folds the owners a bus has no record of — and all
|
|
91
|
+
* of them want the same directory. Resolved once per working directory, and asked again
|
|
92
|
+
* when the store it needs failed, because a failure is no more this cache's to keep.
|
|
37
93
|
*/
|
|
38
94
|
busDir(cwd: string): Promise<string>;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
95
|
+
/**
|
|
96
|
+
* This repository's plan store, where a card's plan file has to live.
|
|
97
|
+
*
|
|
98
|
+
* The board records the artifact a card is executed against, so it has to answer the
|
|
99
|
+
* question `plan` answers: which directory that store is. One key rule — `repoKeyFor`
|
|
100
|
+
* over the effective cwd — and the unkeyed branch outside a repository, so a card cannot
|
|
101
|
+
* name a plan the plan tool would refuse to read. Resolved once per working directory,
|
|
102
|
+
* with a failed resolution as unremembered as a failed store.
|
|
103
|
+
*/
|
|
104
|
+
planDir(cwd: string): Promise<string>;
|
|
105
|
+
/** The seat this session serves the page as, or undefined when it is not serving. */
|
|
106
|
+
serving(): string | undefined;
|
|
107
|
+
/** Serve the page under `seat`, or stand it down when the page stops. */
|
|
108
|
+
serve(seat: string | undefined): void;
|
|
109
|
+
/** Where this session stands on the team, so a claim joins once and status can say. */
|
|
110
|
+
team: TeamStanding;
|
|
43
111
|
}
|
|
44
112
|
|
|
45
113
|
/** Test seams and the one place a caller may redirect the store. */
|
|
46
114
|
export interface BoardSessionOptions {
|
|
47
115
|
now?: () => number;
|
|
48
116
|
agentDir?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Test seam: the store open itself, so a suite can fail one and watch the next call ask
|
|
119
|
+
* again. The product supplies nothing, and `store` is the only caller.
|
|
120
|
+
*/
|
|
121
|
+
open?: (cwd: string) => Promise<BoardStore>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Where this repository's session bus files its records.
|
|
126
|
+
*
|
|
127
|
+
* The board's store carries the repository key and `session-bus` files under the same one
|
|
128
|
+
* — `join(getAgentDir(), BUS_DIR_NAME, repo.key)` in `session-bus/runtime.ts` — so the two
|
|
129
|
+
* name one directory from one fact. A key read back off the board's own path would make
|
|
130
|
+
* the board's directory layout load-bearing for the roster, and regrouping those files
|
|
131
|
+
* would leave every peer reading as no record at all. It takes the key and nothing else,
|
|
132
|
+
* so there is no directory here to read one off.
|
|
133
|
+
*/
|
|
134
|
+
export function busDirFor(agentDir: string, store: Pick<BoardStore, "key">): string {
|
|
135
|
+
return join(agentDir, BUS_DIR_NAME, store.key);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The labels live peers other than `id` hold, lowercased: the names a draw must skip.
|
|
140
|
+
*
|
|
141
|
+
* The bus's own rule, not a stricter one: a quiet or crashed record's label is free at
|
|
142
|
+
* join, so it is free to draw too, and a repository's history cannot use the pool up.
|
|
143
|
+
*/
|
|
144
|
+
function liveLabels(roster: readonly RosterSession[] | undefined, id: string, now: number): Set<string> {
|
|
145
|
+
const held = new Set<string>();
|
|
146
|
+
for (const record of roster ?? []) {
|
|
147
|
+
if (record.id !== id && rosterStateOf(record, now) === "live") held.add(record.label.toLowerCase());
|
|
148
|
+
}
|
|
149
|
+
return held;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The key one host's session is kept under.
|
|
154
|
+
*
|
|
155
|
+
* `Symbol.for` is the global registry, so the copy of this module in `board`'s cache
|
|
156
|
+
* and the copy in `team`'s agree on the key — a plain `Symbol()` would be two keys on
|
|
157
|
+
* one host, which is the drift this reuse exists to stop.
|
|
158
|
+
*/
|
|
159
|
+
const SESSION_KEY = Symbol.for("@gavin-hu/my-pi-agent/board-session");
|
|
160
|
+
|
|
161
|
+
/** A host with this module's key on it. */
|
|
162
|
+
type BoardHost = ExtensionAPI & { [SESSION_KEY]?: BoardSession };
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* One load per key, remembered once it has settled — a rejection is not.
|
|
166
|
+
*
|
|
167
|
+
* A keyed cache is what the three stores below need: two callers on one directory share one
|
|
168
|
+
* promise, so the second call neither opens a second store nor shells out to git again. What
|
|
169
|
+
* must not be shared is a failure — a config read or the agent directory can fail once and
|
|
170
|
+
* answer the next time — so the entry is dropped in its rejection handler, and `lib/lazy.ts`
|
|
171
|
+
* is the same rule for a load with no key.
|
|
172
|
+
*/
|
|
173
|
+
function rememberLoad<T>(cache: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T> {
|
|
174
|
+
const cached = cache.get(key);
|
|
175
|
+
if (cached !== undefined) return cached;
|
|
176
|
+
const pending = Promise.resolve()
|
|
177
|
+
.then(load)
|
|
178
|
+
.catch((error: unknown) => {
|
|
179
|
+
// The entry is this attempt's: a key is only written when absent, so dropping it
|
|
180
|
+
// cannot drop a newer attempt.
|
|
181
|
+
cache.delete(key);
|
|
182
|
+
throw error;
|
|
183
|
+
});
|
|
184
|
+
cache.set(key, pending);
|
|
185
|
+
return pending;
|
|
49
186
|
}
|
|
50
187
|
|
|
51
188
|
/**
|
|
52
|
-
*
|
|
189
|
+
* The session around one host, built on the first call and shared after it.
|
|
53
190
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
191
|
+
* `board` and `team` both work on the same store and must name a write as the same
|
|
192
|
+
* session, so the first caller's options are the ones built: a second extension joins
|
|
193
|
+
* the session that exists rather than opening a second one beside it.
|
|
57
194
|
*/
|
|
58
195
|
export function openBoardSession(pi: ExtensionAPI, options: BoardSessionOptions = {}): BoardSession {
|
|
196
|
+
const host = pi as BoardHost;
|
|
197
|
+
const shared = host[SESSION_KEY];
|
|
198
|
+
if (shared !== undefined) return shared;
|
|
59
199
|
const stores = new Map<string, Promise<BoardStore>>();
|
|
60
200
|
const busDirs = new Map<string, Promise<string>>();
|
|
61
|
-
|
|
201
|
+
const planDirs = new Map<string, Promise<string>>();
|
|
202
|
+
let seat: string | undefined;
|
|
203
|
+
let standing: JoinResult | undefined;
|
|
62
204
|
const agentDir = options.agentDir;
|
|
205
|
+
/** The real open, unless a suite supplied one: the store a call works on. */
|
|
206
|
+
const open = options.open ?? ((cwd: string) => openBoard(pi, cwd, agentDir === undefined ? {} : { agentDir }));
|
|
63
207
|
const session: BoardSession = {
|
|
64
208
|
store(cwd) {
|
|
65
|
-
|
|
66
|
-
if (cached) return cached;
|
|
67
|
-
const opened = openBoard(pi, cwd, agentDir === undefined ? {} : { agentDir });
|
|
68
|
-
stores.set(cwd, opened);
|
|
69
|
-
return opened;
|
|
209
|
+
return rememberLoad(stores, cwd, () => open(cwd));
|
|
70
210
|
},
|
|
71
|
-
who(ctx, cwd) {
|
|
211
|
+
async who(ctx, cwd) {
|
|
72
212
|
const id = sessionIdOf(ctx);
|
|
73
|
-
|
|
74
|
-
|
|
213
|
+
// The seat is a role rather than a session, so it outranks the record: the page a
|
|
214
|
+
// session serves is the name its cards are read against. A host that reports no id
|
|
215
|
+
// has no record to look for, so it keeps the bus's own readable chain.
|
|
216
|
+
if (seat !== undefined) return id === undefined ? { label: seat } : { id, label: seat };
|
|
217
|
+
if (id === undefined) return { label: defaultLabel(sessionNameOf(ctx), cwd) };
|
|
218
|
+
// The record is the bus's word for this session, so it is read rather than derived;
|
|
219
|
+
// a record that names nothing is no record, and draws like one.
|
|
220
|
+
const roster = await readRoster(await session.busDir(cwd));
|
|
221
|
+
const held = normalizeLabel(roster?.find((entry) => entry.id === id)?.label, "");
|
|
222
|
+
if (held !== "") return { id, label: held };
|
|
223
|
+
// No record, so the team gives this session a preset name: the one its id draws,
|
|
224
|
+
// walked past the names a live peer holds. The fallback keeps a unique name for the
|
|
225
|
+
// day the whole list is taken, so two sessions cannot settle on one word.
|
|
226
|
+
const drawn = presetNameFor(id, liveLabels(roster, id, session.now()));
|
|
227
|
+
return { id, label: drawn ?? agentLabel(id) };
|
|
75
228
|
},
|
|
76
229
|
now: options.now ?? Date.now,
|
|
77
|
-
// The board's own directory is `<agentDir>/board/<key>` and the bus's is
|
|
78
|
-
// `<agentDir>/session-bus/<key>`, both keyed by the same repository, so the key is
|
|
79
|
-
// read back off the board rather than resolved a second time through git.
|
|
80
230
|
busDir(cwd) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.store(cwd)
|
|
85
|
-
.then((store) => join(agentDir ?? getAgentDir(), BUS_DIR_NAME, basename(store.dir)));
|
|
86
|
-
busDirs.set(cwd, resolved);
|
|
87
|
-
return resolved;
|
|
231
|
+
return rememberLoad(busDirs, cwd, () =>
|
|
232
|
+
session.store(cwd).then((store) => busDirFor(agentDir ?? getAgentDir(), store)),
|
|
233
|
+
);
|
|
88
234
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
235
|
+
planDir(cwd) {
|
|
236
|
+
return rememberLoad(planDirs, cwd, () =>
|
|
237
|
+
repoKeyFor(pi, resolveEffectiveCwd(cwd)).then((repo) => plansDirFor(agentDir ?? getAgentDir(), repo)),
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
serving: () => seat,
|
|
241
|
+
serve: (label) => {
|
|
242
|
+
seat = label;
|
|
243
|
+
},
|
|
244
|
+
team: {
|
|
245
|
+
last: () => standing,
|
|
246
|
+
record: (result) => {
|
|
247
|
+
standing = result;
|
|
248
|
+
},
|
|
92
249
|
},
|
|
93
250
|
};
|
|
251
|
+
host[SESSION_KEY] = session;
|
|
94
252
|
return session;
|
|
95
253
|
}
|
|
96
|
-
|
|
97
|
-
/** The session id, or undefined when the host does not report one. */
|
|
98
|
-
function sessionIdOf(ctx: ExtensionContext): string | undefined {
|
|
99
|
-
try {
|
|
100
|
-
const id = ctx.sessionManager.getSessionId();
|
|
101
|
-
return typeof id === "string" && id !== "" ? id : undefined;
|
|
102
|
-
} catch {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sentence for the files a board could not read.
|
|
3
|
+
*
|
|
4
|
+
* A skipped file is not an absent one: a board written by a newer build, or a record this
|
|
5
|
+
* one cannot trust, would otherwise read as "nobody has planned anything" while the work
|
|
6
|
+
* sits on disk. The tool prints this under a listing, and the page prints the same sentence
|
|
7
|
+
* on the footer's hover title — one body, so the two cannot name different files for the
|
|
8
|
+
* same board.
|
|
9
|
+
*
|
|
10
|
+
* Value-only: no I/O, no clock, no store. `Skip` is the store's own record of a refusal,
|
|
11
|
+
* and it arrives already phrased for a reader (`bad JSON`, `version 3`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { plural } from "./round-text.ts";
|
|
15
|
+
import type { Skip } from "./store.ts";
|
|
16
|
+
|
|
17
|
+
/** The files this build refused, named with the reason, or nothing when it read them all. */
|
|
18
|
+
export function skippedText(skipped: readonly Skip[]): string {
|
|
19
|
+
if (skipped.length === 0) return "";
|
|
20
|
+
const files = skipped.map((entry) => `${entry.file} (${entry.why})`).join(", ");
|
|
21
|
+
return `${plural(skipped.length, "file")} this build cannot read: ${files}`;
|
|
22
|
+
}
|