@narumitw/pi-plan-mode 0.27.0 → 0.31.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 +4 -3
- package/package.json +4 -4
- package/src/completion-tool.ts +1 -3
- package/src/index.ts +1 -0
- package/src/question-tool.ts +41 -10
- package/src/state.ts +9 -22
package/README.md
CHANGED
|
@@ -180,7 +180,8 @@ This extension maps Codex's `ModeKind::Plan` behavior onto Pi's extension API:
|
|
|
180
180
|
```txt
|
|
181
181
|
extensions/pi-plan-mode/
|
|
182
182
|
├── src/
|
|
183
|
-
│ ├──
|
|
183
|
+
│ ├── index.ts # Pi package entrypoint
|
|
184
|
+
│ ├── plan-mode.ts # Extension registration and mode state
|
|
184
185
|
│ └── *.ts # Package-local prompt, policy, question, and message modules
|
|
185
186
|
├── README.md
|
|
186
187
|
├── LICENSE
|
|
@@ -188,12 +189,12 @@ extensions/pi-plan-mode/
|
|
|
188
189
|
└── package.json
|
|
189
190
|
```
|
|
190
191
|
|
|
191
|
-
|
|
192
|
+
`index.ts` is the Pi entrypoint and forwards to `plan-mode.ts`; the other source modules are internal. The package exposes its Pi extension through `package.json`:
|
|
192
193
|
|
|
193
194
|
```json
|
|
194
195
|
{
|
|
195
196
|
"pi": {
|
|
196
|
-
"extensions": ["./src/
|
|
197
|
+
"extensions": ["./src/index.ts"]
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-plan-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Pi extension that adds a Codex-like read-only /plan collaboration mode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"pi": {
|
|
21
21
|
"extensions": [
|
|
22
|
-
"./src/
|
|
22
|
+
"./src/index.ts"
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"typecheck": "tsc --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@biomejs/biome": "2.5.
|
|
32
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
31
|
+
"@biomejs/biome": "2.5.5",
|
|
32
|
+
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
33
33
|
"typescript": "7.0.2"
|
|
34
34
|
},
|
|
35
35
|
"repository": {
|
package/src/completion-tool.ts
CHANGED
|
@@ -22,9 +22,7 @@ export const PLAN_MODE_COMPLETE_PARAMS = {
|
|
|
22
22
|
},
|
|
23
23
|
} as const;
|
|
24
24
|
|
|
25
|
-
type NormalizePlanModeCompletionResult =
|
|
26
|
-
| { ok: true; plan: string }
|
|
27
|
-
| { ok: false; error: string };
|
|
25
|
+
type NormalizePlanModeCompletionResult = { ok: true; plan: string } | { ok: false; error: string };
|
|
28
26
|
|
|
29
27
|
export function normalizePlanModeCompletion(input: unknown): NormalizePlanModeCompletionResult {
|
|
30
28
|
if (!isRecord(input) || typeof input.plan !== "string") {
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./plan-mode.js";
|
package/src/question-tool.ts
CHANGED
|
@@ -23,7 +23,11 @@ type PlanModeQuestionAnswer = {
|
|
|
23
23
|
optionIndex?: number;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
type PlanModeQuestionReason =
|
|
26
|
+
type PlanModeQuestionReason =
|
|
27
|
+
| "cancelled"
|
|
28
|
+
| "ui_unavailable"
|
|
29
|
+
| "plan_mode_inactive"
|
|
30
|
+
| "invalid_input";
|
|
27
31
|
|
|
28
32
|
type PlanModeQuestionDetails = {
|
|
29
33
|
cancelled: boolean;
|
|
@@ -47,8 +51,14 @@ export const PLAN_MODE_QUESTION_PARAMS = {
|
|
|
47
51
|
additionalProperties: false,
|
|
48
52
|
required: ["id", "header", "question", "options"],
|
|
49
53
|
properties: {
|
|
50
|
-
id: {
|
|
51
|
-
|
|
54
|
+
id: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Stable identifier for mapping answers (snake_case).",
|
|
57
|
+
},
|
|
58
|
+
header: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Short header label shown in the UI (12 or fewer chars).",
|
|
61
|
+
},
|
|
52
62
|
question: { type: "string", description: "Single-sentence prompt shown to the user." },
|
|
53
63
|
options: {
|
|
54
64
|
type: "array",
|
|
@@ -79,7 +89,9 @@ type NormalizePlanModeQuestionParamsResult =
|
|
|
79
89
|
| { ok: true; questions: PlanModeQuestion[] }
|
|
80
90
|
| { ok: false; error: string };
|
|
81
91
|
|
|
82
|
-
export function normalizePlanModeQuestionParams(
|
|
92
|
+
export function normalizePlanModeQuestionParams(
|
|
93
|
+
input: unknown,
|
|
94
|
+
): NormalizePlanModeQuestionParamsResult {
|
|
83
95
|
if (!isRecord(input) || !Array.isArray(input.questions)) {
|
|
84
96
|
return { ok: false, error: "questions must be an array" };
|
|
85
97
|
}
|
|
@@ -96,7 +108,10 @@ export function normalizePlanModeQuestionParams(input: unknown): NormalizePlanMo
|
|
|
96
108
|
const header = stringField(rawQuestion.header);
|
|
97
109
|
const question = stringField(rawQuestion.question);
|
|
98
110
|
if (!id || !header || !question) {
|
|
99
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
error: `question ${questionIndex + 1} requires non-empty id, header, and question`,
|
|
114
|
+
};
|
|
100
115
|
}
|
|
101
116
|
if (!Array.isArray(rawQuestion.options)) {
|
|
102
117
|
return { ok: false, error: `question ${questionIndex + 1} options must be an array` };
|
|
@@ -114,7 +129,10 @@ export function normalizePlanModeQuestionParams(input: unknown): NormalizePlanMo
|
|
|
114
129
|
}
|
|
115
130
|
const label = stringField(rawOption.label);
|
|
116
131
|
if (!label) {
|
|
117
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
error: `question ${questionIndex + 1} option ${optionIndex + 1} requires a label`,
|
|
135
|
+
};
|
|
118
136
|
}
|
|
119
137
|
const description = stringField(rawOption.description);
|
|
120
138
|
if (!description) {
|
|
@@ -138,7 +156,10 @@ export async function askPlanModeQuestions(
|
|
|
138
156
|
for (const question of questions) {
|
|
139
157
|
const choices = question.options.map(formatPlanModeQuestionChoice);
|
|
140
158
|
const otherChoice = `${question.options.length + 1}. Other (free-form)`;
|
|
141
|
-
const choice = await ctx.ui.select(`${question.header}: ${question.question}`, [
|
|
159
|
+
const choice = await ctx.ui.select(`${question.header}: ${question.question}`, [
|
|
160
|
+
...choices,
|
|
161
|
+
otherChoice,
|
|
162
|
+
]);
|
|
142
163
|
if (!choice) return undefined;
|
|
143
164
|
if (choice === otherChoice) {
|
|
144
165
|
const customAnswer = (await ctx.ui.editor(question.question, ""))?.trim();
|
|
@@ -171,9 +192,14 @@ function formatPlanModeQuestionChoice(option: PlanModeQuestionOption, index: num
|
|
|
171
192
|
return `${index + 1}. ${option.label}${option.description ? ` — ${option.description}` : ""}`;
|
|
172
193
|
}
|
|
173
194
|
|
|
174
|
-
export function planModeQuestionAnswered(
|
|
195
|
+
export function planModeQuestionAnswered(
|
|
196
|
+
questions: PlanModeQuestion[],
|
|
197
|
+
answers: PlanModeQuestionAnswer[],
|
|
198
|
+
) {
|
|
175
199
|
return {
|
|
176
|
-
content: [
|
|
200
|
+
content: [
|
|
201
|
+
{ type: "text" as const, text: formatPlanModeQuestionPayload({ cancelled: false, answers }) },
|
|
202
|
+
],
|
|
177
203
|
details: { cancelled: false, questions, answers } satisfies PlanModeQuestionDetails,
|
|
178
204
|
};
|
|
179
205
|
}
|
|
@@ -184,7 +210,12 @@ export function planModeQuestionCancelled(
|
|
|
184
210
|
message: string,
|
|
185
211
|
) {
|
|
186
212
|
return {
|
|
187
|
-
content: [
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text" as const,
|
|
216
|
+
text: formatPlanModeQuestionPayload({ cancelled: true, reason, message }),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
188
219
|
details: { cancelled: true, reason, questions } satisfies PlanModeQuestionDetails,
|
|
189
220
|
};
|
|
190
221
|
}
|
package/src/state.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
PLAN_MODE_COMPLETE_TOOL_NAME,
|
|
3
2
|
normalizePlanModeCompletion,
|
|
3
|
+
PLAN_MODE_COMPLETE_TOOL_NAME,
|
|
4
4
|
planFromCompletionDetails,
|
|
5
5
|
} from "./completion-tool.js";
|
|
6
|
-
import {
|
|
7
|
-
PLAN_MODE_THINKING_LEVELS,
|
|
8
|
-
type PlanModeFixedThinkingLevel,
|
|
9
|
-
} from "./settings.js";
|
|
6
|
+
import { PLAN_MODE_THINKING_LEVELS, type PlanModeFixedThinkingLevel } from "./settings.js";
|
|
10
7
|
|
|
11
|
-
export type PlanCompletionSource =
|
|
12
|
-
| typeof PLAN_MODE_COMPLETE_TOOL_NAME
|
|
13
|
-
| "legacy_proposed_plan";
|
|
8
|
+
export type PlanCompletionSource = typeof PLAN_MODE_COMPLETE_TOOL_NAME | "legacy_proposed_plan";
|
|
14
9
|
|
|
15
10
|
export interface PlanModeState {
|
|
16
11
|
enabled: boolean;
|
|
@@ -49,23 +44,19 @@ export function restorePlanModeState(entries: unknown[], stateEntryType: string)
|
|
|
49
44
|
if (!isRecord(entry?.data)) return { enabled: false, awaitingAction: false };
|
|
50
45
|
|
|
51
46
|
const enabled = entry.data.enabled === true;
|
|
52
|
-
const persistedSource = enabled
|
|
53
|
-
? planCompletionSource(entry.data.latestPlanSource)
|
|
54
|
-
: undefined;
|
|
47
|
+
const persistedSource = enabled ? planCompletionSource(entry.data.latestPlanSource) : undefined;
|
|
55
48
|
const persistedPlan = enabled
|
|
56
49
|
? normalizePersistedPlan(entry.data.latestPlan, persistedSource)
|
|
57
50
|
: undefined;
|
|
58
51
|
const recoveredPlan =
|
|
59
|
-
enabled && !persistedPlan
|
|
60
|
-
? latestCompletionPlan(branch.slice(stateEntryIndex + 1))
|
|
61
|
-
: undefined;
|
|
52
|
+
enabled && !persistedPlan ? latestCompletionPlan(branch.slice(stateEntryIndex + 1)) : undefined;
|
|
62
53
|
const latestPlan = persistedPlan ?? recoveredPlan;
|
|
63
54
|
return {
|
|
64
55
|
enabled,
|
|
65
56
|
latestPlan,
|
|
66
57
|
latestPlanSource: enabled
|
|
67
|
-
? (persistedPlan ? persistedSource : undefined) ??
|
|
68
|
-
(recoveredPlan ? PLAN_MODE_COMPLETE_TOOL_NAME : undefined)
|
|
58
|
+
? ((persistedPlan ? persistedSource : undefined) ??
|
|
59
|
+
(recoveredPlan ? PLAN_MODE_COMPLETE_TOOL_NAME : undefined))
|
|
69
60
|
: undefined,
|
|
70
61
|
awaitingAction: enabled && latestPlan !== undefined,
|
|
71
62
|
selectedToolNames: stringArray(entry.data.selectedToolNames),
|
|
@@ -73,12 +64,8 @@ export function restorePlanModeState(entries: unknown[], stateEntryType: string)
|
|
|
73
64
|
previousThinkingLevel: enabled
|
|
74
65
|
? fixedThinkingLevel(entry.data.previousThinkingLevel)
|
|
75
66
|
: undefined,
|
|
76
|
-
appliedThinkingLevel: enabled
|
|
77
|
-
|
|
78
|
-
: undefined,
|
|
79
|
-
manualThinkingLevel: enabled
|
|
80
|
-
? fixedThinkingLevel(entry.data.manualThinkingLevel)
|
|
81
|
-
: undefined,
|
|
67
|
+
appliedThinkingLevel: enabled ? fixedThinkingLevel(entry.data.appliedThinkingLevel) : undefined,
|
|
68
|
+
manualThinkingLevel: enabled ? fixedThinkingLevel(entry.data.manualThinkingLevel) : undefined,
|
|
82
69
|
};
|
|
83
70
|
}
|
|
84
71
|
|