pi-ui-extend 0.1.37 → 0.1.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/app.d.ts +0 -1
- package/dist/app/app.js +22 -21
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +40 -12
- package/dist/app/model/model-usage-status.js +4 -2
- package/dist/app/session/request-history.js +2 -0
- package/dist/app/session/session-event-controller.d.ts +13 -0
- package/dist/app/session/session-event-controller.js +27 -0
- package/dist/app/session/tabs-controller.d.ts +8 -0
- package/dist/app/session/tabs-controller.js +37 -6
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +2 -1
- package/dist/bundled-extensions/terminal-bell/index.js +1 -1
- package/dist/markdown-format.js +14 -25
- package/dist/terminal-width.d.ts +14 -0
- package/dist/terminal-width.js +31 -2
- package/dist/theme.js +2 -2
- package/external/pi-tools-suite/README.md +34 -9
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +35 -21
- package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +70 -12
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/index.ts +6 -6
- package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +4 -2
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
- package/external/pi-tools-suite/src/{glm-coding-discipline → coding-discipline}/index.ts +17 -8
- package/external/pi-tools-suite/src/config.ts +1 -1
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +368 -0
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +3 -0
- package/external/pi-tools-suite/src/dcp/config.ts +23 -0
- package/external/pi-tools-suite/src/dcp/index.ts +112 -7
- package/external/pi-tools-suite/src/dcp/prompts.ts +8 -0
- package/external/pi-tools-suite/src/dcp/state.ts +41 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +30 -22
- package/external/pi-tools-suite/src/index.ts +2 -1
- package/external/pi-tools-suite/src/session-name/index.ts +37 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +16 -4
- package/package.json +4 -4
package/dist/app/app.d.ts
CHANGED
|
@@ -94,7 +94,6 @@ export declare class PiUiExtendApp {
|
|
|
94
94
|
private loadSessionHistory;
|
|
95
95
|
private openSearchResultInNewTab;
|
|
96
96
|
private scrollToUserMessageJumpTarget;
|
|
97
|
-
private findUserEntryBySessionEntryId;
|
|
98
97
|
private findUserEntryByJumpText;
|
|
99
98
|
private loadSessionHistoryAsync;
|
|
100
99
|
private handleSessionEvent;
|
package/dist/app/app.js
CHANGED
|
@@ -360,6 +360,7 @@ export class PiUiExtendApp {
|
|
|
360
360
|
});
|
|
361
361
|
this.workspaceActions = new AppWorkspaceActionsController({
|
|
362
362
|
entries: this.entries,
|
|
363
|
+
allEntries: () => this.sessionEvents.allEntries(),
|
|
363
364
|
runtime: () => this.runtime,
|
|
364
365
|
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
365
366
|
findUserEntry: (entryId) => this.findUserEntry(entryId),
|
|
@@ -999,39 +1000,39 @@ export class PiUiExtendApp {
|
|
|
999
1000
|
return true;
|
|
1000
1001
|
this.workspaceActions.syncUserSessionEntryMetadata();
|
|
1001
1002
|
if (target.sessionEntryId) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const loaded = await this.sessionEvents.loadOlderSessionHistory({ render: false });
|
|
1005
|
-
if (!loaded)
|
|
1006
|
-
break;
|
|
1007
|
-
entry = this.findUserEntryBySessionEntryId(target.sessionEntryId);
|
|
1008
|
-
}
|
|
1009
|
-
if (entry && this.scrollController.scrollToConversationEntry(entry.id))
|
|
1003
|
+
const entryId = this.sessionEvents.revealHistoryEntryForSessionEntryId(target.sessionEntryId);
|
|
1004
|
+
if (entryId && this.scrollController.scrollToConversationEntry(entryId))
|
|
1010
1005
|
return true;
|
|
1011
1006
|
}
|
|
1012
1007
|
const fallbackEntry = this.findUserEntryByJumpText(target);
|
|
1013
1008
|
return fallbackEntry ? this.scrollController.scrollToConversationEntry(fallbackEntry.id) : false;
|
|
1014
1009
|
}
|
|
1015
|
-
findUserEntryBySessionEntryId(sessionEntryId) {
|
|
1016
|
-
return this.entries.find((entry) => entry.kind === "user" && entry.sessionEntryId === sessionEntryId);
|
|
1017
|
-
}
|
|
1018
1010
|
findUserEntryByJumpText(target) {
|
|
1019
1011
|
if (!target.text)
|
|
1020
1012
|
return undefined;
|
|
1021
|
-
const userEntries = this.
|
|
1013
|
+
const userEntries = this.sessionEvents.allEntries().filter((entry) => entry.kind === "user");
|
|
1014
|
+
let matched;
|
|
1022
1015
|
if (target.userIndex !== undefined && target.userCount !== undefined) {
|
|
1023
|
-
const
|
|
1024
|
-
const entry = userEntries[visibleIndex];
|
|
1016
|
+
const entry = userEntries[target.userIndex];
|
|
1025
1017
|
if (entry && normalizeJumpTargetText(entry.text) === normalizeJumpTargetText(target.text))
|
|
1026
|
-
|
|
1018
|
+
matched = entry;
|
|
1019
|
+
}
|
|
1020
|
+
if (!matched) {
|
|
1021
|
+
const normalizedTargetText = normalizeJumpTargetText(target.text);
|
|
1022
|
+
for (let index = userEntries.length - 1; index >= 0; index -= 1) {
|
|
1023
|
+
const entry = userEntries[index];
|
|
1024
|
+
if (entry && normalizeJumpTargetText(entry.text) === normalizedTargetText) {
|
|
1025
|
+
matched = entry;
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1027
1029
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
return entry;
|
|
1030
|
+
if (matched?.sessionEntryId) {
|
|
1031
|
+
// Shift the sliding window onto the matched entry so it is present in the
|
|
1032
|
+
// viewport for the subsequent scrollToConversationEntry call.
|
|
1033
|
+
this.sessionEvents.revealHistoryEntryForSessionEntryId(matched.sessionEntryId);
|
|
1033
1034
|
}
|
|
1034
|
-
return
|
|
1035
|
+
return matched;
|
|
1035
1036
|
}
|
|
1036
1037
|
async loadSessionHistoryAsync(options) {
|
|
1037
1038
|
return this.sessionEvents.loadSessionHistoryAsync(options);
|
|
@@ -35,6 +35,7 @@ export declare class AppInputController {
|
|
|
35
35
|
private readonly pasteHandler;
|
|
36
36
|
constructor(host: InputControllerHost);
|
|
37
37
|
handleChunk(chunk: Buffer): void;
|
|
38
|
+
private consumeBufferedSharedEditorInput;
|
|
38
39
|
private consumeSharedEditorInput;
|
|
39
40
|
private drainInputBuffer;
|
|
40
41
|
private consumeBracketedPastePayload;
|
|
@@ -12,13 +12,23 @@ export class AppInputController {
|
|
|
12
12
|
}
|
|
13
13
|
handleChunk(chunk) {
|
|
14
14
|
let data = chunk.toString("utf8");
|
|
15
|
+
const bufferedSharedEditorInput = this.consumeBufferedSharedEditorInput(data);
|
|
16
|
+
if (bufferedSharedEditorInput.kind === "consumed" || bufferedSharedEditorInput.kind === "pending")
|
|
17
|
+
return;
|
|
18
|
+
if (bufferedSharedEditorInput.kind === "passthrough")
|
|
19
|
+
data = bufferedSharedEditorInput.data;
|
|
15
20
|
if (this.inputBuffer.startsWith("\x1b[<") || data.startsWith("\x1b[<")) {
|
|
16
21
|
this.inputBuffer += data;
|
|
17
22
|
this.drainInputBuffer();
|
|
18
23
|
return;
|
|
19
24
|
}
|
|
20
|
-
|
|
25
|
+
const sharedEditorInput = this.consumeSharedEditorInput(data);
|
|
26
|
+
if (sharedEditorInput === "consumed")
|
|
27
|
+
return;
|
|
28
|
+
if (sharedEditorInput === "pending") {
|
|
29
|
+
this.inputBuffer = data;
|
|
21
30
|
return;
|
|
31
|
+
}
|
|
22
32
|
const extensionInput = this.host.handleExtensionTerminalInput(data);
|
|
23
33
|
if (extensionInput.consume)
|
|
24
34
|
return;
|
|
@@ -29,41 +39,59 @@ export class AppInputController {
|
|
|
29
39
|
this.inputBuffer += data;
|
|
30
40
|
this.drainInputBuffer();
|
|
31
41
|
}
|
|
42
|
+
consumeBufferedSharedEditorInput(data) {
|
|
43
|
+
if (this.host.extensionInputUsesEditor?.() !== true)
|
|
44
|
+
return { kind: "none" };
|
|
45
|
+
if (this.inputBuffer.length === 0)
|
|
46
|
+
return { kind: "none" };
|
|
47
|
+
const buffered = `${this.inputBuffer}${data}`;
|
|
48
|
+
const result = this.consumeSharedEditorInput(buffered);
|
|
49
|
+
if (result === "pending") {
|
|
50
|
+
this.inputBuffer = buffered;
|
|
51
|
+
return { kind: "pending" };
|
|
52
|
+
}
|
|
53
|
+
this.inputBuffer = "";
|
|
54
|
+
if (result === "consumed")
|
|
55
|
+
return { kind: "consumed" };
|
|
56
|
+
return { kind: "passthrough", data: buffered };
|
|
57
|
+
}
|
|
32
58
|
consumeSharedEditorInput(data) {
|
|
33
59
|
if (this.host.extensionInputUsesEditor?.() !== true)
|
|
34
|
-
return
|
|
60
|
+
return "none";
|
|
35
61
|
if (this.host.inputEditor.isInBracketedPaste)
|
|
36
|
-
return
|
|
62
|
+
return "none";
|
|
37
63
|
if (data === "\n") {
|
|
38
64
|
this.insertInputNewline();
|
|
39
|
-
return
|
|
65
|
+
return "consumed";
|
|
40
66
|
}
|
|
41
67
|
if (data === "\r" && this.isShiftPressed()) {
|
|
42
68
|
this.insertInputNewline();
|
|
43
|
-
return
|
|
69
|
+
return "consumed";
|
|
44
70
|
}
|
|
45
71
|
if (SHIFT_ENTER_ESCAPE_SEQUENCES.includes(data)) {
|
|
46
72
|
this.insertInputNewline();
|
|
47
|
-
return
|
|
73
|
+
return "consumed";
|
|
48
74
|
}
|
|
49
75
|
if (data === "\x16") {
|
|
50
76
|
void this.pasteHandler.handleClipboardImagePaste();
|
|
51
|
-
return
|
|
77
|
+
return "consumed";
|
|
52
78
|
}
|
|
53
79
|
const modifiedKey = parseTerminalModifiedKeySequence(data);
|
|
80
|
+
if (modifiedKey.kind === "pending")
|
|
81
|
+
return "pending";
|
|
54
82
|
if (modifiedKey.kind !== "key")
|
|
55
|
-
return
|
|
83
|
+
return "none";
|
|
56
84
|
if (terminalKeyShouldIgnore(modifiedKey.key))
|
|
57
|
-
return
|
|
85
|
+
return "consumed";
|
|
58
86
|
if (terminalKeyIsShiftEnter(modifiedKey.key)) {
|
|
59
87
|
this.insertInputNewline();
|
|
60
|
-
return
|
|
88
|
+
return "consumed";
|
|
61
89
|
}
|
|
62
90
|
if (terminalKeyIsClipboardImagePaste(modifiedKey.key)) {
|
|
63
91
|
void this.pasteHandler.handleClipboardImagePaste();
|
|
64
|
-
return
|
|
92
|
+
return "consumed";
|
|
65
93
|
}
|
|
66
|
-
return
|
|
94
|
+
return "none";
|
|
67
95
|
}
|
|
68
96
|
drainInputBuffer() {
|
|
69
97
|
while (this.inputBuffer.length > 0) {
|
|
@@ -13,6 +13,8 @@ const GOOGLE_ANTIGRAVITY_USER_AGENT = "antigravity/1.11.9 windows/amd64";
|
|
|
13
13
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
14
14
|
const DAY_SECONDS = 86_400;
|
|
15
15
|
const HOUR_SECONDS = 3_600;
|
|
16
|
+
const MODEL_USAGE_WARNING_MIN_USED_PERCENT = 5;
|
|
17
|
+
const MODEL_USAGE_WARNING_MIN_ELAPSED_SECONDS = 6 * HOUR_SECONDS;
|
|
16
18
|
const PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
|
17
19
|
const DEFAULT_ANTIGRAVITY_PROJECT_ID = "rising-fact-p41fc";
|
|
18
20
|
function getPiAuthPath() {
|
|
@@ -929,11 +931,11 @@ function modelUsageWindowWillExhaustBeforeReset(window, now) {
|
|
|
929
931
|
return false;
|
|
930
932
|
const timeUntilResetSeconds = Math.max(0, (window.resetAt - now) / 1000);
|
|
931
933
|
const elapsedSeconds = Math.max(0, window.windowSeconds - timeUntilResetSeconds);
|
|
932
|
-
if (elapsedSeconds
|
|
934
|
+
if (elapsedSeconds < MODEL_USAGE_WARNING_MIN_ELAPSED_SECONDS)
|
|
933
935
|
return false;
|
|
934
936
|
const total = 100;
|
|
935
937
|
const used = total - window.remainingPercent;
|
|
936
|
-
if (used
|
|
938
|
+
if (used < MODEL_USAGE_WARNING_MIN_USED_PERCENT)
|
|
937
939
|
return false;
|
|
938
940
|
const remaining = total - used;
|
|
939
941
|
const averageRate = used / elapsedSeconds;
|
|
@@ -61,6 +61,8 @@ export class AppRequestHistory {
|
|
|
61
61
|
if (this.cursor === undefined) {
|
|
62
62
|
if (direction > 0)
|
|
63
63
|
return false;
|
|
64
|
+
if (this.host.getInput().length > 0)
|
|
65
|
+
return false;
|
|
64
66
|
this.draft = this.host.getInput();
|
|
65
67
|
this.cursor = this.entries.length - 1;
|
|
66
68
|
const entry = this.entries[this.cursor];
|
|
@@ -90,6 +90,7 @@ export declare class AppSessionEventController {
|
|
|
90
90
|
snapshotState(): AppSessionEventControllerState;
|
|
91
91
|
restoreState(state: AppSessionEventControllerState): void;
|
|
92
92
|
reset(): void;
|
|
93
|
+
allEntries(): readonly Entry[];
|
|
93
94
|
loadSessionHistory(): void;
|
|
94
95
|
loadSessionHistoryAsync(options: {
|
|
95
96
|
isCancelled: () => boolean;
|
|
@@ -99,6 +100,18 @@ export declare class AppSessionEventController {
|
|
|
99
100
|
hasOlderSessionHistory(): boolean;
|
|
100
101
|
isLoadingOlderSessionHistory(): boolean;
|
|
101
102
|
loadOlderSessionHistory(options?: LoadOlderSessionHistoryOptions): Promise<boolean>;
|
|
103
|
+
/**
|
|
104
|
+
* Reveal the conversation entry for a session entry id (e.g. a user message
|
|
105
|
+
* selected from the jump menu) by shifting the sliding window directly onto
|
|
106
|
+
* it, then return its local entry id so the caller can scroll to it.
|
|
107
|
+
*
|
|
108
|
+
* Unlike paging older history in fixed increments, this works for any branch
|
|
109
|
+
* position in a single synchronous step and does not race with concurrent
|
|
110
|
+
* window re-anchors (e.g. while the agent is streaming). Returns undefined
|
|
111
|
+
* when the entry is neither in the full history window nor in the live
|
|
112
|
+
* entries (e.g. older than the loaded branch).
|
|
113
|
+
*/
|
|
114
|
+
revealHistoryEntryForSessionEntryId(sessionEntryId: string): string | undefined;
|
|
102
115
|
hasNewerSessionHistory(): boolean;
|
|
103
116
|
isLoadingNewerSessionHistory(): boolean;
|
|
104
117
|
loadNewerSessionHistory(options?: {
|
|
@@ -109,6 +109,9 @@ export class AppSessionEventController {
|
|
|
109
109
|
this.assistantTextBuffer = "";
|
|
110
110
|
this.olderHistoryLoader = undefined;
|
|
111
111
|
}
|
|
112
|
+
allEntries() {
|
|
113
|
+
return this.historyEntries.length > 0 ? this.historyEntries : this.host.entries;
|
|
114
|
+
}
|
|
112
115
|
loadSessionHistory() {
|
|
113
116
|
const runtime = this.host.runtime();
|
|
114
117
|
if (!runtime)
|
|
@@ -165,6 +168,30 @@ export class AppSessionEventController {
|
|
|
165
168
|
return this.shiftHistoryWindow(-HISTORY_WINDOW_SHIFT_ENTRIES, options);
|
|
166
169
|
return this.olderHistoryLoader?.loadOlder(options) ?? false;
|
|
167
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Reveal the conversation entry for a session entry id (e.g. a user message
|
|
173
|
+
* selected from the jump menu) by shifting the sliding window directly onto
|
|
174
|
+
* it, then return its local entry id so the caller can scroll to it.
|
|
175
|
+
*
|
|
176
|
+
* Unlike paging older history in fixed increments, this works for any branch
|
|
177
|
+
* position in a single synchronous step and does not race with concurrent
|
|
178
|
+
* window re-anchors (e.g. while the agent is streaming). Returns undefined
|
|
179
|
+
* when the entry is neither in the full history window nor in the live
|
|
180
|
+
* entries (e.g. older than the loaded branch).
|
|
181
|
+
*/
|
|
182
|
+
revealHistoryEntryForSessionEntryId(sessionEntryId) {
|
|
183
|
+
if (this.historyEntries.length > 0) {
|
|
184
|
+
const index = this.historyEntries.findIndex((entry) => entry.kind === "user" && entry.sessionEntryId === sessionEntryId);
|
|
185
|
+
if (index === -1)
|
|
186
|
+
return undefined;
|
|
187
|
+
const targetStart = Math.max(0, Math.min(this.maxHistoryWindowStart(), index - Math.floor(this.historyWindowSize() / 2)));
|
|
188
|
+
if (targetStart !== this.historyWindowStart)
|
|
189
|
+
this.setHistoryWindowStart(targetStart);
|
|
190
|
+
return this.historyEntries[index]?.id;
|
|
191
|
+
}
|
|
192
|
+
const entry = this.host.entries.find((entry) => entry.kind === "user" && entry.sessionEntryId === sessionEntryId);
|
|
193
|
+
return entry?.id;
|
|
194
|
+
}
|
|
168
195
|
hasNewerSessionHistory() {
|
|
169
196
|
return this.historyEntries.length > 0 && this.historyWindowStart < this.maxHistoryWindowStart();
|
|
170
197
|
}
|
|
@@ -155,6 +155,14 @@ export declare class AppTabsController {
|
|
|
155
155
|
private scheduleTabPrewarm;
|
|
156
156
|
private prewarmTabs;
|
|
157
157
|
private cleanupOldProjectSessions;
|
|
158
|
+
/**
|
|
159
|
+
* Unlink a project session file and, best-effort, its DCP sidecar state.
|
|
160
|
+
* The sidecar path is derived from the session id in the first line of the
|
|
161
|
+
* `.jsonl` (mirrors the DCP module's `safeSessionFileName`), so retention
|
|
162
|
+
* never leaves orphan sidecars behind. Everything here is best-effort:
|
|
163
|
+
* session retention must never interrupt the terminal UI.
|
|
164
|
+
*/
|
|
165
|
+
private unlinkSessionAndDcpSidecar;
|
|
158
166
|
private preservedSessionPaths;
|
|
159
167
|
private maxProjectSessions;
|
|
160
168
|
}
|
|
@@ -1543,12 +1543,7 @@ export class AppTabsController {
|
|
|
1543
1543
|
for (const session of sessions) {
|
|
1544
1544
|
if (keep.has(session.path))
|
|
1545
1545
|
continue;
|
|
1546
|
-
|
|
1547
|
-
await unlink(session.path);
|
|
1548
|
-
}
|
|
1549
|
-
catch {
|
|
1550
|
-
// Session retention must never interrupt the terminal UI.
|
|
1551
|
-
}
|
|
1546
|
+
await this.unlinkSessionAndDcpSidecar(session.path);
|
|
1552
1547
|
}
|
|
1553
1548
|
}
|
|
1554
1549
|
catch {
|
|
@@ -1558,6 +1553,42 @@ export class AppTabsController {
|
|
|
1558
1553
|
this.retentionCleanupRunning = false;
|
|
1559
1554
|
}
|
|
1560
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Unlink a project session file and, best-effort, its DCP sidecar state.
|
|
1558
|
+
* The sidecar path is derived from the session id in the first line of the
|
|
1559
|
+
* `.jsonl` (mirrors the DCP module's `safeSessionFileName`), so retention
|
|
1560
|
+
* never leaves orphan sidecars behind. Everything here is best-effort:
|
|
1561
|
+
* session retention must never interrupt the terminal UI.
|
|
1562
|
+
*/
|
|
1563
|
+
async unlinkSessionAndDcpSidecar(sessionPath) {
|
|
1564
|
+
let sidecarPath;
|
|
1565
|
+
try {
|
|
1566
|
+
const firstLine = (await readFile(sessionPath, "utf8")).split("\n", 1)[0]?.trim();
|
|
1567
|
+
if (firstLine) {
|
|
1568
|
+
const parsed = JSON.parse(firstLine);
|
|
1569
|
+
if (parsed.type === "session" && typeof parsed.id === "string" && parsed.id) {
|
|
1570
|
+
sidecarPath = join(dirname(sessionPath), "dcp-state", parsed.id.replace(/[^a-zA-Z0-9._-]/g, "_") + ".json");
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
catch {
|
|
1575
|
+
// Reading the session id is best-effort; proceed to unlink the file.
|
|
1576
|
+
}
|
|
1577
|
+
try {
|
|
1578
|
+
await unlink(sessionPath);
|
|
1579
|
+
}
|
|
1580
|
+
catch {
|
|
1581
|
+
// Session retention must never interrupt the terminal UI.
|
|
1582
|
+
}
|
|
1583
|
+
if (sidecarPath) {
|
|
1584
|
+
try {
|
|
1585
|
+
await unlink(sidecarPath);
|
|
1586
|
+
}
|
|
1587
|
+
catch {
|
|
1588
|
+
// Sidecar removal is best-effort; never interrupt the terminal UI.
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1561
1592
|
preservedSessionPaths() {
|
|
1562
1593
|
const preserved = new Set();
|
|
1563
1594
|
const add = (sessionPath) => {
|
|
@@ -3,6 +3,7 @@ import type { Entry } from "../types.js";
|
|
|
3
3
|
import { type WorkspaceMutation, type WorkspaceMutationFromToolInput, type WorkspaceMutationPreparation } from "./workspace-undo.js";
|
|
4
4
|
export type AppWorkspaceActionsControllerHost = {
|
|
5
5
|
readonly entries: Entry[];
|
|
6
|
+
allEntries?(): readonly Entry[];
|
|
6
7
|
runtime(): AgentSessionRuntime | undefined;
|
|
7
8
|
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
|
|
8
9
|
findUserEntry(entryId: string): Extract<Entry, {
|
|
@@ -48,8 +48,9 @@ export class AppWorkspaceActionsController {
|
|
|
48
48
|
});
|
|
49
49
|
if (branchUserEntries.length === 0)
|
|
50
50
|
return;
|
|
51
|
+
const loadedEntries = this.host.allEntries?.() ?? this.host.entries;
|
|
51
52
|
let branchIndex = 0;
|
|
52
|
-
for (const entry of
|
|
53
|
+
for (const entry of loadedEntries) {
|
|
53
54
|
if (entry.kind !== "user")
|
|
54
55
|
continue;
|
|
55
56
|
const sessionEntry = branchUserEntries[branchIndex];
|
|
@@ -15,7 +15,7 @@ const TERMINAL_BELL_ATTENTION_EVENT = "pix:terminal-bell:attention";
|
|
|
15
15
|
* extensions, so the renderer emits this on the extension event bus.
|
|
16
16
|
*/
|
|
17
17
|
const RETRY_ACTIVE_EVENT = "pix:retry-active";
|
|
18
|
-
const DEFAULT_COMPLETION_NOTIFICATION_TITLE = "Pix -
|
|
18
|
+
const DEFAULT_COMPLETION_NOTIFICATION_TITLE = "Pix - complete";
|
|
19
19
|
const DEFAULT_ERROR_NOTIFICATION_TITLE = "Pix - error";
|
|
20
20
|
const DEFAULT_QUESTION_NOTIFICATION_TITLE = "Pix - question";
|
|
21
21
|
const DEFAULT_NOTIFICATION_MESSAGE = "{sessionName}";
|
package/dist/markdown-format.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expandTabs, stringDisplayWidth } from "./terminal-width.js";
|
|
1
|
+
import { displayGraphemes, expandTabs, stringDisplayWidth } from "./terminal-width.js";
|
|
2
2
|
import { syntaxHighlightLanguageForMarkdownFence, } from "./syntax-highlight.js";
|
|
3
3
|
const MIN_TRAILING_WORD_WIDTH_TO_REBALANCE = 5;
|
|
4
4
|
export function formatMarkdownTables(text, maxWidth) {
|
|
@@ -211,20 +211,16 @@ function displayTokensWithRanges(text) {
|
|
|
211
211
|
let current = "";
|
|
212
212
|
let currentStart = 0;
|
|
213
213
|
let currentWhitespace;
|
|
214
|
-
for (
|
|
215
|
-
const codePoint = text.codePointAt(index) ?? 0;
|
|
216
|
-
const char = String.fromCodePoint(codePoint);
|
|
214
|
+
for (const { text: char, start } of displayGraphemes(text)) {
|
|
217
215
|
const whitespace = /\s/u.test(char);
|
|
218
216
|
if (current && currentWhitespace !== whitespace) {
|
|
219
|
-
tokens.push({ text: current, start: currentStart, end:
|
|
217
|
+
tokens.push({ text: current, start: currentStart, end: start, whitespace: currentWhitespace ?? false });
|
|
220
218
|
current = "";
|
|
221
|
-
currentStart = index;
|
|
222
219
|
}
|
|
223
220
|
if (!current)
|
|
224
|
-
currentStart =
|
|
221
|
+
currentStart = start;
|
|
225
222
|
current += char;
|
|
226
223
|
currentWhitespace = whitespace;
|
|
227
|
-
index += char.length;
|
|
228
224
|
}
|
|
229
225
|
if (current)
|
|
230
226
|
tokens.push({ text: current, start: currentStart, end: text.length, whitespace: currentWhitespace ?? false });
|
|
@@ -235,19 +231,16 @@ function wrapDisplayTokenByWidth(token, width) {
|
|
|
235
231
|
let chunkText = "";
|
|
236
232
|
let chunkStart = token.start;
|
|
237
233
|
let chunkWidth = 0;
|
|
238
|
-
for (
|
|
239
|
-
const
|
|
240
|
-
const char = String.fromCodePoint(codePoint);
|
|
241
|
-
const charWidth = stringDisplayWidth(char);
|
|
234
|
+
for (const { text: char, width: charWidth, start } of displayGraphemes(token.text)) {
|
|
235
|
+
const absoluteStart = token.start + start;
|
|
242
236
|
if (chunkText && chunkWidth + charWidth > width) {
|
|
243
|
-
chunks.push({ text: chunkText, start: chunkStart, end:
|
|
237
|
+
chunks.push({ text: chunkText, start: chunkStart, end: absoluteStart });
|
|
244
238
|
chunkText = "";
|
|
245
|
-
chunkStart =
|
|
239
|
+
chunkStart = absoluteStart;
|
|
246
240
|
chunkWidth = 0;
|
|
247
241
|
}
|
|
248
242
|
chunkText += char;
|
|
249
243
|
chunkWidth += charWidth;
|
|
250
|
-
index += char.length;
|
|
251
244
|
}
|
|
252
245
|
chunks.push({ text: chunkText, start: chunkStart, end: token.end });
|
|
253
246
|
return chunks;
|
|
@@ -506,20 +499,16 @@ function smartDisplayBreakIndex(text, width) {
|
|
|
506
499
|
let used = 0;
|
|
507
500
|
let fallbackIndex = 0;
|
|
508
501
|
let breakIndex = 0;
|
|
509
|
-
for (
|
|
510
|
-
const
|
|
511
|
-
const char = String.fromCodePoint(codePoint);
|
|
512
|
-
const nextIndex = index + char.length;
|
|
513
|
-
const nextUsed = used + stringDisplayWidth(char);
|
|
502
|
+
for (const { text: char, width: charWidth, start, end } of displayGraphemes(text)) {
|
|
503
|
+
const nextUsed = used + charWidth;
|
|
514
504
|
if (nextUsed > width)
|
|
515
505
|
break;
|
|
516
506
|
used = nextUsed;
|
|
517
|
-
fallbackIndex =
|
|
518
|
-
if (char === "/" &&
|
|
519
|
-
breakIndex =
|
|
507
|
+
fallbackIndex = end;
|
|
508
|
+
if (char === "/" && start > 0)
|
|
509
|
+
breakIndex = start;
|
|
520
510
|
else if (/[._:-]/u.test(char))
|
|
521
|
-
breakIndex =
|
|
522
|
-
index = nextIndex;
|
|
511
|
+
breakIndex = end;
|
|
523
512
|
}
|
|
524
513
|
return breakIndex > 0 ? breakIndex : fallbackIndex;
|
|
525
514
|
}
|
package/dist/terminal-width.d.ts
CHANGED
|
@@ -3,6 +3,20 @@ export declare function stringDisplayWidth(text: string): number;
|
|
|
3
3
|
export declare function sliceByDisplayWidth(text: string, width: number): string;
|
|
4
4
|
export declare function displayIndexForColumn(text: string, column: number): number;
|
|
5
5
|
export declare function sliceByDisplayColumns(text: string, startColumn: number, endColumn: number): string;
|
|
6
|
+
export type DisplayGrapheme = {
|
|
7
|
+
text: string;
|
|
8
|
+
width: number;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Grapheme clusters of `text` with their display width and absolute string
|
|
14
|
+
* indices. Iterating graphemes (instead of code points) is required for correct
|
|
15
|
+
* width accounting: multi-codepoint emoji such as `⚠️` (U+26A0 U+FE0F), keycaps,
|
|
16
|
+
* skin-tone modifiers and regional-indicator flags are one width-2 cluster even
|
|
17
|
+
* though several of their code points have zero width.
|
|
18
|
+
*/
|
|
19
|
+
export declare function displayGraphemes(text: string): DisplayGrapheme[];
|
|
6
20
|
export declare function padOrTrimDisplay(text: string, width: number): string;
|
|
7
21
|
export declare function wrapDisplayLine(text: string, width: number): string[];
|
|
8
22
|
export declare function wrapDisplayLineByWords(text: string, width: number): string[];
|
package/dist/terminal-width.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const TAB_WIDTH = 4;
|
|
2
2
|
const ANSI_RESET = "\x1b[0m";
|
|
3
3
|
const EMOJI_PRESENTATION_REGEX = /\p{Emoji_Presentation}/u;
|
|
4
|
-
const
|
|
4
|
+
const REGIONAL_INDICATOR_REGEX = /[\u{1F1E6}-\u{1F1FF}]/u;
|
|
5
5
|
const GRAPHEME_SEGMENTER = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) : undefined;
|
|
6
6
|
export function expandTabs(text, tabWidth = TAB_WIDTH) {
|
|
7
7
|
if (!text.includes("\t"))
|
|
@@ -86,6 +86,20 @@ export function sliceByDisplayColumns(text, startColumn, endColumn) {
|
|
|
86
86
|
const endIndex = Math.max(startIndex, displayIndexForColumn(text, endColumn));
|
|
87
87
|
return text.slice(startIndex, endIndex);
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Grapheme clusters of `text` with their display width and absolute string
|
|
91
|
+
* indices. Iterating graphemes (instead of code points) is required for correct
|
|
92
|
+
* width accounting: multi-codepoint emoji such as `⚠️` (U+26A0 U+FE0F), keycaps,
|
|
93
|
+
* skin-tone modifiers and regional-indicator flags are one width-2 cluster even
|
|
94
|
+
* though several of their code points have zero width.
|
|
95
|
+
*/
|
|
96
|
+
export function displayGraphemes(text) {
|
|
97
|
+
const graphemes = [];
|
|
98
|
+
for (const cluster of indexedDisplayClusters(text)) {
|
|
99
|
+
graphemes.push({ text: cluster.text, width: cluster.width, start: cluster.start, end: cluster.end });
|
|
100
|
+
}
|
|
101
|
+
return graphemes;
|
|
102
|
+
}
|
|
89
103
|
export function padOrTrimDisplay(text, width) {
|
|
90
104
|
const safeWidth = Math.max(0, width);
|
|
91
105
|
if (isPrintableAscii(text)) {
|
|
@@ -280,7 +294,22 @@ function graphemeDisplayWidth(text) {
|
|
|
280
294
|
return width;
|
|
281
295
|
}
|
|
282
296
|
function isEmojiGrapheme(text) {
|
|
283
|
-
|
|
297
|
+
// Default-presentation emoji (⛔ ✅ 🚀 ❌), supplementary pictographs, and
|
|
298
|
+
// regional-indicator flags render two cells wide in conforming terminals
|
|
299
|
+
// including iTerm2 and Zed, so they are measured at width 2.
|
|
300
|
+
if (EMOJI_PRESENTATION_REGEX.test(text))
|
|
301
|
+
return true;
|
|
302
|
+
// Keycap sequences (base + U+FE0F + U+20E3, e.g. 1️⃣) and regional-indicator
|
|
303
|
+
// pairs (🇷🇺) also occupy two cells.
|
|
304
|
+
if (text.includes("\u20e3"))
|
|
305
|
+
return true;
|
|
306
|
+
if (REGIONAL_INDICATOR_REGEX.test(text) && /[\u{1F1E6}-\u{1F1FF}]{2}/u.test(text))
|
|
307
|
+
return true;
|
|
308
|
+
// Symbols promoted to an emoji glyph only by a variation selector (⚠️ ✔️ ©️
|
|
309
|
+
// ☀️) keep their base width of 1. Their base code point is East-Asian-Width
|
|
310
|
+
// Ambiguous, and iTerm2/Zed/wcwidth render them one cell wide; counting them
|
|
311
|
+
// as 2 would misalign table columns and shorten rendered rows.
|
|
312
|
+
return false;
|
|
284
313
|
}
|
|
285
314
|
function codePointLength(codePoint) {
|
|
286
315
|
return codePoint > 0xffff ? 2 : 1;
|
package/dist/theme.js
CHANGED
|
@@ -9,7 +9,7 @@ export const THEMES = {
|
|
|
9
9
|
muted: "#7d8590",
|
|
10
10
|
headerForeground: "#c9d1d9",
|
|
11
11
|
headerBackground: "#161b22",
|
|
12
|
-
statusForeground: "#
|
|
12
|
+
statusForeground: "#9ba5af",
|
|
13
13
|
statusBackground: "#0f1520",
|
|
14
14
|
inputForeground: "#f0f6fc",
|
|
15
15
|
inputBackground: "#090d13",
|
|
@@ -64,7 +64,7 @@ export const THEMES = {
|
|
|
64
64
|
muted: "#64748b",
|
|
65
65
|
headerForeground: "#0f172a",
|
|
66
66
|
headerBackground: "#e2e8f0",
|
|
67
|
-
statusForeground: "#
|
|
67
|
+
statusForeground: "#566578",
|
|
68
68
|
statusBackground: "#edf0f4",
|
|
69
69
|
inputForeground: "#0f172a",
|
|
70
70
|
inputBackground: "#f8fafc",
|