evolcore 0.0.26 → 0.0.27

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/claude-runner.js +123 -22
  5. package/dist/agents/codex/app-server-client.js +63 -26
  6. package/dist/agents/codex-runner.js +316 -411
  7. package/dist/agents/ecagent-runner.js +170 -25
  8. package/dist/agents/gemini-runner.js +6 -0
  9. package/dist/agents/runner-types.js +50 -2
  10. package/dist/aun/aid/agentmd.js +73 -31
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/control-aid.js +180 -1
  13. package/dist/aun/aid/identity.js +3 -3
  14. package/dist/aun/group/history.js +303 -0
  15. package/dist/aun/group/identity.js +43 -0
  16. package/dist/aun/group/index.js +5 -0
  17. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  18. package/dist/aun/{menu-client.js → menu/client.js} +12 -7
  19. package/dist/aun/menu/index.js +2 -0
  20. package/dist/aun/menu/transport.js +49 -0
  21. package/dist/aun/msg/index.js +2 -1
  22. package/dist/aun/msg/managed-operation.js +84 -18
  23. package/dist/aun/msg/mention-schema.js +51 -0
  24. package/dist/aun/msg/p2p.js +13 -8
  25. package/dist/aun/msg/upload.js +43 -88
  26. package/dist/aun/outbox.js +29 -7
  27. package/dist/aun/rpc/client.js +1 -0
  28. package/dist/aun/storage/client.js +39 -50
  29. package/dist/channels/aun.js +522 -233
  30. package/dist/channels/daemon.js +10 -6
  31. package/dist/cli/agent-command.js +108 -6
  32. package/dist/cli/agent-create.js +515 -2
  33. package/dist/cli/agent.js +109 -8
  34. package/dist/cli/aun-commands.js +191 -42
  35. package/dist/cli/config.js +8 -5
  36. package/dist/cli/contact.js +2 -2
  37. package/dist/cli/ctl-command.js +2 -2
  38. package/dist/cli/daemon-commands.js +35 -23
  39. package/dist/cli/diag.js +2 -2
  40. package/dist/cli/fs-command.js +3 -3
  41. package/dist/cli/handoff-command.js +6 -6
  42. package/dist/cli/index.js +8 -2
  43. package/dist/cli/init.js +34 -1
  44. package/dist/cli/managed-command-guard.js +2 -1
  45. package/dist/cli/menu-client.js +9 -5
  46. package/dist/cli/model.js +3 -3
  47. package/dist/cli/queue-command.js +2 -2
  48. package/dist/cli/response.js +2 -2
  49. package/dist/cli/restart-monitor.js +16 -2
  50. package/dist/cli/task-command.js +59 -0
  51. package/dist/cli/task-context.js +97 -49
  52. package/dist/cli/trigger-command.js +20 -10
  53. package/dist/config/builtin-roles.js +10 -0
  54. package/dist/config/config-field-policy.js +11 -0
  55. package/dist/config/config-manager.js +32 -4
  56. package/dist/config/config-operation-service.js +20 -5
  57. package/dist/config/contact-book-store.js +1 -1
  58. package/dist/config/contact-book.js +1 -1
  59. package/dist/config/contact-operation-service.js +1 -1
  60. package/dist/config/peer-role-resolver.js +1 -1
  61. package/dist/config/resolved-config-op.js +3 -0
  62. package/dist/config-store.js +1 -1
  63. package/dist/core/agent-application-service.js +5 -0
  64. package/dist/core/audit/event-key.js +17 -2
  65. package/dist/core/audit/log-integrity.js +34 -8
  66. package/dist/core/auth/agent-delegation.js +48 -14
  67. package/dist/core/auth/auth-gateway.js +2 -0
  68. package/dist/core/auth/authorization-audit.js +34 -38
  69. package/dist/core/auth/operation-authorizer.js +67 -5
  70. package/dist/core/auth/operation-catalog.js +110 -0
  71. package/dist/core/auth/remote-access-store.js +115 -9
  72. package/dist/core/bootstrap-messages.js +66 -7
  73. package/dist/core/bootstrap-service.js +110 -19
  74. package/dist/core/command/agent-control.js +88 -7
  75. package/dist/core/command/agent-storage-menu.js +443 -0
  76. package/dist/core/command/cli-intent-parser.js +19 -0
  77. package/dist/core/command/command-handler.js +76 -39
  78. package/dist/core/command/data-root-resolver.js +273 -0
  79. package/dist/core/command/group-menu.js +7 -2
  80. package/dist/core/command/menu-catalog.js +34 -4
  81. package/dist/core/command/menu-handler.js +348 -77
  82. package/dist/core/command/menu-protocol.js +40 -6
  83. package/dist/core/command/role-menu.js +2 -2
  84. package/dist/core/command/slash-handler.js +37 -22
  85. package/dist/core/event-catalog.js +4 -0
  86. package/dist/core/evolagent-registry.js +28 -2
  87. package/dist/core/group/invite-service.js +924 -0
  88. package/dist/core/interaction-router.js +22 -8
  89. package/dist/core/message/im-renderer.js +76 -15
  90. package/dist/core/message/message-bridge.js +94 -11
  91. package/dist/core/message/message-log.js +5 -3
  92. package/dist/core/message/message-queue.js +73 -41
  93. package/dist/core/message/response-engine.js +709 -117
  94. package/dist/core/permission/approval-gateway.js +36 -3
  95. package/dist/core/permission/ec-command-parser.js +61 -29
  96. package/dist/core/permission/protected-paths.js +134 -16
  97. package/dist/core/permission/sandbox-runtime.js +72 -3
  98. package/dist/core/permission/tool-error-code.js +15 -0
  99. package/dist/core/permission/tool-policy.js +7 -7
  100. package/dist/core/relation/peer-identity.js +57 -6
  101. package/dist/core/relation/shared-data.js +106 -0
  102. package/dist/core/role/runtime-policy.js +64 -0
  103. package/dist/core/runtime-lock.js +10 -26
  104. package/dist/core/session/runner-task-history.js +170 -0
  105. package/dist/core/session/session-manager.js +34 -6
  106. package/dist/core/session/session-mapper.js +41 -0
  107. package/dist/core/session/session-turn-coordinator.js +37 -7
  108. package/dist/core/session/session-turns.js +48 -7
  109. package/dist/core/task/task-recovery-service.js +176 -0
  110. package/dist/core/task/task-resume-operation.js +32 -0
  111. package/dist/eck/group-rules-sync.js +5 -3
  112. package/dist/eck/kit-renderer.js +1 -0
  113. package/dist/index.js +308 -54
  114. package/dist/ipc.js +141 -15
  115. package/dist/paths.js +17 -4
  116. package/dist/product.js +2 -0
  117. package/dist/trigger/event-source.js +19 -0
  118. package/dist/trigger/feedback.js +233 -40
  119. package/dist/trigger/parser.js +55 -5
  120. package/dist/trigger/patch.js +9 -0
  121. package/dist/trigger/scheduler.js +30 -4
  122. package/dist/trigger/validation.js +20 -3
  123. package/dist/utils/ecweb-utils.js +1 -0
  124. package/dist/utils/error-utils.js +40 -2
  125. package/dist/utils/logger.js +23 -13
  126. package/dist/utils/tool-summary.js +59 -3
  127. package/dist/utils/welcome.js +174 -1
  128. package/ecagent/dist/harness/agent-harness.js +3 -0
  129. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  130. package/kits/docs/INDEX.md +0 -6
  131. package/kits/docs/channels/aun.md +15 -2
  132. package/kits/docs/channels/feishu.md +1 -1
  133. package/kits/docs/context-assembly.md +29 -17
  134. package/kits/docs/evolcore/INDEX.md +8 -3
  135. package/kits/docs/evolcore/agent-create.md +39 -7
  136. package/kits/docs/evolcore/agent.md +33 -1
  137. package/kits/docs/evolcore/aid.md +9 -1
  138. package/kits/docs/evolcore/config.md +40 -1
  139. package/kits/docs/evolcore/group-collaboration.md +1 -1
  140. package/kits/docs/evolcore/group.md +117 -2
  141. package/kits/docs/evolcore/menu-client.md +16 -0
  142. package/kits/docs/evolcore/msg.md +3 -0
  143. package/kits/docs/evolcore/storage.md +3 -0
  144. package/kits/docs/evolcore/task.md +14 -0
  145. package/kits/docs/evolcore/trigger.md +51 -3
  146. package/kits/docs/venues/group.md +5 -0
  147. package/kits/eck_manifest.json +27 -0
  148. package/kits/rules/01-overview.md +11 -7
  149. package/kits/rules/04-relation.md +2 -1
  150. package/kits/rules/05-venue.md +1 -1
  151. package/kits/rules/06-channel.md +6 -4
  152. package/kits/schemas/agent-config.schema.12.json +25 -1
  153. package/kits/schemas/daemon.schema.6.json +2 -2
  154. package/kits/schemas/defaults.schema.5.json +1 -1
  155. package/kits/schemas/relation-config.schema.1.json +1 -1
  156. package/kits/schemas/relation-config.schema.5.json +1 -1
  157. package/kits/schemas/role-config.schema.1.json +14 -0
  158. package/kits/templates/roles/member.json +5 -0
  159. package/kits/templates/roles/visitor.json +5 -0
  160. package/kits/templates/system-fragments/baseagent.md +1 -1
  161. package/kits/templates/system-fragments/bootstrap.md +26 -9
  162. package/kits/templates/system-fragments/channel.md +8 -44
  163. package/kits/templates/system-fragments/commands.md +10 -2
  164. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  165. package/kits/templates/system-fragments/reply.md +56 -0
  166. package/package.json +7 -3
  167. package/dist/aun/group-identity.js +0 -9
  168. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  169. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -1,14 +1,18 @@
1
1
  import { normalizeCausation } from '../core/causation/context.js';
2
2
  import { RUNTIME_LOCK_DIR_ENV } from '../core/runtime-lock.js';
3
3
  import crypto from 'node:crypto';
4
+ import { execFileSync } from 'node:child_process';
4
5
  import fs from 'node:fs';
5
6
  import os from 'node:os';
6
7
  import path from 'node:path';
8
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
9
+ import { resolveRoot } from '../paths.js';
7
10
  export const TASK_RUNTIME_CONTEXT_ENV = 'EVOLCORE_TASK_RUNTIME_CONTEXT';
8
11
  export const SESSION_RUNTIME_DIR_ENV = 'EVOLCORE_SESSION_RUNTIME_DIR';
9
12
  export const DAEMON_RUNTIME_EPOCH_ENV = 'EVOLCORE_DAEMON_RUNTIME_EPOCH';
10
13
  export { RUNTIME_LOCK_DIR_ENV };
11
14
  const generatedProcessManagedTempDirs = new Set();
15
+ const preparedWindowsSessionRuntimeDirs = new Set();
12
16
  let processTempCleanupRegistered = false;
13
17
  function isPlainObject(value) {
14
18
  return !!value && typeof value === 'object' && !Array.isArray(value);
@@ -37,7 +41,9 @@ function normalizeTaskRuntimeContext(value) {
37
41
  processRole: optionalProcessRole(value.processRole),
38
42
  dataScope: optionalDataScope(value.dataScope),
39
43
  authorizedBy: optionalString(value.authorizedBy),
40
- executionSource: value.executionSource === 'fullaccess-command' || value.executionSource === 'trigger'
44
+ executionSource: value.executionSource === 'fullaccess-command'
45
+ || value.executionSource === 'trigger'
46
+ || value.executionSource === 'agent-policy'
41
47
  ? value.executionSource : undefined,
42
48
  daemonRuntimeEpoch: optionalString(value.daemonRuntimeEpoch),
43
49
  threadId: optionalString(value.threadId),
@@ -189,29 +195,81 @@ export function ensureProcessManagedTempDir() {
189
195
  throw error;
190
196
  }
191
197
  }
192
- function isRunnerOwnedSessionRuntimeDir(directory) {
193
- const resolved = path.resolve(directory);
194
- const base = path.basename(resolved);
195
- const parent = path.dirname(resolved);
196
- // Codex allocates a private, direct child named with a 24-character digest
197
- // below its private evolcore-codex-* parent. The runner has already checked
198
- // canonical containment before returning this path to the response engine.
199
- const isCodexRuntime = /^[a-f0-9]{24}$/.test(base)
200
- && /^evolcore-codex-(?:\d+|user)$/.test(path.basename(parent));
201
- return isCodexRuntime && isPrivateDirectory(parent) && isPrivateDirectory(resolved);
198
+ function canonicalHome() {
199
+ const configured = path.resolve(resolveRoot());
200
+ let canonical;
201
+ try {
202
+ canonical = fs.realpathSync.native(configured);
203
+ }
204
+ catch {
205
+ canonical = configured;
206
+ }
207
+ return process.platform === 'win32' ? canonical.toLowerCase() : canonical;
208
+ }
209
+ function systemTempRoot() {
210
+ const testRoot = process.env.EVOLCORE_TEST === '1' && process.env.TMPDIR?.trim();
211
+ const candidate = testRoot && path.isAbsolute(testRoot)
212
+ ? testRoot
213
+ : process.platform === 'win32'
214
+ ? path.join(process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows', 'Temp')
215
+ : process.platform === 'linux'
216
+ ? '/tmp'
217
+ : os.tmpdir();
218
+ try {
219
+ return fs.realpathSync.native(candidate);
220
+ }
221
+ catch {
222
+ return path.resolve(candidate);
223
+ }
224
+ }
225
+ function sessionRuntimeName(sessionId) {
226
+ const digest = crypto.createHash('sha256')
227
+ .update(canonicalHome())
228
+ .update('\0')
229
+ .update(sessionId)
230
+ .digest('hex')
231
+ .slice(0, 24);
232
+ return `ec-session-${digest}`;
202
233
  }
203
- /** Whether a task-provided runtime directory stays inside the process-managed
204
- * TMPDIR and is safe to use for transient writes. */
234
+ /**
235
+ * Windows Codex sandbox accounts and ordinary runner accounts are members of
236
+ * BUILTIN\Users. Session isolation is enforced by the distinct path plus the
237
+ * runner filesystem policy, so the runtime directory only needs a portable
238
+ * writable ACL rather than host-user secrecy.
239
+ */
240
+ function ensureWindowsSessionRuntimeAccess(directory) {
241
+ if (process.platform !== 'win32' || preparedWindowsSessionRuntimeDirs.has(directory))
242
+ return;
243
+ const icacls = path.join(process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows', 'System32', 'icacls.exe');
244
+ execFileSync(icacls, [
245
+ directory,
246
+ '/grant',
247
+ '*S-1-5-32-545:(OI)(CI)M',
248
+ '/Q',
249
+ ], { windowsHide: true, stdio: 'pipe' });
250
+ preparedWindowsSessionRuntimeDirs.add(directory);
251
+ }
252
+ /** Whether a task-provided runtime directory is a stable EvolCore session root. */
205
253
  export function isManagedSessionRuntimeDir(directory, managedRoot) {
206
- const tmpDir = (managedRoot ?? process.env.TMPDIR)?.trim();
207
- if (!directory || !path.isAbsolute(directory) || !tmpDir || !path.isAbsolute(tmpDir))
254
+ if (!directory || !path.isAbsolute(directory))
208
255
  return false;
209
- const root = path.resolve(tmpDir);
210
256
  const resolved = path.resolve(directory);
211
- const relative = path.relative(root, resolved);
212
- if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
257
+ const suppliedRoot = managedRoot?.trim();
258
+ const candidateRoot = suppliedRoot && path.isAbsolute(suppliedRoot)
259
+ ? path.resolve(suppliedRoot)
260
+ : systemTempRoot();
261
+ const root = candidateRoot === resolved ? path.dirname(resolved) : candidateRoot;
262
+ if (path.dirname(resolved) !== root || !/^ec-session-[a-f0-9]{24}$/.test(path.basename(resolved)))
263
+ return false;
264
+ try {
265
+ const rootStat = fs.lstatSync(root);
266
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink())
267
+ return false;
268
+ }
269
+ catch {
213
270
  return false;
214
- return isPrivateDirectory(root) && isPrivateDirectory(resolved);
271
+ }
272
+ return isPrivateDirectory(resolved);
215
273
  }
216
274
  /**
217
275
  * Resolve the session-owned temporary root for a managed child process.
@@ -241,58 +299,43 @@ export function getManagedTaskTempDir(runtimeEnv) {
241
299
  && (!processRoot || !path.isAbsolute(processRoot) || path.resolve(configured) !== path.resolve(processRoot))
242
300
  && contextDir
243
301
  && path.resolve(contextDir) === path.resolve(configured)
244
- && (isManagedSessionRuntimeDir(configured) || isRunnerOwnedSessionRuntimeDir(configured));
302
+ && isManagedSessionRuntimeDir(configured);
245
303
  if (!trusted) {
246
304
  throw new Error('managed session TMPDIR is unavailable or untrusted');
247
305
  }
248
306
  return path.resolve(configured);
249
307
  }
250
308
  /**
251
- * Create a private runtime directory below the process-provided TMPDIR.
252
- * There is deliberately no os.tmpdir() fallback: managed sessions must not
253
- * silently escape the session-owned temporary workspace.
309
+ * Create the stable private runtime directory for one EvolCore session.
310
+ * Its identity is independent of daemon/runner lifetimes and rotates only
311
+ * when EvolCore creates a new session id.
254
312
  */
255
313
  export function ensureSessionRuntimeDir(sessionId) {
256
314
  if (!sessionId)
257
315
  return undefined;
258
- let base = process.env.TMPDIR?.trim();
259
- try {
260
- if (!base || !path.isAbsolute(base) || !isPrivateDirectory(path.resolve(base))) {
261
- base = ensureProcessManagedTempDir();
262
- }
263
- }
264
- catch {
265
- return undefined;
266
- }
267
- if (!base || !path.isAbsolute(base))
268
- return undefined;
269
- const root = path.resolve(base);
270
- const digest = crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
271
- const directory = path.join(root, `evolcore-runtime-${digest}`);
316
+ const root = systemTempRoot();
317
+ const directory = path.join(root, sessionRuntimeName(sessionId));
272
318
  try {
273
319
  fs.mkdirSync(root, { recursive: true, mode: 0o700 });
274
- if (!isPrivateDirectory(root))
275
- return undefined;
276
320
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
321
+ ensureWindowsSessionRuntimeAccess(directory);
277
322
  fs.mkdirSync(path.join(directory, 'locks'), { recursive: true, mode: 0o700 });
278
- return isManagedSessionRuntimeDir(directory) ? directory : undefined;
323
+ return isManagedSessionRuntimeDir(directory, root) ? directory : undefined;
279
324
  }
280
325
  catch {
281
326
  return undefined;
282
327
  }
283
328
  }
284
- /** Shared by tasks in one daemon runtime, while remaining outside each
285
- * session's history/files area. The caller must still pass this path through
286
- * the task environment before a sandboxed child can use it. */
329
+ /** Return the lock namespace inside the session-owned runtime directory. */
287
330
  export function ensureRuntimeLockDir(sessionRuntimeDir) {
288
331
  if (!sessionRuntimeDir || !path.isAbsolute(sessionRuntimeDir))
289
332
  return undefined;
290
- if (!isManagedSessionRuntimeDir(sessionRuntimeDir) && !isRunnerOwnedSessionRuntimeDir(sessionRuntimeDir))
333
+ if (!isManagedSessionRuntimeDir(sessionRuntimeDir))
291
334
  return undefined;
292
- const parent = path.dirname(path.resolve(sessionRuntimeDir));
335
+ const parent = path.resolve(sessionRuntimeDir);
293
336
  if (!isPrivateDirectory(parent))
294
337
  return undefined;
295
- const directory = path.join(parent, 'evolcore-locks');
338
+ const directory = path.join(parent, 'locks');
296
339
  try {
297
340
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
298
341
  return isPrivateDirectory(directory) ? directory : undefined;
@@ -325,7 +368,7 @@ export function parseTaskRuntimeContext(raw) {
325
368
  export function readTaskRuntimeContextFromEnv(env = process.env) {
326
369
  return parseTaskRuntimeContext(env[TASK_RUNTIME_CONTEXT_ENV]);
327
370
  }
328
- export async function readTaskRuntimeContextFromDaemon(sessionId, timeoutMs = 500) {
371
+ export async function readTaskRuntimeContextFromDaemon(sessionId, timeoutMs = 500, env = process.env) {
329
372
  if (!sessionId)
330
373
  return undefined;
331
374
  try {
@@ -333,7 +376,12 @@ export async function readTaskRuntimeContextFromDaemon(sessionId, timeoutMs = 50
333
376
  import('../paths.js'),
334
377
  import('../ipc.js'),
335
378
  ]);
336
- const response = await ipcQuery(resolvePaths().socket, { type: 'task-runtime-context', sessionId }, timeoutMs);
379
+ const response = await ipcQuery(resolvePaths().socket, {
380
+ type: 'task-runtime-context',
381
+ sessionId,
382
+ delegationToken: readDelegationCredential(env),
383
+ daemonRuntimeEpoch: env[DAEMON_RUNTIME_EPOCH_ENV],
384
+ }, timeoutMs);
337
385
  return response?.ok && response.context ? response.context : undefined;
338
386
  }
339
387
  catch {
@@ -343,7 +391,7 @@ export async function readTaskRuntimeContextFromDaemon(sessionId, timeoutMs = 50
343
391
  export async function readBestTaskRuntimeContext(env = process.env) {
344
392
  const envContext = readTaskRuntimeContextFromEnv(env);
345
393
  const sessionId = env.EVOLCORE_SESSION_ID || envContext?.sessionId;
346
- return (await readTaskRuntimeContextFromDaemon(sessionId)) ?? envContext;
394
+ return (await readTaskRuntimeContextFromDaemon(sessionId, 500, env)) ?? envContext;
347
395
  }
348
396
  export function buildTaskRuntimeEnv(ctx) {
349
397
  const clean = normalizeTaskRuntimeContext(ctx);
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { resolvePaths } from '../paths.js';
4
4
  import { ipcQuery } from '../ipc.js';
5
5
  import { isHelpFlag, wantsHelp } from './help.js';
6
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
6
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
7
7
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
8
8
  import { normalizeTriggerDefinition, resolveScriptPath, } from '../trigger/validation.js';
9
9
  export async function cmdTrigger(args) {
@@ -101,13 +101,15 @@ function printHelp() {
101
101
 
102
102
  Create 参数模式支持:
103
103
  --once | --delay <时长> | --at <ISO时间> | --cron <表达式> | --every <时长> | --event <事件模式>
104
+ --event-filter <JSON> (仅 --event;按事件 payload 字段匹配)
104
105
  --exec <script|trigger-session|target-session> (必填)
105
106
  --prompt <文本> | --prompt-file <路径>
106
107
  Shell 双引号内的双引号、反引号和变量形式的美元符号都需在前面加反斜杠;trigger 保存的是还原后的原文
107
108
  --script-path <路径> --script-runtime <node|python|bash>
108
109
  --script-args <JSON> --script-timeout <时长>
109
- --feedback <target> (origin/silent 仅可通过 --file 导入)
110
+ --feedback <target|event-session> (event-session 使用事件来源会话)
110
111
  --baseagent <name> (仅 --exec trigger-session)
112
+ --execution-timeout <时长>(模型类 Trigger 单次执行总时限)
111
113
  --model <模型> --effort <low|medium|high|xhigh|max>
112
114
  --max-runs <次数> --max-duration <时长: 30s|15m|2h|1d>
113
115
  --permission <readonly|auto|request|bypass|fullaccess>(省略则继承当前身份配置;fullaccess 仅 daemon-owner 且 feature flag 开启时可用)
@@ -125,10 +127,11 @@ Update 参数:
125
127
  --once | --delay | --at | --cron | --every | --event [--tz]
126
128
  --prompt <文本> | --prompt-file <路径> --script-file <路径> --name <名称>
127
129
  --description <文本> (空字符串删除)
130
+ --execution-timeout <时长>(模型类 Trigger 单次执行总时限)
128
131
  --model <模型> --effort <强度> --permission <权限模式|inherit>(inherit 清除覆盖)
129
132
  --max-runs <次数> --max-duration <时长>
130
133
  --concurrency <forbid|replace|allow> --missed-policy <skip|run-once|run-all>
131
- --feedback <origin|target|silent>
134
+ --feedback <origin|target|event-session|silent>
132
135
  --target-channel <channelKey> --target-channel-id <ID> (必须同时指定)
133
136
  --target-chat-type <private|group> --target-session <main|thread> [--target-thread-id <threadId>]
134
137
  --trigger-thread <by-run|by-trigger> --baseagent <name> (仅 trigger_session 之间)
@@ -189,6 +192,7 @@ async function showTrigger(args, json) {
189
192
  if (t.execution.type === 'script')
190
193
  console.log(`script: ${scriptCommandLabel(t.execution.script)}`);
191
194
  console.log(`model: ${t.execution.model ?? 'inherit'}`);
195
+ console.log(`executionTimeoutMs: ${t.execution.timeoutMs ?? 'default'}`);
192
196
  console.log(`effort: ${t.execution.effort ?? 'inherit'}`);
193
197
  console.log(`permission: ${t.execution.permissionMode ?? 'inherit'}`);
194
198
  console.log(`feedback: ${t.feedback.strategy}${t.feedback.target ? ` -> ${t.feedback.target.channelKey}/${t.feedback.target.channelId}/${t.feedback.target.session}` : ''}`);
@@ -266,13 +270,14 @@ async function createTrigger(args, json) {
266
270
  }
267
271
  const parsed = parseResult.value;
268
272
  const agentAid = parsed.agentId ?? resolveAgentAid(args);
269
- if (parsed.feedbackStrategy !== 'target') {
273
+ const eventSession = parsed.feedbackStrategy === 'event_session';
274
+ if (parsed.feedbackStrategy !== 'target' && !eventSession) {
270
275
  throw new Error('CLI flag 模式仅支持 --feedback target;origin/silent 请使用 /trigger 或 --file 导入 Trigger V3 第二阶段 JSON');
271
276
  }
272
- if (!parsed.targetChannel || !parsed.targetChannelId) {
277
+ if (!eventSession && (!parsed.targetChannel || !parsed.targetChannelId)) {
273
278
  throw new Error('CLI flag 模式需要显式指定 --target-channel <channelKey> --target-channel-id <id>');
274
279
  }
275
- const targetIsAun = parsed.targetChannel === 'aun' || parsed.targetChannel.startsWith('aun#');
280
+ const targetIsAun = parsed.targetChannel === 'aun' || parsed.targetChannel?.startsWith('aun#');
276
281
  if (targetIsAun && !parsed.targetChatType) {
277
282
  throw new Error('AUN 目标必须显式指定 --target-chat-type private 或 group');
278
283
  }
@@ -299,6 +304,7 @@ async function createTrigger(args, json) {
299
304
  type: parsed.executionType,
300
305
  ...(script ? { script } : { prompt: parsed.prompt }),
301
306
  ...(parsed.model ? { model: parsed.model } : {}),
307
+ ...(parsed.executionTimeoutMs !== undefined ? { timeoutMs: parsed.executionTimeoutMs } : {}),
302
308
  ...(parsed.effort ? { effort: parsed.effort } : {}),
303
309
  ...(parsed.permissionMode ? { permissionMode: parsed.permissionMode } : {}),
304
310
  ...(parsed.executionType === 'trigger_session' ? {
@@ -308,7 +314,7 @@ async function createTrigger(args, json) {
308
314
  onError: 'retry',
309
315
  noopSentinel: '[[NOOP]]',
310
316
  },
311
- feedback: {
317
+ feedback: eventSession ? { strategy: 'event_session' } : {
312
318
  strategy: 'target',
313
319
  target: {
314
320
  channelKey: parsed.targetChannel,
@@ -362,8 +368,12 @@ function sourceFromParsed(parsed) {
362
368
  }
363
369
  if (parsed.scheduleType === 'interval')
364
370
  return { type: 'interval', everyMs: Number(parsed.scheduleValue) };
365
- if (parsed.scheduleType === 'event')
366
- return { type: 'event', eventPattern: parsed.scheduleValue };
371
+ if (parsed.scheduleType === 'event') {
372
+ const source = { type: 'event', eventPattern: parsed.scheduleValue };
373
+ if (parsed.eventFilter !== undefined)
374
+ source.filter = parsed.eventFilter;
375
+ return source;
376
+ }
367
377
  throw new Error(`unsupported scheduleType: ${parsed.scheduleType}`);
368
378
  }
369
379
  function executionTypeFromFlag(args) {
@@ -565,7 +575,7 @@ function handleTemplate(args) {
565
575
  }
566
576
  async function request(cmd, timeoutMs = 10_000) {
567
577
  const actorSessionId = process.env.EVOLCORE_SESSION_ID;
568
- const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
578
+ const delegationToken = readDelegationCredential();
569
579
  let controlToken;
570
580
  if (!actorSessionId) {
571
581
  try {
@@ -44,6 +44,16 @@ export function getManagementCommandPermissions(role) {
44
44
  'agent.baseagent.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
45
45
  'observable.current': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true } },
46
46
  'observable.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
47
+ 'file.root.agent': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
48
+ 'file.root.storage': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
49
+ 'file.storage.list': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
50
+ 'file.storage.query': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
51
+ 'file.storage.fetch': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
52
+ 'file.storage.quota': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
53
+ 'file.root.relation': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
54
+ 'file.data.list': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
55
+ 'file.data.query': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
56
+ 'file.data.fetch': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
47
57
  'config.get': { allow: true, scopes: ['agent', 'relation'] },
48
58
  'config.set': { allow: true, scopes: ['agent', 'relation'] },
49
59
  'config.unset': { allow: true, scopes: ['agent', 'relation'] },
@@ -29,14 +29,25 @@ const BASEAGENT_BEHAVIOR_FIELDS = new Set([
29
29
  'useVertex',
30
30
  ]);
31
31
  const SUPPORTED_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'ecagent']);
32
+ /** Public behavior fields for a runner this version does not implement. */
33
+ export function isUnsupportedBaseagentConfigField(fieldPath) {
34
+ const parts = fieldPath.split('.');
35
+ return parts.length === 3
36
+ && parts[0] === 'baseagents'
37
+ && /^[a-z][a-z0-9_-]*$/.test(parts[1])
38
+ && !SUPPORTED_BASEAGENTS.has(parts[1])
39
+ && BASEAGENT_BEHAVIOR_FIELDS.has(parts[2]);
40
+ }
32
41
  const SENSITIVE_TOP_FIELDS = new Set([
33
42
  '$schema_version',
34
43
  'aid',
35
44
  'enabled',
36
45
  'lifecycle',
37
46
  'initialized',
47
+ 'initialization',
38
48
  'owners',
39
49
  'access',
50
+ 'fullaccess',
40
51
  'admins',
41
52
  'channels',
42
53
  'aun',
@@ -19,7 +19,8 @@ import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
19
19
  import { fileCache } from '../core/daemon-file-cache.js';
20
20
  import { isValidAid } from '../aun/aid/validation.js';
21
21
  import { normalizeAidDomain } from '../aun/aid/domain.js';
22
- import { isExplicitGroupId } from '../aun/group-identity.js';
22
+ import { isExplicitGroupId } from '../aun/group/index.js';
23
+ import { ensureRelationSharedDirectory } from '../core/relation/shared-data.js';
23
24
  import { parseContactAlias } from './contact-alias.js';
24
25
  import { loadSchema, listSchemaVersions, currentVersion, isSchemaName, } from './schema-registry.js';
25
26
  import { mergeLayers, expandVars, buildEnvResolver } from './merge.js';
@@ -881,6 +882,9 @@ export function write(target, value, sel, opts = {}) {
881
882
  }
882
883
  // 2. 写入文件
883
884
  fs.mkdirSync(path.dirname(file), { recursive: true });
885
+ if (target === ConfigTarget.Relation && sel?.self && sel.peerKey) {
886
+ ensureRelationSharedDirectory(sel.self, sel.peerKey);
887
+ }
884
888
  atomicWriteJson(file, normalized);
885
889
  finishConfigWrite(target, sel, file);
886
890
  }
@@ -1291,6 +1295,27 @@ export function resolveAgentConfig(sel, opts = {}) {
1291
1295
  stripRelationRoleData(stripStaticOwnerField(relationConfig)),
1292
1296
  ];
1293
1297
  const merged = mergeLayers(layers, fields);
1298
+ // A baseagent block is a configuration record rather than an atomic value:
1299
+ // selecting a model at Agent or relation scope must retain the defaults
1300
+ // gateway credentials and endpoint. Keep the generic dictionary merge
1301
+ // contract unchanged for every other field.
1302
+ merged.baseagents = mergeBaseagentBlocks(layers);
1303
+ return merged;
1304
+ }
1305
+ function mergeBaseagentBlocks(layers) {
1306
+ const merged = {};
1307
+ for (const layer of layers) {
1308
+ const baseagents = layer?.baseagents;
1309
+ if (!baseagents || typeof baseagents !== 'object' || Array.isArray(baseagents))
1310
+ continue;
1311
+ for (const [name, rawBlock] of Object.entries(baseagents)) {
1312
+ if (!rawBlock || typeof rawBlock !== 'object' || Array.isArray(rawBlock)) {
1313
+ merged[name] = rawBlock;
1314
+ continue;
1315
+ }
1316
+ merged[name] = { ...(merged[name] ?? {}), ...rawBlock };
1317
+ }
1318
+ }
1294
1319
  return merged;
1295
1320
  }
1296
1321
  /**
@@ -1326,12 +1351,13 @@ function stripStaticOwnerField(config) {
1326
1351
  if (!Object.prototype.hasOwnProperty.call(config, 'owners')
1327
1352
  && !Object.prototype.hasOwnProperty.call(config, 'admins')
1328
1353
  && !Object.prototype.hasOwnProperty.call(config, 'access')
1329
- && !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions'))
1354
+ && !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions')
1355
+ && !Object.prototype.hasOwnProperty.call(config, 'fullaccess'))
1330
1356
  return config;
1331
- // groupSlashRequireMentions is intentionally Agent-only. Relation/defaults
1357
+ // fullaccess and groupSlashRequireMentions are Agent-only. Relation/defaults
1332
1358
  // files allow forward-compatible unknown properties, so strip it before the
1333
1359
  // generic merge chain can treat a manually written value as an override.
1334
- const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, ...rest } = config;
1360
+ const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, fullaccess: _fullaccess, ...rest } = config;
1335
1361
  return rest;
1336
1362
  }
1337
1363
  function stripRelationRoleData(config) {
@@ -1354,7 +1380,9 @@ export function resolveEffective(sel, opts = {}) {
1354
1380
  enabled: config.enabled,
1355
1381
  lifecycle: config.lifecycle,
1356
1382
  initialized: config.initialized,
1383
+ initialization: config.initialization,
1357
1384
  owners: config.owners,
1385
+ fullaccess: config.fullaccess,
1358
1386
  aun: config.aun,
1359
1387
  channels: config.channels ?? [],
1360
1388
  models: config.models,
@@ -6,8 +6,9 @@ import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { resolvePaths, agentDir, agentRelationConfig } from '../paths.js';
8
8
  import { roleMenuQuery, roleMenuUpdate } from '../core/command/role-menu.js';
9
- import { parsePeerKey } from '../core/relation/peer-identity.js';
10
- import { isExplicitGroupId } from '../aun/group-identity.js';
9
+ import { parsePeerKey, PeerIdentityCache } from '../core/relation/peer-identity.js';
10
+ import { isExplicitGroupId } from '../aun/group/index.js';
11
+ import { isUnsupportedBaseagentConfigField } from './config-field-policy.js';
11
12
  export const AUTHENTICATED_CONFIG_ROLE_ENV = 'EVOLCORE_AUTHENTICATED_CONFIG_ROLE';
12
13
  export function executeResolvedConfigCommand(command, options = {}) {
13
14
  try {
@@ -63,7 +64,7 @@ async function executeRelationRoleCommand(op, options) {
63
64
  if (!op.self || !op.peerKey) {
64
65
  return { ok: false, code: 'PEER_REQUIRED', error: 'role requires --self <aid> --peer <peer-aid>' };
65
66
  }
66
- const target = roleTargetFromPeerKey(op.peerKey);
67
+ const target = roleTargetFromPeerKey(op.self, op.peerKey);
67
68
  const context = options.roleContext ?? {
68
69
  self: op.self,
69
70
  actorRole: options.role || 'owner',
@@ -139,7 +140,7 @@ async function executeRelationRoleCommand(op, options) {
139
140
  roleDetails: updatedDetails,
140
141
  };
141
142
  }
142
- function roleTargetFromPeerKey(peerKey) {
143
+ function roleTargetFromPeerKey(self, peerKey) {
143
144
  let parsed;
144
145
  try {
145
146
  parsed = parsePeerKey(peerKey);
@@ -150,7 +151,13 @@ function roleTargetFromPeerKey(peerKey) {
150
151
  if (parsed.channelType !== 'aun') {
151
152
  throw new ConfigError('NOT_SUPPORTED', 'Relation role assignments currently support AUN peers only');
152
153
  }
153
- return isExplicitGroupId(parsed.channelId)
154
+ const relation = read(ConfigTarget.Relation, { self, peerKey });
155
+ const identity = PeerIdentityCache.get('aun', parsed.channelId, agentDir(self));
156
+ const verifiedType = identity?.aid === parsed.channelId && identity.source === 'agentmd' && identity.verifiedAt > 0
157
+ ? identity.type : undefined;
158
+ const isGroup = relation?.targetKind ? relation.targetKind === 'group'
159
+ : verifiedType ? verifiedType === 'group' : isExplicitGroupId(parsed.channelId);
160
+ return isGroup
154
161
  ? { kind: 'group', groupId: parsed.channelId }
155
162
  : { kind: 'private', peerAid: parsed.channelId };
156
163
  }
@@ -159,6 +166,11 @@ export function executeResolvedConfigOperation(op, options = {}) {
159
166
  const missingAgent = selectedAgentError(op);
160
167
  if (missingAgent)
161
168
  return missingAgent;
169
+ if (op.configScope === 'agent'
170
+ && (op.field === 'fullaccess' || op.field.startsWith('fullaccess.'))
171
+ && op.subcommand !== 'get') {
172
+ return { ok: false, code: 'FORBIDDEN', error: 'Agent fullaccess can only be configured in ECWeb Agent settings' };
173
+ }
162
174
  if (op.subcommand === 'get')
163
175
  return executeGet(op, options);
164
176
  if (op.subcommand === 'set')
@@ -353,6 +365,9 @@ function executeSchema(command) {
353
365
  };
354
366
  }
355
367
  function executeGet(op, options) {
368
+ if (isUnsupportedBaseagentConfigField(op.field)) {
369
+ return { ok: false, code: 'UNSUPPORTED_FIELD', error: `Config field ${op.field} is not supported by this version` };
370
+ }
356
371
  const selector = selectorFor(op, options);
357
372
  if (op.configScope === 'process') {
358
373
  const config = read(ConfigTarget.Process, selector) || {};
@@ -2,7 +2,7 @@ import crypto from 'crypto';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { isValidAid } from '../aun/aid/validation.js';
5
- import { isExplicitGroupId } from '../aun/group-identity.js';
5
+ import { isExplicitGroupId } from '../aun/group/index.js';
6
6
  import { agentConfig, agentContactConfig, agentDir, resolvePaths } from '../paths.js';
7
7
  import { atomicReadJson, atomicWrite, atomicWriteJson } from '../utils/atomic-write.js';
8
8
  import { fileCache } from '../core/daemon-file-cache.js';
@@ -1,6 +1,6 @@
1
1
  import { agentContactConfig } from '../paths.js';
2
2
  import { isValidAid } from '../aun/aid/validation.js';
3
- import { isExplicitGroupId } from '../aun/group-identity.js';
3
+ import { isExplicitGroupId } from '../aun/group/index.js';
4
4
  import { ConfigTarget, read, validateConfig } from './config-manager.js';
5
5
  import { ContactMutationError, contactRevision, mutateContactBookSync } from './contact-book-store.js';
6
6
  import { formatContactAlias, normalizeContactChannelKey, parseContactAlias, } from './contact-alias.js';
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import { isValidAid } from '../aun/aid/validation.js';
3
- import { isExplicitGroupId } from '../aun/group-identity.js';
3
+ import { isExplicitGroupId } from '../aun/group/index.js';
4
4
  import { agentConfig } from '../paths.js';
5
5
  import { getContactSnapshot, resolveContactView } from './contact-book.js';
6
6
  import { ContactMutationError } from './contact-book-store.js';
@@ -10,7 +10,7 @@ import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from
10
10
  import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
11
11
  import { resolvePrincipal } from './contact-book.js';
12
12
  import { isValidAid } from '../aun/aid/validation.js';
13
- import { isExplicitGroupId } from '../aun/group-identity.js';
13
+ import { isExplicitGroupId } from '../aun/group/index.js';
14
14
  export function isAuthenticated(userId) {
15
15
  return isValidAid(String(userId || '').trim());
16
16
  }
@@ -97,6 +97,9 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
97
97
  reason: 'permissionMode is defined by roles; use the role policy editor',
98
98
  };
99
99
  }
100
+ if (subcommand !== 'get' && (field === 'initialization' || field.startsWith('initialization.'))) {
101
+ return { ok: false, code: 'INITIALIZATION_MANAGED', reason: 'initialization is owned by the creation service' };
102
+ }
100
103
  const isRelationRole = field === 'role';
101
104
  if (isRelationRole && selector.scope !== 'relation') {
102
105
  return {
@@ -58,7 +58,7 @@ export function loadDaemonConfig() {
58
58
  return validateDaemonConfig(normalizeProcessConfigCompat(normalizeDaemonServices(sanitized)), false);
59
59
  }
60
60
  export function isFullAccessEnabled(config = loadDaemonConfig()) {
61
- return config.fullaccess?.enabled === true;
61
+ return config.fullaccess?.enabled === undefined || config.fullaccess.enabled === true;
62
62
  }
63
63
  let eckSnapshotsConfigCache;
64
64
  /** Parse the process-level snapshot gate once for the current daemon lifecycle. */
@@ -48,6 +48,8 @@ function errorFrom(error, fallback = 'agent operation failed') {
48
48
  */
49
49
  export class AgentApplicationService {
50
50
  deps;
51
+ initialization;
52
+ setInitializationService(service) { this.initialization = service; }
51
53
  constructor(deps) {
52
54
  this.deps = deps;
53
55
  }
@@ -212,6 +214,8 @@ export class AgentApplicationService {
212
214
  }
213
215
  if (!config)
214
216
  return { ok: false, error: `Agent "${aid}" not found`, code: 'NOT_FOUND' };
217
+ if (!enabled)
218
+ await this.initialization?.cancel(aid);
215
219
  const previousEnabled = config.enabled;
216
220
  try {
217
221
  config.enabled = enabled;
@@ -240,6 +244,7 @@ export class AgentApplicationService {
240
244
  return { ok: true, aid, enabled, reloaded: result.ok };
241
245
  }
242
246
  async delete(aid, purge = false) {
247
+ await this.initialization?.cancel(aid);
243
248
  const paths = resolvePaths();
244
249
  const agentDir = path.join(paths.agentsDir, aid);
245
250
  const configPath = path.join(agentDir, 'config.json');
@@ -7,16 +7,31 @@ function stableDigest(parts) {
7
7
  const material = parts.map(part => compactPart(part) ?? '-').join('\u0000');
8
8
  return crypto.createHash('sha256').update(material).digest('hex').slice(0, 20);
9
9
  }
10
+ /** Preserve distinct source IDs; fill aliases only when the producer omitted them. */
11
+ export function normalizeToolCorrelation(input) {
12
+ const callId = compactPart(input.callId);
13
+ const correlationId = compactPart(input.correlationId);
14
+ const requestId = compactPart(input.requestId);
15
+ return {
16
+ callId: callId ?? correlationId ?? requestId,
17
+ correlationId: correlationId ?? callId ?? requestId,
18
+ requestId: requestId ?? callId ?? correlationId,
19
+ };
20
+ }
10
21
  /** Stable across source/mirror records for one tool-call lifecycle. */
11
22
  export function buildToolLifecycleEventKey(input) {
23
+ const { callId } = normalizeToolCorrelation(input);
24
+ // No source ID means no safe join. Never merge unrelated anonymous calls.
25
+ if (!callId)
26
+ return `tool:unlinked:${crypto.randomUUID()}`;
12
27
  return `tool:${stableDigest([
13
28
  input.sessionId ?? 'unknown',
14
- input.callId ?? input.correlationId ?? input.requestId ?? 'unknown',
29
+ callId,
15
30
  ])}`;
16
31
  }
17
32
  /** Stable across repeated projections of one authorization decision. */
18
33
  export function buildAuthorizationEventKey(input) {
19
- const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
34
+ const lifecycleId = normalizeToolCorrelation(input).callId;
20
35
  if (lifecycleId) {
21
36
  return `${buildToolLifecycleEventKey({
22
37
  sessionId: input.sessionId,