pi-ui-extend 1.0.38 → 1.0.39
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/dist/app/extensions/extension-ui-controller.js +3 -0
- package/dist/app/rendering/extension-entry-renderer.js +2 -0
- package/dist/bundled-extensions/question/contract.d.ts +3 -0
- package/dist/bundled-extensions/question/contract.js +24 -0
- package/dist/bundled-extensions/question/desktop.d.ts +11 -0
- package/dist/bundled-extensions/question/desktop.js +143 -0
- package/dist/bundled-extensions/question/index.d.ts +1 -0
- package/dist/bundled-extensions/question/index.js +5 -1
- package/dist/bundled-extensions/question/render.js +6 -0
- package/dist/bundled-extensions/question/result.js +71 -9
- package/dist/bundled-extensions/question/tool-description.js +4 -3
- package/dist/bundled-extensions/question/tui.js +127 -12
- package/dist/bundled-extensions/question/types.d.ts +23 -2
- package/dist/tool-renderers/question.js +20 -1
- package/docs/desktop-markdown-media.md +77 -0
- package/docs/desktop-mvp.md +134 -0
- package/docs/desktop-task-manager.md +124 -0
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +8 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +41 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +12 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +1 -1
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +1 -1
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -51
- package/external/pi-tools-suite/src/dcp/debug-log.ts +6 -0
- package/external/pi-tools-suite/src/dcp/index.ts +27 -125
- package/external/pi-tools-suite/src/dcp/prompts.ts +2 -2
- package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +2 -1
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +31 -10
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +6 -7
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +153 -111
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +28 -17
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +61 -27
- package/external/pi-tools-suite/src/dcp/pruner.ts +21 -13
- package/external/pi-tools-suite/src/dcp/state.ts +59 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +4 -0
- package/external/pi-tools-suite/src/lib/rpc-session-state.ts +34 -0
- package/external/pi-tools-suite/src/todo/todo.ts +8 -3
- package/package.json +8 -4
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
NudgeThresholds,
|
|
8
8
|
} from "./pruner-types.js";
|
|
9
9
|
import { extractBlockId, messageText } from "./pruner-metadata.js";
|
|
10
|
-
import {
|
|
10
|
+
import { stableMessageKeys } from "./pruner-message-ids.js";
|
|
11
11
|
|
|
12
12
|
function coercePercentThreshold(value: number | string | undefined, fallback: number): number {
|
|
13
13
|
if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
|
|
@@ -56,6 +56,7 @@ function isRealAnchorCandidate(msg: any): boolean {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function findAnchorMessage(messages: any[]): { msg: any; index: number; stableId: string; timestamp: number; role: string } | null {
|
|
59
|
+
const stableKeys = stableMessageKeys(messages);
|
|
59
60
|
// Prefer the latest real user message: it gives the reminder direct user-like
|
|
60
61
|
// salience without creating a new synthetic message at the end of context.
|
|
61
62
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -65,7 +66,7 @@ function findAnchorMessage(messages: any[]): { msg: any; index: number; stableId
|
|
|
65
66
|
return {
|
|
66
67
|
msg,
|
|
67
68
|
index: i,
|
|
68
|
-
stableId:
|
|
69
|
+
stableId: stableKeys[i]!,
|
|
69
70
|
timestamp: msg.timestamp,
|
|
70
71
|
role: msg.role,
|
|
71
72
|
};
|
|
@@ -79,7 +80,7 @@ function findAnchorMessage(messages: any[]): { msg: any; index: number; stableId
|
|
|
79
80
|
return {
|
|
80
81
|
msg,
|
|
81
82
|
index: i,
|
|
82
|
-
stableId:
|
|
83
|
+
stableId: stableKeys[i]!,
|
|
83
84
|
timestamp: msg.timestamp,
|
|
84
85
|
role: msg.role,
|
|
85
86
|
};
|
|
@@ -88,8 +89,8 @@ function findAnchorMessage(messages: any[]): { msg: any; index: number; stableId
|
|
|
88
89
|
return null;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
function anchorMatchesMessage(anchor: DcpNudgeAnchor, msg: any,
|
|
92
|
-
if (anchor.anchorStableId &&
|
|
92
|
+
function anchorMatchesMessage(anchor: DcpNudgeAnchor, msg: any, stableKey: string): boolean {
|
|
93
|
+
if (anchor.anchorStableId && stableKey === anchor.anchorStableId) return true;
|
|
93
94
|
return msg?.timestamp === anchor.anchorTimestamp;
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -207,34 +208,33 @@ export function upsertNudgeAnchor(
|
|
|
207
208
|
messages: any[],
|
|
208
209
|
state: DcpState,
|
|
209
210
|
type: DcpNudgeType,
|
|
210
|
-
options: { contextPercent?: number } = {},
|
|
211
|
+
options: { contextPercent?: number; renderedReminder?: string } = {},
|
|
211
212
|
): { anchor: DcpNudgeAnchor | null; created: boolean; updated: boolean } {
|
|
212
|
-
const
|
|
213
|
-
if (!target) {
|
|
214
|
-
// The caller will append one synthetic tail reminder. Drop persisted
|
|
215
|
-
// anchors first so applyAnchoredNudges cannot render a second reminder.
|
|
216
|
-
state.nudgeAnchors = [];
|
|
217
|
-
state.lastNudge = undefined;
|
|
218
|
-
return { anchor: null, created: false, updated: false };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const key = `${target.stableId}|${target.timestamp}`;
|
|
213
|
+
const stableKeys = stableMessageKeys(messages);
|
|
222
214
|
let existing: DcpNudgeAnchor | null = null;
|
|
223
215
|
for (const anchor of state.nudgeAnchors) {
|
|
224
|
-
const
|
|
225
|
-
|
|
216
|
+
const stillPresent = messages.some((message, index) =>
|
|
217
|
+
isRealAnchorCandidate(message) && anchorMatchesMessage(anchor, message, stableKeys[index]!),
|
|
218
|
+
);
|
|
219
|
+
if (!stillPresent) continue;
|
|
226
220
|
if (!existing || isNewerAnchor(anchor, existing)) existing = anchor;
|
|
227
221
|
}
|
|
228
222
|
|
|
229
223
|
const now = Date.now();
|
|
230
224
|
if (existing) {
|
|
231
|
-
//
|
|
232
|
-
//
|
|
225
|
+
// Keep the original carrier and frozen text. Moving a reminder to the
|
|
226
|
+
// newest user message would remove bytes from an old provider-cache prefix.
|
|
233
227
|
state.nudgeAnchors = [existing];
|
|
234
228
|
const shouldUpgrade = typePriority(type) > typePriority(existing.type);
|
|
235
|
-
if (shouldUpgrade)
|
|
236
|
-
|
|
237
|
-
|
|
229
|
+
if (shouldUpgrade) {
|
|
230
|
+
existing.type = type;
|
|
231
|
+
existing.contextPercent = options.contextPercent ?? existing.contextPercent;
|
|
232
|
+
existing.renderedReminder = options.renderedReminder ?? existing.renderedReminder;
|
|
233
|
+
existing.updatedAt = now;
|
|
234
|
+
} else if (!existing.renderedReminder && options.renderedReminder) {
|
|
235
|
+
// One-time migration for anchors persisted before frozen rendering.
|
|
236
|
+
existing.renderedReminder = options.renderedReminder;
|
|
237
|
+
}
|
|
238
238
|
state.lastNudge = {
|
|
239
239
|
type: existing.type,
|
|
240
240
|
anchorId: existing.id,
|
|
@@ -246,6 +246,15 @@ export function upsertNudgeAnchor(
|
|
|
246
246
|
return { anchor: existing, created: false, updated: shouldUpgrade };
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
const target = findAnchorMessage(messages);
|
|
250
|
+
if (!target) {
|
|
251
|
+
// The caller will append one synthetic tail reminder. Drop persisted
|
|
252
|
+
// anchors first so applyAnchoredNudges cannot render a second reminder.
|
|
253
|
+
state.nudgeAnchors = [];
|
|
254
|
+
state.lastNudge = undefined;
|
|
255
|
+
return { anchor: null, created: false, updated: false };
|
|
256
|
+
}
|
|
257
|
+
|
|
249
258
|
const anchor: DcpNudgeAnchor = {
|
|
250
259
|
id: state.nextNudgeAnchorId++,
|
|
251
260
|
type,
|
|
@@ -254,6 +263,7 @@ export function upsertNudgeAnchor(
|
|
|
254
263
|
anchorRole: target.role,
|
|
255
264
|
turnIndex: state.currentTurn,
|
|
256
265
|
contextPercent: options.contextPercent,
|
|
266
|
+
renderedReminder: options.renderedReminder,
|
|
257
267
|
createdAt: now,
|
|
258
268
|
updatedAt: now,
|
|
259
269
|
};
|
|
@@ -275,12 +285,16 @@ export function applyAnchoredNudges(
|
|
|
275
285
|
messages: any[],
|
|
276
286
|
state: DcpState,
|
|
277
287
|
render: (anchor: DcpNudgeAnchor) => string,
|
|
278
|
-
):
|
|
279
|
-
if (state.nudgeAnchors.length === 0) return;
|
|
288
|
+
): { rendered: boolean; stateChanged: boolean } {
|
|
289
|
+
if (state.nudgeAnchors.length === 0) return { rendered: false, stateChanged: false };
|
|
280
290
|
|
|
291
|
+
const stableKeys = stableMessageKeys(messages);
|
|
292
|
+
const priorAnchors = state.nudgeAnchors;
|
|
281
293
|
let selected: { anchor: DcpNudgeAnchor; index: number } | null = null;
|
|
282
294
|
for (const anchor of state.nudgeAnchors) {
|
|
283
|
-
const index = messages.findIndex((msg, messageIndex) =>
|
|
295
|
+
const index = messages.findIndex((msg, messageIndex) =>
|
|
296
|
+
anchorMatchesMessage(anchor, msg, stableKeys[messageIndex]!),
|
|
297
|
+
);
|
|
284
298
|
if (index === -1) continue;
|
|
285
299
|
if (!selected || isNewerAnchor(anchor, selected.anchor)) {
|
|
286
300
|
selected = { anchor, index };
|
|
@@ -290,7 +304,27 @@ export function applyAnchoredNudges(
|
|
|
290
304
|
// Defensive migration for persisted pre-singleton state: render only the
|
|
291
305
|
// newest valid anchor and discard every stale predecessor.
|
|
292
306
|
state.nudgeAnchors = selected ? [selected.anchor] : [];
|
|
293
|
-
|
|
307
|
+
const anchorSetChanged = state.nudgeAnchors.length !== priorAnchors.length ||
|
|
308
|
+
state.nudgeAnchors.some((anchor, index) => anchor !== priorAnchors[index]);
|
|
309
|
+
if (!selected) return { rendered: false, stateChanged: anchorSetChanged };
|
|
310
|
+
|
|
311
|
+
const materializedReminder = selected.anchor.renderedReminder === undefined;
|
|
312
|
+
const reminder = selected.anchor.renderedReminder ?? render(selected.anchor);
|
|
313
|
+
if (materializedReminder) selected.anchor.renderedReminder = reminder;
|
|
314
|
+
const anchorMessage = messages[selected.index];
|
|
315
|
+
if (anchorMessage?.role === "assistant") {
|
|
316
|
+
messages.splice(selected.index + 1, 0, {
|
|
317
|
+
role: "user",
|
|
318
|
+
content: reminder,
|
|
319
|
+
timestamp: selected.anchor.createdAt,
|
|
320
|
+
});
|
|
321
|
+
} else {
|
|
322
|
+
appendTextToMessage(anchorMessage, reminder);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
rendered: true,
|
|
326
|
+
stateChanged: anchorSetChanged || materializedReminder,
|
|
327
|
+
};
|
|
294
328
|
}
|
|
295
329
|
|
|
296
330
|
export function clearDcpNudgeAnchors(state: DcpState): number {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DcpConfig } from "./config.js";
|
|
2
2
|
import type { DcpState } from "./state.js";
|
|
3
3
|
import { applyCompressionBlocks, repairOrphanedToolPairs, syncCompressionBlocks } from "./pruner-compression-blocks.js";
|
|
4
|
-
import {
|
|
4
|
+
import { stripStaleDcpMetadataFromMessage } from "./pruner-metadata.js";
|
|
5
5
|
import { injectMessageIds } from "./pruner-message-ids.js";
|
|
6
6
|
import {
|
|
7
7
|
applyAutoToolOutputPruning,
|
|
@@ -63,7 +63,7 @@ export function applyPruning(
|
|
|
63
63
|
typeof contentBlock === "object" && contentBlock !== null ? { ...contentBlock } : contentBlock,
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
|
-
return
|
|
66
|
+
return stripStaleDcpMetadataFromMessage(clone);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
// 1. Count user turns → update state.currentTurn. Do this before inserting
|
|
@@ -79,21 +79,29 @@ export function applyPruning(
|
|
|
79
79
|
// expansion logic could not catch (e.g. multi-block interactions, pre-broken state).
|
|
80
80
|
repairOrphanedToolPairs(msgs);
|
|
81
81
|
|
|
82
|
-
// 3.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
// 3-5. Discover new automatic pruning decisions only at stable checkpoints.
|
|
83
|
+
// Rewriting an old result after every same-turn duplicate breaks provider
|
|
84
|
+
// continuation repeatedly. A new user turn or compression block already
|
|
85
|
+
// establishes a natural history boundary where one prefix rebuild is
|
|
86
|
+
// acceptable. Branches that move the turn count backwards also reopen the
|
|
87
|
+
// checkpoint instead of suppressing pruning indefinitely.
|
|
88
|
+
const newestBlockId = Math.max(0, state.nextBlockId - 1);
|
|
89
|
+
const automaticPruneCheckpoint =
|
|
90
|
+
state.currentTurn !== state.lastAutomaticPruneTurn ||
|
|
91
|
+
newestBlockId !== state.lastAutomaticPruneBlockId;
|
|
92
|
+
if (automaticPruneCheckpoint) {
|
|
93
|
+
applyDeduplication(msgs, state, config);
|
|
94
|
+
applyErrorPurging(msgs, state, config);
|
|
95
|
+
applyAutoToolOutputPruning(msgs, state, config);
|
|
96
|
+
state.lastAutomaticPruneTurn = state.currentTurn;
|
|
97
|
+
state.lastAutomaticPruneBlockId = newestBlockId;
|
|
98
|
+
}
|
|
90
99
|
|
|
91
100
|
// 6. Apply explicit tool output pruning (prunedToolIds)
|
|
92
101
|
applyToolOutputPruning(msgs, state);
|
|
93
102
|
|
|
94
|
-
// 7. Refresh message ID snapshots
|
|
95
|
-
//
|
|
96
|
-
// hook delivers the id map as a hidden control payload.
|
|
103
|
+
// 7. Refresh message ID snapshots and append stable distributed metadata to
|
|
104
|
+
// user/tool-result carriers. Assistant items remain byte-stable.
|
|
97
105
|
injectMessageIds(msgs, state, { config });
|
|
98
106
|
|
|
99
107
|
// 8. state.messageIdSnapshot/messageMetaSnapshot are already updated by injectMessageIds
|
|
@@ -119,6 +119,8 @@ export interface DcpNudgeAnchor {
|
|
|
119
119
|
turnIndex: number
|
|
120
120
|
/** Approximate context usage that triggered the reminder, as a 0-1 fraction. */
|
|
121
121
|
contextPercent?: number
|
|
122
|
+
/** Frozen provider-visible reminder text for cache-stable reapplication. */
|
|
123
|
+
renderedReminder?: string
|
|
122
124
|
/** Wall-clock creation time. */
|
|
123
125
|
createdAt: number
|
|
124
126
|
/** Wall-clock time of the latest re-application/update. */
|
|
@@ -166,6 +168,10 @@ export interface DcpState {
|
|
|
166
168
|
messageIdSnapshot: Map<string, number>
|
|
167
169
|
/** Extra metadata for the model-visible DCP message IDs in messageIdSnapshot. */
|
|
168
170
|
messageMetaSnapshot: Map<string, MessageIdMeta>
|
|
171
|
+
/** Stable message identity → persistent model-visible mNNN assignment. */
|
|
172
|
+
messageIdsByStableId: Map<string, string>
|
|
173
|
+
/** Monotonic counter for persistent message IDs. */
|
|
174
|
+
nextMessageId: number
|
|
169
175
|
|
|
170
176
|
// ── Turn tracking ──────────────────────────────────────────────────────────
|
|
171
177
|
/**
|
|
@@ -174,6 +180,10 @@ export interface DcpState {
|
|
|
174
180
|
* context array in the `context` event handler.
|
|
175
181
|
*/
|
|
176
182
|
currentTurn: number
|
|
183
|
+
/** Last user-turn count at which automatic prune discovery ran. */
|
|
184
|
+
lastAutomaticPruneTurn: number
|
|
185
|
+
/** Highest created block ID at the last automatic-prune checkpoint. */
|
|
186
|
+
lastAutomaticPruneBlockId: number
|
|
177
187
|
|
|
178
188
|
// ── Statistics ─────────────────────────────────────────────────────────────
|
|
179
189
|
/** Running total of tokens estimated to have been saved by pruning/compression */
|
|
@@ -253,7 +263,11 @@ export function createState(): DcpState {
|
|
|
253
263
|
nextBlockId: 1,
|
|
254
264
|
messageIdSnapshot: new Map(),
|
|
255
265
|
messageMetaSnapshot: new Map(),
|
|
266
|
+
messageIdsByStableId: new Map(),
|
|
267
|
+
nextMessageId: 1,
|
|
256
268
|
currentTurn: 0,
|
|
269
|
+
lastAutomaticPruneTurn: -1,
|
|
270
|
+
lastAutomaticPruneBlockId: 0,
|
|
257
271
|
tokensSaved: 0,
|
|
258
272
|
totalPruneCount: 0,
|
|
259
273
|
totalToolCallCount: 0,
|
|
@@ -285,7 +299,11 @@ export function resetState(state: DcpState): void {
|
|
|
285
299
|
state.nextBlockId = 1
|
|
286
300
|
state.messageIdSnapshot.clear()
|
|
287
301
|
state.messageMetaSnapshot.clear()
|
|
302
|
+
state.messageIdsByStableId.clear()
|
|
303
|
+
state.nextMessageId = 1
|
|
288
304
|
state.currentTurn = 0
|
|
305
|
+
state.lastAutomaticPruneTurn = -1
|
|
306
|
+
state.lastAutomaticPruneBlockId = 0
|
|
289
307
|
state.tokensSaved = 0
|
|
290
308
|
state.totalPruneCount = 0
|
|
291
309
|
state.totalToolCallCount = 0
|
|
@@ -424,12 +442,18 @@ export interface SerializedDcpState {
|
|
|
424
442
|
nudgeAnchors?: DcpNudgeAnchor[]
|
|
425
443
|
nextNudgeAnchorId?: number
|
|
426
444
|
lastNudge?: DcpLastNudge
|
|
445
|
+
/** Persistent stable-identity → mNNN assignments. */
|
|
446
|
+
messageIdsByStableId?: Array<[string, string]>
|
|
447
|
+
nextMessageId?: number
|
|
427
448
|
/**
|
|
428
449
|
* Persisted since v??. `context` events re-seed currentTurn from raw
|
|
429
450
|
* messages, but keeping it across session restarts gives diagnostics and
|
|
430
451
|
* telemetry a contiguous turn counter instead of resetting to 0.
|
|
431
452
|
*/
|
|
432
453
|
currentTurn?: number
|
|
454
|
+
/** Automatic pruning runs only when one of these checkpoints changes. */
|
|
455
|
+
lastAutomaticPruneTurn?: number
|
|
456
|
+
lastAutomaticPruneBlockId?: number
|
|
433
457
|
/**
|
|
434
458
|
* Persisted since v?.?. Without persistence a pi process restart silently
|
|
435
459
|
* reset the nudge cadence counter to 0, which could suppress the next
|
|
@@ -622,7 +646,11 @@ export function serializeState(state: DcpState): SerializedDcpState {
|
|
|
622
646
|
nudgeAnchors: state.nudgeAnchors,
|
|
623
647
|
nextNudgeAnchorId: state.nextNudgeAnchorId,
|
|
624
648
|
lastNudge: state.lastNudge,
|
|
649
|
+
messageIdsByStableId: Array.from(state.messageIdsByStableId.entries()),
|
|
650
|
+
nextMessageId: state.nextMessageId,
|
|
625
651
|
currentTurn: state.currentTurn,
|
|
652
|
+
lastAutomaticPruneTurn: state.lastAutomaticPruneTurn,
|
|
653
|
+
lastAutomaticPruneBlockId: state.lastAutomaticPruneBlockId,
|
|
626
654
|
nudgeCounter: state.nudgeCounter,
|
|
627
655
|
lastNudgeTurn: state.lastNudgeTurn,
|
|
628
656
|
lastContextWindow: state.lastContextWindow,
|
|
@@ -776,6 +804,31 @@ export function restoreState(state: DcpState, data: unknown): void {
|
|
|
776
804
|
state.lastNudge = saved.lastNudge
|
|
777
805
|
}
|
|
778
806
|
|
|
807
|
+
if (Array.isArray(saved.messageIdsByStableId)) {
|
|
808
|
+
const usedIds = new Set<string>()
|
|
809
|
+
const usedStableKeys = new Set<string>()
|
|
810
|
+
state.messageIdsByStableId = new Map(
|
|
811
|
+
saved.messageIdsByStableId.filter(
|
|
812
|
+
(entry): entry is [string, string] => {
|
|
813
|
+
if (!Array.isArray(entry) || typeof entry[0] !== "string" || typeof entry[1] !== "string") return false
|
|
814
|
+
if (!entry[0] || !/^m\d+$/.test(entry[1]) || usedIds.has(entry[1]) || usedStableKeys.has(entry[0])) return false
|
|
815
|
+
usedIds.add(entry[1])
|
|
816
|
+
usedStableKeys.add(entry[0])
|
|
817
|
+
return true
|
|
818
|
+
},
|
|
819
|
+
),
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
const highestRestoredMessageId = Math.max(
|
|
823
|
+
0,
|
|
824
|
+
...Array.from(state.messageIdsByStableId.values()).map((id) => Number.parseInt(id.slice(1), 10) || 0),
|
|
825
|
+
)
|
|
826
|
+
if (typeof saved.nextMessageId === "number" && Number.isFinite(saved.nextMessageId) && saved.nextMessageId > 0) {
|
|
827
|
+
state.nextMessageId = Math.max(Math.floor(saved.nextMessageId), highestRestoredMessageId + 1)
|
|
828
|
+
} else {
|
|
829
|
+
state.nextMessageId = highestRestoredMessageId + 1
|
|
830
|
+
}
|
|
831
|
+
|
|
779
832
|
// nudgeCounter: clamp to a non-negative integer so a corrupted payload
|
|
780
833
|
// cannot stall reminders by going negative. Default 0 keeps older sessions
|
|
781
834
|
// behaving like a fresh cadence on first post-restart context event.
|
|
@@ -793,6 +846,12 @@ export function restoreState(state: DcpState, data: unknown): void {
|
|
|
793
846
|
if (typeof saved.currentTurn === "number" && Number.isFinite(saved.currentTurn) && saved.currentTurn >= 0) {
|
|
794
847
|
state.currentTurn = Math.floor(saved.currentTurn)
|
|
795
848
|
}
|
|
849
|
+
if (typeof saved.lastAutomaticPruneTurn === "number" && Number.isFinite(saved.lastAutomaticPruneTurn)) {
|
|
850
|
+
state.lastAutomaticPruneTurn = Math.floor(saved.lastAutomaticPruneTurn)
|
|
851
|
+
}
|
|
852
|
+
if (typeof saved.lastAutomaticPruneBlockId === "number" && Number.isFinite(saved.lastAutomaticPruneBlockId) && saved.lastAutomaticPruneBlockId >= 0) {
|
|
853
|
+
state.lastAutomaticPruneBlockId = Math.floor(saved.lastAutomaticPruneBlockId)
|
|
854
|
+
}
|
|
796
855
|
if (typeof saved.lastContextWindow === "number" && Number.isFinite(saved.lastContextWindow) && saved.lastContextWindow > 0) {
|
|
797
856
|
state.lastContextWindow = saved.lastContextWindow
|
|
798
857
|
}
|
|
@@ -329,6 +329,10 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
|
|
|
329
329
|
"description": "Use when the sub-agent should make or plan code changes for a feature, bug fix, or refactor.",
|
|
330
330
|
"model": "openai-codex/gpt-5.6-sol",
|
|
331
331
|
"fallbackModels": ["zai/glm-5.3"],
|
|
332
|
+
"modelByParent": {
|
|
333
|
+
"openai-codex/gpt-5.6-luna*": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5.3"] },
|
|
334
|
+
"openai-codex/gpt-5.6-sol*": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5.3"] }
|
|
335
|
+
},
|
|
332
336
|
"thinking": "high"
|
|
333
337
|
},
|
|
334
338
|
"deep": {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Reserved RPC widget key consumed by Pix ACP as a structured event envelope. */
|
|
2
|
+
export const RPC_SESSION_STATE_WIDGET_KEY = "pix.session-state";
|
|
3
|
+
export const RPC_SESSION_STATE_ENV = "PIX_ACP_SESSION_STATE_BRIDGE";
|
|
4
|
+
|
|
5
|
+
export interface RpcSessionStateContext {
|
|
6
|
+
readonly mode?: unknown;
|
|
7
|
+
readonly ui?: {
|
|
8
|
+
setWidget?: (key: string, lines: string[] | undefined) => void;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Publish structured extension state through pi's supported RPC UI protocol.
|
|
14
|
+
*
|
|
15
|
+
* TUI/print/json modes keep using their native event consumers. Serialization
|
|
16
|
+
* is best-effort so an optional external UI can never break the source tool.
|
|
17
|
+
*/
|
|
18
|
+
export function publishRpcSessionState(
|
|
19
|
+
ctx: RpcSessionStateContext | undefined,
|
|
20
|
+
channel: string,
|
|
21
|
+
data: unknown,
|
|
22
|
+
): void {
|
|
23
|
+
if (
|
|
24
|
+
process.env[RPC_SESSION_STATE_ENV] !== "1"
|
|
25
|
+
|| ctx?.mode !== "rpc"
|
|
26
|
+
|| !ctx.ui?.setWidget
|
|
27
|
+
|| !channel.trim()
|
|
28
|
+
) return;
|
|
29
|
+
try {
|
|
30
|
+
ctx.ui.setWidget(RPC_SESSION_STATE_WIDGET_KEY, [channel, JSON.stringify(data)]);
|
|
31
|
+
} catch {
|
|
32
|
+
// The event bus remains authoritative when RPC UI transport is absent.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "./tool/types.js";
|
|
43
43
|
import { formatCommandTaskLine } from "./view/format.js";
|
|
44
44
|
import { formatStatusLabel } from "./view/labels.js";
|
|
45
|
+
import { publishRpcSessionState, type RpcSessionStateContext } from "../lib/rpc-session-state.js";
|
|
45
46
|
|
|
46
47
|
const SECTION_PENDING = "── Pending ──";
|
|
47
48
|
const SECTION_IN_PROGRESS = "── In Progress ──";
|
|
@@ -96,7 +97,9 @@ interface TodoToolRegistrationOptions extends TodoToolHooks {
|
|
|
96
97
|
parameters?: typeof TodoParamsSchema;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
type TodoStateEventContext =
|
|
100
|
+
type TodoStateEventContext = RpcSessionStateContext & {
|
|
101
|
+
sessionManager?: { getSessionFile?: () => unknown; getSessionId?: () => unknown };
|
|
102
|
+
};
|
|
100
103
|
type TodoStateEventEmitter = { events?: { emit?: (channel: string, data: unknown) => void } };
|
|
101
104
|
type TodoStateEntryWriter = { appendEntry<T = unknown>(customType: string, data?: T): void };
|
|
102
105
|
|
|
@@ -225,7 +228,7 @@ export function publishTodoState(
|
|
|
225
228
|
const state = getState();
|
|
226
229
|
const sessionFile = sessionFileFromContext(ctx);
|
|
227
230
|
const sessionId = sessionIdFromContext(ctx);
|
|
228
|
-
|
|
231
|
+
const snapshot = {
|
|
229
232
|
version: 1,
|
|
230
233
|
details: {
|
|
231
234
|
action,
|
|
@@ -236,7 +239,9 @@ export function publishTodoState(
|
|
|
236
239
|
...(sessionFile ? { sessionFile } : {}),
|
|
237
240
|
...(sessionId ? { sessionId } : {}),
|
|
238
241
|
checkedAt: Date.now(),
|
|
239
|
-
}
|
|
242
|
+
};
|
|
243
|
+
pi.events?.emit?.(TODO_STATE_EVENT, snapshot);
|
|
244
|
+
publishRpcSessionState(ctx as TodoStateEventContext | undefined, TODO_STATE_EVENT, snapshot);
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
function appendTodoStateSnapshot(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ui-extend",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.39",
|
|
4
4
|
"description": "Pix: a workspace-first terminal UI for Pi with tabs, readable tool activity, voice input, and bundled agent tools.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
"test:tools-suite": "npm --prefix external/pi-tools-suite test",
|
|
63
63
|
"build:acp": "npm --prefix acp run build",
|
|
64
64
|
"check:acp": "npm --prefix acp run check",
|
|
65
|
+
"dev:desktop": "npm --prefix desktop run dev",
|
|
66
|
+
"build:desktop": "npm --prefix desktop run build",
|
|
67
|
+
"check:desktop": "npm --prefix desktop run check",
|
|
65
68
|
"test:browser-qa-e2e": "npm --prefix external/pi-tools-suite run test:browser-qa-e2e",
|
|
66
69
|
"sync:pi-tools-suite": "node scripts/sync-pi-tools-suite.mjs",
|
|
67
70
|
"sync:pi-tools-suite:check": "node scripts/sync-pi-tools-suite.mjs --check",
|
|
@@ -80,9 +83,10 @@
|
|
|
80
83
|
"prepublishOnly": "npm run check && npm run build:pix && npm run generate-schemas"
|
|
81
84
|
},
|
|
82
85
|
"dependencies": {
|
|
83
|
-
"@earendil-works/pi-ai": "0.
|
|
84
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
85
|
-
"@earendil-works/pi-
|
|
86
|
+
"@earendil-works/pi-ai": "0.85.0",
|
|
87
|
+
"@earendil-works/pi-coding-agent": "0.85.0",
|
|
88
|
+
"@earendil-works/pi-server": "0.85.0",
|
|
89
|
+
"@earendil-works/pi-tui": "0.85.0",
|
|
86
90
|
"@mariozechner/clipboard": "^0.3.9",
|
|
87
91
|
"grok-mermaid": "0.2.2",
|
|
88
92
|
"jsonc-parser": "3.3.1",
|