loadtoagent 1.3.5 → 1.3.7
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 +1 -1
- package/renderer/app-dashboard.js +90 -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 +831 -31
- package/renderer/styles-readability.css +30 -15
- package/renderer/styles-responsive-shell.css +37 -9
- 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
|
@@ -16,6 +16,13 @@
|
|
|
16
16
|
--panel-2: #121d28;
|
|
17
17
|
--panel-3: #172532;
|
|
18
18
|
--focus: #99ebff;
|
|
19
|
+
--space-1: 4px;
|
|
20
|
+
--space-2: 8px;
|
|
21
|
+
--space-3: 12px;
|
|
22
|
+
--space-4: 16px;
|
|
23
|
+
--space-5: 24px;
|
|
24
|
+
--control-gap: 12px;
|
|
25
|
+
--control-height: 44px;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
html,
|
|
@@ -118,6 +125,10 @@ body[data-current-view="all"] #sessionSection { order: 8; }
|
|
|
118
125
|
min-height: 48px;
|
|
119
126
|
}
|
|
120
127
|
|
|
128
|
+
.top-actions {
|
|
129
|
+
gap: var(--space-4);
|
|
130
|
+
}
|
|
131
|
+
|
|
121
132
|
.sync-state.connection-error > span {
|
|
122
133
|
background: var(--red, #ff7373);
|
|
123
134
|
box-shadow: 0 0 9px rgba(255, 115, 115, .55);
|
|
@@ -474,14 +485,7 @@ body :is(input, textarea)::placeholder {
|
|
|
474
485
|
}
|
|
475
486
|
|
|
476
487
|
.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;
|
|
488
|
+
grid-template-columns: minmax(0, 1fr);
|
|
485
489
|
}
|
|
486
490
|
|
|
487
491
|
.terminal-font-controls {
|
|
@@ -886,7 +890,7 @@ body :is(
|
|
|
886
890
|
|
|
887
891
|
body button:not(.icon-button) {
|
|
888
892
|
min-width: 44px;
|
|
889
|
-
min-height:
|
|
893
|
+
min-height: var(--control-height) !important;
|
|
890
894
|
font-size: 12px !important;
|
|
891
895
|
line-height: 1.35;
|
|
892
896
|
}
|
|
@@ -914,7 +918,6 @@ body :is(
|
|
|
914
918
|
.terminal-workspace-actions,
|
|
915
919
|
.terminal-key-actions,
|
|
916
920
|
.terminal-tmux-tools,
|
|
917
|
-
.terminal-reorder-actions,
|
|
918
921
|
.tmux-section-actions,
|
|
919
922
|
.tmux-pane-actions,
|
|
920
923
|
.run-modal-actions,
|
|
@@ -924,7 +927,7 @@ body :is(
|
|
|
924
927
|
.detail-meta-actions,
|
|
925
928
|
.mobile-bottom-nav
|
|
926
929
|
) {
|
|
927
|
-
gap:
|
|
930
|
+
gap: var(--control-gap) !important;
|
|
928
931
|
}
|
|
929
932
|
|
|
930
933
|
/* Keep adjacent controls visually and physically distinct at every breakpoint. */
|
|
@@ -938,18 +941,30 @@ body :is(
|
|
|
938
941
|
.run-prompt-examples,
|
|
939
942
|
.path-field
|
|
940
943
|
) {
|
|
941
|
-
gap:
|
|
944
|
+
gap: var(--control-gap) !important;
|
|
942
945
|
}
|
|
943
946
|
|
|
944
947
|
.sidebar .section-title {
|
|
945
948
|
height: auto;
|
|
946
949
|
min-height: 44px;
|
|
947
|
-
margin-bottom:
|
|
950
|
+
margin-bottom: var(--space-3);
|
|
948
951
|
padding-block: 2px;
|
|
949
952
|
}
|
|
950
953
|
|
|
951
954
|
.sidebar .workspace-list {
|
|
952
|
-
gap:
|
|
955
|
+
gap: 10px !important;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.session-tools,
|
|
959
|
+
.modal-actions,
|
|
960
|
+
.run-modal-actions,
|
|
961
|
+
.app-error-actions {
|
|
962
|
+
gap: var(--control-gap) !important;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.modal-actions,
|
|
966
|
+
.run-modal-actions {
|
|
967
|
+
margin-top: var(--space-5);
|
|
953
968
|
}
|
|
954
969
|
|
|
955
970
|
.run-modal.terminal-modal :is(.field-label, label, small),
|
|
@@ -1025,7 +1040,7 @@ body .primary-button:disabled {
|
|
|
1025
1040
|
}
|
|
1026
1041
|
|
|
1027
1042
|
.mobile-bottom-nav {
|
|
1028
|
-
gap:
|
|
1043
|
+
gap: 10px !important;
|
|
1029
1044
|
}
|
|
1030
1045
|
}
|
|
1031
1046
|
|
|
@@ -7,13 +7,25 @@
|
|
|
7
7
|
|
|
8
8
|
/* Compact screens ≤ 1280px: collapse content columns and simplify the side rail. */
|
|
9
9
|
@media (max-width:1280px) {
|
|
10
|
+
body[data-current-view="all"] .topbar {
|
|
11
|
+
display: block;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body[data-current-view="all"] .top-actions {
|
|
15
|
+
width: 100%;
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
justify-content: flex-end;
|
|
18
|
+
margin-top: 16px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body[data-current-view="all"] .top-actions > * {
|
|
22
|
+
flex: 0 0 auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
.session-grid {
|
|
11
26
|
grid-template-columns: 1fr;
|
|
12
27
|
}
|
|
13
|
-
}
|
|
14
28
|
|
|
15
|
-
/* Compact screens ≤ 1100px: collapse content columns and simplify the side rail. */
|
|
16
|
-
@media (max-width:1100px) {
|
|
17
29
|
.sessions-heading {
|
|
18
30
|
align-items: flex-start;
|
|
19
31
|
flex-direction: column;
|
|
@@ -34,11 +46,6 @@
|
|
|
34
46
|
max-width: 100%;
|
|
35
47
|
overflow-x: auto;
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
.top-actions {
|
|
39
|
-
flex-wrap: wrap;
|
|
40
|
-
justify-content: flex-end;
|
|
41
|
-
}
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/* Compact screens ≤ 980px: keep project filtering readable while reducing the side rail. */
|
|
@@ -399,10 +406,17 @@
|
|
|
399
406
|
}
|
|
400
407
|
|
|
401
408
|
.chat-progress-updates,
|
|
402
|
-
.chat-turn-waiting
|
|
409
|
+
.chat-turn-waiting,
|
|
410
|
+
.subagent-call-moment,
|
|
411
|
+
.subagent-call-event {
|
|
403
412
|
margin-left: 0;
|
|
404
413
|
}
|
|
405
414
|
|
|
415
|
+
.subagent-call-moment,
|
|
416
|
+
.subagent-call-event {
|
|
417
|
+
width: 100%;
|
|
418
|
+
}
|
|
419
|
+
|
|
406
420
|
.json-object>div {
|
|
407
421
|
grid-template-columns: 1fr;
|
|
408
422
|
}
|
|
@@ -475,6 +489,20 @@
|
|
|
475
489
|
border-radius: 7px;
|
|
476
490
|
}
|
|
477
491
|
|
|
492
|
+
.subagent-call-event {
|
|
493
|
+
grid-template-columns: 32px minmax(0,1fr);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.subagent-call-anchor {
|
|
497
|
+
grid-template-columns: 26px minmax(0,1fr);
|
|
498
|
+
padding: 9px 10px;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.subagent-call-action {
|
|
502
|
+
grid-column: 2;
|
|
503
|
+
justify-items: start;
|
|
504
|
+
}
|
|
505
|
+
|
|
478
506
|
.main-stage {
|
|
479
507
|
padding-right: 11px;
|
|
480
508
|
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
|
});
|