@gavin-hu/my-pi-agent 0.17.0 → 0.19.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 +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
package/extensions/job/index.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* processes with their output streamed to log files; the status chip shows
|
|
6
6
|
* running and unreported-failure counts, and finished jobs are reported to the
|
|
7
7
|
* model — and shown as a bytefly notice — at the next turn, or by triggering one
|
|
8
|
-
* turn for a `wake` job
|
|
8
|
+
* turn for a `wake` job as soon as the agent is idle, including right after a
|
|
9
|
+
* running turn settles.
|
|
9
10
|
*
|
|
10
11
|
* Load with: pi --extension ./extensions/job
|
|
11
12
|
*/
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
14
15
|
import { isExtensionEnabled } from "../../lib/env.ts";
|
|
15
16
|
import { JOB_FINISHED_CHANNEL, type JobFinishedEvent } from "../../lib/events.ts";
|
|
16
|
-
import { noticeMessage, type NoticeMessage } from "../../lib/notice.ts";
|
|
17
|
+
import { burstText, noticeMessage, type NoticeMessage } from "../../lib/notice.ts";
|
|
17
18
|
import { createPendingReporter, registerPendingChannel } from "../../lib/report.ts";
|
|
18
19
|
import { registerCommands } from "./commands.ts";
|
|
19
20
|
import { formatCompletion, jobOutcome, shortLabel } from "./format.ts";
|
|
@@ -25,8 +26,13 @@ import type { JobRecord } from "./types.ts";
|
|
|
25
26
|
const JOB_CONTEXT_MARKER = "[BACKGROUND JOBS]";
|
|
26
27
|
|
|
27
28
|
function buildContext(jobs: JobRecord[]): string {
|
|
28
|
-
const
|
|
29
|
-
return
|
|
29
|
+
const count = jobs.length;
|
|
30
|
+
return burstText({
|
|
31
|
+
marker: JOB_CONTEXT_MARKER,
|
|
32
|
+
headline: `${count} background job${count === 1 ? "" : "s"} finished`,
|
|
33
|
+
lines: jobs.map((job) => formatCompletion(job)),
|
|
34
|
+
hint: "Run `job logs <id>` for output.",
|
|
35
|
+
});
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
/** The shared notice for pending completions: model text plus renderer details. */
|
|
@@ -66,15 +72,17 @@ export default function jobs(pi: ExtensionAPI, deps: JobsDeps = {}): void {
|
|
|
66
72
|
if (!isExtensionEnabled("job")) return;
|
|
67
73
|
const runtime = deps.runtime ?? createJobsRuntime();
|
|
68
74
|
|
|
69
|
-
// Opt-in wake: a `wake` job finished
|
|
70
|
-
//
|
|
71
|
-
//
|
|
75
|
+
// Opt-in wake: a `wake` job finished. The reporter reports *all* pending
|
|
76
|
+
// completions, not just the waking one, so a job that finished earlier is not
|
|
77
|
+
// silently marked seen and dropped, and it retries a completion that arrived
|
|
78
|
+
// mid-turn once the run settles.
|
|
72
79
|
const reporter = createPendingReporter<JobRecord>({
|
|
73
80
|
wake: (notice) => pi.sendMessage(notice, { triggerTurn: true }),
|
|
74
81
|
pending: () => runtime.pendingCompletions(),
|
|
75
82
|
markReported: (records) => runtime.markReported(records.map((job) => job.id)),
|
|
76
83
|
buildNotice: jobNotice,
|
|
77
84
|
source: "job",
|
|
85
|
+
wakeRequested: () => runtime.pendingCompletions().some((job) => job.wake),
|
|
78
86
|
});
|
|
79
87
|
|
|
80
88
|
registerTools(pi, runtime);
|
|
@@ -74,17 +74,24 @@ export function readMemoryFile(path: string): string {
|
|
|
74
74
|
return readFileSync(path, "utf-8");
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Identity of a store file, for cheap change detection.
|
|
79
|
+
*
|
|
80
|
+
* The stamp is nanoseconds, not milliseconds: an edit that keeps the file's size
|
|
81
|
+
* is otherwise invisible for a whole millisecond, which is long enough for an
|
|
82
|
+
* editor that writes in place. `size` stays a number so the doctor's byte counts
|
|
83
|
+
* read the same.
|
|
84
|
+
*/
|
|
78
85
|
export interface MemoryFileStamp {
|
|
79
|
-
|
|
86
|
+
mtimeNs: bigint;
|
|
80
87
|
size: number;
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/** Freshness stamp for a store file; `undefined` when it is missing or unstattable. */
|
|
84
91
|
export function memoryFileStamp(path: string): MemoryFileStamp | undefined {
|
|
85
92
|
try {
|
|
86
|
-
const info = statSync(path);
|
|
87
|
-
return {
|
|
93
|
+
const info = statSync(path, { bigint: true });
|
|
94
|
+
return { mtimeNs: info.mtimeNs, size: Number(info.size) };
|
|
88
95
|
} catch {
|
|
89
96
|
return undefined;
|
|
90
97
|
}
|
|
@@ -93,7 +100,7 @@ export function memoryFileStamp(path: string): MemoryFileStamp | undefined {
|
|
|
93
100
|
/** Whether two stamps describe the same file state. */
|
|
94
101
|
export function sameStamp(a: MemoryFileStamp | undefined, b: MemoryFileStamp | undefined): boolean {
|
|
95
102
|
if (a === undefined || b === undefined) return a === b;
|
|
96
|
-
return a.
|
|
103
|
+
return a.mtimeNs === b.mtimeNs && a.size === b.size;
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
/** Note text of every bullet line, sanitized to one terminal-safe line. */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* step.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { FENCE, HEADING } from "./
|
|
17
|
+
import { FENCE, HEADING } from "./sections.ts";
|
|
18
18
|
|
|
19
19
|
/** How a block is rendered and grouped. */
|
|
20
20
|
export type PlanBlockKind = "heading" | "paragraph" | "list" | "code" | "quote" | "table" | "hr" | "blank";
|
|
@@ -176,7 +176,7 @@ export function planBlocks(plan: string): PlanBlock[] {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/** The block containing source line `line`, or the nearest block before it. */
|
|
179
|
-
export function blockAt(blocks: PlanBlock[], line: number): PlanBlock | undefined {
|
|
179
|
+
export function blockAt(blocks: readonly PlanBlock[], line: number): PlanBlock | undefined {
|
|
180
180
|
let candidate: PlanBlock | undefined;
|
|
181
181
|
for (const block of blocks) {
|
|
182
182
|
if (block.start <= line && line <= block.end) return block;
|
|
@@ -186,6 +186,27 @@ export function blockAt(blocks: PlanBlock[], line: number): PlanBlock | undefine
|
|
|
186
186
|
return candidate;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* The nearest block with content at or before `line`, else the first one after
|
|
191
|
+
* it: what a comment in blank space belongs to.
|
|
192
|
+
*
|
|
193
|
+
* A blank line is a block, so {@link blockAt} may name one, but a comment cannot
|
|
194
|
+
* hang on nothing: the review page renders no line for a blank block, so an
|
|
195
|
+
* anchor there is one it can never attach a thread to.
|
|
196
|
+
*/
|
|
197
|
+
export function contentBlockAt(blocks: readonly PlanBlock[], line: number): PlanBlock | undefined {
|
|
198
|
+
let before: PlanBlock | undefined;
|
|
199
|
+
for (const block of blocks) {
|
|
200
|
+
const content = block.kind !== "blank";
|
|
201
|
+
if (block.start <= line) {
|
|
202
|
+
if (content) before = block;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (content) return before ?? block;
|
|
206
|
+
}
|
|
207
|
+
return before;
|
|
208
|
+
}
|
|
209
|
+
|
|
189
210
|
/** The source inside a fenced code block, without its opening and closing fence lines. */
|
|
190
211
|
export function codeBody(block: PlanBlock): string[] {
|
|
191
212
|
if (block.kind !== "code") return block.lines.slice();
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
* Slash command for the plan-mode extension.
|
|
3
3
|
*
|
|
4
4
|
* `/plan` toggles plan mode, or enters it and sends an optional task;
|
|
5
|
-
* `Ctrl+Alt+P` toggles too.
|
|
5
|
+
* `Ctrl+Alt+P` toggles too. Four subcommands are reserved: `/plan review [path]`
|
|
6
6
|
* opens a plan in the browser (and sends saved comments to the model as a
|
|
7
|
-
* refinement),
|
|
8
|
-
* Enter opening a
|
|
7
|
+
* refinement), `/plan share [stop]` prints or revokes the link a teammate opens,
|
|
8
|
+
* `/plan list` shows the plans directory, newest first with Enter opening a
|
|
9
|
+
* review, and `/plan cancel` leaves the mode without approving.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
13
|
import { isRemoteTurn } from "../../lib/interaction.ts";
|
|
13
14
|
import { READ_ONLY_SUMMARY } from "../../lib/plan-entry.ts";
|
|
15
|
+
import { qrLines } from "../../lib/qr.ts";
|
|
16
|
+
import { withRailsSuppressed } from "../../lib/rails.ts";
|
|
14
17
|
import { feedbackSummary } from "./feedback.ts";
|
|
15
18
|
import { PlanIndexComponent, formatPlanIndex } from "./plan-index.ts";
|
|
16
19
|
import { runBrowserReview } from "./review.ts";
|
|
@@ -21,6 +24,7 @@ import { createPlanWebReview, type PlanWebReview } from "./web.ts";
|
|
|
21
24
|
const ENABLED_NOTICE = `Plan mode enabled — ${READ_ONLY_SUMMARY}.`;
|
|
22
25
|
const DISABLED_NOTICE = "Plan mode disabled — full access restored.";
|
|
23
26
|
const REVIEW_PREFIX = "review";
|
|
27
|
+
const SHARE_PREFIX = "share";
|
|
24
28
|
const LIST_PREFIX = "list";
|
|
25
29
|
const CANCEL_PREFIX = "cancel";
|
|
26
30
|
|
|
@@ -67,6 +71,44 @@ function isReviewSubcommand(prompt: string): boolean {
|
|
|
67
71
|
return target.endsWith(".md") || target.includes("/") || target.includes("\\");
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Whether a command line is the reserved `share` subcommand. Exact `share` and
|
|
76
|
+
* `share stop` are; `share the tokens` stays a task.
|
|
77
|
+
*/
|
|
78
|
+
function isShareSubcommand(prompt: string): boolean {
|
|
79
|
+
const text = prompt.toLowerCase();
|
|
80
|
+
return text === SHARE_PREFIX || text === `${SHARE_PREFIX} stop`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `/plan share`: print the link (and a QR) a teammate opens, or revoke it. The
|
|
85
|
+
* browser page has the same button; this is the path a phone-owned or remote
|
|
86
|
+
* session needs, where there is no page to click.
|
|
87
|
+
*/
|
|
88
|
+
async function shareReview(web: PlanWebReview, prompt: string, ctx: ExtensionContext): Promise<void> {
|
|
89
|
+
const stopping = prompt.toLowerCase().endsWith("stop");
|
|
90
|
+
const result = stopping ? await web.stopSharing() : await web.share();
|
|
91
|
+
if (!result) {
|
|
92
|
+
ctx.ui.notify("Plan share: no browser review is open; run /plan review first.", "warning");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (stopping) {
|
|
96
|
+
ctx.ui.notify("Plan share: reviewers can no longer open this review.", "info");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const link = result.state.links.at(-1);
|
|
100
|
+
if (!result.state.sharing || !link) {
|
|
101
|
+
ctx.ui.notify(`Plan share: ${result.state.reason ?? "no link is available"}.`, "warning");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const lines = ["Review link: " + link.url, ...(await qrLines(link.url))];
|
|
105
|
+
// One link is one person, so more than one is the team the page can list.
|
|
106
|
+
if (result.state.links.length > 1) {
|
|
107
|
+
lines.push(`${result.state.links.length} links shared — the page lists every link and who each one posts as.`);
|
|
108
|
+
}
|
|
109
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
110
|
+
}
|
|
111
|
+
|
|
70
112
|
/**
|
|
71
113
|
* Open `planPath` in the browser and, if the user saves comments, hand them to
|
|
72
114
|
* the model. A cancel is silent beyond a notice; nothing is sent.
|
|
@@ -137,6 +179,10 @@ async function openPlanIndex(
|
|
|
137
179
|
ctx: ExtensionContext,
|
|
138
180
|
): Promise<void> {
|
|
139
181
|
const dir = await runtime.plans.dirFor(ctx.cwd);
|
|
182
|
+
// A store an older version kept in the checkout was just moved in; the list is
|
|
183
|
+
// where a user notices their plans are somewhere else.
|
|
184
|
+
const moved = runtime.plans.takeNotice();
|
|
185
|
+
if (moved) ctx.ui.notify(moved, "info");
|
|
140
186
|
const summarise = async (): Promise<Awaited<ReturnType<typeof runtime.plans.index>>> => runtime.plans.index(ctx.cwd);
|
|
141
187
|
const summaries = await summarise();
|
|
142
188
|
|
|
@@ -147,22 +193,26 @@ async function openPlanIndex(
|
|
|
147
193
|
|
|
148
194
|
// The review runs after the screen closes, so the input focus is free again.
|
|
149
195
|
let selected: (PlanSummary | undefined) | undefined;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
196
|
+
// A dock list shares the editor slot with the goal and todo rails, so it takes
|
|
197
|
+
// the dock to itself for as long as it is open, like the other list screens.
|
|
198
|
+
await withRailsSuppressed(pi, () =>
|
|
199
|
+
ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
200
|
+
const component = new PlanIndexComponent({
|
|
201
|
+
summaries,
|
|
202
|
+
dir,
|
|
203
|
+
theme,
|
|
204
|
+
onSelect: (summary) => {
|
|
205
|
+
selected = summary;
|
|
206
|
+
done();
|
|
207
|
+
},
|
|
208
|
+
onClose: () => done(),
|
|
209
|
+
reload: summarise,
|
|
210
|
+
requestRender: () => tui.requestRender(),
|
|
211
|
+
viewportRows: () => tui.terminal?.rows,
|
|
212
|
+
});
|
|
213
|
+
return component;
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
166
216
|
if (selected) await reviewInBrowser(pi, runtime, web, selected.path, ctx);
|
|
167
217
|
}
|
|
168
218
|
|
|
@@ -173,11 +223,13 @@ export function registerCommands(
|
|
|
173
223
|
): void {
|
|
174
224
|
pi.registerCommand("plan", {
|
|
175
225
|
description:
|
|
176
|
-
"Toggle plan mode (read-only exploration), enter it with an optional task, list saved plans, review one in the browser, or cancel planning without approving",
|
|
226
|
+
"Toggle plan mode (read-only exploration), enter it with an optional task, list saved plans, review one in the browser, share that review with a link, or cancel planning without approving",
|
|
177
227
|
getArgumentCompletions: (prefix) => {
|
|
178
228
|
const text = prefix.trimStart();
|
|
179
229
|
if (text.includes(" ")) return null;
|
|
180
|
-
const subcommands = [LIST_PREFIX, REVIEW_PREFIX, CANCEL_PREFIX].filter((name) =>
|
|
230
|
+
const subcommands = [LIST_PREFIX, REVIEW_PREFIX, SHARE_PREFIX, CANCEL_PREFIX].filter((name) =>
|
|
231
|
+
name.startsWith(text),
|
|
232
|
+
);
|
|
181
233
|
return subcommands.length > 0 ? subcommands.map((value) => ({ value, label: value })) : null;
|
|
182
234
|
},
|
|
183
235
|
handler: async (args, ctx) => {
|
|
@@ -198,6 +250,10 @@ export function registerCommands(
|
|
|
198
250
|
await openPlanIndex(pi, runtime, web, ctx);
|
|
199
251
|
return;
|
|
200
252
|
}
|
|
253
|
+
if (isShareSubcommand(prompt)) {
|
|
254
|
+
await shareReview(web, prompt, ctx);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
201
257
|
if (isReviewSubcommand(prompt)) {
|
|
202
258
|
await reviewInBrowser(pi, runtime, web, reviewTarget(prompt), ctx);
|
|
203
259
|
return;
|