pi-ui-extend 0.1.18 → 0.1.20
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/app.js +8 -6
- package/dist/app/constants.d.ts +1 -0
- package/dist/app/constants.js +2 -1
- package/dist/app/input/voice-controller.js +16 -12
- package/dist/app/popup/popup-menu-controller.d.ts +1 -5
- package/dist/app/popup/popup-menu-controller.js +7 -8
- package/dist/app/process.js +7 -0
- package/dist/app/rendering/conversation-entry-renderer.js +17 -16
- package/dist/app/rendering/conversation-viewport.js +4 -35
- package/dist/app/rendering/editor-layout-renderer.d.ts +5 -1
- package/dist/app/rendering/editor-layout-renderer.js +29 -20
- package/dist/app/rendering/popup-menu-renderer.d.ts +1 -5
- package/dist/app/rendering/popup-menu-renderer.js +24 -34
- package/dist/app/rendering/render-controller.d.ts +2 -0
- package/dist/app/rendering/render-controller.js +40 -49
- package/dist/app/rendering/render-text.js +2 -2
- package/dist/app/rendering/status-line-renderer.d.ts +2 -0
- package/dist/app/rendering/status-line-renderer.js +75 -29
- package/dist/app/rendering/tab-line-renderer.js +3 -3
- package/dist/app/runtime.js +29 -3
- package/dist/app/screen/file-link-opener.d.ts +2 -0
- package/dist/app/screen/file-link-opener.js +84 -17
- package/dist/app/screen/mouse-controller.d.ts +1 -3
- package/dist/app/screen/mouse-controller.js +14 -14
- package/dist/app/screen/screen-styler.js +1 -1
- package/dist/app/session/lazy-session-manager.d.ts +1 -1
- package/dist/app/session/lazy-session-manager.js +64 -52
- package/dist/app/session/queued-message-controller.d.ts +6 -0
- package/dist/app/session/queued-message-controller.js +9 -1
- package/dist/app/session/queued-message-entries.d.ts +8 -0
- package/dist/app/session/queued-message-entries.js +41 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +9 -1
- package/dist/app/session/session-lifecycle-controller.js +45 -11
- package/dist/app/session/tabs-controller.d.ts +11 -1
- package/dist/app/session/tabs-controller.js +197 -30
- package/dist/app/terminal/terminal-controller.d.ts +2 -0
- package/dist/app/terminal/terminal-controller.js +7 -5
- package/dist/app/types.d.ts +1 -1
- package/dist/schemas/pi-tools-suite-schema.d.ts +3 -0
- package/dist/schemas/pi-tools-suite-schema.js +3 -0
- package/dist/theme.d.ts +11 -0
- package/dist/theme.js +26 -4
- package/extensions/question/tui.ts +1 -1
- package/extensions/session-title/config.ts +3 -3
- package/extensions/session-title/index.ts +60 -5
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +1 -0
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +0 -3
- package/external/pi-tools-suite/src/async-subagents/core/notifications.ts +64 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +54 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +4 -4
- package/external/pi-tools-suite/src/config.ts +13 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -4
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +5 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +683 -0
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/lib/lsp.ts +2 -5
- package/external/pi-tools-suite/src/lsp/_shared/config.ts +2 -0
- package/external/pi-tools-suite/src/lsp/_shared/types.ts +2 -0
- package/external/pi-tools-suite/src/lsp/manager.ts +15 -9
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +1 -0
- package/external/pi-tools-suite/src/todo/index.ts +81 -4
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +5 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +3 -14
- package/schemas/pi-tools-suite.json +19 -0
- package/apps/desktop-tauri/README.md +0 -103
- package/apps/desktop-tauri/bin/pix-desktop.mjs +0 -89
|
@@ -320,7 +320,7 @@ export class AppMouseController {
|
|
|
320
320
|
return false;
|
|
321
321
|
const opened = this.host.openFileLink?.(link) ?? openDetectedFileLink(link);
|
|
322
322
|
if (!opened)
|
|
323
|
-
this.host.showToast("Could not open file link
|
|
323
|
+
this.host.showToast("Could not open file link in the detected editor or system viewer.", "warning");
|
|
324
324
|
return true;
|
|
325
325
|
}
|
|
326
326
|
handleInputScrollBar(event) {
|
|
@@ -784,14 +784,15 @@ export class AppMouseController {
|
|
|
784
784
|
const topOffset = editorLayoutTopOffset(tabPanelRows);
|
|
785
785
|
const toScreenRow = (layoutRow) => Math.max(1, Math.min(terminalRows, topOffset + layoutRow));
|
|
786
786
|
const toScreenRowExclusive = (layoutRow) => Math.max(1, Math.min(terminalRows + 1, topOffset + layoutRow));
|
|
787
|
-
|
|
787
|
+
const inputFrame = {
|
|
788
788
|
inputStartRow: toScreenRow(layout.inputStartRow),
|
|
789
789
|
inputEndRow: toScreenRowExclusive(layout.inputStartRow + layout.renderedInput.lines.length),
|
|
790
790
|
inputSeparatorRow: toScreenRow(layout.inputSeparatorRow),
|
|
791
|
-
inputBottomSeparatorRow: toScreenRow(layout.inputBottomSeparatorRow),
|
|
792
|
-
contentStartColumn: 2,
|
|
793
|
-
contentEndColumn: columns,
|
|
794
791
|
};
|
|
792
|
+
if (layout.inputBottomSeparatorRow !== undefined) {
|
|
793
|
+
inputFrame.inputBottomSeparatorRow = toScreenRow(layout.inputBottomSeparatorRow);
|
|
794
|
+
}
|
|
795
|
+
return inputFrame;
|
|
795
796
|
}
|
|
796
797
|
getSelectedConversationText(anchor, current) {
|
|
797
798
|
const range = orderedConversationSelection(anchor, current);
|
|
@@ -800,11 +801,13 @@ export class AppMouseController {
|
|
|
800
801
|
const renderedLines = this.host.conversationViewport().slice(width, range.start.line, count);
|
|
801
802
|
const lines = [];
|
|
802
803
|
for (let index = 0; index < count; index += 1) {
|
|
803
|
-
const
|
|
804
|
+
const rendered = renderedLines[index];
|
|
805
|
+
const text = rendered?.text ?? "";
|
|
804
806
|
const line = range.start.line + index;
|
|
805
807
|
const startColumn = line === range.start.line ? range.start.x : 1;
|
|
806
808
|
const endColumn = line === range.end.line ? range.end.x : text.length + 1;
|
|
807
|
-
|
|
809
|
+
const lineText = sliceByDisplayColumns(text, startColumn, endColumn);
|
|
810
|
+
lines.push(lineText.trimEnd());
|
|
808
811
|
}
|
|
809
812
|
return lines.join("\n").replace(/\s+$/u, "");
|
|
810
813
|
}
|
|
@@ -935,16 +938,13 @@ function orderedConversationSelection(anchor, current) {
|
|
|
935
938
|
return anchor.x <= current.x ? { start: anchor, end: current } : { start: current, end: anchor };
|
|
936
939
|
}
|
|
937
940
|
export function screenSelectionLineText(row, text, startColumn, endColumn, inputFrame) {
|
|
938
|
-
if (inputFrame &&
|
|
941
|
+
if (inputFrame && row === inputFrame.inputSeparatorRow) {
|
|
939
942
|
return undefined;
|
|
940
943
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (inputFrame && row >= inputFrame.inputStartRow && row < inputFrame.inputEndRow) {
|
|
944
|
-
copyStartColumn = Math.max(copyStartColumn, inputFrame.contentStartColumn);
|
|
945
|
-
copyEndColumn = Math.min(copyEndColumn, inputFrame.contentEndColumn);
|
|
944
|
+
if (inputFrame?.inputBottomSeparatorRow !== undefined && row === inputFrame.inputBottomSeparatorRow) {
|
|
945
|
+
return undefined;
|
|
946
946
|
}
|
|
947
|
-
return sliceByDisplayColumns(text,
|
|
947
|
+
return sliceByDisplayColumns(text, startColumn, endColumn);
|
|
948
948
|
}
|
|
949
949
|
function sameConversationPoint(left, right) {
|
|
950
950
|
return !!left && left.line === right.line && left.x === right.x;
|
|
@@ -75,7 +75,7 @@ export class ScreenStyler {
|
|
|
75
75
|
}
|
|
76
76
|
styleInputLine(row, text, tagSpans, suggestionSpans, width, tagColor, suggestionColor, frameColor) {
|
|
77
77
|
const colors = this.host.theme.colors;
|
|
78
|
-
const baseOptions = { foreground: colors.
|
|
78
|
+
const baseOptions = { foreground: colors.userForeground };
|
|
79
79
|
if (this.selectionRangeForRow(row, width, text))
|
|
80
80
|
return this.styleLine(row, text, width, baseOptions);
|
|
81
81
|
const plain = padOrTrimPlain(text, width);
|
|
@@ -8,4 +8,4 @@ export type LazySessionHistoryReader = {
|
|
|
8
8
|
hasOlder(): boolean;
|
|
9
9
|
readOlder(limit: number): Promise<SessionEntry[]>;
|
|
10
10
|
};
|
|
11
|
-
export declare function openLazySessionManager(sessionPath: string, options?: LazySessionManagerOptions): SessionManager
|
|
11
|
+
export declare function openLazySessionManager(sessionPath: string, options?: LazySessionManagerOptions): Promise<SessionManager>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { appendFileSync,
|
|
3
|
-
import { open as openFile } from "node:fs/promises";
|
|
2
|
+
import { appendFileSync, createReadStream, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, open as openFile, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { buildSessionContext, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
@@ -9,8 +9,8 @@ const CURRENT_SESSION_VERSION = 3;
|
|
|
9
9
|
const DEFAULT_TAIL_ENTRY_COUNT = 180;
|
|
10
10
|
const INITIAL_TAIL_BYTES = 256 * 1024;
|
|
11
11
|
const MAX_TAIL_BYTES = 16 * 1024 * 1024;
|
|
12
|
-
export function openLazySessionManager(sessionPath, options = {}) {
|
|
13
|
-
return
|
|
12
|
+
export async function openLazySessionManager(sessionPath, options = {}) {
|
|
13
|
+
return await LazySessionManager.open(sessionPath, options);
|
|
14
14
|
}
|
|
15
15
|
class LazySessionManager {
|
|
16
16
|
sessionFilePath;
|
|
@@ -29,9 +29,18 @@ class LazySessionManager {
|
|
|
29
29
|
this.sessionFilePath = resolve(sessionPath);
|
|
30
30
|
this.sessionDirPath = resolve(options.sessionDir ?? dirname(this.sessionFilePath));
|
|
31
31
|
this.tailEntryCount = Math.max(1, Math.floor(options.tailEntryCount ?? DEFAULT_TAIL_ENTRY_COUNT));
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
|
|
32
|
+
this.cwdPath = resolve(options.cwdOverride ?? process.cwd());
|
|
33
|
+
this.header = createSessionHeader(this.cwdPath);
|
|
34
|
+
}
|
|
35
|
+
static async open(sessionPath, options = {}) {
|
|
36
|
+
const manager = new LazySessionManager(sessionPath, options);
|
|
37
|
+
await manager.initialize(options.cwdOverride);
|
|
38
|
+
return manager;
|
|
39
|
+
}
|
|
40
|
+
async initialize(cwdOverride) {
|
|
41
|
+
this.header = await this.loadHeaderAsync(cwdOverride);
|
|
42
|
+
this.cwdPath = resolve(cwdOverride ?? this.header.cwd ?? process.cwd());
|
|
43
|
+
await this.loadTailEntriesAsync();
|
|
35
44
|
}
|
|
36
45
|
setSessionFile(sessionFile) {
|
|
37
46
|
if (this.hydrated) {
|
|
@@ -40,9 +49,7 @@ class LazySessionManager {
|
|
|
40
49
|
}
|
|
41
50
|
this.sessionFilePath = resolve(sessionFile);
|
|
42
51
|
this.sessionDirPath = dirname(this.sessionFilePath);
|
|
43
|
-
|
|
44
|
-
this.cwdPath = resolve(this.header.cwd || this.cwdPath);
|
|
45
|
-
this.loadTailEntries();
|
|
52
|
+
throw new Error("LazySessionManager.setSessionFile() before hydration is unsupported");
|
|
46
53
|
}
|
|
47
54
|
newSession(options) {
|
|
48
55
|
if (this.hydrated)
|
|
@@ -106,16 +113,25 @@ class LazySessionManager {
|
|
|
106
113
|
if (this.hydrated || this.tailStartOffset <= 0)
|
|
107
114
|
return undefined;
|
|
108
115
|
let cursorOffset = this.tailStartOffset;
|
|
109
|
-
|
|
116
|
+
let firstEntryOffset = 0;
|
|
117
|
+
let firstEntryOffsetPromise;
|
|
118
|
+
const loadFirstEntryOffset = async () => {
|
|
119
|
+
firstEntryOffsetPromise ??= readFirstSessionEntryOffset(this.sessionFilePath).then((offset) => {
|
|
120
|
+
firstEntryOffset = offset;
|
|
121
|
+
return offset;
|
|
122
|
+
});
|
|
123
|
+
return await firstEntryOffsetPromise;
|
|
124
|
+
};
|
|
110
125
|
return {
|
|
111
126
|
hasOlder: () => cursorOffset > firstEntryOffset,
|
|
112
127
|
readOlder: async (limit) => {
|
|
113
|
-
|
|
128
|
+
const resolvedFirstEntryOffset = await loadFirstEntryOffset();
|
|
129
|
+
if (cursorOffset <= resolvedFirstEntryOffset)
|
|
114
130
|
return [];
|
|
115
131
|
const result = await readSessionEntriesBeforeOffset(this.sessionFilePath, cursorOffset, Math.max(1, Math.floor(limit)));
|
|
116
132
|
cursorOffset = result.startOffset;
|
|
117
133
|
if (result.entries.length === 0)
|
|
118
|
-
cursorOffset =
|
|
134
|
+
cursorOffset = resolvedFirstEntryOffset;
|
|
119
135
|
return result.entries;
|
|
120
136
|
},
|
|
121
137
|
};
|
|
@@ -256,18 +272,17 @@ class LazySessionManager {
|
|
|
256
272
|
}
|
|
257
273
|
return this.hydrated;
|
|
258
274
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const result = readTailSessionEntries(this.sessionFilePath, this.tailEntryCount);
|
|
275
|
+
async loadHeaderAsync(cwdOverride) {
|
|
276
|
+
const existingHeader = await readSessionHeaderFast(this.sessionFilePath);
|
|
277
|
+
if (existingHeader)
|
|
278
|
+
return existingHeader;
|
|
279
|
+
await mkdir(dirname(this.sessionFilePath), { recursive: true });
|
|
280
|
+
const header = createSessionHeader(resolve(cwdOverride ?? process.cwd()));
|
|
281
|
+
await writeFile(this.sessionFilePath, `${JSON.stringify(header)}\n`, "utf8");
|
|
282
|
+
return header;
|
|
283
|
+
}
|
|
284
|
+
async loadTailEntriesAsync() {
|
|
285
|
+
const result = await readTailSessionEntries(this.sessionFilePath, this.tailEntryCount);
|
|
271
286
|
this.entries = result.entries;
|
|
272
287
|
this.tailStartOffset = result.startOffset;
|
|
273
288
|
this.rebuildIndexes();
|
|
@@ -338,8 +353,8 @@ function createSessionHeader(cwd) {
|
|
|
338
353
|
function createSessionId() {
|
|
339
354
|
return randomUUID();
|
|
340
355
|
}
|
|
341
|
-
function readSessionHeaderFast(filePath) {
|
|
342
|
-
const line = readFirstLine(filePath, 64 * 1024);
|
|
356
|
+
async function readSessionHeaderFast(filePath) {
|
|
357
|
+
const line = await readFirstLine(filePath, 64 * 1024);
|
|
343
358
|
if (!line)
|
|
344
359
|
return undefined;
|
|
345
360
|
try {
|
|
@@ -353,12 +368,12 @@ function readSessionHeaderFast(filePath) {
|
|
|
353
368
|
return undefined;
|
|
354
369
|
}
|
|
355
370
|
}
|
|
356
|
-
function readFirstLine(filePath, maxBytes) {
|
|
357
|
-
let
|
|
371
|
+
async function readFirstLine(filePath, maxBytes) {
|
|
372
|
+
let file;
|
|
358
373
|
try {
|
|
359
|
-
|
|
374
|
+
file = await openFile(filePath, "r");
|
|
360
375
|
const buffer = Buffer.alloc(maxBytes);
|
|
361
|
-
const bytesRead =
|
|
376
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
362
377
|
const text = buffer.toString("utf8", 0, bytesRead);
|
|
363
378
|
return text.split("\n")[0];
|
|
364
379
|
}
|
|
@@ -366,16 +381,15 @@ function readFirstLine(filePath, maxBytes) {
|
|
|
366
381
|
return undefined;
|
|
367
382
|
}
|
|
368
383
|
finally {
|
|
369
|
-
|
|
370
|
-
closeSync(fd);
|
|
384
|
+
await file?.close();
|
|
371
385
|
}
|
|
372
386
|
}
|
|
373
|
-
function readFirstSessionEntryOffset(filePath) {
|
|
374
|
-
let
|
|
387
|
+
async function readFirstSessionEntryOffset(filePath) {
|
|
388
|
+
let file;
|
|
375
389
|
try {
|
|
376
|
-
|
|
390
|
+
file = await openFile(filePath, "r");
|
|
377
391
|
const buffer = Buffer.alloc(64 * 1024);
|
|
378
|
-
const bytesRead =
|
|
392
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
379
393
|
const entries = parseSessionEntryBufferLines(buffer.subarray(0, bytesRead), 0);
|
|
380
394
|
return entries[0]?.offset ?? 0;
|
|
381
395
|
}
|
|
@@ -383,34 +397,30 @@ function readFirstSessionEntryOffset(filePath) {
|
|
|
383
397
|
return 0;
|
|
384
398
|
}
|
|
385
399
|
finally {
|
|
386
|
-
|
|
387
|
-
closeSync(fd);
|
|
400
|
+
await file?.close();
|
|
388
401
|
}
|
|
389
402
|
}
|
|
390
|
-
function readTailSessionEntries(filePath, limit) {
|
|
391
|
-
|
|
392
|
-
return { entries: [], startOffset: 0 };
|
|
393
|
-
const size = statSync(filePath).size;
|
|
403
|
+
async function readTailSessionEntries(filePath, limit) {
|
|
404
|
+
const size = await stat(filePath).then((result) => result.size).catch(() => 0);
|
|
394
405
|
if (size <= 0)
|
|
395
406
|
return { entries: [], startOffset: 0 };
|
|
396
407
|
let byteCount = Math.min(size, INITIAL_TAIL_BYTES);
|
|
397
408
|
const maxBytes = Math.min(size, MAX_TAIL_BYTES);
|
|
398
409
|
while (byteCount <= maxBytes) {
|
|
399
|
-
const result = readTailSessionEntriesWithByteCount(filePath, byteCount, limit);
|
|
410
|
+
const result = await readTailSessionEntriesWithByteCount(filePath, byteCount, limit, size);
|
|
400
411
|
if (result.entries.length >= limit || byteCount >= maxBytes || byteCount >= size)
|
|
401
412
|
return result;
|
|
402
413
|
byteCount = Math.min(size, Math.max(byteCount + 1, byteCount * 2));
|
|
403
414
|
}
|
|
404
415
|
return { entries: [], startOffset: 0 };
|
|
405
416
|
}
|
|
406
|
-
function readTailSessionEntriesWithByteCount(filePath, byteCount, limit) {
|
|
407
|
-
let
|
|
417
|
+
async function readTailSessionEntriesWithByteCount(filePath, byteCount, limit, size) {
|
|
418
|
+
let file;
|
|
408
419
|
try {
|
|
409
|
-
const size = statSync(filePath).size;
|
|
410
420
|
const start = Math.max(0, size - byteCount);
|
|
411
421
|
const buffer = Buffer.alloc(size - start);
|
|
412
|
-
|
|
413
|
-
|
|
422
|
+
file = await openFile(filePath, "r");
|
|
423
|
+
await file.read(buffer, 0, buffer.length, start);
|
|
414
424
|
let parseStart = 0;
|
|
415
425
|
if (start > 0) {
|
|
416
426
|
const firstNewline = buffer.indexOf(10);
|
|
@@ -422,12 +432,14 @@ function readTailSessionEntriesWithByteCount(filePath, byteCount, limit) {
|
|
|
422
432
|
return { entries: [], startOffset: 0 };
|
|
423
433
|
}
|
|
424
434
|
finally {
|
|
425
|
-
|
|
426
|
-
closeSync(fd);
|
|
435
|
+
await file?.close();
|
|
427
436
|
}
|
|
428
437
|
}
|
|
429
438
|
async function readSessionEntriesBeforeOffset(filePath, endOffset, limit) {
|
|
430
|
-
if (
|
|
439
|
+
if (endOffset <= 0)
|
|
440
|
+
return { entries: [], startOffset: 0 };
|
|
441
|
+
const exists = await stat(filePath).then(() => true).catch(() => false);
|
|
442
|
+
if (!exists)
|
|
431
443
|
return { entries: [], startOffset: 0 };
|
|
432
444
|
let byteCount = Math.min(endOffset, INITIAL_TAIL_BYTES);
|
|
433
445
|
const maxBytes = Math.min(endOffset, MAX_TAIL_BYTES);
|
|
@@ -51,6 +51,12 @@ export declare class AppQueuedMessageController {
|
|
|
51
51
|
findQueuedEntry(entryId: string): Extract<Entry, {
|
|
52
52
|
kind: "queued";
|
|
53
53
|
}> | undefined;
|
|
54
|
+
queuedEntries(): Extract<Entry, {
|
|
55
|
+
kind: "queued";
|
|
56
|
+
}>[];
|
|
57
|
+
deferredQueuedEntries(): Extract<Entry, {
|
|
58
|
+
kind: "queued";
|
|
59
|
+
}>[];
|
|
54
60
|
private shouldDeferUserMessage;
|
|
55
61
|
deferUserMessage(message: SubmittedUserMessage): void;
|
|
56
62
|
private rewriteSdkQueuedMessages;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createId } from "../id.js";
|
|
2
2
|
import { stringifyUnknown, submittedUserDisplayText } from "../rendering/message-content.js";
|
|
3
|
+
import { deferredQueuedMessageEntries, queuedMessageEntries } from "./queued-message-entries.js";
|
|
3
4
|
export class AppQueuedMessageController {
|
|
4
5
|
host;
|
|
5
6
|
deferredUserMessages = [];
|
|
@@ -228,9 +229,16 @@ export class AppQueuedMessageController {
|
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
findQueuedEntry(entryId) {
|
|
231
|
-
const entry = this.
|
|
232
|
+
const entry = this.queuedEntries().find((candidate) => candidate.id === entryId)
|
|
233
|
+
?? this.host.visibleEntries().find((candidate) => candidate.id === entryId);
|
|
232
234
|
return entry?.kind === "queued" ? entry : undefined;
|
|
233
235
|
}
|
|
236
|
+
queuedEntries() {
|
|
237
|
+
return queuedMessageEntries(this.host.runtime()?.session, this.deferredUserMessages);
|
|
238
|
+
}
|
|
239
|
+
deferredQueuedEntries() {
|
|
240
|
+
return deferredQueuedMessageEntries(this.deferredUserMessages);
|
|
241
|
+
}
|
|
234
242
|
shouldDeferUserMessage(session) {
|
|
235
243
|
return session.isCompacting || this.promptSubmissionInFlight;
|
|
236
244
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Entry, SubmittedUserMessage } from "../types.js";
|
|
3
|
+
export type QueuedEntry = Extract<Entry, {
|
|
4
|
+
kind: "queued";
|
|
5
|
+
}>;
|
|
6
|
+
export declare function sdkQueuedMessageEntries(session: AgentSession | undefined): QueuedEntry[];
|
|
7
|
+
export declare function deferredQueuedMessageEntries(messages: readonly SubmittedUserMessage[]): QueuedEntry[];
|
|
8
|
+
export declare function queuedMessageEntries(session: AgentSession | undefined, deferredUserMessages: readonly SubmittedUserMessage[]): QueuedEntry[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { shortHash } from "../rendering/render-text.js";
|
|
2
|
+
export function sdkQueuedMessageEntries(session) {
|
|
3
|
+
const entries = [];
|
|
4
|
+
for (const [index, text] of (session?.getSteeringMessages() ?? []).entries()) {
|
|
5
|
+
entries.push({
|
|
6
|
+
id: `queued-sdk-steering-${index}-${shortHash(text)}`,
|
|
7
|
+
kind: "queued",
|
|
8
|
+
mode: "steering",
|
|
9
|
+
text,
|
|
10
|
+
queueSource: "sdk-steering",
|
|
11
|
+
queueIndex: index,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
for (const [index, text] of (session?.getFollowUpMessages() ?? []).entries()) {
|
|
15
|
+
entries.push({
|
|
16
|
+
id: `queued-sdk-follow-up-${index}-${shortHash(text)}`,
|
|
17
|
+
kind: "queued",
|
|
18
|
+
mode: "follow-up",
|
|
19
|
+
text,
|
|
20
|
+
queueSource: "sdk-follow-up",
|
|
21
|
+
queueIndex: index,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return entries;
|
|
25
|
+
}
|
|
26
|
+
export function deferredQueuedMessageEntries(messages) {
|
|
27
|
+
return messages.map((message, index) => ({
|
|
28
|
+
id: `${message.id}-${index}`,
|
|
29
|
+
kind: "queued",
|
|
30
|
+
mode: "steering",
|
|
31
|
+
text: message.displayText,
|
|
32
|
+
queueSource: "deferred",
|
|
33
|
+
queueIndex: index,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
export function queuedMessageEntries(session, deferredUserMessages) {
|
|
37
|
+
return [
|
|
38
|
+
...sdkQueuedMessageEntries(session),
|
|
39
|
+
...deferredQueuedMessageEntries(deferredUserMessages),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
@@ -48,17 +48,25 @@ export type AppSessionLifecycleHost = {
|
|
|
48
48
|
restoreTabsAfterStartup(): Promise<void>;
|
|
49
49
|
render(): void;
|
|
50
50
|
};
|
|
51
|
+
export type BindCurrentSessionOptions = {
|
|
52
|
+
awaitExtensions?: boolean;
|
|
53
|
+
};
|
|
51
54
|
export declare class AppSessionLifecycleController {
|
|
52
55
|
private readonly host;
|
|
53
56
|
private unsubscribe;
|
|
57
|
+
private extensionBindPromise;
|
|
58
|
+
private extensionBindRuntime;
|
|
59
|
+
private extensionBindSession;
|
|
54
60
|
constructor(host: AppSessionLifecycleHost);
|
|
55
61
|
start(): Promise<void>;
|
|
56
|
-
bindCurrentSession(): Promise<void>;
|
|
62
|
+
bindCurrentSession(options?: BindCurrentSessionOptions): Promise<void>;
|
|
57
63
|
unsubscribeSession(): void;
|
|
58
64
|
afterSessionReplacement(message?: string): void;
|
|
59
65
|
private loadReplacementHistory;
|
|
60
66
|
resetSessionView(): void;
|
|
61
67
|
loadSessionHistory(): void;
|
|
62
68
|
requireRuntime(): AgentSessionRuntime;
|
|
69
|
+
private bindSessionExtensions;
|
|
70
|
+
private isCurrentRuntimeSession;
|
|
63
71
|
private extensionUiScope;
|
|
64
72
|
}
|
|
@@ -5,6 +5,9 @@ import { createStartupInfoMessage, isEmptyStartupSession } from "../cli/startup-
|
|
|
5
5
|
export class AppSessionLifecycleController {
|
|
6
6
|
host;
|
|
7
7
|
unsubscribe;
|
|
8
|
+
extensionBindPromise;
|
|
9
|
+
extensionBindRuntime;
|
|
10
|
+
extensionBindSession;
|
|
8
11
|
constructor(host) {
|
|
9
12
|
this.host = host;
|
|
10
13
|
}
|
|
@@ -25,9 +28,9 @@ export class AppSessionLifecycleController {
|
|
|
25
28
|
}
|
|
26
29
|
this.host.setRuntime(runtime);
|
|
27
30
|
runtime.setRebindSession(async () => {
|
|
28
|
-
await this.bindCurrentSession();
|
|
31
|
+
await this.bindCurrentSession({ awaitExtensions: false });
|
|
29
32
|
});
|
|
30
|
-
await this.bindCurrentSession();
|
|
33
|
+
await this.bindCurrentSession({ awaitExtensions: false });
|
|
31
34
|
if (isEmptyStartupSession(runtime)) {
|
|
32
35
|
this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(runtime) });
|
|
33
36
|
}
|
|
@@ -64,21 +67,28 @@ export class AppSessionLifecycleController {
|
|
|
64
67
|
this.host.render();
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
|
-
async bindCurrentSession() {
|
|
70
|
+
async bindCurrentSession(options = {}) {
|
|
68
71
|
const runtime = this.requireRuntime();
|
|
72
|
+
const session = runtime.session;
|
|
69
73
|
this.unsubscribe?.();
|
|
70
|
-
this.unsubscribe =
|
|
74
|
+
this.unsubscribe = session.subscribe((event) => {
|
|
71
75
|
this.host.handleSessionEvent(event);
|
|
72
76
|
});
|
|
73
77
|
this.host.closeSdkMenuForBind();
|
|
74
|
-
const extensionUiScope = this.extensionUiScope(
|
|
78
|
+
const extensionUiScope = this.extensionUiScope(session);
|
|
75
79
|
this.host.clearExtensionWidgets(extensionUiScope, { cancelCustomUi: false });
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
const bindPromise = this.bindSessionExtensions(runtime, session, extensionUiScope);
|
|
81
|
+
if (options.awaitExtensions === false) {
|
|
82
|
+
void bindPromise.catch((error) => {
|
|
83
|
+
if (!this.isCurrentRuntimeSession(runtime, session))
|
|
84
|
+
return;
|
|
85
|
+
this.host.addEntry({ id: createId("error"), kind: "error", text: `Extension bind failed: ${stringifyUnknown(error)}` });
|
|
86
|
+
this.host.showToast("Extension initialization failed", "error");
|
|
87
|
+
this.host.render();
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await bindPromise;
|
|
82
92
|
}
|
|
83
93
|
unsubscribeSession() {
|
|
84
94
|
this.unsubscribe?.();
|
|
@@ -123,6 +133,30 @@ export class AppSessionLifecycleController {
|
|
|
123
133
|
throw new Error("Runtime is not initialized");
|
|
124
134
|
return runtime;
|
|
125
135
|
}
|
|
136
|
+
bindSessionExtensions(runtime, session, scopeKey) {
|
|
137
|
+
if (this.extensionBindPromise && this.extensionBindRuntime === runtime && this.extensionBindSession === session) {
|
|
138
|
+
return this.extensionBindPromise;
|
|
139
|
+
}
|
|
140
|
+
const promise = session.bindExtensions({
|
|
141
|
+
uiContext: this.host.createExtensionUIContext(scopeKey),
|
|
142
|
+
commandContextActions: this.host.createExtensionCommandContextActions(runtime),
|
|
143
|
+
shutdownHandler: this.host.extensionShutdownHandler(),
|
|
144
|
+
onError: (error) => this.host.handleExtensionError(error),
|
|
145
|
+
}).finally(() => {
|
|
146
|
+
if (this.extensionBindPromise !== promise)
|
|
147
|
+
return;
|
|
148
|
+
this.extensionBindPromise = undefined;
|
|
149
|
+
this.extensionBindRuntime = undefined;
|
|
150
|
+
this.extensionBindSession = undefined;
|
|
151
|
+
});
|
|
152
|
+
this.extensionBindPromise = promise;
|
|
153
|
+
this.extensionBindRuntime = runtime;
|
|
154
|
+
this.extensionBindSession = session;
|
|
155
|
+
return promise;
|
|
156
|
+
}
|
|
157
|
+
isCurrentRuntimeSession(runtime, session) {
|
|
158
|
+
return this.host.isRunning() && this.host.runtime() === runtime && runtime.session === session;
|
|
159
|
+
}
|
|
126
160
|
extensionUiScope(session) {
|
|
127
161
|
return session.sessionFile ?? session.sessionId;
|
|
128
162
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentSession, type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { BindCurrentSessionOptions } from "./session-lifecycle-controller.js";
|
|
2
3
|
import type { InputEditorDraftState } from "../../input-editor.js";
|
|
3
4
|
import type { AppBlinkController } from "../screen/blink-controller.js";
|
|
4
5
|
import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessage } from "../types.js";
|
|
@@ -10,7 +11,7 @@ export type AppTabsControllerHost = {
|
|
|
10
11
|
runtime(): AgentSessionRuntime | undefined;
|
|
11
12
|
createRuntimeForNewSession(): Promise<AgentSessionRuntime>;
|
|
12
13
|
createRuntimeForSession(sessionPath: string): Promise<AgentSessionRuntime>;
|
|
13
|
-
activateRuntime(runtime: AgentSessionRuntime): Promise<void>;
|
|
14
|
+
activateRuntime(runtime: AgentSessionRuntime, options?: BindCurrentSessionOptions): Promise<void>;
|
|
14
15
|
disposeRuntime(runtime: AgentSessionRuntime): Promise<void>;
|
|
15
16
|
isRunning(): boolean;
|
|
16
17
|
setStatus(status: string): void;
|
|
@@ -37,7 +38,9 @@ export declare class AppTabsController {
|
|
|
37
38
|
private readonly host;
|
|
38
39
|
private readonly tabItems;
|
|
39
40
|
private readonly runtimesByTabId;
|
|
41
|
+
private readonly runtimeLoadsByTabId;
|
|
40
42
|
private readonly runtimeSubscriptionsByTabId;
|
|
43
|
+
private readonly runtimeRefreshTimersByTabId;
|
|
41
44
|
private readonly inputStatesByTabId;
|
|
42
45
|
private readonly deferredUserMessagesByTabId;
|
|
43
46
|
private activeTabId;
|
|
@@ -46,6 +49,8 @@ export declare class AppTabsController {
|
|
|
46
49
|
private restored;
|
|
47
50
|
private retentionCleanupRunning;
|
|
48
51
|
private retentionCleanupScheduled;
|
|
52
|
+
private prewarmScheduled;
|
|
53
|
+
private prewarmRunning;
|
|
49
54
|
constructor(host: AppTabsControllerHost);
|
|
50
55
|
tabs(): readonly SessionTab[];
|
|
51
56
|
isSwitching(): boolean;
|
|
@@ -81,6 +86,9 @@ export declare class AppTabsController {
|
|
|
81
86
|
private clearRuntimeSubscriptions;
|
|
82
87
|
private observeRuntimeForTab;
|
|
83
88
|
private shouldSyncTabFromRuntimeEvent;
|
|
89
|
+
private shouldScheduleDelayedSyncForRuntimeEvent;
|
|
90
|
+
private scheduleDelayedRuntimeSync;
|
|
91
|
+
private clearRuntimeRefreshTimers;
|
|
84
92
|
private syncTabFromObservedRuntime;
|
|
85
93
|
private storeActiveInputState;
|
|
86
94
|
private storeActiveDeferredUserMessages;
|
|
@@ -115,6 +123,8 @@ export declare class AppTabsController {
|
|
|
115
123
|
private filePath;
|
|
116
124
|
private sessionDir;
|
|
117
125
|
private scheduleProjectSessionRetention;
|
|
126
|
+
private scheduleTabPrewarm;
|
|
127
|
+
private prewarmTabs;
|
|
118
128
|
private cleanupOldProjectSessions;
|
|
119
129
|
private preservedSessionPaths;
|
|
120
130
|
private maxProjectSessions;
|