loadtoagent 1.3.4 → 1.3.6
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/docs/PROVIDER-CONTRACTS.md +3 -1
- package/package.json +4 -2
- package/renderer/app-dashboard.js +75 -13
- package/renderer/app-drawer-content.js +189 -8
- package/renderer/app-drawer.js +4 -3
- package/renderer/app-events-dialogs.js +6 -1
- package/renderer/app-events-filters.js +80 -2
- package/renderer/app-events-sessions.js +110 -17
- package/renderer/app-graph-model.js +8 -5
- package/renderer/app-graph-orchestration.js +43 -11
- package/renderer/app-graph-view.js +75 -14
- package/renderer/app-quality.js +2 -0
- package/renderer/app-session-render.js +7 -7
- package/renderer/app.js +104 -0
- package/renderer/i18n-messages.js +46 -9
- package/renderer/index.html +33 -7
- package/renderer/styles-components.css +218 -1
- package/renderer/styles-control-room.css +820 -29
- package/renderer/styles-readability.css +1 -9
- package/renderer/styles-responsive-shell.css +22 -1
- package/renderer/styles-terminal.css +21 -31
- package/renderer/styles-workflow-map.css +8 -0
- package/renderer/terminal-events.js +0 -13
- package/renderer/terminal-workbench.js +1 -4
- package/src/agentMonitor/claudeParser.js +189 -4
- package/src/agentMonitor/codexCollaboration.js +1 -0
- package/src/agentMonitor/codexParser.js +6 -5
- package/src/agentMonitor/executionActivity.js +26 -1
- package/src/agentMonitor/hierarchy.js +6 -3
- package/src/agentMonitor.js +10 -3
- package/src/contracts.js +1 -1
- package/src/monitorWorker.js +2 -0
- package/src/nodePtyRuntime.js +58 -0
- package/src/terminalHost.js +104 -5
- package/src/terminalManager.js +5 -1
|
@@ -474,14 +474,7 @@ body :is(input, textarea)::placeholder {
|
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
.terminal-session-row {
|
|
477
|
-
grid-template-columns: minmax(0, 1fr)
|
|
478
|
-
gap: 7px;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
.terminal-reorder-actions button {
|
|
482
|
-
min-width: 44px;
|
|
483
|
-
min-height: 44px;
|
|
484
|
-
font-size: 16px;
|
|
477
|
+
grid-template-columns: minmax(0, 1fr);
|
|
485
478
|
}
|
|
486
479
|
|
|
487
480
|
.terminal-font-controls {
|
|
@@ -914,7 +907,6 @@ body :is(
|
|
|
914
907
|
.terminal-workspace-actions,
|
|
915
908
|
.terminal-key-actions,
|
|
916
909
|
.terminal-tmux-tools,
|
|
917
|
-
.terminal-reorder-actions,
|
|
918
910
|
.tmux-section-actions,
|
|
919
911
|
.tmux-pane-actions,
|
|
920
912
|
.run-modal-actions,
|
|
@@ -399,10 +399,17 @@
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
.chat-progress-updates,
|
|
402
|
-
.chat-turn-waiting
|
|
402
|
+
.chat-turn-waiting,
|
|
403
|
+
.subagent-call-moment,
|
|
404
|
+
.subagent-call-event {
|
|
403
405
|
margin-left: 0;
|
|
404
406
|
}
|
|
405
407
|
|
|
408
|
+
.subagent-call-moment,
|
|
409
|
+
.subagent-call-event {
|
|
410
|
+
width: 100%;
|
|
411
|
+
}
|
|
412
|
+
|
|
406
413
|
.json-object>div {
|
|
407
414
|
grid-template-columns: 1fr;
|
|
408
415
|
}
|
|
@@ -475,6 +482,20 @@
|
|
|
475
482
|
border-radius: 7px;
|
|
476
483
|
}
|
|
477
484
|
|
|
485
|
+
.subagent-call-event {
|
|
486
|
+
grid-template-columns: 32px minmax(0,1fr);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.subagent-call-anchor {
|
|
490
|
+
grid-template-columns: 26px minmax(0,1fr);
|
|
491
|
+
padding: 9px 10px;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.subagent-call-action {
|
|
495
|
+
grid-column: 2;
|
|
496
|
+
justify-items: start;
|
|
497
|
+
}
|
|
498
|
+
|
|
478
499
|
.main-stage {
|
|
479
500
|
padding-right: 11px;
|
|
480
501
|
padding-left: 11px;
|
|
@@ -421,35 +421,8 @@
|
|
|
421
421
|
|
|
422
422
|
.terminal-session-row {
|
|
423
423
|
display: grid;
|
|
424
|
-
grid-template-columns: minmax(0,1fr)
|
|
424
|
+
grid-template-columns: minmax(0,1fr);
|
|
425
425
|
align-items: stretch;
|
|
426
|
-
gap: 4px;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.terminal-reorder-actions {
|
|
430
|
-
display: grid;
|
|
431
|
-
gap: 4px;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.terminal-reorder-actions button {
|
|
435
|
-
min-width: 26px;
|
|
436
|
-
padding: 0;
|
|
437
|
-
border: 1px solid #243946;
|
|
438
|
-
border-radius: 7px;
|
|
439
|
-
background: #101a24;
|
|
440
|
-
color: #7f91a3;
|
|
441
|
-
cursor: pointer;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
.terminal-reorder-actions button:hover:not(:disabled),
|
|
445
|
-
.terminal-reorder-actions button:focus-visible {
|
|
446
|
-
border-color: #397766;
|
|
447
|
-
color: #70e6bd;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
.terminal-reorder-actions button:disabled {
|
|
451
|
-
opacity: .3;
|
|
452
|
-
cursor: default;
|
|
453
426
|
}
|
|
454
427
|
|
|
455
428
|
.terminal-session-item:hover {
|
|
@@ -1395,7 +1368,7 @@
|
|
|
1395
1368
|
cursor: grab;
|
|
1396
1369
|
position: relative;
|
|
1397
1370
|
display: grid;
|
|
1398
|
-
grid-template-columns: 38px minmax(0,1fr)
|
|
1371
|
+
grid-template-columns: 12px 38px minmax(0,1fr) auto;
|
|
1399
1372
|
gap: 10px;
|
|
1400
1373
|
min-height: 72px;
|
|
1401
1374
|
padding: 10px 9px;
|
|
@@ -1406,6 +1379,23 @@
|
|
|
1406
1379
|
transition: transform var(--motion-fast) var(--motion-ease),background-color var(--motion-fast) ease,border-color var(--motion-fast) ease,color var(--motion-fast) ease,box-shadow var(--motion-medium) ease,opacity var(--motion-fast) ease;
|
|
1407
1380
|
}
|
|
1408
1381
|
|
|
1382
|
+
.terminal-session-drag-handle {
|
|
1383
|
+
width: 3px;
|
|
1384
|
+
height: 3px;
|
|
1385
|
+
justify-self: center;
|
|
1386
|
+
border-radius: 50%;
|
|
1387
|
+
background: #607281;
|
|
1388
|
+
box-shadow: 0 -5px #607281,0 5px #607281;
|
|
1389
|
+
transition: background-color var(--motion-fast) ease,box-shadow var(--motion-fast) ease;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
.terminal-session-item:hover .terminal-session-drag-handle,
|
|
1393
|
+
.terminal-session-item:focus-visible .terminal-session-drag-handle,
|
|
1394
|
+
.terminal-session-item.dragging .terminal-session-drag-handle {
|
|
1395
|
+
background: #68dcb6;
|
|
1396
|
+
box-shadow: 0 -5px #68dcb6,0 5px #68dcb6;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1409
1399
|
.terminal-session-item.dragging {
|
|
1410
1400
|
opacity: .45;
|
|
1411
1401
|
cursor: grabbing;
|
|
@@ -1468,10 +1458,10 @@ body[data-current-view="terminal"] .topbar h1 {
|
|
|
1468
1458
|
.terminal-resource-head>div span { margin-top: 5px; color: #93a6b2; font-size: 12px; }
|
|
1469
1459
|
.terminal-resource-live { font-size: 12px; }
|
|
1470
1460
|
.terminal-session-list { gap: 9px; padding: 12px; }
|
|
1471
|
-
.terminal-session-row { grid-template-columns: minmax(0,1fr)
|
|
1461
|
+
.terminal-session-row { grid-template-columns: minmax(0,1fr); }
|
|
1472
1462
|
|
|
1473
1463
|
.terminal-session-item {
|
|
1474
|
-
grid-template-columns: 42px minmax(0,1fr) auto;
|
|
1464
|
+
grid-template-columns: 12px 42px minmax(0,1fr) auto;
|
|
1475
1465
|
gap: 11px;
|
|
1476
1466
|
min-height: 86px;
|
|
1477
1467
|
padding: 11px 10px;
|
|
@@ -610,6 +610,10 @@
|
|
|
610
610
|
border-color: rgba(230,104,112,.4);
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
.execution-activity-card.unverified {
|
|
614
|
+
border-color: rgba(229,184,95,.34);
|
|
615
|
+
}
|
|
616
|
+
|
|
613
617
|
.execution-activity-icon {
|
|
614
618
|
width: 36px;
|
|
615
619
|
height: 36px;
|
|
@@ -716,6 +720,10 @@
|
|
|
716
720
|
background: #e26972;
|
|
717
721
|
}
|
|
718
722
|
|
|
723
|
+
.execution-activity-card.unverified .execution-activity-state>span:first-child i {
|
|
724
|
+
background: #d6b36c;
|
|
725
|
+
}
|
|
726
|
+
|
|
719
727
|
.execution-activity-state b {
|
|
720
728
|
color: #c6d2d7;
|
|
721
729
|
font-size: 12px;
|
|
@@ -93,19 +93,6 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
|
|
|
93
93
|
});
|
|
94
94
|
};
|
|
95
95
|
sessionList.addEventListener('click', event => {
|
|
96
|
-
const move = event.target.closest('[data-session-move][data-session-move-id]');
|
|
97
|
-
if (move) {
|
|
98
|
-
const changed = moveSessionByOffset(move.dataset.sessionMoveId, Number(move.dataset.sessionMove));
|
|
99
|
-
if (!changed) return;
|
|
100
|
-
renderAll();
|
|
101
|
-
requestAnimationFrame(() => {
|
|
102
|
-
const next = sessionList.querySelector(`[data-session-move-id="${CSS.escape(move.dataset.sessionMoveId)}"][data-session-move="${CSS.escape(move.dataset.sessionMove)}"]`);
|
|
103
|
-
if (next && !next.disabled) next.focus();
|
|
104
|
-
else sessionList.querySelector(`[data-terminal-id="${CSS.escape(move.dataset.sessionMoveId)}"]`)?.focus();
|
|
105
|
-
});
|
|
106
|
-
notice(window.LoadToAgentI18n.t('terminal.reordered'), 'success');
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
96
|
if (state.sessionDragJustEnded) return;
|
|
110
97
|
const item = event.target.closest('[data-terminal-id]');
|
|
111
98
|
if (item) selectSession(item.dataset.terminalId);
|
|
@@ -178,14 +178,11 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
178
178
|
aria-grabbed="false"
|
|
179
179
|
aria-describedby="terminalReorderHelp"
|
|
180
180
|
title="${esc(session.cwd || window.LoadToAgentI18n.t('terminal.reorder_hint'))}">
|
|
181
|
+
<span class="terminal-session-drag-handle" aria-hidden="true"></span>
|
|
181
182
|
<span class="terminal-session-icon">${esc(terminalTypeMark(session))}</span>
|
|
182
183
|
<span><b>${esc(session.title)}</b><small>${esc(terminalTypeLabel(session))}${session.background ? ` · ${t('terminal.background_kept')}` : ''}${session.recoveredAfterHostRestart ? ` · ${t('terminal.recovered_after_host_restart')}` : ''}</small><em>${esc(session.cwd || session.distro || `PID ${session.pid || '--'}`)}</em><span class="sr-only">${index + 1}/${general.length}</span></span>
|
|
183
184
|
<span class="terminal-session-status" data-status="${esc(presentation.tone)}"><i></i>${esc(presentation.label)}</span>
|
|
184
185
|
</button>
|
|
185
|
-
<span class="terminal-reorder-actions" aria-label="${esc(window.LoadToAgentI18n.t('terminal.reorder_group', { title: session.title }))}">
|
|
186
|
-
<button type="button" data-session-move="-1" data-session-move-id="${esc(session.id)}" aria-label="${esc(window.LoadToAgentI18n.t('terminal.move_up', { title: session.title }))}" ${index === 0 ? 'disabled' : ''}>↑</button>
|
|
187
|
-
<button type="button" data-session-move="1" data-session-move-id="${esc(session.id)}" aria-label="${esc(window.LoadToAgentI18n.t('terminal.move_down', { title: session.title }))}" ${index === general.length - 1 ? 'disabled' : ''}>↓</button>
|
|
188
|
-
</span>
|
|
189
186
|
</div>`;
|
|
190
187
|
}).join('') : `<div class="terminal-resource-empty">${t('terminal.empty.general')}</div>`;
|
|
191
188
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const { createExecutionTracker } = require('./executionActivity');
|
|
4
|
+
const { createExecutionTracker, reconcileExecutionActivities } = require('./executionActivity');
|
|
5
5
|
|
|
6
6
|
function createClaudeParser(dependencies) {
|
|
7
7
|
const {
|
|
@@ -23,6 +23,34 @@ function createClaudeParser(dependencies) {
|
|
|
23
23
|
isUserInputTool,
|
|
24
24
|
} = dependencies;
|
|
25
25
|
|
|
26
|
+
function taskNotification(value) {
|
|
27
|
+
const text = String(value || '');
|
|
28
|
+
if (!/<task-notification\b/i.test(text)) return null;
|
|
29
|
+
const field = name => {
|
|
30
|
+
const match = text.match(new RegExp(`<${name}>([\\s\\S]*?)<\\/${name}>`, 'i'));
|
|
31
|
+
return match ? match[1].trim() : '';
|
|
32
|
+
};
|
|
33
|
+
const taskId = field('task-id');
|
|
34
|
+
const toolUseId = field('tool-use-id');
|
|
35
|
+
const status = field('status').toLowerCase();
|
|
36
|
+
if (!taskId && !toolUseId) return null;
|
|
37
|
+
return { taskId, toolUseId, status, text };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function recordTaskNotification(state, value, at) {
|
|
41
|
+
const notification = taskNotification(value);
|
|
42
|
+
if (!notification || !/^(?:completed|failed|error|cancelled)$/.test(notification.status)) return null;
|
|
43
|
+
state.executionTracker.recordOutput({
|
|
44
|
+
name: 'bash',
|
|
45
|
+
callId: notification.toolUseId,
|
|
46
|
+
args: { task_id: notification.taskId },
|
|
47
|
+
output: notification.text,
|
|
48
|
+
at,
|
|
49
|
+
isError: /^(?:failed|error)$/.test(notification.status),
|
|
50
|
+
});
|
|
51
|
+
return notification;
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
function normalizeUsage(raw = {}) {
|
|
27
55
|
return finalizeUsage({
|
|
28
56
|
input: raw.input_tokens,
|
|
@@ -53,6 +81,136 @@ function createClaudeParser(dependencies) {
|
|
|
53
81
|
return raw;
|
|
54
82
|
}
|
|
55
83
|
|
|
84
|
+
function isClaudeAgentTool(name) {
|
|
85
|
+
return /^(?:Agent|Task)$/i.test(String(name || ''));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function addClaudeCommunication(session, event) {
|
|
89
|
+
const communications = session.collaboration.communications;
|
|
90
|
+
const row = {
|
|
91
|
+
id: String(event.id || `claude-communication:${communications.length}`),
|
|
92
|
+
kind: event.kind || 'message',
|
|
93
|
+
label: compactText(event.label || '메시지', 100),
|
|
94
|
+
from: compactText(event.from, 180),
|
|
95
|
+
to: compactText(event.to, 180),
|
|
96
|
+
taskName: compactText(event.taskName, 180),
|
|
97
|
+
childId: compactText(event.childId, 180),
|
|
98
|
+
text: compactText(event.text, 6000),
|
|
99
|
+
protected: false,
|
|
100
|
+
assignmentSource: compactText(event.assignmentSource, 80),
|
|
101
|
+
timestamp: timestamp(event.timestamp, session.updatedAt),
|
|
102
|
+
};
|
|
103
|
+
if (!communications.some(item => item.id === row.id)) communications.push(row);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findClaudeSpawn(session, callId) {
|
|
107
|
+
return session.collaboration.spawns.find(record => record.callId === String(callId || ''));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function recordClaudeAgentCall(session, state, row, callId, args) {
|
|
111
|
+
const prompt = compactText(args.prompt || args.message, 6000);
|
|
112
|
+
const taskName = compactText(args.description || args.name || args.subagent_type, 180);
|
|
113
|
+
const record = {
|
|
114
|
+
callId: String(callId || `claude-spawn:${session.collaboration.spawns.length}`),
|
|
115
|
+
taskName,
|
|
116
|
+
agentPath: '',
|
|
117
|
+
childId: '',
|
|
118
|
+
assignment: prompt,
|
|
119
|
+
assignmentObserved: Boolean(prompt),
|
|
120
|
+
assignmentProtected: false,
|
|
121
|
+
assignmentSource: prompt ? 'claude-agent-prompt' : 'unavailable',
|
|
122
|
+
assignmentContext: '',
|
|
123
|
+
sharedGoal: compactText(state.latestUser, 6000),
|
|
124
|
+
status: 'requested',
|
|
125
|
+
startedAt: timestamp(row.timestamp, session.updatedAt),
|
|
126
|
+
completedAt: null,
|
|
127
|
+
result: '',
|
|
128
|
+
agentName: compactText(args.subagent_type, 120),
|
|
129
|
+
currentlyRetained: false,
|
|
130
|
+
};
|
|
131
|
+
session.collaboration.spawns.push(record);
|
|
132
|
+
addClaudeCommunication(session, {
|
|
133
|
+
id: `assign:${record.callId}`,
|
|
134
|
+
kind: 'assignment',
|
|
135
|
+
label: '새 작업 배정',
|
|
136
|
+
from: session.agentPath || session.id,
|
|
137
|
+
to: record.taskName,
|
|
138
|
+
taskName: record.taskName,
|
|
139
|
+
text: record.assignment,
|
|
140
|
+
assignmentSource: record.assignmentSource,
|
|
141
|
+
timestamp: row.timestamp,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function updateClaudeSpawn(session, callId, details = {}) {
|
|
146
|
+
const record = findClaudeSpawn(session, callId);
|
|
147
|
+
if (!record) return;
|
|
148
|
+
const childExternalId = compactText(details.childExternalId, 180);
|
|
149
|
+
if (childExternalId) {
|
|
150
|
+
record.childId = `claude:${childExternalId}`;
|
|
151
|
+
record.agentPath = record.childId;
|
|
152
|
+
}
|
|
153
|
+
if (details.status) record.status = details.status;
|
|
154
|
+
if (details.completedAt) record.completedAt = timestamp(details.completedAt, record.completedAt || session.updatedAt);
|
|
155
|
+
if (details.result) record.result = compactText(details.result, 6000);
|
|
156
|
+
for (const communication of session.collaboration.communications) {
|
|
157
|
+
if (communication.taskName !== record.taskName || communication.childId) continue;
|
|
158
|
+
communication.childId = record.childId;
|
|
159
|
+
communication.to = record.agentPath || communication.to;
|
|
160
|
+
}
|
|
161
|
+
if ((record.status === 'running' || record.childId) && !session.collaboration.communications.some(item => item.id === `started:${record.callId}`)) {
|
|
162
|
+
addClaudeCommunication(session, {
|
|
163
|
+
id: `started:${record.callId}`,
|
|
164
|
+
kind: 'started',
|
|
165
|
+
label: '서브에이전트 실행 시작',
|
|
166
|
+
from: 'Claude 런타임',
|
|
167
|
+
to: record.agentPath || record.taskName,
|
|
168
|
+
taskName: record.taskName,
|
|
169
|
+
childId: record.childId,
|
|
170
|
+
timestamp: record.startedAt,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (record.completedAt && !session.collaboration.communications.some(item => item.id === `result:${record.callId}`)) {
|
|
174
|
+
addClaudeCommunication(session, {
|
|
175
|
+
id: `result:${record.callId}`,
|
|
176
|
+
kind: 'result',
|
|
177
|
+
label: '결과 반환 확인',
|
|
178
|
+
from: record.agentPath || record.taskName,
|
|
179
|
+
to: session.agentPath || session.id,
|
|
180
|
+
taskName: record.taskName,
|
|
181
|
+
childId: record.childId,
|
|
182
|
+
text: record.result,
|
|
183
|
+
timestamp: record.completedAt,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function recordClaudeAgentToolResult(session, state, item, at) {
|
|
189
|
+
const call = state.toolCalls.get(String(item.tool_use_id || ''));
|
|
190
|
+
if (!call || !isClaudeAgentTool(call.name)) return;
|
|
191
|
+
const output = compactText(item.content || item, 12000);
|
|
192
|
+
const agentId = output.match(/\bagentId:\s*([A-Za-z0-9_-]+)/i);
|
|
193
|
+
const launched = /agent launched successfully|working in the background/i.test(output);
|
|
194
|
+
const failed = item.is_error === true;
|
|
195
|
+
updateClaudeSpawn(session, item.tool_use_id, {
|
|
196
|
+
childExternalId: agentId && agentId[1],
|
|
197
|
+
status: failed ? 'failed' : (launched ? 'running' : 'completed'),
|
|
198
|
+
completedAt: launched ? null : at,
|
|
199
|
+
result: launched ? '' : output,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function recordClaudeTaskCompletion(session, notification, at) {
|
|
204
|
+
if (!notification) return;
|
|
205
|
+
const resultMatch = notification.text.match(/<result>([\s\S]*?)<\/result>/i);
|
|
206
|
+
updateClaudeSpawn(session, notification.toolUseId, {
|
|
207
|
+
childExternalId: notification.taskId,
|
|
208
|
+
status: notification.status === 'completed' ? 'completed' : notification.status,
|
|
209
|
+
completedAt: at,
|
|
210
|
+
result: resultMatch ? resultMatch[1].trim() : '',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
56
214
|
function recordContent(session, state, row, item, index) {
|
|
57
215
|
const kind = item && item.type;
|
|
58
216
|
const id = `${row.uuid || row.requestId || session.externalId}:${index}`;
|
|
@@ -64,6 +222,7 @@ function createClaudeParser(dependencies) {
|
|
|
64
222
|
const callId = item.id || id;
|
|
65
223
|
const args = item.input && typeof item.input === 'object' ? item.input : {};
|
|
66
224
|
state.toolCalls.set(String(callId), { name, args });
|
|
225
|
+
if (isClaudeAgentTool(name)) recordClaudeAgentCall(session, state, row, callId, args);
|
|
67
226
|
state.executionTracker.recordCall({ name, callId, args, rawInput: item.input, at: row.timestamp });
|
|
68
227
|
addMessage(session, {
|
|
69
228
|
id,
|
|
@@ -93,6 +252,7 @@ function createClaudeParser(dependencies) {
|
|
|
93
252
|
at: row.timestamp,
|
|
94
253
|
isError: item.is_error === true,
|
|
95
254
|
});
|
|
255
|
+
recordClaudeAgentToolResult(session, state, item, row.timestamp);
|
|
96
256
|
settleLifecycle(session, item.tool_use_id, item.is_error ? 'failed' : 'done', row.timestamp);
|
|
97
257
|
addLifecycle(session, {
|
|
98
258
|
id: `result:${item.tool_use_id || id}`,
|
|
@@ -136,11 +296,18 @@ function createClaudeParser(dependencies) {
|
|
|
136
296
|
function processMessageRow(session, state, row) {
|
|
137
297
|
const role = row.message.role === 'assistant' ? 'assistant' : 'user';
|
|
138
298
|
const internalUserRow = role === 'user' && Boolean(row.isMeta || row.sourceToolUseID);
|
|
299
|
+
state.lastTurnFinished = false;
|
|
139
300
|
state.lastRole = role;
|
|
140
301
|
if (row.message.model) session.model = row.message.model;
|
|
141
302
|
const content = Array.isArray(row.message.content)
|
|
142
303
|
? row.message.content
|
|
143
304
|
: [{ type: 'text', text: row.message.content }];
|
|
305
|
+
content.filter(item => item && (!item.type || item.type === 'text'))
|
|
306
|
+
.forEach((item) => {
|
|
307
|
+
const notification = recordTaskNotification(state, typeof item === 'string' ? item : item.text, row.timestamp);
|
|
308
|
+
recordClaudeTaskCompletion(session, notification, row.timestamp);
|
|
309
|
+
});
|
|
310
|
+
if (session.depth && role === 'user') state.subagentCompletedAt = null;
|
|
144
311
|
content.forEach((item, index) => {
|
|
145
312
|
if (internalUserRow && (!item.type || item.type === 'text')) return;
|
|
146
313
|
recordContent(session, state, row, item, index);
|
|
@@ -176,6 +343,10 @@ function createClaudeParser(dependencies) {
|
|
|
176
343
|
}
|
|
177
344
|
content.filter(item => item && item.type === 'tool_use' && isUserInputTool(item.name))
|
|
178
345
|
.forEach(item => state.pendingUserInputCalls.add(String(item.id || item.name)));
|
|
346
|
+
if (String(row.message.stop_reason || '').toLowerCase() === 'end_turn') {
|
|
347
|
+
state.lastTurnFinished = true;
|
|
348
|
+
if (session.depth) state.subagentCompletedAt = timestamp(row.timestamp, state.latestTs);
|
|
349
|
+
}
|
|
179
350
|
}
|
|
180
351
|
if (role === 'assistant' && row.message.usage) {
|
|
181
352
|
const key = row.requestId || row.message.id || row.uuid;
|
|
@@ -198,6 +369,7 @@ function createClaudeParser(dependencies) {
|
|
|
198
369
|
executionTracker: createExecutionTracker({ compactText, timestamp }),
|
|
199
370
|
latestTs: session.updatedAt,
|
|
200
371
|
lastTurnFinished: false,
|
|
372
|
+
subagentCompletedAt: null,
|
|
201
373
|
};
|
|
202
374
|
for (const row of rows) {
|
|
203
375
|
state.latestTs = timestamp(row.timestamp, state.latestTs);
|
|
@@ -206,6 +378,8 @@ function createClaudeParser(dependencies) {
|
|
|
206
378
|
if (row.gitBranch) session.branch = row.gitBranch;
|
|
207
379
|
if (row.agentId && session.depth) session.agentName = row.agentId;
|
|
208
380
|
if (row.type === 'queue-operation' && row.operation === 'enqueue' && row.content) {
|
|
381
|
+
const notification = recordTaskNotification(state, row.content, row.timestamp);
|
|
382
|
+
recordClaudeTaskCompletion(session, notification, row.timestamp);
|
|
209
383
|
const detectedUtility = utilityKind(row.content);
|
|
210
384
|
if (detectedUtility) session.utilityKind = detectedUtility;
|
|
211
385
|
const visibleUser = visibleUserText(row.content);
|
|
@@ -245,7 +419,6 @@ function createClaudeParser(dependencies) {
|
|
|
245
419
|
session.updatedAt = state.latestTs;
|
|
246
420
|
session.startedAt = timestamp(parsed.rows[0].timestamp, session.updatedAt);
|
|
247
421
|
session.usage = sumUsage([...state.requestUsage.values()]);
|
|
248
|
-
session.executions = state.executionTracker.finalize();
|
|
249
422
|
const utilityTitle = session.utilityKind === 'memory-extraction'
|
|
250
423
|
? 'Claude 백그라운드 메모리 추출'
|
|
251
424
|
: (session.utilityKind === 'authentication-check' ? 'Claude 인증 점검' : '');
|
|
@@ -260,10 +433,17 @@ function createClaudeParser(dependencies) {
|
|
|
260
433
|
const conversationalInput = state.lastConversationRole === 'assistant'
|
|
261
434
|
&& assistantRequestsUserResponse(state.lastAssistantText)
|
|
262
435
|
&& (state.lastTurnFinished || age >= ACTIVE_THRESHOLD_MS);
|
|
263
|
-
if (
|
|
436
|
+
if (session.depth && state.subagentCompletedAt) {
|
|
437
|
+
session.status = 'completed';
|
|
438
|
+
session.statusDetail = '작업 완료';
|
|
439
|
+
session.completedAt = state.subagentCompletedAt;
|
|
440
|
+
session.completionObserved = true;
|
|
441
|
+
session.statusObserved = false;
|
|
442
|
+
session.result = state.lastAssistantText || session.result;
|
|
443
|
+
} else if (!session.depth && (pendingUserInput || conversationalInput)) {
|
|
264
444
|
session.status = 'waiting';
|
|
265
445
|
session.statusDetail = pendingUserInput ? '선택 또는 입력 대기' : '답변 또는 선택 대기';
|
|
266
|
-
} else if (age <
|
|
446
|
+
} else if (age < STALE_TURN_THRESHOLD_MS && !state.lastTurnFinished) {
|
|
267
447
|
session.status = 'running';
|
|
268
448
|
session.statusDetail = state.lastRole === 'user' ? '응답 생성 중' : '도구 실행 또는 스트리밍 중';
|
|
269
449
|
} else if (state.lastRole === 'user' && age < STALE_TURN_THRESHOLD_MS) {
|
|
@@ -273,6 +453,11 @@ function createClaudeParser(dependencies) {
|
|
|
273
453
|
session.status = 'idle';
|
|
274
454
|
session.statusDetail = state.lastRole === 'user' ? '마지막 응답 기록이 종료됨' : '다음 요청 대기';
|
|
275
455
|
}
|
|
456
|
+
session.executions = reconcileExecutionActivities(state.executionTracker.finalize(), {
|
|
457
|
+
staleAfterMs: STALE_TURN_THRESHOLD_MS,
|
|
458
|
+
turnFinished: state.lastTurnFinished,
|
|
459
|
+
waitingForUser: session.status === 'waiting',
|
|
460
|
+
});
|
|
276
461
|
session.statusObserved = age < ACTIVE_THRESHOLD_MS;
|
|
277
462
|
trimSession(session);
|
|
278
463
|
return session;
|
|
@@ -232,20 +232,21 @@ function createCodexParser(dependencies) {
|
|
|
232
232
|
: record.agentPath;
|
|
233
233
|
record.assignmentProtected = messageProtected;
|
|
234
234
|
const directAssignment = rawMessage && !messageProtected ? rawMessage : '';
|
|
235
|
-
record.assignment = directAssignment
|
|
236
|
-
record.assignmentObserved = Boolean(
|
|
237
|
-
record.assignmentSource = directAssignment ? 'spawn-message' : (
|
|
235
|
+
record.assignment = directAssignment;
|
|
236
|
+
record.assignmentObserved = Boolean(directAssignment);
|
|
237
|
+
record.assignmentSource = directAssignment ? 'spawn-message' : (messageProtected ? 'protected' : 'unavailable');
|
|
238
|
+
record.assignmentContext = messageProtected ? compactText(state.latestDelegationNarration, 6000) : '';
|
|
238
239
|
record.sharedGoal = compactText(state.latestUser, 6000);
|
|
239
240
|
record.startedAt = timestamp(row.timestamp, record.startedAt || session.updatedAt);
|
|
240
241
|
state.collaboration.addCommunication({
|
|
241
242
|
id: `assign:${callId}`,
|
|
242
243
|
kind: 'assignment',
|
|
243
|
-
label:
|
|
244
|
+
label: '새 작업 배정',
|
|
244
245
|
from: session.agentPath || '/root',
|
|
245
246
|
to: record.agentPath,
|
|
246
247
|
taskName: record.taskName,
|
|
247
248
|
text: record.assignment,
|
|
248
|
-
protected: record.assignmentProtected
|
|
249
|
+
protected: record.assignmentProtected,
|
|
249
250
|
assignmentSource: record.assignmentSource,
|
|
250
251
|
timestamp: row.timestamp,
|
|
251
252
|
});
|
|
@@ -156,7 +156,7 @@ function outputStatus(value, explicitError = false) {
|
|
|
156
156
|
if (firstLine.startsWith('script running')) return 'running';
|
|
157
157
|
const code = exitCode(text);
|
|
158
158
|
if (explicitError || (code != null && code !== 0) || /(?:script|command|process)\s+failed|isError["':\s]+true|fatal error/i.test(text)) return 'failed';
|
|
159
|
-
if (
|
|
159
|
+
if (/^(?:command|process)\s+(?:is\s+)?(?:still\s+)?running\b|\brunning\s+in\s+(?:the\s+)?background\b|^background\s+(?:task|command)\b.*\brunning\b|\byielded\b/i.test(firstLine)) return 'running';
|
|
160
160
|
return 'completed';
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -270,6 +270,30 @@ function createExecutionTracker(options = {}) {
|
|
|
270
270
|
return { activities, recordCall, recordOutput, finalize };
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
function reconcileExecutionActivities(activities = [], options = {}) {
|
|
274
|
+
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|
|
275
|
+
const staleAfterMs = Math.max(0, Number(options.staleAfterMs || 0));
|
|
276
|
+
const turnSettled = Boolean(options.turnFinished || options.waitingForUser);
|
|
277
|
+
return activities.map((activity) => {
|
|
278
|
+
if (!activity || activity.status !== 'running') return activity;
|
|
279
|
+
const observedAt = Date.parse(activity.updatedAt || activity.startedAt || 0);
|
|
280
|
+
const observationAge = Number.isFinite(observedAt) ? Math.max(0, now - observedAt) : Number.POSITIVE_INFINITY;
|
|
281
|
+
const foregroundEnded = activity.mode !== 'background' && turnSettled;
|
|
282
|
+
const backgroundUnobserved = activity.mode === 'background'
|
|
283
|
+
&& staleAfterMs > 0
|
|
284
|
+
&& observationAge >= staleAfterMs;
|
|
285
|
+
if (!foregroundEnded && !backgroundUnobserved) return activity;
|
|
286
|
+
return {
|
|
287
|
+
...activity,
|
|
288
|
+
status: 'unverified',
|
|
289
|
+
statusDetail: foregroundEnded
|
|
290
|
+
? '턴 종료 후 실행 상태 미확인'
|
|
291
|
+
: '최근 실행 신호 없음',
|
|
292
|
+
completedAt: null,
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
273
297
|
module.exports = {
|
|
274
298
|
CONTINUATION_TOOLS,
|
|
275
299
|
SHELL_TOOLS,
|
|
@@ -277,6 +301,7 @@ module.exports = {
|
|
|
277
301
|
executionInput,
|
|
278
302
|
outputStatus,
|
|
279
303
|
outputText,
|
|
304
|
+
reconcileExecutionActivities,
|
|
280
305
|
runtimeHandle,
|
|
281
306
|
toolName,
|
|
282
307
|
};
|
|
@@ -45,8 +45,8 @@ function createHierarchyAttacher(dependencies) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function createHistoryChild(parent, record) {
|
|
48
|
-
const externalId = String(record.childId || `${parent.externalId}:spawn:${record.callId}`).replace(/^codex:/, '');
|
|
49
|
-
const child = baseSession(
|
|
48
|
+
const externalId = String(record.childId || `${parent.externalId}:spawn:${record.callId}`).replace(/^(?:codex|claude):/, '');
|
|
49
|
+
const child = baseSession(parent.provider, externalId, '', {
|
|
50
50
|
mtimeMs: Date.parse(record.completedAt || record.startedAt || parent.updatedAt) || Date.now(),
|
|
51
51
|
});
|
|
52
52
|
child.id = record.childId || child.id;
|
|
@@ -69,7 +69,7 @@ function createHierarchyAttacher(dependencies) {
|
|
|
69
69
|
child.completionObserved = child.status === 'completed';
|
|
70
70
|
child.result = record.result || '';
|
|
71
71
|
child.source = 'collaboration-history';
|
|
72
|
-
child.sourceLabel = 'Codex 협업 이벤트';
|
|
72
|
+
child.sourceLabel = parent.provider === 'claude' ? 'Claude 협업 이벤트' : 'Codex 협업 이벤트';
|
|
73
73
|
child.clientKind = parent.clientKind;
|
|
74
74
|
child.model = parent.model;
|
|
75
75
|
child.cwd = parent.cwd;
|
|
@@ -131,6 +131,7 @@ function createHierarchyAttacher(dependencies) {
|
|
|
131
131
|
assignmentObserved: record.assignmentObserved,
|
|
132
132
|
assignmentProtected: record.assignmentProtected,
|
|
133
133
|
assignmentSource: record.assignmentSource,
|
|
134
|
+
assignmentContext: record.assignmentContext || '',
|
|
134
135
|
sharedGoal: record.sharedGoal || child.sharedGoal || parent.title,
|
|
135
136
|
result: record.result,
|
|
136
137
|
startedAt: record.startedAt,
|
|
@@ -151,6 +152,7 @@ function createHierarchyAttacher(dependencies) {
|
|
|
151
152
|
assignmentObserved: false,
|
|
152
153
|
assignmentProtected: false,
|
|
153
154
|
assignmentSource: 'unavailable',
|
|
155
|
+
assignmentContext: '',
|
|
154
156
|
sharedGoal: child.sharedGoal || parent.title,
|
|
155
157
|
status: child.status === 'completed' ? 'completed' : (child.status === 'running' ? 'running' : 'idle'),
|
|
156
158
|
startedAt: child.startedAt,
|
|
@@ -169,6 +171,7 @@ function createHierarchyAttacher(dependencies) {
|
|
|
169
171
|
assignmentObserved: false,
|
|
170
172
|
assignmentProtected: true,
|
|
171
173
|
assignmentSource: 'unavailable',
|
|
174
|
+
assignmentContext: '',
|
|
172
175
|
sharedGoal: child.sharedGoal || parent.title,
|
|
173
176
|
result: child.result || '',
|
|
174
177
|
startedAt: child.startedAt,
|
package/src/agentMonitor.js
CHANGED
|
@@ -492,10 +492,17 @@ class AgentMonitor extends EventEmitter {
|
|
|
492
492
|
|
|
493
493
|
parseFile(info, parser, variant = '') {
|
|
494
494
|
const key = `${info.file}|${info.mtimeMs}|${info.size}|${variant}`;
|
|
495
|
-
const
|
|
496
|
-
|
|
495
|
+
const cachedEntry = this.parseCache.get(key);
|
|
496
|
+
const cached = cachedEntry && cachedEntry.value || cachedEntry;
|
|
497
|
+
const parsedAt = Number(cachedEntry && cachedEntry.parsedAt || 0);
|
|
498
|
+
const timeSensitive = Boolean(cached && (
|
|
499
|
+
cached.status === 'running'
|
|
500
|
+
|| cached.status === 'starting'
|
|
501
|
+
|| (cached.executions || []).some(execution => execution.status === 'running')
|
|
502
|
+
));
|
|
503
|
+
if (cached && (!timeSensitive || Date.now() - parsedAt < ACTIVE_THRESHOLD_MS)) return cached;
|
|
497
504
|
const value = parser(info);
|
|
498
|
-
if (value) this.parseCache.set(key, value);
|
|
505
|
+
if (value) this.parseCache.set(key, { value, parsedAt: Date.now() });
|
|
499
506
|
if (this.parseCache.size > 500) {
|
|
500
507
|
const keep = [...this.parseCache.entries()].slice(-300);
|
|
501
508
|
this.parseCache = new Map(keep);
|
package/src/contracts.js
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* @property {TokenUsage} usage
|
|
43
43
|
* @property {Array<Object>} messages
|
|
44
44
|
* @property {Array<Object>} lifecycle
|
|
45
|
-
* @property {Array<{id:string,kind:'shell'|'background',mode:'foreground'|'background',tool:string,runtime:string,label:string,command:string,cwd:string,status:'running'|'completed'|'failed',statusDetail:string,output:string,backgroundId:string,exitCode:number|null,startedAt:string|null,updatedAt:string|null,completedAt:string|null}>} executions Logged regular and background command runs owned by this AI session.
|
|
45
|
+
* @property {Array<{id:string,kind:'shell'|'background',mode:'foreground'|'background',tool:string,runtime:string,label:string,command:string,cwd:string,status:'running'|'completed'|'failed'|'unverified',statusDetail:string,output:string,backgroundId:string,exitCode:number|null,startedAt:string|null,updatedAt:string|null,completedAt:string|null}>} executions Logged regular and background command runs owned by this AI session. `unverified` means the last running observation is no longer fresh enough to claim current liveness.
|
|
46
46
|
* @property {CollaborationSummary|null} collaboration
|
|
47
47
|
* @property {{kind:string,iteration:number,phase?:string}|boolean|null} loop Safe execution-loop metadata; internal goal text is never included.
|
|
48
48
|
* @property {{required:boolean,kind:string,summary:string,requestedAt:string|null,source:string,confidence:string}} attention Actionable reason why the session needs the user.
|
package/src/monitorWorker.js
CHANGED
|
@@ -107,6 +107,7 @@ function cardCollaboration(value) {
|
|
|
107
107
|
assignmentObserved: Boolean(record.assignmentObserved),
|
|
108
108
|
assignmentProtected: Boolean(record.assignmentProtected),
|
|
109
109
|
assignmentSource: clip(record.assignmentSource, 80),
|
|
110
|
+
assignmentContext: clip(record.assignmentContext, 1200),
|
|
110
111
|
sharedGoal: clip(record.sharedGoal, 1200),
|
|
111
112
|
status: record.status,
|
|
112
113
|
startedAt: record.startedAt,
|
|
@@ -194,6 +195,7 @@ function cardSession(session) {
|
|
|
194
195
|
assignmentObserved: Boolean(session.delegation.assignmentObserved),
|
|
195
196
|
assignmentProtected: Boolean(session.delegation.assignmentProtected),
|
|
196
197
|
assignmentSource: clip(session.delegation.assignmentSource, 80),
|
|
198
|
+
assignmentContext: clip(session.delegation.assignmentContext, 1200),
|
|
197
199
|
sharedGoal: clip(session.delegation.sharedGoal, 1200),
|
|
198
200
|
result: clip(session.delegation.result, 1200),
|
|
199
201
|
startedAt: session.delegation.startedAt,
|