@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/plan/steps.ts
CHANGED
|
@@ -10,13 +10,16 @@
|
|
|
10
10
|
* the digest is computed over.
|
|
11
11
|
*
|
|
12
12
|
* Only top-level list items become steps: nested sub-bullets are notes, and the
|
|
13
|
-
* shallowest list level is treated as the plan.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* shallowest list level is treated as the plan. An indented, marker-less line
|
|
14
|
+
* continues the item above it, so a wrapped step keeps its full text. When the
|
|
15
|
+
* plan has a `Steps` section (the canonical outline's heading), only that
|
|
16
|
+
* section's list items are steps, so prose bullets elsewhere never seed the todo
|
|
17
|
+
* list or consume the step cap. List items inside fenced code blocks are ignored. A `- [ ]` / `- [x]`
|
|
17
18
|
* checkbox or a `[DONE:n]` marker sets the status.
|
|
18
19
|
*/
|
|
19
20
|
|
|
21
|
+
import { extractSections, FENCE, HEADING, normalizeHeadingText } from "./sections.ts";
|
|
22
|
+
|
|
20
23
|
/** Most steps seeded from one plan. */
|
|
21
24
|
export const MAX_STEPS = 50;
|
|
22
25
|
/** Longest seeded step text; longer lines are elided. */
|
|
@@ -82,26 +85,17 @@ export interface PlanStep {
|
|
|
82
85
|
status: "pending" | "completed";
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
/** A heading-delimited chunk of the plan, for the review screen's navigation. */
|
|
86
|
-
export interface PlanSection {
|
|
87
|
-
/** Plain heading text, or `""` for the content before the first heading. */
|
|
88
|
-
title: string;
|
|
89
|
-
/** The section's markdown, including its heading line. */
|
|
90
|
-
text: string;
|
|
91
|
-
/** 0-based source line of the section's heading (0 for the preamble). */
|
|
92
|
-
line: number;
|
|
93
|
-
/** Heading level, or 0 for the preamble before the first heading. */
|
|
94
|
-
level: number;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
88
|
/** A top-level list item located in the source. */
|
|
98
89
|
interface PlanListItem {
|
|
99
|
-
/** Zero-based source line index. */
|
|
90
|
+
/** Zero-based source line index of the item's first line. */
|
|
100
91
|
line: number;
|
|
101
92
|
indent: number;
|
|
102
93
|
/** The list marker with its trailing whitespace, e.g. `1. ` or `- `. */
|
|
103
94
|
lead: string;
|
|
104
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Item text after the marker on its first line; indented continuation lines
|
|
97
|
+
* are folded into {@link step}, so a checkbox edit rewrites only this line.
|
|
98
|
+
*/
|
|
105
99
|
raw: string;
|
|
106
100
|
step: PlanStep;
|
|
107
101
|
}
|
|
@@ -109,39 +103,10 @@ interface PlanListItem {
|
|
|
109
103
|
/** A top-level numbered or bulleted list item, capturing its parts. */
|
|
110
104
|
const LIST_ITEM = /^([ \t]*)((?:\d+[.)]|[-*+])\s+)(.*\S)\s*$/;
|
|
111
105
|
const CHECKBOX = /^\[([ xX])\]\s*(.*)$/;
|
|
112
|
-
/**
|
|
113
|
-
* A markdown heading outside a fence, capturing its level and text: up to three
|
|
114
|
-
* leading spaces, a space after the hashes, and a closing hash run only when a
|
|
115
|
-
* space separates it. Shared with `blocks.ts`, so the section split and the
|
|
116
|
-
* browser outline read a heading line the same way, and `## C#` keeps its title.
|
|
117
|
-
*/
|
|
118
|
-
export const HEADING = /^ {0,3}(#{1,6})\s+(.*?)(?:\s+#+)?\s*$/;
|
|
119
|
-
/**
|
|
120
|
-
* A markdown code-fence opener or closer (backticks or tildes). Shared with
|
|
121
|
-
* `blocks.ts`: a tab-indented fence is prose for both parsers, never code for
|
|
122
|
-
* one and prose for the other.
|
|
123
|
-
*/
|
|
124
|
-
export const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
125
106
|
const DONE_MARKER = /\[DONE:\d+\]/gi;
|
|
126
107
|
|
|
127
|
-
/**
|
|
128
|
-
|
|
129
|
-
* code removed, whitespace collapsed. Exported so `blocks.ts` labels an outline
|
|
130
|
-
* entry the same way `extractPlanSections` labels its section, which is what
|
|
131
|
-
* lets the browser mark a changed section.
|
|
132
|
-
*/
|
|
133
|
-
export function normalizeHeadingText(text: string): string {
|
|
134
|
-
return text
|
|
135
|
-
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
136
|
-
.replace(/\*([^*]+)\*/g, "$1")
|
|
137
|
-
.replace(/(?<![A-Za-z0-9_])_([^_]+)_(?![A-Za-z0-9_])/g, "$1")
|
|
138
|
-
.replace(/`([^`]+)`/g, "$1")
|
|
139
|
-
.replace(/\s+/g, " ")
|
|
140
|
-
.trim();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Strip inline markdown emphasis and code, then collapse to one clean line. */
|
|
144
|
-
function plainText(text: string): string {
|
|
108
|
+
/** A label with its inline markers and any done marker removed. */
|
|
109
|
+
function cleanLabel(text: string): string {
|
|
145
110
|
return normalizeHeadingText(text.replace(DONE_MARKER, ""));
|
|
146
111
|
}
|
|
147
112
|
|
|
@@ -157,7 +122,12 @@ function parseStep(raw: string): PlanStep {
|
|
|
157
122
|
}
|
|
158
123
|
if (/\[DONE:\d+\]/i.test(text)) done = true;
|
|
159
124
|
|
|
160
|
-
return { content:
|
|
125
|
+
return { content: cleanLabel(text), status: done ? "completed" : "pending" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Leading whitespace width of a line, for continuation indent checks. */
|
|
129
|
+
function indentOf(line: string): number {
|
|
130
|
+
return line.length - line.trimStart().length;
|
|
161
131
|
}
|
|
162
132
|
|
|
163
133
|
/** Every top-level list item, in order, before de-duplication and capping. */
|
|
@@ -165,22 +135,57 @@ function scanListItems(plan: string): PlanListItem[] {
|
|
|
165
135
|
const items: PlanListItem[] = [];
|
|
166
136
|
// Fences hide example lists (`- name: foo` in a YAML block) from extraction.
|
|
167
137
|
let fence: string | undefined;
|
|
138
|
+
// The item still collecting indented continuation lines, and those lines.
|
|
139
|
+
let current: { item: PlanListItem; continuation: string[] } | undefined;
|
|
140
|
+
|
|
141
|
+
/** Fold the collected continuation into the item, then close it. */
|
|
142
|
+
const finish = (): void => {
|
|
143
|
+
if (current === undefined) return;
|
|
144
|
+
if (current.continuation.length > 0) {
|
|
145
|
+
current.item.step = parseStep(`${current.item.raw} ${current.continuation.join(" ")}`);
|
|
146
|
+
}
|
|
147
|
+
current = undefined;
|
|
148
|
+
};
|
|
168
149
|
|
|
169
150
|
plan.split("\n").forEach((line, index) => {
|
|
170
151
|
const fenceMatch = line.match(FENCE);
|
|
171
152
|
if (fenceMatch) {
|
|
153
|
+
finish();
|
|
172
154
|
if (fence === undefined) fence = fenceMatch[1][0];
|
|
173
155
|
else if (fence === fenceMatch[1][0]) fence = undefined;
|
|
174
156
|
return;
|
|
175
157
|
}
|
|
176
158
|
if (fence !== undefined) return;
|
|
177
159
|
|
|
160
|
+
if (line.trim() === "") {
|
|
161
|
+
finish();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
178
165
|
const match = line.match(LIST_ITEM);
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
166
|
+
if (match) {
|
|
167
|
+
finish();
|
|
168
|
+
const step = parseStep(match[3]);
|
|
169
|
+
if (!step.content) return;
|
|
170
|
+
const item: PlanListItem = { line: index, indent: match[1].length, lead: match[2], raw: match[3], step };
|
|
171
|
+
items.push(item);
|
|
172
|
+
current = { item, continuation: [] };
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// A deeper, marker-less line continues the step's paragraph; a heading,
|
|
177
|
+
// a shallow line, or a blank line ends it. `raw` stays the first line so
|
|
178
|
+
// `setStepStatuses` edits the checkbox without touching these lines.
|
|
179
|
+
if (current !== undefined && indentOf(line) > current.item.indent && !HEADING.test(line)) {
|
|
180
|
+
const text = line.trim();
|
|
181
|
+
if (text) current.continuation.push(text);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
finish();
|
|
183
186
|
});
|
|
187
|
+
|
|
188
|
+
finish();
|
|
184
189
|
return items;
|
|
185
190
|
}
|
|
186
191
|
|
|
@@ -208,7 +213,7 @@ function stepsSection(plan: string): { start: number; end: number } | undefined
|
|
|
208
213
|
if (!heading) continue;
|
|
209
214
|
const depth = heading[1].length;
|
|
210
215
|
if (start < 0) {
|
|
211
|
-
if (
|
|
216
|
+
if (normalizeHeadingText(heading[2]).toLowerCase() !== "steps") continue;
|
|
212
217
|
start = index;
|
|
213
218
|
level = depth;
|
|
214
219
|
continue;
|
|
@@ -271,7 +276,7 @@ const LIST_PREFIX = /^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s*)?/;
|
|
|
271
276
|
*/
|
|
272
277
|
export function verificationItem(plan: string): PlanStep | undefined {
|
|
273
278
|
if (stepsSection(plan) === undefined) return undefined;
|
|
274
|
-
const section =
|
|
279
|
+
const section = extractSections(plan).find(
|
|
275
280
|
(candidate) => candidate.level > 0 && candidate.title.toLowerCase() === "verification",
|
|
276
281
|
);
|
|
277
282
|
if (!section) return undefined;
|
|
@@ -281,7 +286,7 @@ export function verificationItem(plan: string): PlanStep | undefined {
|
|
|
281
286
|
// A fenced block is how the outline asks for commands; its fence lines are
|
|
282
287
|
// punctuation, not a check, so only the commands inside it are carried.
|
|
283
288
|
.filter((line) => !FENCE.test(line))
|
|
284
|
-
.map((line) =>
|
|
289
|
+
.map((line) => cleanLabel(line.replace(LIST_PREFIX, "")))
|
|
285
290
|
.filter(Boolean);
|
|
286
291
|
if (items.length === 0) return undefined;
|
|
287
292
|
return { content: elide(`Verify: ${items.join("; ")}`), status: "pending" };
|
|
@@ -361,43 +366,6 @@ export function setStepStatuses(plan: string, updates: readonly StepEdit[]): str
|
|
|
361
366
|
return lines.join("\n");
|
|
362
367
|
}
|
|
363
368
|
|
|
364
|
-
/**
|
|
365
|
-
* Sections of a plan, split at heading lines outside code fences. The content
|
|
366
|
-
* before the first heading is a section titled `""`. Pi's `Markdown` renderer
|
|
367
|
-
* exposes no source-line map, so the review screen renders these chunks
|
|
368
|
-
* separately to know where each section begins.
|
|
369
|
-
*/
|
|
370
|
-
export function extractPlanSections(plan: string): PlanSection[] {
|
|
371
|
-
const sections: PlanSection[] = [];
|
|
372
|
-
let current: PlanSection | undefined;
|
|
373
|
-
let fence: string | undefined;
|
|
374
|
-
|
|
375
|
-
const flush = (): void => {
|
|
376
|
-
if (current && (current.title !== "" || current.text.trim() !== "")) sections.push(current);
|
|
377
|
-
current = undefined;
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
plan.split("\n").forEach((line, index) => {
|
|
381
|
-
const fenceMatch = line.match(FENCE);
|
|
382
|
-
if (fenceMatch) {
|
|
383
|
-
if (fence === undefined) fence = fenceMatch[1][0];
|
|
384
|
-
else if (fence === fenceMatch[1][0]) fence = undefined;
|
|
385
|
-
} else if (fence === undefined) {
|
|
386
|
-
const heading = line.match(HEADING);
|
|
387
|
-
if (heading) {
|
|
388
|
-
flush();
|
|
389
|
-
current = { title: plainText(heading[2]), text: line, line: index, level: heading[1].length };
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
if (current === undefined) current = { title: "", text: line, line: 0, level: 0 };
|
|
394
|
-
else current.text += `\n${line}`;
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
flush();
|
|
398
|
-
return sections;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
369
|
/**
|
|
402
370
|
* The plan's own objective: the first paragraph of its `## Goal` section, with
|
|
403
371
|
* inline markdown and a leading list marker removed.
|
|
@@ -407,7 +375,7 @@ export function extractPlanSections(plan: string): PlanSection[] {
|
|
|
407
375
|
* `## Goal` section, so the caller can fall back to the title.
|
|
408
376
|
*/
|
|
409
377
|
export function goalSection(plan: string): string | undefined {
|
|
410
|
-
const section =
|
|
378
|
+
const section = extractSections(plan).find(
|
|
411
379
|
(candidate) => candidate.level === 2 && normalizeHeadingText(candidate.title).toLowerCase() === "goal",
|
|
412
380
|
);
|
|
413
381
|
if (section === undefined) return undefined;
|
|
@@ -12,8 +12,8 @@ import { GLYPHS } from "../../../lib/tui/glyphs.ts";
|
|
|
12
12
|
import { paint } from "../../../lib/tui/tokens.ts";
|
|
13
13
|
import { callLine, detailsOrLine } from "../../../lib/tui/tool.ts";
|
|
14
14
|
import { failedResult } from "../../../lib/tool-result.ts";
|
|
15
|
-
import { isRemoteTurn } from "../../../lib/interaction.ts";
|
|
16
|
-
import { PLAN_CONSENT, READ_ONLY_SUMMARY } from "../../../lib/plan-entry.ts";
|
|
15
|
+
import { isRemoteTurn, remoteLanguage } from "../../../lib/interaction.ts";
|
|
16
|
+
import { PLAN_CONSENT, PLAN_CONSENT_SHORT, PLAN_CONSENT_SHORT_ZH, READ_ONLY_SUMMARY } from "../../../lib/plan-entry.ts";
|
|
17
17
|
import { ENTER_TOOL, type PlanRuntime } from "../runtime.ts";
|
|
18
18
|
import type { EnterPlanModeDetails } from "../types.ts";
|
|
19
19
|
|
|
@@ -53,7 +53,12 @@ export function registerEnterTool(pi: ExtensionAPI, runtime: PlanRuntime): void
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const
|
|
56
|
+
const consent = isRemoteTurn(ctx)
|
|
57
|
+
? remoteLanguage(ctx) === "zh"
|
|
58
|
+
? PLAN_CONSENT_SHORT_ZH
|
|
59
|
+
: PLAN_CONSENT_SHORT
|
|
60
|
+
: PLAN_CONSENT;
|
|
61
|
+
const approved = await ctx.ui.confirm("Enter plan mode?", consent);
|
|
57
62
|
if (!approved) {
|
|
58
63
|
return {
|
|
59
64
|
content: [{ type: "text", text: "Stayed in normal mode. Proceed, or ask the user how to continue." }],
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* It presents the saved plan for approval, then seeds the approved steps into
|
|
5
5
|
* the todo list and finalizes the session goal: a `planning` goal becomes the
|
|
6
|
-
* plan's own `## Goal`, and a session with no goal is
|
|
6
|
+
* plan's own `## Goal`, and a session with no goal is given the same. The
|
|
7
7
|
* review loop and the browser lifecycle belong to `review.ts`; the nested writes
|
|
8
8
|
* belong to `todo-seed.ts` and the goal tool.
|
|
9
9
|
*/
|
|
@@ -19,7 +19,6 @@ import { GLYPHS } from "../../../lib/tui/glyphs.ts";
|
|
|
19
19
|
import { paint } from "../../../lib/tui/tokens.ts";
|
|
20
20
|
import { callLine, detailsOrLine, moreHint } from "../../../lib/tui/tool.ts";
|
|
21
21
|
import { failedResult } from "../../../lib/tool-result.ts";
|
|
22
|
-
import { loadConfig } from "../config.ts";
|
|
23
22
|
import { feedbackSummary } from "../feedback.ts";
|
|
24
23
|
import { mergeSummary, overCap, preview, verificationNote } from "../format.ts";
|
|
25
24
|
import { sanitizePlanText } from "../plan-text.ts";
|
|
@@ -35,7 +34,7 @@ import { seedTodos } from "./todo-seed.ts";
|
|
|
35
34
|
const ExitPlanModeParams = Type.Object({
|
|
36
35
|
plan_path: Type.String({
|
|
37
36
|
description:
|
|
38
|
-
"Path to the plan file written by write_plan
|
|
37
|
+
"Path to the plan file written by write_plan: the absolute path it returned, or the `~/…` form it displayed. A bare name resolves inside the plans directory. The user reviews this file.",
|
|
39
38
|
}),
|
|
40
39
|
});
|
|
41
40
|
|
|
@@ -52,24 +51,18 @@ interface GoalOutcome {
|
|
|
52
51
|
/**
|
|
53
52
|
* Finalise the session goal from the approved plan.
|
|
54
53
|
*
|
|
55
|
-
*
|
|
56
|
-
* session with
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
54
|
+
* Approving the plan *is* the decision, so neither a `planning` goal nor a
|
|
55
|
+
* session with none is asked again. A final goal (`active` or `achieved`) is the
|
|
56
|
+
* user's own: approving ordinary work never rewrites it. Recording runs through
|
|
57
|
+
* the goal tool when it is registered — validation and the widget stay goal's,
|
|
58
|
+
* and no model turn is spent — and stays silent when that sibling extension is
|
|
59
|
+
* not installed, because there is nothing to report and nothing to act on.
|
|
61
60
|
*/
|
|
62
|
-
async function finalizeGoal(ctx: ExtensionToolContext, plan: StoredPlan): Promise<GoalOutcome> {
|
|
61
|
+
async function finalizeGoal(pi: ExtensionAPI, ctx: ExtensionToolContext, plan: StoredPlan): Promise<GoalOutcome> {
|
|
63
62
|
const objective = planObjective(plan);
|
|
64
63
|
const current = lastGoal(ctx.sessionManager.getBranch());
|
|
65
64
|
if (current !== null && current.status !== "planning") return { objective };
|
|
66
|
-
if (
|
|
67
|
-
if (!ctx.hasUI) return { objective };
|
|
68
|
-
if (loadConfig(ctx.cwd).goalPrompt) {
|
|
69
|
-
const approved = await ctx.ui.confirm("Record this plan as the session goal?", objective);
|
|
70
|
-
if (!approved) return { objective };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
65
|
+
if (!pi.getAllTools().some((tool) => tool.name === GOAL_TOOL)) return { objective };
|
|
73
66
|
const outcome = await ctx.executeTool(GOAL_TOOL, { objective, status: "active" });
|
|
74
67
|
const action = current === null ? "recorded" : "promoted";
|
|
75
68
|
return outcome.isError ? { objective, action, error: firstTextLine(outcome) } : { objective, action };
|
|
@@ -101,7 +94,8 @@ export function registerExitTool(pi: ExtensionAPI, runtime: PlanRuntime, web: Pl
|
|
|
101
94
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
102
95
|
|
|
103
96
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
104
|
-
const
|
|
97
|
+
const args = params as ExitPlanModeArgs;
|
|
98
|
+
const planPath = args.plan_path.trim();
|
|
105
99
|
|
|
106
100
|
if (!runtime.isEnabled()) {
|
|
107
101
|
return failedResult("Not in plan mode; there is no plan to approve.", {
|
|
@@ -220,7 +214,7 @@ export function registerExitTool(pi: ExtensionAPI, runtime: PlanRuntime, web: Pl
|
|
|
220
214
|
|
|
221
215
|
runtime.disable(ctx);
|
|
222
216
|
const { steps, merged, error } = await seedTodos(pi, ctx, decision.plan.content);
|
|
223
|
-
const goal = await finalizeGoal(ctx, decision.plan);
|
|
217
|
+
const goal = await finalizeGoal(pi, ctx, decision.plan);
|
|
224
218
|
const listing =
|
|
225
219
|
steps.length > 0
|
|
226
220
|
? `\n\n${steps
|
|
@@ -40,7 +40,7 @@ export function registerWriteTool(pi: ExtensionAPI, runtime: PlanRuntime): void
|
|
|
40
40
|
name: WRITE_PLAN_TOOL,
|
|
41
41
|
label: "Write plan",
|
|
42
42
|
description:
|
|
43
|
-
"Save the plan you designed as a markdown file
|
|
43
|
+
"Save the plan you designed as a markdown file in the plans directory (the agent directory, keyed by " +
|
|
44
44
|
"repository). Write the complete plan, not a summary, in this outline so it is consistent and scannable:\n\n" +
|
|
45
45
|
`${PLAN_OUTLINE}\n\n` +
|
|
46
46
|
"Each section has one job: lead it with its outcome, keep a prose section to a few bullets or two or three " +
|
|
@@ -51,10 +51,11 @@ export function registerWriteTool(pi: ExtensionAPI, runtime: PlanRuntime): void
|
|
|
51
51
|
"The outline is a guide, not a form: drop Context, Approach, Visual, or Risks when there is nothing to say. " +
|
|
52
52
|
"Keep Steps and Verification — the steps are read from that section (without it, the shallowest list " +
|
|
53
53
|
"anywhere becomes the steps), and Verification becomes the last todo item. " +
|
|
54
|
-
"In Steps, each top-level bullet is one outcome in the order you will do them
|
|
54
|
+
"In Steps, each top-level bullet is one outcome in the order you will do them; " +
|
|
55
|
+
"an indented line continues that outcome, and a deeper bullet is a note. " +
|
|
55
56
|
"Returns the path to pass to exit_plan_mode; plan_path overwrites an existing file when refining. " +
|
|
56
57
|
`A plan with more than ${MAX_STEPS} top-level steps in its Steps section is refused, so approval never drops steps it cannot track.`,
|
|
57
|
-
promptSnippet: "Save the designed plan as a markdown file
|
|
58
|
+
promptSnippet: "Save the designed plan as a markdown file in the plans directory.",
|
|
58
59
|
promptGuidelines: [
|
|
59
60
|
"While plan mode is active, write the full plan with write_plan, then call exit_plan_mode with the returned path.",
|
|
60
61
|
"When refining with plan_path, keep every section heading as it was: the review marks changed sections by heading, " +
|
|
@@ -88,6 +89,10 @@ export function registerWriteTool(pi: ExtensionAPI, runtime: PlanRuntime): void
|
|
|
88
89
|
planPath: args.plan_path,
|
|
89
90
|
});
|
|
90
91
|
runtime.setLastPlan(ctx, file.path);
|
|
92
|
+
// A store an older version kept in the checkout was moved in by this
|
|
93
|
+
// write; say so once, where the user is looking.
|
|
94
|
+
const moved = runtime.plans.takeNotice();
|
|
95
|
+
if (moved) ctx.ui.notify(moved, "info");
|
|
91
96
|
return textResult(
|
|
92
97
|
`Wrote the plan to ${file.relativePath}. Call exit_plan_mode with plan_path: ${file.path}${stepElisionNote(summary.elided)}`,
|
|
93
98
|
{ path: file.path, relativePath: file.relativePath, bytes: file.bytes } satisfies WritePlanDetails,
|
package/extensions/plan/tools.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Model-facing registration for the plan-mode tools.
|
|
3
3
|
*
|
|
4
4
|
* `enter_plan_mode` lets the model ask to plan before touching code; it needs
|
|
5
|
-
* user confirmation. `write_plan` saves the plan artifact
|
|
5
|
+
* user confirmation. `write_plan` saves the plan artifact in the plans directory and
|
|
6
6
|
* returns its path. `cancel_plan_mode` leaves planning without approving, so a
|
|
7
7
|
* phone user is never trapped in read-only mode. `exit_plan_mode` reads that file
|
|
8
8
|
* back, presents it for approval (a scrollable review screen in the TUI, a select dialog elsewhere),
|
package/extensions/plan/tui.ts
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* the active Markdown theme, so headings, lists, code, links, and quotes match
|
|
7
7
|
* the rest of the application; plan-authored control characters are stripped
|
|
8
8
|
* first so the file cannot drive the terminal. Task checkboxes are recolored
|
|
9
|
-
* and the footer colors the primary actions.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* and the footer colors the primary actions. Approving leaves plan mode and
|
|
10
|
+
* starts the work, so it asks `y/N` first through the shared `ConfirmState`;
|
|
11
|
+
* the plan stays mounted, and `Esc` backs out of the question before it backs
|
|
12
|
+
* out of the review. It returns the chosen action through `onClose` so the
|
|
13
|
+
* caller can act only after the screen is gone and input focus is free again.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
import { basename } from "node:path";
|
|
@@ -35,14 +37,14 @@ import {
|
|
|
35
37
|
type ViewportRowsSource,
|
|
36
38
|
} from "../../lib/tui/chrome.ts";
|
|
37
39
|
import { isCloseKey } from "../../lib/tui/cursor.ts";
|
|
40
|
+
import { ConfirmState } from "../../lib/tui/confirm.ts";
|
|
38
41
|
import { gaugeBar } from "../../lib/tui/gauge.ts";
|
|
39
42
|
import { GLYPHS } from "../../lib/tui/glyphs.ts";
|
|
40
43
|
import { plainRule, titleRule } from "../../lib/tui/rules.ts";
|
|
41
44
|
import { emptyState, checkbox } from "../../lib/tui/rows.ts";
|
|
42
45
|
import { paint } from "../../lib/tui/tokens.ts";
|
|
43
46
|
import { planTitle, type StoredPlan } from "./plans.ts";
|
|
44
|
-
import { changedSections,
|
|
45
|
-
import { extractPlanSections } from "./steps.ts";
|
|
47
|
+
import { changedSections, planDisplayText, type SectionDigest } from "./plan-text.ts";
|
|
46
48
|
|
|
47
49
|
/** What the user chose from the read/review screen. */
|
|
48
50
|
export type PlanViewAction = "approve" | "refine" | "keep" | "browser" | "cancel";
|
|
@@ -175,6 +177,8 @@ export class PlanViewComponent implements Component {
|
|
|
175
177
|
private cache: BodyCache | null = null;
|
|
176
178
|
/** True while the inline refine editor owns the keyboard. */
|
|
177
179
|
private editing = false;
|
|
180
|
+
/** The y/N approval question, shared with the other destructive screens. */
|
|
181
|
+
private readonly confirm = new ConfirmState();
|
|
178
182
|
/** The inline refine editor, built lazily once a TUI handle is available. */
|
|
179
183
|
private editor: Editor | null = null;
|
|
180
184
|
|
|
@@ -195,12 +199,11 @@ export class PlanViewComponent implements Component {
|
|
|
195
199
|
const padding = width >= MIN_PADDED_WIDTH ? BODY_PADDING : 0;
|
|
196
200
|
const base = this.options.markdownTheme ?? getMarkdownTheme();
|
|
197
201
|
const theme = planMarkdownTheme(this.theme, base);
|
|
198
|
-
const current =
|
|
199
|
-
const
|
|
200
|
-
const chunks = extractPlanSections(current);
|
|
202
|
+
const current = planDisplayText(this.options.plan.content);
|
|
203
|
+
const marks = changedSections(this.options.baseline, current);
|
|
201
204
|
const lines: string[] = [];
|
|
202
205
|
const sections: SectionOffset[] = [];
|
|
203
|
-
|
|
206
|
+
marks.forEach(({ section: chunk, changed }, index) => {
|
|
204
207
|
// Markdown pads every line to the full width for backgrounds; this screen
|
|
205
208
|
// draws no background, so the padding is dropped.
|
|
206
209
|
const rendered = new Markdown(chunk.text, padding, 0, theme)
|
|
@@ -209,14 +212,14 @@ export class PlanViewComponent implements Component {
|
|
|
209
212
|
sections.push({
|
|
210
213
|
title: chunk.title,
|
|
211
214
|
offset: lines.length,
|
|
212
|
-
...(changed
|
|
215
|
+
...(changed ? { changed: true } : {}),
|
|
213
216
|
});
|
|
214
217
|
lines.push(...rendered);
|
|
215
218
|
// Sections render independently, so restore the blank line a heading-only
|
|
216
219
|
// chunk omits before the next section.
|
|
217
|
-
if (index <
|
|
220
|
+
if (index < marks.length - 1 && lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
|
|
218
221
|
});
|
|
219
|
-
this.cache = { width, lines, sections, changedCount:
|
|
222
|
+
this.cache = { width, lines, sections, changedCount: marks.filter((mark) => mark.changed).length };
|
|
220
223
|
return this.cache;
|
|
221
224
|
}
|
|
222
225
|
|
|
@@ -314,6 +317,12 @@ export class PlanViewComponent implements Component {
|
|
|
314
317
|
this.options.requestRender();
|
|
315
318
|
return;
|
|
316
319
|
}
|
|
320
|
+
// A pending question owns the keyboard, so a scroll key cannot slip past it
|
|
321
|
+
// and `Esc` backs out of the question rather than the review.
|
|
322
|
+
if (this.confirm.pending) {
|
|
323
|
+
if (this.confirm.resolve(data) !== undefined) this.options.requestRender();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
317
326
|
if (isCloseKey(data)) {
|
|
318
327
|
this.options.onClose("keep");
|
|
319
328
|
return;
|
|
@@ -330,8 +339,10 @@ export class PlanViewComponent implements Component {
|
|
|
330
339
|
else if (matchesKey(data, Key.end) || data === "G") this.scrollTo(this.maxOffset);
|
|
331
340
|
else if (data === "n") this.jumpSection(1);
|
|
332
341
|
else if (data === "p") this.jumpSection(-1);
|
|
333
|
-
else if (data === "a")
|
|
334
|
-
|
|
342
|
+
else if (data === "a") {
|
|
343
|
+
this.confirm.ask("Approve and run the plan? y/N", () => this.options.onClose("approve"));
|
|
344
|
+
this.options.requestRender();
|
|
345
|
+
} else if (data === "r") this.startRefine();
|
|
335
346
|
else if (data === "w") this.options.onClose("browser");
|
|
336
347
|
else if (data === "x") this.options.onClose("cancel");
|
|
337
348
|
}
|
|
@@ -400,10 +411,13 @@ export class PlanViewComponent implements Component {
|
|
|
400
411
|
} else {
|
|
401
412
|
if (this.maxOffset > 0) lines.push(scrollRow(theme, w, this.offset, end, body.length, sectionLabel));
|
|
402
413
|
else lines.push("");
|
|
414
|
+
const pending = this.confirm.pending;
|
|
403
415
|
const sectionHints = sections.length > 1 ? SECTION_HINTS : [];
|
|
404
416
|
const hints: ScreenHint[] =
|
|
405
417
|
this.maxOffset > 0 ? [...PRIMARY_HINTS, ...SCROLL_HINTS, ...sectionHints] : PRIMARY_HINTS;
|
|
406
|
-
lines.push(
|
|
418
|
+
lines.push(
|
|
419
|
+
pending ? truncateToWidth(` ${paint(theme, "warn", pending.prompt)}`, w) : screenHint(theme, w, hints),
|
|
420
|
+
);
|
|
407
421
|
}
|
|
408
422
|
lines.push(plainRule(theme, w));
|
|
409
423
|
return lines;
|
package/extensions/plan/types.ts
CHANGED
|
@@ -6,6 +6,32 @@ import type { PlanAnnotation } from "./feedback.ts";
|
|
|
6
6
|
import type { SectionDigest } from "./plan-text.ts";
|
|
7
7
|
import type { StepUpdate } from "./steps.ts";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Who is looking at a running review: the session that opened it (`owner`, the
|
|
11
|
+
* only role that can approve), or someone the link was shared with (`reviewer`,
|
|
12
|
+
* who may comment).
|
|
13
|
+
*/
|
|
14
|
+
export type ReviewRole = "owner" | "reviewer";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What a review token names: its role, and — for a reviewer — the name it posts
|
|
18
|
+
* under, bound the first time that link writes.
|
|
19
|
+
*/
|
|
20
|
+
export interface ReviewCaller {
|
|
21
|
+
role: ReviewRole;
|
|
22
|
+
/** The reviewer's name, once this link has written; absent for the owner and
|
|
23
|
+
* for a reviewer that has not commented yet. */
|
|
24
|
+
name?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* One frame on the review's event stream. `comment`, `plan`, and `share` say a
|
|
29
|
+
* page should re-read the island; `closed` ends the review for everyone, and
|
|
30
|
+
* `revoked` tells a reviewer the owner stopped sharing. The frame carries no
|
|
31
|
+
* state of its own, so the island stays the one contract.
|
|
32
|
+
*/
|
|
33
|
+
export type ReviewFrame = "comment" | "plan" | "share" | "closed" | "revoked";
|
|
34
|
+
|
|
9
35
|
/** Persisted (custom entry) plan-mode state. */
|
|
10
36
|
export interface PlanModeEntry {
|
|
11
37
|
enabled: boolean;
|
|
@@ -38,7 +64,7 @@ export interface EnterPlanModeDetails {
|
|
|
38
64
|
export interface WritePlanDetails {
|
|
39
65
|
/** Absolute path of the written plan file. */
|
|
40
66
|
path: string;
|
|
41
|
-
/**
|
|
67
|
+
/** How the path is shown: `~`-shortened, since plans live outside the project. */
|
|
42
68
|
relativePath: string;
|
|
43
69
|
/** Size of the written plan in bytes. */
|
|
44
70
|
bytes: number;
|
|
@@ -48,7 +74,7 @@ export interface WritePlanDetails {
|
|
|
48
74
|
export interface SetPlanStepsDetails {
|
|
49
75
|
/** Absolute path of the plan file that was updated. */
|
|
50
76
|
path: string;
|
|
51
|
-
/**
|
|
77
|
+
/** How the path is shown: `~`-shortened. */
|
|
52
78
|
relativePath: string;
|
|
53
79
|
/** The checkbox changes that were applied, in call order. */
|
|
54
80
|
updates: StepUpdate[];
|
|
@@ -66,7 +92,7 @@ export interface ExitPlanModeDetails {
|
|
|
66
92
|
plan: string;
|
|
67
93
|
/** Absolute path of the reviewed plan file. */
|
|
68
94
|
planPath?: string;
|
|
69
|
-
/**
|
|
95
|
+
/** How the path is shown: `~`-shortened. */
|
|
70
96
|
relativePath?: string;
|
|
71
97
|
/** True when the user asked to refine the plan instead of approving. */
|
|
72
98
|
refined?: boolean;
|
|
@@ -43,7 +43,7 @@ export const WEB_CHROME_JS = String.raw` function setStatus(text, kind) {
|
|
|
43
43
|
var count = annotations.length;
|
|
44
44
|
label += " \u00b7 " + count + (count === 1 ? " comment" : " comments");
|
|
45
45
|
if (dirty && !saved && !approved && !closed) {
|
|
46
|
-
label += " \u00b7 unsaved";
|
|
46
|
+
label += " \u00b7 " + (role === "reviewer" ? "unposted" : "unsaved");
|
|
47
47
|
if (!tone) tone = "warn";
|
|
48
48
|
}
|
|
49
49
|
stateEl.textContent = label;
|
|
@@ -76,6 +76,7 @@ export const WEB_CHROME_JS = String.raw` function setStatus(text, kind) {
|
|
|
76
76
|
function lockActions() {
|
|
77
77
|
if (approveButton) approveButton.disabled = true;
|
|
78
78
|
if (saveButton) saveButton.disabled = true;
|
|
79
|
+
if (postButton) postButton.disabled = true;
|
|
79
80
|
if (cancelButton) cancelButton.disabled = true;
|
|
80
81
|
}
|
|
81
82
|
|