pi-ui-extend 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -2
- package/dist/app/app.d.ts +4 -0
- package/dist/app/app.js +74 -7
- package/dist/app/cli/install.d.ts +2 -0
- package/dist/app/cli/install.js +16 -1
- package/dist/app/commands/command-controller.js +4 -0
- package/dist/app/commands/command-host.d.ts +4 -0
- package/dist/app/commands/command-model-actions.d.ts +5 -0
- package/dist/app/commands/command-model-actions.js +104 -0
- package/dist/app/commands/command-navigation-actions.d.ts +6 -1
- package/dist/app/commands/command-navigation-actions.js +37 -14
- package/dist/app/commands/command-registry.d.ts +4 -0
- package/dist/app/commands/command-registry.js +32 -0
- package/dist/app/commands/command-session-actions.d.ts +1 -0
- package/dist/app/commands/command-session-actions.js +15 -5
- package/dist/app/commands/shell-controller.d.ts +1 -0
- package/dist/app/commands/shell-controller.js +1 -1
- package/dist/app/constants.d.ts +1 -1
- package/dist/app/constants.js +1 -1
- package/dist/app/icons.js +1 -1
- package/dist/app/input/autocomplete-controller.d.ts +52 -0
- package/dist/app/input/autocomplete-controller.js +352 -0
- package/dist/app/input/input-action-controller.d.ts +1 -0
- package/dist/app/input/input-action-controller.js +21 -0
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +2 -0
- package/dist/app/input/input-paste-handler.d.ts +1 -0
- package/dist/app/input/input-paste-handler.js +22 -18
- package/dist/app/input/voice-controller.d.ts +2 -0
- package/dist/app/input/voice-controller.js +27 -15
- package/dist/app/model/model-usage-status.d.ts +9 -0
- package/dist/app/model/model-usage-status.js +124 -34
- package/dist/app/popup/popup-action-controller.js +1 -1
- package/dist/app/process.d.ts +17 -0
- package/dist/app/process.js +68 -0
- package/dist/app/rendering/conversation-entry-renderer.js +17 -6
- package/dist/app/rendering/conversation-tool-renderer.js +3 -2
- package/dist/app/rendering/editor-layout-renderer.d.ts +1 -0
- package/dist/app/rendering/editor-layout-renderer.js +11 -1
- package/dist/app/rendering/message-content.js +65 -7
- package/dist/app/rendering/render-controller.js +6 -1
- package/dist/app/rendering/render-text.d.ts +3 -0
- package/dist/app/rendering/render-text.js +51 -3
- package/dist/app/rendering/status-line-renderer.d.ts +5 -1
- package/dist/app/rendering/status-line-renderer.js +69 -25
- package/dist/app/rendering/tool-block-renderer.js +13 -31
- package/dist/app/runtime.d.ts +6 -1
- package/dist/app/runtime.js +35 -2
- package/dist/app/screen/clipboard.d.ts +2 -2
- package/dist/app/screen/clipboard.js +13 -18
- package/dist/app/screen/mouse-controller.d.ts +5 -2
- package/dist/app/screen/mouse-controller.js +16 -1
- package/dist/app/screen/screen-styler.d.ts +4 -1
- package/dist/app/screen/screen-styler.js +3 -2
- package/dist/app/screen/status-controller.d.ts +3 -0
- package/dist/app/screen/status-controller.js +23 -8
- package/dist/app/session/queued-message-controller.d.ts +7 -1
- package/dist/app/session/queued-message-controller.js +32 -21
- package/dist/app/session/resume-session-loader.d.ts +15 -0
- package/dist/app/session/resume-session-loader.js +204 -0
- package/dist/app/session/session-event-controller.d.ts +5 -1
- package/dist/app/session/session-event-controller.js +72 -5
- package/dist/app/session/session-history.js +4 -3
- package/dist/app/session/session-lifecycle-controller.d.ts +5 -0
- package/dist/app/session/session-lifecycle-controller.js +9 -1
- package/dist/app/session/tabs-controller.d.ts +10 -1
- package/dist/app/session/tabs-controller.js +101 -5
- package/dist/app/terminal/nerd-font-controller.js +16 -17
- package/dist/app/terminal/terminal-controller.d.ts +1 -0
- package/dist/app/terminal/terminal-controller.js +1 -0
- package/dist/app/types.d.ts +14 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -1
- package/dist/app/workspace/workspace-actions-controller.js +3 -3
- package/dist/app/workspace/workspace-undo.d.ts +1 -1
- package/dist/app/workspace/workspace-undo.js +22 -20
- package/dist/config.d.ts +27 -0
- package/dist/config.js +174 -1
- package/dist/default-pix-config.js +38 -353
- package/dist/input-editor.d.ts +7 -1
- package/dist/input-editor.js +47 -6
- package/dist/markdown-format.d.ts +1 -0
- package/dist/markdown-format.js +26 -1
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -0
- package/external/pi-tools-suite/src/dcp/prompts.ts +1 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +45 -195
- package/external/pi-tools-suite/src/lib/lsp.ts +2 -1
- package/external/pi-tools-suite/src/lsp/_shared/output.ts +8 -7
- package/external/pi-tools-suite/src/lsp/manager.ts +4 -4
- package/external/pi-tools-suite/src/repo-discovery/index.ts +49 -2
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +9 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isRecord } from "../guards.js";
|
|
2
2
|
import { createId } from "../id.js";
|
|
3
|
+
import { isOnlyHiddenMetadata } from "../../markdown-format.js";
|
|
3
4
|
import { extractImageContents, renderContent, renderUserMessageContent, stringifyUnknown } from "../rendering/message-content.js";
|
|
4
5
|
const SYSTEM_CUSTOM_MESSAGE_TYPE = "pix-system";
|
|
5
6
|
const HISTORICAL_SUBAGENTS_OBSERVATION = { showSnapshot: false };
|
|
@@ -119,10 +120,10 @@ function renderAssistantHistoryMessage(message, toolResults, options) {
|
|
|
119
120
|
options.addEntry({ id: createId("thinking"), kind: "thinking", text: thinkingText, expanded: false, status: "done" });
|
|
120
121
|
thinkingText = "";
|
|
121
122
|
}
|
|
122
|
-
if (assistantText) {
|
|
123
|
+
if (assistantText && !isOnlyHiddenMetadata(assistantText)) {
|
|
123
124
|
options.addEntry({ id: createId("assistant"), kind: "assistant", text: assistantText });
|
|
124
|
-
assistantText = "";
|
|
125
125
|
}
|
|
126
|
+
assistantText = "";
|
|
126
127
|
const toolCallId = String(block.id ?? createId("tool"));
|
|
127
128
|
const result = toolResults.get(toolCallId);
|
|
128
129
|
const toolName = result?.toolName ?? String(block.name ?? "unknown");
|
|
@@ -158,7 +159,7 @@ function renderAssistantHistoryMessage(message, toolResults, options) {
|
|
|
158
159
|
if (thinkingText) {
|
|
159
160
|
options.addEntry({ id: createId("thinking"), kind: "thinking", text: thinkingText, expanded: false, status: "done" });
|
|
160
161
|
}
|
|
161
|
-
if (assistantText) {
|
|
162
|
+
if (assistantText && !isOnlyHiddenMetadata(assistantText)) {
|
|
162
163
|
options.addEntry({ id: createId("assistant"), kind: "assistant", text: assistantText });
|
|
163
164
|
}
|
|
164
165
|
}
|
|
@@ -37,6 +37,10 @@ export type AppSessionLifecycleHost = {
|
|
|
37
37
|
clearMouseRenderState(): void;
|
|
38
38
|
scrollReset(): void;
|
|
39
39
|
loadSessionHistoryEntries(): void;
|
|
40
|
+
loadSessionHistoryEntriesAsync(options: {
|
|
41
|
+
isCancelled: () => boolean;
|
|
42
|
+
render: () => void;
|
|
43
|
+
}): Promise<boolean>;
|
|
40
44
|
syncUserSessionEntryMetadata(): void;
|
|
41
45
|
restoreTabsAfterStartup(): Promise<void>;
|
|
42
46
|
render(): void;
|
|
@@ -49,6 +53,7 @@ export declare class AppSessionLifecycleController {
|
|
|
49
53
|
bindCurrentSession(): Promise<void>;
|
|
50
54
|
unsubscribeSession(): void;
|
|
51
55
|
afterSessionReplacement(message?: string): void;
|
|
56
|
+
private loadReplacementHistory;
|
|
52
57
|
resetSessionView(): void;
|
|
53
58
|
loadSessionHistory(): void;
|
|
54
59
|
requireRuntime(): AgentSessionRuntime;
|
|
@@ -84,7 +84,15 @@ export class AppSessionLifecycleController {
|
|
|
84
84
|
}
|
|
85
85
|
afterSessionReplacement(message) {
|
|
86
86
|
this.resetSessionView();
|
|
87
|
-
this.
|
|
87
|
+
void this.loadReplacementHistory(message);
|
|
88
|
+
this.host.render();
|
|
89
|
+
}
|
|
90
|
+
async loadReplacementHistory(message) {
|
|
91
|
+
await this.host.loadSessionHistoryEntriesAsync({
|
|
92
|
+
isCancelled: () => !this.host.isRunning(),
|
|
93
|
+
render: () => this.host.render(),
|
|
94
|
+
});
|
|
95
|
+
this.host.syncUserSessionEntryMetadata();
|
|
88
96
|
if (message)
|
|
89
97
|
this.host.addEntry({ id: createId("system"), kind: "system", text: message });
|
|
90
98
|
const session = this.host.runtime()?.session;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AgentSession, type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AppBlinkController } from "../screen/blink-controller.js";
|
|
3
|
-
import type { AppOptions, Entry, SessionActivity, SessionTab } from "../types.js";
|
|
3
|
+
import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessage } from "../types.js";
|
|
4
4
|
export type TabInputState = {
|
|
5
5
|
text: string;
|
|
6
6
|
cursor: number;
|
|
@@ -26,6 +26,8 @@ export type AppTabsControllerHost = {
|
|
|
26
26
|
syncUserSessionEntryMetadata(): void;
|
|
27
27
|
captureInputState(): TabInputState;
|
|
28
28
|
restoreInputState(state: TabInputState): void;
|
|
29
|
+
captureDeferredUserMessages?(): readonly SubmittedUserMessage[];
|
|
30
|
+
restoreDeferredUserMessages?(messages: readonly SubmittedUserMessage[]): void;
|
|
29
31
|
addEntry(entry: Entry): void;
|
|
30
32
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
31
33
|
render(): void;
|
|
@@ -36,6 +38,7 @@ export declare class AppTabsController {
|
|
|
36
38
|
private readonly runtimesByTabId;
|
|
37
39
|
private readonly runtimeSubscriptionsByTabId;
|
|
38
40
|
private readonly inputStatesByTabId;
|
|
41
|
+
private readonly deferredUserMessagesByTabId;
|
|
39
42
|
private activeTabId;
|
|
40
43
|
private pendingActiveTabId;
|
|
41
44
|
private historyLoadGeneration;
|
|
@@ -50,6 +53,7 @@ export declare class AppTabsController {
|
|
|
50
53
|
setInputStateForTab(tabId: string | undefined, state: TabInputState): Promise<void>;
|
|
51
54
|
disposeInactiveRuntimes(disposeRuntime?: (runtime: AgentSessionRuntime) => Promise<void>): Promise<void>;
|
|
52
55
|
saveInputStateForQuit(): Promise<void>;
|
|
56
|
+
persistActiveDeferredUserMessages(): void;
|
|
53
57
|
syncActiveTabFromRuntime(options?: {
|
|
54
58
|
save?: boolean;
|
|
55
59
|
force?: boolean;
|
|
@@ -74,13 +78,17 @@ export declare class AppTabsController {
|
|
|
74
78
|
private shouldSyncTabFromRuntimeEvent;
|
|
75
79
|
private syncTabFromObservedRuntime;
|
|
76
80
|
private storeActiveInputState;
|
|
81
|
+
private storeActiveDeferredUserMessages;
|
|
77
82
|
private restoreInputState;
|
|
83
|
+
private restoreDeferredUserMessages;
|
|
84
|
+
private cloneSubmittedUserMessage;
|
|
78
85
|
private runtimeForTab;
|
|
79
86
|
private findTabForSession;
|
|
80
87
|
private findTabBySessionPath;
|
|
81
88
|
private replaceTabs;
|
|
82
89
|
private removeTab;
|
|
83
90
|
private restorePersistedInputStates;
|
|
91
|
+
private restorePersistedDeferredUserMessages;
|
|
84
92
|
private ensureCurrentSessionTab;
|
|
85
93
|
private tabFromSession;
|
|
86
94
|
private updateTabFromSession;
|
|
@@ -95,6 +103,7 @@ export declare class AppTabsController {
|
|
|
95
103
|
private restoredTabs;
|
|
96
104
|
private loadTabs;
|
|
97
105
|
private parsePersistedInputState;
|
|
106
|
+
private parsePersistedSubmittedUserMessages;
|
|
98
107
|
private saveTabs;
|
|
99
108
|
private filePath;
|
|
100
109
|
}
|
|
@@ -7,7 +7,7 @@ import { isRecord } from "../guards.js";
|
|
|
7
7
|
import { createId } from "../id.js";
|
|
8
8
|
import { createStartupInfoMessage, isEmptyStartupSession } from "../cli/startup-info.js";
|
|
9
9
|
import { tabPanelRows } from "../rendering/tab-line-renderer.js";
|
|
10
|
-
const TAB_STATE_VERSION =
|
|
10
|
+
const TAB_STATE_VERSION = 3;
|
|
11
11
|
const MAX_RESTORED_TABS = 8;
|
|
12
12
|
const TAB_ATTENTION_BLINK_KEY = "tab-attention";
|
|
13
13
|
export class AppTabsController {
|
|
@@ -16,6 +16,7 @@ export class AppTabsController {
|
|
|
16
16
|
runtimesByTabId = new Map();
|
|
17
17
|
runtimeSubscriptionsByTabId = new Map();
|
|
18
18
|
inputStatesByTabId = new Map();
|
|
19
|
+
deferredUserMessagesByTabId = new Map();
|
|
19
20
|
activeTabId;
|
|
20
21
|
pendingActiveTabId;
|
|
21
22
|
historyLoadGeneration = 0;
|
|
@@ -96,8 +97,13 @@ export class AppTabsController {
|
|
|
96
97
|
async saveInputStateForQuit() {
|
|
97
98
|
this.syncActiveTabFromRuntime({ save: false });
|
|
98
99
|
this.storeActiveInputState();
|
|
100
|
+
this.storeActiveDeferredUserMessages();
|
|
99
101
|
await this.saveTabs();
|
|
100
102
|
}
|
|
103
|
+
persistActiveDeferredUserMessages() {
|
|
104
|
+
this.storeActiveDeferredUserMessages();
|
|
105
|
+
void this.saveTabs();
|
|
106
|
+
}
|
|
101
107
|
syncActiveTabFromRuntime(options = {}) {
|
|
102
108
|
if (this.pendingActiveTabId && options.force !== true)
|
|
103
109
|
return;
|
|
@@ -108,14 +114,17 @@ export class AppTabsController {
|
|
|
108
114
|
const active = this.activeTab();
|
|
109
115
|
const existing = sessionPath ? this.findTabBySessionPath(sessionPath, active ? { excludeTabId: active.id } : {}) : undefined;
|
|
110
116
|
if (existing) {
|
|
111
|
-
if (active)
|
|
117
|
+
if (active) {
|
|
112
118
|
this.storeActiveInputState();
|
|
119
|
+
this.storeActiveDeferredUserMessages();
|
|
120
|
+
}
|
|
113
121
|
this.activeTabId = existing.id;
|
|
114
122
|
this.clearTabAttention(existing);
|
|
115
123
|
this.updateTabFromSession(existing, session);
|
|
116
124
|
if (active)
|
|
117
125
|
this.deleteRuntimeForTab(active.id);
|
|
118
126
|
this.storeActiveRuntime();
|
|
127
|
+
this.restoreDeferredUserMessages(existing.id);
|
|
119
128
|
if (options.save !== false)
|
|
120
129
|
void this.saveTabs();
|
|
121
130
|
return;
|
|
@@ -167,6 +176,7 @@ export class AppTabsController {
|
|
|
167
176
|
: restoredTabs[0]?.sessionPath;
|
|
168
177
|
this.replaceTabs(restoredTabs, desiredPath);
|
|
169
178
|
this.restorePersistedInputStates(saved);
|
|
179
|
+
this.restorePersistedDeferredUserMessages(saved);
|
|
170
180
|
if (explicitSessionPath && currentPath)
|
|
171
181
|
this.ensureCurrentSessionTab(runtime.session);
|
|
172
182
|
if (!desiredPath) {
|
|
@@ -191,6 +201,8 @@ export class AppTabsController {
|
|
|
191
201
|
}
|
|
192
202
|
this.syncActiveTabFromRuntime({ save: false });
|
|
193
203
|
this.host.resetSessionView();
|
|
204
|
+
if (this.activeTabId)
|
|
205
|
+
this.restoreDeferredUserMessages(this.activeTabId);
|
|
194
206
|
this.host.loadSessionHistory();
|
|
195
207
|
this.host.setSessionStatus(runtime.session);
|
|
196
208
|
this.host.setSessionActivity(this.sessionActivity(runtime.session));
|
|
@@ -211,6 +223,7 @@ export class AppTabsController {
|
|
|
211
223
|
this.cancelHistoryLoad();
|
|
212
224
|
this.syncActiveTabFromRuntime();
|
|
213
225
|
this.storeActiveInputState();
|
|
226
|
+
this.storeActiveDeferredUserMessages();
|
|
214
227
|
this.host.setStatus("starting new tab");
|
|
215
228
|
this.host.render();
|
|
216
229
|
const newRuntime = await this.host.createRuntimeForNewSession();
|
|
@@ -233,6 +246,7 @@ export class AppTabsController {
|
|
|
233
246
|
}
|
|
234
247
|
void this.saveTabs();
|
|
235
248
|
this.host.resetSessionView();
|
|
249
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
236
250
|
if (isEmptyStartupSession(newRuntime)) {
|
|
237
251
|
this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(newRuntime) });
|
|
238
252
|
}
|
|
@@ -270,6 +284,7 @@ export class AppTabsController {
|
|
|
270
284
|
}
|
|
271
285
|
this.cancelHistoryLoad();
|
|
272
286
|
this.storeActiveInputState();
|
|
287
|
+
this.storeActiveDeferredUserMessages();
|
|
273
288
|
const previousTabId = this.activeTabId;
|
|
274
289
|
const previousRuntime = runtime;
|
|
275
290
|
this.host.setStatus("opening session tab");
|
|
@@ -312,6 +327,8 @@ export class AppTabsController {
|
|
|
312
327
|
void this.host.disposeRuntime(newRuntime);
|
|
313
328
|
this.host.showToast("Could not open session tab", "warning");
|
|
314
329
|
this.host.resetSessionView();
|
|
330
|
+
if (previousTabId)
|
|
331
|
+
this.restoreDeferredUserMessages(previousTabId);
|
|
315
332
|
this.host.loadSessionHistory();
|
|
316
333
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
317
334
|
this.host.setSessionActivity(this.sessionActivity(this.host.runtime()?.session));
|
|
@@ -353,12 +370,14 @@ export class AppTabsController {
|
|
|
353
370
|
const previousTargetActivity = target.activity;
|
|
354
371
|
this.storeActiveRuntime(runtime);
|
|
355
372
|
this.storeActiveInputState();
|
|
373
|
+
this.storeActiveDeferredUserMessages();
|
|
356
374
|
this.activeTabId = target.id;
|
|
357
375
|
this.pendingActiveTabId = target.id;
|
|
358
376
|
target.activity = "thinking";
|
|
359
377
|
this.clearTabAttention(target);
|
|
360
378
|
this.restoreInputState(target.id);
|
|
361
379
|
this.host.resetSessionView();
|
|
380
|
+
this.restoreDeferredUserMessages(target.id);
|
|
362
381
|
this.host.setStatus("switching tab");
|
|
363
382
|
this.host.setSessionActivity("thinking");
|
|
364
383
|
this.host.render();
|
|
@@ -388,6 +407,8 @@ export class AppTabsController {
|
|
|
388
407
|
}
|
|
389
408
|
this.host.showToast("Could not switch tab", "warning");
|
|
390
409
|
this.host.resetSessionView();
|
|
410
|
+
if (previousTabId)
|
|
411
|
+
this.restoreDeferredUserMessages(previousTabId);
|
|
391
412
|
this.host.loadSessionHistory();
|
|
392
413
|
const activeSession = this.host.runtime()?.session;
|
|
393
414
|
this.host.setSessionStatus(activeSession);
|
|
@@ -426,7 +447,9 @@ export class AppTabsController {
|
|
|
426
447
|
this.tabItems.splice(index, 1);
|
|
427
448
|
this.deleteRuntimeForTab(tabId);
|
|
428
449
|
this.inputStatesByTabId.delete(tabId);
|
|
450
|
+
this.deferredUserMessagesByTabId.delete(tabId);
|
|
429
451
|
this.storeActiveInputState();
|
|
452
|
+
this.storeActiveDeferredUserMessages();
|
|
430
453
|
this.stopAttentionBlinkIfIdle();
|
|
431
454
|
if (tabRuntime)
|
|
432
455
|
void this.host.disposeRuntime(tabRuntime);
|
|
@@ -451,6 +474,7 @@ export class AppTabsController {
|
|
|
451
474
|
this.tabItems.splice(index, 1);
|
|
452
475
|
this.deleteRuntimeForTab(tabId);
|
|
453
476
|
this.inputStatesByTabId.delete(tabId);
|
|
477
|
+
this.deferredUserMessagesByTabId.delete(tabId);
|
|
454
478
|
this.stopAttentionBlinkIfIdle();
|
|
455
479
|
this.activeTabId = nextTab.id;
|
|
456
480
|
this.clearTabAttention(nextTab);
|
|
@@ -483,9 +507,11 @@ export class AppTabsController {
|
|
|
483
507
|
this.updateTabFromSession(tab, runtime.session);
|
|
484
508
|
this.setRuntimeForTab(tab.id, runtime);
|
|
485
509
|
this.inputStatesByTabId.delete(tab.id);
|
|
510
|
+
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
486
511
|
this.restoreInputState(tab.id);
|
|
487
512
|
this.stopAttentionBlinkIfIdle();
|
|
488
513
|
this.host.resetSessionView();
|
|
514
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
489
515
|
this.host.addEntry({ id: createId("system"), kind: "system", text: `Started a new session. cwd=${runtime.cwd}` });
|
|
490
516
|
if (runtime.modelFallbackMessage)
|
|
491
517
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
@@ -502,6 +528,8 @@ export class AppTabsController {
|
|
|
502
528
|
const generation = ++this.historyLoadGeneration;
|
|
503
529
|
const isCancelled = () => generation !== this.historyLoadGeneration || this.host.runtime() !== runtime;
|
|
504
530
|
this.host.resetSessionView();
|
|
531
|
+
if (this.activeTabId)
|
|
532
|
+
this.restoreDeferredUserMessages(this.activeTabId);
|
|
505
533
|
this.host.setStatus("loading session history");
|
|
506
534
|
this.host.setSessionActivity("thinking");
|
|
507
535
|
this.host.render();
|
|
@@ -620,9 +648,31 @@ export class AppTabsController {
|
|
|
620
648
|
cursor: state.cursor,
|
|
621
649
|
});
|
|
622
650
|
}
|
|
651
|
+
storeActiveDeferredUserMessages() {
|
|
652
|
+
if (!this.activeTabId || !this.host.captureDeferredUserMessages)
|
|
653
|
+
return;
|
|
654
|
+
const messages = this.host.captureDeferredUserMessages();
|
|
655
|
+
if (messages.length > 0) {
|
|
656
|
+
this.deferredUserMessagesByTabId.set(this.activeTabId, messages.map((message) => this.cloneSubmittedUserMessage(message)));
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
this.deferredUserMessagesByTabId.delete(this.activeTabId);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
623
662
|
restoreInputState(tabId) {
|
|
624
663
|
this.host.restoreInputState(this.inputStatesByTabId.get(tabId) ?? { text: "", cursor: 0 });
|
|
625
664
|
}
|
|
665
|
+
restoreDeferredUserMessages(tabId) {
|
|
666
|
+
this.host.restoreDeferredUserMessages?.(this.deferredUserMessagesByTabId.get(tabId) ?? []);
|
|
667
|
+
}
|
|
668
|
+
cloneSubmittedUserMessage(message) {
|
|
669
|
+
return {
|
|
670
|
+
id: message.id,
|
|
671
|
+
promptText: message.promptText,
|
|
672
|
+
displayText: message.displayText,
|
|
673
|
+
images: message.images.map((image) => ({ ...image })),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
626
676
|
async runtimeForTab(tab) {
|
|
627
677
|
const existing = this.runtimesByTabId.get(tab.id);
|
|
628
678
|
if (existing)
|
|
@@ -650,6 +700,7 @@ export class AppTabsController {
|
|
|
650
700
|
this.runtimesByTabId.clear();
|
|
651
701
|
this.clearRuntimeSubscriptions();
|
|
652
702
|
this.inputStatesByTabId.clear();
|
|
703
|
+
this.deferredUserMessagesByTabId.clear();
|
|
653
704
|
const seen = new Set();
|
|
654
705
|
for (const tab of tabs) {
|
|
655
706
|
const sessionPath = tab.sessionPath ? resolve(tab.sessionPath) : undefined;
|
|
@@ -678,6 +729,7 @@ export class AppTabsController {
|
|
|
678
729
|
this.tabItems.splice(index, 1);
|
|
679
730
|
this.deleteRuntimeForTab(tabId);
|
|
680
731
|
this.inputStatesByTabId.delete(tabId);
|
|
732
|
+
this.deferredUserMessagesByTabId.delete(tabId);
|
|
681
733
|
}
|
|
682
734
|
restorePersistedInputStates(saved) {
|
|
683
735
|
const inputsByPath = new Map();
|
|
@@ -695,6 +747,22 @@ export class AppTabsController {
|
|
|
695
747
|
this.inputStatesByTabId.set(tab.id, input);
|
|
696
748
|
}
|
|
697
749
|
}
|
|
750
|
+
restorePersistedDeferredUserMessages(saved) {
|
|
751
|
+
const messagesByPath = new Map();
|
|
752
|
+
for (const tab of saved.tabs) {
|
|
753
|
+
if (!tab.deferredUserMessages || tab.deferredUserMessages.length === 0)
|
|
754
|
+
continue;
|
|
755
|
+
messagesByPath.set(resolve(tab.path), tab.deferredUserMessages.map((message) => this.cloneSubmittedUserMessage(message)));
|
|
756
|
+
}
|
|
757
|
+
for (const tab of this.tabItems) {
|
|
758
|
+
if (!tab.sessionPath)
|
|
759
|
+
continue;
|
|
760
|
+
const messages = messagesByPath.get(resolve(tab.sessionPath));
|
|
761
|
+
if (!messages || messages.length === 0)
|
|
762
|
+
continue;
|
|
763
|
+
this.deferredUserMessagesByTabId.set(tab.id, messages);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
698
766
|
ensureCurrentSessionTab(session) {
|
|
699
767
|
const currentPath = session.sessionFile ? resolve(session.sessionFile) : undefined;
|
|
700
768
|
if (!currentPath)
|
|
@@ -783,7 +851,8 @@ export class AppTabsController {
|
|
|
783
851
|
for (const tab of saved.tabs) {
|
|
784
852
|
const sessionPath = resolve(tab.path);
|
|
785
853
|
const hasDraftInput = (tab.input?.text.length ?? 0) > 0;
|
|
786
|
-
|
|
854
|
+
const hasDeferredQueue = (tab.deferredUserMessages?.length ?? 0) > 0;
|
|
855
|
+
if (seen.has(sessionPath) || (!existsSync(sessionPath) && !hasDraftInput && !hasDeferredQueue))
|
|
787
856
|
continue;
|
|
788
857
|
seen.add(sessionPath);
|
|
789
858
|
const title = titles.get(sessionPath) ?? tab.title?.trim();
|
|
@@ -802,21 +871,23 @@ export class AppTabsController {
|
|
|
802
871
|
try {
|
|
803
872
|
const raw = await readFile(this.filePath(), "utf8");
|
|
804
873
|
const parsed = JSON.parse(raw);
|
|
805
|
-
if (!isRecord(parsed) || (parsed.version !== 1 && parsed.version !== TAB_STATE_VERSION) || !Array.isArray(parsed.tabs))
|
|
874
|
+
if (!isRecord(parsed) || (parsed.version !== 1 && parsed.version !== 2 && parsed.version !== TAB_STATE_VERSION) || !Array.isArray(parsed.tabs))
|
|
806
875
|
return undefined;
|
|
807
876
|
const tabs = [];
|
|
808
877
|
for (const value of parsed.tabs) {
|
|
809
878
|
if (!isRecord(value) || typeof value.path !== "string")
|
|
810
879
|
continue;
|
|
811
880
|
const input = this.parsePersistedInputState(value.input);
|
|
881
|
+
const deferredUserMessages = this.parsePersistedSubmittedUserMessages(value.deferredUserMessages);
|
|
812
882
|
tabs.push({
|
|
813
883
|
path: value.path,
|
|
814
884
|
...(typeof value.title === "string" ? { title: value.title } : {}),
|
|
815
885
|
...(input ? { input } : {}),
|
|
886
|
+
...(deferredUserMessages.length > 0 ? { deferredUserMessages } : {}),
|
|
816
887
|
});
|
|
817
888
|
}
|
|
818
889
|
return {
|
|
819
|
-
version: parsed.version === 1 ? 1 : TAB_STATE_VERSION,
|
|
890
|
+
version: parsed.version === 1 ? 1 : parsed.version === 2 ? 2 : TAB_STATE_VERSION,
|
|
820
891
|
cwd: typeof parsed.cwd === "string" ? parsed.cwd : this.host.options.cwd,
|
|
821
892
|
tabs,
|
|
822
893
|
...(typeof parsed.activePath === "string" ? { activePath: parsed.activePath } : {}),
|
|
@@ -836,6 +907,27 @@ export class AppTabsController {
|
|
|
836
907
|
: value.text.length;
|
|
837
908
|
return { text: value.text, cursor };
|
|
838
909
|
}
|
|
910
|
+
parsePersistedSubmittedUserMessages(value) {
|
|
911
|
+
if (!Array.isArray(value))
|
|
912
|
+
return [];
|
|
913
|
+
const messages = [];
|
|
914
|
+
for (const item of value) {
|
|
915
|
+
if (!isRecord(item) || typeof item.promptText !== "string" || typeof item.displayText !== "string")
|
|
916
|
+
continue;
|
|
917
|
+
const images = Array.isArray(item.images)
|
|
918
|
+
? item.images.flatMap((image) => (isRecord(image) && typeof image.data === "string" && typeof image.mimeType === "string"
|
|
919
|
+
? [{ type: "image", data: image.data, mimeType: image.mimeType }]
|
|
920
|
+
: []))
|
|
921
|
+
: [];
|
|
922
|
+
messages.push({
|
|
923
|
+
id: typeof item.id === "string" ? item.id : createId("queued-user"),
|
|
924
|
+
promptText: item.promptText,
|
|
925
|
+
displayText: item.displayText,
|
|
926
|
+
images,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return messages;
|
|
930
|
+
}
|
|
839
931
|
async saveTabs() {
|
|
840
932
|
if (this.host.options.noSession)
|
|
841
933
|
return;
|
|
@@ -857,6 +949,10 @@ export class AppTabsController {
|
|
|
857
949
|
cursor: Math.max(0, Math.min(input.text.length, Math.trunc(input.cursor))),
|
|
858
950
|
};
|
|
859
951
|
}
|
|
952
|
+
const deferredUserMessages = this.deferredUserMessagesByTabId.get(tab.id);
|
|
953
|
+
if (deferredUserMessages && deferredUserMessages.length > 0) {
|
|
954
|
+
persistedTab.deferredUserMessages = deferredUserMessages.map((message) => this.cloneSubmittedUserMessage(message));
|
|
955
|
+
}
|
|
860
956
|
tabs.push(persistedTab);
|
|
861
957
|
}
|
|
862
958
|
if (tabs.length === 0)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import { commandExists, runProcess } from "../process.js";
|
|
6
7
|
const CASK_NAME = "font-jetbrains-mono-nerd-font";
|
|
7
8
|
export const FONT_FAMILY_NAME = "JetBrainsMono Nerd Font Mono";
|
|
8
9
|
export const FONT_FILE_NAME = "JetBrainsMonoNerdFontMono-Regular.ttf";
|
|
@@ -42,10 +43,13 @@ export class NerdFontController {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
export async function isJetBrainsNerdFontInstalled() {
|
|
45
|
-
if (commandExists("brew")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
if (await commandExists("brew")) {
|
|
47
|
+
const result = await runProcess("brew", ["list", "--cask", CASK_NAME], { maxBufferBytes: 1024 });
|
|
48
|
+
if (result.status === 0)
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (process.platform === "linux" && await commandExists("fc-match")) {
|
|
52
|
+
const result = await runProcess("fc-match", ["-f", "%{family}", FONT_FAMILY_NAME], { maxBufferBytes: 1024 });
|
|
49
53
|
if (result.status === 0 && /JetBrains.*Nerd/iu.test(result.stdout))
|
|
50
54
|
return true;
|
|
51
55
|
}
|
|
@@ -57,7 +61,7 @@ export async function isJetBrainsNerdFontInstalled() {
|
|
|
57
61
|
return false;
|
|
58
62
|
}
|
|
59
63
|
export async function installJetBrainsNerdFont() {
|
|
60
|
-
if (process.platform === "darwin" && commandExists("brew")) {
|
|
64
|
+
if (process.platform === "darwin" && await commandExists("brew")) {
|
|
61
65
|
await runBrewInstall();
|
|
62
66
|
return CASK_NAME;
|
|
63
67
|
}
|
|
@@ -74,9 +78,9 @@ export async function installJetBrainsNerdFont() {
|
|
|
74
78
|
throw new Error("downloaded font is unexpectedly small");
|
|
75
79
|
await writeFile(targetPath, bytes);
|
|
76
80
|
if (process.platform === "linux")
|
|
77
|
-
runOptionalCommand("fc-cache", ["-f", dirname(targetPath)]);
|
|
81
|
+
await runOptionalCommand("fc-cache", ["-f", dirname(targetPath)]);
|
|
78
82
|
if (process.platform === "win32")
|
|
79
|
-
registerWindowsUserFont(targetPath);
|
|
83
|
+
await registerWindowsUserFont(targetPath);
|
|
80
84
|
return targetPath;
|
|
81
85
|
}
|
|
82
86
|
export function userFontInstallPath() {
|
|
@@ -147,10 +151,10 @@ async function runBrewInstall() {
|
|
|
147
151
|
});
|
|
148
152
|
});
|
|
149
153
|
}
|
|
150
|
-
function registerWindowsUserFont(fontPath) {
|
|
154
|
+
async function registerWindowsUserFont(fontPath) {
|
|
151
155
|
const escapedPath = fontPath.replaceAll("'", "''");
|
|
152
156
|
const escapedName = `${FONT_FAMILY_NAME} (TrueType)`.replaceAll("'", "''");
|
|
153
|
-
runOptionalCommand("powershell.exe", [
|
|
157
|
+
await runOptionalCommand("powershell.exe", [
|
|
154
158
|
"-NoProfile",
|
|
155
159
|
"-ExecutionPolicy",
|
|
156
160
|
"Bypass",
|
|
@@ -158,13 +162,8 @@ function registerWindowsUserFont(fontPath) {
|
|
|
158
162
|
`New-Item -Path 'HKCU:\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts' -Force | Out-Null; New-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts' -Name '${escapedName}' -Value '${escapedPath}' -PropertyType String -Force | Out-Null`,
|
|
159
163
|
]);
|
|
160
164
|
}
|
|
161
|
-
function runOptionalCommand(command, args) {
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
function commandExists(command) {
|
|
165
|
-
if (process.platform === "win32")
|
|
166
|
-
return spawnSync("where", [command], { stdio: "ignore" }).status === 0;
|
|
167
|
-
return spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" }).status === 0;
|
|
165
|
+
async function runOptionalCommand(command, args) {
|
|
166
|
+
await runProcess(command, args, { maxBufferBytes: 1024 });
|
|
168
167
|
}
|
|
169
168
|
function errorMessage(error) {
|
|
170
169
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -13,6 +13,7 @@ export type AppTerminalControllerHost = {
|
|
|
13
13
|
stopSubagentsPolling(): void;
|
|
14
14
|
stopModelUsagePolling(): void;
|
|
15
15
|
stopVoiceInput(): Promise<void>;
|
|
16
|
+
stopAutocomplete(): void;
|
|
16
17
|
stopShellCommand(): void;
|
|
17
18
|
unsubscribeSession(): void;
|
|
18
19
|
clearExtensionWidgets(): void;
|
|
@@ -104,6 +104,7 @@ export class AppTerminalController {
|
|
|
104
104
|
this.host.stopSubagentsPolling();
|
|
105
105
|
this.host.stopModelUsagePolling();
|
|
106
106
|
await this.host.stopVoiceInput();
|
|
107
|
+
this.host.stopAutocomplete();
|
|
107
108
|
this.host.stopShellCommand();
|
|
108
109
|
process.stdin.off("data", this.onInputData);
|
|
109
110
|
process.stdin.pause();
|
package/dist/app/types.d.ts
CHANGED
|
@@ -264,6 +264,7 @@ export type StatusLineLayout = {
|
|
|
264
264
|
modelUsageLabel?: string;
|
|
265
265
|
contextBarLabel?: string;
|
|
266
266
|
userJumpWidget?: StatusUserJumpWidgetLayout;
|
|
267
|
+
draftQueueWidget?: StatusDraftQueueWidgetLayout;
|
|
267
268
|
thinkingExpandWidget?: StatusThinkingExpandWidgetLayout;
|
|
268
269
|
compactToolsWidget?: StatusCompactToolsWidgetLayout;
|
|
269
270
|
terminalBellSoundWidget?: StatusTerminalBellSoundWidgetLayout;
|
|
@@ -274,6 +275,10 @@ export type StatusUserJumpWidgetLayout = {
|
|
|
274
275
|
startColumn: number;
|
|
275
276
|
endColumn: number;
|
|
276
277
|
};
|
|
278
|
+
export type StatusDraftQueueWidgetLayout = {
|
|
279
|
+
startColumn: number;
|
|
280
|
+
endColumn: number;
|
|
281
|
+
};
|
|
277
282
|
export type StatusThinkingExpandWidgetLayout = {
|
|
278
283
|
startColumn: number;
|
|
279
284
|
endColumn: number;
|
|
@@ -357,6 +362,10 @@ export type RenderedInput = {
|
|
|
357
362
|
start: number;
|
|
358
363
|
end: number;
|
|
359
364
|
}[])[];
|
|
365
|
+
suggestionSpans: readonly (readonly {
|
|
366
|
+
start: number;
|
|
367
|
+
end: number;
|
|
368
|
+
}[])[];
|
|
360
369
|
};
|
|
361
370
|
export type ScrollBarMetrics = {
|
|
362
371
|
top: number;
|
|
@@ -547,6 +556,11 @@ export type StatusUserJumpTarget = {
|
|
|
547
556
|
startColumn: number;
|
|
548
557
|
endColumn: number;
|
|
549
558
|
};
|
|
559
|
+
export type StatusDraftQueueTarget = {
|
|
560
|
+
row: number;
|
|
561
|
+
startColumn: number;
|
|
562
|
+
endColumn: number;
|
|
563
|
+
};
|
|
550
564
|
export type StatusThinkingExpandTarget = {
|
|
551
565
|
row: number;
|
|
552
566
|
startColumn: number;
|
|
@@ -28,7 +28,7 @@ export declare class AppWorkspaceActionsController {
|
|
|
28
28
|
recordWorkspaceMutationForUserEntry(entryId: string, mutation: WorkspaceMutation): void;
|
|
29
29
|
scheduleUserSessionEntryMetadataSync(): void;
|
|
30
30
|
syncUserSessionEntryMetadata(): void;
|
|
31
|
-
copyUserMessage(entryId: string): void
|
|
31
|
+
copyUserMessage(entryId: string): Promise<void>;
|
|
32
32
|
forkFromUserMessage(entryId: string): Promise<void>;
|
|
33
33
|
undoChangesFromUserMessage(entryId: string): Promise<void>;
|
|
34
34
|
private resolveUserSessionEntryId;
|
|
@@ -74,11 +74,11 @@ export class AppWorkspaceActionsController {
|
|
|
74
74
|
this.host.touchEntry(entry);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
copyUserMessage(entryId) {
|
|
77
|
+
async copyUserMessage(entryId) {
|
|
78
78
|
const entry = this.host.findUserEntry(entryId);
|
|
79
79
|
if (!entry)
|
|
80
80
|
throw new Error("User message is no longer available");
|
|
81
|
-
copyTextToClipboard(entry.text);
|
|
81
|
+
await copyTextToClipboard(entry.text);
|
|
82
82
|
this.host.showToast("Message copied", "success");
|
|
83
83
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
84
84
|
}
|
|
@@ -127,7 +127,7 @@ export class AppWorkspaceActionsController {
|
|
|
127
127
|
this.host.setStatus("reverting recorded commands");
|
|
128
128
|
this.host.render();
|
|
129
129
|
}
|
|
130
|
-
const reverted = mutations.length === 0 ? { ok: true, changedFiles: 0, revertedChanges: 0 } : revertWorkspaceMutations(runtime.cwd, mutations);
|
|
130
|
+
const reverted = mutations.length === 0 ? { ok: true, changedFiles: 0, revertedChanges: 0 } : await revertWorkspaceMutations(runtime.cwd, mutations);
|
|
131
131
|
if (!reverted.ok)
|
|
132
132
|
throw new Error(reverted.error);
|
|
133
133
|
this.host.setStatus("truncating session");
|
|
@@ -41,4 +41,4 @@ export declare function loadWorkspaceUndoIndex(agentDir: string): WorkspaceUndoI
|
|
|
41
41
|
export declare function saveWorkspaceUndoIndex(agentDir: string, index: WorkspaceUndoIndex): void;
|
|
42
42
|
export declare function prepareWorkspaceMutation(cwd: string, toolName: string, args: unknown): WorkspaceMutationPreparation | undefined;
|
|
43
43
|
export declare function workspaceMutationFromToolExecution(input: WorkspaceMutationFromToolInput): WorkspaceMutation | undefined;
|
|
44
|
-
export declare function revertWorkspaceMutations(cwd: string, mutations: readonly WorkspaceMutation[]): WorkspaceRevertResult
|
|
44
|
+
export declare function revertWorkspaceMutations(cwd: string, mutations: readonly WorkspaceMutation[]): Promise<WorkspaceRevertResult>;
|