pi-ui-extend 0.1.9 → 0.1.13
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 +76 -7
- package/dist/app/cli/install.d.ts +16 -0
- package/dist/app/cli/install.js +34 -7
- package/dist/app/cli/startup-info.js +5 -2
- package/dist/app/cli/update.d.ts +7 -0
- package/dist/app/cli/update.js +11 -3
- 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-command.d.ts +7 -0
- package/dist/app/commands/shell-command.js +12 -4
- 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.d.ts +1 -0
- package/dist/app/icons.js +3 -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/prompt-enhancer-controller.d.ts +7 -1
- package/dist/app/input/prompt-enhancer-controller.js +12 -3
- package/dist/app/input/voice-controller.d.ts +51 -1
- package/dist/app/input/voice-controller.js +42 -19
- 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 +8 -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 +61 -25
- package/dist/app/rendering/toast-renderer.js +10 -13
- package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
- package/dist/app/rendering/tool-block-renderer.js +16 -33
- package/dist/app/runtime.d.ts +6 -1
- package/dist/app/runtime.js +35 -2
- package/dist/app/screen/clipboard.d.ts +11 -2
- package/dist/app/screen/clipboard.js +29 -21
- package/dist/app/screen/file-link-opener.d.ts +8 -0
- package/dist/app/screen/file-link-opener.js +11 -3
- package/dist/app/screen/file-links.js +3 -3
- package/dist/app/screen/image-opener.d.ts +12 -0
- package/dist/app/screen/image-opener.js +13 -5
- 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 +36 -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.d.ts +16 -0
- package/dist/app/terminal/nerd-font-controller.js +30 -23
- 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 +39 -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/dist/schemas/index.d.ts +5 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +177 -0
- package/dist/schemas/pi-tools-suite-schema.js +218 -0
- package/dist/schemas/pix-schema.d.ts +65 -0
- package/dist/schemas/pix-schema.js +91 -0
- package/dist/terminal-width.js +73 -56
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +3 -0
- 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 +46 -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/index.ts +4 -2
- package/external/pi-tools-suite/src/todo/state/selectors.ts +4 -0
- package/external/pi-tools-suite/src/todo/todo.ts +2 -6
- 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 +12 -3
- package/schemas/pi-tools-suite.json +881 -0
- package/schemas/pix.json +298 -0
|
@@ -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,3 +1,18 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { commandExists, runProcess } from "../process.js";
|
|
5
|
+
type NerdFontControllerDeps = {
|
|
6
|
+
commandExists: typeof commandExists;
|
|
7
|
+
existsSync: typeof existsSync;
|
|
8
|
+
fetch: typeof fetch;
|
|
9
|
+
mkdir: typeof mkdir;
|
|
10
|
+
readdir: typeof readdir;
|
|
11
|
+
runProcess: typeof runProcess;
|
|
12
|
+
spawn: typeof spawn;
|
|
13
|
+
writeFile: typeof writeFile;
|
|
14
|
+
};
|
|
15
|
+
export declare function setNerdFontControllerTestDeps(overrides: Partial<NerdFontControllerDeps>): () => void;
|
|
1
16
|
export type NerdFontInstallHost = {
|
|
2
17
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
3
18
|
render(): void;
|
|
@@ -15,3 +30,4 @@ export declare class NerdFontController {
|
|
|
15
30
|
export declare function isJetBrainsNerdFontInstalled(): Promise<boolean>;
|
|
16
31
|
export declare function installJetBrainsNerdFont(): Promise<string>;
|
|
17
32
|
export declare function userFontInstallPath(): string;
|
|
33
|
+
export {};
|
|
@@ -1,8 +1,17 @@
|
|
|
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";
|
|
7
|
+
let deps = { commandExists, existsSync, fetch, mkdir, readdir, runProcess, spawn, writeFile };
|
|
8
|
+
export function setNerdFontControllerTestDeps(overrides) {
|
|
9
|
+
const previous = deps;
|
|
10
|
+
deps = { ...deps, ...overrides };
|
|
11
|
+
return () => {
|
|
12
|
+
deps = previous;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
6
15
|
const CASK_NAME = "font-jetbrains-mono-nerd-font";
|
|
7
16
|
export const FONT_FAMILY_NAME = "JetBrainsMono Nerd Font Mono";
|
|
8
17
|
export const FONT_FILE_NAME = "JetBrainsMonoNerdFontMono-Regular.ttf";
|
|
@@ -42,10 +51,13 @@ export class NerdFontController {
|
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
export async function isJetBrainsNerdFontInstalled() {
|
|
45
|
-
if (commandExists("brew")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
if (await deps.commandExists("brew")) {
|
|
55
|
+
const result = await deps.runProcess("brew", ["list", "--cask", CASK_NAME], { maxBufferBytes: 1024 });
|
|
56
|
+
if (result.status === 0)
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (process.platform === "linux" && await deps.commandExists("fc-match")) {
|
|
60
|
+
const result = await deps.runProcess("fc-match", ["-f", "%{family}", FONT_FAMILY_NAME], { maxBufferBytes: 1024 });
|
|
49
61
|
if (result.status === 0 && /JetBrains.*Nerd/iu.test(result.stdout))
|
|
50
62
|
return true;
|
|
51
63
|
}
|
|
@@ -57,13 +69,13 @@ export async function isJetBrainsNerdFontInstalled() {
|
|
|
57
69
|
return false;
|
|
58
70
|
}
|
|
59
71
|
export async function installJetBrainsNerdFont() {
|
|
60
|
-
if (process.platform === "darwin" && commandExists("brew")) {
|
|
72
|
+
if (process.platform === "darwin" && await deps.commandExists("brew")) {
|
|
61
73
|
await runBrewInstall();
|
|
62
74
|
return CASK_NAME;
|
|
63
75
|
}
|
|
64
76
|
const targetPath = userFontInstallPath();
|
|
65
|
-
await mkdir(dirname(targetPath), { recursive: true });
|
|
66
|
-
const response = await fetch(FONT_DOWNLOAD_URL, {
|
|
77
|
+
await deps.mkdir(dirname(targetPath), { recursive: true });
|
|
78
|
+
const response = await deps.fetch(FONT_DOWNLOAD_URL, {
|
|
67
79
|
headers: { "User-Agent": "pix-font-installer" },
|
|
68
80
|
signal: AbortSignal.timeout(30_000),
|
|
69
81
|
});
|
|
@@ -72,11 +84,11 @@ export async function installJetBrainsNerdFont() {
|
|
|
72
84
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
73
85
|
if (bytes.length < 100_000)
|
|
74
86
|
throw new Error("downloaded font is unexpectedly small");
|
|
75
|
-
await writeFile(targetPath, bytes);
|
|
87
|
+
await deps.writeFile(targetPath, bytes);
|
|
76
88
|
if (process.platform === "linux")
|
|
77
|
-
runOptionalCommand("fc-cache", ["-f", dirname(targetPath)]);
|
|
89
|
+
await runOptionalCommand("fc-cache", ["-f", dirname(targetPath)]);
|
|
78
90
|
if (process.platform === "win32")
|
|
79
|
-
registerWindowsUserFont(targetPath);
|
|
91
|
+
await registerWindowsUserFont(targetPath);
|
|
80
92
|
return targetPath;
|
|
81
93
|
}
|
|
82
94
|
export function userFontInstallPath() {
|
|
@@ -103,7 +115,7 @@ function platformFontDirs() {
|
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
async function directoryContainsFont(root) {
|
|
106
|
-
if (!existsSync(root))
|
|
118
|
+
if (!deps.existsSync(root))
|
|
107
119
|
return false;
|
|
108
120
|
const pending = [{ dir: root, depth: 0 }];
|
|
109
121
|
let scanned = 0;
|
|
@@ -113,7 +125,7 @@ async function directoryContainsFont(root) {
|
|
|
113
125
|
continue;
|
|
114
126
|
let entries;
|
|
115
127
|
try {
|
|
116
|
-
entries = await readdir(current.dir, { withFileTypes: true });
|
|
128
|
+
entries = await deps.readdir(current.dir, { withFileTypes: true });
|
|
117
129
|
}
|
|
118
130
|
catch {
|
|
119
131
|
continue;
|
|
@@ -130,7 +142,7 @@ async function directoryContainsFont(root) {
|
|
|
130
142
|
}
|
|
131
143
|
async function runBrewInstall() {
|
|
132
144
|
await new Promise((resolve, reject) => {
|
|
133
|
-
const child = spawn("brew", ["install", "--cask", CASK_NAME], {
|
|
145
|
+
const child = deps.spawn("brew", ["install", "--cask", CASK_NAME], {
|
|
134
146
|
env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: "1" },
|
|
135
147
|
stdio: ["ignore", "ignore", "pipe"],
|
|
136
148
|
});
|
|
@@ -147,10 +159,10 @@ async function runBrewInstall() {
|
|
|
147
159
|
});
|
|
148
160
|
});
|
|
149
161
|
}
|
|
150
|
-
function registerWindowsUserFont(fontPath) {
|
|
162
|
+
async function registerWindowsUserFont(fontPath) {
|
|
151
163
|
const escapedPath = fontPath.replaceAll("'", "''");
|
|
152
164
|
const escapedName = `${FONT_FAMILY_NAME} (TrueType)`.replaceAll("'", "''");
|
|
153
|
-
runOptionalCommand("powershell.exe", [
|
|
165
|
+
await runOptionalCommand("powershell.exe", [
|
|
154
166
|
"-NoProfile",
|
|
155
167
|
"-ExecutionPolicy",
|
|
156
168
|
"Bypass",
|
|
@@ -158,13 +170,8 @@ function registerWindowsUserFont(fontPath) {
|
|
|
158
170
|
`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
171
|
]);
|
|
160
172
|
}
|
|
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;
|
|
173
|
+
async function runOptionalCommand(command, args) {
|
|
174
|
+
await deps.runProcess(command, args, { maxBufferBytes: 1024 });
|
|
168
175
|
}
|
|
169
176
|
function errorMessage(error) {
|
|
170
177
|
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>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { runProcess } from "../process.js";
|
|
4
5
|
const UNDO_INDEX_VERSION = 1;
|
|
5
6
|
export function workspaceUndoIndexKey(_sessionFile, _sessionId, entryId) {
|
|
6
7
|
return entryId;
|
|
@@ -63,13 +64,13 @@ export function workspaceMutationFromToolExecution(input) {
|
|
|
63
64
|
}
|
|
64
65
|
return undefined;
|
|
65
66
|
}
|
|
66
|
-
export function revertWorkspaceMutations(cwd, mutations) {
|
|
67
|
+
export async function revertWorkspaceMutations(cwd, mutations) {
|
|
67
68
|
const changedFiles = new Set();
|
|
68
69
|
const applied = [];
|
|
69
70
|
for (const mutation of [...mutations].reverse()) {
|
|
70
|
-
const result = applyMutation(cwd, mutation, "undo");
|
|
71
|
+
const result = await applyMutation(cwd, mutation, "undo");
|
|
71
72
|
if (!result.ok) {
|
|
72
|
-
const rollback = rollbackMutations(cwd, applied);
|
|
73
|
+
const rollback = await rollbackMutations(cwd, applied);
|
|
73
74
|
const rollbackText = rollback.ok ? "Rolled back already-applied undo steps." : `Rollback failed: ${rollback.error}`;
|
|
74
75
|
return { ok: false, error: `${result.error}\n${rollbackText}` };
|
|
75
76
|
}
|
|
@@ -79,30 +80,30 @@ export function revertWorkspaceMutations(cwd, mutations) {
|
|
|
79
80
|
}
|
|
80
81
|
return { ok: true, changedFiles: changedFiles.size, revertedChanges: applied.length };
|
|
81
82
|
}
|
|
82
|
-
function rollbackMutations(cwd, appliedUndoMutations) {
|
|
83
|
+
async function rollbackMutations(cwd, appliedUndoMutations) {
|
|
83
84
|
for (const mutation of [...appliedUndoMutations].reverse()) {
|
|
84
|
-
const result = applyMutation(cwd, mutation, "redo");
|
|
85
|
+
const result = await applyMutation(cwd, mutation, "redo");
|
|
85
86
|
if (!result.ok)
|
|
86
87
|
return { ok: false, error: result.error };
|
|
87
88
|
}
|
|
88
89
|
return { ok: true, changedFiles: 0, revertedChanges: appliedUndoMutations.length };
|
|
89
90
|
}
|
|
90
|
-
function applyMutation(cwd, mutation, direction) {
|
|
91
|
+
async function applyMutation(cwd, mutation, direction) {
|
|
91
92
|
if (mutation.type === "patch")
|
|
92
93
|
return applyPatchMutation(cwd, mutation, direction);
|
|
93
94
|
return applyWriteMutation(cwd, mutation, direction);
|
|
94
95
|
}
|
|
95
|
-
function applyPatchMutation(cwd, mutation, direction) {
|
|
96
|
+
async function applyPatchMutation(cwd, mutation, direction) {
|
|
96
97
|
const args = ["apply", ...(direction === "undo" ? ["--reverse"] : []), "--whitespace=nowarn"];
|
|
97
|
-
const check = runGitApply(cwd, [...args, "--check"], mutation.patch);
|
|
98
|
+
const check = await runGitApply(cwd, [...args, "--check"], mutation.patch);
|
|
98
99
|
if (check.status !== 0)
|
|
99
100
|
return { ok: false, error: commandError(`git ${args.join(" ")} --check`, check) };
|
|
100
|
-
const apply = runGitApply(cwd, args, mutation.patch);
|
|
101
|
+
const apply = await runGitApply(cwd, args, mutation.patch);
|
|
101
102
|
if (apply.status !== 0)
|
|
102
103
|
return { ok: false, error: commandError(`git ${args.join(" ")}`, apply) };
|
|
103
104
|
return { ok: true, changedFiles: filesFromPatch(mutation.patch) };
|
|
104
105
|
}
|
|
105
|
-
function applyWriteMutation(cwd, mutation, direction) {
|
|
106
|
+
async function applyWriteMutation(cwd, mutation, direction) {
|
|
106
107
|
const safePath = safeRelativePath(cwd, mutation.path);
|
|
107
108
|
if (!safePath)
|
|
108
109
|
return { ok: false, error: `Refusing to modify path outside workspace: ${mutation.path}` };
|
|
@@ -110,17 +111,17 @@ function applyWriteMutation(cwd, mutation, direction) {
|
|
|
110
111
|
const expectedContent = direction === "undo" ? mutation.afterContent : mutation.beforeContent;
|
|
111
112
|
const nextContent = direction === "undo" ? mutation.beforeContent : mutation.afterContent;
|
|
112
113
|
const currentExists = existsSync(absolutePath);
|
|
113
|
-
const currentContent = currentExists ?
|
|
114
|
+
const currentContent = currentExists ? await readFile(absolutePath, "utf8") : undefined;
|
|
114
115
|
if (currentContent !== expectedContent) {
|
|
115
116
|
return { ok: false, error: `Refusing to ${direction} write for ${safePath}: file content changed since the recorded command.` };
|
|
116
117
|
}
|
|
117
118
|
if (nextContent === undefined) {
|
|
118
119
|
if (currentExists)
|
|
119
|
-
|
|
120
|
+
await rm(absolutePath, { force: true });
|
|
120
121
|
}
|
|
121
122
|
else {
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
124
|
+
await writeFile(absolutePath, nextContent, "utf8");
|
|
124
125
|
}
|
|
125
126
|
return { ok: true, changedFiles: [safePath] };
|
|
126
127
|
}
|
|
@@ -138,17 +139,18 @@ function parseUndoIndex(value) {
|
|
|
138
139
|
function workspaceUndoIndexPath(agentDir) {
|
|
139
140
|
return join(agentDir, "pix", "workspace-undo", "index.json");
|
|
140
141
|
}
|
|
141
|
-
function runGitApply(cwd, args, input) {
|
|
142
|
-
return
|
|
142
|
+
async function runGitApply(cwd, args, input) {
|
|
143
|
+
return runProcess("git", ["-c", "core.autocrlf=false", ...args], {
|
|
143
144
|
cwd,
|
|
144
145
|
input,
|
|
145
|
-
|
|
146
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
146
|
+
maxBufferBytes: 20 * 1024 * 1024,
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
function commandError(command, result) {
|
|
150
150
|
if (result.error)
|
|
151
151
|
return `${command} failed: ${result.error.message}`;
|
|
152
|
+
if (result.timedOut)
|
|
153
|
+
return `${command} timed out`;
|
|
152
154
|
const message = result.stderr?.trim() || result.stdout?.trim() || `exit code ${result.status ?? "unknown"}`;
|
|
153
155
|
return `${command} failed: ${message}`;
|
|
154
156
|
}
|