tabminal 2.0.13 → 2.0.15
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/ACP_PLANING.md +184 -0
- package/README.md +238 -105
- package/package.json +6 -4
- package/public/app.js +8481 -553
- package/public/index.html +150 -2
- package/public/styles.css +1977 -84
- package/shell/tabminal-hooks.bash +10 -0
- package/src/acp-manager.mjs +3469 -0
- package/src/acp-test-agent.mjs +691 -0
- package/src/persistence.mjs +153 -0
- package/src/server.mjs +300 -12
- package/src/terminal-manager.mjs +184 -73
- package/src/terminal-session.mjs +233 -15
package/src/terminal-session.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
const WS_STATE_OPEN = 1;
|
|
14
14
|
const DEFAULT_HISTORY_LIMIT = 512 * 1024; // chars
|
|
15
15
|
const OSC_SEQUENCE_REGEX =
|
|
16
|
-
/\u001b\]1337;(ExitCode=(\d+);CommandB64=([a-zA-Z0-9+/=]+)|TabminalPrompt)\u0007/g;
|
|
16
|
+
/\u001b\]1337;(ExitCode=(\d+);CommandB64=([a-zA-Z0-9+/=]+)|CommandStartB64=([a-zA-Z0-9+/=]+)|TabminalPrompt)\u0007/g;
|
|
17
17
|
const EXTRA_PRIVATE_MODE_REGEX = /\u001b\[\?(1005|1006|1015)([hl])/g;
|
|
18
18
|
const CSI_SEQUENCE_REGEX = /\u001b\[[0-9;?]*[ -\/]*[@-~]/g;
|
|
19
19
|
const OSC_STRIP_REGEX = /\u001b\][\s\S]*?(?:\u0007|\u001b\\)/g;
|
|
@@ -29,6 +29,13 @@ const IGNORED_COMMANDS = [
|
|
|
29
29
|
'TABMINAL_SHELL_READY=1'
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
+
function isIgnoredExecutionCommand(command) {
|
|
33
|
+
return !!(
|
|
34
|
+
command
|
|
35
|
+
&& IGNORED_COMMANDS.some((ignored) => command.includes(ignored))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
const PROMPT_PREFIX = "You are now operating as an AI terminal assistant. Your name is `Tabminal`. You will assist users in resolving terminal or coding issues and answering other inquiries. When troubleshooting terminal errors, you will be provided with the execution history to understand the context. However, please focus primarily on the most recent runtime errors and the user's latest questions. Keep your answers concise and accurate. Resolve the issue clearly and provide the reasoning while avoiding lengthy elaborations. Most user terminal variable keys are normal under typical circumstances and do not need to be treated as security risks.\n\n";
|
|
33
40
|
|
|
34
41
|
async function loadHeadlessXtermPackages() {
|
|
@@ -96,10 +103,17 @@ export class TerminalSession {
|
|
|
96
103
|
this.id = options.id;
|
|
97
104
|
this.manager = options.manager;
|
|
98
105
|
this.createdAt = options.createdAt ?? new Date();
|
|
106
|
+
this.updatedAt = this.createdAt;
|
|
99
107
|
this.shell = options.shell;
|
|
100
108
|
this.initialCwd = options.initialCwd;
|
|
101
|
-
|
|
102
|
-
this.
|
|
109
|
+
this.managed = options.managed || null;
|
|
110
|
+
this.persistent = options.persistent !== false;
|
|
111
|
+
this.removeOnExit = options.removeOnExit !== false;
|
|
112
|
+
this.enableAiHijack = options.enableAiHijack !== false;
|
|
113
|
+
this.enableTitlePolling = options.enableTitlePolling !== false;
|
|
114
|
+
|
|
115
|
+
this.title = options.title
|
|
116
|
+
|| (this.shell ? this.shell.split('/').pop() : 'Terminal');
|
|
103
117
|
this.cwd = this.initialCwd;
|
|
104
118
|
this.inputBuffer = '';
|
|
105
119
|
|
|
@@ -116,12 +130,20 @@ export class TerminalSession {
|
|
|
116
130
|
this.clients = new Set();
|
|
117
131
|
this.pendingClients = new Map();
|
|
118
132
|
this.closed = false;
|
|
133
|
+
this.exitStatus = null;
|
|
134
|
+
this.exitWaiters = [];
|
|
135
|
+
this.stateListeners = new Set();
|
|
119
136
|
this.pollingInterval = null;
|
|
120
137
|
this.captureBuffer = '';
|
|
121
138
|
this.captureStartedAt = null;
|
|
122
139
|
this.lastExecution = null;
|
|
140
|
+
this.executionCounter = 0;
|
|
141
|
+
this.currentExecutionId = '';
|
|
142
|
+
this.ignoreCurrentExecution = false;
|
|
123
143
|
this.skipNextShellLog = false;
|
|
144
|
+
this.skipNextShellLogResetTimer = null;
|
|
124
145
|
this.partialSequenceBuffer = '';
|
|
146
|
+
this.activeAiRun = null;
|
|
125
147
|
this.snapshotScrollback = estimateSnapshotScrollback(
|
|
126
148
|
this.pty.cols,
|
|
127
149
|
this.pty.rows,
|
|
@@ -150,7 +172,9 @@ export class TerminalSession {
|
|
|
150
172
|
const newTitle = s.substring(2);
|
|
151
173
|
if (newTitle && newTitle !== this.title) {
|
|
152
174
|
this.title = newTitle;
|
|
175
|
+
this.updatedAt = new Date();
|
|
153
176
|
this._broadcast({ type: 'meta', title: this.title, cwd: this.cwd, env: this.env, cols: this.pty.cols, rows: this.pty.rows });
|
|
177
|
+
this._emitStateChange();
|
|
154
178
|
}
|
|
155
179
|
} else if (s.startsWith('7;')) {
|
|
156
180
|
try {
|
|
@@ -159,7 +183,9 @@ export class TerminalSession {
|
|
|
159
183
|
const newCwd = decodeURIComponent(url.pathname);
|
|
160
184
|
if (newCwd !== this.cwd) {
|
|
161
185
|
this.cwd = newCwd;
|
|
186
|
+
this.updatedAt = new Date();
|
|
162
187
|
this._broadcast({ type: 'meta', title: this.title, cwd: this.cwd, env: this.env, cols: this.pty.cols, rows: this.pty.rows });
|
|
188
|
+
this._emitStateChange();
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
191
|
} catch { /* ignore */ }
|
|
@@ -196,6 +222,8 @@ export class TerminalSession {
|
|
|
196
222
|
const exitCodeStr = match[2];
|
|
197
223
|
const cmdB64 = match[3];
|
|
198
224
|
this._handleExitCodeSequence(exitCodeStr, cmdB64);
|
|
225
|
+
} else if (sequence.startsWith('CommandStartB64=')) {
|
|
226
|
+
this._handleCommandStartSequence(match[4]);
|
|
199
227
|
} else {
|
|
200
228
|
this._handlePromptMarker();
|
|
201
229
|
}
|
|
@@ -213,27 +241,43 @@ export class TerminalSession {
|
|
|
213
241
|
|
|
214
242
|
this._appendSnapshotData(cleaned);
|
|
215
243
|
this._appendHistory(cleaned);
|
|
244
|
+
this.updatedAt = new Date();
|
|
216
245
|
this.ansiParser.parse(cleaned);
|
|
217
246
|
if (this.manager?.scheduleSnapshotPersist) {
|
|
218
247
|
this.manager.scheduleSnapshotPersist(this.id);
|
|
219
248
|
}
|
|
220
249
|
this._broadcast({ type: 'output', data: cleaned });
|
|
250
|
+
this._emitStateChange();
|
|
221
251
|
};
|
|
222
252
|
|
|
223
253
|
this._handleExit = (details) => {
|
|
224
254
|
this.closed = true;
|
|
255
|
+
this.exitStatus = {
|
|
256
|
+
exitCode: Number.isFinite(details?.exitCode)
|
|
257
|
+
? details.exitCode
|
|
258
|
+
: null,
|
|
259
|
+
signal: details?.signal ?? null
|
|
260
|
+
};
|
|
261
|
+
this.updatedAt = new Date();
|
|
225
262
|
this.stopTitlePolling();
|
|
226
263
|
this._broadcast({
|
|
227
264
|
type: 'status',
|
|
228
265
|
status: 'terminated',
|
|
229
|
-
code:
|
|
230
|
-
signal:
|
|
266
|
+
code: this.exitStatus.exitCode ?? 0,
|
|
267
|
+
signal: this.exitStatus.signal
|
|
231
268
|
});
|
|
269
|
+
this._emitStateChange();
|
|
270
|
+
for (const waiter of this.exitWaiters) {
|
|
271
|
+
waiter(this.exitStatus);
|
|
272
|
+
}
|
|
273
|
+
this.exitWaiters.length = 0;
|
|
232
274
|
};
|
|
233
275
|
|
|
234
276
|
this.dataSubscription = this.pty.onData(this._handleData);
|
|
235
277
|
this.exitSubscription = this.pty.onExit(this._handleExit);
|
|
236
|
-
this.
|
|
278
|
+
if (this.enableTitlePolling) {
|
|
279
|
+
this.startTitlePolling();
|
|
280
|
+
}
|
|
237
281
|
}
|
|
238
282
|
|
|
239
283
|
startTitlePolling() {
|
|
@@ -321,7 +365,9 @@ export class TerminalSession {
|
|
|
321
365
|
if (titleChanged) this.title = newTitle;
|
|
322
366
|
if (envChanged) this.env = newEnv;
|
|
323
367
|
if (cwdChanged) this.cwd = newCwd;
|
|
368
|
+
this.updatedAt = new Date();
|
|
324
369
|
this._broadcast({ type: 'meta', title: this.title, cwd: this.cwd, env: this.env, cols: this.pty.cols, rows: this.pty.rows });
|
|
370
|
+
this._emitStateChange();
|
|
325
371
|
}
|
|
326
372
|
} catch { /* ignore */ }
|
|
327
373
|
};
|
|
@@ -352,6 +398,7 @@ export class TerminalSession {
|
|
|
352
398
|
|
|
353
399
|
dispose() {
|
|
354
400
|
this.stopTitlePolling();
|
|
401
|
+
this._clearSkipNextShellLogResetTimer();
|
|
355
402
|
this.clients.clear();
|
|
356
403
|
this.pendingClients.clear();
|
|
357
404
|
this.dataSubscription?.dispose?.();
|
|
@@ -369,11 +416,40 @@ export class TerminalSession {
|
|
|
369
416
|
this.manager.scheduleSnapshotPersist(this.id);
|
|
370
417
|
}
|
|
371
418
|
this._broadcast({ type: 'meta', title: this.title, cwd: this.cwd, env: this.env, cols, rows });
|
|
419
|
+
this.updatedAt = new Date();
|
|
420
|
+
this._emitStateChange();
|
|
372
421
|
if (this.manager && this.manager.saveSessionState) {
|
|
373
422
|
this.manager.saveSessionState(this);
|
|
374
423
|
}
|
|
375
424
|
}
|
|
376
425
|
|
|
426
|
+
waitForExit() {
|
|
427
|
+
if (this.exitStatus) {
|
|
428
|
+
return Promise.resolve(this.exitStatus);
|
|
429
|
+
}
|
|
430
|
+
return new Promise((resolve) => {
|
|
431
|
+
this.exitWaiters.push(resolve);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
onStateChange(listener) {
|
|
436
|
+
if (typeof listener !== 'function') return () => {};
|
|
437
|
+
this.stateListeners.add(listener);
|
|
438
|
+
return () => {
|
|
439
|
+
this.stateListeners.delete(listener);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_emitStateChange() {
|
|
444
|
+
for (const listener of this.stateListeners) {
|
|
445
|
+
try {
|
|
446
|
+
listener(this);
|
|
447
|
+
} catch {
|
|
448
|
+
// Ignore state listener failures.
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
377
453
|
async restoreSnapshot(snapshot) {
|
|
378
454
|
if (typeof snapshot !== 'string' || !snapshot) return;
|
|
379
455
|
this.history = snapshot;
|
|
@@ -416,6 +492,11 @@ export class TerminalSession {
|
|
|
416
492
|
}
|
|
417
493
|
|
|
418
494
|
write(data) {
|
|
495
|
+
if (this.activeAiRun && typeof data === 'string') {
|
|
496
|
+
this._handleInputDuringAi(data);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
419
500
|
if (typeof data === 'string' && data.startsWith('\x1b')) {
|
|
420
501
|
this.pty.write(data);
|
|
421
502
|
this.inputBuffer = '';
|
|
@@ -428,7 +509,7 @@ export class TerminalSession {
|
|
|
428
509
|
}
|
|
429
510
|
|
|
430
511
|
let startIndex = 0;
|
|
431
|
-
const aiEnabled = this._isAiEnabled();
|
|
512
|
+
const aiEnabled = this.enableAiHijack && this._isAiEnabled();
|
|
432
513
|
for (let i = 0; i < data.length; i++) {
|
|
433
514
|
const char = data[i];
|
|
434
515
|
|
|
@@ -503,6 +584,19 @@ export class TerminalSession {
|
|
|
503
584
|
}
|
|
504
585
|
}
|
|
505
586
|
|
|
587
|
+
_handleInputDuringAi(data) {
|
|
588
|
+
for (const char of data) {
|
|
589
|
+
if (char === '\x03') {
|
|
590
|
+
this._cancelActiveAiRun('ctrl_c');
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (char === '\x04') {
|
|
594
|
+
this._cancelActiveAiRun('ctrl_d');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
506
600
|
_buildAiContext(history) {
|
|
507
601
|
let pendingShellHistory = '';
|
|
508
602
|
const conversationHistory = [];
|
|
@@ -526,9 +620,67 @@ export class TerminalSession {
|
|
|
526
620
|
return { conversationHistory, pendingShellHistory };
|
|
527
621
|
}
|
|
528
622
|
|
|
623
|
+
_promptAi(prompt, options) {
|
|
624
|
+
return alan.prompt(prompt, options);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
_clearSkipNextShellLogResetTimer() {
|
|
628
|
+
if (this.skipNextShellLogResetTimer) {
|
|
629
|
+
clearTimeout(this.skipNextShellLogResetTimer);
|
|
630
|
+
this.skipNextShellLogResetTimer = null;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
_scheduleSkipNextShellLogReset() {
|
|
635
|
+
this._clearSkipNextShellLogResetTimer();
|
|
636
|
+
this.skipNextShellLogResetTimer = setTimeout(() => {
|
|
637
|
+
this.skipNextShellLog = false;
|
|
638
|
+
this.skipNextShellLogResetTimer = null;
|
|
639
|
+
}, 500);
|
|
640
|
+
this.skipNextShellLogResetTimer.unref?.();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
_finishAiInteraction(mode = 'normal') {
|
|
644
|
+
this.suppressPtyOutput = false;
|
|
645
|
+
this._scheduleSkipNextShellLogReset();
|
|
646
|
+
|
|
647
|
+
if (mode === 'ctrl_c') {
|
|
648
|
+
this._writeToLogAndBroadcast('\x1b[0m');
|
|
649
|
+
this.pty.write('\x03');
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
this._writeToLogAndBroadcast('\x1b[0m\r\n');
|
|
654
|
+
if (mode === 'ctrl_d') {
|
|
655
|
+
this.pty.write('\x15');
|
|
656
|
+
}
|
|
657
|
+
this.pty.write('\r');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
_cancelActiveAiRun(reason) {
|
|
661
|
+
const run = this.activeAiRun;
|
|
662
|
+
if (!run || run.cancelled) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
run.cancelled = true;
|
|
667
|
+
this.activeAiRun = null;
|
|
668
|
+
this._logCommandExecution({
|
|
669
|
+
command: 'ai',
|
|
670
|
+
exitCode: 130,
|
|
671
|
+
input: run.prompt,
|
|
672
|
+
output: 'AI generation cancelled.',
|
|
673
|
+
startedAt: run.startedAt,
|
|
674
|
+
completedAt: new Date()
|
|
675
|
+
});
|
|
676
|
+
this._finishAiInteraction(reason);
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
|
|
529
680
|
async _handleAiCommand(prompt) {
|
|
530
681
|
// Prevent duplicate logging from shell integration
|
|
531
682
|
this.skipNextShellLog = true;
|
|
683
|
+
this._clearSkipNextShellLogResetTimer();
|
|
532
684
|
// Ensure clean line start and set Cyan color (No prefix yet)
|
|
533
685
|
this._writeToLogAndBroadcast('\r\x1b[K\x1b[36m');
|
|
534
686
|
// Gather Context (Current Session Only)
|
|
@@ -546,8 +698,17 @@ export class TerminalSession {
|
|
|
546
698
|
const startTime = new Date();
|
|
547
699
|
let fullResponse = '';
|
|
548
700
|
let isFirstChunk = true;
|
|
701
|
+
const run = {
|
|
702
|
+
prompt,
|
|
703
|
+
startedAt: startTime,
|
|
704
|
+
cancelled: false
|
|
705
|
+
};
|
|
706
|
+
this.activeAiRun = run;
|
|
549
707
|
try {
|
|
550
708
|
const streamCallback = (chunk) => {
|
|
709
|
+
if (this.activeAiRun !== run || run.cancelled) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
551
712
|
// console.log('Chunk Received:');
|
|
552
713
|
// console.log(chunk);
|
|
553
714
|
if (chunk && chunk.text) {
|
|
@@ -563,13 +724,17 @@ export class TerminalSession {
|
|
|
563
724
|
}
|
|
564
725
|
};
|
|
565
726
|
// console.log('Start AI Prompt...');
|
|
566
|
-
const result = await
|
|
727
|
+
const result = await this._promptAi(finalPrompt, {
|
|
567
728
|
stream: streamCallback,
|
|
568
729
|
delta: true,
|
|
569
730
|
messages: conversationHistory,
|
|
570
731
|
trimBeginning: true
|
|
571
732
|
});
|
|
572
733
|
|
|
734
|
+
if (this.activeAiRun !== run || run.cancelled) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
573
738
|
if (result && result.text) {
|
|
574
739
|
fullResponse = result.text;
|
|
575
740
|
}
|
|
@@ -588,6 +753,9 @@ export class TerminalSession {
|
|
|
588
753
|
});
|
|
589
754
|
|
|
590
755
|
} catch (e) {
|
|
756
|
+
if (this.activeAiRun !== run || run.cancelled) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
591
759
|
this._writeToLogAndBroadcast(`\x1b[31mAI Error: ${e.message}\x1b[0m\r\n`);
|
|
592
760
|
|
|
593
761
|
this._logCommandExecution({
|
|
@@ -598,13 +766,12 @@ export class TerminalSession {
|
|
|
598
766
|
startedAt: startTime,
|
|
599
767
|
completedAt: new Date()
|
|
600
768
|
});
|
|
769
|
+
} finally {
|
|
770
|
+
if (this.activeAiRun === run) {
|
|
771
|
+
this.activeAiRun = null;
|
|
772
|
+
this._finishAiInteraction('normal');
|
|
773
|
+
}
|
|
601
774
|
}
|
|
602
|
-
|
|
603
|
-
// Resume PTY output
|
|
604
|
-
this.suppressPtyOutput = false;
|
|
605
|
-
|
|
606
|
-
// Restore prompt by sending \r to pty (empty command)
|
|
607
|
-
this.pty.write('\r');
|
|
608
775
|
}
|
|
609
776
|
|
|
610
777
|
_handleResize(cols, rows) {
|
|
@@ -632,13 +799,43 @@ export class TerminalSession {
|
|
|
632
799
|
}
|
|
633
800
|
|
|
634
801
|
_handlePromptMarker() {
|
|
802
|
+
if (this.currentExecutionId && !this.ignoreCurrentExecution) {
|
|
803
|
+
this._broadcast({
|
|
804
|
+
type: 'execution',
|
|
805
|
+
phase: 'idle',
|
|
806
|
+
executionId: this.currentExecutionId
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
this.currentExecutionId = '';
|
|
810
|
+
this.ignoreCurrentExecution = false;
|
|
635
811
|
this.captureBuffer = '';
|
|
636
812
|
this.captureStartedAt = null;
|
|
637
813
|
}
|
|
638
814
|
|
|
815
|
+
_handleCommandStartSequence(cmdB64) {
|
|
816
|
+
const command = this._decodeCommandSafe(cmdB64);
|
|
817
|
+
const startedAt = new Date();
|
|
818
|
+
this.captureStartedAt = startedAt;
|
|
819
|
+
this.ignoreCurrentExecution = isIgnoredExecutionCommand(command);
|
|
820
|
+
if (this.ignoreCurrentExecution) {
|
|
821
|
+
this.currentExecutionId = '';
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
this.executionCounter += 1;
|
|
825
|
+
this.currentExecutionId = `exec-${this.executionCounter}`;
|
|
826
|
+
this._broadcast({
|
|
827
|
+
type: 'execution',
|
|
828
|
+
phase: 'started',
|
|
829
|
+
executionId: this.currentExecutionId,
|
|
830
|
+
command,
|
|
831
|
+
startedAt
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
639
835
|
_handleExitCodeSequence(exitCodeStr, cmdB64) {
|
|
640
836
|
if (this.skipNextShellLog) {
|
|
641
837
|
this.skipNextShellLog = false;
|
|
838
|
+
this._clearSkipNextShellLogResetTimer();
|
|
642
839
|
this.captureBuffer = '';
|
|
643
840
|
this.captureStartedAt = null;
|
|
644
841
|
return;
|
|
@@ -646,6 +843,10 @@ export class TerminalSession {
|
|
|
646
843
|
|
|
647
844
|
const exitCode = Number.parseInt(exitCodeStr, 10);
|
|
648
845
|
const command = this._decodeCommandSafe(cmdB64);
|
|
846
|
+
const executionId = this.currentExecutionId
|
|
847
|
+
|| `exec-${++this.executionCounter}`;
|
|
848
|
+
const isIgnored = this.ignoreCurrentExecution
|
|
849
|
+
|| isIgnoredExecutionCommand(command);
|
|
649
850
|
|
|
650
851
|
const completedAt = new Date();
|
|
651
852
|
const entry = this._postProcessExecutionEntry({
|
|
@@ -659,9 +860,22 @@ export class TerminalSession {
|
|
|
659
860
|
});
|
|
660
861
|
|
|
661
862
|
this.lastExecution = entry;
|
|
863
|
+
this.currentExecutionId = '';
|
|
864
|
+
this.ignoreCurrentExecution = false;
|
|
865
|
+
if (isIgnored) {
|
|
866
|
+
this.captureBuffer = '';
|
|
867
|
+
this.captureStartedAt = null;
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
662
870
|
this._logCommandExecution(entry);
|
|
663
871
|
this.captureBuffer = '';
|
|
664
872
|
this.captureStartedAt = null;
|
|
873
|
+
this._broadcast({
|
|
874
|
+
type: 'execution',
|
|
875
|
+
phase: 'completed',
|
|
876
|
+
executionId,
|
|
877
|
+
entry
|
|
878
|
+
});
|
|
665
879
|
|
|
666
880
|
// Auto-Fix: If command failed, ask AI for help
|
|
667
881
|
if (exitCode !== 0 && entry.command && this._isAiEnabled()) {
|
|
@@ -970,7 +1184,7 @@ export class TerminalSession {
|
|
|
970
1184
|
|
|
971
1185
|
_logCommandExecution(entry) {
|
|
972
1186
|
// Filter out internal shell integration commands
|
|
973
|
-
if (
|
|
1187
|
+
if (isIgnoredExecutionCommand(entry.command)) {
|
|
974
1188
|
return;
|
|
975
1189
|
}
|
|
976
1190
|
|
|
@@ -1001,6 +1215,8 @@ export class TerminalSession {
|
|
|
1001
1215
|
if (this.manager) {
|
|
1002
1216
|
this.manager.saveSessionState(this);
|
|
1003
1217
|
}
|
|
1218
|
+
this.updatedAt = new Date();
|
|
1219
|
+
this._emitStateChange();
|
|
1004
1220
|
}
|
|
1005
1221
|
|
|
1006
1222
|
_broadcast(message) {
|
|
@@ -1022,10 +1238,12 @@ export class TerminalSession {
|
|
|
1022
1238
|
if (!text) return;
|
|
1023
1239
|
this._appendSnapshotData(text);
|
|
1024
1240
|
this._appendHistory(text);
|
|
1241
|
+
this.updatedAt = new Date();
|
|
1025
1242
|
if (this.manager?.scheduleSnapshotPersist) {
|
|
1026
1243
|
this.manager.scheduleSnapshotPersist(this.id);
|
|
1027
1244
|
}
|
|
1028
1245
|
this._broadcast({ type: 'output', data: text });
|
|
1246
|
+
this._emitStateChange();
|
|
1029
1247
|
}
|
|
1030
1248
|
|
|
1031
1249
|
_queueSnapshotMutation(mutate) {
|