loadtoagent 1.3.3 → 1.3.4
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/main.js +53 -8
- package/package.json +2 -1
- package/renderer/app-agent-actions.js +172 -66
- package/renderer/app-bootstrap.js +3 -1
- package/renderer/app-dashboard.js +36 -7
- package/renderer/app-drawer-content.js +177 -59
- package/renderer/app-drawer-data.js +7 -3
- package/renderer/app-drawer.js +72 -33
- package/renderer/app-events-dialogs.js +8 -1
- package/renderer/app-events-navigation.js +7 -0
- package/renderer/app-events-sessions.js +136 -13
- package/renderer/app-graph-model.js +2 -5
- package/renderer/app-graph-orchestration.js +7 -18
- package/renderer/app-graph-view.js +194 -48
- package/renderer/app-management.js +330 -35
- package/renderer/app-quality.js +5 -0
- package/renderer/app-session-render.js +20 -83
- package/renderer/app.js +5 -1
- package/renderer/i18n-messages.js +232 -21
- package/renderer/index.html +9 -6
- package/renderer/styles-components.css +140 -0
- package/renderer/styles-control-room.css +1439 -0
- package/renderer/styles-management.css +390 -3
- package/renderer/styles-responsive-shell.css +5 -0
- package/renderer/styles-runtime-overview.css +20 -0
- package/renderer/terminal-agent.js +31 -7
- package/renderer/terminal.js +4 -1
- package/src/agentMonitor/claudeParser.js +8 -4
- package/src/agentMonitor/codexParser.js +5 -4
- package/src/agentMonitor/genericParser.js +7 -6
- package/src/agentMonitor.js +44 -4
- package/src/attentionNotifier.js +3 -0
- package/src/ipc/registerTerminalIpc.js +2 -2
- package/src/monitorWorker.js +1 -1
- package/src/processMonitor.js +68 -7
- package/src/terminalManager.js +16 -2
|
@@ -60,19 +60,20 @@ function createGenericParser(dependencies) {
|
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function readSessionFile(fileInfo) {
|
|
63
|
+
function readSessionFile(fileInfo, options = {}) {
|
|
64
64
|
const isJsonl = /\.jsonl$/i.test(fileInfo.file);
|
|
65
65
|
return isJsonl
|
|
66
|
-
? readJsonLines(fileInfo.file)
|
|
66
|
+
? readJsonLines(fileInfo.file, options.fullHistory ? Math.max(1, Number(fileInfo.size || 0) + 1) : undefined)
|
|
67
67
|
: { rows: [readJson(fileInfo.file, {})], truncated: false };
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function initializeSession(fileInfo, provider, parsed) {
|
|
70
|
+
function initializeSession(fileInfo, provider, parsed, options = {}) {
|
|
71
71
|
const rows = parsed.rows.filter(Boolean);
|
|
72
72
|
const root = rows.length === 1 ? rows[0] : { events: rows };
|
|
73
73
|
const externalId = root.session_id || root.sessionId || root.id
|
|
74
74
|
|| path.basename(fileInfo.file).replace(/\.(jsonl?|ndjson)$/i, '');
|
|
75
75
|
const session = baseSession(provider, externalId, fileInfo.file, fileInfo);
|
|
76
|
+
session.fullHistory = Boolean(options.fullHistory);
|
|
76
77
|
session.truncated = parsed.truncated;
|
|
77
78
|
session.cwd = root.cwd || root.projectPath || root.project_path || '';
|
|
78
79
|
session.originCwd = session.cwd;
|
|
@@ -264,9 +265,9 @@ function createGenericParser(dependencies) {
|
|
|
264
265
|
return session;
|
|
265
266
|
}
|
|
266
267
|
|
|
267
|
-
return function parseGeneric(fileInfo, provider) {
|
|
268
|
-
const parsed = readSessionFile(fileInfo);
|
|
269
|
-
const initialized = initializeSession(fileInfo, provider, parsed);
|
|
268
|
+
return function parseGeneric(fileInfo, provider, options = {}) {
|
|
269
|
+
const parsed = readSessionFile(fileInfo, options);
|
|
270
|
+
const initialized = initializeSession(fileInfo, provider, parsed, options);
|
|
270
271
|
if (!initialized.rows.length) return null;
|
|
271
272
|
const events = initialized.rows.length === 1 && Array.isArray(initialized.root.events)
|
|
272
273
|
? initialized.root.events
|
package/src/agentMonitor.js
CHANGED
|
@@ -56,7 +56,7 @@ function timestamp(value, fallback = null) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function addMessage(session, message) {
|
|
59
|
-
const text = compactText(message.text, message.type === 'tool' ? 1600 : 6000);
|
|
59
|
+
const text = compactText(message.text, session.fullHistory ? Number.MAX_SAFE_INTEGER : (message.type === 'tool' ? 1600 : 6000));
|
|
60
60
|
if (!text && message.type !== 'tool') return;
|
|
61
61
|
const row = {
|
|
62
62
|
id: String(message.id || `${session.id}:m:${session.messages.length}`),
|
|
@@ -353,6 +353,13 @@ function contextInfo(used, windowInfo) {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
function trimSession(session) {
|
|
356
|
+
if (session.fullHistory) {
|
|
357
|
+
session.omittedMessages = 0;
|
|
358
|
+
session.omittedLifecycle = 0;
|
|
359
|
+
session.truncated = false;
|
|
360
|
+
if (!session.messages.length) addMessage(session, { id: `${session.id}:empty`, role: 'system', text: '표시할 대화 메시지가 아직 없습니다.', timestamp: session.updatedAt });
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
356
363
|
session.omittedMessages = Math.max(0, session.messages.length - MAX_MESSAGES);
|
|
357
364
|
session.omittedLifecycle = Math.max(0, session.lifecycle.length - MAX_LIFECYCLE);
|
|
358
365
|
session.messages = session.messages.slice(-MAX_MESSAGES);
|
|
@@ -360,7 +367,7 @@ function trimSession(session) {
|
|
|
360
367
|
if (!session.messages.length) addMessage(session, { id: `${session.id}:empty`, role: 'system', text: '표시할 대화 메시지가 아직 없습니다.', timestamp: session.updatedAt });
|
|
361
368
|
}
|
|
362
369
|
|
|
363
|
-
function parseManagedSession(runDir) {
|
|
370
|
+
function parseManagedSession(runDir, options = {}) {
|
|
364
371
|
const meta = readJson(path.join(runDir, 'meta.json'));
|
|
365
372
|
const live = readJson(path.join(runDir, 'session.json'));
|
|
366
373
|
if (!meta || !live) return null;
|
|
@@ -373,6 +380,7 @@ function parseManagedSession(runDir) {
|
|
|
373
380
|
source: 'loadtoagent',
|
|
374
381
|
sourceLabel: 'LoadToAgent 실행',
|
|
375
382
|
statusObserved: true,
|
|
383
|
+
fullHistory: Boolean(options.fullHistory),
|
|
376
384
|
};
|
|
377
385
|
session.originCwd = session.originCwd || meta.cwd || session.cwd || '';
|
|
378
386
|
session.usage = finalizeUsage(session.usage);
|
|
@@ -482,8 +490,8 @@ class AgentMonitor extends EventEmitter {
|
|
|
482
490
|
}).filter(Boolean).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, max);
|
|
483
491
|
}
|
|
484
492
|
|
|
485
|
-
parseFile(info, parser) {
|
|
486
|
-
const key = `${info.file}|${info.mtimeMs}|${info.size}`;
|
|
493
|
+
parseFile(info, parser, variant = '') {
|
|
494
|
+
const key = `${info.file}|${info.mtimeMs}|${info.size}|${variant}`;
|
|
487
495
|
const cached = this.parseCache.get(key);
|
|
488
496
|
if (cached) return cached;
|
|
489
497
|
const value = parser(info);
|
|
@@ -495,6 +503,38 @@ class AgentMonitor extends EventEmitter {
|
|
|
495
503
|
return value;
|
|
496
504
|
}
|
|
497
505
|
|
|
506
|
+
detailSession(sessionId) {
|
|
507
|
+
const stored = (this.lastSnapshot.sessions || []).find(session => session.id === String(sessionId || '')) || null;
|
|
508
|
+
if (!stored) return null;
|
|
509
|
+
let detailed = null;
|
|
510
|
+
if (stored.source === 'loadtoagent' && stored.runId && this.runsDir) {
|
|
511
|
+
detailed = parseManagedSession(path.join(this.runsDir, stored.runId), { fullHistory: true });
|
|
512
|
+
} else if (stored.file) {
|
|
513
|
+
const stat = safeStat(stored.file);
|
|
514
|
+
if (stat && stat.isFile()) {
|
|
515
|
+
const info = { file: stored.file, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
516
|
+
const parser = stored.provider === 'claude'
|
|
517
|
+
? item => parseClaude(item, { fullHistory: true })
|
|
518
|
+
: stored.provider === 'codex'
|
|
519
|
+
? item => parseCodex(item, { fullHistory: true })
|
|
520
|
+
: item => parseGeneric(item, stored.provider, { fullHistory: true });
|
|
521
|
+
detailed = this.parseFile(info, parser, 'full-history');
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (!detailed) return stored;
|
|
525
|
+
return {
|
|
526
|
+
...stored,
|
|
527
|
+
...detailed,
|
|
528
|
+
environment: stored.environment,
|
|
529
|
+
delegation: stored.delegation || detailed.delegation,
|
|
530
|
+
childIds: stored.childIds || detailed.childIds || [],
|
|
531
|
+
fullHistory: true,
|
|
532
|
+
truncated: false,
|
|
533
|
+
omittedMessages: 0,
|
|
534
|
+
omittedLifecycle: 0,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
498
538
|
hintedFiles(paths, max) {
|
|
499
539
|
return (paths || []).map(value => {
|
|
500
540
|
if (value && typeof value === 'object' && value.file && Number.isFinite(value.mtimeMs) && Number.isFinite(value.size)) {
|
package/src/attentionNotifier.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
class AttentionNotifier {
|
|
4
4
|
constructor(options = {}) {
|
|
5
|
+
this.enabled = options.enabled !== false;
|
|
5
6
|
this.Notification = options.Notification;
|
|
6
7
|
this.isSupported = options.isSupported || (() => Boolean(this.Notification));
|
|
7
8
|
this.copy = options.copy || (() => ({ title: '내 확인이 필요합니다', body: '응답이나 선택을 기다리는 AI 세션이 있습니다.' }));
|
|
@@ -12,6 +13,7 @@ class AttentionNotifier {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
sync(snapshot) {
|
|
16
|
+
if (!this.enabled) return [];
|
|
15
17
|
const needsAttention = (snapshot && Array.isArray(snapshot.sessions) ? snapshot.sessions : [])
|
|
16
18
|
.filter(session => session && session.id && (
|
|
17
19
|
session.attention?.required
|
|
@@ -32,6 +34,7 @@ class AttentionNotifier {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
notify(session) {
|
|
37
|
+
if (!this.enabled) return null;
|
|
35
38
|
let supported = false;
|
|
36
39
|
try {
|
|
37
40
|
supported = Boolean(this.Notification && this.isSupported());
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
function registerTerminalIpc({ ipcMain, requireTrustedSender, trustedSender, manager, isProviderVisible = () => true, listWslDistros, sendError }) {
|
|
4
4
|
ipcMain.handle('terminals:list', event => {
|
|
5
5
|
requireTrustedSender(event);
|
|
6
|
-
return manager() ? manager().list().filter(session => session.type !== 'agent' || isProviderVisible(session.provider)) : [];
|
|
6
|
+
return manager() ? manager().list().filter(session => !session.transient && (session.type !== 'agent' || isProviderVisible(session.provider))) : [];
|
|
7
7
|
});
|
|
8
8
|
ipcMain.handle('wsl:list-distros', event => {
|
|
9
9
|
requireTrustedSender(event);
|
|
@@ -12,7 +12,7 @@ function registerTerminalIpc({ ipcMain, requireTrustedSender, trustedSender, man
|
|
|
12
12
|
ipcMain.handle('terminals:get', async (event, id) => {
|
|
13
13
|
requireTrustedSender(event);
|
|
14
14
|
const session = manager() ? await manager().get(id, true) : null;
|
|
15
|
-
return session && session.type === 'agent' && !isProviderVisible(session.provider) ? null : session;
|
|
15
|
+
return session && (session.transient || (session.type === 'agent' && !isProviderVisible(session.provider))) ? null : session;
|
|
16
16
|
});
|
|
17
17
|
ipcMain.handle('terminals:create', (event, options) => {
|
|
18
18
|
requireTrustedSender(event);
|
package/src/monitorWorker.js
CHANGED
|
@@ -317,7 +317,7 @@ parentPort.on('message', message => {
|
|
|
317
317
|
}
|
|
318
318
|
if (message.type === 'detail') {
|
|
319
319
|
const runtime = lastPublishedSessions.find(item => item.id === message.sessionId) || null;
|
|
320
|
-
const stored =
|
|
320
|
+
const stored = monitor.detailSession(message.sessionId);
|
|
321
321
|
const merged = stored && runtime
|
|
322
322
|
? { ...stored, status: runtime.status, statusDetail: runtime.statusDetail, statusObserved: runtime.statusObserved, runtimePresence: runtime.runtimePresence || [] }
|
|
323
323
|
: (stored || runtime);
|
package/src/processMonitor.js
CHANGED
|
@@ -5,6 +5,18 @@ const { blankUsage } = require('./providerRegistry');
|
|
|
5
5
|
|
|
6
6
|
const DEFAULT_SCAN_TTL_MS = 12_000;
|
|
7
7
|
const WMIC_QUERY = "Name='claude.exe' or Name='codex.exe' or Name='node.exe' or Name='gemini.exe' or Name='grok.exe'";
|
|
8
|
+
const WINDOWS_PROCESS_SCRIPT = [
|
|
9
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
10
|
+
"$ErrorActionPreference = 'Stop'",
|
|
11
|
+
'[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)',
|
|
12
|
+
"$names = @('claude.exe','codex.exe','node.exe','gemini.exe','grok.exe')",
|
|
13
|
+
'$rows = Get-CimInstance Win32_Process | Where-Object { $names -contains $_.Name } | ForEach-Object {',
|
|
14
|
+
" $started = if ($_.CreationDate) { $_.CreationDate.ToUniversalTime().ToString('o') } else { $null }",
|
|
15
|
+
' [pscustomobject]@{ pid = [int]$_.ProcessId; parentPid = [int]$_.ParentProcessId; name = [string]$_.Name; commandLine = [string]$_.CommandLine; startedAt = $started }',
|
|
16
|
+
'}',
|
|
17
|
+
'@($rows) | ConvertTo-Json -Compress',
|
|
18
|
+
].join('; ');
|
|
19
|
+
const WINDOWS_PROCESS_SCRIPT_BASE64 = Buffer.from(WINDOWS_PROCESS_SCRIPT, 'utf16le').toString('base64');
|
|
8
20
|
|
|
9
21
|
function parseCsvRows(value) {
|
|
10
22
|
const text = Buffer.isBuffer(value) ? value.toString('utf8') : String(value || '');
|
|
@@ -131,6 +143,42 @@ function processRows(value) {
|
|
|
131
143
|
})).filter(item => item.pid > 0);
|
|
132
144
|
}
|
|
133
145
|
|
|
146
|
+
function powershellProcessRows(value) {
|
|
147
|
+
const text = (Buffer.isBuffer(value) ? value.toString('utf8') : String(value || '')).replace(/^\uFEFF/, '').trim();
|
|
148
|
+
if (!text) return [];
|
|
149
|
+
const parsed = JSON.parse(text);
|
|
150
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).map(row => {
|
|
151
|
+
const timestamp = Date.parse(row.startedAt || '');
|
|
152
|
+
return {
|
|
153
|
+
pid: Number(row.pid || 0),
|
|
154
|
+
parentPid: Number(row.parentPid || 0),
|
|
155
|
+
name: String(row.name || ''),
|
|
156
|
+
commandLine: String(row.commandLine || ''),
|
|
157
|
+
startedAt: Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null,
|
|
158
|
+
};
|
|
159
|
+
}).filter(item => item.pid > 0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function windowsProcessRows(run = execFileSync) {
|
|
163
|
+
try {
|
|
164
|
+
const output = run('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', WINDOWS_PROCESS_SCRIPT_BASE64], {
|
|
165
|
+
encoding: 'utf8', windowsHide: true, timeout: 8_000, maxBuffer: 4 * 1024 * 1024,
|
|
166
|
+
});
|
|
167
|
+
return powershellProcessRows(output);
|
|
168
|
+
} catch (powershellError) {
|
|
169
|
+
try {
|
|
170
|
+
const output = run('wmic.exe', ['process', 'where', WMIC_QUERY, 'get', 'ProcessId,ParentProcessId,CreationDate,Name,CommandLine', '/format:csv'], {
|
|
171
|
+
encoding: 'utf8', windowsHide: true, timeout: 8_000, maxBuffer: 2 * 1024 * 1024,
|
|
172
|
+
});
|
|
173
|
+
return processRows(output);
|
|
174
|
+
} catch (wmicError) {
|
|
175
|
+
const error = new Error(`Windows 프로세스 조회 실패: ${powershellError.message || powershellError}; ${wmicError.message || wmicError}`);
|
|
176
|
+
error.cause = powershellError;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
134
182
|
function commandArgument(commandLine, name) {
|
|
135
183
|
const escaped = String(name || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
136
184
|
const match = String(commandLine || '').match(new RegExp(`(?:^|\\s)${escaped}\\s+(?:"([^"]+)"|'([^']+)'|(\\S+))`, 'i'));
|
|
@@ -151,6 +199,14 @@ function processSessionExternalId(processInfo = {}, provider = '') {
|
|
|
151
199
|
return '';
|
|
152
200
|
}
|
|
153
201
|
|
|
202
|
+
function processInteractionMode(processInfo = {}, provider = '') {
|
|
203
|
+
const commandLine = String(processInfo.commandLine || processInfo.CommandLine || '');
|
|
204
|
+
if (provider === 'claude' && /(?:^|\s)(?:-p|--print)(?:\s|$)/i.test(commandLine)) return 'batch';
|
|
205
|
+
if (provider === 'codex' && /(?:^|\s)exec(?:\s|$)/i.test(commandLine)) return 'batch';
|
|
206
|
+
if (provider === 'gemini' && /(?:^|\s)(?:-p|--prompt)(?:\s|$)/i.test(commandLine)) return 'batch';
|
|
207
|
+
return 'interactive';
|
|
208
|
+
}
|
|
209
|
+
|
|
154
210
|
function pathlessSessionId(value) {
|
|
155
211
|
const text = String(value || '').trim().replace(/^"|"$/g, '');
|
|
156
212
|
if (!text) return '';
|
|
@@ -192,14 +248,13 @@ function selectAgentProcesses(rows, options = {}) {
|
|
|
192
248
|
command: String(item.name || '').replace(/\.exe$/i, '').split(/[\\/]/).pop(),
|
|
193
249
|
startedAt: item.startedAt,
|
|
194
250
|
externalId: processSessionExternalId(item, item.provider),
|
|
251
|
+
interactionMode: processInteractionMode(item, item.provider),
|
|
195
252
|
})).sort((a, b) => a.provider.localeCompare(b.provider) || a.pid - b.pid);
|
|
196
253
|
}
|
|
197
254
|
|
|
198
255
|
function utilityProcess(processInfo = {}) {
|
|
199
256
|
const commandLine = String(processInfo.commandLine || processInfo.args || '');
|
|
200
|
-
return /Extract durable memory candidates from this Claude Code transcript tail/i.test(commandLine)
|
|
201
|
-
|| /Reply with exactly OK\. Do not use tools\.?/i.test(commandLine)
|
|
202
|
-
|| /You are a memory extraction/i.test(commandLine);
|
|
257
|
+
return /(?:^|\s)(?:-p|--print)\s+["']?(?:Extract durable memory candidates from this Claude Code transcript tail|Reply with exactly OK\. Do not use tools\.?|You are a memory extraction)/i.test(commandLine);
|
|
203
258
|
}
|
|
204
259
|
|
|
205
260
|
function utilitySession(session) {
|
|
@@ -230,6 +285,12 @@ function markRuntime(session, presence) {
|
|
|
230
285
|
const existing = Array.isArray(session.runtimePresence) ? session.runtimePresence : [];
|
|
231
286
|
if (!existing.some(item => item.id === presence.id)) existing.push(presence);
|
|
232
287
|
session.runtimePresence = existing;
|
|
288
|
+
if (presence.interactionMode === 'batch') {
|
|
289
|
+
session.conversationStatus = session.status;
|
|
290
|
+
session.status = 'running';
|
|
291
|
+
session.statusDetail = `백그라운드 자율 실행 중 · PID ${presence.pid || '--'}`;
|
|
292
|
+
session.statusObserved = true;
|
|
293
|
+
}
|
|
233
294
|
return session;
|
|
234
295
|
}
|
|
235
296
|
|
|
@@ -417,10 +478,7 @@ class ProcessMonitor {
|
|
|
417
478
|
try {
|
|
418
479
|
let processes;
|
|
419
480
|
if (this.platform === 'win32') {
|
|
420
|
-
|
|
421
|
-
encoding: 'utf8', windowsHide: true, timeout: 8_000, maxBuffer: 2 * 1024 * 1024,
|
|
422
|
-
});
|
|
423
|
-
processes = selectAgentProcesses(processRows(output));
|
|
481
|
+
processes = selectAgentProcesses(windowsProcessRows(this.execFileSync));
|
|
424
482
|
} else {
|
|
425
483
|
const output = this.execFileSync('ps', ['-axo', 'pid=,ppid=,etime=,comm=,args='], { encoding: 'utf8', timeout: 8_000, maxBuffer: 2 * 1024 * 1024 });
|
|
426
484
|
const environment = this.platform === 'darwin' ? 'macos' : 'linux';
|
|
@@ -438,6 +496,8 @@ module.exports = {
|
|
|
438
496
|
ProcessMonitor,
|
|
439
497
|
parseCsvRows,
|
|
440
498
|
processRows,
|
|
499
|
+
powershellProcessRows,
|
|
500
|
+
windowsProcessRows,
|
|
441
501
|
wmiDateToIso,
|
|
442
502
|
providerFromWindowsProcess,
|
|
443
503
|
providerFromPosixProcess,
|
|
@@ -445,6 +505,7 @@ module.exports = {
|
|
|
445
505
|
elapsedSeconds,
|
|
446
506
|
selectAgentProcesses,
|
|
447
507
|
processSessionExternalId,
|
|
508
|
+
processInteractionMode,
|
|
448
509
|
utilityProcess,
|
|
449
510
|
runtimeLinkScore,
|
|
450
511
|
bridgeLinkScore,
|
package/src/terminalManager.js
CHANGED
|
@@ -40,7 +40,7 @@ function terminalEnvironment(extra = {}) {
|
|
|
40
40
|
for (const [key, value] of Object.entries({ ...process.env, ...extra })) {
|
|
41
41
|
if (value != null) env[key] = String(value);
|
|
42
42
|
}
|
|
43
|
-
env.TERM = env.TERM || 'xterm-256color';
|
|
43
|
+
env.TERM = !env.TERM || String(env.TERM).toLowerCase() === 'dumb' ? 'xterm-256color' : env.TERM;
|
|
44
44
|
env.COLORTERM = env.COLORTERM || 'truecolor';
|
|
45
45
|
return env;
|
|
46
46
|
}
|
|
@@ -142,6 +142,7 @@ function normalizeLaunchOptions(options = {}, platform = process.platform) {
|
|
|
142
142
|
args,
|
|
143
143
|
bridgeId: cleanText(options.bridgeId, 100),
|
|
144
144
|
title: cleanText(options.title, 100),
|
|
145
|
+
transient: Boolean(options.transient),
|
|
145
146
|
cols: numericDimension(options.cols, 120, 20, 500),
|
|
146
147
|
rows: numericDimension(options.rows, 32, 5, 200),
|
|
147
148
|
};
|
|
@@ -207,6 +208,7 @@ function publicSession(session, includeReplay = false) {
|
|
|
207
208
|
tmuxPane: session.options.tmuxPane,
|
|
208
209
|
provider: session.options.provider,
|
|
209
210
|
bridgeId: session.options.bridgeId,
|
|
211
|
+
transient: Boolean(session.options.transient),
|
|
210
212
|
background: session.options.type === 'agent',
|
|
211
213
|
recoveredAfterHostRestart: Boolean(session.recoveredAfterHostRestart),
|
|
212
214
|
recoverySkippedReason: session.recoverySkippedReason || '',
|
|
@@ -243,6 +245,7 @@ function restoredOptions(value = {}, platform = process.platform) {
|
|
|
243
245
|
args: Array.isArray(value.args) ? value.args.slice(0, 80).map(item => cleanText(item, 2_000)) : [],
|
|
244
246
|
bridgeId: cleanText(value.bridgeId, 100),
|
|
245
247
|
title: cleanText(value.title, 100),
|
|
248
|
+
transient: Boolean(value.transient),
|
|
246
249
|
cols: numericDimension(value.cols, 120, 20, 500),
|
|
247
250
|
rows: numericDimension(value.rows, 32, 5, 200),
|
|
248
251
|
};
|
|
@@ -356,7 +359,7 @@ class TerminalManager extends EventEmitter {
|
|
|
356
359
|
this.fileSystem.mkdirSync(path.dirname(this.storeFile), { recursive: true });
|
|
357
360
|
const payload = {
|
|
358
361
|
version: STORE_VERSION,
|
|
359
|
-
sessions: [...this.sessions.values()].map(persistedSession),
|
|
362
|
+
sessions: [...this.sessions.values()].filter(session => !session.options.transient).map(persistedSession),
|
|
360
363
|
};
|
|
361
364
|
this.fileSystem.writeFileSync(temporary, JSON.stringify(payload), 'utf8');
|
|
362
365
|
this.fileSystem.renameSync(temporary, this.storeFile);
|
|
@@ -376,6 +379,11 @@ class TerminalManager extends EventEmitter {
|
|
|
376
379
|
for (const session of this.sessions.values()) {
|
|
377
380
|
if (!session.recoveryPending) continue;
|
|
378
381
|
session.recoveryPending = false;
|
|
382
|
+
if (session.options.type === 'agent'
|
|
383
|
+
&& /TERM is set to ["']?dumb["']?[\s\S]{0,500}Continue anyway\?/i.test(session.replay)) {
|
|
384
|
+
this.sessions.delete(session.id);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
379
387
|
if (!hasSafeAgentResume(session.options)) {
|
|
380
388
|
session.status = 'exited';
|
|
381
389
|
session.pid = null;
|
|
@@ -485,6 +493,12 @@ class TerminalManager extends EventEmitter {
|
|
|
485
493
|
session.exitCode = Number.isFinite(event.exitCode) ? event.exitCode : null;
|
|
486
494
|
session.signal = Number.isFinite(event.signal) ? event.signal : null;
|
|
487
495
|
session.updatedAt = new Date().toISOString();
|
|
496
|
+
if (session.options.transient) {
|
|
497
|
+
this.sessions.delete(session.id);
|
|
498
|
+
this.emit('state', { change: 'removed', session: publicSession(session, false), sessions: this.list() });
|
|
499
|
+
this.persistNow();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
488
502
|
this.persistNow();
|
|
489
503
|
this.emitState('updated', session);
|
|
490
504
|
});
|