ec-web 0.0.28 → 0.0.29

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/server.js CHANGED
@@ -616,13 +616,31 @@ function handleConnection(ws, req, log, tokenRecord) {
616
616
  const timeoutMs = Number.isFinite(Number(msg.timeoutMs)) && Number(msg.timeoutMs) > 0
617
617
  ? Math.min(Number(msg.timeoutMs), 120_000)
618
618
  : 5000;
619
+ const ecwebRequestId = (typeof msg.requestId === 'string' || typeof msg.requestId === 'number')
620
+ ? String(msg.requestId)
621
+ : crypto.randomUUID();
622
+ const logicalToolName = typeof msg.payload?.name === 'string' && msg.payload.name.trim()
623
+ ? msg.payload.name.trim()
624
+ : typeof msg.payload?.cmd === 'string' && msg.payload.cmd.trim()
625
+ ? msg.payload.cmd.trim().split(/\s+/, 1)[0]
626
+ : typeof msg.payload?.type === 'string' ? msg.payload.type : undefined;
619
627
  const menuAuth = daemonOwnerMenuAuth(localDirect, actorAid);
620
- const resp = await ipcQuery(p.socket, { type: 'menu.exec', payload: msg.payload, auth: { source: 'ecweb', ...menuAuth } }, timeoutMs);
628
+ const resp = await ipcQuery(p.socket, {
629
+ type: 'menu.exec',
630
+ payload: msg.payload,
631
+ auth: {
632
+ source: 'ecweb',
633
+ ...menuAuth,
634
+ ecwebRequestId,
635
+ endpoint: 'WS menu.exec',
636
+ logicalToolName,
637
+ },
638
+ }, timeoutMs);
621
639
  if (resp?.ok) {
622
- send({ type: 'menu.response', requestId: msg.requestId, data: resp.response });
640
+ send({ type: 'menu.response', requestId: ecwebRequestId, data: resp.response });
623
641
  }
624
642
  else {
625
- send({ type: 'menu.response', requestId: msg.requestId, data: {
643
+ send({ type: 'menu.response', requestId: ecwebRequestId, data: {
626
644
  type: 'menu.response', id: msg.payload?.id ?? '',
627
645
  error: { code: 'INTERNAL', message: resp?.error ?? 'daemon unreachable' },
628
646
  } });
@@ -18,13 +18,13 @@ const SERVICE_VISIBILITIES = new Set(['public', 'private', 'aun-auth']);
18
18
  const SENSITIVE_METADATA_KEY = /(?:^|[_-])(endpoint|url|uri|token|access[_-]?token|authorization|cookie|secret|password|private[_-]?key|key|cert|certificate)(?:$|[_-])/i;
19
19
  const DAEMON_EXACT_PATHS = new Set([
20
20
  'owners',
21
- 'fullaccess.enabled',
22
21
  'aun.defaultAidDomain',
23
22
  'aun.minEvolVersion',
24
23
  'aun.menuTokenRequired',
25
24
  'aun.defaultEncrypt',
26
25
  'services',
27
26
  'configSnapshots',
27
+ 'restartRecovery.enabled',
28
28
  'idleMonitor.enabled',
29
29
  'idleMonitor.timeout',
30
30
  'idleMonitor.apiRetryTimeout',
@@ -97,10 +97,10 @@ const BASEAGENT_FIELDS = {
97
97
  ]),
98
98
  };
99
99
  const BOOLEAN_PATHS = new Set([
100
- 'fullaccess.enabled',
101
100
  'aun.menuTokenRequired',
102
101
  'aun.defaultEncrypt',
103
102
  'configSnapshots',
103
+ 'restartRecovery.enabled',
104
104
  'idleMonitor.enabled',
105
105
  'debug.aunTrace',
106
106
  'debug.aunSdkLog',
@@ -122,6 +122,7 @@ const RESTART_DAEMON_PREFIXES = [
122
122
  'aun.',
123
123
  'services',
124
124
  'configSnapshots',
125
+ 'restartRecovery.',
125
126
  'idleMonitor.',
126
127
  'debug.',
127
128
  ];
@@ -484,7 +485,7 @@ function validateDefaultsValue(fieldPath, value) {
484
485
  return;
485
486
  }
486
487
  if (fieldPath === 'config.mentionMode') {
487
- if (!['disabled', 'mention-only'].includes(String(value)))
488
+ if (!['disabled', 'mention-only', 'auto'].includes(String(value)))
488
489
  throw new Error(`${fieldPath} is invalid`);
489
490
  return;
490
491
  }
@@ -14,7 +14,7 @@ import { decodeDirSegment, isTransientProtocolMessage, readAllJsonlLines, readJs
14
14
  function topicMatchesArchiveFilter(chat, archived) {
15
15
  if (archived === 'all')
16
16
  return true;
17
- const isArchived = chat.partition === 'thread' && chat.lable === '归档';
17
+ const isArchived = chat.partition === 'thread' && chat.topicStatus === 'archived';
18
18
  return archived === true ? isArchived : !isArchived;
19
19
  }
20
20
  let contextModulesPromise = null;
@@ -589,8 +589,10 @@ function loadChats(peerDisplayResolver, metrics) {
589
589
  threadName: item.partition === 'thread'
590
590
  ? String(partitionActive.name || partitionActive.metadata?.topicName || item.threadId)
591
591
  : null,
592
- lable: item.partition === 'thread' && typeof partitionActive.lable === 'string'
593
- ? partitionActive.lable || null : null,
592
+ label: item.partition === 'thread'
593
+ ? partitionActive.label || (partitionActive.lable === '归档' ? null : partitionActive.lable || null) : null,
594
+ topicStatus: item.partition === 'thread'
595
+ ? partitionActive.topicStatus || (partitionActive.lable === '归档' ? 'archived' : 'pending') : null,
594
596
  updatedAt,
595
597
  messages: item.messages,
596
598
  active: partitionActive,
@@ -700,7 +702,8 @@ function mergePeerInfo(chats) {
700
702
  partition: chat.partition,
701
703
  threadId: chat.threadId,
702
704
  threadName: chat.threadName,
703
- lable: chat.lable,
705
+ label: chat.label,
706
+ topicStatus: chat.topicStatus,
704
707
  inbound: 0,
705
708
  outbound: 0,
706
709
  lastAt: 0,
@@ -831,7 +834,8 @@ async function buildSessionContext(chat) {
831
834
  sessionId: active.id || null,
832
835
  evolcoreSessionId: active.id || null,
833
836
  name: active.name || null,
834
- lable: chat.lable,
837
+ label: chat.label,
838
+ topicStatus: chat.topicStatus,
835
839
  baseagent: active.baseagent || active.agentType || null,
836
840
  chatMode,
837
841
  permissionMode,
@@ -129,6 +129,7 @@ async function authorizeRoleAssignmentRead(modules, aid, auth) {
129
129
  processRole: actor.daemonOwner ? 'daemon-owner' : 'none',
130
130
  isDaemonOwner: actor.daemonOwner,
131
131
  decision: decision.allow ? 'allow' : 'deny',
132
+ ...(decision.allow ? {} : { decisionSource: 'role', errorCode: 'ROLE_DENIED' }),
132
133
  code: decision.allow ? undefined : decision.code,
133
134
  reason: decision.allow ? undefined : decision.reason,
134
135
  matchedRule: decision.matchedRule,
@@ -209,6 +210,8 @@ async function authorizeRoleAssignmentWrite(modules, aid, auth, operation, targe
209
210
  processRole: daemonOwner ? 'daemon-owner' : 'none',
210
211
  isDaemonOwner: daemonOwner,
211
212
  decision: 'deny',
213
+ decisionSource: 'role',
214
+ errorCode: 'ROLE_DENIED',
212
215
  code: decision.code,
213
216
  reason: decision.reason,
214
217
  matchedRule: decision.matchedRule,
@@ -243,6 +246,8 @@ async function authorizeRoleAssignmentWrite(modules, aid, auth, operation, targe
243
246
  processRole: daemonOwner ? 'daemon-owner' : 'none',
244
247
  isDaemonOwner: daemonOwner,
245
248
  decision: 'deny',
249
+ decisionSource: 'role',
250
+ errorCode: 'ROLE_DENIED',
246
251
  code: 'NOT_ALLOWED',
247
252
  reason,
248
253
  matchedRule: decision.matchedRule,
@@ -189,7 +189,9 @@ const COMMAND_CONSTRAINT_KEYS = new Set([
189
189
  'requireDaemonOwner',
190
190
  'requireControlChannel',
191
191
  'requireExplicitDangerousGrant',
192
+ 'currentRelationOnly',
192
193
  'requireFieldOverride',
194
+ 'configFieldPolicy',
193
195
  'allowedArgs',
194
196
  'deniedArgs',
195
197
  'forbiddenFlags',
@@ -211,7 +213,9 @@ const COMMAND_BOOLEAN_CONSTRAINTS = new Set([
211
213
  'requireDaemonOwner',
212
214
  'requireControlChannel',
213
215
  'requireExplicitDangerousGrant',
216
+ 'currentRelationOnly',
214
217
  ]);
218
+ const COMMAND_CONFIG_FIELD_POLICIES = new Set(['behavior-read', 'role-overridable-write']);
215
219
  const COMMAND_STRING_ARRAY_CONSTRAINTS = new Set([
216
220
  'forbiddenFlags',
217
221
  'allowedConfigKeys',
@@ -394,6 +398,9 @@ function validateCommandPermissionConstraints(rule, constraints, errors) {
394
398
  else if (key === 'requireFieldOverride' && typeof value !== 'string') {
395
399
  errors.push(`${label} must be a string`);
396
400
  }
401
+ else if (key === 'configFieldPolicy' && !COMMAND_CONFIG_FIELD_POLICIES.has(String(value))) {
402
+ errors.push(`${label} must be behavior-read or role-overridable-write`);
403
+ }
397
404
  else if (COMMAND_STRING_ARRAY_CONSTRAINTS.has(key)) {
398
405
  if (!Array.isArray(value) || !value.every(item => typeof item === 'string')) {
399
406
  errors.push(`${label} must be a string array`);
@@ -197,13 +197,13 @@ export function classifyTranscriptSource(binding, cliCandidate) {
197
197
  let historyModulePromise = null;
198
198
  async function historyModule() {
199
199
  if (!historyModulePromise) {
200
- historyModulePromise = import(toFileUrl(resolveParentDistModule('core', 'session', 'runner-task-history.js')));
200
+ historyModulePromise = import(toFileUrl(resolveParentDistModule('core', 'session', 'runner-event-summary.js')));
201
201
  }
202
202
  return historyModulePromise;
203
203
  }
204
204
  export async function readRunnerTaskTurns(logsDir, evolSessionId) {
205
205
  const module = await historyModule();
206
- return module.readRunnerTaskHistory(logsDir, evolSessionId).map((task) => ({
206
+ return module.readRunnerEventSummary(logsDir, evolSessionId).map((task) => ({
207
207
  role: 'system', ts: task.startedAt,
208
208
  uuid: `runner-task:${evolSessionId}:${task.taskId}`,
209
209
  category: 'subtask', blocks: [{ kind: 'subtask', ...task }],
@@ -39,15 +39,6 @@ function readDaemonOwnerAid(p) {
39
39
  return null;
40
40
  }
41
41
  }
42
- function readFullAccessEnabled(p) {
43
- try {
44
- const raw = JSON.parse(fs.readFileSync(p.daemonConfig, 'utf8'));
45
- return raw?.fullaccess?.enabled === undefined || raw.fullaccess.enabled === true;
46
- }
47
- catch {
48
- return false;
49
- }
50
- }
51
42
  function normalizeAgent(item) {
52
43
  const aid = String(item?.value || item?.aid || '').trim();
53
44
  if (!aid)
@@ -169,7 +160,6 @@ async function listTriggersForAgent(agentAid, agentLabel) {
169
160
  }
170
161
  async function buildSnapshot(params) {
171
162
  const p = resolvePaths();
172
- const fullaccessEnabled = readFullAccessEnabled(p);
173
163
  // This capability is derived from the server-injected ECWeb connection
174
164
  // context; browser-supplied subscription parameters are never trusted for it.
175
165
  const canApprovePersistentFullAccess = watchMenuAuth(params).canApprovePersistentFullAccess;
@@ -201,11 +191,11 @@ async function buildSnapshot(params) {
201
191
  if (agent) {
202
192
  const found = agentInfos.find((x) => x.aid === agent);
203
193
  const triggers = await listTriggersForAgent(agent, found?.label || agent);
204
- return { agents: agentInfos, triggers, selectedAgent: agent, fullaccessEnabled, canApprovePersistentFullAccess };
194
+ return { agents: agentInfos, triggers, selectedAgent: agent, canApprovePersistentFullAccess };
205
195
  }
206
196
  const batches = await Promise.all(agentInfos.map((x) => listTriggersForAgent(x.aid, x.label)));
207
197
  const triggers = batches.flat();
208
- return { agents: agentInfos, triggers, selectedAgent: agent, fullaccessEnabled, canApprovePersistentFullAccess };
198
+ return { agents: agentInfos, triggers, selectedAgent: agent, canApprovePersistentFullAccess };
209
199
  }
210
200
  export const triggersSource = {
211
201
  kind: 'triggers',