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.
- package/main.js +3 -2
- package/package.json +2 -1
- package/renderer/app-agent-actions.js +57 -22
- package/renderer/app-dashboard.js +56 -13
- package/renderer/app-drawer-content.js +78 -8
- package/renderer/app-events-filters.js +19 -23
- package/renderer/app-events-sessions.js +98 -0
- package/renderer/app-graph-model.js +6 -2
- package/renderer/app-graph-orchestration.js +12 -18
- package/renderer/app-graph-view.js +17 -6
- package/renderer/app-quality.js +5 -0
- package/renderer/app.js +13 -5
- package/renderer/i18n-messages.js +15 -0
- package/renderer/index.html +4 -6
- package/renderer/styles-components.css +77 -0
- package/renderer/styles-control-room.css +64 -43
- package/renderer/terminal-agent.js +14 -0
- package/src/agentMonitor/claudeParser.js +85 -10
- package/src/agentMonitor/hierarchy.js +7 -2
- package/src/agentMonitor.js +70 -1
- package/src/processMonitor.js +5 -0
- package/src/terminalHost.js +1 -1
- package/src/terminalManager.js +15 -3
|
@@ -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-
|
|
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-
|
|
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:
|
|
509
|
+
min-width: 76px;
|
|
518
510
|
min-height: 40px !important;
|
|
519
|
-
|
|
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-
|
|
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-
|
|
525
|
+
.control-room-disclosure-actions button:disabled {
|
|
542
526
|
opacity: .36;
|
|
543
527
|
cursor: default;
|
|
544
528
|
}
|
|
545
529
|
|
|
546
|
-
#
|
|
547
|
-
#
|
|
548
|
-
min-width:
|
|
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:
|
|
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
|
-
#
|
|
2046
|
-
#
|
|
2056
|
+
#controlRoomExpandAll,
|
|
2057
|
+
#controlRoomCollapseAll {
|
|
2047
2058
|
min-height: 44px !important;
|
|
2048
2059
|
}
|
|
2049
2060
|
|
|
2050
2061
|
.control-room-search,
|
|
2051
|
-
.control-room-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:${
|
|
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
|
|
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
|
-
|
|
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);
|
package/src/agentMonitor.js
CHANGED
|
@@ -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)
|
|
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
|
};
|
package/src/processMonitor.js
CHANGED
|
@@ -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,
|
package/src/terminalHost.js
CHANGED
|
@@ -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 =
|
|
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;
|
package/src/terminalManager.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|