pi-ui-extend 0.1.21 → 0.1.25
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 +1 -10
- package/bin/pix.mjs +11 -154
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +34 -9
- package/dist/app/cli/startup-info.d.ts +0 -1
- package/dist/app/cli/startup-info.js +0 -3
- package/dist/app/commands/command-session-actions.js +3 -0
- package/dist/app/input/autocomplete-controller.js +0 -1
- package/dist/app/popup/popup-menu-controller.js +7 -1
- package/dist/app/rendering/conversation-entry-renderer.js +29 -40
- package/dist/app/rendering/render-text.d.ts +6 -0
- package/dist/app/rendering/render-text.js +9 -0
- package/dist/app/rendering/tab-line-renderer.js +1 -5
- package/dist/app/rendering/tool-block-renderer.js +7 -1
- package/dist/app/screen/mouse-controller.js +14 -6
- package/dist/app/session/session-event-controller.js +5 -4
- package/dist/app/session/session-lifecycle-controller.js +0 -4
- package/dist/app/session/tabs-controller.d.ts +5 -1
- package/dist/app/session/tabs-controller.js +111 -23
- package/dist/app/types.d.ts +5 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +3 -0
- package/dist/app/workspace/workspace-actions-controller.js +71 -16
- package/dist/app/workspace/workspace-undo.js +41 -6
- package/dist/markdown-format.d.ts +4 -0
- package/dist/markdown-format.js +6 -1
- package/dist/schemas/pi-tools-suite-schema.d.ts +0 -1
- package/dist/schemas/pi-tools-suite-schema.js +0 -1
- package/dist/theme.js +18 -18
- package/extensions/session-title/config.ts +0 -5
- package/extensions/session-title/index.ts +0 -1
- package/external/pi-tools-suite/README.md +1 -1
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -1
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +0 -5
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +0 -1
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +0 -1
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +1 -1
- package/external/pi-tools-suite/src/telegram-mirror/README.md +81 -46
- package/external/pi-tools-suite/src/telegram-mirror/bot.ts +81 -10
- package/external/pi-tools-suite/src/telegram-mirror/events.ts +6 -38
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +246 -40
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +20 -0
- package/external/pi-tools-suite/src/telegram-mirror/multiplexer.ts +247 -17
- package/external/pi-tools-suite/src/telegram-mirror/renderer.ts +75 -78
- package/external/pi-tools-suite/src/todo/index.ts +7 -6
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +1 -1
- package/external/pi-tools-suite/src/web-search/index.ts +139 -2
- package/package.json +7 -7
- package/schemas/pi-tools-suite.json +0 -6
|
@@ -79,7 +79,7 @@ export declare class AppTabsController {
|
|
|
79
79
|
private runtimeForCommand;
|
|
80
80
|
private idleRuntime;
|
|
81
81
|
private activeTab;
|
|
82
|
-
private
|
|
82
|
+
private clearStartupTabPlaceholders;
|
|
83
83
|
private storeActiveRuntime;
|
|
84
84
|
private setRuntimeForTab;
|
|
85
85
|
private deleteRuntimeForTab;
|
|
@@ -109,6 +109,7 @@ export declare class AppTabsController {
|
|
|
109
109
|
private sessionPath;
|
|
110
110
|
private sessionTitle;
|
|
111
111
|
private sessionTitleFromParts;
|
|
112
|
+
private updatedSessionTitle;
|
|
112
113
|
private sessionActivity;
|
|
113
114
|
private tabActivity;
|
|
114
115
|
private clearTabAttention;
|
|
@@ -116,6 +117,9 @@ export declare class AppTabsController {
|
|
|
116
117
|
private stopAttentionBlinkIfIdle;
|
|
117
118
|
private restoredTabs;
|
|
118
119
|
private defaultSessionTitleFromPath;
|
|
120
|
+
private loadSessionTitles;
|
|
121
|
+
private scheduleRestoredTabTitleRefresh;
|
|
122
|
+
private refreshRestoredTabTitles;
|
|
119
123
|
private loadTabs;
|
|
120
124
|
private parsePersistedInputState;
|
|
121
125
|
private parsePersistedSubmittedUserMessages;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, open as openFile, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
5
5
|
import { getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { isRecord } from "../guards.js";
|
|
7
7
|
import { createId } from "../id.js";
|
|
8
|
-
import { createStartupInfoMessage, isEmptyStartupSession } from "../cli/startup-info.js";
|
|
9
8
|
import { tabPanelRows } from "../rendering/tab-line-renderer.js";
|
|
10
9
|
const TAB_STATE_VERSION = 3;
|
|
11
10
|
const MAX_RESTORED_TABS = 8;
|
|
12
11
|
const BACKGROUND_PREWARM_TAB_LIMIT = 2;
|
|
13
12
|
const TAB_ATTENTION_BLINK_KEY = "tab-attention";
|
|
14
13
|
const LOADING_TAB_TITLE_PATTERN = /^loading(?:…|\.\.\.)?$/iu;
|
|
14
|
+
const DEFAULT_SESSION_TITLE_PATTERN = /^session [0-9a-f]{8}$/iu;
|
|
15
|
+
const SESSION_TITLE_SCAN_MAX_BYTES = 2 * 1024 * 1024;
|
|
15
16
|
export class AppTabsController {
|
|
16
17
|
host;
|
|
17
18
|
tabItems = [];
|
|
@@ -140,7 +141,7 @@ export class AppTabsController {
|
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
142
143
|
if (!active) {
|
|
143
|
-
const tab = this.tabFromSession(session, { titlePlaceholder:
|
|
144
|
+
const tab = this.tabFromSession(session, { titlePlaceholder: "loading" });
|
|
144
145
|
this.tabItems.push(tab);
|
|
145
146
|
this.activeTabId = tab.id;
|
|
146
147
|
this.clearTabAttention(tab);
|
|
@@ -164,18 +165,18 @@ export class AppTabsController {
|
|
|
164
165
|
return;
|
|
165
166
|
this.syncActiveTabFromRuntime({ save: false });
|
|
166
167
|
if (this.host.options.noSession) {
|
|
167
|
-
this.
|
|
168
|
+
this.clearStartupTabPlaceholders();
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
const saved = await this.loadTabs();
|
|
171
172
|
if (!saved || saved.tabs.length === 0) {
|
|
172
|
-
this.
|
|
173
|
+
this.clearStartupTabPlaceholders();
|
|
173
174
|
await this.saveTabs();
|
|
174
175
|
return;
|
|
175
176
|
}
|
|
176
177
|
const restoredTabs = this.restoredTabs(saved);
|
|
177
178
|
if (restoredTabs.length === 0) {
|
|
178
|
-
this.
|
|
179
|
+
this.clearStartupTabPlaceholders();
|
|
179
180
|
await this.saveTabs();
|
|
180
181
|
this.scheduleProjectSessionRetention();
|
|
181
182
|
return;
|
|
@@ -191,11 +192,13 @@ export class AppTabsController {
|
|
|
191
192
|
this.replaceTabs(restoredTabs, desiredPath);
|
|
192
193
|
this.restorePersistedInputStates(saved);
|
|
193
194
|
this.restorePersistedDeferredUserMessages(saved);
|
|
195
|
+
const restoredSessionPaths = saved.tabs.map((tab) => tab.path);
|
|
194
196
|
if (explicitSessionPath && currentPath)
|
|
195
197
|
this.ensureCurrentSessionTab(runtime.session);
|
|
196
198
|
if (!desiredPath) {
|
|
197
|
-
this.
|
|
199
|
+
this.clearStartupTabPlaceholders();
|
|
198
200
|
await this.saveTabs();
|
|
201
|
+
this.scheduleRestoredTabTitleRefresh(restoredSessionPaths);
|
|
199
202
|
this.scheduleProjectSessionRetention();
|
|
200
203
|
this.scheduleTabPrewarm();
|
|
201
204
|
return;
|
|
@@ -212,20 +215,22 @@ export class AppTabsController {
|
|
|
212
215
|
this.host.showToast("Could not restore the previous active tab", "warning");
|
|
213
216
|
this.replaceTabs([this.tabFromSession(runtime.session), ...restoredTabs], currentPath);
|
|
214
217
|
this.storeActiveRuntime(runtime);
|
|
215
|
-
this.
|
|
218
|
+
this.clearStartupTabPlaceholders();
|
|
216
219
|
await this.saveTabs();
|
|
220
|
+
this.scheduleRestoredTabTitleRefresh(restoredSessionPaths);
|
|
217
221
|
this.scheduleProjectSessionRetention();
|
|
218
222
|
return;
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
225
|
this.syncActiveTabFromRuntime({ save: false });
|
|
222
|
-
this.
|
|
226
|
+
this.clearStartupTabPlaceholders();
|
|
223
227
|
if (this.activeTabId)
|
|
224
228
|
this.restoreInputState(this.activeTabId);
|
|
225
229
|
await this.saveTabs();
|
|
226
230
|
this.scheduleProjectSessionRetention();
|
|
227
231
|
this.scheduleTabPrewarm();
|
|
228
232
|
await this.loadActiveSessionHistory(restoredRuntime);
|
|
233
|
+
this.scheduleRestoredTabTitleRefresh(restoredSessionPaths);
|
|
229
234
|
}
|
|
230
235
|
async openNewTab() {
|
|
231
236
|
if (this.pendingActiveTabId) {
|
|
@@ -307,12 +312,7 @@ export class AppTabsController {
|
|
|
307
312
|
this.scheduleProjectSessionRetention();
|
|
308
313
|
this.host.resetSessionView();
|
|
309
314
|
this.restoreDeferredUserMessages(targetTab.id);
|
|
310
|
-
|
|
311
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(newRuntime) });
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: `Opened a new tab. cwd=${newRuntime.cwd}` });
|
|
315
|
-
}
|
|
315
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: `Opened a new tab. cwd=${newRuntime.cwd}` });
|
|
316
316
|
if (newRuntime.modelFallbackMessage)
|
|
317
317
|
this.host.addEntry({ id: createId("system"), kind: "system", text: newRuntime.modelFallbackMessage });
|
|
318
318
|
for (const diag of newRuntime.diagnostics ?? []) {
|
|
@@ -796,10 +796,9 @@ export class AppTabsController {
|
|
|
796
796
|
activeTab() {
|
|
797
797
|
return this.activeTabId ? this.tabItems.find((tab) => tab.id === this.activeTabId) : undefined;
|
|
798
798
|
}
|
|
799
|
-
|
|
799
|
+
clearStartupTabPlaceholders() {
|
|
800
800
|
for (const tab of this.tabItems) {
|
|
801
|
-
|
|
802
|
-
tab.titlePlaceholder = "new";
|
|
801
|
+
delete tab.titlePlaceholder;
|
|
803
802
|
}
|
|
804
803
|
}
|
|
805
804
|
storeActiveRuntime(runtime = this.host.runtime()) {
|
|
@@ -1071,9 +1070,10 @@ export class AppTabsController {
|
|
|
1071
1070
|
};
|
|
1072
1071
|
}
|
|
1073
1072
|
updateTabFromSession(tab, session) {
|
|
1074
|
-
tab.
|
|
1075
|
-
tab.activity = this.sessionActivity(session);
|
|
1073
|
+
const previousSessionPath = tab.sessionPath ? resolve(tab.sessionPath) : undefined;
|
|
1076
1074
|
const sessionPath = this.sessionPath(session);
|
|
1075
|
+
tab.title = this.updatedSessionTitle(tab.title, this.sessionTitle(session), previousSessionPath, sessionPath, tab.titlePlaceholder !== undefined);
|
|
1076
|
+
tab.activity = this.sessionActivity(session);
|
|
1077
1077
|
if (sessionPath)
|
|
1078
1078
|
tab.sessionPath = sessionPath;
|
|
1079
1079
|
}
|
|
@@ -1087,6 +1087,15 @@ export class AppTabsController {
|
|
|
1087
1087
|
const name = sessionName?.trim();
|
|
1088
1088
|
return name && !LOADING_TAB_TITLE_PATTERN.test(name) ? name : `session ${sessionId.slice(0, 8)}`;
|
|
1089
1089
|
}
|
|
1090
|
+
updatedSessionTitle(currentTitle, nextTitle, currentSessionPath, nextSessionPath, hasTitlePlaceholder) {
|
|
1091
|
+
if (!isDefaultSessionTitle(nextTitle))
|
|
1092
|
+
return nextTitle;
|
|
1093
|
+
if (hasTitlePlaceholder)
|
|
1094
|
+
return nextTitle;
|
|
1095
|
+
if (currentSessionPath !== undefined && nextSessionPath !== undefined && currentSessionPath !== nextSessionPath)
|
|
1096
|
+
return nextTitle;
|
|
1097
|
+
return validSessionTitle(currentTitle) && !isDefaultSessionTitle(currentTitle) ? currentTitle : nextTitle;
|
|
1098
|
+
}
|
|
1090
1099
|
sessionActivity(session) {
|
|
1091
1100
|
return session?.isStreaming || session?.isCompacting ? "running" : "idle";
|
|
1092
1101
|
}
|
|
@@ -1117,7 +1126,7 @@ export class AppTabsController {
|
|
|
1117
1126
|
initialVisible: true,
|
|
1118
1127
|
});
|
|
1119
1128
|
}
|
|
1120
|
-
restoredTabs(saved) {
|
|
1129
|
+
restoredTabs(saved, sessionTitles = new Map()) {
|
|
1121
1130
|
const tabs = [];
|
|
1122
1131
|
const seen = new Set();
|
|
1123
1132
|
for (const tab of saved.tabs) {
|
|
@@ -1129,11 +1138,11 @@ export class AppTabsController {
|
|
|
1129
1138
|
seen.add(sessionPath);
|
|
1130
1139
|
const savedTitle = tab.title?.trim();
|
|
1131
1140
|
const restoredLoadingTitle = savedTitle !== undefined && LOADING_TAB_TITLE_PATTERN.test(savedTitle);
|
|
1132
|
-
const
|
|
1141
|
+
const sessionTitle = validSessionTitle(sessionTitles.get(sessionPath));
|
|
1142
|
+
const title = sessionTitle || (restoredLoadingTitle ? this.defaultSessionTitleFromPath(sessionPath) : savedTitle);
|
|
1133
1143
|
tabs.push({
|
|
1134
1144
|
id: createId("tab"),
|
|
1135
1145
|
title: title || "session",
|
|
1136
|
-
...(restoredLoadingTitle ? { titlePlaceholder: "new" } : {}),
|
|
1137
1146
|
status: "waiting",
|
|
1138
1147
|
sessionPath,
|
|
1139
1148
|
});
|
|
@@ -1148,6 +1157,40 @@ export class AppTabsController {
|
|
|
1148
1157
|
?? createHash("sha256").update(sessionPath).digest("hex").slice(0, 8);
|
|
1149
1158
|
return `session ${sessionId}`;
|
|
1150
1159
|
}
|
|
1160
|
+
async loadSessionTitles(sessionPaths) {
|
|
1161
|
+
const uniquePaths = [...new Set(sessionPaths.map((sessionPath) => resolve(sessionPath)))].slice(0, MAX_RESTORED_TABS);
|
|
1162
|
+
const entries = await Promise.all(uniquePaths.map(async (sessionPath) => {
|
|
1163
|
+
const title = await readLatestSessionTitle(sessionPath);
|
|
1164
|
+
return title ? [sessionPath, title] : undefined;
|
|
1165
|
+
}));
|
|
1166
|
+
return new Map(entries.filter((entry) => entry !== undefined));
|
|
1167
|
+
}
|
|
1168
|
+
scheduleRestoredTabTitleRefresh(sessionPaths) {
|
|
1169
|
+
if (sessionPaths.length === 0)
|
|
1170
|
+
return;
|
|
1171
|
+
setTimeout(() => {
|
|
1172
|
+
void this.refreshRestoredTabTitles(sessionPaths);
|
|
1173
|
+
}, 0).unref?.();
|
|
1174
|
+
}
|
|
1175
|
+
async refreshRestoredTabTitles(sessionPaths) {
|
|
1176
|
+
const titles = await this.loadSessionTitles(sessionPaths);
|
|
1177
|
+
if (titles.size === 0)
|
|
1178
|
+
return;
|
|
1179
|
+
let changed = false;
|
|
1180
|
+
for (const tab of this.tabItems) {
|
|
1181
|
+
const sessionPath = tab.sessionPath ? resolve(tab.sessionPath) : undefined;
|
|
1182
|
+
const title = sessionPath ? titles.get(sessionPath) : undefined;
|
|
1183
|
+
if (!title || tab.title === title)
|
|
1184
|
+
continue;
|
|
1185
|
+
tab.title = title;
|
|
1186
|
+
delete tab.titlePlaceholder;
|
|
1187
|
+
changed = true;
|
|
1188
|
+
}
|
|
1189
|
+
if (!changed)
|
|
1190
|
+
return;
|
|
1191
|
+
void this.saveTabs();
|
|
1192
|
+
this.host.render();
|
|
1193
|
+
}
|
|
1151
1194
|
async loadTabs() {
|
|
1152
1195
|
try {
|
|
1153
1196
|
const raw = await readFile(this.filePath(), "utf8");
|
|
@@ -1408,6 +1451,51 @@ function parsePersistedImage(value) {
|
|
|
1408
1451
|
? { type: "image", data: value.data, mimeType: value.mimeType }
|
|
1409
1452
|
: undefined;
|
|
1410
1453
|
}
|
|
1454
|
+
async function readLatestSessionTitle(sessionPath) {
|
|
1455
|
+
let file;
|
|
1456
|
+
try {
|
|
1457
|
+
file = await openFile(sessionPath, "r");
|
|
1458
|
+
const { size } = await file.stat();
|
|
1459
|
+
if (size <= 0)
|
|
1460
|
+
return undefined;
|
|
1461
|
+
const byteCount = Math.min(size, SESSION_TITLE_SCAN_MAX_BYTES);
|
|
1462
|
+
const buffer = Buffer.alloc(byteCount);
|
|
1463
|
+
await file.read(buffer, 0, byteCount, size - byteCount);
|
|
1464
|
+
const text = buffer.toString("utf8");
|
|
1465
|
+
const lines = text.split("\n");
|
|
1466
|
+
if (size > byteCount)
|
|
1467
|
+
lines.shift();
|
|
1468
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1469
|
+
const line = lines[index]?.trim();
|
|
1470
|
+
if (!line)
|
|
1471
|
+
continue;
|
|
1472
|
+
let parsed;
|
|
1473
|
+
try {
|
|
1474
|
+
parsed = JSON.parse(line);
|
|
1475
|
+
}
|
|
1476
|
+
catch {
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
if (!isRecord(parsed) || parsed.type !== "session_info" || typeof parsed.name !== "string")
|
|
1480
|
+
continue;
|
|
1481
|
+
return validSessionTitle(parsed.name);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
catch {
|
|
1485
|
+
return undefined;
|
|
1486
|
+
}
|
|
1487
|
+
finally {
|
|
1488
|
+
await file?.close();
|
|
1489
|
+
}
|
|
1490
|
+
return undefined;
|
|
1491
|
+
}
|
|
1492
|
+
function validSessionTitle(value) {
|
|
1493
|
+
const title = value?.trim();
|
|
1494
|
+
return title && !LOADING_TAB_TITLE_PATTERN.test(title) ? title : undefined;
|
|
1495
|
+
}
|
|
1496
|
+
function isDefaultSessionTitle(value) {
|
|
1497
|
+
return DEFAULT_SESSION_TITLE_PATTERN.test(value.trim());
|
|
1498
|
+
}
|
|
1411
1499
|
function clonePersistedAttachment(attachment) {
|
|
1412
1500
|
if (attachment.kind === "image")
|
|
1413
1501
|
return { kind: "image", tag: attachment.tag, image: { ...attachment.image } };
|
package/dist/app/types.d.ts
CHANGED
|
@@ -201,6 +201,8 @@ export type SubagentsWidgetState = {
|
|
|
201
201
|
};
|
|
202
202
|
export type RenderedLine = {
|
|
203
203
|
text: string;
|
|
204
|
+
copyText?: string;
|
|
205
|
+
continuesOnNextLine?: boolean;
|
|
204
206
|
variant?: "normal" | "muted" | "error" | "accent";
|
|
205
207
|
colorOverride?: string;
|
|
206
208
|
backgroundOverride?: string;
|
|
@@ -512,6 +514,9 @@ export type UserMessageMenuValue = "copy" | "fork" | "fork-new-tab" | "undo";
|
|
|
512
514
|
export type UserMessageJumpMenuValue = {
|
|
513
515
|
entryId?: string;
|
|
514
516
|
sessionEntryId?: string;
|
|
517
|
+
text?: string;
|
|
518
|
+
userIndex?: number;
|
|
519
|
+
userCount?: number;
|
|
515
520
|
};
|
|
516
521
|
export type QueueMessageMenuValue = "cancel" | "edit" | "send-now";
|
|
517
522
|
export type ResumeMenuValue = {
|
|
@@ -33,6 +33,9 @@ export declare class AppWorkspaceActionsController {
|
|
|
33
33
|
forkFromUserMessage(entryId: string): Promise<void>;
|
|
34
34
|
forkFromUserMessageInNewTab(entryId: string): Promise<void>;
|
|
35
35
|
undoChangesFromUserMessage(entryId: string): Promise<void>;
|
|
36
|
+
private workspaceMutationPlanFromSessionEntry;
|
|
37
|
+
private workspaceMutationPlanForSingleEntry;
|
|
38
|
+
private findUserEntryBySessionEntryId;
|
|
36
39
|
private resolveUserSessionEntryId;
|
|
37
40
|
private getIdleRuntimeForAction;
|
|
38
41
|
private workspaceMutationsForSessionEntry;
|
|
@@ -130,18 +130,7 @@ export class AppWorkspaceActionsController {
|
|
|
130
130
|
const sessionEntryId = this.resolveUserSessionEntryId(entry);
|
|
131
131
|
if (!sessionEntryId)
|
|
132
132
|
throw new Error("Session entry for this message is not available yet");
|
|
133
|
-
const
|
|
134
|
-
const mutations = entry.workspaceMutations ?? this.workspaceMutationsForSessionEntry(sessionEntryId);
|
|
135
|
-
if (!hasMutationLog) {
|
|
136
|
-
throw new Error("No workspace mutation log was captured for this message. Undo is available for messages sent after this build.");
|
|
137
|
-
}
|
|
138
|
-
if (mutations.length > 0) {
|
|
139
|
-
this.host.setStatus("reverting recorded commands");
|
|
140
|
-
this.host.render();
|
|
141
|
-
}
|
|
142
|
-
const reverted = mutations.length === 0 ? { ok: true, changedFiles: 0, revertedChanges: 0 } : await revertWorkspaceMutations(runtime.cwd, mutations);
|
|
143
|
-
if (!reverted.ok)
|
|
144
|
-
throw new Error(reverted.error);
|
|
133
|
+
const mutationPlan = this.workspaceMutationPlanFromSessionEntry(sessionEntryId);
|
|
145
134
|
this.host.setStatus("truncating session");
|
|
146
135
|
this.host.render();
|
|
147
136
|
const result = await runtime.session.navigateTree(sessionEntryId);
|
|
@@ -157,15 +146,69 @@ export class AppWorkspaceActionsController {
|
|
|
157
146
|
}
|
|
158
147
|
this.host.resetSessionView();
|
|
159
148
|
this.host.loadSessionHistory();
|
|
160
|
-
|
|
161
|
-
|
|
149
|
+
this.host.setInput(result.editorText ?? entry.text);
|
|
150
|
+
let revertSummary = "No recorded file mutations were found for the removed branch.";
|
|
151
|
+
let revertToastKind = "success";
|
|
152
|
+
if (mutationPlan.mutations.length > 0) {
|
|
153
|
+
this.host.setStatus("reverting recorded commands");
|
|
154
|
+
this.host.render();
|
|
155
|
+
const reverted = await revertWorkspaceMutations(runtime.cwd, mutationPlan.mutations);
|
|
156
|
+
if (reverted.ok) {
|
|
157
|
+
revertSummary = `Reverted ${reverted.revertedChanges} command${reverted.revertedChanges === 1 ? "" : "s"} across ${reverted.changedFiles} file${reverted.changedFiles === 1 ? "" : "s"}.`;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
revertSummary = `Workspace revert failed: ${reverted.error}`;
|
|
161
|
+
revertToastKind = "warning";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (mutationPlan.messagesWithoutLogs > 0) {
|
|
165
|
+
revertSummary = `No recorded file mutations were available for ${mutationPlan.messagesWithoutLogs} removed message${mutationPlan.messagesWithoutLogs === 1 ? "" : "s"}.`;
|
|
166
|
+
revertToastKind = "warning";
|
|
167
|
+
}
|
|
162
168
|
this.host.addEntry({
|
|
163
169
|
id: createId("system"),
|
|
164
170
|
kind: "system",
|
|
165
|
-
text: `Undid changes from entry ${sessionEntryId}.
|
|
171
|
+
text: `Undid changes from entry ${sessionEntryId}. ${revertSummary}`,
|
|
166
172
|
});
|
|
167
173
|
this.host.setSessionStatus(runtime.session);
|
|
168
|
-
this.host.showToast("Changes undone"
|
|
174
|
+
this.host.showToast(revertToastKind === "success" ? "Changes undone" : "Session rewound with revert warnings", revertToastKind);
|
|
175
|
+
}
|
|
176
|
+
workspaceMutationPlanFromSessionEntry(entryId) {
|
|
177
|
+
const runtime = this.host.runtime();
|
|
178
|
+
if (!runtime)
|
|
179
|
+
return { mutations: [], messagesWithoutLogs: 0 };
|
|
180
|
+
const branch = runtime.session.sessionManager.getBranch();
|
|
181
|
+
const startIndex = branch.findIndex((entry) => entry.id === entryId);
|
|
182
|
+
if (startIndex < 0) {
|
|
183
|
+
return this.workspaceMutationPlanForSingleEntry(entryId);
|
|
184
|
+
}
|
|
185
|
+
const mutations = [];
|
|
186
|
+
let messagesWithoutLogs = 0;
|
|
187
|
+
for (const branchEntry of branch.slice(startIndex)) {
|
|
188
|
+
if (branchEntry.type !== "message")
|
|
189
|
+
continue;
|
|
190
|
+
if (!isRecord(branchEntry.message) || branchEntry.message.role !== "user")
|
|
191
|
+
continue;
|
|
192
|
+
const visibleEntry = this.findUserEntryBySessionEntryId(branchEntry.id);
|
|
193
|
+
const hasMutationLog = visibleEntry?.workspaceMutations !== undefined || this.hasWorkspaceMutationsForSessionEntry(branchEntry.id);
|
|
194
|
+
const entryMutations = visibleEntry?.workspaceMutations ?? this.workspaceMutationsForSessionEntry(branchEntry.id);
|
|
195
|
+
if (!hasMutationLog) {
|
|
196
|
+
messagesWithoutLogs += 1;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
mutations.push(...entryMutations);
|
|
200
|
+
}
|
|
201
|
+
return { mutations, messagesWithoutLogs };
|
|
202
|
+
}
|
|
203
|
+
workspaceMutationPlanForSingleEntry(entryId) {
|
|
204
|
+
const visibleEntry = this.findUserEntryBySessionEntryId(entryId);
|
|
205
|
+
const hasMutationLog = visibleEntry?.workspaceMutations !== undefined || this.hasWorkspaceMutationsForSessionEntry(entryId);
|
|
206
|
+
if (!hasMutationLog)
|
|
207
|
+
return { mutations: [], messagesWithoutLogs: 1 };
|
|
208
|
+
return { mutations: visibleEntry?.workspaceMutations ?? this.workspaceMutationsForSessionEntry(entryId), messagesWithoutLogs: 0 };
|
|
209
|
+
}
|
|
210
|
+
findUserEntryBySessionEntryId(sessionEntryId) {
|
|
211
|
+
return this.host.entries.find((entry) => entry.kind === "user" && entry.sessionEntryId === sessionEntryId);
|
|
169
212
|
}
|
|
170
213
|
resolveUserSessionEntryId(entry) {
|
|
171
214
|
if (!entry.sessionEntryId)
|
|
@@ -204,6 +247,18 @@ export class AppWorkspaceActionsController {
|
|
|
204
247
|
const key = this.workspaceUndoIndexKey(entryId);
|
|
205
248
|
if (!key)
|
|
206
249
|
return;
|
|
250
|
+
if (mutations.length === 0) {
|
|
251
|
+
if (!Object.prototype.hasOwnProperty.call(this.workspaceUndoIndex.entries, key))
|
|
252
|
+
return;
|
|
253
|
+
delete this.workspaceUndoIndex.entries[key];
|
|
254
|
+
try {
|
|
255
|
+
saveWorkspaceUndoIndex(getAgentDir(), this.workspaceUndoIndex);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Undo persistence is best-effort; in-memory undo still works for this run.
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
207
262
|
const hasExisting = Object.prototype.hasOwnProperty.call(this.workspaceUndoIndex.entries, key);
|
|
208
263
|
if (hasExisting && sameWorkspaceMutations(this.workspaceUndoIndex.entries[key] ?? [], mutations))
|
|
209
264
|
return;
|
|
@@ -25,7 +25,7 @@ export function prepareWorkspaceMutation(cwd, toolName, args) {
|
|
|
25
25
|
if (name !== "write")
|
|
26
26
|
return undefined;
|
|
27
27
|
const record = plainRecord(args);
|
|
28
|
-
const rawPath =
|
|
28
|
+
const rawPath = toolPathValue(record);
|
|
29
29
|
const afterContent = stringValue(record?.content);
|
|
30
30
|
if (!rawPath || afterContent === undefined)
|
|
31
31
|
return undefined;
|
|
@@ -45,8 +45,9 @@ export function workspaceMutationFromToolExecution(input) {
|
|
|
45
45
|
const name = normalizedToolName(input.toolName);
|
|
46
46
|
if (name === "write" && input.preparation?.type === "write") {
|
|
47
47
|
const record = plainRecord(input.args);
|
|
48
|
+
const rawPath = toolPathValue(record);
|
|
48
49
|
const afterContent = stringValue(record?.content);
|
|
49
|
-
if (afterContent === undefined || input.preparation.beforeContent === afterContent)
|
|
50
|
+
if (!rawPath || afterContent === undefined || input.preparation.beforeContent === afterContent)
|
|
50
51
|
return undefined;
|
|
51
52
|
return {
|
|
52
53
|
type: "write",
|
|
@@ -56,7 +57,8 @@ export function workspaceMutationFromToolExecution(input) {
|
|
|
56
57
|
toolName: input.toolName,
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
|
-
const
|
|
60
|
+
const rawPatch = patchFromDetails(input.details) ?? patchFromArgs(input.args);
|
|
61
|
+
const patch = rawPatch && normalizePatchForWorkspace(input.cwd, rawPatch);
|
|
60
62
|
if (!patch || !looksLikeUnifiedPatch(patch))
|
|
61
63
|
return undefined;
|
|
62
64
|
if (name === "edit" || name === "apply_patch" || name === "ast_apply") {
|
|
@@ -94,14 +96,17 @@ async function applyMutation(cwd, mutation, direction) {
|
|
|
94
96
|
return applyWriteMutation(cwd, mutation, direction);
|
|
95
97
|
}
|
|
96
98
|
async function applyPatchMutation(cwd, mutation, direction) {
|
|
99
|
+
const patch = normalizePatchForWorkspace(cwd, mutation.patch);
|
|
100
|
+
if (!patch)
|
|
101
|
+
return { ok: false, error: "Refusing to apply patch with paths outside workspace." };
|
|
97
102
|
const args = ["apply", ...(direction === "undo" ? ["--reverse"] : []), "--whitespace=nowarn"];
|
|
98
|
-
const check = await runGitApply(cwd, [...args, "--check"],
|
|
103
|
+
const check = await runGitApply(cwd, [...args, "--check"], patch);
|
|
99
104
|
if (check.status !== 0)
|
|
100
105
|
return { ok: false, error: commandError(`git ${args.join(" ")} --check`, check) };
|
|
101
|
-
const apply = await runGitApply(cwd, args,
|
|
106
|
+
const apply = await runGitApply(cwd, args, patch);
|
|
102
107
|
if (apply.status !== 0)
|
|
103
108
|
return { ok: false, error: commandError(`git ${args.join(" ")}`, apply) };
|
|
104
|
-
return { ok: true, changedFiles: filesFromPatch(
|
|
109
|
+
return { ok: true, changedFiles: filesFromPatch(patch) };
|
|
105
110
|
}
|
|
106
111
|
async function applyWriteMutation(cwd, mutation, direction) {
|
|
107
112
|
const safePath = safeRelativePath(cwd, mutation.path);
|
|
@@ -163,6 +168,9 @@ function plainRecord(value) {
|
|
|
163
168
|
function stringValue(value) {
|
|
164
169
|
return typeof value === "string" ? value : undefined;
|
|
165
170
|
}
|
|
171
|
+
function toolPathValue(record) {
|
|
172
|
+
return stringValue(record?.path) ?? stringValue(record?.file_path);
|
|
173
|
+
}
|
|
166
174
|
function patchFromDetails(details) {
|
|
167
175
|
const record = plainRecord(details);
|
|
168
176
|
return stringValue(record?.patch) ?? stringValue(record?.diff);
|
|
@@ -174,6 +182,33 @@ function patchFromArgs(args) {
|
|
|
174
182
|
function looksLikeUnifiedPatch(text) {
|
|
175
183
|
return /^---\s+/m.test(text) && /^\+\+\+\s+/m.test(text) && /^@@\s/m.test(text);
|
|
176
184
|
}
|
|
185
|
+
function normalizePatchForWorkspace(cwd, patch) {
|
|
186
|
+
const normalizedLines = [];
|
|
187
|
+
for (const line of patch.split("\n")) {
|
|
188
|
+
const match = /^(---|\+\+\+)\s+(\S+)(.*)$/.exec(line);
|
|
189
|
+
if (!match) {
|
|
190
|
+
normalizedLines.push(line);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const marker = match[1];
|
|
194
|
+
const rawPath = match[2];
|
|
195
|
+
const suffix = match[3] ?? "";
|
|
196
|
+
if (rawPath === "/dev/null") {
|
|
197
|
+
normalizedLines.push(line);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const relativePath = patchWorkspacePath(cwd, rawPath);
|
|
201
|
+
if (!relativePath)
|
|
202
|
+
return undefined;
|
|
203
|
+
const prefix = marker === "---" ? "a" : "b";
|
|
204
|
+
normalizedLines.push(`${marker} ${prefix}/${relativePath}${suffix}`);
|
|
205
|
+
}
|
|
206
|
+
return normalizedLines.join("\n");
|
|
207
|
+
}
|
|
208
|
+
function patchWorkspacePath(cwd, inputPath) {
|
|
209
|
+
const path = !isAbsolute(inputPath) && /^[ab]\//u.test(inputPath) ? inputPath.slice(2) : inputPath;
|
|
210
|
+
return safeRelativePath(cwd, path);
|
|
211
|
+
}
|
|
177
212
|
function filesFromPatch(patch) {
|
|
178
213
|
const files = new Set();
|
|
179
214
|
for (const line of patch.split("\n")) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type SyntaxLineHighlight, type ToolBodySyntaxHighlights } from "./syntax-highlight.js";
|
|
2
2
|
export type RenderedMarkdownLine = {
|
|
3
3
|
text: string;
|
|
4
|
+
copyText?: string;
|
|
5
|
+
continuesOnNextLine?: boolean;
|
|
4
6
|
segments: readonly {
|
|
5
7
|
start: number;
|
|
6
8
|
end: number;
|
|
@@ -10,6 +12,8 @@ export type RenderedMarkdownLine = {
|
|
|
10
12
|
};
|
|
11
13
|
export type RenderedMarkdownTextLine = {
|
|
12
14
|
text: string;
|
|
15
|
+
copyText?: string;
|
|
16
|
+
continuesOnNextLine?: boolean;
|
|
13
17
|
segments?: readonly {
|
|
14
18
|
start: number;
|
|
15
19
|
end: number;
|
package/dist/markdown-format.js
CHANGED
|
@@ -86,6 +86,8 @@ export function renderMarkdownTextLines(text, width, start = 0) {
|
|
|
86
86
|
for (const wrapped of wrapRenderedMarkdownLine(markdownLine ?? { text: rawLine, segments: [] }, width)) {
|
|
87
87
|
lines.push({
|
|
88
88
|
text: wrapped.text,
|
|
89
|
+
...(wrapped.copyText === undefined ? {} : { copyText: wrapped.copyText }),
|
|
90
|
+
...(wrapped.continuesOnNextLine ? { continuesOnNextLine: true } : {}),
|
|
89
91
|
...(wrapped.segments.length > 0 ? { segments: wrapped.segments } : {}),
|
|
90
92
|
...(syntaxHighlight ? { syntaxHighlight } : {}),
|
|
91
93
|
...(isHeadingLine ? { heading: true } : {}),
|
|
@@ -124,8 +126,11 @@ function wrapRenderedMarkdownLine(line, width) {
|
|
|
124
126
|
const safeWidth = Math.max(1, width);
|
|
125
127
|
if (stringDisplayWidth(line.text) <= safeWidth)
|
|
126
128
|
return [line];
|
|
127
|
-
|
|
129
|
+
const ranges = wrapDisplayLineByWordsWithRanges(line.text, safeWidth);
|
|
130
|
+
return ranges.map((range, index) => ({
|
|
128
131
|
text: range.text,
|
|
132
|
+
copyText: line.text.slice(range.start, ranges[index + 1]?.start ?? range.end),
|
|
133
|
+
...(index < ranges.length - 1 ? { continuesOnNextLine: true } : {}),
|
|
129
134
|
segments: line.segments.flatMap((segment) => shiftSegmentToRange(segment, range.start, range.end)),
|
|
130
135
|
}));
|
|
131
136
|
}
|
|
@@ -83,7 +83,6 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
|
|
|
83
83
|
maxTaskChars: Type.TOptional<Type.TNumber>;
|
|
84
84
|
maxTokens: Type.TOptional<Type.TNumber>;
|
|
85
85
|
maxRetries: Type.TOptional<Type.TNumber>;
|
|
86
|
-
temperature: Type.TOptional<Type.TNumber>;
|
|
87
86
|
timeoutMs: Type.TOptional<Type.TNumber>;
|
|
88
87
|
debug: Type.TOptional<Type.TBoolean>;
|
|
89
88
|
}>>;
|
|
@@ -115,7 +115,6 @@ const SubagentRoutingConfig = Type.Object({
|
|
|
115
115
|
maxTaskChars: Type.Optional(Type.Number({ description: "Max task/scope characters sent to router.", minimum: 100 })),
|
|
116
116
|
maxTokens: Type.Optional(Type.Number({ description: "Max router response tokens.", minimum: 8 })),
|
|
117
117
|
maxRetries: Type.Optional(Type.Number({ description: "Router request retries.", minimum: 0 })),
|
|
118
|
-
temperature: Type.Optional(Type.Number({ description: "Router sampling temperature.", minimum: 0, maximum: 2 })),
|
|
119
118
|
timeoutMs: Type.Optional(Type.Number({ description: "Router request timeout in ms.", minimum: 1000 })),
|
|
120
119
|
debug: Type.Optional(Type.Boolean({ description: "Show routing debug warnings." })),
|
|
121
120
|
}, { description: "LLM-based role routing configuration." });
|
package/dist/theme.js
CHANGED
|
@@ -36,15 +36,15 @@ export const THEMES = {
|
|
|
36
36
|
warning: "#d49a4a",
|
|
37
37
|
heading: "#d4b35e",
|
|
38
38
|
info: "#7fb3c8",
|
|
39
|
-
toolMutation: "#
|
|
40
|
-
toolSearch: "#
|
|
41
|
-
toolTitle: "#
|
|
42
|
-
toolBash: "#
|
|
43
|
-
toolRead: "#
|
|
44
|
-
toolIndex: "#
|
|
45
|
-
toolEdit: "#
|
|
46
|
-
toolWeb: "#
|
|
47
|
-
toolMeta: "#
|
|
39
|
+
toolMutation: "#b87f98",
|
|
40
|
+
toolSearch: "#9780bb",
|
|
41
|
+
toolTitle: "#858f99",
|
|
42
|
+
toolBash: "#b88862",
|
|
43
|
+
toolRead: "#639b7c",
|
|
44
|
+
toolIndex: "#698bb4",
|
|
45
|
+
toolEdit: "#b46680",
|
|
46
|
+
toolWeb: "#768ab6",
|
|
47
|
+
toolMeta: "#787d92",
|
|
48
48
|
thinkingForeground: "#64748b",
|
|
49
49
|
userForeground: "#d97706",
|
|
50
50
|
thinkingXHigh: "#ff8a86",
|
|
@@ -91,15 +91,15 @@ export const THEMES = {
|
|
|
91
91
|
warning: "#9a631d",
|
|
92
92
|
heading: "#b88a28",
|
|
93
93
|
info: "#246b8e",
|
|
94
|
-
toolMutation: "#
|
|
95
|
-
toolSearch: "#
|
|
96
|
-
toolTitle: "#
|
|
97
|
-
toolBash: "#
|
|
98
|
-
toolRead: "#
|
|
99
|
-
toolIndex: "#
|
|
100
|
-
toolEdit: "#
|
|
101
|
-
toolWeb: "#
|
|
102
|
-
toolMeta: "#
|
|
94
|
+
toolMutation: "#8c526a",
|
|
95
|
+
toolSearch: "#68578c",
|
|
96
|
+
toolTitle: "#5d6978",
|
|
97
|
+
toolBash: "#8c704b",
|
|
98
|
+
toolRead: "#477a5d",
|
|
99
|
+
toolIndex: "#497496",
|
|
100
|
+
toolEdit: "#8c4d65",
|
|
101
|
+
toolWeb: "#52638c",
|
|
102
|
+
toolMeta: "#747b8a",
|
|
103
103
|
thinkingForeground: "#6b5491",
|
|
104
104
|
userForeground: "#854d0e",
|
|
105
105
|
thinkingXHigh: "#cf333d",
|
|
@@ -12,7 +12,6 @@ export interface SessionTitleConfig {
|
|
|
12
12
|
maxRetries: number;
|
|
13
13
|
generationAttempts: number;
|
|
14
14
|
retryDelayMs: number;
|
|
15
|
-
temperature: number;
|
|
16
15
|
timeoutMs: number;
|
|
17
16
|
terminalTitle: boolean;
|
|
18
17
|
terminalTitlePrefix: string;
|
|
@@ -29,7 +28,6 @@ const DEFAULT_CONFIG: SessionTitleConfig = {
|
|
|
29
28
|
maxRetries: 2,
|
|
30
29
|
generationAttempts: 3,
|
|
31
30
|
retryDelayMs: 3000,
|
|
32
|
-
temperature: 0.2,
|
|
33
31
|
timeoutMs: 12_000,
|
|
34
32
|
terminalTitle: true,
|
|
35
33
|
terminalTitlePrefix: "pi — ",
|
|
@@ -83,9 +81,6 @@ function mergeConfig(base: SessionTitleConfig, raw: Record<string, unknown>): Se
|
|
|
83
81
|
if (typeof raw.retryDelayMs === "number" && Number.isFinite(raw.retryDelayMs)) {
|
|
84
82
|
next.retryDelayMs = Math.max(250, Math.floor(raw.retryDelayMs));
|
|
85
83
|
}
|
|
86
|
-
if (typeof raw.temperature === "number" && Number.isFinite(raw.temperature)) {
|
|
87
|
-
next.temperature = Math.min(2, Math.max(0, raw.temperature));
|
|
88
|
-
}
|
|
89
84
|
if (typeof raw.timeoutMs === "number" && Number.isFinite(raw.timeoutMs)) {
|
|
90
85
|
next.timeoutMs = Math.max(1000, Math.floor(raw.timeoutMs));
|
|
91
86
|
}
|