@oh-my-pi/pi-coding-agent 8.4.0 → 8.4.1
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/CHANGELOG.md +11 -0
- package/package.json +6 -6
- package/scripts/format-prompts.ts +65 -23
- package/src/commit/agentic/prompts/session-user.md +0 -1
- package/src/commit/agentic/prompts/split-confirm.md +1 -1
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/prompts/analysis-system.md +23 -26
- package/src/commit/prompts/analysis-user.md +1 -1
- package/src/commit/prompts/changelog-system.md +1 -2
- package/src/commit/prompts/changelog-user.md +1 -2
- package/src/commit/prompts/file-observer-system.md +1 -3
- package/src/commit/prompts/file-observer-user.md +1 -2
- package/src/commit/prompts/reduce-system.md +16 -16
- package/src/commit/prompts/reduce-user.md +1 -1
- package/src/commit/prompts/summary-retry.md +1 -2
- package/src/commit/prompts/summary-system.md +10 -10
- package/src/commit/prompts/summary-user.md +1 -1
- package/src/commit/prompts/types-description.md +1 -1
- package/src/config/keybindings.ts +3 -0
- package/src/config/settings-manager.ts +5 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/plan-protocol.ts +95 -0
- package/src/modes/components/status-line/presets.ts +7 -7
- package/src/modes/components/status-line/segments.ts +16 -0
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +16 -2
- package/src/modes/controllers/command-controller.ts +42 -0
- package/src/modes/controllers/event-controller.ts +13 -0
- package/src/modes/controllers/input-controller.ts +16 -0
- package/src/modes/interactive-mode.ts +219 -1
- package/src/modes/theme/theme.ts +7 -0
- package/src/modes/types.ts +7 -0
- package/src/patch/index.ts +9 -3
- package/src/plan-mode/state.ts +6 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/frontmatter.md +1 -1
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/plan.md +1 -12
- package/src/prompts/agents/reviewer.md +7 -7
- package/src/prompts/agents/task.md +1 -2
- package/src/prompts/compaction/branch-summary-preamble.md +1 -1
- package/src/prompts/compaction/branch-summary.md +3 -1
- package/src/prompts/compaction/compaction-summary.md +3 -1
- package/src/prompts/compaction/compaction-turn-prefix.md +2 -1
- package/src/prompts/compaction/compaction-update-summary.md +3 -1
- package/src/prompts/review-request.md +4 -1
- package/src/prompts/system/custom-system-prompt.md +8 -8
- package/src/prompts/system/file-operations.md +1 -1
- package/src/prompts/system/plan-mode-active.md +136 -0
- package/src/prompts/system/plan-mode-approved.md +11 -0
- package/src/prompts/system/plan-mode-reference.md +13 -0
- package/src/prompts/system/plan-mode-subagent.md +38 -0
- package/src/prompts/system/summarization-system.md +1 -1
- package/src/prompts/system/system-prompt.md +17 -27
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/system/web-search.md +1 -1
- package/src/prompts/tools/ask.md +1 -3
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/calculator.md +1 -1
- package/src/prompts/tools/enter-plan-mode.md +73 -0
- package/src/prompts/tools/exit-plan-mode.md +23 -0
- package/src/prompts/tools/fetch.md +1 -1
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/gemini-image.md +1 -1
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/patch.md +1 -3
- package/src/prompts/tools/python.md +2 -4
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/replace.md +16 -16
- package/src/prompts/tools/ssh.md +1 -4
- package/src/prompts/tools/task.md +1 -3
- package/src/prompts/tools/todo-write.md +13 -16
- package/src/prompts/tools/web-search.md +1 -1
- package/src/prompts/tools/write.md +1 -1
- package/src/sdk.ts +61 -10
- package/src/session/agent-session.ts +267 -0
- package/src/task/executor.ts +1 -0
- package/src/task/index.ts +18 -4
- package/src/tools/enter-plan-mode.ts +76 -0
- package/src/tools/exit-plan-mode.ts +62 -0
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +13 -12
- package/src/tools/index.ts +19 -1
- package/src/tools/plan-mode-guard.ts +46 -0
- package/src/tools/read.ts +8 -4
- package/src/tools/write.ts +3 -2
- package/src/utils/tools-manager.ts +38 -9
- package/src/web/search/providers/perplexity.ts +3 -1
- package/src/web/search/types.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [8.4.1] - 2026-01-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added core plan mode with plan file approval workflow and tool gating
|
|
9
|
+
- Added plan:// internal URLs for plan file access and subagent plan-mode system prompt
|
|
10
|
+
- Added plan mode toggle shortcut with paused status indicator
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fixed plan reference injection and workflow prompt parameters for plan mode
|
|
14
|
+
- Fixed tool downloads hanging on slow/blocked GitHub by adding timeouts and zip extraction fallback
|
|
15
|
+
- Fixed missing UI notification when tools are downloaded or installed on demand
|
|
5
16
|
## [8.4.0] - 2026-01-25
|
|
6
17
|
|
|
7
18
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"test": "bun test"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@oh-my-pi/omp-stats": "8.4.
|
|
79
|
-
"@oh-my-pi/pi-agent-core": "8.4.
|
|
80
|
-
"@oh-my-pi/pi-ai": "8.4.
|
|
81
|
-
"@oh-my-pi/pi-tui": "8.4.
|
|
82
|
-
"@oh-my-pi/pi-utils": "8.4.
|
|
78
|
+
"@oh-my-pi/omp-stats": "8.4.1",
|
|
79
|
+
"@oh-my-pi/pi-agent-core": "8.4.1",
|
|
80
|
+
"@oh-my-pi/pi-ai": "8.4.1",
|
|
81
|
+
"@oh-my-pi/pi-tui": "8.4.1",
|
|
82
|
+
"@oh-my-pi/pi-utils": "8.4.1",
|
|
83
83
|
"@openai/agents": "^0.4.3",
|
|
84
84
|
"@sinclair/typebox": "^0.34.46",
|
|
85
85
|
"ajv": "^8.17.1",
|
|
@@ -3,16 +3,22 @@
|
|
|
3
3
|
* Format prompt files (mixed XML + Markdown + Handlebars).
|
|
4
4
|
*
|
|
5
5
|
* Rules:
|
|
6
|
-
* 1. No blank line
|
|
6
|
+
* 1. No blank line before list items
|
|
7
7
|
* 2. No blank line after opening XML tag or Handlebars block
|
|
8
8
|
* 3. No blank line before closing XML tag or Handlebars block
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
9
|
+
* 4. Strip leading whitespace from closing XML tags and Handlebars (lines starting with {{)
|
|
10
|
+
* 5. Compact markdown tables (remove padding)
|
|
11
|
+
* 6. Collapse 2+ blank lines to single blank line
|
|
12
|
+
* 7. Trim trailing whitespace (preserve indentation)
|
|
13
|
+
* 8. No trailing newline at EOF
|
|
12
14
|
*/
|
|
13
15
|
import { Glob } from "bun";
|
|
14
16
|
|
|
15
17
|
const PROMPTS_DIR = new URL("../src/prompts/", import.meta.url).pathname;
|
|
18
|
+
const COMMIT_PROMPTS_DIR = new URL("../src/commit/prompts/", import.meta.url).pathname;
|
|
19
|
+
const AGENTIC_PROMPTS_DIR = new URL("../src/commit/agentic/prompts/", import.meta.url).pathname;
|
|
20
|
+
|
|
21
|
+
const PROMPT_DIRS = [PROMPTS_DIR, COMMIT_PROMPTS_DIR, AGENTIC_PROMPTS_DIR];
|
|
16
22
|
|
|
17
23
|
// Opening XML tag (not self-closing, not closing)
|
|
18
24
|
const OPENING_XML = /^<([a-z_-]+)(?:\s+[^>]*)?>$/;
|
|
@@ -22,12 +28,38 @@ const CLOSING_XML = /^<\/([a-z_-]+)>$/;
|
|
|
22
28
|
const OPENING_HBS = /^\{\{#/;
|
|
23
29
|
// Handlebars block end: {{/if}}, {{/has}}, {{/list}}, etc.
|
|
24
30
|
const CLOSING_HBS = /^\{\{\//;
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
// List item or Handlebars conditional that acts like list
|
|
28
|
-
const LIST_OR_BLOCK = /^(\s*)[-*]|\d+\.\s|^\{\{#/;
|
|
31
|
+
// List item (- or * or 1.)
|
|
32
|
+
const LIST_ITEM = /^[-*]|\d+\.\s/;
|
|
29
33
|
// Code fence
|
|
30
34
|
const CODE_FENCE = /^```/;
|
|
35
|
+
// Table row
|
|
36
|
+
const TABLE_ROW = /^\|.*\|$/;
|
|
37
|
+
// Table separator (|---|---|)
|
|
38
|
+
const TABLE_SEP = /^\|[-:\s|]+\|$/;
|
|
39
|
+
|
|
40
|
+
/** Compact a table row by trimming cell padding */
|
|
41
|
+
function compactTableRow(line: string): string {
|
|
42
|
+
// Split by |, trim each cell, rejoin
|
|
43
|
+
const cells = line.split("|");
|
|
44
|
+
return cells.map((c) => c.trim()).join("|");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Compact a table separator row */
|
|
48
|
+
function compactTableSep(line: string): string {
|
|
49
|
+
// Normalize to minimal |---|---|
|
|
50
|
+
const cells = line.split("|").filter((c) => c.trim());
|
|
51
|
+
const normalized = cells.map((c) => {
|
|
52
|
+
const trimmed = c.trim();
|
|
53
|
+
// Preserve alignment markers
|
|
54
|
+
const left = trimmed.startsWith(":");
|
|
55
|
+
const right = trimmed.endsWith(":");
|
|
56
|
+
if (left && right) return ":---:";
|
|
57
|
+
if (left) return ":---";
|
|
58
|
+
if (right) return "---:";
|
|
59
|
+
return "---";
|
|
60
|
+
});
|
|
61
|
+
return "|" + normalized.join("|") + "|";
|
|
62
|
+
}
|
|
31
63
|
|
|
32
64
|
function formatPrompt(content: string): string {
|
|
33
65
|
const lines = content.split("\n");
|
|
@@ -37,9 +69,6 @@ function formatPrompt(content: string): string {
|
|
|
37
69
|
for (let i = 0; i < lines.length; i++) {
|
|
38
70
|
let line = lines[i];
|
|
39
71
|
|
|
40
|
-
// Trim trailing whitespace (preserve leading)
|
|
41
|
-
line = line.trimEnd();
|
|
42
|
-
|
|
43
72
|
const trimmed = line.trim();
|
|
44
73
|
|
|
45
74
|
// Track code blocks - don't modify inside them
|
|
@@ -54,6 +83,20 @@ function formatPrompt(content: string): string {
|
|
|
54
83
|
continue;
|
|
55
84
|
}
|
|
56
85
|
|
|
86
|
+
// Strip leading whitespace from closing XML tags and Handlebars
|
|
87
|
+
if (CLOSING_XML.test(trimmed) || trimmed.startsWith("{{")) {
|
|
88
|
+
line = trimmed;
|
|
89
|
+
} else if (TABLE_SEP.test(trimmed)) {
|
|
90
|
+
// Compact table separator
|
|
91
|
+
line = compactTableSep(trimmed);
|
|
92
|
+
} else if (TABLE_ROW.test(trimmed)) {
|
|
93
|
+
// Compact table row
|
|
94
|
+
line = compactTableRow(trimmed);
|
|
95
|
+
} else {
|
|
96
|
+
// Trim trailing whitespace (preserve leading for non-closing-tags)
|
|
97
|
+
line = line.trimEnd();
|
|
98
|
+
}
|
|
99
|
+
|
|
57
100
|
const isBlank = trimmed === "";
|
|
58
101
|
|
|
59
102
|
// Skip blank lines that violate our rules
|
|
@@ -61,8 +104,8 @@ function formatPrompt(content: string): string {
|
|
|
61
104
|
const prevLine = result[result.length - 1]?.trim() ?? "";
|
|
62
105
|
const nextLine = lines[i + 1]?.trim() ?? "";
|
|
63
106
|
|
|
64
|
-
// Rule 1: No blank
|
|
65
|
-
if (
|
|
107
|
+
// Rule 1: No blank line before list items
|
|
108
|
+
if (LIST_ITEM.test(nextLine)) {
|
|
66
109
|
continue;
|
|
67
110
|
}
|
|
68
111
|
|
|
@@ -93,11 +136,10 @@ function formatPrompt(content: string): string {
|
|
|
93
136
|
result.push(line);
|
|
94
137
|
}
|
|
95
138
|
|
|
96
|
-
// Rule
|
|
139
|
+
// Rule 8: No trailing newline at EOF
|
|
97
140
|
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
|
98
141
|
result.pop();
|
|
99
142
|
}
|
|
100
|
-
result.push("");
|
|
101
143
|
|
|
102
144
|
return result.join("\n");
|
|
103
145
|
}
|
|
@@ -106,24 +148,24 @@ async function main() {
|
|
|
106
148
|
const glob = new Glob("**/*.md");
|
|
107
149
|
const files: string[] = [];
|
|
108
150
|
let changed = 0;
|
|
151
|
+
const check = process.argv.includes("--check");
|
|
109
152
|
|
|
110
|
-
for
|
|
111
|
-
|
|
153
|
+
for (const dir of PROMPT_DIRS) {
|
|
154
|
+
for await (const path of glob.scan(dir)) {
|
|
155
|
+
files.push(`${dir}${path}`);
|
|
156
|
+
}
|
|
112
157
|
}
|
|
113
158
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
for (const relativePath of files) {
|
|
117
|
-
const fullPath = `${PROMPTS_DIR}${relativePath}`;
|
|
159
|
+
for (const fullPath of files) {
|
|
118
160
|
const original = await Bun.file(fullPath).text();
|
|
119
161
|
const formatted = formatPrompt(original);
|
|
120
162
|
|
|
121
163
|
if (original !== formatted) {
|
|
122
164
|
if (check) {
|
|
123
|
-
console.log(`Would format: ${
|
|
165
|
+
console.log(`Would format: ${fullPath}`);
|
|
124
166
|
} else {
|
|
125
167
|
await Bun.write(fullPath, formatted);
|
|
126
|
-
console.log(`Formatted: ${
|
|
168
|
+
console.log(`Formatted: ${fullPath}`);
|
|
127
169
|
}
|
|
128
170
|
changed++;
|
|
129
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Split commit plan has {{count}} commits. Proceed? (y/N):
|
|
1
|
+
Split commit plan has {{count}} commits. Proceed? (y/N):
|
|
@@ -37,4 +37,4 @@ Tool guidance:
|
|
|
37
37
|
## Changelog Requirements
|
|
38
38
|
|
|
39
39
|
If changelog targets are provided, you MUST call `propose_changelog` before finishing.
|
|
40
|
-
If you propose a split commit plan, include changelog target files in the relevant commit changes.
|
|
40
|
+
If you propose a split commit plan, include changelog target files in the relevant commit changes.
|
|
@@ -4,7 +4,6 @@ You are a senior release engineer who writes precise, changelog-ready commit cla
|
|
|
4
4
|
|
|
5
5
|
<instructions>
|
|
6
6
|
Classify this git diff into conventional commit format. Get this right — it affects release notes and semantic versioning.
|
|
7
|
-
|
|
8
7
|
## 1. Determine Scope
|
|
9
8
|
|
|
10
9
|
Apply scope when 60%+ of line changes target a single component:
|
|
@@ -16,7 +15,6 @@ Use null for: cross-cutting changes, no dominant component, project-wide refacto
|
|
|
16
15
|
Forbidden scopes (use null): src, lib, include, tests, benches, examples, docs, project name, app, main, entire, all, misc.
|
|
17
16
|
|
|
18
17
|
Prefer scopes from <common_scopes> over inventing new ones.
|
|
19
|
-
|
|
20
18
|
## 2. Generate Details (0-6 items)
|
|
21
19
|
|
|
22
20
|
Each detail:
|
|
@@ -39,17 +37,16 @@ Priority: user-visible -> perf/security -> architecture -> internal.
|
|
|
39
37
|
Exclude: import changes, whitespace, formatting, trivial renames, debug prints, comment-only, file moves without modification.
|
|
40
38
|
|
|
41
39
|
State only visible rationale. If unclear, use neutral: "Updated logic for correctness."
|
|
42
|
-
|
|
43
40
|
## 3. Assign Changelog Metadata
|
|
44
41
|
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
42
|
+
|Condition|changelog_category|
|
|
43
|
+
|---|---|
|
|
44
|
+
|New public API, feature, capability|"Added"|
|
|
45
|
+
|Modified existing behavior|"Changed"|
|
|
46
|
+
|Bug fix, correction|"Fixed"|
|
|
47
|
+
|Feature marked for removal|"Deprecated"|
|
|
48
|
+
|Feature/API removed|"Removed"|
|
|
49
|
+
|Security fix or improvement|"Security"|
|
|
53
50
|
|
|
54
51
|
user_visible: true for: new features, APIs, breaking changes, user-affecting bug fixes, user-facing docs, security fixes.
|
|
55
52
|
|
|
@@ -62,20 +59,20 @@ Omit changelog_category when user_visible is false.
|
|
|
62
59
|
Call create_conventional_analysis with:
|
|
63
60
|
|
|
64
61
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
62
|
+
"type": "feat|fix|refactor|docs|test|chore|style|perf|build|ci|revert",
|
|
63
|
+
"scope": "component-name" | null,
|
|
64
|
+
"details": [
|
|
65
|
+
{
|
|
66
|
+
"text": "Past-tense description ending with period.",
|
|
67
|
+
"changelog_category": "Added|Changed|Fixed|Deprecated|Removed|Security",
|
|
68
|
+
"user_visible": true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"text": "Internal change description.",
|
|
72
|
+
"user_visible": false
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"issue_refs": []
|
|
79
76
|
}
|
|
80
77
|
</output_format>
|
|
81
78
|
|
|
@@ -152,4 +149,4 @@ Call create_conventional_analysis with:
|
|
|
152
149
|
</example>
|
|
153
150
|
</examples>
|
|
154
151
|
|
|
155
|
-
Be thorough. This matters.
|
|
152
|
+
Be thorough. This matters.
|
|
@@ -2,7 +2,6 @@ You are an expert changelog writer who analyzes git diffs and produces Keep a Ch
|
|
|
2
2
|
|
|
3
3
|
<instructions>
|
|
4
4
|
Analyze the diff and return JSON changelog entries.
|
|
5
|
-
|
|
6
5
|
1. Identify user-visible changes only
|
|
7
6
|
2. Categorize each change (Added, Changed, Deprecated, Removed, Fixed, Security, Breaking Changes)
|
|
8
7
|
3. Write entries starting with past-tense verb describing user impact
|
|
@@ -53,4 +52,4 @@ Return ONLY valid JSON. No markdown fences, no explanation.
|
|
|
53
52
|
|
|
54
53
|
With entries: {"entries": {"Added": ["entry 1"], "Fixed": ["entry 2"]}}
|
|
55
54
|
No changelog-worthy changes: {"entries": {}}
|
|
56
|
-
</output_format>
|
|
55
|
+
</output_format>
|
|
@@ -3,7 +3,6 @@ Changelog: {{ changelog_path }}
|
|
|
3
3
|
{{#if is_package_changelog}}Scope: Package-level changelog. Omit package name prefix from entries.{{/if}}
|
|
4
4
|
</context>
|
|
5
5
|
{{#if existing_entries}}
|
|
6
|
-
|
|
7
6
|
<existing_entries>
|
|
8
7
|
Already documented—skip these:
|
|
9
8
|
{{ existing_entries }}
|
|
@@ -16,4 +15,4 @@ Already documented—skip these:
|
|
|
16
15
|
|
|
17
16
|
<diff>
|
|
18
17
|
{{ diff }}
|
|
19
|
-
</diff>
|
|
18
|
+
</diff>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
<instructions>
|
|
4
4
|
Extract factual observations from the diff. This matters—be precise.
|
|
5
|
-
|
|
6
5
|
1. Use past-tense verb + specific target + optional purpose
|
|
7
6
|
2. Max 100 characters per observation
|
|
8
7
|
3. Consolidate related changes (e.g., "renamed 5 helper functions")
|
|
@@ -17,10 +16,9 @@ Exclude: import reordering, whitespace/formatting, comment-only changes, debug s
|
|
|
17
16
|
|
|
18
17
|
<output_format>
|
|
19
18
|
Plain list, no preamble, no summary, no markdown formatting.
|
|
20
|
-
|
|
21
19
|
- added 'parse_config()' function for TOML configuration loading
|
|
22
20
|
- removed deprecated 'legacy_init()' and all callers
|
|
23
21
|
- changed 'Connection::new()' to accept '&Config' instead of individual params
|
|
24
22
|
</output_format>
|
|
25
23
|
|
|
26
|
-
Observations only. Classification happens in reduce phase.
|
|
24
|
+
Observations only. Classification happens in reduce phase.
|
|
@@ -41,20 +41,20 @@ Input observations:
|
|
|
41
41
|
|
|
42
42
|
Output:
|
|
43
43
|
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
"type": "fix",
|
|
45
|
+
"scope": "api",
|
|
46
|
+
"details": [
|
|
47
|
+
{
|
|
48
|
+
"text": "Added token refresh guard to prevent duplicate refreshes.",
|
|
49
|
+
"changelog_category": "Fixed",
|
|
50
|
+
"user_visible": true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"text": "Introduced retry wrapper for 429 responses.",
|
|
54
|
+
"changelog_category": "Fixed",
|
|
55
|
+
"user_visible": true
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"issue_refs": []
|
|
59
59
|
}
|
|
60
|
-
</example>
|
|
60
|
+
</example>
|
|
@@ -15,15 +15,15 @@ Get this right.
|
|
|
15
15
|
</instructions>
|
|
16
16
|
|
|
17
17
|
<verb_reference>
|
|
18
|
-
|
|
|
19
|
-
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
18
|
+
|Type|Use instead|
|
|
19
|
+
|---|---|
|
|
20
|
+
|feat|added, introduced, implemented, enabled|
|
|
21
|
+
|fix|corrected, resolved, patched, addressed|
|
|
22
|
+
|refactor|restructured, reorganized, migrated, simplified|
|
|
23
|
+
|perf|optimized, reduced, eliminated, accelerated|
|
|
24
|
+
|docs|documented, clarified, expanded|
|
|
25
|
+
|build|upgraded, pinned, configured|
|
|
26
|
+
|chore|cleaned, removed, renamed, organized|
|
|
27
27
|
</verb_reference>
|
|
28
28
|
|
|
29
29
|
<examples>
|
|
@@ -49,4 +49,4 @@ comprehensive, various, several, improved, enhanced, quickly, simply, basically,
|
|
|
49
49
|
|
|
50
50
|
<output_format>
|
|
51
51
|
Output the description text only. Include motivation, name specifics, stay focused.
|
|
52
|
-
</output_format>
|
|
52
|
+
</output_format>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
Types: feat, fix, refactor, perf, docs, test, build, ci, chore, style, revert.
|
|
2
|
-
Format: <type>(<scope>): <summary> with past-tense summary.
|
|
2
|
+
Format: <type>(<scope>): <summary> with past-tense summary.
|
|
@@ -23,6 +23,7 @@ export type AppAction =
|
|
|
23
23
|
| "cycleModelForward"
|
|
24
24
|
| "cycleModelBackward"
|
|
25
25
|
| "selectModel"
|
|
26
|
+
| "togglePlanMode"
|
|
26
27
|
| "expandTools"
|
|
27
28
|
| "toggleThinking"
|
|
28
29
|
| "externalEditor"
|
|
@@ -55,6 +56,7 @@ export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
|
|
55
56
|
cycleModelForward: "ctrl+p",
|
|
56
57
|
cycleModelBackward: "shift+ctrl+p",
|
|
57
58
|
selectModel: "ctrl+l",
|
|
59
|
+
togglePlanMode: "alt+shift+p",
|
|
58
60
|
historySearch: "ctrl+r",
|
|
59
61
|
expandTools: "ctrl+o",
|
|
60
62
|
toggleThinking: "ctrl+t",
|
|
@@ -82,6 +84,7 @@ const APP_ACTIONS: AppAction[] = [
|
|
|
82
84
|
"cycleModelForward",
|
|
83
85
|
"cycleModelBackward",
|
|
84
86
|
"selectModel",
|
|
87
|
+
"togglePlanMode",
|
|
85
88
|
"historySearch",
|
|
86
89
|
"expandTools",
|
|
87
90
|
"toggleThinking",
|
|
@@ -155,6 +155,7 @@ export interface TodoCompletionSettings {
|
|
|
155
155
|
export type StatusLineSegmentId =
|
|
156
156
|
| "pi"
|
|
157
157
|
| "model"
|
|
158
|
+
| "plan_mode"
|
|
158
159
|
| "path"
|
|
159
160
|
| "git"
|
|
160
161
|
| "subagents"
|
|
@@ -547,6 +548,10 @@ export class SettingsManager {
|
|
|
547
548
|
return { ...this.settings };
|
|
548
549
|
}
|
|
549
550
|
|
|
551
|
+
getPlansDirectory(_cwd: string = this.cwd ?? process.cwd()): string {
|
|
552
|
+
return path.join(getAgentDir(), "plans");
|
|
553
|
+
}
|
|
554
|
+
|
|
550
555
|
/**
|
|
551
556
|
* Access the underlying agent storage (null for in-memory settings).
|
|
552
557
|
*/
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
export { AgentProtocolHandler, type AgentProtocolOptions } from "./agent-protocol";
|
|
23
23
|
export { ArtifactProtocolHandler, type ArtifactProtocolOptions } from "./artifact-protocol";
|
|
24
24
|
export { applyQuery, parseQuery, pathToQuery } from "./json-query";
|
|
25
|
+
export { PlanProtocolHandler, type PlanProtocolOptions, resolvePlanUrlToPath } from "./plan-protocol";
|
|
25
26
|
export { InternalUrlRouter } from "./router";
|
|
26
27
|
export { RuleProtocolHandler, type RuleProtocolOptions } from "./rule-protocol";
|
|
27
28
|
export { SkillProtocolHandler, type SkillProtocolOptions } from "./skill-protocol";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol handler for plan:// URLs.
|
|
3
|
+
*
|
|
4
|
+
* Resolves plan references to plan files under the plans directory.
|
|
5
|
+
*
|
|
6
|
+
* URL forms:
|
|
7
|
+
* - plan://<sessionId> (defaults to plan.md)
|
|
8
|
+
* - plan://<sessionId>/plan.md
|
|
9
|
+
* - plan://<plan-id>.md (resolves directly under plans dir)
|
|
10
|
+
*/
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
14
|
+
|
|
15
|
+
export interface PlanProtocolOptions {
|
|
16
|
+
getPlansDirectory: (cwd?: string) => string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parsePlanUrl(input: string): InternalUrl {
|
|
21
|
+
let parsed: URL;
|
|
22
|
+
try {
|
|
23
|
+
parsed = new URL(input);
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error(`Invalid URL: ${input}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hostMatch = input.match(/^([a-z][a-z0-9+.-]*):\/\/([^/?#]*)/i);
|
|
29
|
+
let rawHost = hostMatch ? hostMatch[2] : parsed.hostname;
|
|
30
|
+
try {
|
|
31
|
+
rawHost = decodeURIComponent(rawHost);
|
|
32
|
+
} catch {
|
|
33
|
+
// Leave rawHost as-is if decoding fails.
|
|
34
|
+
}
|
|
35
|
+
(parsed as InternalUrl).rawHost = rawHost;
|
|
36
|
+
return parsed as InternalUrl;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeRelativePath(host: string, pathname: string): string {
|
|
40
|
+
const trimmedHost = host.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
41
|
+
const trimmedPath = pathname.replace(/^\/+/, "");
|
|
42
|
+
|
|
43
|
+
if (!trimmedHost) {
|
|
44
|
+
throw new Error("plan:// URL requires a session or plan identifier");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (trimmedPath) {
|
|
48
|
+
return path.join(trimmedHost, trimmedPath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (trimmedHost.endsWith(".md")) {
|
|
52
|
+
return trimmedHost;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return path.join(trimmedHost, "plan.md");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolvePlanUrlToPath(input: string | InternalUrl, options: PlanProtocolOptions): string {
|
|
59
|
+
const url = typeof input === "string" ? parsePlanUrl(input) : input;
|
|
60
|
+
const host = url.rawHost || url.hostname;
|
|
61
|
+
const relativePath = normalizeRelativePath(host, url.pathname ?? "");
|
|
62
|
+
const plansDir = path.resolve(options.getPlansDirectory(options.cwd));
|
|
63
|
+
const resolved = path.resolve(plansDir, relativePath);
|
|
64
|
+
|
|
65
|
+
if (resolved !== plansDir && !resolved.startsWith(`${plansDir}${path.sep}`)) {
|
|
66
|
+
throw new Error("plan:// URL escapes the plans directory");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class PlanProtocolHandler implements ProtocolHandler {
|
|
73
|
+
readonly scheme = "plan";
|
|
74
|
+
|
|
75
|
+
constructor(private readonly options: PlanProtocolOptions) {}
|
|
76
|
+
|
|
77
|
+
async resolve(url: InternalUrl): Promise<InternalResource> {
|
|
78
|
+
const planPath = resolvePlanUrlToPath(url, this.options);
|
|
79
|
+
try {
|
|
80
|
+
const content = await Bun.file(planPath).text();
|
|
81
|
+
return {
|
|
82
|
+
url: url.href,
|
|
83
|
+
content,
|
|
84
|
+
contentType: "text/markdown",
|
|
85
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
86
|
+
sourcePath: planPath,
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (isEnoent(error)) {
|
|
90
|
+
throw new Error(`Plan file not found: ${url.href}`);
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|