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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const LIVE_STATUSES = new Set(['starting', 'running']);
|
|
4
|
+
const COMPLETE_STATUSES = new Set(['completed', 'cancelled', 'failed']);
|
|
5
|
+
const FAILURE_PATTERN = /(?:error|failed|failure|fatal|exception|오류|실패)/i;
|
|
6
|
+
const PERMISSION_PATTERN = /(?:permission|approve|approval|권한|승인)/i;
|
|
7
|
+
const DECISION_PATTERN = /(?:choose|select|decision|선택|결정|골라)/i;
|
|
8
|
+
const INPUT_PATTERN = /(?:input|reply|answer|confirm|provide|입력|답변|확인|알려|제공)/i;
|
|
9
|
+
const TEST_PATTERN = /(?:^|[^A-Za-z0-9])(?:tests?|testing|specs?|pytest|vitest|jest|mocha)(?=$|[^A-Za-z0-9])|검증|테스트/i;
|
|
10
|
+
const FAILED_CHECK_STATUSES = new Set(['failed', 'failure', 'error', 'errored']);
|
|
11
|
+
const RUNNING_CHECK_STATUSES = new Set(['running', 'pending', 'started', 'starting', 'in-progress', 'in_progress']);
|
|
12
|
+
const PASSED_CHECK_STATUSES = new Set(['passed', 'completed', 'complete', 'done', 'success', 'succeeded']);
|
|
13
|
+
|
|
14
|
+
function text(value, limit = 1200) {
|
|
15
|
+
const output = String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
|
|
16
|
+
return output.length > limit ? `${output.slice(0, limit).trimEnd()}…` : output;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function timestamp(value) {
|
|
20
|
+
const parsed = Date.parse(value || 0);
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function latestActivity(session) {
|
|
25
|
+
const values = [session.updatedAt, session.startedAt];
|
|
26
|
+
for (const row of session.messages || []) values.push(row && row.timestamp);
|
|
27
|
+
for (const row of session.lifecycle || []) values.push(row && (row.completedAt || row.timestamp));
|
|
28
|
+
const millis = Math.max(0, ...values.map(timestamp));
|
|
29
|
+
return millis ? new Date(millis).toISOString() : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function latestMeaningfulText(session) {
|
|
33
|
+
const messages = [...(session.messages || [])].reverse();
|
|
34
|
+
const preferred = messages.find(row => row && row.role === 'assistant' && text(row.text));
|
|
35
|
+
const fallback = messages.find(row => row && text(row.text));
|
|
36
|
+
return text((preferred || fallback || {}).text || session.statusDetail || session.result || '', 360);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function checkStatus(value) {
|
|
40
|
+
const status = String(value || '').trim().toLowerCase();
|
|
41
|
+
if (FAILED_CHECK_STATUSES.has(status)) return 'failed';
|
|
42
|
+
if (RUNNING_CHECK_STATUSES.has(status)) return 'running';
|
|
43
|
+
if (PASSED_CHECK_STATUSES.has(status)) return 'passed';
|
|
44
|
+
return 'unknown';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function controlCapabilities(session) {
|
|
48
|
+
const live = LIVE_STATUSES.has(session.status);
|
|
49
|
+
const managed = Boolean(session.runId);
|
|
50
|
+
const resumable = Boolean(session.externalId && ['claude', 'codex', 'gemini'].includes(session.provider));
|
|
51
|
+
const directlyControllable = (session.runtimePresence || []).some(item => item && (item.terminalId || item.paneId || item.nativeId));
|
|
52
|
+
const canSend = directlyControllable || resumable;
|
|
53
|
+
return {
|
|
54
|
+
managed,
|
|
55
|
+
respond: (session.status === 'waiting' || live) && canSend,
|
|
56
|
+
approve: session.status === 'waiting' && canSend,
|
|
57
|
+
deny: session.status === 'waiting' && canSend,
|
|
58
|
+
sendInstruction: canSend,
|
|
59
|
+
stop: managed && (live || session.status === 'paused'),
|
|
60
|
+
pause: managed && session.status === 'running',
|
|
61
|
+
resume: (managed && session.status === 'paused') || (!live && resumable),
|
|
62
|
+
retry: managed && ['failed', 'cancelled'].includes(session.status),
|
|
63
|
+
reassign: Boolean(session.cwd && (session.title || session.sharedGoal || latestMeaningfulText(session))),
|
|
64
|
+
openOrigin: ['claude-desktop', 'codex-desktop'].includes(session.clientKind),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function evidenceFor(session) {
|
|
69
|
+
const statusObserved = Boolean(session.statusObserved || session.runId || session.runtimePresence?.length);
|
|
70
|
+
const delegation = session.delegation || {};
|
|
71
|
+
const hierarchyObserved = !session.parentId
|
|
72
|
+
|| Boolean(delegation.assignmentObserved && delegation.assignmentSource !== 'unavailable')
|
|
73
|
+
|| session.source === 'collaboration-history';
|
|
74
|
+
const completionObserved = Boolean(session.completionObserved || (session.runId && COMPLETE_STATUSES.has(session.status)));
|
|
75
|
+
const sources = [session.sourceLabel, statusObserved ? 'runtime-event' : 'activity-inference'];
|
|
76
|
+
if (session.parentId) sources.push(hierarchyObserved ? 'delegation-event' : 'hierarchy-inference');
|
|
77
|
+
if (completionObserved) sources.push('completion-event');
|
|
78
|
+
return {
|
|
79
|
+
confidence: statusObserved && hierarchyObserved ? 'high' : statusObserved || hierarchyObserved ? 'medium' : 'low',
|
|
80
|
+
status: statusObserved ? 'observed' : 'inferred',
|
|
81
|
+
hierarchy: hierarchyObserved ? 'observed' : 'inferred',
|
|
82
|
+
completion: completionObserved ? 'observed' : 'unverified',
|
|
83
|
+
sources: [...new Set(sources.filter(Boolean).map(value => text(value, 80)))],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function progressFor(session, attention) {
|
|
88
|
+
const lifecycle = (session.lifecycle || []).filter(Boolean);
|
|
89
|
+
const checkpoints = lifecycle.slice(-12).map((row, index) => ({
|
|
90
|
+
id: String(row.id || `${session.id}:checkpoint:${index}`),
|
|
91
|
+
label: text(row.label || row.type || 'Activity', 140),
|
|
92
|
+
detail: text(row.detail || '', 240),
|
|
93
|
+
status: ['failed', 'error'].includes(row.status) ? 'failed'
|
|
94
|
+
: ['running', 'pending', 'started'].includes(row.status) ? 'running'
|
|
95
|
+
: 'completed',
|
|
96
|
+
timestamp: row.completedAt || row.timestamp || session.updatedAt || null,
|
|
97
|
+
}));
|
|
98
|
+
const completedSteps = checkpoints.filter(row => row.status === 'completed').length;
|
|
99
|
+
const failedSteps = checkpoints.filter(row => row.status === 'failed').length;
|
|
100
|
+
const running = [...checkpoints].reverse().find(row => row.status === 'running');
|
|
101
|
+
const last = checkpoints.at(-1);
|
|
102
|
+
const totalSteps = checkpoints.length;
|
|
103
|
+
let percent = totalSteps ? Math.round(completedSteps / totalSteps * 100) : 0;
|
|
104
|
+
if (session.status === 'completed') percent = 100;
|
|
105
|
+
else if (LIVE_STATUSES.has(session.status) && percent >= 100) percent = 95;
|
|
106
|
+
const stage = session.status === 'completed' ? 'completed'
|
|
107
|
+
: session.status === 'failed' ? 'failed'
|
|
108
|
+
: session.status === 'waiting' ? 'waiting'
|
|
109
|
+
: session.status === 'paused' ? 'paused'
|
|
110
|
+
: LIVE_STATUSES.has(session.status) ? 'executing' : 'idle';
|
|
111
|
+
return {
|
|
112
|
+
stage,
|
|
113
|
+
percent,
|
|
114
|
+
completedSteps,
|
|
115
|
+
failedSteps,
|
|
116
|
+
totalSteps,
|
|
117
|
+
currentStep: text((running || last || {}).label || session.statusDetail || '', 180),
|
|
118
|
+
blocker: attention.required ? attention.summary : '',
|
|
119
|
+
lastActivityAt: latestActivity(session),
|
|
120
|
+
source: totalSteps ? 'lifecycle-events' : 'session-status',
|
|
121
|
+
checkpoints,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function extractArtifacts(session) {
|
|
126
|
+
const body = [
|
|
127
|
+
session.result,
|
|
128
|
+
...(session.messages || []).map(row => row && row.text),
|
|
129
|
+
...(session.lifecycle || []).map(row => row && `${row.label || ''} ${row.detail || ''}`),
|
|
130
|
+
].filter(Boolean).join('\n');
|
|
131
|
+
const artifacts = [];
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
const add = (kind, value, verified = false) => {
|
|
134
|
+
const clean = text(value, 260).replace(/[),.;:]+$/, '');
|
|
135
|
+
const key = `${kind}:${clean.toLowerCase()}`;
|
|
136
|
+
if (!clean || seen.has(key) || artifacts.length >= 24) return;
|
|
137
|
+
seen.add(key);
|
|
138
|
+
artifacts.push({ kind, value: clean, verified });
|
|
139
|
+
};
|
|
140
|
+
const filePattern = /(?:[A-Za-z]:\\|\/)?(?:[\w.@-]+[\\/])+[\w.@()+-]+\.[A-Za-z0-9]{1,12}/g;
|
|
141
|
+
for (const match of body.match(filePattern) || []) add(TEST_PATTERN.test(match) ? 'test' : 'file', match, false);
|
|
142
|
+
if (/(?:commit|커밋)/i.test(body)) {
|
|
143
|
+
// A hash mentioned in a log is only a candidate reference. Confirming that
|
|
144
|
+
// the commit exists belongs to repository verification, which this view
|
|
145
|
+
// intentionally does not perform.
|
|
146
|
+
for (const match of body.match(/\b[0-9a-f]{7,40}\b/gi) || []) add('commit', match, false);
|
|
147
|
+
}
|
|
148
|
+
return artifacts;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function outcomeFor(session, evidence) {
|
|
152
|
+
const artifacts = extractArtifacts(session);
|
|
153
|
+
const checks = (session.lifecycle || [])
|
|
154
|
+
.filter(row => row && TEST_PATTERN.test(`${row.label || ''} ${row.detail || ''}`))
|
|
155
|
+
.slice(-12)
|
|
156
|
+
.map(row => ({
|
|
157
|
+
label: text(row.label || row.detail || 'Test', 180),
|
|
158
|
+
status: checkStatus(row.status),
|
|
159
|
+
timestamp: row.completedAt || row.timestamp || null,
|
|
160
|
+
}));
|
|
161
|
+
const latestAssistant = [...(session.messages || [])].reverse().find(row => row && row.role === 'assistant' && text(row.text));
|
|
162
|
+
return {
|
|
163
|
+
status: session.status === 'completed' ? 'completed'
|
|
164
|
+
: session.status === 'failed' ? 'failed'
|
|
165
|
+
: session.status === 'cancelled' ? 'cancelled' : 'in-progress',
|
|
166
|
+
summary: text(session.result || (COMPLETE_STATUSES.has(session.status) && latestAssistant && latestAssistant.text) || session.statusDetail || '', 800),
|
|
167
|
+
verified: evidence.completion === 'observed',
|
|
168
|
+
verification: evidence.completion,
|
|
169
|
+
completedAt: session.completedAt || session.endedAt || null,
|
|
170
|
+
artifacts,
|
|
171
|
+
checks,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function attentionFor(session) {
|
|
176
|
+
const latest = latestMeaningfulText(session);
|
|
177
|
+
const combined = `${session.statusDetail || ''} ${latest}`;
|
|
178
|
+
let kind = 'none';
|
|
179
|
+
if (session.status === 'failed' || (session.status === 'waiting' && FAILURE_PATTERN.test(session.statusDetail || ''))) kind = 'error';
|
|
180
|
+
else if (session.status === 'paused') kind = 'paused';
|
|
181
|
+
else if (session.status === 'waiting' && PERMISSION_PATTERN.test(combined)) kind = 'approval';
|
|
182
|
+
else if (session.status === 'waiting' && DECISION_PATTERN.test(combined)) kind = 'decision';
|
|
183
|
+
else if (session.status === 'waiting' && INPUT_PATTERN.test(combined)) kind = 'input';
|
|
184
|
+
else if (session.status === 'waiting') kind = 'response';
|
|
185
|
+
const required = kind !== 'none';
|
|
186
|
+
const summaries = {
|
|
187
|
+
error: session.statusDetail || latest || 'The run failed and needs review.',
|
|
188
|
+
paused: session.statusDetail || 'The run is paused.',
|
|
189
|
+
approval: latest || session.statusDetail || 'Approval is required.',
|
|
190
|
+
decision: latest || session.statusDetail || 'A decision is required.',
|
|
191
|
+
input: latest || session.statusDetail || 'Input is required.',
|
|
192
|
+
response: latest || session.statusDetail || 'A response is required.',
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
required,
|
|
196
|
+
kind,
|
|
197
|
+
summary: required ? text(summaries[kind], 360) : '',
|
|
198
|
+
requestedAt: required ? latestActivity(session) : null,
|
|
199
|
+
source: session.statusObserved || session.runId ? 'observed-status' : 'message-inference',
|
|
200
|
+
confidence: session.statusObserved || session.runId ? 'high' : required ? 'medium' : 'low',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function healthFor(session, sessions, attention, progress, evidence, nowValue) {
|
|
205
|
+
const now = Number(nowValue || Date.now());
|
|
206
|
+
const byId = new Map((sessions || []).map(row => [row.id, row]));
|
|
207
|
+
const signals = [];
|
|
208
|
+
const add = (code, severity, detail = '') => signals.push({ code, severity, detail: text(detail, 240) });
|
|
209
|
+
const activity = timestamp(progress.lastActivityAt);
|
|
210
|
+
const ageMs = activity ? Math.max(0, now - activity) : 0;
|
|
211
|
+
if (session.status === 'failed') add('run-failed', 'critical', session.statusDetail);
|
|
212
|
+
if (session.status === 'paused') add('run-paused', 'warning', session.statusDetail);
|
|
213
|
+
if (LIVE_STATUSES.has(session.status) && ageMs >= 10 * 60_000) add('stalled', 'critical', progress.currentStep);
|
|
214
|
+
else if (LIVE_STATUSES.has(session.status) && ageMs >= 2 * 60_000) add('stale', 'warning', progress.currentStep);
|
|
215
|
+
if (session.status === 'waiting' && ageMs >= 60 * 60_000) add('waiting-too-long', 'critical', attention.summary);
|
|
216
|
+
else if (session.status === 'waiting' && ageMs >= 10 * 60_000) add('waiting-too-long', 'warning', attention.summary);
|
|
217
|
+
const contextPercent = Number(session.context && session.context.percent || 0);
|
|
218
|
+
if (contextPercent >= 90) add('context-critical', 'critical', `${contextPercent.toFixed(1)}%`);
|
|
219
|
+
else if (contextPercent >= 75) add('context-warning', 'warning', `${contextPercent.toFixed(1)}%`);
|
|
220
|
+
if (progress.failedSteps >= 2) add('repeated-failures', 'critical', String(progress.failedSteps));
|
|
221
|
+
if (session.parentId && !byId.has(session.parentId)) add('orphan-agent', 'warning', session.parentId);
|
|
222
|
+
if (evidence.confidence === 'low') add('low-confidence', 'info', evidence.sources.join(', '));
|
|
223
|
+
const rank = { info: 1, warning: 2, critical: 3 };
|
|
224
|
+
const max = signals.reduce((value, signal) => Math.max(value, rank[signal.severity] || 0), 0);
|
|
225
|
+
return {
|
|
226
|
+
level: max >= 3 ? 'critical' : max === 2 ? 'warning' : max === 1 ? 'unknown' : 'healthy',
|
|
227
|
+
score: Math.max(0, 100 - signals.reduce((sum, signal) => sum + (signal.severity === 'critical' ? 35 : signal.severity === 'warning' ? 18 : 5), 0)),
|
|
228
|
+
signals,
|
|
229
|
+
lastActivityAt: progress.lastActivityAt,
|
|
230
|
+
ageSeconds: activity ? Math.round(ageMs / 1000) : null,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function enrichSession(session, sessions = [], nowValue = Date.now()) {
|
|
235
|
+
if (!session) return session;
|
|
236
|
+
const attention = attentionFor(session);
|
|
237
|
+
const progress = progressFor(session, attention);
|
|
238
|
+
const evidence = evidenceFor(session);
|
|
239
|
+
const controls = controlCapabilities(session);
|
|
240
|
+
const health = healthFor(session, sessions, attention, progress, evidence, nowValue);
|
|
241
|
+
return {
|
|
242
|
+
...session,
|
|
243
|
+
attention,
|
|
244
|
+
progress,
|
|
245
|
+
health,
|
|
246
|
+
controlCapabilities: controls,
|
|
247
|
+
evidence,
|
|
248
|
+
outcome: outcomeFor(session, evidence),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
attentionFor,
|
|
254
|
+
controlCapabilities,
|
|
255
|
+
enrichSession,
|
|
256
|
+
evidenceFor,
|
|
257
|
+
extractArtifacts,
|
|
258
|
+
healthFor,
|
|
259
|
+
outcomeFor,
|
|
260
|
+
progressFor,
|
|
261
|
+
};
|