@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/extensions/plan/web.ts
CHANGED
|
@@ -10,37 +10,26 @@
|
|
|
10
10
|
* the socket, and it takes injected seams so tests can run one on `127.0.0.1:0`.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { networkInterfaces } from "node:os";
|
|
14
|
-
import { basename } from "node:path";
|
|
15
13
|
import { openInBrowser } from "../../lib/browser.ts";
|
|
16
14
|
import {
|
|
15
|
+
type HttpMount,
|
|
17
16
|
jsonHeaders,
|
|
18
17
|
type LoopbackRequest,
|
|
19
18
|
type LoopbackReply,
|
|
20
|
-
type
|
|
19
|
+
type MountOutcome,
|
|
20
|
+
type MountSource,
|
|
21
21
|
parseRequestUrl,
|
|
22
|
-
serveLoopback,
|
|
23
22
|
type ServeResponse,
|
|
24
23
|
} from "../../lib/http/index.ts";
|
|
25
|
-
import { fenceLanguage, highlightLines } from "../../lib/highlight.ts";
|
|
26
24
|
import { type ArmTimer, defaultInterval, defaultTimer, type RepeatTimer } from "../../lib/timer.ts";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
MAX_COMMENT_LENGTH,
|
|
32
|
-
MAX_QUOTE_LENGTH,
|
|
33
|
-
normalizeAuthor,
|
|
34
|
-
readFeedback,
|
|
35
|
-
stripFeedback,
|
|
36
|
-
type PlanAnnotation,
|
|
37
|
-
} from "./feedback.ts";
|
|
38
|
-
import { planTitle, type PlanStore, type StoredPlan } from "./plans.ts";
|
|
39
|
-
import { changedSections, planDigest, type SectionDigest } from "./plan-text.ts";
|
|
40
|
-
import { extractPlanSteps } from "./steps.ts";
|
|
25
|
+
import { mintToken } from "../../lib/http/index.ts";
|
|
26
|
+
import { stripFeedback, type PlanAnnotation } from "./feedback.ts";
|
|
27
|
+
import type { PlanStore, StoredPlan } from "./plans.ts";
|
|
28
|
+
import type { SectionDigest } from "./plan-text.ts";
|
|
41
29
|
import type { ReviewCaller } from "./types.ts";
|
|
42
30
|
import { handleReviewRequest, type ReviewContext, type SaveResult } from "./web-router.ts";
|
|
43
|
-
import type
|
|
31
|
+
import { reviewPageData, type ReviewPageData } from "./web-payload.ts";
|
|
32
|
+
import { postReviewerAnnotations, saveOwnerAnnotations } from "./web-write.ts";
|
|
44
33
|
import { createShareSession, type ShareSession } from "./web-share-session.ts";
|
|
45
34
|
import { createStreams } from "./web-streams.ts";
|
|
46
35
|
import { createTokens } from "./web-tokens.ts";
|
|
@@ -68,7 +57,7 @@ export interface PlanReviewSession {
|
|
|
68
57
|
export interface PlanReviewOptions {
|
|
69
58
|
/** Abort signal; aborting closes the session as cancelled. */
|
|
70
59
|
signal?: AbortSignal;
|
|
71
|
-
/** Whether the browser may approve the plan (only the `
|
|
60
|
+
/** Whether the browser may approve the plan (only the `plan` tool's `exit` flow). */
|
|
72
61
|
canApprove?: boolean;
|
|
73
62
|
/** The section digests the previous review showed, so this page can mark changed sections. */
|
|
74
63
|
baseline?: SectionDigest[];
|
|
@@ -100,8 +89,10 @@ export interface PlanWebReviewDeps {
|
|
|
100
89
|
open?: (url: string) => void;
|
|
101
90
|
/** Override the session token (tests). */
|
|
102
91
|
token?: () => string;
|
|
103
|
-
/**
|
|
104
|
-
|
|
92
|
+
/** Where the review's prefix comes from: the session's server, or a suite's own. */
|
|
93
|
+
mount: MountSource;
|
|
94
|
+
/** The Pi session asking, so the prefix lands on this session's socket. */
|
|
95
|
+
session: () => string;
|
|
105
96
|
/** Override the closed-tab grace; the default leaves a reload time to reconnect. */
|
|
106
97
|
dropGraceMs?: number;
|
|
107
98
|
/**
|
|
@@ -140,14 +131,11 @@ const DROP_GRACE_MS = 1500;
|
|
|
140
131
|
*/
|
|
141
132
|
const PLAN_POLL_MS = 1000;
|
|
142
133
|
|
|
143
|
-
export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps
|
|
134
|
+
export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps): PlanWebReview {
|
|
144
135
|
const sessions = new Set<PlanReviewSession>();
|
|
145
136
|
// What each running review can do about sharing; the session itself stays the
|
|
146
137
|
// four things a caller awaits and closes.
|
|
147
138
|
const shares = new Map<PlanReviewSession, ShareSession>();
|
|
148
|
-
// Last bound port per plan path, so the same plan keeps one origin across
|
|
149
|
-
// review turns (and its saved browser theme with it).
|
|
150
|
-
const ports = new Map<string, number>();
|
|
151
139
|
|
|
152
140
|
const start = async (
|
|
153
141
|
cwd: string,
|
|
@@ -164,8 +152,8 @@ export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps =
|
|
|
164
152
|
// the name each link has bound.
|
|
165
153
|
const tokens = createTokens(deps.token);
|
|
166
154
|
const token = tokens.mint("owner");
|
|
167
|
-
// The
|
|
168
|
-
|
|
155
|
+
// The review's own path segment: short, opaque, and stable for this review's life.
|
|
156
|
+
const id = mintToken().slice(0, 8);
|
|
169
157
|
let settled = false;
|
|
170
158
|
let resolveDone!: (result: PlanReviewResult) => void;
|
|
171
159
|
const done = new Promise<PlanReviewResult>((resolve) => {
|
|
@@ -239,65 +227,21 @@ export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps =
|
|
|
239
227
|
if (!current) return;
|
|
240
228
|
if (stripFeedback(current.content) !== pageText) notifyPlanChange();
|
|
241
229
|
};
|
|
242
|
-
stopTicker = (deps.tick ?? defaultInterval)(deps.pollMs ?? PLAN_POLL_MS, () => void checkPlanFile());
|
|
243
230
|
|
|
244
231
|
const page = async (caller: ReviewCaller): Promise<ReviewPageData> => {
|
|
245
|
-
const { role } = caller;
|
|
246
232
|
const stored = await refreshPageText();
|
|
247
|
-
// The
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const marks = changedSections(options.baseline, current);
|
|
256
|
-
const outline = marks.flatMap(({ section, changed }) =>
|
|
257
|
-
section.title === ""
|
|
258
|
-
? []
|
|
259
|
-
: [
|
|
260
|
-
{
|
|
261
|
-
id: `s-${section.line}`,
|
|
262
|
-
line: section.line,
|
|
263
|
-
level: section.level,
|
|
264
|
-
text: section.title,
|
|
265
|
-
...(changed ? { changed: true } : {}),
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
);
|
|
269
|
-
// The same step list approval seeds and set_plan_steps indexes, so the page's
|
|
270
|
-
// progress cannot disagree with the todo list.
|
|
271
|
-
const stepList = extractPlanSteps(current);
|
|
272
|
-
// Highlight code blocks once, at the boundary: the client copies only the
|
|
273
|
-
// tok-* spans out of the server-escaped HTML.
|
|
274
|
-
const code: Record<string, string[]> = {};
|
|
275
|
-
for (const block of blocks) {
|
|
276
|
-
if (block.kind !== "code") continue;
|
|
277
|
-
const language = fenceLanguage(fenceInfo(block.lines[0] ?? ""));
|
|
278
|
-
if (language) code[String(block.start)] = highlightLines(codeBody(block), language);
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
// The document's own H1 reads better than the file-name slug, and the
|
|
282
|
-
// slug is still the fallback for a plan that starts with something else.
|
|
283
|
-
title: marks.find((mark) => mark.section.level === 1)?.section.title || planTitle(basename(plan.path)),
|
|
284
|
-
blocks,
|
|
285
|
-
annotations: read.annotations,
|
|
286
|
-
outline,
|
|
287
|
-
steps: {
|
|
288
|
-
done: stepList.filter((step) => step.status === "completed").length,
|
|
289
|
-
total: stepList.length,
|
|
290
|
-
},
|
|
291
|
-
code,
|
|
292
|
-
limits: { comments: MAX_ANNOTATIONS, comment: MAX_COMMENT_LENGTH, quote: MAX_QUOTE_LENGTH },
|
|
233
|
+
// The island is one function of what the page renders: the text, the baseline
|
|
234
|
+
// the previous review left, and who is asking.
|
|
235
|
+
return reviewPageData({
|
|
236
|
+
base,
|
|
237
|
+
path: plan.path,
|
|
238
|
+
content: stored.content,
|
|
239
|
+
baseline: options.baseline,
|
|
240
|
+
caller,
|
|
293
241
|
token,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
...(caller.name === undefined ? {} : { author: caller.name }),
|
|
298
|
-
canApprove: role === "owner" && canApprove,
|
|
299
|
-
share: role === "owner" ? shareSession.state() : undefined,
|
|
300
|
-
};
|
|
242
|
+
canApprove,
|
|
243
|
+
share: () => shareSession.state(),
|
|
244
|
+
});
|
|
301
245
|
};
|
|
302
246
|
|
|
303
247
|
/**
|
|
@@ -309,43 +253,48 @@ export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps =
|
|
|
309
253
|
*/
|
|
310
254
|
const save = async (annotations: PlanAnnotation[], caller: ReviewCaller): Promise<SaveResult> => {
|
|
311
255
|
const { role } = caller;
|
|
312
|
-
const
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
if (role === "owner") {
|
|
319
|
-
savedPlan = await store.saveFeedback(cwd, plan.path, annotations);
|
|
320
|
-
streams.broadcast("comment");
|
|
321
|
-
return { ok: true };
|
|
322
|
-
}
|
|
323
|
-
// The link, not the payload, decides whose markers this post replaces: the name
|
|
324
|
-
// is bound to the token on its first write, and a page that signs another one
|
|
325
|
-
// is a stale tab rather than an authorized writer.
|
|
326
|
-
const claim = claimAuthor({ bound: caller.name, taken: tokens.namesExcept(caller) }, annotations);
|
|
327
|
-
if (!("name" in claim)) return { ok: false, status: claim.status, message: claim.message };
|
|
328
|
-
const own = annotations.filter((annotation) => normalizeAuthor(annotation.author) === claim.name);
|
|
329
|
-
// The saved plan is deliberately not recorded here: a post is not a result
|
|
330
|
-
// the session can return, so there is nothing for a later flush to resolve.
|
|
331
|
-
await store.saveFeedback(cwd, plan.path, own, { author: claim.name });
|
|
332
|
-
caller.name ??= claim.name;
|
|
256
|
+
const write = { store, cwd, path: plan.path, pageText: () => pageText };
|
|
257
|
+
if (role === "owner") {
|
|
258
|
+
const saved = await saveOwnerAnnotations(write, annotations);
|
|
259
|
+
if (!saved.ok) return saved;
|
|
260
|
+
savedPlan = saved.plan;
|
|
333
261
|
streams.broadcast("comment");
|
|
334
|
-
|
|
335
|
-
return { ok: true, author: claim.name };
|
|
336
|
-
} catch (error) {
|
|
337
|
-
return { ok: false, status: 500, message: error instanceof Error ? error.message : String(error) };
|
|
262
|
+
return { ok: true };
|
|
338
263
|
}
|
|
264
|
+
// The saved plan is deliberately not recorded for a reviewer: a post is not a
|
|
265
|
+
// result the session can return, so there is nothing for a later flush to resolve.
|
|
266
|
+
const posted = await postReviewerAnnotations(
|
|
267
|
+
{ ...write, caller, taken: tokens.namesExcept(caller) },
|
|
268
|
+
annotations,
|
|
269
|
+
);
|
|
270
|
+
if (!posted.save.ok) return posted.save;
|
|
271
|
+
caller.name ??= posted.save.author;
|
|
272
|
+
streams.broadcast("comment");
|
|
273
|
+
// The page is told the name its link stored, and the terminal is told a peer wrote.
|
|
274
|
+
if (posted.count > 0) options.onReviewerComment?.(posted.save.author ?? "", posted.count);
|
|
275
|
+
return posted.save;
|
|
339
276
|
};
|
|
340
277
|
|
|
278
|
+
// One review, one prefix on the session's server: the id is the review's, so two
|
|
279
|
+
// reviews of two plans (or one plan twice) never share a namespace, and the origin
|
|
280
|
+
// is the session's single port.
|
|
281
|
+
const base = `/plan/${id}`;
|
|
282
|
+
const outcome: MountOutcome = await deps.mount({
|
|
283
|
+
session: deps.session(),
|
|
284
|
+
prefix: base,
|
|
285
|
+
maxBodyBytes: bodyLimit,
|
|
286
|
+
handler: handleRequest,
|
|
287
|
+
});
|
|
288
|
+
if (outcome.kind === "refused") return undefined;
|
|
289
|
+
const mount: HttpMount = outcome.mount;
|
|
290
|
+
const port = mount.port;
|
|
291
|
+
|
|
341
292
|
// The second listener, its reviewer token, and the state the page and
|
|
342
293
|
// `/plan share` read. It is handed the request handler, the role map, and the
|
|
343
294
|
// frame hooks, so sharing stays one concern instead of five fields here.
|
|
344
295
|
const shareSession = createShareSession({
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
ownerPort: () => currentPort,
|
|
348
|
-
networkInterfaces: deps.networkInterfaces ?? networkInterfaces,
|
|
296
|
+
mount,
|
|
297
|
+
base,
|
|
349
298
|
mintReviewer: () => tokens.mint("reviewer"),
|
|
350
299
|
revokeReviewer: (value) => tokens.revoke(value),
|
|
351
300
|
nameOf: (value) => tokens.get(value)?.name,
|
|
@@ -412,24 +361,11 @@ export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps =
|
|
|
412
361
|
};
|
|
413
362
|
}
|
|
414
363
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
server = await serveLoopback({
|
|
419
|
-
ports: preferred === 0 ? [0] : [preferred, 0],
|
|
420
|
-
maxBodyBytes: bodyLimit,
|
|
421
|
-
handler: handleRequest,
|
|
422
|
-
});
|
|
423
|
-
} catch {
|
|
424
|
-
// The remembered port may have been taken between turns; the fallback above
|
|
425
|
-
// covers that, so reaching here means neither port answered.
|
|
426
|
-
return undefined;
|
|
427
|
-
}
|
|
428
|
-
const port = server.port;
|
|
429
|
-
ports.set(plan.path, port);
|
|
430
|
-
currentPort = port;
|
|
364
|
+
// Armed only now: a review whose listener never bound has no page to keep current,
|
|
365
|
+
// and an interval started for it would outlive the session that failed to start.
|
|
366
|
+
stopTicker = (deps.tick ?? defaultInterval)(deps.pollMs ?? PLAN_POLL_MS, () => void checkPlanFile());
|
|
431
367
|
|
|
432
|
-
const url =
|
|
368
|
+
const url = `${mount.origin}${base}/?token=${token}`;
|
|
433
369
|
let closed = false;
|
|
434
370
|
let abortHandler: (() => void) | undefined;
|
|
435
371
|
const close = async (): Promise<void> => {
|
|
@@ -444,7 +380,7 @@ export function createPlanWebReview(store: PlanStore, deps: PlanWebReviewDeps =
|
|
|
444
380
|
stopTicker();
|
|
445
381
|
streams.clear();
|
|
446
382
|
await shareSession.close();
|
|
447
|
-
await
|
|
383
|
+
await mount.release();
|
|
448
384
|
};
|
|
449
385
|
|
|
450
386
|
const session: PlanReviewSession = { url, port, done, close };
|
|
@@ -14,7 +14,6 @@ import { ASK_USER_QUESTION_TOOL } from "../../lib/tool-names.ts";
|
|
|
14
14
|
import { UNAVAILABLE_TEXT, formatAnswerText, optionSummary, questionHeaders, unavailableResult } from "./answers.ts";
|
|
15
15
|
import { askViaDialogs, type QuestionUI } from "./dialogs.ts";
|
|
16
16
|
import { AskUserQuestionParams, normalizeQuestions, type AskUserQuestionArgs } from "./schema.ts";
|
|
17
|
-
import { askViaTui } from "./tui.ts";
|
|
18
17
|
import type { AskResult } from "./types.ts";
|
|
19
18
|
|
|
20
19
|
export const TOOL_NAME = ASK_USER_QUESTION_TOOL;
|
|
@@ -59,7 +58,12 @@ export function registerTools(pi: ExtensionAPI): void {
|
|
|
59
58
|
// A remote-answered turn falls back to dialogs; only a local TUI turn uses
|
|
60
59
|
// the full-screen questionnaire.
|
|
61
60
|
result = await askHuman(ctx, {
|
|
62
|
-
custom:
|
|
61
|
+
custom: async () => {
|
|
62
|
+
// The questionnaire is the TUI's: a remote or headless answer below loads
|
|
63
|
+
// neither it nor the rails wrapper.
|
|
64
|
+
const { askViaTui } = await import("./view/question-screen.ts");
|
|
65
|
+
return withRailsSuppressed(pi, () => askViaTui(ctx, questions, signal));
|
|
66
|
+
},
|
|
63
67
|
dialogs: () => askViaDialogs(dialogUI(ctx), questions, signal),
|
|
64
68
|
});
|
|
65
69
|
}
|
|
@@ -15,15 +15,15 @@ import {
|
|
|
15
15
|
type TuiMouseEvent,
|
|
16
16
|
type TuiMouseEventResult,
|
|
17
17
|
} from "@earendil-works/pi-tui";
|
|
18
|
-
import { sanitizeLine } from "
|
|
19
|
-
import { flowCells } from "
|
|
20
|
-
import { clampScroll, formatRange, keepVisible, selectionMarker, wheelDelta } from "
|
|
21
|
-
import { editorTheme, screenHint, type ScreenHint } from "
|
|
22
|
-
import { GLYPHS } from "
|
|
23
|
-
import { checkbox } from "
|
|
24
|
-
import { plainRule, titleRule } from "
|
|
25
|
-
import { paint } from "
|
|
26
|
-
import { OTHER_LABEL, cancelledResult, collectAnswers, completedResult, describeSelection } from "
|
|
18
|
+
import { sanitizeLine } from "../../../lib/format.ts";
|
|
19
|
+
import { flowCells } from "../../../lib/tui/columns.ts";
|
|
20
|
+
import { clampScroll, formatRange, keepVisible, selectionMarker, wheelDelta } from "../../../lib/tui/cursor.ts";
|
|
21
|
+
import { editorTheme, screenHint, type ScreenHint } from "../../../lib/tui/chrome.ts";
|
|
22
|
+
import { GLYPHS } from "../../../lib/tui/glyphs.ts";
|
|
23
|
+
import { checkbox } from "../../../lib/tui/rows.ts";
|
|
24
|
+
import { plainRule, titleRule } from "../../../lib/tui/rules.ts";
|
|
25
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
26
|
+
import { OTHER_LABEL, cancelledResult, collectAnswers, completedResult, describeSelection } from "../answers.ts";
|
|
27
27
|
import {
|
|
28
28
|
allAnswered,
|
|
29
29
|
applyCustomText,
|
|
@@ -34,8 +34,8 @@ import {
|
|
|
34
34
|
type Effect,
|
|
35
35
|
type KeyAction,
|
|
36
36
|
type TuiState,
|
|
37
|
-
} from "
|
|
38
|
-
import type { AskResult, Question, Selection } from "
|
|
37
|
+
} from "../tui-state.ts";
|
|
38
|
+
import type { AskResult, Question, Selection } from "../types.ts";
|
|
39
39
|
|
|
40
40
|
type Outcome =
|
|
41
41
|
| { type: "cancelled"; selections: Map<string, Selection> }
|
|
@@ -10,15 +10,14 @@
|
|
|
10
10
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { withRailsSuppressed } from "../../../lib/rails.ts";
|
|
12
12
|
import { formatRewindListText, formatRewindRow } from "../text/format.ts";
|
|
13
|
-
import { openReview } from "./review.ts";
|
|
14
|
-
import { rewindTo } from "./rewind.ts";
|
|
15
13
|
import type { RewindRuntime } from "../state/runtime.ts";
|
|
16
|
-
import { RewindListComponent } from "../view/rewind-tui.ts";
|
|
17
14
|
import { buildRewindPoints, type RewindPoint } from "../state/timeline.ts";
|
|
18
15
|
|
|
19
16
|
/** Pick a point from the timeline, or undefined when the user cancels. */
|
|
20
17
|
async function choosePoint(ctx: ExtensionCommandContext, points: RewindPoint[]): Promise<RewindPoint | undefined> {
|
|
21
18
|
if (ctx.mode === "tui") {
|
|
19
|
+
// The timeline is the TUI's: the picker below and registration load neither.
|
|
20
|
+
const { RewindListComponent } = await import("../view/rewind-tui.ts");
|
|
22
21
|
return ctx.ui.custom<RewindPoint | undefined>(
|
|
23
22
|
(tui, theme, _keybindings, done) =>
|
|
24
23
|
new RewindListComponent({
|
|
@@ -62,6 +61,9 @@ async function openRewind(pi: ExtensionAPI, runtime: RewindRuntime, ctx: Extensi
|
|
|
62
61
|
|
|
63
62
|
const point = await withRailsSuppressed(pi, () => choosePoint(ctx, points));
|
|
64
63
|
if (!point) return;
|
|
64
|
+
// The rewind run and the review gate both load on the verb that needs them: the
|
|
65
|
+
// listing above and registration load neither.
|
|
66
|
+
const { rewindTo } = await import("./rewind.ts");
|
|
65
67
|
await rewindTo(pi, runtime, ctx, root, point);
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -76,6 +78,7 @@ export function registerCommands(pi: ExtensionAPI, runtime: RewindRuntime): void
|
|
|
76
78
|
pi.registerCommand("review", {
|
|
77
79
|
description: "Review the latest turn's changes and revert a file or the whole turn",
|
|
78
80
|
handler: async (_args, ctx) => {
|
|
81
|
+
const { openReview } = await import("./review.ts");
|
|
79
82
|
await openReview(pi, runtime, ctx);
|
|
80
83
|
},
|
|
81
84
|
});
|
|
@@ -24,7 +24,7 @@ import { createOperationQueue } from "../transport/queue.ts";
|
|
|
24
24
|
import { applyRestore, planRestore, type PlanResult, type RestoreInput } from "../transport/restore.ts";
|
|
25
25
|
import type { PathStat } from "../text/paths.ts";
|
|
26
26
|
import { createSnapshot } from "../transport/snapshot.ts";
|
|
27
|
-
import { createStatusChip } from "../
|
|
27
|
+
import { createStatusChip } from "../status.ts";
|
|
28
28
|
import { belongsToRoot, listSnapshots, pruneSnapshots } from "../transport/store.ts";
|
|
29
29
|
import { createTempIndexStore } from "../transport/temp-index.ts";
|
|
30
30
|
import { lastUserEntryId } from "./timeline.ts";
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import { GLYPHS, STATUS_KEYS } from "
|
|
12
|
-
import { paint } from "
|
|
13
|
-
import type { RewindConfig } from "
|
|
14
|
-
import { userMessagesFromBranch } from "
|
|
11
|
+
import { GLYPHS, STATUS_KEYS } from "../../lib/tui/glyphs.ts";
|
|
12
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
13
|
+
import type { RewindConfig } from "./config/config.ts";
|
|
14
|
+
import { userMessagesFromBranch } from "./state/timeline.ts";
|
|
15
15
|
|
|
16
16
|
const STATUS_KEY = STATUS_KEYS.rewind;
|
|
17
17
|
const STATUS_GLYPH = GLYPHS.rewind;
|
|
@@ -193,10 +193,10 @@ export class ReviewListComponent extends ChecklistScreen<ReviewEntry> {
|
|
|
193
193
|
protected hint(width: number): string {
|
|
194
194
|
return screenHint(this.theme, width, [
|
|
195
195
|
"Esc close",
|
|
196
|
-
{ text: "Enter diff", color: "accent" },
|
|
196
|
+
{ text: "Enter diff", color: "accent" as const },
|
|
197
197
|
"Space select",
|
|
198
|
-
{ text: "r revert", color: "
|
|
199
|
-
{ text: "R all", color: "
|
|
198
|
+
{ text: "r revert", color: "warn" },
|
|
199
|
+
{ text: "R all", color: "warn" },
|
|
200
200
|
"a/n all/none",
|
|
201
201
|
]);
|
|
202
202
|
}
|
|
@@ -314,7 +314,7 @@ export class ReviewDiffComponent implements Component {
|
|
|
314
314
|
return screenHint(this.theme, width, [
|
|
315
315
|
"Esc back",
|
|
316
316
|
...(this.options.canRevertHunks ? [{ text: "h hunks", color: "accent" as const }] : []),
|
|
317
|
-
{ text: "r revert file", color: "
|
|
317
|
+
{ text: "r revert file", color: "warn" },
|
|
318
318
|
"j/k scroll",
|
|
319
319
|
"g/G top/end",
|
|
320
320
|
]);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session's one port: the `serve` section of the shared `bytefly.json` (global
|
|
3
|
+
* then project, project wins).
|
|
4
|
+
*
|
|
5
|
+
* `0` — the default — takes the project's own port: its repository key hashed into the
|
|
6
|
+
* band `lib/http/port.ts` holds, so a session that starts again lands where a bookmark
|
|
7
|
+
* already points and two repositories do not compete for one number. A positive value
|
|
8
|
+
* pins the session's server, so every page it serves keeps the address a bookmark
|
|
9
|
+
* already points at. Either way a port another process holds costs the start its
|
|
10
|
+
* OS-chosen fallback — unless it was pinned, which fails the start rather than moving
|
|
11
|
+
* the pages out from under the bookmark.
|
|
12
|
+
*
|
|
13
|
+
* Pin it in the project document when a repository needs a number of its own: a global
|
|
14
|
+
* value is one port for every session.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { clampInteger, loadConfigSection } from "../../lib/config.ts";
|
|
18
|
+
|
|
19
|
+
/** The `serve` section. */
|
|
20
|
+
export interface ServeConfig {
|
|
21
|
+
/** TCP port on `127.0.0.1`; `0` takes the project's port, a positive value pins it. */
|
|
22
|
+
port: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_SERVE_CONFIG: ServeConfig = { port: 0 };
|
|
26
|
+
|
|
27
|
+
/** Validate and clamp a raw config object over the defaults. */
|
|
28
|
+
export function normalizeServeConfig(
|
|
29
|
+
raw: Record<string, unknown> | undefined,
|
|
30
|
+
base: ServeConfig = DEFAULT_SERVE_CONFIG,
|
|
31
|
+
): ServeConfig {
|
|
32
|
+
if (!raw) return base;
|
|
33
|
+
return { port: clampInteger(raw.port, base.port, 0, 65_535) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Load the effective config for a working directory. */
|
|
37
|
+
export function loadServeConfig(cwd: string): ServeConfig {
|
|
38
|
+
return loadConfigSection(cwd, "serve", DEFAULT_SERVE_CONFIG, normalizeServeConfig);
|
|
39
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serve — the one loopback server a session serves its pages on.
|
|
3
|
+
*
|
|
4
|
+
* It registers one bus listener and closes what it opened; it draws no page of its
|
|
5
|
+
* own. `plan`, `board`, and `file-browse` ask for a path prefix on
|
|
6
|
+
* {@link HTTP_MOUNT_CHANNEL} and the registry answers with the session's origin, so
|
|
7
|
+
* a session has one port, one close path, and one chip instead of one socket per
|
|
8
|
+
* extension. Binding happens on the first page that asks, never in the factory, and the
|
|
9
|
+
* port it binds is the project's own number unless `serve.port` pins another — so a
|
|
10
|
+
* bookmarked page URL survives a restart.
|
|
11
|
+
*
|
|
12
|
+
* Load with: pi --extension ./extensions/serve
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { networkInterfaces } from "node:os";
|
|
16
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { isExtensionEnabled } from "../../lib/env.ts";
|
|
18
|
+
import { HTTP_MOUNT_CHANNEL, type MountRequest } from "../../lib/http/mount.ts";
|
|
19
|
+
import { GLYPHS, STATUS_KEYS } from "../../lib/tui/glyphs.ts";
|
|
20
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
21
|
+
import { loadServeConfig } from "./config.ts";
|
|
22
|
+
import { createServeRegistry } from "./registry.ts";
|
|
23
|
+
|
|
24
|
+
export default function serve(pi: ExtensionAPI): void {
|
|
25
|
+
if (!isExtensionEnabled("serve")) return;
|
|
26
|
+
/** Each session's working directory, so its pin is read from its own config. */
|
|
27
|
+
const cwds = new Map<string, string>();
|
|
28
|
+
/** Each session's context, which is where the port chip is drawn. */
|
|
29
|
+
const contexts = new Map<string, ExtensionContext>();
|
|
30
|
+
|
|
31
|
+
/** The directory a session serves: its config and its port both come from it. */
|
|
32
|
+
const cwdOf = (session: string): string => cwds.get(session) ?? process.cwd();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Publish the session's port, or clear the chip with `undefined`. The chip belongs
|
|
36
|
+
* to the server, not to a page: one number, shown while anything is served on it.
|
|
37
|
+
*/
|
|
38
|
+
const setChip = (session: string, port: number | undefined): void => {
|
|
39
|
+
const ctx = contexts.get(session);
|
|
40
|
+
if (ctx === undefined) return;
|
|
41
|
+
try {
|
|
42
|
+
const value =
|
|
43
|
+
port === undefined
|
|
44
|
+
? undefined
|
|
45
|
+
: `${paint(ctx.ui.theme, "ok", GLYPHS.http)} ${paint(ctx.ui.theme, "accent", String(port))}`;
|
|
46
|
+
ctx.ui.setStatus(STATUS_KEYS.http, value);
|
|
47
|
+
} catch {
|
|
48
|
+
// A host without a status line is not a reason to keep a page from starting.
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const registry = createServeRegistry({
|
|
53
|
+
portFor: (session) => loadServeConfig(cwdOf(session)).port,
|
|
54
|
+
pickPort: async (session) => {
|
|
55
|
+
// The repository key is a git answer, so it is paid on the first bind rather than
|
|
56
|
+
// at registration: a session that opens no page never asks for it.
|
|
57
|
+
const [{ repoKeyFor }, { projectPort }] = await Promise.all([
|
|
58
|
+
import("../../lib/repo-key.ts"),
|
|
59
|
+
import("../../lib/http/port.ts"),
|
|
60
|
+
]);
|
|
61
|
+
return projectPort((await repoKeyFor(pi, cwdOf(session))).key);
|
|
62
|
+
},
|
|
63
|
+
interfaces: () => networkInterfaces(),
|
|
64
|
+
onPort: setChip,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
pi.events.on(HTTP_MOUNT_CHANNEL, (data) => {
|
|
68
|
+
const request = data as Partial<MountRequest> | undefined;
|
|
69
|
+
if (request?.spec === undefined) return;
|
|
70
|
+
registry.handle(request as MountRequest);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
74
|
+
const id = ctx.sessionManager.getSessionId();
|
|
75
|
+
cwds.set(id, ctx.cwd);
|
|
76
|
+
contexts.set(id, ctx);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
80
|
+
const id = ctx.sessionManager.getSessionId();
|
|
81
|
+
await registry.closeSession(id);
|
|
82
|
+
cwds.delete(id);
|
|
83
|
+
contexts.delete(id);
|
|
84
|
+
});
|
|
85
|
+
}
|