loadtoagent 1.3.7 → 1.3.8

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.
@@ -344,6 +344,14 @@
344
344
  font-variant-numeric: tabular-nums;
345
345
  }
346
346
 
347
+ .control-room-project-toolbar .workspace-live-state b {
348
+ display: inline;
349
+ }
350
+
351
+ .control-room-project-toolbar .workspace-item.has-live-sessions {
352
+ border-color: rgba(76,227,154,.28);
353
+ }
354
+
347
355
  .control-room-project-toolbar .workspace-item:hover {
348
356
  border-color: rgba(89,216,255,.38);
349
357
  background: rgba(89,216,255,.065);
@@ -489,63 +497,39 @@
489
497
  min-height: 40px !important;
490
498
  }
491
499
 
492
- .control-room-pagination {
500
+ .control-room-disclosure-actions {
493
501
  display: flex;
494
502
  flex: 0 0 auto;
495
503
  align-items: center;
496
504
  gap: 10px;
497
505
  }
498
506
 
499
- .control-room-page-summary {
500
- min-width: 76px;
501
- color: #c3d0d8;
502
- text-align: center;
503
- white-space: nowrap;
504
- font-size: 12px;
505
- font-variant-numeric: tabular-nums;
506
- }
507
-
508
- .control-room-page-buttons {
509
- display: flex;
510
- flex: 0 0 auto;
511
- gap: 10px;
512
- }
513
-
514
- .control-room-page-buttons button {
515
- width: 40px;
507
+ .control-room-disclosure-actions button {
516
508
  height: 40px;
517
- min-width: 40px;
509
+ min-width: 76px;
518
510
  min-height: 40px !important;
519
- display: grid;
520
- place-items: center;
511
+ padding: 0 12px;
521
512
  border: 1px solid rgba(104,130,151,.28);
513
+ border-radius: 8px;
522
514
  background: rgba(11,18,27,.88);
523
515
  color: #a8b7c4;
516
+ font-size: 12px;
524
517
  cursor: pointer;
525
518
  }
526
519
 
527
- .control-room-page-buttons button:first-child {
528
- border-radius: 8px;
529
- }
530
-
531
- .control-room-page-buttons button:last-child {
532
- margin-left: 0;
533
- border-radius: 8px;
534
- }
535
-
536
- .control-room-page-buttons button:hover:not(:disabled) {
520
+ .control-room-disclosure-actions button:hover:not(:disabled) {
537
521
  border-color: rgba(89,216,255,.45);
538
522
  color: #80e6f4;
539
523
  }
540
524
 
541
- .control-room-page-buttons button:disabled {
525
+ .control-room-disclosure-actions button:disabled {
542
526
  opacity: .36;
543
527
  cursor: default;
544
528
  }
545
529
 
546
- #controlRoomPagePrev,
547
- #controlRoomPageNext {
548
- min-width: 40px;
530
+ #controlRoomExpandAll,
531
+ #controlRoomCollapseAll {
532
+ min-width: 76px;
549
533
  min-height: 40px !important;
550
534
  }
551
535
 
@@ -693,6 +677,7 @@ body details.control-room-project-group > summary.control-project-header {
693
677
  display: grid;
694
678
  place-items: center;
695
679
  color: #718597;
680
+ cursor: grab;
696
681
  }
697
682
 
698
683
  .control-project-handle::before {
@@ -803,10 +788,6 @@ body details.control-room-project-group > summary.control-project-header {
803
788
  margin-top: 0;
804
789
  }
805
790
 
806
- .control-room-project-group:not([open]) .control-project-handle {
807
- display: none;
808
- }
809
-
810
791
  .control-room-project-group:not([open]) .control-project-heading {
811
792
  padding-left: 10px;
812
793
  }
@@ -815,6 +796,36 @@ body details.control-room-project-group > summary.control-project-header {
815
796
  display: none;
816
797
  }
817
798
 
799
+ .control-room-project-group.project-sort-dragging {
800
+ opacity: .48;
801
+ }
802
+
803
+ .control-room-project-group[data-project-drop-edge]::after {
804
+ position: absolute;
805
+ z-index: 9;
806
+ right: 12px;
807
+ left: 12px;
808
+ height: 3px;
809
+ border-radius: 999px;
810
+ background: #64e2b5;
811
+ box-shadow: 0 0 0 3px rgba(100,226,181,.13),0 0 16px rgba(100,226,181,.55);
812
+ content: "";
813
+ pointer-events: none;
814
+ }
815
+
816
+ .control-room-project-group[data-project-drop-edge="top"]::after {
817
+ top: 3px;
818
+ }
819
+
820
+ .control-room-project-group[data-project-drop-edge="bottom"]::after {
821
+ bottom: 3px;
822
+ }
823
+
824
+ .control-project-header[draggable="true"]:active,
825
+ .control-room-project-group.project-sort-dragging .control-project-header {
826
+ cursor: grabbing;
827
+ }
828
+
818
829
  .control-room-session {
819
830
  --provider: #7ddcf2;
820
831
  position: relative;
@@ -2020,7 +2031,7 @@ body details.control-room-project-group > summary.control-project-header {
2020
2031
 
2021
2032
  .control-room-list-toolbar {
2022
2033
  gap: 10px;
2023
- flex-wrap: nowrap;
2034
+ flex-wrap: wrap;
2024
2035
  }
2025
2036
 
2026
2037
  .control-room-toolbar-spacer,
@@ -2042,13 +2053,13 @@ body details.control-room-project-group > summary.control-project-header {
2042
2053
 
2043
2054
  #controlRoomListToolbar select,
2044
2055
  #controlRoomSearchBtn,
2045
- #controlRoomPagePrev,
2046
- #controlRoomPageNext {
2056
+ #controlRoomExpandAll,
2057
+ #controlRoomCollapseAll {
2047
2058
  min-height: 44px !important;
2048
2059
  }
2049
2060
 
2050
2061
  .control-room-search,
2051
- .control-room-page-buttons button {
2062
+ .control-room-disclosure-actions button {
2052
2063
  height: 44px;
2053
2064
  min-height: 44px !important;
2054
2065
  }
@@ -2062,6 +2073,16 @@ body details.control-room-project-group > summary.control-project-header {
2062
2073
  flex: 1 1 180px;
2063
2074
  }
2064
2075
 
2076
+ .control-room-disclosure-actions {
2077
+ display: grid;
2078
+ flex: 1 1 100%;
2079
+ grid-template-columns: repeat(2,minmax(0,1fr));
2080
+ }
2081
+
2082
+ .control-room-disclosure-actions button {
2083
+ width: 100%;
2084
+ }
2085
+
2065
2086
  .control-room-search button {
2066
2087
  width: 40px;
2067
2088
  height: 38px;
@@ -132,6 +132,19 @@ window.LoadToAgentTerminalAgentActions = function createModule(context) {
132
132
  if (!support.supported) throw new Error(support.reason);
133
133
  const cwd = String(agentSession.cwd || preferredWorkspace() || '').trim();
134
134
  if (!cwd) throw new Error(t('terminal.agent.cwd_missing'));
135
+ const environment = agentSession.environment || {};
136
+ const tmuxPresence = (agentSession.runtimePresence || []).find(item => item.kind === 'tmux') || {};
137
+ const tmuxPresenceId = String(tmuxPresence.id || '');
138
+ const distroFromPresenceId = tmuxPresenceId.startsWith('tmux:')
139
+ ? tmuxPresenceId.slice(5, tmuxPresenceId.lastIndexOf(':'))
140
+ : '';
141
+ const wslCwd = state.platform.id === 'win32'
142
+ && (environment.kind === 'wsl' || /^\/(?:mnt|home|root|workspace)(?:\/|$)/.test(cwd));
143
+ const distro = wslCwd
144
+ ? String(environment.distro || tmuxPresence.distro || distroFromPresenceId
145
+ || (state.wslDistros.length === 1 ? state.wslDistros[0] : '')).trim()
146
+ : '';
147
+ if (wslCwd && !distro) throw new Error(t('terminal.agent.wsl_distro_missing'));
135
148
  const prompt = String(draft || '').trim();
136
149
  const title = t('terminal.agent.resume_title', {
137
150
  provider: providerLabel(agentSession.provider),
@@ -142,6 +155,7 @@ window.LoadToAgentTerminalAgentActions = function createModule(context) {
142
155
  provider: support.provider,
143
156
  args: resumeLaunchArgs(support, sendDraft ? prompt : '', { background: options.focus === false }),
144
157
  cwd,
158
+ distro,
145
159
  bridgeId: agentSession.id,
146
160
  title,
147
161
  transient: options.focus === false,
@@ -85,6 +85,15 @@ function createClaudeParser(dependencies) {
85
85
  return /^(?:Agent|Task)$/i.test(String(name || ''));
86
86
  }
87
87
 
88
+ function isClaudeMessageTool(name) {
89
+ return /^(?:SendMessage|send_message)$/i.test(String(name || ''));
90
+ }
91
+
92
+ function claudeChildId(value) {
93
+ const externalId = compactText(value, 180).replace(/^claude:/i, '');
94
+ return externalId ? `claude:${externalId}` : '';
95
+ }
96
+
88
97
  function addClaudeCommunication(session, event) {
89
98
  const communications = session.collaboration.communications;
90
99
  const row = {
@@ -103,8 +112,10 @@ function createClaudeParser(dependencies) {
103
112
  if (!communications.some(item => item.id === row.id)) communications.push(row);
104
113
  }
105
114
 
106
- function findClaudeSpawn(session, callId) {
107
- return session.collaboration.spawns.find(record => record.callId === String(callId || ''));
115
+ function findClaudeSpawn(session, callId, childExternalId = '') {
116
+ const childId = claudeChildId(childExternalId);
117
+ return session.collaboration.spawns.find(record => record.callId === String(callId || '')
118
+ || (childId && (record.childId === childId || record.agentPath === childId)));
108
119
  }
109
120
 
110
121
  function recordClaudeAgentCall(session, state, row, callId, args) {
@@ -143,14 +154,16 @@ function createClaudeParser(dependencies) {
143
154
  }
144
155
 
145
156
  function updateClaudeSpawn(session, callId, details = {}) {
146
- const record = findClaudeSpawn(session, callId);
157
+ const record = findClaudeSpawn(session, callId, details.childExternalId);
147
158
  if (!record) return;
148
159
  const childExternalId = compactText(details.childExternalId, 180);
149
160
  if (childExternalId) {
150
- record.childId = `claude:${childExternalId}`;
161
+ record.childId = claudeChildId(childExternalId);
151
162
  record.agentPath = record.childId;
152
163
  }
153
164
  if (details.status) record.status = details.status;
165
+ if (details.startedAt) record.lastSentAt = timestamp(details.startedAt, record.lastSentAt || record.startedAt);
166
+ if (details.status === 'running') record.completedAt = null;
154
167
  if (details.completedAt) record.completedAt = timestamp(details.completedAt, record.completedAt || session.updatedAt);
155
168
  if (details.result) record.result = compactText(details.result, 6000);
156
169
  for (const communication of session.collaboration.communications) {
@@ -170,9 +183,10 @@ function createClaudeParser(dependencies) {
170
183
  timestamp: record.startedAt,
171
184
  });
172
185
  }
173
- if (record.completedAt && !session.collaboration.communications.some(item => item.id === `result:${record.callId}`)) {
186
+ const resultCallId = String(details.resultCallId || record.callId);
187
+ if (record.completedAt && !session.collaboration.communications.some(item => item.id === `result:${resultCallId}`)) {
174
188
  addClaudeCommunication(session, {
175
- id: `result:${record.callId}`,
189
+ id: `result:${resultCallId}`,
176
190
  kind: 'result',
177
191
  label: '결과 반환 확인',
178
192
  from: record.agentPath || record.taskName,
@@ -185,9 +199,60 @@ function createClaudeParser(dependencies) {
185
199
  }
186
200
  }
187
201
 
202
+ function recordClaudeMessageCall(session, state, row, callId, args) {
203
+ const target = compactText(args.to || args.recipient || args.agent_id || args.agentId || args.task_id || args.taskId, 180);
204
+ const childId = claudeChildId(target);
205
+ const record = findClaudeSpawn(session, '', target);
206
+ const messageType = String(args.type || '').toLowerCase();
207
+ const kind = /interrupt|cancel|shutdown|stop/.test(messageType) ? 'interrupt' : 'followup';
208
+ const text = compactText(args.message || args.content || args.prompt, 6000);
209
+ const taskName = record && record.taskName || compactText(args.summary, 180);
210
+ state.claudeMessageCalls.set(String(callId), { target, childId, kind, text, taskName });
211
+ if (record && kind === 'followup') {
212
+ updateClaudeSpawn(session, callId, {
213
+ childExternalId: target,
214
+ status: 'running',
215
+ startedAt: row.timestamp,
216
+ });
217
+ }
218
+ addClaudeCommunication(session, {
219
+ id: `${kind}:${callId}`,
220
+ kind,
221
+ label: kind === 'interrupt' ? '서브에이전트 중단 요청' : '후속 메시지 전달',
222
+ from: session.agentPath || session.id,
223
+ to: childId || target,
224
+ taskName,
225
+ childId,
226
+ text,
227
+ timestamp: row.timestamp,
228
+ });
229
+ }
230
+
231
+ function recordClaudeMessageToolResult(session, state, item, at) {
232
+ const callId = String(item.tool_use_id || '');
233
+ const messageCall = state.claudeMessageCalls.get(callId);
234
+ if (!messageCall) return;
235
+ const output = compactText(item.content || item, 12000);
236
+ const resumed = output.match(/"resumedAgentId"\s*:\s*"([^"]+)"/i)
237
+ || output.match(/\bAgent\s+"([^"]+)"\s+had no active task/i);
238
+ const target = resumed && resumed[1] || messageCall.target;
239
+ updateClaudeSpawn(session, callId, {
240
+ childExternalId: target,
241
+ status: item.is_error === true || /"success"\s*:\s*false/i.test(output)
242
+ ? 'failed'
243
+ : (messageCall.kind === 'interrupt' ? 'cancelled' : 'running'),
244
+ startedAt: at,
245
+ });
246
+ }
247
+
188
248
  function recordClaudeAgentToolResult(session, state, item, at) {
189
249
  const call = state.toolCalls.get(String(item.tool_use_id || ''));
190
- if (!call || !isClaudeAgentTool(call.name)) return;
250
+ if (!call) return;
251
+ if (isClaudeMessageTool(call.name)) {
252
+ recordClaudeMessageToolResult(session, state, item, at);
253
+ return;
254
+ }
255
+ if (!isClaudeAgentTool(call.name)) return;
191
256
  const output = compactText(item.content || item, 12000);
192
257
  const agentId = output.match(/\bagentId:\s*([A-Za-z0-9_-]+)/i);
193
258
  const launched = /agent launched successfully|working in the background/i.test(output);
@@ -200,14 +265,16 @@ function createClaudeParser(dependencies) {
200
265
  });
201
266
  }
202
267
 
203
- function recordClaudeTaskCompletion(session, notification, at) {
268
+ function recordClaudeTaskCompletion(session, state, notification, at) {
204
269
  if (!notification) return;
205
270
  const resultMatch = notification.text.match(/<result>([\s\S]*?)<\/result>/i);
271
+ const messageCall = state.claudeMessageCalls.get(String(notification.toolUseId || ''));
206
272
  updateClaudeSpawn(session, notification.toolUseId, {
207
273
  childExternalId: notification.taskId,
208
274
  status: notification.status === 'completed' ? 'completed' : notification.status,
209
275
  completedAt: at,
210
276
  result: resultMatch ? resultMatch[1].trim() : '',
277
+ resultCallId: messageCall ? notification.toolUseId : undefined,
211
278
  });
212
279
  }
213
280
 
@@ -223,6 +290,7 @@ function createClaudeParser(dependencies) {
223
290
  const args = item.input && typeof item.input === 'object' ? item.input : {};
224
291
  state.toolCalls.set(String(callId), { name, args });
225
292
  if (isClaudeAgentTool(name)) recordClaudeAgentCall(session, state, row, callId, args);
293
+ if (isClaudeMessageTool(name)) recordClaudeMessageCall(session, state, row, callId, args);
226
294
  state.executionTracker.recordCall({ name, callId, args, rawInput: item.input, at: row.timestamp });
227
295
  addMessage(session, {
228
296
  id,
@@ -305,7 +373,7 @@ function createClaudeParser(dependencies) {
305
373
  content.filter(item => item && (!item.type || item.type === 'text'))
306
374
  .forEach((item) => {
307
375
  const notification = recordTaskNotification(state, typeof item === 'string' ? item : item.text, row.timestamp);
308
- recordClaudeTaskCompletion(session, notification, row.timestamp);
376
+ recordClaudeTaskCompletion(session, state, notification, row.timestamp);
309
377
  });
310
378
  if (session.depth && role === 'user') state.subagentCompletedAt = null;
311
379
  content.forEach((item, index) => {
@@ -367,6 +435,7 @@ function createClaudeParser(dependencies) {
367
435
  pendingUserInputCalls: new Set(),
368
436
  toolCalls: new Map(),
369
437
  executionTracker: createExecutionTracker({ compactText, timestamp }),
438
+ claudeMessageCalls: new Map(),
370
439
  latestTs: session.updatedAt,
371
440
  lastTurnFinished: false,
372
441
  subagentCompletedAt: null,
@@ -379,7 +448,7 @@ function createClaudeParser(dependencies) {
379
448
  if (row.agentId && session.depth) session.agentName = row.agentId;
380
449
  if (row.type === 'queue-operation' && row.operation === 'enqueue' && row.content) {
381
450
  const notification = recordTaskNotification(state, row.content, row.timestamp);
382
- recordClaudeTaskCompletion(session, notification, row.timestamp);
451
+ recordClaudeTaskCompletion(session, state, notification, row.timestamp);
383
452
  const detectedUtility = utilityKind(row.content);
384
453
  if (detectedUtility) session.utilityKind = detectedUtility;
385
454
  const visibleUser = visibleUserText(row.content);
@@ -430,6 +499,7 @@ function createClaudeParser(dependencies) {
430
499
  session.context = contextInfo(currentInput, modelContextWindow('claude', session.model, 0));
431
500
  const age = Date.now() - fileInfo.mtimeMs;
432
501
  const pendingUserInput = state.pendingUserInputCalls.size > 0;
502
+ const activeSubagents = session.collaboration.spawns.filter(record => record.status === 'running');
433
503
  const conversationalInput = state.lastConversationRole === 'assistant'
434
504
  && assistantRequestsUserResponse(state.lastAssistantText)
435
505
  && (state.lastTurnFinished || age >= ACTIVE_THRESHOLD_MS);
@@ -443,6 +513,11 @@ function createClaudeParser(dependencies) {
443
513
  } else if (!session.depth && (pendingUserInput || conversationalInput)) {
444
514
  session.status = 'waiting';
445
515
  session.statusDetail = pendingUserInput ? '선택 또는 입력 대기' : '답변 또는 선택 대기';
516
+ } else if (!session.depth && activeSubagents.length) {
517
+ session.status = 'running';
518
+ session.statusDetail = activeSubagents.length === 1
519
+ ? '서브에이전트 작업 진행 중'
520
+ : `서브에이전트 ${activeSubagents.length}개 작업 진행 중`;
446
521
  } else if (age < STALE_TURN_THRESHOLD_MS && !state.lastTurnFinished) {
447
522
  session.status = 'running';
448
523
  session.statusDetail = state.lastRole === 'user' ? '응답 생성 중' : '도구 실행 또는 스트리밍 중';
@@ -114,9 +114,14 @@ function createHierarchyAttacher(dependencies) {
114
114
  record.taskName = record.taskName || child.taskName || collaborationTaskName(child.agentPath);
115
115
  record.agentName = child.agentName || record.agentName;
116
116
  record.result = record.result || child.result || '';
117
- if (child.status === 'running' || child.status === 'starting') record.status = 'running';
117
+ const lastSentAt = Date.parse(record.lastSentAt || 0);
118
+ const childCompletedAt = Date.parse(child.completedAt || 0);
119
+ const followupStillNewer = record.status === 'running'
120
+ && Number.isFinite(lastSentAt)
121
+ && (!Number.isFinite(childCompletedAt) || lastSentAt > childCompletedAt);
122
+ if (child.status === 'running' || child.status === 'starting' || followupStillNewer) record.status = 'running';
118
123
  else if (child.status === 'completed' || child.completionObserved || record.result) record.status = 'completed';
119
- if (!record.completedAt && child.completedAt) record.completedAt = child.completedAt;
124
+ if (!record.completedAt && child.completedAt && !followupStillNewer) record.completedAt = child.completedAt;
120
125
 
121
126
  const retained = retainedByTask.get(record.taskName);
122
127
  record.currentlyRetained = Boolean(retained);
@@ -406,6 +406,59 @@ function isProjectlessSession(session) {
406
406
  && /(?:^|\/)Documents\/Codex\/\d{4}-\d{2}-\d{2}\/new-chat$/i.test(normalized);
407
407
  }
408
408
 
409
+ function mergeObservedRows(historyRows = [], managedRows = []) {
410
+ const rows = [];
411
+ const ids = new Map();
412
+ const signatures = new Set();
413
+ for (const row of [...historyRows, ...managedRows]) {
414
+ if (!row) continue;
415
+ const id = String(row.id || row.callId || row.path || '');
416
+ const signature = [
417
+ row.role || row.type || '',
418
+ compactText(row.text || row.detail || row.label || row.assignment || row.taskName || row.result, 6000),
419
+ timestamp(row.timestamp || row.updatedAt || row.startedAt, ''),
420
+ ].join('\u0000');
421
+ if (id && ids.has(id)) {
422
+ Object.assign(rows[ids.get(id)], row);
423
+ continue;
424
+ }
425
+ if (signatures.has(signature)) continue;
426
+ if (id) ids.set(id, rows.length);
427
+ signatures.add(signature);
428
+ rows.push(structuredClone(row));
429
+ }
430
+ return rows.sort((left, right) =>
431
+ Date.parse(left.timestamp || left.updatedAt || left.startedAt || 0)
432
+ - Date.parse(right.timestamp || right.updatedAt || right.startedAt || 0));
433
+ }
434
+
435
+ function mergeManagedWithHistory(history, managed) {
436
+ if (!history) return managed;
437
+ if (!managed) return history;
438
+ const historyCollaboration = history.collaboration || {};
439
+ const managedCollaboration = managed.collaboration || {};
440
+ const collaboration = {
441
+ ...managedCollaboration,
442
+ ...historyCollaboration,
443
+ capacity: Number(historyCollaboration.capacity && historyCollaboration.capacity.totalThreads || 0)
444
+ ? historyCollaboration.capacity
445
+ : managedCollaboration.capacity,
446
+ spawns: mergeObservedRows(historyCollaboration.spawns, managedCollaboration.spawns),
447
+ communications: mergeObservedRows(historyCollaboration.communications, managedCollaboration.communications),
448
+ retainedAgents: mergeObservedRows(historyCollaboration.retainedAgents, managedCollaboration.retainedAgents),
449
+ };
450
+ return {
451
+ ...history,
452
+ ...managed,
453
+ historyFile: history.historyFile || history.file || '',
454
+ messages: mergeObservedRows(history.messages, managed.messages),
455
+ lifecycle: mergeObservedRows(history.lifecycle, managed.lifecycle),
456
+ executions: (history.executions || []).length ? history.executions : (managed.executions || []),
457
+ childIds: [...new Set([...(history.childIds || []), ...(managed.childIds || [])])],
458
+ collaboration,
459
+ };
460
+ }
461
+
409
462
  const attachHierarchy = createHierarchyAttacher({
410
463
  addMessage,
411
464
  baseSession,
@@ -516,6 +569,19 @@ class AgentMonitor extends EventEmitter {
516
569
  let detailed = null;
517
570
  if (stored.source === 'loadtoagent' && stored.runId && this.runsDir) {
518
571
  detailed = parseManagedSession(path.join(this.runsDir, stored.runId), { fullHistory: true });
572
+ if (stored.historyFile) {
573
+ const historyStat = safeStat(stored.historyFile);
574
+ if (historyStat && historyStat.isFile()) {
575
+ const historyInfo = { file: stored.historyFile, mtimeMs: historyStat.mtimeMs, size: historyStat.size };
576
+ const historyParser = stored.provider === 'claude'
577
+ ? item => parseClaude(item, { fullHistory: true })
578
+ : stored.provider === 'codex'
579
+ ? item => parseCodex(item, { fullHistory: true })
580
+ : item => parseGeneric(item, stored.provider, { fullHistory: true });
581
+ const historyDetail = this.parseFile(historyInfo, historyParser, 'full-history');
582
+ detailed = mergeManagedWithHistory(historyDetail, detailed);
583
+ }
584
+ }
519
585
  } else if (stored.file) {
520
586
  const stat = safeStat(stored.file);
521
587
  if (stat && stat.isFile()) {
@@ -609,7 +675,9 @@ class AgentMonitor extends EventEmitter {
609
675
  const existing = byId.get(session.id);
610
676
  if (!existing || Date.parse(session.updatedAt || 0) > Date.parse(existing.updatedAt || 0)) byId.set(session.id, session);
611
677
  }
612
- for (const session of managed) byId.set(session.id, session);
678
+ for (const session of managed) {
679
+ byId.set(session.id, mergeManagedWithHistory(byId.get(session.id), session));
680
+ }
613
681
  const merged = [...byId.values()]
614
682
  .map(session => {
615
683
  const originCwd = session.originCwd || session.cwd || '';
@@ -653,4 +721,5 @@ module.exports = {
653
721
  buildSummary,
654
722
  contextInfo,
655
723
  attachHierarchy,
724
+ mergeManagedWithHistory,
656
725
  };
@@ -410,6 +410,11 @@ function applyRuntimePresence(agentSessions, tmuxSnapshot, processSnapshot, now
410
410
  id: `tmux:${distro.name}:${pane.nativeId}`,
411
411
  kind: 'tmux',
412
412
  label: `${distro.name} · ${tmuxSession.name} · pane ${pane.index}`,
413
+ distro: distro.name,
414
+ sessionId: tmuxSession.id,
415
+ sessionName: tmuxSession.name,
416
+ paneId: pane.id,
417
+ paneNativeId: pane.nativeId,
413
418
  provider: agent.provider,
414
419
  pid: agent.pid,
415
420
  startedAt: agent.startedAt,
@@ -10,7 +10,7 @@ const { spawn } = require('child_process');
10
10
  const { endpointFor, safeWriteJson } = require('./bridgeServer');
11
11
  const { runBestEffort } = require('./diagnostics');
12
12
 
13
- const TERMINAL_HOST_PROTOCOL = 2;
13
+ const TERMINAL_HOST_PROTOCOL = 3;
14
14
  const TERMINAL_HOST_RUNTIME = `node-pty-${require('node-pty/package.json').version}`;
15
15
  const MAX_FRAME_CHARS = 4 * 1024 * 1024;
16
16
  const AUTH_TIMEOUT_MS = 5_000;
@@ -120,10 +120,11 @@ function normalizeLaunchOptions(options = {}, platform = process.platform) {
120
120
  const type = TERMINAL_TYPES.has(options.type) ? options.type : fallbackType;
121
121
  const suppliedCwd = String(options.cwd || '').trim();
122
122
  const localCwd = suppliedCwd || os.homedir();
123
- if (['powershell', 'cmd', 'shell', 'agent'].includes(type) && (!fs.existsSync(localCwd) || !fs.statSync(localCwd).isDirectory())) {
123
+ const distro = cleanText(options.distro, 100);
124
+ const wslAgent = platform === 'win32' && type === 'agent' && Boolean(distro);
125
+ if (['powershell', 'cmd', 'shell', 'agent'].includes(type) && !wslAgent && (!fs.existsSync(localCwd) || !fs.statSync(localCwd).isDirectory())) {
124
126
  throw new Error(`작업 폴더를 찾을 수 없습니다: ${localCwd}`);
125
127
  }
126
- const distro = cleanText(options.distro, 100);
127
128
  if ((type === 'wsl' || type === 'tmux') && !distro) throw new Error(type === 'tmux' ? 'tmux 환경을 선택하세요.' : 'WSL 배포판을 선택하세요.');
128
129
  const tmuxSession = cleanText(options.tmuxSession, 100);
129
130
  const tmuxPane = cleanText(options.tmuxPane, 100);
@@ -135,7 +136,7 @@ function normalizeLaunchOptions(options = {}, platform = process.platform) {
135
136
  : [];
136
137
  return {
137
138
  type,
138
- cwd: ['powershell', 'cmd', 'shell', 'agent'].includes(type) ? path.resolve(localCwd) : suppliedCwd,
139
+ cwd: ['powershell', 'cmd', 'shell', 'agent'].includes(type) && !wslAgent ? path.resolve(localCwd) : suppliedCwd,
139
140
  distro,
140
141
  tmuxSession,
141
142
  tmuxPane,
@@ -162,6 +163,17 @@ function launchSpec(options, platform = process.platform, agentProviders = AGENT
162
163
  if (options.type === 'agent') {
163
164
  const provider = agentProviders[options.provider] || AGENT_PROVIDERS[options.provider];
164
165
  if (platform === 'win32') {
166
+ if (options.distro) {
167
+ const args = ['-d', options.distro];
168
+ if (options.cwd) args.push('--cd', options.cwd);
169
+ args.push('--', provider.command, ...(provider.args || []), ...options.args);
170
+ return {
171
+ file: 'wsl.exe',
172
+ args,
173
+ cwd: os.homedir(),
174
+ label: `${provider.label} · ${options.distro}`,
175
+ };
176
+ }
165
177
  const command = resolveWindowsCommand(provider.command);
166
178
  if (path.extname(command).toLowerCase() === '.ps1') {
167
179
  return {