loadtoagent 1.1.0 → 1.3.2
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/README.ko.md +14 -5
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +25 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +157 -38
- package/package.json +13 -4
- package/preload.js +13 -2
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -19
- package/renderer/app-dashboard.js +196 -55
- package/renderer/app-drawer-content.js +48 -44
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +72 -54
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -13
- package/renderer/app-events-navigation.js +77 -30
- package/renderer/app-events-sessions.js +156 -9
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +20 -38
- package/renderer/app-graph-orchestration.js +15 -13
- package/renderer/app-graph-view.js +228 -113
- package/renderer/app-management.js +257 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +104 -34
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +52 -44
- package/renderer/app.js +153 -75
- package/renderer/i18n-messages.js +867 -37
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +154 -67
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +16 -10
- package/renderer/styles-cards.css +44 -18
- package/renderer/styles-collaboration.css +23 -23
- package/renderer/styles-components.css +122 -45
- package/renderer/styles-management.css +364 -0
- package/renderer/styles-onboarding.css +8 -8
- package/renderer/styles-overlays.css +13 -7
- package/renderer/styles-product.css +48 -40
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +1040 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +25 -17
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +39 -5
- package/renderer/styles-run-composer.css +28 -23
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +176 -16
- package/renderer/styles-terminal.css +374 -37
- package/renderer/styles-tmux.css +49 -49
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +116 -49
- package/renderer/styles.css +34 -19
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +253 -37
- package/renderer/terminal-workbench.js +208 -89
- package/renderer/terminal.js +350 -35
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +14 -10
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -14
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +80 -22
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +261 -0
- package/src/terminalHost.js +453 -0
- package/src/terminalHostDaemon.js +61 -0
- package/src/terminalManager.js +218 -6
- package/src/updateInstaller.js +175 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const { createExecutionTracker } = require('./executionActivity');
|
|
4
5
|
|
|
5
6
|
function createClaudeParser(dependencies) {
|
|
6
7
|
const {
|
|
@@ -18,6 +19,8 @@ function createClaudeParser(dependencies) {
|
|
|
18
19
|
sumUsage,
|
|
19
20
|
timestamp,
|
|
20
21
|
trimSession,
|
|
22
|
+
assistantRequestsUserResponse,
|
|
23
|
+
isUserInputTool,
|
|
21
24
|
} = dependencies;
|
|
22
25
|
|
|
23
26
|
function normalizeUsage(raw = {}) {
|
|
@@ -30,18 +33,27 @@ function createClaudeParser(dependencies) {
|
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
function utilityKind(value) {
|
|
37
|
+
const raw = compactText(value, 12000);
|
|
38
|
+
if (/^Extract durable memory candidates from this Claude Code transcript tail/i.test(raw)
|
|
39
|
+
|| /^You are a memory extraction/i.test(raw)) return 'memory-extraction';
|
|
40
|
+
if (/^Reply with exactly OK\. Do not use tools\.?$/i.test(raw)) return 'authentication-check';
|
|
41
|
+
if (/^Approved command prefix saved:/i.test(raw)) return 'command-approval';
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
function visibleUserText(value) {
|
|
34
46
|
const raw = compactText(value, 12000);
|
|
35
47
|
if (!raw) return '';
|
|
36
48
|
const objective = raw.match(/<objective>\s*([\s\S]*?)\s*<\/objective>/i);
|
|
37
49
|
if (objective) return compactText(objective[1], 6000);
|
|
38
50
|
if (/^<(?:local-command-[^>]+|command-name|command-message|system-reminder|task-notification)>/i.test(raw)) return '';
|
|
39
|
-
if (
|
|
51
|
+
if (utilityKind(raw)) return '';
|
|
40
52
|
if (/^(?:Updated task #\d+|Your questions have been answered:)/i.test(raw)) return '';
|
|
41
53
|
return raw;
|
|
42
54
|
}
|
|
43
55
|
|
|
44
|
-
function recordContent(session, row, item, index) {
|
|
56
|
+
function recordContent(session, state, row, item, index) {
|
|
45
57
|
const kind = item && item.type;
|
|
46
58
|
const id = `${row.uuid || row.requestId || session.externalId}:${index}`;
|
|
47
59
|
if (kind === 'text' && item.text) {
|
|
@@ -49,6 +61,10 @@ function createClaudeParser(dependencies) {
|
|
|
49
61
|
if (text) addMessage(session, { id, role: row.message.role, text, timestamp: row.timestamp });
|
|
50
62
|
} else if (kind === 'tool_use') {
|
|
51
63
|
const name = item.name || 'tool';
|
|
64
|
+
const callId = item.id || id;
|
|
65
|
+
const args = item.input && typeof item.input === 'object' ? item.input : {};
|
|
66
|
+
state.toolCalls.set(String(callId), { name, args });
|
|
67
|
+
state.executionTracker.recordCall({ name, callId, args, rawInput: item.input, at: row.timestamp });
|
|
52
68
|
addMessage(session, {
|
|
53
69
|
id,
|
|
54
70
|
role: 'tool',
|
|
@@ -68,6 +84,15 @@ function createClaudeParser(dependencies) {
|
|
|
68
84
|
timestamp: row.timestamp,
|
|
69
85
|
});
|
|
70
86
|
} else if (kind === 'tool_result') {
|
|
87
|
+
const call = state.toolCalls.get(String(item.tool_use_id || ''));
|
|
88
|
+
state.executionTracker.recordOutput({
|
|
89
|
+
name: call && call.name,
|
|
90
|
+
callId: item.tool_use_id,
|
|
91
|
+
args: call && call.args || {},
|
|
92
|
+
output: item.content || item,
|
|
93
|
+
at: row.timestamp,
|
|
94
|
+
isError: item.is_error === true,
|
|
95
|
+
});
|
|
71
96
|
settleLifecycle(session, item.tool_use_id, item.is_error ? 'failed' : 'done', row.timestamp);
|
|
72
97
|
addLifecycle(session, {
|
|
73
98
|
id: `result:${item.tool_use_id || id}`,
|
|
@@ -106,19 +131,47 @@ function createClaudeParser(dependencies) {
|
|
|
106
131
|
|
|
107
132
|
function processMessageRow(session, state, row) {
|
|
108
133
|
const role = row.message.role === 'assistant' ? 'assistant' : 'user';
|
|
134
|
+
const internalUserRow = role === 'user' && Boolean(row.isMeta || row.sourceToolUseID);
|
|
109
135
|
state.lastRole = role;
|
|
110
136
|
if (row.message.model) session.model = row.message.model;
|
|
111
137
|
const content = Array.isArray(row.message.content)
|
|
112
138
|
? row.message.content
|
|
113
139
|
: [{ type: 'text', text: row.message.content }];
|
|
114
|
-
content.forEach((item, index) =>
|
|
140
|
+
content.forEach((item, index) => {
|
|
141
|
+
if (internalUserRow && (!item.type || item.type === 'text')) return;
|
|
142
|
+
recordContent(session, state, row, item, index);
|
|
143
|
+
});
|
|
115
144
|
if (role === 'user') {
|
|
116
|
-
|
|
145
|
+
content.filter(item => item && item.type === 'tool_result')
|
|
146
|
+
.forEach(item => state.pendingUserInputCalls.delete(String(item.tool_use_id || '')));
|
|
147
|
+
const rawUser = content
|
|
117
148
|
.filter(item => !item.type || item.type === 'text')
|
|
118
149
|
.map(item => typeof item === 'string' ? item : item.text)
|
|
119
150
|
.filter(Boolean)
|
|
120
|
-
.join('\n')
|
|
121
|
-
if (
|
|
151
|
+
.join('\n');
|
|
152
|
+
if (!internalUserRow) {
|
|
153
|
+
const detectedUtility = utilityKind(rawUser);
|
|
154
|
+
if (detectedUtility) session.utilityKind = detectedUtility;
|
|
155
|
+
const visibleUser = visibleUserText(rawUser);
|
|
156
|
+
if (visibleUser) {
|
|
157
|
+
session.utilityKind = '';
|
|
158
|
+
state.latestUser = visibleUser;
|
|
159
|
+
state.lastConversationRole = 'user';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (role === 'assistant') {
|
|
164
|
+
const assistantText = compactText(content
|
|
165
|
+
.filter(item => item && item.type === 'text')
|
|
166
|
+
.map(item => item.text)
|
|
167
|
+
.filter(Boolean)
|
|
168
|
+
.join('\n'), 6000);
|
|
169
|
+
if (assistantText) {
|
|
170
|
+
state.lastAssistantText = assistantText;
|
|
171
|
+
state.lastConversationRole = 'assistant';
|
|
172
|
+
}
|
|
173
|
+
content.filter(item => item && item.type === 'tool_use' && isUserInputTool(item.name))
|
|
174
|
+
.forEach(item => state.pendingUserInputCalls.add(String(item.id || item.name)));
|
|
122
175
|
}
|
|
123
176
|
if (role === 'assistant' && row.message.usage) {
|
|
124
177
|
const key = row.requestId || row.message.id || row.uuid;
|
|
@@ -134,14 +187,38 @@ function createClaudeParser(dependencies) {
|
|
|
134
187
|
requestUsage: new Map(),
|
|
135
188
|
latestUser: '',
|
|
136
189
|
lastRole: '',
|
|
190
|
+
lastConversationRole: '',
|
|
191
|
+
lastAssistantText: '',
|
|
192
|
+
pendingUserInputCalls: new Set(),
|
|
193
|
+
toolCalls: new Map(),
|
|
194
|
+
executionTracker: createExecutionTracker({ compactText, timestamp }),
|
|
137
195
|
latestTs: session.updatedAt,
|
|
138
196
|
lastTurnFinished: false,
|
|
139
197
|
};
|
|
140
198
|
for (const row of rows) {
|
|
141
199
|
state.latestTs = timestamp(row.timestamp, state.latestTs);
|
|
200
|
+
if (row.cwd && !session.originCwd) session.originCwd = row.cwd;
|
|
142
201
|
if (row.cwd && !session.cwd) session.cwd = row.cwd;
|
|
143
202
|
if (row.gitBranch) session.branch = row.gitBranch;
|
|
144
203
|
if (row.agentId && session.depth) session.agentName = row.agentId;
|
|
204
|
+
if (row.type === 'queue-operation' && row.operation === 'enqueue' && row.content) {
|
|
205
|
+
const detectedUtility = utilityKind(row.content);
|
|
206
|
+
if (detectedUtility) session.utilityKind = detectedUtility;
|
|
207
|
+
const visibleUser = visibleUserText(row.content);
|
|
208
|
+
const queueTitle = /^\//.test(visibleUser)
|
|
209
|
+
? compactText(visibleUser.split(/\r?\n/)[0], 6000)
|
|
210
|
+
: visibleUser;
|
|
211
|
+
if (queueTitle) {
|
|
212
|
+
session.utilityKind = '';
|
|
213
|
+
state.latestUser = queueTitle;
|
|
214
|
+
state.lastRole = 'user';
|
|
215
|
+
state.lastConversationRole = 'user';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (row.type === 'last-prompt' && !state.latestUser && row.lastPrompt) {
|
|
219
|
+
const visibleUser = visibleUserText(row.lastPrompt);
|
|
220
|
+
if (visibleUser) state.latestUser = visibleUser;
|
|
221
|
+
}
|
|
145
222
|
if (row.type === 'system' && row.subtype === 'init') {
|
|
146
223
|
session.model = row.model || session.model;
|
|
147
224
|
addLifecycle(session, {
|
|
@@ -164,13 +241,25 @@ function createClaudeParser(dependencies) {
|
|
|
164
241
|
session.updatedAt = state.latestTs;
|
|
165
242
|
session.startedAt = timestamp(parsed.rows[0].timestamp, session.updatedAt);
|
|
166
243
|
session.usage = sumUsage([...state.requestUsage.values()]);
|
|
244
|
+
session.executions = state.executionTracker.finalize();
|
|
245
|
+
const utilityTitle = session.utilityKind === 'memory-extraction'
|
|
246
|
+
? 'Claude 백그라운드 메모리 추출'
|
|
247
|
+
: (session.utilityKind === 'authentication-check' ? 'Claude 인증 점검' : '');
|
|
167
248
|
session.title = compactText(state.latestUser, 180)
|
|
249
|
+
|| utilityTitle
|
|
168
250
|
|| (session.depth ? `Claude ${session.agentName}` : 'Claude 세션');
|
|
169
251
|
const currentInput = session.turnUsage.input + session.turnUsage.cachedInput
|
|
170
252
|
+ session.turnUsage.cacheWrite + session.turnUsage.output + session.turnUsage.reasoning;
|
|
171
253
|
session.context = contextInfo(currentInput, modelContextWindow('claude', session.model, 0));
|
|
172
254
|
const age = Date.now() - fileInfo.mtimeMs;
|
|
173
|
-
|
|
255
|
+
const pendingUserInput = state.pendingUserInputCalls.size > 0;
|
|
256
|
+
const conversationalInput = state.lastConversationRole === 'assistant'
|
|
257
|
+
&& assistantRequestsUserResponse(state.lastAssistantText)
|
|
258
|
+
&& (state.lastTurnFinished || age >= ACTIVE_THRESHOLD_MS);
|
|
259
|
+
if (!session.depth && (pendingUserInput || conversationalInput)) {
|
|
260
|
+
session.status = 'waiting';
|
|
261
|
+
session.statusDetail = pendingUserInput ? '선택 또는 입력 대기' : '답변 또는 선택 대기';
|
|
262
|
+
} else if (age < ACTIVE_THRESHOLD_MS && !state.lastTurnFinished) {
|
|
174
263
|
session.status = 'running';
|
|
175
264
|
session.statusDetail = state.lastRole === 'user' ? '응답 생성 중' : '도구 실행 또는 스트리밍 중';
|
|
176
265
|
} else if (state.lastRole === 'user' && age < STALE_TURN_THRESHOLD_MS) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { createCodexCollaboration } = require('./codexCollaboration');
|
|
5
|
+
const { createExecutionTracker } = require('./executionActivity');
|
|
5
6
|
|
|
6
7
|
const COLLABORATION_TOOLS = new Set([
|
|
7
8
|
'spawn_agent',
|
|
@@ -26,10 +27,12 @@ function createCodexParser(dependencies) {
|
|
|
26
27
|
},
|
|
27
28
|
textOps: {
|
|
28
29
|
agentEnvelope,
|
|
30
|
+
assistantRequestsUserResponse,
|
|
29
31
|
codexContentText,
|
|
30
32
|
codexVisibleUserText,
|
|
31
33
|
compactText,
|
|
32
34
|
encryptedCollaborationText,
|
|
35
|
+
isUserInputTool,
|
|
33
36
|
jsonObject,
|
|
34
37
|
},
|
|
35
38
|
collaborationOps: {
|
|
@@ -52,6 +55,7 @@ function createCodexParser(dependencies) {
|
|
|
52
55
|
const session = baseSession('codex', externalId, fileInfo.file, fileInfo);
|
|
53
56
|
session.truncated = parsed.truncated;
|
|
54
57
|
session.cwd = meta.cwd || '';
|
|
58
|
+
session.originCwd = meta.cwd || '';
|
|
55
59
|
session.model = meta.model || '';
|
|
56
60
|
session.branch = meta.git && meta.git.branch || '';
|
|
57
61
|
session.startedAt = timestamp(meta.timestamp || (metaRow && metaRow.timestamp), session.updatedAt);
|
|
@@ -82,14 +86,19 @@ function createCodexParser(dependencies) {
|
|
|
82
86
|
return {
|
|
83
87
|
latestUser: '',
|
|
84
88
|
latestInternalGoal: '',
|
|
89
|
+
goalContexts: new Set(),
|
|
85
90
|
latestDelegationNarration: '',
|
|
86
91
|
activeTurn: false,
|
|
87
92
|
lastTurnCompleted: false,
|
|
88
93
|
lastFinalAnswer: '',
|
|
94
|
+
lastAssistantText: '',
|
|
95
|
+
lastConversationRole: '',
|
|
96
|
+
pendingUserInputCalls: new Set(),
|
|
89
97
|
latestTs: session.updatedAt,
|
|
90
98
|
observedWindow: Number(meta.context_window || 0),
|
|
91
99
|
messageObservations: new Map(),
|
|
92
100
|
collaboration: createCodexCollaboration(session, { compactText, timestamp }),
|
|
101
|
+
executionTracker: createExecutionTracker({ compactText, timestamp }),
|
|
93
102
|
sessionStartedMs: Date.parse(session.startedAt || '') || 0,
|
|
94
103
|
};
|
|
95
104
|
}
|
|
@@ -153,7 +162,11 @@ function createCodexParser(dependencies) {
|
|
|
153
162
|
settleRunningLifecycle(session, payload.completed_at || row.timestamp);
|
|
154
163
|
session.completedAt = timestamp(payload.completed_at || row.timestamp, session.updatedAt);
|
|
155
164
|
session.completionObserved = true;
|
|
156
|
-
if (payload.last_agent_message)
|
|
165
|
+
if (payload.last_agent_message) {
|
|
166
|
+
state.lastFinalAnswer = compactText(payload.last_agent_message, 6000);
|
|
167
|
+
state.lastAssistantText = state.lastFinalAnswer;
|
|
168
|
+
state.lastConversationRole = 'assistant';
|
|
169
|
+
}
|
|
157
170
|
addLifecycle(session, {
|
|
158
171
|
id: payload.turn_id,
|
|
159
172
|
type: 'turn-complete',
|
|
@@ -170,15 +183,21 @@ function createCodexParser(dependencies) {
|
|
|
170
183
|
if (!text) return;
|
|
171
184
|
if (/<codex_internal_context(?:\s|>)/i.test(rawUser)) {
|
|
172
185
|
state.latestInternalGoal = text;
|
|
186
|
+
if (/<codex_internal_context[^>]*\bsource=["']goal["']/i.test(rawUser)) state.goalContexts.add(`${row.timestamp || ''}:${text}`);
|
|
173
187
|
return;
|
|
174
188
|
}
|
|
175
189
|
state.latestUser = text;
|
|
190
|
+
state.lastConversationRole = 'user';
|
|
176
191
|
const key = `u:${payload.client_id || row.timestamp}:${text}`;
|
|
177
192
|
addCodexMessage(session, state.messageObservations, { id: key, role: 'user', text, timestamp: row.timestamp }, 'event');
|
|
178
193
|
} else if (payload.type === 'agent_message') {
|
|
179
194
|
const text = compactText(payload.message);
|
|
180
195
|
if (text) state.latestDelegationNarration = text;
|
|
181
196
|
if (payload.phase === 'final_answer') state.lastFinalAnswer = text;
|
|
197
|
+
if (text) {
|
|
198
|
+
state.lastAssistantText = text;
|
|
199
|
+
state.lastConversationRole = 'assistant';
|
|
200
|
+
}
|
|
182
201
|
const key = `a:${row.timestamp}:${text}`;
|
|
183
202
|
addCodexMessage(session, state.messageObservations, { id: key, role: 'assistant', text, timestamp: row.timestamp }, 'event');
|
|
184
203
|
} else if (payload.type === 'agent_reasoning') {
|
|
@@ -264,6 +283,14 @@ function createCodexParser(dependencies) {
|
|
|
264
283
|
const collaborationTool = payload.namespace === 'collaboration' || COLLABORATION_TOOLS.has(name);
|
|
265
284
|
if (collaborationTool && !timing.ownCollaborationRow) return;
|
|
266
285
|
state.collaboration.calls.set(String(callId || ''), { name, args, timestamp: row.timestamp });
|
|
286
|
+
state.executionTracker.recordCall({
|
|
287
|
+
name,
|
|
288
|
+
callId,
|
|
289
|
+
args,
|
|
290
|
+
rawInput: payload.arguments || payload.input,
|
|
291
|
+
at: row.timestamp,
|
|
292
|
+
});
|
|
293
|
+
if (isUserInputTool(name)) state.pendingUserInputCalls.add(String(callId || name));
|
|
267
294
|
|
|
268
295
|
let collaborationMessageProtected = false;
|
|
269
296
|
if (collaborationTool) {
|
|
@@ -299,6 +326,15 @@ function createCodexParser(dependencies) {
|
|
|
299
326
|
function processToolOutput(session, state, row, payload) {
|
|
300
327
|
settleLifecycle(session, payload.call_id, 'done', row.timestamp);
|
|
301
328
|
const call = state.collaboration.calls.get(String(payload.call_id || ''));
|
|
329
|
+
state.executionTracker.recordOutput({
|
|
330
|
+
name: call && call.name,
|
|
331
|
+
callId: payload.call_id,
|
|
332
|
+
args: call && call.args || {},
|
|
333
|
+
output: payload.output,
|
|
334
|
+
at: row.timestamp,
|
|
335
|
+
isError: payload.is_error === true || payload.status === 'failed',
|
|
336
|
+
});
|
|
337
|
+
state.pendingUserInputCalls.delete(String(payload.call_id || ''));
|
|
302
338
|
const output = jsonObject(payload.output);
|
|
303
339
|
if (call && call.name === 'spawn_agent') {
|
|
304
340
|
const record = state.collaboration.ensureSpawn(payload.call_id);
|
|
@@ -356,10 +392,18 @@ function createCodexParser(dependencies) {
|
|
|
356
392
|
if (!text) return;
|
|
357
393
|
if (role === 'user' && /<codex_internal_context(?:\s|>)/i.test(rawText)) {
|
|
358
394
|
state.latestInternalGoal = text;
|
|
395
|
+
if (/<codex_internal_context[^>]*\bsource=["']goal["']/i.test(rawText)) state.goalContexts.add(`${row.timestamp || ''}:${text}`);
|
|
359
396
|
return;
|
|
360
397
|
}
|
|
361
|
-
if (role === 'user')
|
|
362
|
-
|
|
398
|
+
if (role === 'user') {
|
|
399
|
+
state.latestUser = text;
|
|
400
|
+
state.lastConversationRole = 'user';
|
|
401
|
+
}
|
|
402
|
+
if (role === 'assistant') {
|
|
403
|
+
state.latestDelegationNarration = text;
|
|
404
|
+
state.lastAssistantText = text;
|
|
405
|
+
state.lastConversationRole = 'assistant';
|
|
406
|
+
}
|
|
363
407
|
const key = payload.id || `message:${role}:${row.timestamp}:${text.slice(0, 80)}`;
|
|
364
408
|
addCodexMessage(session, state.messageObservations, { id: key, role, text, timestamp: row.timestamp }, 'response');
|
|
365
409
|
}
|
|
@@ -386,6 +430,7 @@ function createCodexParser(dependencies) {
|
|
|
386
430
|
};
|
|
387
431
|
if (row.type === 'turn_context') {
|
|
388
432
|
session.model = payload.model || session.model;
|
|
433
|
+
if (payload.cwd && !session.originCwd) session.originCwd = payload.cwd;
|
|
389
434
|
session.cwd = payload.cwd || session.cwd;
|
|
390
435
|
} else if (row.type === 'world_state') {
|
|
391
436
|
const retainedText = payload.state && payload.state.environments && payload.state.environments.subagents;
|
|
@@ -408,15 +453,25 @@ function createCodexParser(dependencies) {
|
|
|
408
453
|
? (session.taskName || `${session.agentName} 서브에이전트`)
|
|
409
454
|
: (compactText(state.latestUser || state.latestInternalGoal, 180) || 'GPT 작업 세션');
|
|
410
455
|
session.result = state.lastFinalAnswer;
|
|
456
|
+
if (!session.depth && state.goalContexts.size) session.loop = { kind: 'goal', iteration: state.goalContexts.size };
|
|
411
457
|
const collaboration = state.collaboration.finalize(session.collaboration.retainedAgents);
|
|
412
458
|
session.collaboration.spawns = collaboration.spawns;
|
|
413
459
|
session.collaboration.communications = collaboration.communications;
|
|
460
|
+
session.executions = state.executionTracker.finalize();
|
|
414
461
|
const windowInfo = modelContextWindow('codex', session.model, state.observedWindow);
|
|
415
462
|
session.context = contextInfo(session.turnUsage.total || session.turnUsage.input, windowInfo);
|
|
416
463
|
|
|
417
464
|
if (session.status !== 'failed') {
|
|
418
465
|
const turnAge = Date.now() - fileInfo.mtimeMs;
|
|
419
|
-
|
|
466
|
+
const pendingUserInput = state.pendingUserInputCalls.size > 0;
|
|
467
|
+
const conversationalInput = state.lastConversationRole === 'assistant'
|
|
468
|
+
&& assistantRequestsUserResponse(state.lastAssistantText || state.lastFinalAnswer)
|
|
469
|
+
&& (state.lastTurnCompleted || turnAge >= ACTIVE_THRESHOLD_MS);
|
|
470
|
+
if (!session.depth && (pendingUserInput || conversationalInput)) {
|
|
471
|
+
session.status = 'waiting';
|
|
472
|
+
session.statusDetail = pendingUserInput ? '선택 또는 입력 대기' : '답변 또는 선택 대기';
|
|
473
|
+
session.statusObserved = true;
|
|
474
|
+
} else if (state.activeTurn && turnAge < STALE_TURN_THRESHOLD_MS) {
|
|
420
475
|
session.status = 'running';
|
|
421
476
|
session.statusDetail = '턴 실행 중';
|
|
422
477
|
session.statusObserved = true;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SHELL_TOOLS = new Set([
|
|
4
|
+
'bash',
|
|
5
|
+
'cmd',
|
|
6
|
+
'exec_command',
|
|
7
|
+
'powershell',
|
|
8
|
+
'pwsh',
|
|
9
|
+
'shell',
|
|
10
|
+
'shell_command',
|
|
11
|
+
'terminal',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const CONTINUATION_TOOLS = new Set([
|
|
15
|
+
'bashoutput',
|
|
16
|
+
'taskoutput',
|
|
17
|
+
'wait',
|
|
18
|
+
'write_stdin',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function toolName(value) {
|
|
22
|
+
return String(value || '').trim().toLowerCase().split(/[.:/]/).filter(Boolean).pop() || '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function outputText(value, seen = new Set()) {
|
|
26
|
+
if (typeof value === 'string') return value;
|
|
27
|
+
if (value == null || typeof value === 'number' || typeof value === 'boolean') return value == null ? '' : String(value);
|
|
28
|
+
if (seen.has(value)) return '';
|
|
29
|
+
if (typeof value !== 'object') return '';
|
|
30
|
+
seen.add(value);
|
|
31
|
+
if (Array.isArray(value)) return value.map(item => outputText(item, seen)).filter(Boolean).join('\n');
|
|
32
|
+
return [
|
|
33
|
+
value.text,
|
|
34
|
+
value.input_text,
|
|
35
|
+
value.output_text,
|
|
36
|
+
value.message,
|
|
37
|
+
value.content,
|
|
38
|
+
value.output,
|
|
39
|
+
value.error,
|
|
40
|
+
].map(item => outputText(item, seen)).filter(Boolean).join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function quotedProperty(source, key) {
|
|
44
|
+
const pattern = new RegExp(`\\b${key}\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`, 'm');
|
|
45
|
+
const match = String(source || '').match(pattern);
|
|
46
|
+
if (!match) return '';
|
|
47
|
+
const literal = match[1];
|
|
48
|
+
if (literal.startsWith('"')) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(literal);
|
|
51
|
+
} catch (_invalidStringLiteral) {
|
|
52
|
+
return literal.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return literal.slice(1, -1)
|
|
56
|
+
.replace(/\\([\\'`])/g, '$1')
|
|
57
|
+
.replace(/\\n/g, '\n')
|
|
58
|
+
.replace(/\\r/g, '\r')
|
|
59
|
+
.replace(/\\t/g, '\t');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function booleanProperty(source, key) {
|
|
63
|
+
const match = String(source || '').match(new RegExp(`\\b${key}\\s*:\\s*(true|false)`, 'i'));
|
|
64
|
+
return match ? match[1].toLowerCase() === 'true' : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function scalarProperty(source, key) {
|
|
68
|
+
const quoted = quotedProperty(source, key);
|
|
69
|
+
if (quoted) return quoted;
|
|
70
|
+
const match = String(source || '').match(new RegExp(`\\b${key}\\s*:\\s*([A-Za-z0-9._:-]+)`, 'i'));
|
|
71
|
+
return match ? match[1] : '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function nestedToolName(name, rawInput) {
|
|
75
|
+
const normalizedName = toolName(name);
|
|
76
|
+
if (normalizedName !== 'exec') return normalizedName;
|
|
77
|
+
const match = String(rawInput || '').match(/tools\.(exec_command|write_stdin|wait)\s*\(/);
|
|
78
|
+
return match ? match[1] : normalizedName;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function executionInput(name, args = {}, rawInput = '') {
|
|
82
|
+
const normalizedName = toolName(name);
|
|
83
|
+
const source = String(rawInput || '');
|
|
84
|
+
const nestedExec = normalizedName === 'exec' && /tools\.exec_command\s*\(/.test(source);
|
|
85
|
+
const shell = SHELL_TOOLS.has(normalizedName) || nestedExec;
|
|
86
|
+
const runInBackground = args.run_in_background === true
|
|
87
|
+
|| args.background === true
|
|
88
|
+
|| args.detach === true
|
|
89
|
+
|| booleanProperty(source, 'run_in_background') === true
|
|
90
|
+
|| booleanProperty(source, 'background') === true;
|
|
91
|
+
if (!shell && !runInBackground) return null;
|
|
92
|
+
if (/^(?:task|agent|spawn_agent)$/.test(normalizedName)) return null;
|
|
93
|
+
const command = args.command || args.cmd || args.script
|
|
94
|
+
|| quotedProperty(source, 'cmd') || quotedProperty(source, 'command');
|
|
95
|
+
const cwd = args.workdir || args.cwd
|
|
96
|
+
|| quotedProperty(source, 'workdir') || quotedProperty(source, 'cwd');
|
|
97
|
+
const description = args.description || quotedProperty(source, 'description');
|
|
98
|
+
return {
|
|
99
|
+
kind: shell ? 'shell' : 'background',
|
|
100
|
+
mode: runInBackground ? 'background' : 'foreground',
|
|
101
|
+
command: String(command || ''),
|
|
102
|
+
cwd: String(cwd || ''),
|
|
103
|
+
description: String(description || ''),
|
|
104
|
+
tool: nestedExec ? 'exec_command' : normalizedName,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function runtimeLabel(activity) {
|
|
109
|
+
const value = `${activity.tool || ''} ${activity.command || ''}`.toLowerCase();
|
|
110
|
+
if (/\bpowershell\b|\bpwsh\b/.test(value)) return 'PowerShell';
|
|
111
|
+
if (/\bcmd(?:\.exe)?\b/.test(value)) return 'Command Prompt';
|
|
112
|
+
if (/\bbash\b/.test(value)) return 'Bash';
|
|
113
|
+
return activity.kind === 'shell' ? 'Shell' : 'Background';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function firstCommandLine(value, fallback = '') {
|
|
117
|
+
return String(value || '').split(/\r?\n/).map(line => line.trim()).find(Boolean) || fallback;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function runtimeHandle(value) {
|
|
121
|
+
const text = outputText(value);
|
|
122
|
+
const patterns = [
|
|
123
|
+
{ type: 'cell', pattern: /(?:cell(?:\s+id)?|cell_id)[\s:=`"']+([A-Za-z0-9._:-]+)/i },
|
|
124
|
+
{ type: 'session', pattern: /(?:session(?:\s+id)?|session_id)[\s:=`"']+([A-Za-z0-9._:-]+)/i },
|
|
125
|
+
{ type: 'task', pattern: /(?:background(?:\s+(?:task|command))?(?:\s+with)?(?:\s+id)?|task_id)[\s:=`"']+([A-Za-z0-9._:-]+)/i },
|
|
126
|
+
];
|
|
127
|
+
for (const candidate of patterns) {
|
|
128
|
+
const match = text.match(candidate.pattern);
|
|
129
|
+
if (match) return { type: candidate.type, id: match[1].replace(/[.,;]+$/, '') };
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function handleFromArgs(args = {}) {
|
|
135
|
+
const values = [
|
|
136
|
+
['cell', args.cell_id || args.cellId],
|
|
137
|
+
['session', args.session_id || args.sessionId],
|
|
138
|
+
['task', args.task_id || args.taskId || args.bash_id || args.shell_id],
|
|
139
|
+
];
|
|
140
|
+
const found = values.find(([, value]) => value != null && String(value));
|
|
141
|
+
return found ? { type: found[0], id: String(found[1]) } : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function exitCode(value) {
|
|
145
|
+
const text = outputText(value);
|
|
146
|
+
const match = text.match(/^\s*(?:exit\s+code|exit_code)\s*[:=]\s*(-?\d+)\b/im)
|
|
147
|
+
|| text.match(/\b(?:process|command)\s+exited\s+with\s+(?:exit\s+)?code\s+(-?\d+)\b/i);
|
|
148
|
+
return match ? Number(match[1]) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function outputStatus(value, explicitError = false) {
|
|
152
|
+
const text = outputText(value);
|
|
153
|
+
const firstLine = text.split(/\r?\n/).map(line => line.trim().toLowerCase()).find(Boolean) || '';
|
|
154
|
+
if (firstLine.startsWith('script failed')) return 'failed';
|
|
155
|
+
if (firstLine.startsWith('script completed')) return 'completed';
|
|
156
|
+
if (firstLine.startsWith('script running')) return 'running';
|
|
157
|
+
const code = exitCode(text);
|
|
158
|
+
if (explicitError || (code != null && code !== 0) || /(?:script|command|process)\s+failed|isError["':\s]+true|fatal error/i.test(text)) return 'failed';
|
|
159
|
+
if (/(?:script|command|process)\s+(?:is\s+)?(?:still\s+)?running|running\s+in\s+(?:the\s+)?background|background\s+(?:task|command)|yielded/i.test(text)) return 'running';
|
|
160
|
+
return 'completed';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createExecutionTracker(options = {}) {
|
|
164
|
+
const compactText = options.compactText || (value => String(value || ''));
|
|
165
|
+
const timestamp = options.timestamp || (value => value || null);
|
|
166
|
+
const activities = [];
|
|
167
|
+
const byCallId = new Map();
|
|
168
|
+
const byHandle = new Map();
|
|
169
|
+
const continuations = new Map();
|
|
170
|
+
|
|
171
|
+
function registerHandle(activity, handle) {
|
|
172
|
+
if (!activity || !handle || !handle.id) return;
|
|
173
|
+
activity.backgroundId = handle.id;
|
|
174
|
+
activity.backgroundIdType = handle.type;
|
|
175
|
+
activity.mode = 'background';
|
|
176
|
+
byHandle.set(`${handle.type}:${handle.id}`, activity);
|
|
177
|
+
byHandle.set(handle.id, activity);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function recordCall({ name, callId, args = {}, rawInput = '', at }) {
|
|
181
|
+
const normalizedName = nestedToolName(name, rawInput);
|
|
182
|
+
const effectiveArgs = args && typeof args === 'object' && !Array.isArray(args) ? { ...args } : {};
|
|
183
|
+
if (normalizedName === 'wait' && !effectiveArgs.cell_id) effectiveArgs.cell_id = scalarProperty(rawInput, 'cell_id');
|
|
184
|
+
if (normalizedName === 'write_stdin' && !effectiveArgs.session_id) effectiveArgs.session_id = scalarProperty(rawInput, 'session_id');
|
|
185
|
+
if (CONTINUATION_TOOLS.has(normalizedName)) {
|
|
186
|
+
continuations.set(String(callId || ''), { name: normalizedName, args: effectiveArgs });
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const input = executionInput(normalizedName, effectiveArgs, rawInput);
|
|
190
|
+
if (!input) return null;
|
|
191
|
+
const startedAt = timestamp(at, null);
|
|
192
|
+
const activity = {
|
|
193
|
+
id: String(callId || `execution:${activities.length}`),
|
|
194
|
+
callId: String(callId || ''),
|
|
195
|
+
kind: input.kind,
|
|
196
|
+
mode: input.mode,
|
|
197
|
+
tool: input.tool,
|
|
198
|
+
runtime: '',
|
|
199
|
+
label: compactText(input.description || firstCommandLine(input.command, input.kind === 'shell' ? '셸 명령' : '백그라운드 작업'), 180),
|
|
200
|
+
command: compactText(input.command, 1200),
|
|
201
|
+
cwd: compactText(input.cwd, 360),
|
|
202
|
+
status: 'running',
|
|
203
|
+
statusDetail: input.mode === 'background' ? '백그라운드 실행 시작' : '포그라운드 실행 시작',
|
|
204
|
+
output: '',
|
|
205
|
+
backgroundId: '',
|
|
206
|
+
backgroundIdType: '',
|
|
207
|
+
exitCode: null,
|
|
208
|
+
startedAt,
|
|
209
|
+
updatedAt: startedAt,
|
|
210
|
+
completedAt: null,
|
|
211
|
+
source: 'tool-call',
|
|
212
|
+
};
|
|
213
|
+
activity.runtime = runtimeLabel(activity);
|
|
214
|
+
activities.push(activity);
|
|
215
|
+
if (activity.callId) byCallId.set(activity.callId, activity);
|
|
216
|
+
const requestedHandle = handleFromArgs(effectiveArgs);
|
|
217
|
+
if (requestedHandle) registerHandle(activity, requestedHandle);
|
|
218
|
+
return activity;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function findContinuation(args = {}) {
|
|
222
|
+
const handle = handleFromArgs(args);
|
|
223
|
+
if (handle) return byHandle.get(`${handle.type}:${handle.id}`) || byHandle.get(handle.id) || null;
|
|
224
|
+
return [...activities].reverse().find(activity => activity.mode === 'background' && activity.status === 'running') || null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function recordOutput({ name, callId, args = {}, output, at, isError = false }) {
|
|
228
|
+
const pendingContinuation = continuations.get(String(callId || ''));
|
|
229
|
+
const normalizedName = pendingContinuation && pendingContinuation.name || toolName(name);
|
|
230
|
+
const effectiveArgs = pendingContinuation && pendingContinuation.args || args;
|
|
231
|
+
const continuation = CONTINUATION_TOOLS.has(normalizedName);
|
|
232
|
+
const activity = continuation ? findContinuation(effectiveArgs) : byCallId.get(String(callId || ''));
|
|
233
|
+
if (!activity) {
|
|
234
|
+
continuations.delete(String(callId || ''));
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const status = outputStatus(output, isError);
|
|
238
|
+
const observedOutput = compactText(outputText(output), 2400);
|
|
239
|
+
const argumentHandle = handleFromArgs(effectiveArgs);
|
|
240
|
+
const handle = argumentHandle || (status === 'running' ? runtimeHandle(output) : null);
|
|
241
|
+
if (handle && (status === 'running' || continuation || activity.mode === 'background')) registerHandle(activity, handle);
|
|
242
|
+
const code = exitCode(output);
|
|
243
|
+
activity.status = status;
|
|
244
|
+
if (observedOutput) {
|
|
245
|
+
activity.output = continuation && activity.output
|
|
246
|
+
? compactText(`${activity.output}\n${observedOutput}`, 2400)
|
|
247
|
+
: observedOutput;
|
|
248
|
+
}
|
|
249
|
+
activity.exitCode = code;
|
|
250
|
+
activity.updatedAt = timestamp(at, activity.updatedAt);
|
|
251
|
+
if (status === 'running') {
|
|
252
|
+
activity.mode = 'background';
|
|
253
|
+
activity.statusDetail = handle && handle.id
|
|
254
|
+
? `백그라운드 ${handle.type} ${handle.id}`
|
|
255
|
+
: '백그라운드에서 계속 실행 중';
|
|
256
|
+
} else {
|
|
257
|
+
activity.completedAt = activity.updatedAt;
|
|
258
|
+
activity.statusDetail = status === 'failed'
|
|
259
|
+
? (code == null ? '실행 실패' : `종료 코드 ${code}`)
|
|
260
|
+
: (code == null ? '실행 완료' : `종료 코드 ${code}`);
|
|
261
|
+
}
|
|
262
|
+
continuations.delete(String(callId || ''));
|
|
263
|
+
return activity;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function finalize(limit = 120) {
|
|
267
|
+
return activities.slice(-limit).map(activity => ({ ...activity }));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { activities, recordCall, recordOutput, finalize };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = {
|
|
274
|
+
CONTINUATION_TOOLS,
|
|
275
|
+
SHELL_TOOLS,
|
|
276
|
+
createExecutionTracker,
|
|
277
|
+
executionInput,
|
|
278
|
+
outputStatus,
|
|
279
|
+
outputText,
|
|
280
|
+
runtimeHandle,
|
|
281
|
+
toolName,
|
|
282
|
+
};
|