@gavin-hu/my-pi-agent 0.16.0 → 0.18.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 +29 -15
- package/README.zh-CN.md +27 -15
- package/extensions/bytefly/README.md +195 -115
- package/extensions/bytefly/footer/collect/entries.ts +1 -6
- package/extensions/bytefly/footer/collect/snapshot.ts +23 -9
- package/extensions/bytefly/footer/price/price.ts +12 -8
- package/extensions/bytefly/footer/register.ts +13 -1
- package/extensions/bytefly/footer/types.ts +11 -0
- package/extensions/bytefly/footer/unpriced.ts +36 -0
- package/extensions/bytefly/header/component.ts +15 -29
- package/extensions/bytefly/header/identity.ts +71 -0
- package/extensions/bytefly/header/register.ts +9 -3
- package/extensions/bytefly/index.ts +13 -5
- package/extensions/bytefly/message/format.ts +20 -13
- package/extensions/bytefly/message/register.ts +8 -7
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +46 -25
- package/extensions/bytefly/panel/format.ts +49 -14
- package/extensions/bytefly/panel/join.ts +35 -9
- package/extensions/bytefly/panel/settings.ts +29 -39
- package/extensions/bytefly/panel/skills.ts +51 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +7 -61
- package/extensions/bytefly/panel/tui/frame.ts +9 -1
- package/extensions/bytefly/panel/tui/list-view.ts +72 -0
- package/extensions/bytefly/panel/tui/nav.ts +71 -3
- package/extensions/bytefly/panel/tui/settings-section.ts +250 -0
- package/extensions/bytefly/panel/tui/skills-view.ts +50 -0
- package/extensions/bytefly/panel/tui.ts +223 -331
- package/extensions/bytefly/panel/types.ts +26 -8
- package/extensions/bytefly/picker/discovery.ts +32 -27
- package/extensions/bytefly/picker/resources.ts +16 -8
- package/extensions/bytefly/picker/runtime.ts +62 -34
- package/extensions/bytefly/picker/toggle.ts +16 -10
- package/extensions/bytefly/picker/types.ts +31 -13
- package/extensions/bytefly/update/format.ts +66 -8
- package/extensions/bytefly/update/patch.ts +66 -0
- package/extensions/bytefly/update/register.ts +4 -11
- package/extensions/bytefly/update/release.ts +65 -0
- package/extensions/bytefly/update/state.ts +14 -0
- package/extensions/bytefly/update/suppress.ts +47 -29
- package/extensions/bytefly/update/types.ts +15 -5
- package/extensions/check/tui.ts +2 -0
- package/extensions/context/README.md +21 -11
- package/extensions/context/format.ts +10 -8
- package/extensions/context/report.ts +21 -8
- package/extensions/context/tui.ts +26 -10
- package/extensions/context/types.ts +5 -3
- package/extensions/file-browse/README.md +89 -22
- package/extensions/file-browse/chrome.ts +83 -0
- package/extensions/file-browse/client.ts +74 -459
- package/extensions/file-browse/config.ts +12 -3
- package/extensions/file-browse/diff-html.ts +94 -0
- package/extensions/file-browse/html.ts +26 -152
- package/extensions/file-browse/live-client.ts +47 -1
- package/extensions/file-browse/live.ts +35 -14
- package/extensions/file-browse/palette.ts +224 -0
- package/extensions/file-browse/rev.ts +42 -0
- package/extensions/file-browse/router.ts +27 -6
- package/extensions/file-browse/server.ts +7 -0
- package/extensions/file-browse/styles.ts +10 -3
- package/extensions/file-browse/tree.ts +381 -0
- package/extensions/file-read/README.md +4 -1
- package/extensions/file-read/extract/odt.ts +7 -5
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/goal/tools.ts +6 -11
- package/extensions/job/README.md +7 -5
- package/extensions/job/index.ts +35 -52
- package/extensions/job/tools.ts +51 -77
- package/extensions/memory/README.md +3 -3
- package/extensions/memory/tools.ts +4 -6
- package/extensions/memory/tui.ts +5 -1
- package/extensions/plan/README.md +333 -84
- package/extensions/plan/commands.ts +73 -7
- package/extensions/plan/feedback.ts +300 -63
- package/extensions/plan/index.ts +1 -1
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plan-text.ts +18 -1
- package/extensions/plan/plans.ts +184 -58
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +95 -95
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/split.ts +87 -0
- package/extensions/plan/steps.ts +123 -107
- package/extensions/plan/tools/cancel.ts +79 -0
- package/extensions/plan/tools/enter.ts +17 -12
- package/extensions/plan/tools/exit.ts +148 -59
- package/extensions/plan/tools/steps.ts +7 -19
- package/extensions/plan/tools/write.ts +29 -31
- package/extensions/plan/tools.ts +6 -3
- package/extensions/plan/tui.ts +60 -15
- package/extensions/plan/types.ts +45 -3
- package/extensions/plan/web-chrome.ts +102 -0
- package/extensions/plan/web-client.ts +177 -446
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-markdown.ts +222 -0
- package/extensions/plan/web-page.ts +131 -19
- package/extensions/plan/web-router.ts +148 -32
- package/extensions/plan/web-share-session.ts +197 -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 +193 -45
- package/extensions/question/README.md +3 -0
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/checklist.ts +4 -0
- package/extensions/rewind/review-tui.ts +3 -1
- package/extensions/rewind/rewind-tui.ts +4 -0
- package/extensions/session-bus/README.md +247 -0
- package/extensions/session-bus/commands.ts +165 -0
- package/extensions/session-bus/format.ts +124 -0
- package/extensions/session-bus/index.ts +69 -0
- package/extensions/session-bus/notice.ts +58 -0
- package/extensions/session-bus/protocol.ts +134 -0
- package/extensions/session-bus/result.ts +243 -0
- package/extensions/session-bus/rows.ts +169 -0
- package/extensions/session-bus/runtime.ts +575 -0
- package/extensions/session-bus/store.ts +329 -0
- package/extensions/session-bus/team.ts +102 -0
- package/extensions/session-bus/tools.ts +227 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/README.md +26 -13
- package/extensions/subagent/format.ts +72 -25
- package/extensions/subagent/index.ts +21 -47
- package/extensions/subagent/orchestrate.ts +18 -41
- package/extensions/subagent/render.ts +29 -42
- package/extensions/subagent/stream.ts +24 -18
- package/extensions/subagent/types.ts +7 -0
- package/extensions/todo/tools.ts +5 -6
- package/extensions/todo/tui.ts +5 -1
- package/extensions/watch/README.md +9 -5
- package/extensions/watch/index.ts +25 -44
- package/extensions/watch/tools.ts +31 -59
- package/extensions/wechat/README.md +45 -36
- package/extensions/wechat/bridge.ts +46 -48
- package/extensions/wechat/client.ts +1 -4
- package/extensions/wechat/commands.ts +3 -3
- package/extensions/wechat/deliver.ts +57 -37
- package/extensions/wechat/interaction.ts +54 -18
- package/extensions/wechat/login-screen.ts +2 -3
- package/extensions/wechat/notices.ts +9 -7
- package/extensions/wechat/peer-worker.ts +19 -34
- package/extensions/wechat/presence.ts +3 -4
- package/extensions/wechat/prompt.ts +53 -23
- package/extensions/wechat/reply-pipeline.ts +12 -4
- package/extensions/wechat/slash.ts +14 -0
- package/extensions/wechat/status-screen.ts +2 -2
- package/extensions/wechat/tools.ts +3 -11
- package/extensions/worktree/README.md +48 -12
- package/extensions/worktree/commands.ts +109 -65
- package/extensions/worktree/enter.ts +1 -1
- package/extensions/worktree/exit.ts +11 -1
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/lifecycle.ts +9 -1
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/screen.ts +263 -0
- package/extensions/worktree/status.ts +84 -40
- package/lib/README.md +12 -7
- package/lib/claims/overlap.ts +43 -0
- package/lib/config.ts +12 -0
- package/lib/format.ts +6 -2
- package/lib/http/router.ts +40 -13
- package/lib/http/server.ts +27 -13
- package/lib/interaction.ts +28 -1
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/plans/sections.ts +172 -0
- package/lib/plans/setting.ts +52 -0
- package/lib/plans/split.ts +215 -0
- package/lib/plans/store.ts +19 -0
- package/lib/plans/team.ts +151 -0
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +117 -25
- package/lib/requirements.ts +1 -2
- package/lib/tool-result.ts +54 -4
- package/lib/tui/README.md +30 -8
- package/lib/tui/chrome.ts +27 -6
- package/lib/tui/dock-screen.ts +91 -19
- package/lib/tui/gauge.ts +9 -5
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/usage.ts +26 -0
- package/lib/web/README.md +131 -0
- package/lib/web/base.ts +19 -2
- package/lib/web/components.ts +10 -6
- package/lib/web/layout.ts +2 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +103 -25
- package/lib/web/tokens.ts +23 -7
- package/package.json +5 -2
- package/skills/changelog/SKILL.md +57 -45
- 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 +209 -0
- package/skills/parallel-dev/SKILL.md +150 -0
- package/extensions/wechat/qrcode.ts +0 -46
package/extensions/plan/steps.ts
CHANGED
|
@@ -10,73 +10,111 @@
|
|
|
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 {
|
|
22
|
+
extractSections as extractPlanSections,
|
|
23
|
+
FENCE,
|
|
24
|
+
HEADING,
|
|
25
|
+
normalizeHeadingText,
|
|
26
|
+
} from "../../lib/plans/sections.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The section machinery is `lib/plans/sections.ts`, because the split writes
|
|
30
|
+
* `## Team` with the same fence-aware scan this module navigates with. These
|
|
31
|
+
* re-exports keep the plan extension's public surface one import.
|
|
32
|
+
*/
|
|
33
|
+
export {
|
|
34
|
+
extractSections as extractPlanSections,
|
|
35
|
+
FENCE,
|
|
36
|
+
HEADING,
|
|
37
|
+
normalizeHeadingText,
|
|
38
|
+
replaceSection,
|
|
39
|
+
} from "../../lib/plans/sections.ts";
|
|
40
|
+
export type { PlanSection } from "../../lib/plans/sections.ts";
|
|
41
|
+
|
|
20
42
|
/** Most steps seeded from one plan. */
|
|
21
43
|
export const MAX_STEPS = 50;
|
|
22
44
|
/** Longest seeded step text; longer lines are elided. */
|
|
23
45
|
export const MAX_STEP_LENGTH = 200;
|
|
24
46
|
|
|
25
47
|
/**
|
|
26
|
-
* The outline a plan is guided to follow
|
|
27
|
-
*
|
|
48
|
+
* The outline a plan is guided to follow: one job per section, and every example
|
|
49
|
+
* is the shape wanted rather than a description of it. `Visual` is the one
|
|
50
|
+
* section that carries the end state — the same surface before and after when the
|
|
51
|
+
* change alters one that already exists, one frame when it is new — in a fence
|
|
52
|
+
* that stays untagged unless every line is code, so the reviewer sees the result
|
|
53
|
+
* before the steps that build it. Guidance only: `write_plan` never rejects or
|
|
54
|
+
* rewrites a plan that omits a section, and a change with no visible surface drops
|
|
55
|
+
* `Visual`.
|
|
28
56
|
*/
|
|
29
57
|
export const PLAN_OUTLINE = [
|
|
30
58
|
"# <Title>",
|
|
31
59
|
"",
|
|
32
60
|
"## Goal",
|
|
33
|
-
"
|
|
61
|
+
"One sentence: the outcome this change delivers, and what it deliberately does not do.",
|
|
34
62
|
"",
|
|
35
63
|
"## Context",
|
|
36
|
-
"
|
|
64
|
+
"- `src/load.ts:42` reads the config once at startup; that is the only caller.",
|
|
65
|
+
"- The file is user-editable, so a bad key must name the file and line.",
|
|
37
66
|
"",
|
|
38
67
|
"## Approach",
|
|
39
|
-
"
|
|
68
|
+
"- Parse into a typed record in `parseConfig`; `load()` keeps the file read.",
|
|
69
|
+
"- Rejected: validating inside `load()`, because the test would need a filesystem.",
|
|
70
|
+
"",
|
|
71
|
+
"## Visual",
|
|
72
|
+
"A bad key at startup, before and after:",
|
|
73
|
+
"```",
|
|
74
|
+
"$ bun start",
|
|
75
|
+
'config.json:12: ignoring unknown key "timeout_ms"',
|
|
76
|
+
"```",
|
|
77
|
+
"",
|
|
78
|
+
"```",
|
|
79
|
+
"$ bun start",
|
|
80
|
+
'config.json:12: unknown key "timeout_ms" — did you mean "timeoutMs"?',
|
|
81
|
+
"```",
|
|
40
82
|
"",
|
|
41
83
|
"## Steps",
|
|
42
84
|
"- [ ] Add `parseConfig` in `src/config.ts`, with a test for a missing key",
|
|
43
|
-
"-
|
|
85
|
+
" - `load()` is the only caller today; it keeps the file read.",
|
|
86
|
+
"- [ ] Report a bad key with its file and line, and note the rule in the README",
|
|
44
87
|
"",
|
|
45
88
|
"## Risks",
|
|
46
|
-
"
|
|
89
|
+
"- A missing key now fails at startup rather than on first use: name that in the README.",
|
|
90
|
+
"- Mitigated by the test in the first step.",
|
|
47
91
|
"",
|
|
48
92
|
"## Verification",
|
|
49
|
-
"
|
|
93
|
+
"```bash",
|
|
94
|
+
"bun test src/config.test.ts",
|
|
95
|
+
"bun run check",
|
|
96
|
+
"```",
|
|
50
97
|
].join("\n");
|
|
51
98
|
|
|
52
99
|
/** The outline's section titles, in order, for documentation and tests. */
|
|
53
|
-
export const PLAN_SECTIONS = ["Goal", "Context", "Approach", "Steps", "Risks", "Verification"] as const;
|
|
100
|
+
export const PLAN_SECTIONS = ["Goal", "Context", "Approach", "Visual", "Steps", "Risks", "Verification"] as const;
|
|
54
101
|
|
|
55
102
|
export interface PlanStep {
|
|
56
103
|
content: string;
|
|
57
104
|
status: "pending" | "completed";
|
|
58
105
|
}
|
|
59
106
|
|
|
60
|
-
/** A heading-delimited chunk of the plan, for the review screen's navigation. */
|
|
61
|
-
export interface PlanSection {
|
|
62
|
-
/** Plain heading text, or `""` for the content before the first heading. */
|
|
63
|
-
title: string;
|
|
64
|
-
/** The section's markdown, including its heading line. */
|
|
65
|
-
text: string;
|
|
66
|
-
/** 0-based source line of the section's heading (0 for the preamble). */
|
|
67
|
-
line: number;
|
|
68
|
-
/** Heading level, or 0 for the preamble before the first heading. */
|
|
69
|
-
level: number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
107
|
/** A top-level list item located in the source. */
|
|
73
108
|
interface PlanListItem {
|
|
74
|
-
/** Zero-based source line index. */
|
|
109
|
+
/** Zero-based source line index of the item's first line. */
|
|
75
110
|
line: number;
|
|
76
111
|
indent: number;
|
|
77
112
|
/** The list marker with its trailing whitespace, e.g. `1. ` or `- `. */
|
|
78
113
|
lead: string;
|
|
79
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Item text after the marker on its first line; indented continuation lines
|
|
116
|
+
* are folded into {@link step}, so a checkbox edit rewrites only this line.
|
|
117
|
+
*/
|
|
80
118
|
raw: string;
|
|
81
119
|
step: PlanStep;
|
|
82
120
|
}
|
|
@@ -84,39 +122,10 @@ interface PlanListItem {
|
|
|
84
122
|
/** A top-level numbered or bulleted list item, capturing its parts. */
|
|
85
123
|
const LIST_ITEM = /^([ \t]*)((?:\d+[.)]|[-*+])\s+)(.*\S)\s*$/;
|
|
86
124
|
const CHECKBOX = /^\[([ xX])\]\s*(.*)$/;
|
|
87
|
-
/**
|
|
88
|
-
* A markdown heading outside a fence, capturing its level and text: up to three
|
|
89
|
-
* leading spaces, a space after the hashes, and a closing hash run only when a
|
|
90
|
-
* space separates it. Shared with `blocks.ts`, so the section split and the
|
|
91
|
-
* browser outline read a heading line the same way, and `## C#` keeps its title.
|
|
92
|
-
*/
|
|
93
|
-
export const HEADING = /^ {0,3}(#{1,6})\s+(.*?)(?:\s+#+)?\s*$/;
|
|
94
|
-
/**
|
|
95
|
-
* A markdown code-fence opener or closer (backticks or tildes). Shared with
|
|
96
|
-
* `blocks.ts`: a tab-indented fence is prose for both parsers, never code for
|
|
97
|
-
* one and prose for the other.
|
|
98
|
-
*/
|
|
99
|
-
export const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
100
125
|
const DONE_MARKER = /\[DONE:\d+\]/gi;
|
|
101
126
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
* code removed, whitespace collapsed. Exported so `blocks.ts` labels an outline
|
|
105
|
-
* entry the same way `extractPlanSections` labels its section, which is what
|
|
106
|
-
* lets the browser mark a changed section.
|
|
107
|
-
*/
|
|
108
|
-
export function normalizeHeadingText(text: string): string {
|
|
109
|
-
return text
|
|
110
|
-
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
111
|
-
.replace(/\*([^*]+)\*/g, "$1")
|
|
112
|
-
.replace(/(?<![A-Za-z0-9_])_([^_]+)_(?![A-Za-z0-9_])/g, "$1")
|
|
113
|
-
.replace(/`([^`]+)`/g, "$1")
|
|
114
|
-
.replace(/\s+/g, " ")
|
|
115
|
-
.trim();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Strip inline markdown emphasis and code, then collapse to one clean line. */
|
|
119
|
-
function plainText(text: string): string {
|
|
127
|
+
/** A label with its inline markers and any done marker removed. */
|
|
128
|
+
function cleanLabel(text: string): string {
|
|
120
129
|
return normalizeHeadingText(text.replace(DONE_MARKER, ""));
|
|
121
130
|
}
|
|
122
131
|
|
|
@@ -132,7 +141,12 @@ function parseStep(raw: string): PlanStep {
|
|
|
132
141
|
}
|
|
133
142
|
if (/\[DONE:\d+\]/i.test(text)) done = true;
|
|
134
143
|
|
|
135
|
-
return { content:
|
|
144
|
+
return { content: cleanLabel(text), status: done ? "completed" : "pending" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Leading whitespace width of a line, for continuation indent checks. */
|
|
148
|
+
function indentOf(line: string): number {
|
|
149
|
+
return line.length - line.trimStart().length;
|
|
136
150
|
}
|
|
137
151
|
|
|
138
152
|
/** Every top-level list item, in order, before de-duplication and capping. */
|
|
@@ -140,22 +154,57 @@ function scanListItems(plan: string): PlanListItem[] {
|
|
|
140
154
|
const items: PlanListItem[] = [];
|
|
141
155
|
// Fences hide example lists (`- name: foo` in a YAML block) from extraction.
|
|
142
156
|
let fence: string | undefined;
|
|
157
|
+
// The item still collecting indented continuation lines, and those lines.
|
|
158
|
+
let current: { item: PlanListItem; continuation: string[] } | undefined;
|
|
159
|
+
|
|
160
|
+
/** Fold the collected continuation into the item, then close it. */
|
|
161
|
+
const finish = (): void => {
|
|
162
|
+
if (current === undefined) return;
|
|
163
|
+
if (current.continuation.length > 0) {
|
|
164
|
+
current.item.step = parseStep(`${current.item.raw} ${current.continuation.join(" ")}`);
|
|
165
|
+
}
|
|
166
|
+
current = undefined;
|
|
167
|
+
};
|
|
143
168
|
|
|
144
169
|
plan.split("\n").forEach((line, index) => {
|
|
145
170
|
const fenceMatch = line.match(FENCE);
|
|
146
171
|
if (fenceMatch) {
|
|
172
|
+
finish();
|
|
147
173
|
if (fence === undefined) fence = fenceMatch[1][0];
|
|
148
174
|
else if (fence === fenceMatch[1][0]) fence = undefined;
|
|
149
175
|
return;
|
|
150
176
|
}
|
|
151
177
|
if (fence !== undefined) return;
|
|
152
178
|
|
|
179
|
+
if (line.trim() === "") {
|
|
180
|
+
finish();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
153
184
|
const match = line.match(LIST_ITEM);
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
185
|
+
if (match) {
|
|
186
|
+
finish();
|
|
187
|
+
const step = parseStep(match[3]);
|
|
188
|
+
if (!step.content) return;
|
|
189
|
+
const item: PlanListItem = { line: index, indent: match[1].length, lead: match[2], raw: match[3], step };
|
|
190
|
+
items.push(item);
|
|
191
|
+
current = { item, continuation: [] };
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// A deeper, marker-less line continues the step's paragraph; a heading,
|
|
196
|
+
// a shallow line, or a blank line ends it. `raw` stays the first line so
|
|
197
|
+
// `setStepStatuses` edits the checkbox without touching these lines.
|
|
198
|
+
if (current !== undefined && indentOf(line) > current.item.indent && !HEADING.test(line)) {
|
|
199
|
+
const text = line.trim();
|
|
200
|
+
if (text) current.continuation.push(text);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
finish();
|
|
158
205
|
});
|
|
206
|
+
|
|
207
|
+
finish();
|
|
159
208
|
return items;
|
|
160
209
|
}
|
|
161
210
|
|
|
@@ -183,7 +232,7 @@ function stepsSection(plan: string): { start: number; end: number } | undefined
|
|
|
183
232
|
if (!heading) continue;
|
|
184
233
|
const depth = heading[1].length;
|
|
185
234
|
if (start < 0) {
|
|
186
|
-
if (
|
|
235
|
+
if (normalizeHeadingText(heading[2]).toLowerCase() !== "steps") continue;
|
|
187
236
|
start = index;
|
|
188
237
|
level = depth;
|
|
189
238
|
continue;
|
|
@@ -233,11 +282,12 @@ const LIST_PREFIX = /^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s*)?/;
|
|
|
233
282
|
* The plan's `Verification` section as one trailing checklist item, or
|
|
234
283
|
* `undefined` when the plan has none.
|
|
235
284
|
*
|
|
236
|
-
* The section is prose ("commands and checks")
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
285
|
+
* The section is prose ("commands and checks") or a fenced block of them, and a
|
|
286
|
+
* todo item is one line, so the fence lines are dropped, the rest is stripped of
|
|
287
|
+
* list markers and inline emphasis, and the lines are joined with `; `, prefixed
|
|
288
|
+
* with `Verify:` and elided like a long step. The item is deliberately *not* a
|
|
289
|
+
* plan step: it is not in the `Steps` section, so it must not count against the
|
|
290
|
+
* step cap or appear in `set_plan_steps` numbering.
|
|
241
291
|
*
|
|
242
292
|
* A plan with no `Steps` heading feeds its shallowest list as steps (the older
|
|
243
293
|
* rule), so that list already covers the verification bullets; appending a
|
|
@@ -252,7 +302,10 @@ export function verificationItem(plan: string): PlanStep | undefined {
|
|
|
252
302
|
const items = section.text
|
|
253
303
|
.split("\n")
|
|
254
304
|
.slice(1)
|
|
255
|
-
|
|
305
|
+
// A fenced block is how the outline asks for commands; its fence lines are
|
|
306
|
+
// punctuation, not a check, so only the commands inside it are carried.
|
|
307
|
+
.filter((line) => !FENCE.test(line))
|
|
308
|
+
.map((line) => cleanLabel(line.replace(LIST_PREFIX, "")))
|
|
256
309
|
.filter(Boolean);
|
|
257
310
|
if (items.length === 0) return undefined;
|
|
258
311
|
return { content: elide(`Verify: ${items.join("; ")}`), status: "pending" };
|
|
@@ -332,43 +385,6 @@ export function setStepStatuses(plan: string, updates: readonly StepEdit[]): str
|
|
|
332
385
|
return lines.join("\n");
|
|
333
386
|
}
|
|
334
387
|
|
|
335
|
-
/**
|
|
336
|
-
* Sections of a plan, split at heading lines outside code fences. The content
|
|
337
|
-
* before the first heading is a section titled `""`. Pi's `Markdown` renderer
|
|
338
|
-
* exposes no source-line map, so the review screen renders these chunks
|
|
339
|
-
* separately to know where each section begins.
|
|
340
|
-
*/
|
|
341
|
-
export function extractPlanSections(plan: string): PlanSection[] {
|
|
342
|
-
const sections: PlanSection[] = [];
|
|
343
|
-
let current: PlanSection | undefined;
|
|
344
|
-
let fence: string | undefined;
|
|
345
|
-
|
|
346
|
-
const flush = (): void => {
|
|
347
|
-
if (current && (current.title !== "" || current.text.trim() !== "")) sections.push(current);
|
|
348
|
-
current = undefined;
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
plan.split("\n").forEach((line, index) => {
|
|
352
|
-
const fenceMatch = line.match(FENCE);
|
|
353
|
-
if (fenceMatch) {
|
|
354
|
-
if (fence === undefined) fence = fenceMatch[1][0];
|
|
355
|
-
else if (fence === fenceMatch[1][0]) fence = undefined;
|
|
356
|
-
} else if (fence === undefined) {
|
|
357
|
-
const heading = line.match(HEADING);
|
|
358
|
-
if (heading) {
|
|
359
|
-
flush();
|
|
360
|
-
current = { title: plainText(heading[2]), text: line, line: index, level: heading[1].length };
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (current === undefined) current = { title: "", text: line, line: 0, level: 0 };
|
|
365
|
-
else current.text += `\n${line}`;
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
flush();
|
|
369
|
-
return sections;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
388
|
/**
|
|
373
389
|
* The plan's own objective: the first paragraph of its `## Goal` section, with
|
|
374
390
|
* inline markdown and a leading list marker removed.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cancel_plan_mode` tool registration.
|
|
3
|
+
*
|
|
4
|
+
* This is the escape hatch that does not need a plan file or a review dialog:
|
|
5
|
+
* when the user asks to leave planning, the model can restore write access
|
|
6
|
+
* without approving anything. The plan store is untouched; a later `/plan` can
|
|
7
|
+
* still review the saved artifact.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { Type } from "typebox";
|
|
12
|
+
import { reuseText, toolDetails } from "../../../lib/tui/chrome.ts";
|
|
13
|
+
import { paint } from "../../../lib/tui/tokens.ts";
|
|
14
|
+
import { callLine, detailsOrLine } from "../../../lib/tui/tool.ts";
|
|
15
|
+
import { failedResult } from "../../../lib/tool-result.ts";
|
|
16
|
+
import { CANCEL_TOOL, type PlanRuntime } from "../runtime.ts";
|
|
17
|
+
import type { CancelPlanModeDetails } from "../types.ts";
|
|
18
|
+
|
|
19
|
+
const CANCELLED_TEXT =
|
|
20
|
+
"Plan mode is off and write access is restored. The plan was not approved; no todo items or goal were recorded. " +
|
|
21
|
+
"Any saved plan file remains for a later review.";
|
|
22
|
+
|
|
23
|
+
export function registerCancelTool(pi: ExtensionAPI, runtime: PlanRuntime): void {
|
|
24
|
+
pi.registerTool({
|
|
25
|
+
name: CANCEL_TOOL,
|
|
26
|
+
label: "Cancel plan mode",
|
|
27
|
+
description:
|
|
28
|
+
"Leave plan mode without approving a plan. Restores write access; no todo items or goal are recorded. " +
|
|
29
|
+
"Use when the user asks to exit, cancel, or abandon planning.",
|
|
30
|
+
promptSnippet: "Leave plan mode without approving the plan.",
|
|
31
|
+
promptGuidelines: [
|
|
32
|
+
"Call cancel_plan_mode when the user asks to leave or abandon planning without approving a plan.",
|
|
33
|
+
],
|
|
34
|
+
parameters: Type.Object({}),
|
|
35
|
+
defaultActive: false,
|
|
36
|
+
executionMode: "sequential",
|
|
37
|
+
// The mode change touches no files; like enter_plan_mode, it needs no
|
|
38
|
+
// destructive annotation.
|
|
39
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
40
|
+
|
|
41
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
42
|
+
if (!runtime.isEnabled()) {
|
|
43
|
+
return failedResult("Not in plan mode; there is nothing to cancel.", {
|
|
44
|
+
cancelled: false,
|
|
45
|
+
} satisfies CancelPlanModeDetails);
|
|
46
|
+
}
|
|
47
|
+
runtime.disable(ctx);
|
|
48
|
+
ctx.ui.notify("Plan mode off — full access restored. The plan was not approved.", "info");
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: CANCELLED_TEXT }],
|
|
51
|
+
details: { cancelled: true } satisfies CancelPlanModeDetails,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
renderCall(_args, theme, context) {
|
|
56
|
+
return callLine(theme, context?.lastComponent, CANCEL_TOOL, "left plan mode");
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
renderResult(result, options, theme, context) {
|
|
60
|
+
const text = reuseText(context?.lastComponent);
|
|
61
|
+
const outcome = detailsOrLine(
|
|
62
|
+
theme,
|
|
63
|
+
result,
|
|
64
|
+
options.expanded,
|
|
65
|
+
toolDetails<CancelPlanModeDetails>(result.details),
|
|
66
|
+
);
|
|
67
|
+
if ("line" in outcome) {
|
|
68
|
+
text.setText(outcome.line);
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
text.setText(
|
|
72
|
+
outcome.details.cancelled
|
|
73
|
+
? paint(theme, "warn", "Plan mode off — plan not approved")
|
|
74
|
+
: paint(theme, "meta", "Not in plan mode"),
|
|
75
|
+
);
|
|
76
|
+
return text;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -11,7 +11,9 @@ import { reuseText, toolDetails } from "../../../lib/tui/chrome.ts";
|
|
|
11
11
|
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
|
-
import {
|
|
14
|
+
import { failedResult } from "../../../lib/tool-result.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";
|
|
15
17
|
import { ENTER_TOOL, type PlanRuntime } from "../runtime.ts";
|
|
16
18
|
import type { EnterPlanModeDetails } from "../types.ts";
|
|
17
19
|
|
|
@@ -45,19 +47,18 @@ export function registerEnterTool(pi: ExtensionAPI, runtime: PlanRuntime): void
|
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
49
|
if (!ctx.hasUI) {
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
text: "No interactive UI is available to confirm entering plan mode. Ask the user whether they want a plan.",
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
details: { entered: false, unavailable: true } satisfies EnterPlanModeDetails,
|
|
56
|
-
isError: true,
|
|
57
|
-
};
|
|
50
|
+
return failedResult(
|
|
51
|
+
"No interactive UI is available to confirm entering plan mode. Ask the user whether they want a plan.",
|
|
52
|
+
{ entered: false, unavailable: true } satisfies EnterPlanModeDetails,
|
|
53
|
+
);
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
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);
|
|
61
62
|
if (!approved) {
|
|
62
63
|
return {
|
|
63
64
|
content: [{ type: "text", text: "Stayed in normal mode. Proceed, or ask the user how to continue." }],
|
|
@@ -65,6 +66,10 @@ export function registerEnterTool(pi: ExtensionAPI, runtime: PlanRuntime): void
|
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
runtime.enable(ctx);
|
|
69
|
+
if (isRemoteTurn(ctx)) {
|
|
70
|
+
// The peer UI has no footer chip; say the mode and its exit in the chat.
|
|
71
|
+
ctx.ui.notify("Plan mode on — write access is off. Say “exit plan mode” to leave without approving.", "info");
|
|
72
|
+
}
|
|
68
73
|
return {
|
|
69
74
|
content: [{ type: "text", text: ENTERED_TEXT }],
|
|
70
75
|
details: { entered: true } satisfies EnterPlanModeDetails,
|