pi-midcompact 0.5.0 → 0.5.2
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 +23 -23
- package/package.json +1 -1
- package/skills/midcompact/SKILL.md +4 -2
- package/skills/midcompact/references/tool-interface.md +4 -0
- package/src/index.ts +120 -71
- package/src/start-ui.ts +46 -51
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ That is planning guidance, not an enforced token target: semantic importance win
|
|
|
29
29
|
|
|
30
30
|
### A selective projection, prepared on a temporary branch
|
|
31
31
|
|
|
32
|
-
`/midcompact
|
|
32
|
+
`/midcompact:start` freezes the current session leaf as an **anchor**. Planning happens on a disposable child branch, so the discussion used to create and edit the draft never becomes part of the committed working context.
|
|
33
33
|
|
|
34
34
|
```text
|
|
35
35
|
Frozen anchor: raw session history
|
|
@@ -42,7 +42,7 @@ Planning is isolated on a temporary branch:
|
|
|
42
42
|
... [latest work] ──┬── [transaction] ── [draft v1] ── [draft v2] ◀ review / edit
|
|
43
43
|
│ (abandoned at commit)
|
|
44
44
|
└── [midcompact-state] ◀ committed leaf
|
|
45
|
-
(reviewed selection metadata, not a model message; written only by /midcompact
|
|
45
|
+
(reviewed selection metadata, not a model message; written only by /midcompact:commit)
|
|
46
46
|
|
|
47
47
|
Later model requests see a selective projection:
|
|
48
48
|
|
|
@@ -91,10 +91,10 @@ pi-midcompact — mid-context compression, review, then human commit
|
|
|
91
91
|
|
|
92
92
|
| | Pi `/compact` | `pi-midcompact` |
|
|
93
93
|
| --- | --- | --- |
|
|
94
|
-
| Starts | Automatically near the context limit, or with `/compact` | At an explicit natural checkpoint with `/midcompact
|
|
94
|
+
| Starts | Automatically near the context limit, or with `/compact` | At an explicit natural checkpoint with `/midcompact:start` |
|
|
95
95
|
| Selects | One older contiguous prefix; keeps a recent token-budgeted tail | One or more reviewed ranges, including non-contiguous ranges and `KEEP` holes |
|
|
96
96
|
| Planning | Optional one-shot instruction to focus the generated summary | User states scope and retention depth; the Agent discusses trade-offs and drafts selective ranges and summaries |
|
|
97
|
-
| Decision gate | Generates a compaction checkpoint directly | Draft → TUI or browser review → explicit human `/midcompact
|
|
97
|
+
| Decision gate | Generates a compaction checkpoint directly | Draft → TUI or browser review → explicit human `/midcompact:commit` |
|
|
98
98
|
| Best fit | Automatic context maintenance and overflow recovery | Deliberate cleanup of completed phases while retaining specific decisions verbatim |
|
|
99
99
|
|
|
100
100
|
`pi-midcompact` does not disable or replace Pi's automatic compaction; it gives you a separate, human-reviewed way to make selective reductions. See [Pi's compaction documentation](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/docs/compaction.md) for the built-in mechanism.
|
|
@@ -124,13 +124,13 @@ Start a transaction at a natural breakpoint: the current work is complete enough
|
|
|
124
124
|
Run:
|
|
125
125
|
|
|
126
126
|
```text
|
|
127
|
-
/midcompact
|
|
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
|
-
/midcompact
|
|
133
|
+
/midcompact:start Compress the early repository exploration, but keep user requirements verbatim.
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
### 2. Discuss what to compress with the Agent
|
|
@@ -150,7 +150,7 @@ The Agent locates relevant parts of the frozen conversation, proposes one or mor
|
|
|
150
150
|
Run:
|
|
151
151
|
|
|
152
152
|
```text
|
|
153
|
-
/midcompact
|
|
153
|
+
/midcompact:review
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
In interactive mode, the native TUI displays the frozen conversation as a linear timeline. Each item is marked either `KEEP` or as belonging to a proposed range. Review the range boundaries and the summary that will replace each range.
|
|
@@ -158,14 +158,14 @@ In interactive mode, the native TUI displays the frozen conversation as a linear
|
|
|
158
158
|
For RPC, print, or other no-TUI modes, use the editable local browser interface instead:
|
|
159
159
|
|
|
160
160
|
```text
|
|
161
|
-
/midcompact
|
|
161
|
+
/midcompact:review-webui
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
Use Selection to create or change ranges and KEEP holes:
|
|
165
165
|
|
|
166
166
|
```text
|
|
167
|
-
/midcompact
|
|
168
|
-
/midcompact
|
|
167
|
+
/midcompact:select
|
|
168
|
+
/midcompact:select-webui
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
Use the TUI or local browser Review surface to edit summaries/topics and reject ranges. Review deliberately does not create or resize ranges; reopen Selection for boundary changes. To continue with Agent after a user-created plan, send a normal message asking it to continue the current midcompact draft; the Agent is prompted to read the existing plan first.
|
|
@@ -175,7 +175,7 @@ Use the TUI or local browser Review surface to edit summaries/topics and reject
|
|
|
175
175
|
When the plan is correct, run:
|
|
176
176
|
|
|
177
177
|
```text
|
|
178
|
-
/midcompact
|
|
178
|
+
/midcompact:commit
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
This is deliberately a human command. The Agent cannot commit compression itself.
|
|
@@ -187,14 +187,14 @@ Pi returns to the anchor, discards the temporary planning branch, stores the rev
|
|
|
187
187
|
Keep working normally after committing. If you decide not to compress, run:
|
|
188
188
|
|
|
189
189
|
```text
|
|
190
|
-
/midcompact
|
|
190
|
+
/midcompact:abort
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
This returns to the anchor and discards the transaction without changing the active context.
|
|
194
194
|
|
|
195
195
|
## Native TUI Controls
|
|
196
196
|
|
|
197
|
-
Inside `/midcompact
|
|
197
|
+
Inside `/midcompact:review`:
|
|
198
198
|
|
|
199
199
|
```text
|
|
200
200
|
n/p or Left/Right select a proposed range
|
|
@@ -211,14 +211,14 @@ Enter/Esc/q close
|
|
|
211
211
|
|
|
212
212
|
| Command | Result |
|
|
213
213
|
| --- | --- |
|
|
214
|
-
| `/midcompact
|
|
215
|
-
| `/midcompact
|
|
216
|
-
| `/midcompact
|
|
217
|
-
| `/midcompact
|
|
218
|
-
| `/midcompact
|
|
219
|
-
| `/midcompact
|
|
220
|
-
| `/midcompact
|
|
221
|
-
| `/midcompact
|
|
214
|
+
| `/midcompact:start [instructions]` | Opens Agent direct / User manual / Drop, then starts a transaction at the current session-tree leaf. |
|
|
215
|
+
| `/midcompact:select` | Opens the native TUI Selection workbench for range and KEEP editing. |
|
|
216
|
+
| `/midcompact:select-webui` | Opens the local browser Selection workbench. |
|
|
217
|
+
| `/midcompact:review` | Opens summary/topic review in the native TUI. |
|
|
218
|
+
| `/midcompact:review-webui` | Opens summary/topic review in a local browser. |
|
|
219
|
+
| `/midcompact:commit` | Commits the reviewed draft. Human only. |
|
|
220
|
+
| `/midcompact:abort` | Abandons the transaction and returns to the anchor. |
|
|
221
|
+
| `/midcompact:status` | Displays the current draft, or the committed compression state on this branch. |
|
|
222
222
|
|
|
223
223
|
The extension shows planning status in Pi's footer only while a transaction is active. It disappears after commit or abort.
|
|
224
224
|
|
|
@@ -227,7 +227,7 @@ The extension shows planning status in Pi's footer only while a transaction is a
|
|
|
227
227
|
- **Original history is retained.** Compression changes what later model requests see, not the stored Pi messages.
|
|
228
228
|
- **Fail-open projection.** If an exact reviewed sequence no longer resolves, the extension sends the raw history unchanged rather than removing uncertain content.
|
|
229
229
|
- **State is branch-local.** Navigating with `/tree` to a point before a committed state restores raw history; returning to its descendant restores the projection.
|
|
230
|
-
- **Human review is required.** The Agent can propose a plan but cannot execute `/midcompact
|
|
230
|
+
- **Human review is required.** The Agent can propose a plan but cannot execute `/midcompact:commit`.
|
|
231
231
|
- **Tool protocol is protected.** Unknown, incomplete, or orphaned tool exchanges are not compressible.
|
|
232
232
|
- **Repeated transactions work.** Later transactions can compress newly accumulated raw context; existing summaries remain protected.
|
|
233
233
|
- **Native Pi `/compact` interaction needs more real-session validation.** Avoid relying on mixed automatic/native compaction behavior for critical work until it has been exercised in your environment.
|
package/package.json
CHANGED
|
@@ -18,9 +18,11 @@ This skill handles two independent tasks: planning compression and recalling com
|
|
|
18
18
|
|
|
19
19
|
The state-specific runtime prompt is authoritative. Recall does not enter planning or mutate the DraftPlan. During the User-manual acknowledgement turn, the no-tool instruction overrides every other route.
|
|
20
20
|
|
|
21
|
+
The tool groups parameters by `action`: each action accepts only its own fields, and fields from other actions are rejected. Read `references/tool-interface.md` before adding workload-specific parameters, and treat the action-specific field lists there as exhaustive.
|
|
22
|
+
|
|
21
23
|
## Plan compression
|
|
22
24
|
|
|
23
|
-
Selected ranges become summaries in future model context; content outside them stays verbatim. Originals remain stored, but recall is a recovery path, not a substitute for a sufficient summary. Agent and user edit one DraftPlan; the user retains final control through review and `/midcompact
|
|
25
|
+
Selected ranges become summaries in future model context; content outside them stays verbatim. Originals remain stored, but recall is a recovery path, not a substitute for a sufficient summary. Agent and user edit one DraftPlan; the user retains final control through review and `/midcompact:commit`.
|
|
24
26
|
|
|
25
27
|
The start mode controls how the first draft is created. It does not determine whether a user selection is final, how much initiative the Agent should take, or how deeply the history should be investigated.
|
|
26
28
|
|
|
@@ -107,7 +109,7 @@ Length follows the information that must survive, not a target ratio. Final test
|
|
|
107
109
|
|
|
108
110
|
Call `action="plan", op="show"`. Check that the intended semantic phases are covered, KEEP holes remain outside ranges, every range has a summary, and the summaries conserve the future working state.
|
|
109
111
|
|
|
110
|
-
Describe the completed proposal with the same recognizable landmarks used during alignment. Direct the user to `/midcompact
|
|
112
|
+
Describe the completed proposal with the same recognizable landmarks used during alignment. Direct the user to `/midcompact:select` or `/midcompact:select-webui` for boundaries and KEEP holes, and to `/midcompact:review` or `/midcompact:review-webui` for summary inspection or rejection. Use browser variants when the TUI is unavailable or preferred. Ask the user to run `/midcompact:commit` when ready; never commit for them.
|
|
111
113
|
|
|
112
114
|
## Recall compressed content
|
|
113
115
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Read this reference when exact call requirements, limits, rejection behavior, repeated compression, or recall truncation matters. The main workflow remains in `../SKILL.md`.
|
|
4
4
|
|
|
5
|
+
## Parameter grouping
|
|
6
|
+
|
|
7
|
+
The tool schema is a discriminated union on `action`: selecting an action admits exactly the fields documented in that action's section, and no fields from other actions. A call that mixes actions (for example `locate` fields on `plan`) is invalid; do not repair it by dropping fields, re-issue the call with only the selected action's parameters. Shared field names (`ref`, `pattern`, `limit`, `detail`) are defined independently per action with the meaning documented in that section.
|
|
8
|
+
|
|
5
9
|
## Inspect
|
|
6
10
|
|
|
7
11
|
Without `spans`, `action="inspect"` inventories the frozen anchor. It returns factual structure and bounded user landmarks, not full message bodies, assistant/tool previews, summaries, or image base64.
|
package/src/index.ts
CHANGED
|
@@ -6,8 +6,6 @@ import {
|
|
|
6
6
|
type ExtensionContext,
|
|
7
7
|
type SessionEntry,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
10
|
-
|
|
11
9
|
import { buildAtoms, formatLocatedAtom, isProtectedAtom, locateAtomMatches } from "./atoms.js";
|
|
12
10
|
import { buildInventory, formatInventory, formatSpanInspection } from "./inventory.js";
|
|
13
11
|
import { messageText } from "./messages.js";
|
|
@@ -17,8 +15,20 @@ import { projectMessages } from "./projection.js";
|
|
|
17
15
|
import { registerStateRenderer, stateTreeLabel } from "./renderers.js";
|
|
18
16
|
import { showReviewUi } from "./review-ui.js";
|
|
19
17
|
import { showSelectionUi } from "./selection-ui.js";
|
|
20
|
-
import {
|
|
18
|
+
import { showStartChoice } from "./start-ui.js";
|
|
21
19
|
import { showReviewWebUi } from "./review-webui.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Browser opener for the web workbenches. Tests override it to keep the suites
|
|
23
|
+
* from spawning a system browser; production leaves it undefined and opens the
|
|
24
|
+
* default browser.
|
|
25
|
+
*/
|
|
26
|
+
export let openReviewWebBrowser: ((url: string) => void) | undefined;
|
|
27
|
+
|
|
28
|
+
/** Test seam setter; see `openReviewWebBrowser`. */
|
|
29
|
+
export function setOpenReviewWebBrowser(opener?: (url: string) => void): void {
|
|
30
|
+
openReviewWebBrowser = opener;
|
|
31
|
+
}
|
|
22
32
|
import {
|
|
23
33
|
DRAFT_ENTRY,
|
|
24
34
|
STATE_ENTRY,
|
|
@@ -57,8 +67,20 @@ const TOOL_DESCRIPTION =
|
|
|
57
67
|
const STATUS_KEY = "midcompact";
|
|
58
68
|
const START_PROMPT_PREFIX = "A mid-compaction transaction is active on a frozen anchor snapshot.";
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
// The tool is a discriminated union: `action` selects the only parameter
|
|
71
|
+
// group that applies. The schema exposes just that group's fields, so a call
|
|
72
|
+
// cannot mix parameters from different actions, and cross-action misuse
|
|
73
|
+
// becomes a compile-time error in the handler types rather than a runtime rule.
|
|
74
|
+
const InspectParams = Type.Object({
|
|
75
|
+
action: Type.Literal("inspect"),
|
|
76
|
+
// inventory pagination or explicit candidate-span measurement
|
|
77
|
+
spans: Type.Optional(Type.Array(Type.Object({ start: Type.String(), end: Type.String() }))),
|
|
78
|
+
page_size: Type.Optional(Type.Number()),
|
|
79
|
+
cursor: Type.Optional(Type.String()),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const LocateParams = Type.Object({
|
|
83
|
+
action: Type.Literal("locate"),
|
|
62
84
|
ref: Type.Optional(Type.String()),
|
|
63
85
|
pattern: Type.Optional(Type.String()),
|
|
64
86
|
source: Type.Optional(StringEnum(["any", "user", "assistant", "tool_call", "tool_result"] as const)),
|
|
@@ -66,19 +88,34 @@ const Params = Type.Object({
|
|
|
66
88
|
direction: Type.Optional(StringEnum(["oldest", "newest"] as const)),
|
|
67
89
|
limit: Type.Optional(Type.Number()),
|
|
68
90
|
detail: Type.Optional(StringEnum(["brief", "full"] as const)),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const PlanParams = Type.Object({
|
|
94
|
+
action: Type.Literal("plan"),
|
|
69
95
|
op: Type.Optional(StringEnum(["show", "add", "update", "remove"] as const)),
|
|
70
96
|
start: Type.Optional(Type.String()),
|
|
71
97
|
end: Type.Optional(Type.String()),
|
|
72
98
|
draft_id: Type.Optional(Type.String()),
|
|
73
99
|
topic: Type.Optional(Type.String()),
|
|
74
100
|
summary: Type.Optional(Type.String()),
|
|
75
|
-
|
|
76
|
-
page_size: Type.Optional(Type.Number()),
|
|
77
|
-
cursor: Type.Optional(Type.String()),
|
|
78
|
-
spans: Type.Optional(Type.Array(Type.Object({ start: Type.String(), end: Type.String() }))),
|
|
101
|
+
detail: Type.Optional(StringEnum(["brief", "full"] as const)),
|
|
79
102
|
});
|
|
80
103
|
|
|
104
|
+
const RecallParams = Type.Object({
|
|
105
|
+
action: Type.Literal("recall"),
|
|
106
|
+
ref: Type.Optional(Type.String()),
|
|
107
|
+
pattern: Type.Optional(Type.String()),
|
|
108
|
+
limit: Type.Optional(Type.Number()),
|
|
109
|
+
detail: Type.Optional(StringEnum(["brief", "full"] as const)),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const Params = Type.Union([InspectParams, LocateParams, PlanParams, RecallParams]);
|
|
113
|
+
|
|
81
114
|
type ParamsType = Static<typeof Params>;
|
|
115
|
+
type InspectParamsType = Static<typeof InspectParams>;
|
|
116
|
+
type LocateParamsType = Static<typeof LocateParams>;
|
|
117
|
+
type PlanParamsType = Static<typeof PlanParams>;
|
|
118
|
+
type RecallParamsType = Static<typeof RecallParams>;
|
|
82
119
|
|
|
83
120
|
type RuntimeSnapshot = { atoms: Atom[]; anchorState?: CompressionState };
|
|
84
121
|
|
|
@@ -160,48 +197,63 @@ export default function (pi: ExtensionAPI) {
|
|
|
160
197
|
return { messages: projectMessages(event.messages as MessageLike[], activeState) as typeof event.messages };
|
|
161
198
|
});
|
|
162
199
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const items: AutocompleteItem[] = [
|
|
169
|
-
{ value: "start", label: "start — Start a new midcompact transaction at the current anchor" },
|
|
170
|
-
{ value: "abort", label: "abort — Abort the active transaction and return to anchor" },
|
|
171
|
-
{ value: "commit", label: "commit — Commit the current draft to the branch state" },
|
|
172
|
-
{ value: "review", label: "review — Open interactive TUI to inspect and edit the draft" },
|
|
173
|
-
{ value: "review-webui", label: "review-webui — Open a local web page to inspect and edit the draft (works without TUI)" },
|
|
174
|
-
{ value: "select", label: "select — Open the TUI Selection surface to edit range boundaries" },
|
|
175
|
-
{ value: "select-webui", label: "select-webui — Open Selection in a local browser" },
|
|
176
|
-
{ value: "status", label: "status — Show current transaction and draft status" },
|
|
177
|
-
];
|
|
178
|
-
const filtered = items.filter((item) => item.value.startsWith(query));
|
|
179
|
-
return filtered.length > 0 ? filtered : null;
|
|
180
|
-
},
|
|
200
|
+
// One command per operation; the `midcompact:` prefix follows the same
|
|
201
|
+
// naming convention as Pi's own `skill:<name>` commands. The former
|
|
202
|
+
// composite `/midcompact <subcommand>` command is gone.
|
|
203
|
+
pi.registerCommand("midcompact:start", {
|
|
204
|
+
description: "Start a new midcompact transaction at the current anchor. Optional trailing text becomes the initial focus.",
|
|
181
205
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
182
206
|
await ctx.waitForIdle();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
);
|
|
207
|
+
return startTransaction(ctx, args.trim() || undefined);
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
pi.registerCommand("midcompact:abort", {
|
|
211
|
+
description: "Abort the active transaction and return to the anchor",
|
|
212
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
213
|
+
await ctx.waitForIdle();
|
|
214
|
+
return abortTransaction(ctx);
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
pi.registerCommand("midcompact:commit", {
|
|
218
|
+
description: "Commit the current draft to the branch state",
|
|
219
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
220
|
+
await ctx.waitForIdle();
|
|
221
|
+
return commitTransaction(ctx);
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
pi.registerCommand("midcompact:review", {
|
|
225
|
+
description: "Open the interactive TUI review to inspect and edit the draft",
|
|
226
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
227
|
+
await ctx.waitForIdle();
|
|
228
|
+
return reviewTransaction(ctx, "tui");
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
pi.registerCommand("midcompact:review-webui", {
|
|
232
|
+
description: "Open a local web page to inspect and edit the draft (works without TUI)",
|
|
233
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
234
|
+
await ctx.waitForIdle();
|
|
235
|
+
return reviewTransaction(ctx, "web");
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
pi.registerCommand("midcompact:select", {
|
|
239
|
+
description: "Open the TUI Selection surface to edit range boundaries",
|
|
240
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
241
|
+
await ctx.waitForIdle();
|
|
242
|
+
return openSelectionUi(ctx, "auto");
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
pi.registerCommand("midcompact:select-webui", {
|
|
246
|
+
description: "Open Selection in a local browser",
|
|
247
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
248
|
+
await ctx.waitForIdle();
|
|
249
|
+
return openSelectionUi(ctx, "web");
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
pi.registerCommand("midcompact:status", {
|
|
253
|
+
description: "Show current transaction and draft status",
|
|
254
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
255
|
+
await ctx.waitForIdle();
|
|
256
|
+
return showStatus(ctx);
|
|
205
257
|
},
|
|
206
258
|
});
|
|
207
259
|
|
|
@@ -218,23 +270,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
270
|
ctx.ui.notify("Cannot start midcompact without a session leaf.", "error");
|
|
219
271
|
return;
|
|
220
272
|
}
|
|
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
273
|
// Mode choice: Agent-first or User-first. Both operate on the same DraftPlan;
|
|
232
|
-
// neither freezes boundaries.
|
|
274
|
+
// neither freezes boundaries. Drop maps to cancellation, so this one chooser
|
|
275
|
+
// replaces the separate confirmation. No mode flags on the command line.
|
|
233
276
|
const modeChoice = await chooseStartMode(ctx);
|
|
234
277
|
if (modeChoice === "cancelled") {
|
|
235
278
|
ctx.ui.notify("Midcompact start cancelled.", "info");
|
|
236
279
|
return;
|
|
237
280
|
}
|
|
281
|
+
if (modeChoice === "unrecognized") {
|
|
282
|
+
ctx.ui.notify("Midcompact start cancelled: the dialog returned an unrecognized choice.", "warning");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
238
285
|
const startMode: StartMode = modeChoice;
|
|
239
286
|
transaction = {
|
|
240
287
|
version: 1,
|
|
@@ -261,9 +308,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
261
308
|
await openSelectionUi(ctx);
|
|
262
309
|
}
|
|
263
310
|
|
|
264
|
-
async function chooseStartMode(ctx: ExtensionCommandContext): Promise<StartMode | "cancelled"> {
|
|
265
|
-
|
|
266
|
-
|
|
311
|
+
async function chooseStartMode(ctx: ExtensionCommandContext): Promise<StartMode | "cancelled" | "unrecognized"> {
|
|
312
|
+
// The standard `select` dialog works identically in TUI and RPC; modes
|
|
313
|
+
// without any UI (json/print) cannot prompt and default to Agent-first.
|
|
314
|
+
if (!ctx.hasUI) return "agent";
|
|
315
|
+
return showStartChoice(ctx);
|
|
267
316
|
}
|
|
268
317
|
|
|
269
318
|
async function openSelectionUi(ctx: ExtensionCommandContext, mode: "auto" | "tui" | "web" = "auto"): Promise<void> {
|
|
@@ -312,7 +361,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
312
361
|
pi.appendEntry(DRAFT_ENTRY, draft);
|
|
313
362
|
updateStatus(ctx, currentTx, draft, planningLock.owner);
|
|
314
363
|
},
|
|
315
|
-
}, "selection");
|
|
364
|
+
}, "selection", { openBrowser: openReviewWebBrowser });
|
|
316
365
|
ctx.ui.notify("Selection closed. The DraftPlan is saved; tell the Agent to continue when ready.", "info");
|
|
317
366
|
} finally {
|
|
318
367
|
releaseUi(planningLock);
|
|
@@ -457,14 +506,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
457
506
|
editSummary: (id, summary) => commitMutation(updateDraftRange(draft ?? emptyDraft(currentTx.id), id, { summary })),
|
|
458
507
|
editTopic: (id, topic) => commitMutation(updateDraftRange(draft ?? emptyDraft(currentTx.id), id, { topic: topic || undefined })),
|
|
459
508
|
remove: (id) => commitMutation(removeDraftRange(draft ?? emptyDraft(currentTx.id), id)),
|
|
460
|
-
});
|
|
509
|
+
}, undefined, { openBrowser: openReviewWebBrowser });
|
|
461
510
|
ctx.ui.notify("Midcompact review-webui closed.", "info");
|
|
462
511
|
return;
|
|
463
512
|
}
|
|
464
513
|
|
|
465
514
|
if (ctx.mode !== "tui") {
|
|
466
515
|
ctx.ui.notify(
|
|
467
|
-
"Interactive TUI review is only available in interactive (tui) mode. Use /midcompact
|
|
516
|
+
"Interactive TUI review is only available in interactive (tui) mode. Use /midcompact:review-webui to open a local web page instead.",
|
|
468
517
|
"warning",
|
|
469
518
|
);
|
|
470
519
|
return;
|
|
@@ -529,7 +578,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
529
578
|
if (params.action === "recall") return toolResult(handleRecall(params, ctx));
|
|
530
579
|
const restored = restoreTransaction(ctx.sessionManager.getBranch() as SessionEntry[]);
|
|
531
580
|
const currentTx = withCompatDefaults(restored.transaction ?? transaction);
|
|
532
|
-
if (!currentTx) return toolResult("No active midcompact transaction. Ask the user to run `/midcompact
|
|
581
|
+
if (!currentTx) return toolResult("No active midcompact transaction. Ask the user to run `/midcompact:start` first.");
|
|
533
582
|
transaction = currentTx;
|
|
534
583
|
draft = restored.draft ? { ...restored.draft, ranges: restored.draft.ranges.map(coerceDraftRange) } : (draft ?? emptyDraft(currentTx.id));
|
|
535
584
|
if (!requireAgentAccess(ctx)) {
|
|
@@ -560,7 +609,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
560
609
|
},
|
|
561
610
|
});
|
|
562
611
|
|
|
563
|
-
function handleInspect(params:
|
|
612
|
+
function handleInspect(params: InspectParamsType, atoms: Atom[], tx: TransactionState): string {
|
|
564
613
|
if (params.spans) {
|
|
565
614
|
if (params.page_size !== undefined || params.cursor !== undefined) {
|
|
566
615
|
throw new Error("inspect spans cannot be combined with inventory pagination.");
|
|
@@ -571,7 +620,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
571
620
|
return formatInventory(page);
|
|
572
621
|
}
|
|
573
622
|
|
|
574
|
-
function handleRecall(params:
|
|
623
|
+
function handleRecall(params: RecallParamsType, ctx: ExtensionContext): string {
|
|
575
624
|
const sm = ctx.sessionManager;
|
|
576
625
|
const branchState = restoreCompressionState(sm.getBranch() as SessionEntry[]) ?? activeState;
|
|
577
626
|
if (!branchState?.blocks.length) return "No compressed blocks are active on this branch.";
|
|
@@ -607,7 +656,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
607
656
|
|
|
608
657
|
// ---- Pure handlers ----
|
|
609
658
|
|
|
610
|
-
function handleLocate(params:
|
|
659
|
+
function handleLocate(params: LocateParamsType, atoms: Atom[]): string {
|
|
611
660
|
const hasFilter = Boolean(params.pattern || params.tool_name || (params.source && params.source !== "any"));
|
|
612
661
|
if (params.ref && hasFilter) {
|
|
613
662
|
throw new Error("locate accepts either one direct ref or search filters, not both.");
|
|
@@ -639,7 +688,7 @@ type PlanHandleResult =
|
|
|
639
688
|
| { op: "show"; draft: DraftPlan }
|
|
640
689
|
| { op: "add" | "update" | "remove"; draft: DraftPlan; changedId: string };
|
|
641
690
|
|
|
642
|
-
function handlePlan(params:
|
|
691
|
+
function handlePlan(params: PlanParamsType, current: DraftPlan, atoms: Atom[]): PlanHandleResult {
|
|
643
692
|
const op = params.op ?? "show";
|
|
644
693
|
if (op === "show") return { op, draft: current };
|
|
645
694
|
if (op === "remove") {
|
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
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -178,7 +178,7 @@ export interface TransactionState {
|
|
|
178
178
|
startedAt: string;
|
|
179
179
|
/** Initial routing chosen at start: Agent-first prompt or User-first UI. */
|
|
180
180
|
startMode?: StartMode;
|
|
181
|
-
/** Frozen awareness captured when /midcompact
|
|
181
|
+
/** Frozen awareness captured when /midcompact:start runs; informational, never a target. */
|
|
182
182
|
anchorUsage?: ContextUsageSnapshot;
|
|
183
183
|
}
|
|
184
184
|
|