pi-midcompact 0.5.0 → 0.5.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/README.md +2 -2
- package/package.json +1 -1
- package/src/index.ts +26 -17
- package/src/start-ui.ts +46 -51
package/README.md
CHANGED
|
@@ -127,7 +127,7 @@ Run:
|
|
|
127
127
|
/midcompact start
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
Pi opens a three-way chooser before creating transaction state: **
|
|
130
|
+
Pi opens a three-way chooser before creating transaction state: **Agent direct**, **User manual**, or **Drop**. Agent direct is the first and default-highlighted option, matching the previous fast path. The chooser is the standard `select` dialog, identical in interactive and RPC mode; RPC carries it as an extension UI `select` message with a bounded timeout, so an unresponsive client cancels instead of blocking. print/JSON modes have no dialog and default to **Agent direct**. Agent direct starts the existing inventory-first Agent workflow. User manual sends the same transaction guidance with a final “acknowledge only” instruction; after the Agent replies briefly, the Selection workbench opens (browser-based outside interactive mode). It does not start planning or mutate the DraftPlan until the user hands off later. Save the initial DraftPlan, close the UI, then tell the Agent to continue when you are ready. You can include an initial focus in the same command:
|
|
131
131
|
|
|
132
132
|
```text
|
|
133
133
|
/midcompact start Compress the early repository exploration, but keep user requirements verbatim.
|
|
@@ -211,7 +211,7 @@ Enter/Esc/q close
|
|
|
211
211
|
|
|
212
212
|
| Command | Result |
|
|
213
213
|
| --- | --- |
|
|
214
|
-
| `/midcompact start [instructions]` | Opens
|
|
214
|
+
| `/midcompact start [instructions]` | Opens Agent direct / User manual / Drop, then starts a transaction at the current session-tree leaf. |
|
|
215
215
|
| `/midcompact select` | Opens the native TUI Selection workbench for range and KEEP editing. |
|
|
216
216
|
| `/midcompact select-webui` | Opens the local browser Selection workbench. |
|
|
217
217
|
| `/midcompact review` | Opens summary/topic review in the native TUI. |
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -17,8 +17,20 @@ import { projectMessages } from "./projection.js";
|
|
|
17
17
|
import { registerStateRenderer, stateTreeLabel } from "./renderers.js";
|
|
18
18
|
import { showReviewUi } from "./review-ui.js";
|
|
19
19
|
import { showSelectionUi } from "./selection-ui.js";
|
|
20
|
-
import {
|
|
20
|
+
import { showStartChoice } from "./start-ui.js";
|
|
21
21
|
import { showReviewWebUi } from "./review-webui.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Browser opener for the web workbenches. Tests override it to keep the suites
|
|
25
|
+
* from spawning a system browser; production leaves it undefined and opens the
|
|
26
|
+
* default browser.
|
|
27
|
+
*/
|
|
28
|
+
export let openReviewWebBrowser: ((url: string) => void) | undefined;
|
|
29
|
+
|
|
30
|
+
/** Test seam setter; see `openReviewWebBrowser`. */
|
|
31
|
+
export function setOpenReviewWebBrowser(opener?: (url: string) => void): void {
|
|
32
|
+
openReviewWebBrowser = opener;
|
|
33
|
+
}
|
|
22
34
|
import {
|
|
23
35
|
DRAFT_ENTRY,
|
|
24
36
|
STATE_ENTRY,
|
|
@@ -218,23 +230,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
230
|
ctx.ui.notify("Cannot start midcompact without a session leaf.", "error");
|
|
219
231
|
return;
|
|
220
232
|
}
|
|
221
|
-
if (ctx.hasUI && ctx.mode !== "tui") {
|
|
222
|
-
const ok = await ctx.ui.confirm(
|
|
223
|
-
"Start midcompact transaction?",
|
|
224
|
-
"The current context snapshot will be frozen as an anchor. You will need to review and explicitly commit or abort later.",
|
|
225
|
-
);
|
|
226
|
-
if (!ok) {
|
|
227
|
-
ctx.ui.notify("Midcompact start cancelled.", "info");
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
233
|
// Mode choice: Agent-first or User-first. Both operate on the same DraftPlan;
|
|
232
|
-
// neither freezes boundaries.
|
|
234
|
+
// neither freezes boundaries. Drop maps to cancellation, so this one chooser
|
|
235
|
+
// replaces the separate confirmation. No mode flags on the command line.
|
|
233
236
|
const modeChoice = await chooseStartMode(ctx);
|
|
234
237
|
if (modeChoice === "cancelled") {
|
|
235
238
|
ctx.ui.notify("Midcompact start cancelled.", "info");
|
|
236
239
|
return;
|
|
237
240
|
}
|
|
241
|
+
if (modeChoice === "unrecognized") {
|
|
242
|
+
ctx.ui.notify("Midcompact start cancelled: the dialog returned an unrecognized choice.", "warning");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
238
245
|
const startMode: StartMode = modeChoice;
|
|
239
246
|
transaction = {
|
|
240
247
|
version: 1,
|
|
@@ -261,9 +268,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
261
268
|
await openSelectionUi(ctx);
|
|
262
269
|
}
|
|
263
270
|
|
|
264
|
-
async function chooseStartMode(ctx: ExtensionCommandContext): Promise<StartMode | "cancelled"> {
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
async function chooseStartMode(ctx: ExtensionCommandContext): Promise<StartMode | "cancelled" | "unrecognized"> {
|
|
272
|
+
// The standard `select` dialog works identically in TUI and RPC; modes
|
|
273
|
+
// without any UI (json/print) cannot prompt and default to Agent-first.
|
|
274
|
+
if (!ctx.hasUI) return "agent";
|
|
275
|
+
return showStartChoice(ctx);
|
|
267
276
|
}
|
|
268
277
|
|
|
269
278
|
async function openSelectionUi(ctx: ExtensionCommandContext, mode: "auto" | "tui" | "web" = "auto"): Promise<void> {
|
|
@@ -312,7 +321,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
312
321
|
pi.appendEntry(DRAFT_ENTRY, draft);
|
|
313
322
|
updateStatus(ctx, currentTx, draft, planningLock.owner);
|
|
314
323
|
},
|
|
315
|
-
}, "selection");
|
|
324
|
+
}, "selection", { openBrowser: openReviewWebBrowser });
|
|
316
325
|
ctx.ui.notify("Selection closed. The DraftPlan is saved; tell the Agent to continue when ready.", "info");
|
|
317
326
|
} finally {
|
|
318
327
|
releaseUi(planningLock);
|
|
@@ -457,7 +466,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
457
466
|
editSummary: (id, summary) => commitMutation(updateDraftRange(draft ?? emptyDraft(currentTx.id), id, { summary })),
|
|
458
467
|
editTopic: (id, topic) => commitMutation(updateDraftRange(draft ?? emptyDraft(currentTx.id), id, { topic: topic || undefined })),
|
|
459
468
|
remove: (id) => commitMutation(removeDraftRange(draft ?? emptyDraft(currentTx.id), id)),
|
|
460
|
-
});
|
|
469
|
+
}, undefined, { openBrowser: openReviewWebBrowser });
|
|
461
470
|
ctx.ui.notify("Midcompact review-webui closed.", "info");
|
|
462
471
|
return;
|
|
463
472
|
}
|
package/src/start-ui.ts
CHANGED
|
@@ -1,57 +1,52 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
2
|
|
|
4
3
|
import type { StartMode } from "./types.js";
|
|
5
4
|
|
|
6
|
-
export type StartChoice = StartMode | "cancelled";
|
|
5
|
+
export type StartChoice = StartMode | "cancelled" | "unrecognized";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
(tui, theme, _keybindings, done) => {
|
|
12
|
-
const choices: Array<{ value: StartChoice; title: string; detail: string }> = [
|
|
13
|
-
{ value: "cancelled", title: "Drop", detail: "Leave the session unchanged" },
|
|
14
|
-
{ value: "agent", title: "Agent direct", detail: "Inspect and draft with Agent" },
|
|
15
|
-
{ value: "user", title: "User manual", detail: "Select the initial DraftPlan yourself" },
|
|
16
|
-
];
|
|
17
|
-
let selected = 1;
|
|
18
|
-
const component = {
|
|
19
|
-
render(width: number): string[] {
|
|
20
|
-
const w = Math.max(48, Math.min(width, 94));
|
|
21
|
-
const line = (text: string) => theme.fg("border", `| ${truncateToWidth(text, w - 4, "...", true).padEnd(w - 4)} |`);
|
|
22
|
-
const rule = theme.fg("border", `+${"-".repeat(w - 2)}+`);
|
|
23
|
-
const rows = choices.map((choice, index) => {
|
|
24
|
-
const marker = index === selected ? theme.fg("accent", ">") : " ";
|
|
25
|
-
const rawTitle = choice.title.padEnd(18);
|
|
26
|
-
const title = index === selected ? theme.fg("accent", theme.bold(rawTitle)) : rawTitle;
|
|
27
|
-
return line(`${marker} ${String(index + 1)} ${title} ${theme.fg("dim", choice.detail)}`);
|
|
28
|
-
});
|
|
29
|
-
return [
|
|
30
|
-
rule,
|
|
31
|
-
line(theme.fg("accent", theme.bold("Midcompact | Freeze current context"))),
|
|
32
|
-
line(theme.fg("dim", "Choose how to enter. Only Agent direct starts a model turn.")),
|
|
33
|
-
rule,
|
|
34
|
-
...rows,
|
|
35
|
-
rule,
|
|
36
|
-
line(theme.fg("dim", "left/right or j/k move | Enter choose | Esc drop")),
|
|
37
|
-
rule,
|
|
38
|
-
];
|
|
39
|
-
},
|
|
40
|
-
invalidate(): void {},
|
|
41
|
-
handleInput(data: string): void {
|
|
42
|
-
if (matchesKey(data, Key.escape) || data === "q") { done("cancelled"); return; }
|
|
43
|
-
if (matchesKey(data, Key.enter)) { done(choices[selected]!.value); return; }
|
|
44
|
-
if (data === "1") { done("cancelled"); return; }
|
|
45
|
-
if (data === "2") { done("agent"); return; }
|
|
46
|
-
if (data === "3") { done("user"); return; }
|
|
47
|
-
if (matchesKey(data, Key.left) || matchesKey(data, Key.up) || data === "k") selected = (selected + choices.length - 1) % choices.length;
|
|
48
|
-
else if (matchesKey(data, Key.right) || matchesKey(data, Key.down) || data === "j") selected = (selected + 1) % choices.length;
|
|
49
|
-
else return;
|
|
50
|
-
tui.requestRender();
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
return component;
|
|
54
|
-
},
|
|
55
|
-
{ overlay: true, overlayOptions: { width: "76%", maxHeight: 13, anchor: "center" } },
|
|
56
|
-
);
|
|
7
|
+
interface StartChoiceOption {
|
|
8
|
+
value: StartChoice;
|
|
9
|
+
label: string;
|
|
57
10
|
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The three entry decisions. Shared by TUI and RPC so every mode offers the
|
|
14
|
+
* same choice set in the same order. Agent direct comes first: the standard
|
|
15
|
+
* selector highlights the first option, so Enter on open keeps the fast
|
|
16
|
+
* Agent-first start path, and RPC clients see the recommended entry first.
|
|
17
|
+
*/
|
|
18
|
+
const START_CHOICES: StartChoiceOption[] = [
|
|
19
|
+
{ value: "agent", label: "Agent direct — Inspect and draft with Agent" },
|
|
20
|
+
{ value: "user", label: "User manual — Select the initial DraftPlan yourself" },
|
|
21
|
+
{ value: "cancelled", label: "Drop — Leave the session unchanged" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bound for RPC dialog waits. The timeout exists only as a safety net: when
|
|
26
|
+
* the RPC client never answers, the extension-UI sub-protocol auto-resolves
|
|
27
|
+
* the dialog (`undefined`) so the start cancels instead of blocking the
|
|
28
|
+
* extension forever. 120s is generous for the human on the other side of the
|
|
29
|
+
* wire (pi's own examples use 5s for urgent security confirms) while still
|
|
30
|
+
* bounding the worst-case stall. TUI mode passes no timeout: interactive
|
|
31
|
+
* users may take as long as they want.
|
|
32
|
+
*/
|
|
33
|
+
const START_DIALOG_TIMEOUT_MS = 120_000;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Standard `select` dialog for the start mode, used identically in TUI and
|
|
37
|
+
* RPC. It serializes as an extension UI `select` request over the RPC
|
|
38
|
+
* sub-protocol, while interactive mode renders the built-in selector.
|
|
39
|
+
*/
|
|
40
|
+
export async function showStartChoice(ctx: ExtensionCommandContext): Promise<StartChoice> {
|
|
41
|
+
const options = START_CHOICES.map((choice) => choice.label);
|
|
42
|
+
const choice = await ctx.ui.select(
|
|
43
|
+
"Midcompact start — freeze the current context as an anchor; review, then commit or abort explicitly",
|
|
44
|
+
options,
|
|
45
|
+
ctx.mode === "rpc" ? { timeout: START_DIALOG_TIMEOUT_MS } : undefined,
|
|
46
|
+
);
|
|
47
|
+
if (choice === undefined) return "cancelled";
|
|
48
|
+
// A value outside the offered options is a protocol mismatch on the client
|
|
49
|
+
// side, not a user decision; keep it distinguishable from a real Drop.
|
|
50
|
+
const index = options.indexOf(choice);
|
|
51
|
+
return index >= 0 ? START_CHOICES[index]!.value : "unrecognized";
|
|
52
|
+
}
|