ec-web 0.0.20 → 0.0.22
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/dist/ipc-client.js +7 -1
- package/dist/paths.js +18 -1
- package/dist/server.js +159 -28
- package/dist/sources/aid.js +69 -49
- package/dist/sources/baseagent-detector.js +60 -6
- package/dist/sources/basic-settings.js +818 -0
- package/dist/sources/gateway.js +5 -4
- package/dist/sources/monitor.js +22 -5
- package/dist/sources/msg.js +4 -96
- package/dist/sources/relation-roles.js +216 -207
- package/dist/sources/role-definitions.js +162 -331
- package/dist/sources/session-codex.js +207 -84
- package/dist/sources/system.js +10 -9
- package/dist/sources/triggers.js +82 -13
- package/dist/sources/types.js +20 -1
- package/dist/static/app.js +175 -15
- package/dist/static/basic-settings.js +1576 -0
- package/dist/static/index.html +43 -1
- package/dist/static/style.css +586 -2
- package/ecweb-wrapper.cjs +5 -1
- package/package.json +1 -1
|
@@ -77,6 +77,128 @@ function getDb() {
|
|
|
77
77
|
}
|
|
78
78
|
return _db;
|
|
79
79
|
}
|
|
80
|
+
function messageContentText(content) {
|
|
81
|
+
if (typeof content === 'string')
|
|
82
|
+
return content;
|
|
83
|
+
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
84
|
+
return typeof content.text === 'string' ? content.text.trim() : '';
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(content))
|
|
87
|
+
return '';
|
|
88
|
+
return content
|
|
89
|
+
.map((part) => {
|
|
90
|
+
if (typeof part === 'string')
|
|
91
|
+
return part;
|
|
92
|
+
if (!part || typeof part !== 'object')
|
|
93
|
+
return '';
|
|
94
|
+
if (typeof part.text === 'string')
|
|
95
|
+
return part.text;
|
|
96
|
+
return '';
|
|
97
|
+
})
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join('\n')
|
|
100
|
+
.trim();
|
|
101
|
+
}
|
|
102
|
+
function parseRolloutEvents(raw) {
|
|
103
|
+
const events = [];
|
|
104
|
+
for (const line of raw.split('\n')) {
|
|
105
|
+
if (!line.trim())
|
|
106
|
+
continue;
|
|
107
|
+
try {
|
|
108
|
+
events.push(JSON.parse(line));
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
}
|
|
112
|
+
return events;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Normalize both Codex CLI events and Codex app-server lifecycle events.
|
|
116
|
+
* The app-server writes the same message twice: response_item.message and
|
|
117
|
+
* event_msg.item_completed. We intentionally consume the latter because it
|
|
118
|
+
* carries the canonical UserMessage/AgentMessage item and stable id.
|
|
119
|
+
*/
|
|
120
|
+
export function parseCodexMessageEvent(event) {
|
|
121
|
+
if (!event || !event.payload)
|
|
122
|
+
return null;
|
|
123
|
+
const payload = event.payload;
|
|
124
|
+
if (event.type === 'response_item') {
|
|
125
|
+
if (payload.type !== 'message' || (payload.role !== 'user' && payload.role !== 'assistant'))
|
|
126
|
+
return null;
|
|
127
|
+
const text = messageContentText(payload.content ?? payload.message ?? payload.text);
|
|
128
|
+
if (!text)
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
role: payload.role,
|
|
132
|
+
text,
|
|
133
|
+
id: String(payload.id || ''),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (event.type !== 'event_msg')
|
|
137
|
+
return null;
|
|
138
|
+
if (payload.type === 'user_message' && payload.message) {
|
|
139
|
+
const text = messageContentText(payload.message);
|
|
140
|
+
if (!text)
|
|
141
|
+
return null;
|
|
142
|
+
return {
|
|
143
|
+
role: 'user',
|
|
144
|
+
text,
|
|
145
|
+
id: String(payload.id || ''),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (payload.type === 'agent_message' && payload.message) {
|
|
149
|
+
const text = messageContentText(payload.message);
|
|
150
|
+
if (!text)
|
|
151
|
+
return null;
|
|
152
|
+
return {
|
|
153
|
+
role: 'assistant',
|
|
154
|
+
text,
|
|
155
|
+
id: String(payload.id || ''),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (payload.type !== 'item_completed' || !payload.item)
|
|
159
|
+
return null;
|
|
160
|
+
const item = payload.item;
|
|
161
|
+
const role = item.type === 'UserMessage'
|
|
162
|
+
? 'user'
|
|
163
|
+
: item.type === 'AgentMessage'
|
|
164
|
+
? 'assistant'
|
|
165
|
+
: null;
|
|
166
|
+
if (!role)
|
|
167
|
+
return null;
|
|
168
|
+
const text = messageContentText(item.content ?? item.message ?? item.text);
|
|
169
|
+
if (!text)
|
|
170
|
+
return null;
|
|
171
|
+
return {
|
|
172
|
+
role,
|
|
173
|
+
text,
|
|
174
|
+
id: String(item.id || payload.id || ''),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function normalizedCodexMessages(events) {
|
|
178
|
+
// app-server emits response_item.message followed by item_completed for
|
|
179
|
+
// the same logical message; prefer lifecycle items when available. This
|
|
180
|
+
// also prevents injected context response items from being counted as user
|
|
181
|
+
// turns in an app-server rollout.
|
|
182
|
+
const hasCanonicalMessages = events.some(event => event?.type === 'event_msg' && parseCodexMessageEvent(event) !== null);
|
|
183
|
+
const out = [];
|
|
184
|
+
for (const event of events) {
|
|
185
|
+
const message = parseCodexMessageEvent(event);
|
|
186
|
+
if (!message)
|
|
187
|
+
continue;
|
|
188
|
+
if (event.type === 'response_item' && hasCanonicalMessages)
|
|
189
|
+
continue;
|
|
190
|
+
if (event.type === 'response_item' && message.role === 'user' && isSyntheticCodexContext(message.text))
|
|
191
|
+
continue;
|
|
192
|
+
out.push({ event, message });
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function isSyntheticCodexContext(text) {
|
|
197
|
+
const trimmed = text.trimStart();
|
|
198
|
+
return trimmed.startsWith('# AGENTS.md instructions')
|
|
199
|
+
|| trimmed.startsWith('<system-reminder>')
|
|
200
|
+
|| trimmed.startsWith('<environment_context>');
|
|
201
|
+
}
|
|
80
202
|
// ── 项目列表 ──
|
|
81
203
|
function listProjects() {
|
|
82
204
|
const db = getDb();
|
|
@@ -110,7 +232,7 @@ function listProjects() {
|
|
|
110
232
|
}
|
|
111
233
|
}
|
|
112
234
|
// ── 会话列表 ──
|
|
113
|
-
const CACHE_VERSION =
|
|
235
|
+
const CACHE_VERSION = 4;
|
|
114
236
|
const _metaCache = new Map();
|
|
115
237
|
const _detailCache = new Map();
|
|
116
238
|
const MAX_DETAIL_CACHE_ENTRIES = 6;
|
|
@@ -186,36 +308,29 @@ function extractMetaFromRollout(file, id) {
|
|
|
186
308
|
catch { }
|
|
187
309
|
let title = '', firstUser = '', gitBranch = '', version = '', userMsgs = 0, totalMsgs = 0;
|
|
188
310
|
let hasRealUserInput = false;
|
|
189
|
-
const
|
|
190
|
-
for (const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
hasRealUserInput = true;
|
|
208
|
-
if (!firstUser) {
|
|
209
|
-
const text = event.payload.message.trim().replace(/\s+/g, ' ');
|
|
210
|
-
firstUser = text.substring(0, 120);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// 统计 assistant 消息
|
|
214
|
-
if (event.type === 'event_msg' && event.payload?.type === 'assistant_message') {
|
|
215
|
-
totalMsgs++;
|
|
311
|
+
const events = parseRolloutEvents(raw);
|
|
312
|
+
for (const event of events) {
|
|
313
|
+
// 提取 cwd 和 version
|
|
314
|
+
if (event.type === 'session_meta' && event.payload) {
|
|
315
|
+
if (!gitBranch && event.payload.cwd)
|
|
316
|
+
gitBranch = event.payload.cwd;
|
|
317
|
+
if (!version && event.payload.cli_version)
|
|
318
|
+
version = event.payload.cli_version;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const { message } of normalizedCodexMessages(events)) {
|
|
322
|
+
if (message.role === 'user') {
|
|
323
|
+
totalMsgs++;
|
|
324
|
+
userMsgs++;
|
|
325
|
+
if (!isSyntheticCliPrompt(message.text, 'codex'))
|
|
326
|
+
hasRealUserInput = true;
|
|
327
|
+
if (!firstUser) {
|
|
328
|
+
firstUser = message.text.replace(/\s+/g, ' ').substring(0, 120);
|
|
216
329
|
}
|
|
217
330
|
}
|
|
218
|
-
|
|
331
|
+
else {
|
|
332
|
+
totalMsgs++;
|
|
333
|
+
}
|
|
219
334
|
}
|
|
220
335
|
let stat;
|
|
221
336
|
try {
|
|
@@ -241,8 +356,15 @@ function extractMetaFromRollout(file, id) {
|
|
|
241
356
|
};
|
|
242
357
|
}
|
|
243
358
|
function withThreadSource(meta, row) {
|
|
359
|
+
const dbFirstUser = typeof row.first_user_message === 'string'
|
|
360
|
+
? row.first_user_message.trim().replace(/\s+/g, ' ').substring(0, 120)
|
|
361
|
+
: '';
|
|
362
|
+
const dbTitle = typeof row.title === 'string' ? row.title.trim() : '';
|
|
244
363
|
return {
|
|
245
364
|
...meta,
|
|
365
|
+
title: dbTitle || meta.title,
|
|
366
|
+
firstUser: dbFirstUser || meta.firstUser,
|
|
367
|
+
hasRealUserInput: meta.hasRealUserInput || (!!dbFirstUser && !isSyntheticCliPrompt(dbFirstUser, 'codex')),
|
|
246
368
|
backendSource: typeof row.source === 'string' ? row.source : '',
|
|
247
369
|
threadSource: typeof row.thread_source === 'string' ? row.thread_source : '',
|
|
248
370
|
evolcoreRouted: isEvolcoreRoutedMessage(row.first_user_message),
|
|
@@ -459,20 +581,32 @@ function approvalAuditFromPayload(payload) {
|
|
|
459
581
|
}
|
|
460
582
|
function classifyCodexToolRejection(text, payload) {
|
|
461
583
|
const normalized = text.replace(/\\(["'])/g, '$1');
|
|
462
|
-
|
|
584
|
+
const hookMatch = normalized.match(/(?:Command|Tool call) blocked by PreToolUse hook:\s*(.+?)(?:\.\s+(?:Command|Tool):|$)/is);
|
|
585
|
+
const genericRejected = /Rejected\(\s*["']rejected by user["']\s*\)/i.test(normalized);
|
|
586
|
+
if (!hookMatch && !genericRejected)
|
|
463
587
|
return undefined;
|
|
464
588
|
const audit = approvalAuditFromPayload(payload);
|
|
465
|
-
const source = audit.policyCode || audit.decisionSource?.toLowerCase().includes('policy')
|
|
589
|
+
const source = hookMatch || audit.policyCode || audit.decisionSource?.toLowerCase().includes('policy')
|
|
466
590
|
? 'evolcore-policy'
|
|
467
591
|
: 'codex-approval';
|
|
592
|
+
const backendReason = hookMatch?.[1]?.trim()
|
|
593
|
+
|| 'Rejected("rejected by user")';
|
|
594
|
+
const inferredPolicyCode = audit.policyCode
|
|
595
|
+
|| (hookMatch && /工具调用已达到\s*\d+\s*次/.test(backendReason)
|
|
596
|
+
? 'proactive_tool_report_required'
|
|
597
|
+
: hookMatch && /请先用\s+ec\s+(?:group\s+send|msg\s+send)/.test(backendReason)
|
|
598
|
+
? 'proactive_first_send_required'
|
|
599
|
+
: undefined);
|
|
468
600
|
return {
|
|
469
|
-
kind: 'approval_rejected',
|
|
601
|
+
kind: source === 'evolcore-policy' ? 'policy_blocked' : 'approval_rejected',
|
|
470
602
|
source,
|
|
471
|
-
reason: 'rejected_by_user',
|
|
603
|
+
reason: source === 'evolcore-policy' ? 'policy_denied' : 'rejected_by_user',
|
|
472
604
|
executed: false,
|
|
473
605
|
evidence: audit.available ? 'structured_metadata' : 'backend_text',
|
|
474
|
-
backendReason
|
|
475
|
-
audit
|
|
606
|
+
backendReason,
|
|
607
|
+
audit: inferredPolicyCode && !audit.policyCode
|
|
608
|
+
? { ...audit, policyCode: inferredPolicyCode }
|
|
609
|
+
: audit,
|
|
476
610
|
};
|
|
477
611
|
}
|
|
478
612
|
export function parseCodexToolEvent(event) {
|
|
@@ -584,16 +718,9 @@ function readTranscriptFile(threadId, cwd, readStats) {
|
|
|
584
718
|
let lastUsageKey = '';
|
|
585
719
|
const seenToolCalls = new Set();
|
|
586
720
|
const seenToolResults = new Set();
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
let event;
|
|
591
|
-
try {
|
|
592
|
-
event = JSON.parse(line);
|
|
593
|
-
}
|
|
594
|
-
catch {
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
721
|
+
const events = parseRolloutEvents(raw);
|
|
722
|
+
const messagesByEvent = new Map(normalizedCodexMessages(events).map(({ event, message }) => [event, message]));
|
|
723
|
+
for (const event of events) {
|
|
597
724
|
// 提取 session_meta
|
|
598
725
|
if (event.type === 'session_meta' && event.payload) {
|
|
599
726
|
if (!version && event.payload.cli_version)
|
|
@@ -624,44 +751,37 @@ function readTranscriptFile(threadId, cwd, readStats) {
|
|
|
624
751
|
}
|
|
625
752
|
}
|
|
626
753
|
}
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
else if (payload.type === 'reasoning' && payload.message) {
|
|
660
|
-
// Codex reasoning 对应 Claude 的 thinking
|
|
661
|
-
const lastTurn = turns[turns.length - 1];
|
|
662
|
-
if (lastTurn && lastTurn.role === 'assistant') {
|
|
663
|
-
lastTurn.blocks.unshift({ kind: 'thinking', text: payload.message });
|
|
664
|
-
}
|
|
754
|
+
// 处理用户/助手消息。托管 app-server 使用 item_completed,旧版 CLI
|
|
755
|
+
// 使用 payload.type=user_message/agent_message;统一入口避免重复解析。
|
|
756
|
+
const message = messagesByEvent.get(event);
|
|
757
|
+
if (message?.role === 'user') {
|
|
758
|
+
totalMsgs++;
|
|
759
|
+
userMsgs++;
|
|
760
|
+
counts.userInput++;
|
|
761
|
+
turns.push({
|
|
762
|
+
role: 'user',
|
|
763
|
+
ts: event.timestamp ? Date.parse(event.timestamp) : 0,
|
|
764
|
+
uuid: message.id,
|
|
765
|
+
category: 'user_input',
|
|
766
|
+
blocks: [{ kind: 'text', text: message.text }],
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
else if (message?.role === 'assistant') {
|
|
770
|
+
totalMsgs++;
|
|
771
|
+
counts.modelOutput++;
|
|
772
|
+
turns.push({
|
|
773
|
+
role: 'assistant',
|
|
774
|
+
ts: event.timestamp ? Date.parse(event.timestamp) : 0,
|
|
775
|
+
uuid: message.id,
|
|
776
|
+
category: 'model_output',
|
|
777
|
+
blocks: [{ kind: 'text', text: message.text }],
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
else if (event.type === 'event_msg' && event.payload?.type === 'reasoning' && event.payload.message) {
|
|
781
|
+
// Codex reasoning 对应 Claude 的 thinking
|
|
782
|
+
const lastTurn = turns[turns.length - 1];
|
|
783
|
+
if (lastTurn && lastTurn.role === 'assistant') {
|
|
784
|
+
lastTurn.blocks.unshift({ kind: 'thinking', text: event.payload.message });
|
|
665
785
|
}
|
|
666
786
|
}
|
|
667
787
|
const toolEvent = parseCodexToolEvent(event);
|
|
@@ -748,6 +868,9 @@ async function buildSnapshot(params) {
|
|
|
748
868
|
metrics.totalMs = Date.now() - startedAt;
|
|
749
869
|
return {
|
|
750
870
|
...m,
|
|
871
|
+
// EvolCore topics carry the user-facing name; Codex's DB title is often
|
|
872
|
+
// the routed envelope text for AUN-hosted sessions.
|
|
873
|
+
title: bind?.name?.trim() || m.title,
|
|
751
874
|
sourceType: classifyTranscriptSource(bind, isCodexCliCandidate(m)),
|
|
752
875
|
bound: !!bind,
|
|
753
876
|
boundChannel: bind?.channelType ?? null,
|
|
@@ -781,7 +904,7 @@ async function buildSnapshot(params) {
|
|
|
781
904
|
const transcript = metas.find(meta => meta.id === sessionId);
|
|
782
905
|
const header = {
|
|
783
906
|
sessionId,
|
|
784
|
-
title: detail.title,
|
|
907
|
+
title: bind?.name?.trim() || detail.title,
|
|
785
908
|
model: detail.model,
|
|
786
909
|
gitBranch: detail.gitBranch,
|
|
787
910
|
version: detail.version,
|
package/dist/sources/system.js
CHANGED
|
@@ -11,6 +11,7 @@ import fs from 'fs';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { resolvePaths } from '../paths.js';
|
|
13
13
|
import { ipcQuery } from '../ipc-client.js';
|
|
14
|
+
import { watchMenuRequestAuth } from './types.js';
|
|
14
15
|
function readDefaultBaseagents() {
|
|
15
16
|
try {
|
|
16
17
|
const p = resolvePaths();
|
|
@@ -34,15 +35,15 @@ function readDefaultBaseagents() {
|
|
|
34
35
|
return [];
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
async function menuExec(payload) {
|
|
38
|
+
async function menuExec(payload, params) {
|
|
38
39
|
const p = resolvePaths();
|
|
39
|
-
const r = await ipcQuery(p.socket, { type: 'menu.exec', payload, auth:
|
|
40
|
+
const r = await ipcQuery(p.socket, { type: 'menu.exec', payload, auth: watchMenuRequestAuth(params) }, 5000);
|
|
40
41
|
return r?.ok ? r.response : null;
|
|
41
42
|
}
|
|
42
|
-
async function buildSnapshot() {
|
|
43
|
+
async function buildSnapshot(params = {}) {
|
|
43
44
|
const [listResp, sysResp] = await Promise.all([
|
|
44
|
-
menuExec({ type: 'menu.list', id: 'sys-list' }),
|
|
45
|
-
menuExec({ type: 'menu.query', id: 'sys-q', name: 'system' }),
|
|
45
|
+
menuExec({ type: 'menu.list', id: 'sys-list' }, params),
|
|
46
|
+
menuExec({ type: 'menu.query', id: 'sys-q', name: 'system' }, params),
|
|
46
47
|
]);
|
|
47
48
|
const daemonRunning = listResp !== null;
|
|
48
49
|
// sysResp 形如 { type:'menu.response', id, name, data | error }
|
|
@@ -58,17 +59,17 @@ async function buildSnapshot() {
|
|
|
58
59
|
}
|
|
59
60
|
export const systemSource = {
|
|
60
61
|
kind: 'system',
|
|
61
|
-
async snapshot() {
|
|
62
|
-
return buildSnapshot();
|
|
62
|
+
async snapshot(params = {}) {
|
|
63
|
+
return buildSnapshot(params);
|
|
63
64
|
},
|
|
64
|
-
subscribe(
|
|
65
|
+
subscribe(params, push) {
|
|
65
66
|
let lastJson = '';
|
|
66
67
|
let stopped = false;
|
|
67
68
|
const tick = async () => {
|
|
68
69
|
if (stopped)
|
|
69
70
|
return;
|
|
70
71
|
try {
|
|
71
|
-
const snap = await buildSnapshot();
|
|
72
|
+
const snap = await buildSnapshot(params);
|
|
72
73
|
const json = JSON.stringify(snap);
|
|
73
74
|
if (json !== lastJson) {
|
|
74
75
|
lastJson = json;
|
package/dist/sources/triggers.js
CHANGED
|
@@ -11,9 +11,10 @@ import fs from 'fs';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { resolvePaths } from '../paths.js';
|
|
13
13
|
import { ipcQuery } from '../ipc-client.js';
|
|
14
|
-
|
|
14
|
+
import { watchMenuAuth, watchMenuRequestAuth } from './types.js';
|
|
15
|
+
async function menuExec(payload, params) {
|
|
15
16
|
const p = resolvePaths();
|
|
16
|
-
const r = await ipcQuery(p.socket, { type: 'menu.exec', payload, auth:
|
|
17
|
+
const r = await ipcQuery(p.socket, { type: 'menu.exec', payload, auth: watchMenuRequestAuth(params) }, 5000);
|
|
17
18
|
return r?.ok ? r.response : null;
|
|
18
19
|
}
|
|
19
20
|
function readTriggerControlToken(instanceDir) {
|
|
@@ -38,6 +39,15 @@ function readDaemonOwnerAid(p) {
|
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
function readFullAccessEnabled(p) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = JSON.parse(fs.readFileSync(p.daemonConfig, 'utf8'));
|
|
45
|
+
return raw?.fullaccess?.enabled === true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
41
51
|
function normalizeAgent(item) {
|
|
42
52
|
const aid = String(item?.value || item?.aid || '').trim();
|
|
43
53
|
if (!aid)
|
|
@@ -50,17 +60,71 @@ function normalizeAgent(item) {
|
|
|
50
60
|
baseagent: typeof item?.baseagent === 'string' ? item.baseagent : undefined,
|
|
51
61
|
};
|
|
52
62
|
}
|
|
63
|
+
function channelTypeFromKey(channelKey) {
|
|
64
|
+
const value = typeof channelKey === 'string' ? channelKey.trim() : '';
|
|
65
|
+
if (!value)
|
|
66
|
+
return '';
|
|
67
|
+
const separator = value.indexOf('#');
|
|
68
|
+
return separator > 0 ? value.slice(0, separator) : value;
|
|
69
|
+
}
|
|
70
|
+
function triggerScheduleView(definition, fallback) {
|
|
71
|
+
const source = definition?.source ?? fallback?.source;
|
|
72
|
+
if (source && typeof source === 'object') {
|
|
73
|
+
switch (source.type) {
|
|
74
|
+
case 'once': return { scheduleType: 'once', scheduleValue: '' };
|
|
75
|
+
case 'delay': return { scheduleType: 'delay', scheduleValue: String(source.afterMs ?? '') };
|
|
76
|
+
case 'at': return { scheduleType: 'at', scheduleValue: String(source.at ?? '') };
|
|
77
|
+
case 'cron': return { scheduleType: 'cron', scheduleValue: String(source.expression ?? '') };
|
|
78
|
+
case 'interval': return { scheduleType: 'interval', scheduleValue: String(source.everyMs ?? '') };
|
|
79
|
+
case 'event': return { scheduleType: 'event', scheduleValue: String(source.eventPattern ?? '') };
|
|
80
|
+
default: break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
scheduleType: String(fallback?.scheduleType ?? ''),
|
|
85
|
+
scheduleValue: String(fallback?.scheduleValue ?? ''),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function triggerDisplayFields(definition, fallback) {
|
|
89
|
+
const feedback = definition?.feedback ?? fallback?.feedback;
|
|
90
|
+
const origin = definition?.origin;
|
|
91
|
+
const target = feedback?.strategy === 'target'
|
|
92
|
+
? (feedback.target ?? fallback?.feedback?.target)
|
|
93
|
+
: feedback?.strategy === 'origin'
|
|
94
|
+
? {
|
|
95
|
+
channelKey: origin?.channelKey,
|
|
96
|
+
channelId: origin?.channelId ?? origin?.peerId,
|
|
97
|
+
channelType: origin?.channelType,
|
|
98
|
+
}
|
|
99
|
+
: null;
|
|
100
|
+
const schedule = triggerScheduleView(definition, fallback);
|
|
101
|
+
const targetChannel = target?.channelKey ?? fallback?.targetChannel ?? '';
|
|
102
|
+
const targetChannelType = target?.channelType
|
|
103
|
+
?? fallback?.targetChannelType
|
|
104
|
+
?? (feedback?.strategy === 'target'
|
|
105
|
+
? channelTypeFromKey(targetChannel)
|
|
106
|
+
: (origin?.channelType ?? channelTypeFromKey(targetChannel)));
|
|
107
|
+
return {
|
|
108
|
+
scheduleType: schedule.scheduleType,
|
|
109
|
+
scheduleValue: schedule.scheduleValue,
|
|
110
|
+
targetChannel,
|
|
111
|
+
targetChannelName: targetChannel || fallback?.targetChannelName || '',
|
|
112
|
+
targetChannelId: target?.channelId ?? fallback?.targetChannelId ?? '',
|
|
113
|
+
targetChannelType,
|
|
114
|
+
createdByPeerId: origin?.peerId ?? fallback?.createdByPeerId ?? '',
|
|
115
|
+
createdByChannel: origin?.channelKey ?? fallback?.createdByChannel ?? '',
|
|
116
|
+
createdAt: definition?.createdAt ?? fallback?.createdAt,
|
|
117
|
+
updatedAt: definition?.updatedAt ?? fallback?.updatedAt,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
53
120
|
async function listTriggersForAgent(agentAid, agentLabel) {
|
|
54
121
|
const p = resolvePaths();
|
|
55
122
|
const controlToken = readTriggerControlToken(p.instanceDir);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
agent: agentAid,
|
|
62
|
-
});
|
|
63
|
-
const items = Array.isArray(resp?.data) ? resp.data : [];
|
|
123
|
+
// ECWeb is a management surface, so use the canonical Trigger IPC rather
|
|
124
|
+
// than routing through a regular Agent channel. This also covers the
|
|
125
|
+
// daemon-owned scheduler, which has no self-agent channel in the registry.
|
|
126
|
+
const resp = await ipcQuery(p.socket, { type: 'trigger.list', agentAid, all: true, controlToken }, 5000);
|
|
127
|
+
const items = Array.isArray(resp?.triggers) ? resp.triggers : [];
|
|
64
128
|
return Promise.all(items.map(async (trigger) => {
|
|
65
129
|
const effectiveAgentAid = trigger.agentAid || trigger.schedulerAid || agentAid;
|
|
66
130
|
let definition = null;
|
|
@@ -99,14 +163,19 @@ async function listTriggersForAgent(agentAid, agentLabel) {
|
|
|
99
163
|
revision,
|
|
100
164
|
definitionError,
|
|
101
165
|
subscription,
|
|
166
|
+
...triggerDisplayFields(definition, trigger),
|
|
102
167
|
};
|
|
103
168
|
}));
|
|
104
169
|
}
|
|
105
170
|
async function buildSnapshot(params) {
|
|
106
171
|
const p = resolvePaths();
|
|
172
|
+
const fullaccessEnabled = readFullAccessEnabled(p);
|
|
173
|
+
// This capability is derived from the server-injected ECWeb connection
|
|
174
|
+
// context; browser-supplied subscription parameters are never trusted for it.
|
|
175
|
+
const canApprovePersistentFullAccess = watchMenuAuth(params).canApprovePersistentFullAccess;
|
|
107
176
|
const agent = typeof params?.agent === 'string' && params.agent ? params.agent : null;
|
|
108
177
|
const [agentsResp, runtimeAgentsResp] = await Promise.all([
|
|
109
|
-
menuExec({ type: 'menu.options', id: 'tr-agents', name: 'agent' }),
|
|
178
|
+
menuExec({ type: 'menu.options', id: 'tr-agents', name: 'agent' }, params),
|
|
110
179
|
ipcQuery(p.socket, { type: 'evolagent.list' }, 5000),
|
|
111
180
|
]);
|
|
112
181
|
// menu.options 响应形如 { type:'menu.response', id, name, data: MenuItem[] }
|
|
@@ -132,11 +201,11 @@ async function buildSnapshot(params) {
|
|
|
132
201
|
if (agent) {
|
|
133
202
|
const found = agentInfos.find((x) => x.aid === agent);
|
|
134
203
|
const triggers = await listTriggersForAgent(agent, found?.label || agent);
|
|
135
|
-
return { agents: agentInfos, triggers, selectedAgent: agent };
|
|
204
|
+
return { agents: agentInfos, triggers, selectedAgent: agent, fullaccessEnabled, canApprovePersistentFullAccess };
|
|
136
205
|
}
|
|
137
206
|
const batches = await Promise.all(agentInfos.map((x) => listTriggersForAgent(x.aid, x.label)));
|
|
138
207
|
const triggers = batches.flat();
|
|
139
|
-
return { agents: agentInfos, triggers, selectedAgent: agent };
|
|
208
|
+
return { agents: agentInfos, triggers, selectedAgent: agent, fullaccessEnabled, canApprovePersistentFullAccess };
|
|
140
209
|
}
|
|
141
210
|
export const triggersSource = {
|
|
142
211
|
kind: 'triggers',
|
package/dist/sources/types.js
CHANGED
|
@@ -7,4 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* aid/cache 走 IPC 轮询(无推送),msg/session 走 fs.watch 文件监听。
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
/** Read only the server-injected auth marker; browser parameters never decide it. */
|
|
11
|
+
export function watchMenuAuth(params) {
|
|
12
|
+
const auth = params?.__menuAuth;
|
|
13
|
+
return {
|
|
14
|
+
localDirect: auth?.localDirect === true,
|
|
15
|
+
actorAid: typeof auth?.actorAid === 'string' ? auth.actorAid : null,
|
|
16
|
+
// This marker is injected by the ECWeb server, never read from browser
|
|
17
|
+
// request parameters. Authenticated ECWeb has daemon-owner authority.
|
|
18
|
+
canApprovePersistentFullAccess: auth?.canApprovePersistentFullAccess === true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function watchMenuRequestAuth(params) {
|
|
22
|
+
const auth = watchMenuAuth(params);
|
|
23
|
+
return {
|
|
24
|
+
source: 'ecweb',
|
|
25
|
+
localDirect: auth.localDirect,
|
|
26
|
+
actorAid: auth.actorAid ?? null,
|
|
27
|
+
canApprovePersistentFullAccess: auth.canApprovePersistentFullAccess,
|
|
28
|
+
};
|
|
29
|
+
}
|