evolcore 0.0.35 → 0.0.36

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 (224) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +134 -48
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +109 -34
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -15,12 +15,13 @@ export function decodeTriggerScriptFile(contentBase64) {
15
15
  }
16
16
  return contents;
17
17
  }
18
- export function resolveTriggerLocalPath(input, scope) {
18
+ export function resolveManagedLocalPath(input, scope, kind) {
19
+ const messages = managedLocalFileMessages(kind);
19
20
  if (!input || /[\0\r\n]/.test(input) || hasUnsafeWindowsPathSyntax(input))
20
- throw new Error('invalid Trigger input path');
21
+ throw new Error(messages.invalidPath);
21
22
  const normalizedInput = input.replace(/\\/g, '/');
22
23
  if (normalizedInput.split('/').includes('..'))
23
- throw new Error('Trigger input path cannot traverse parent directories');
24
+ throw new Error(messages.parentTraversal);
24
25
  const lexical = path.resolve(scope?.cwd ?? process.cwd(), input);
25
26
  if (!scope)
26
27
  return fs.realpathSync.native(lexical);
@@ -28,17 +29,17 @@ export function resolveTriggerLocalPath(input, scope) {
28
29
  const runtime = scope.sessionRuntimeDir
29
30
  ? fs.realpathSync.native(scope.sessionRuntimeDir)
30
31
  : undefined;
31
- assertSessionRuntimeScope(lexical, runtime);
32
+ assertSessionRuntimeScope(lexical, runtime, messages.otherSessionTemp);
32
33
  const inProjectLexically = isSameOrDescendant(lexical, project);
33
34
  const inSessionTempLexically = !!runtime && isSameOrDescendant(lexical, runtime);
34
35
  if (!inProjectLexically && !inSessionTempLexically) {
35
- throw new Error('Trigger input must be inside the current project or session TMPDIR');
36
+ throw new Error(messages.outsideScope);
36
37
  }
37
38
  let current = lexical;
38
39
  while (true) {
39
40
  const stat = fs.lstatSync(current);
40
41
  if (stat.isSymbolicLink()) {
41
- throw new Error('Trigger input must not traverse a symbolic link or junction');
42
+ throw new Error(messages.symbolicLink);
42
43
  }
43
44
  if (current === project || current === runtime)
44
45
  break;
@@ -48,23 +49,49 @@ export function resolveTriggerLocalPath(input, scope) {
48
49
  current = parent;
49
50
  }
50
51
  const resolved = fs.realpathSync.native(lexical);
51
- assertSessionRuntimeScope(resolved, runtime);
52
+ assertSessionRuntimeScope(resolved, runtime, messages.otherSessionTemp);
52
53
  const inProject = isSameOrDescendant(lexical, project) && isSameOrDescendant(resolved, project);
53
54
  const inSessionTemp = !!runtime
54
55
  && isSameOrDescendant(lexical, runtime)
55
56
  && isSameOrDescendant(resolved, runtime);
56
57
  if (!inProject && !inSessionTemp) {
57
- throw new Error('Trigger input must be inside the current project or session TMPDIR');
58
+ throw new Error(messages.outsideScope);
58
59
  }
59
60
  const root = resolveRoot();
60
61
  const lexicalClass = classifyProtectedPath(lexical, { root, selfAid: scope.selfAid });
61
62
  const resolvedClass = classifyProtectedPath(resolved, { root, selfAid: scope.selfAid });
62
- if (isProtectedTriggerInputClass(lexicalClass) || isProtectedTriggerInputClass(resolvedClass)) {
63
- throw new Error('Trigger input references a protected path');
63
+ if (isProtectedManagedInputClass(lexicalClass) || isProtectedManagedInputClass(resolvedClass)) {
64
+ throw new Error(messages.protectedPath);
64
65
  }
65
66
  return resolved;
66
67
  }
67
- function isProtectedTriggerInputClass(value) {
68
+ export function resolveTriggerLocalPath(input, scope) {
69
+ return resolveManagedLocalPath(input, scope, 'trigger');
70
+ }
71
+ export function resolveAttachmentLocalPath(input, scope) {
72
+ return resolveManagedLocalPath(input, scope, 'attachment');
73
+ }
74
+ function managedLocalFileMessages(kind) {
75
+ if (kind === 'trigger') {
76
+ return {
77
+ invalidPath: 'invalid Trigger input path',
78
+ parentTraversal: 'Trigger input path cannot traverse parent directories',
79
+ outsideScope: 'Trigger input must be inside the current project or session TMPDIR',
80
+ symbolicLink: 'Trigger input must not traverse a symbolic link or junction',
81
+ protectedPath: 'Trigger input references a protected path',
82
+ otherSessionTemp: 'Trigger input must not reference another session TMPDIR',
83
+ };
84
+ }
85
+ return {
86
+ invalidPath: 'Invalid attachment source path',
87
+ parentTraversal: 'Attachment source path cannot traverse parent directories',
88
+ outsideScope: 'Attachment source must be inside the current project or session TMPDIR',
89
+ symbolicLink: 'Attachment source must not traverse a symbolic link or junction',
90
+ protectedPath: 'Attachment source references a protected path',
91
+ otherSessionTemp: 'Attachment source must not reference another session TMPDIR',
92
+ };
93
+ }
94
+ function isProtectedManagedInputClass(value) {
68
95
  return value === 'h-file' || value === 'h-directory' || value === 'l-path' || value === 'unknown';
69
96
  }
70
97
  function hasUnsafeWindowsPathSyntax(input) {
@@ -74,7 +101,7 @@ function hasUnsafeWindowsPathSyntax(input) {
74
101
  const withoutDrive = /^[A-Za-z]:\//.test(normalized) ? normalized.slice(2) : normalized;
75
102
  return withoutDrive.includes(':') || withoutDrive.split('/').some(segment => (/[. ]$/.test(segment) && segment !== '.' && segment !== '..') || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(segment));
76
103
  }
77
- function assertSessionRuntimeScope(candidate, runtime) {
104
+ function assertSessionRuntimeScope(candidate, runtime, errorMessage) {
78
105
  if (!runtime)
79
106
  return;
80
107
  const parent = path.dirname(runtime);
@@ -83,27 +110,29 @@ function assertSessionRuntimeScope(candidate, runtime) {
83
110
  const firstSegment = path.relative(parent, candidate).split(path.sep)[0];
84
111
  if (/^ec-session-[a-f0-9]{24}$/i.test(firstSegment)
85
112
  && !isSameOrDescendant(candidate, runtime)) {
86
- throw new Error('Trigger input must not reference another session TMPDIR');
113
+ throw new Error(errorMessage);
87
114
  }
88
115
  }
89
- export function readTriggerLocalFile(input, scope) {
90
- const resolved = resolveTriggerLocalPath(input, scope);
116
+ export function readManagedLocalFile(input, scope, kind) {
117
+ const sourceLabel = kind === 'trigger' ? 'Trigger input' : 'Attachment source';
118
+ const resolved = resolveManagedLocalPath(input, scope, kind);
91
119
  const expected = fs.lstatSync(resolved);
92
120
  if (!expected.isFile())
93
- throw new Error('Trigger input must be a regular file');
121
+ throw new Error(`${sourceLabel} must be a regular file`);
94
122
  if (scope && expected.nlink !== 1)
95
- throw new Error('Trigger input must not be hard-linked');
123
+ throw new Error(`${sourceLabel} must not be hard-linked`);
96
124
  const noFollow = fs.constants.O_NOFOLLOW ?? 0;
97
125
  const descriptor = fs.openSync(resolved, fs.constants.O_RDONLY | noFollow);
98
126
  try {
99
127
  const opened = fs.fstatSync(descriptor);
100
128
  if (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino
101
129
  || (scope && opened.nlink !== 1)
102
- || resolveTriggerLocalPath(input, scope) !== resolved) {
103
- throw new Error('Trigger input changed during validation');
130
+ || resolveManagedLocalPath(input, scope, kind) !== resolved) {
131
+ throw new Error(`${sourceLabel} changed during validation`);
132
+ }
133
+ if (opened.size > TRIGGER_LOCAL_FILE_LIMIT) {
134
+ throw new Error(`${sourceLabel} exceeds the 4 MiB limit`);
104
135
  }
105
- if (opened.size > TRIGGER_LOCAL_FILE_LIMIT)
106
- throw new Error('Trigger input exceeds the 4 MiB limit');
107
136
  const openedMtime = opened.mtimeMs;
108
137
  const openedCtime = opened.ctimeMs;
109
138
  const buffer = Buffer.alloc(Math.min(opened.size + 1, TRIGGER_LOCAL_FILE_LIMIT + 1));
@@ -122,8 +151,8 @@ export function readTriggerLocalFile(input, scope) {
122
151
  || !afterPath.isFile() || afterPath.isSymbolicLink()
123
152
  || afterPath.size !== opened.size || afterPath.mtimeMs !== openedMtime || afterPath.ctimeMs !== openedCtime
124
153
  || (scope && (afterRead.nlink !== 1 || afterPath.nlink !== 1))
125
- || resolveTriggerLocalPath(input, scope) !== resolved) {
126
- throw new Error('Trigger input changed during reading');
154
+ || resolveManagedLocalPath(input, scope, kind) !== resolved) {
155
+ throw new Error(`${sourceLabel} changed during reading`);
127
156
  }
128
157
  return buffer.subarray(0, length);
129
158
  }
@@ -131,3 +160,6 @@ export function readTriggerLocalFile(input, scope) {
131
160
  fs.closeSync(descriptor);
132
161
  }
133
162
  }
163
+ export function readTriggerLocalFile(input, scope) {
164
+ return readManagedLocalFile(input, scope, 'trigger');
165
+ }
@@ -8,6 +8,7 @@ import { TargetSessionLockRegistry } from './session-lock.js';
8
8
  import { enterTrigger, formatTriggerPath } from '../core/causation/context.js';
9
9
  import { createRootCausation } from '../core/causation/context.js';
10
10
  import { recordCausationSpan } from '../core/causation/audit.js';
11
+ import { isRetryableError } from '../utils/error-utils.js';
11
12
  import { accumulateTriggerExecutionAnomalies, drainTriggerExecutionAnomalies, mergeTriggerExecutionAnomalies, } from './anomaly-store.js';
12
13
  const MAX_TIMER_MS = 2_147_483_647;
13
14
  const RUN_ABORT_SETTLE_GRACE_MS = 1_000;
@@ -441,7 +442,7 @@ export class TriggerRuntimeScheduler {
441
442
  }
442
443
  return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'invalid_execution_configuration', audit, error: message };
443
444
  }
444
- const executionAuthorization = this.runtime.authorizeExecution?.(definition);
445
+ const executionAuthorization = await this.runtime.authorizeExecution?.(definition);
445
446
  if (executionAuthorization && !executionAuthorization.allowed) {
446
447
  const reason = executionAuthorization.reason || 'execution_authorization_denied';
447
448
  const audit = this.buildAudit({
@@ -583,7 +584,7 @@ export class TriggerRuntimeScheduler {
583
584
  }
584
585
  if (!payload.dryRun) {
585
586
  const executionFailed = execution.reply
586
- ? shouldRetryExecution(execution.reply)
587
+ ? !['success', 'noop'].includes(execution.reply.outcome)
587
588
  : execution.feedbackResult?.status === 'failed';
588
589
  this.state.appendEvent(definition.id, runId, {
589
590
  event: executionFailed ? 'execution.failed' : 'execution.completed',
@@ -758,7 +759,7 @@ export class TriggerRuntimeScheduler {
758
759
  }
759
760
  async runRecordedExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt, previousAttempt) {
760
761
  const attemptId = `${runId}:attempt:${attempt}`;
761
- const executionAuthorization = this.runtime.authorizeExecution?.(definition);
762
+ const executionAuthorization = await this.runtime.authorizeExecution?.(definition);
762
763
  if (executionAuthorization && !executionAuthorization.allowed) {
763
764
  const reply = errorReply(runId, 0, 'authorization_denied', executionAuthorization.reason || 'Trigger execution authorization denied');
764
765
  const execution = {
@@ -836,11 +837,30 @@ export class TriggerRuntimeScheduler {
836
837
  reply,
837
838
  };
838
839
  }
840
+ // 复查通知里"第 N 次 / 已过多久"是**渲染期**数据,必须在每次 fire 时重算。
841
+ // fireCount 来自 history 聚合(skipped / dry-run 不计),本轮尚未落历史,
842
+ // 因此本轮序号 = 已完成次数 + 1。
843
+ const runNumber = this.audit.stats(definition.id).fireCount + 1;
844
+ const firstSentAt = Number(definition.labels?.firstSentAt);
845
+ const elapsedMs = Number.isFinite(firstSentAt) && firstSentAt > 0 ? Math.max(0, firedAt - firstSentAt) : undefined;
846
+ const triggerView = {
847
+ ...definition,
848
+ labels: {
849
+ ...(definition.labels ?? {}),
850
+ ...(Number.isFinite(firstSentAt) && firstSentAt > 0
851
+ ? { firstSentAtTime: new Date(firstSentAt).toLocaleString('zh-CN', { hour12: false }) }
852
+ : {}),
853
+ },
854
+ };
839
855
  const originalPrompt = renderTemplate(definition.execution.prompt ?? '', {
840
- trigger: definition,
856
+ trigger: triggerView,
841
857
  timestamp: firedAt,
842
858
  event: sourcePayload,
843
859
  source: { type: definition.source.type, payload: sourcePayload },
860
+ run: {
861
+ number: runNumber,
862
+ elapsed: elapsedMs === undefined ? '' : formatElapsed(elapsedMs),
863
+ },
844
864
  });
845
865
  const prompt = previousAttempt
846
866
  ? renderRetryContinuationPrompt(originalPrompt, previousAttempt)
@@ -973,7 +993,8 @@ export class TriggerRuntimeScheduler {
973
993
  };
974
994
  }
975
995
  catch (err) {
976
- const reply = errorReply(runId, 0, 'agent_execution_error', err?.message || String(err));
996
+ const errorText = err?.message || String(err);
997
+ const reply = errorReply(runId, 0, 'agent_execution_error', errorText, isRetryableError(err));
977
998
  return {
978
999
  branch: 'onFailure',
979
1000
  script: null,
@@ -1059,7 +1080,7 @@ export class TriggerRuntimeScheduler {
1059
1080
  async reclaimOpenRun(definition, run) {
1060
1081
  if (!definition.enabled || !this.isResumableCheckpoint(definition, run))
1061
1082
  return false;
1062
- const executionAuthorization = this.runtime.authorizeExecution?.(definition);
1083
+ const executionAuthorization = await this.runtime.authorizeExecution?.(definition);
1063
1084
  if (executionAuthorization && !executionAuthorization.allowed)
1064
1085
  return false;
1065
1086
  const session = await this.daemonChannel.getResumableConversationSession(run.executionSessionId);
@@ -1162,7 +1183,7 @@ export class TriggerRuntimeScheduler {
1162
1183
  return { anomalies };
1163
1184
  }
1164
1185
  const executionFailed = execution.reply
1165
- ? shouldRetryExecution(execution.reply)
1186
+ ? !['success', 'noop'].includes(execution.reply.outcome)
1166
1187
  : execution.feedbackResult?.status === 'failed';
1167
1188
  this.state.appendEvent(definition.id, runId, {
1168
1189
  event: executionFailed ? 'execution.failed' : 'execution.completed',
@@ -1348,7 +1369,8 @@ export class TriggerRuntimeScheduler {
1348
1369
  };
1349
1370
  }
1350
1371
  catch (error) {
1351
- const reply = errorReply(run.runId, 0, 'agent_execution_error', error?.message || String(error));
1372
+ const errorText = error?.message || String(error);
1373
+ const reply = errorReply(run.runId, 0, 'agent_execution_error', errorText, isRetryableError(error));
1352
1374
  return {
1353
1375
  branch: 'onFailure',
1354
1376
  script: null,
@@ -1839,6 +1861,7 @@ export class TriggerRuntimeScheduler {
1839
1861
  ...base,
1840
1862
  messageId: this.triggerOutcomeMessageId(audit),
1841
1863
  error: audit.error?.message ?? audit.reason ?? 'trigger failed',
1864
+ recoverable: audit.error?.recoverable,
1842
1865
  phase: 'execute',
1843
1866
  });
1844
1867
  return;
@@ -1929,6 +1952,10 @@ function branchFromReply(reply) {
1929
1952
  return 'onFailure';
1930
1953
  }
1931
1954
  function shouldRetryExecution(reply) {
1955
+ if (reply.outcome === 'success' || reply.outcome === 'noop' || reply.error?.reason === 'authorization_denied')
1956
+ return false;
1957
+ if (reply.error?.recoverable !== undefined)
1958
+ return reply.error.recoverable;
1932
1959
  return reply.outcome === 'error'
1933
1960
  ? reply.error?.reason !== 'authorization_denied'
1934
1961
  : reply.outcome === 'timeout' || reply.outcome === 'interrupted';
@@ -1948,17 +1975,18 @@ function triggerCancellationReason(signal) {
1948
1975
  function shouldRetryExecutionResult(execution) {
1949
1976
  if (execution.reply)
1950
1977
  return shouldRetryExecution(execution.reply);
1951
- return execution.feedbackResult?.status === 'failed';
1978
+ return execution.feedbackResult?.status === 'failed'
1979
+ && execution.feedbackResult.error?.recoverable === true;
1952
1980
  }
1953
1981
  function statusWithExecutionAnomalies(status, anomalies) {
1954
1982
  return status === 'completed' && anomalies.length ? 'degraded' : status;
1955
1983
  }
1956
- function errorReply(runId, durationMs, reason, text) {
1984
+ function errorReply(runId, durationMs, reason, text, recoverable) {
1957
1985
  return {
1958
1986
  outcome: 'error',
1959
1987
  text: '',
1960
1988
  files: [],
1961
- error: { reason, text },
1989
+ error: { reason, text, ...(recoverable !== undefined ? { recoverable } : {}) },
1962
1990
  meta: {
1963
1991
  runId,
1964
1992
  durationMs,
@@ -2013,6 +2041,7 @@ function replyErrorSummary(reply, fallbackCode) {
2013
2041
  return {
2014
2042
  code: reply.error?.reason || fallbackCode,
2015
2043
  message: previewText(reply.error?.text || `Trigger attempt ${fallbackCode}`, 600),
2044
+ ...(reply.error?.recoverable !== undefined ? { recoverable: reply.error.recoverable } : {}),
2016
2045
  };
2017
2046
  }
2018
2047
  function errorSummary(error, fallbackCode) {
@@ -2224,6 +2253,23 @@ function executionAnomaliesFromError(error) {
2224
2253
  function limitsSignature(limits) {
2225
2254
  return JSON.stringify(limits ?? null);
2226
2255
  }
2256
+ /** 人类可读的时长(复查通知里"已过"字段):`25 分钟` / `3 小时 5 分钟` / `2 天`。 */
2257
+ function formatElapsed(ms) {
2258
+ const minutes = Math.floor(ms / 60_000);
2259
+ if (minutes < 1)
2260
+ return '不到 1 分钟';
2261
+ const days = Math.floor(minutes / 1440);
2262
+ const hours = Math.floor((minutes % 1440) / 60);
2263
+ const mins = minutes % 60;
2264
+ const parts = [];
2265
+ if (days)
2266
+ parts.push(`${days} 天`);
2267
+ if (hours)
2268
+ parts.push(`${hours} 小时`);
2269
+ if (mins)
2270
+ parts.push(`${mins} 分钟`);
2271
+ return parts.join(' ') || '不到 1 分钟';
2272
+ }
2227
2273
  function mergeStats(legacy, current) {
2228
2274
  if (!legacy)
2229
2275
  return current;
@@ -102,6 +102,7 @@ export function seedTaskRecoveryTrigger(manager, owner) {
102
102
  source: {
103
103
  type: 'event',
104
104
  eventPattern: 'task:error',
105
+ filter: { match: { recoverable: true } },
105
106
  },
106
107
  execution: {
107
108
  type: 'target_session',
@@ -2,6 +2,7 @@ import path from 'path';
2
2
  import crypto from 'crypto';
3
3
  import { normalizePermissionMode as normalizePermissionModeContract } from '../core/permission/mode.js';
4
4
  import { formatChannelKey } from '../core/channel-loader.js';
5
+ import { isTopicThreadId } from '../core/thread/topic-id.js';
5
6
  const MAX_SCRIPT_TIMEOUT_MS = 900_000;
6
7
  const TRIGGER_SESSION_TIMEOUT_MS = 30 * 60_000;
7
8
  const MAX_EXECUTION_TIMEOUT_MS = 2_147_483_647;
@@ -204,6 +205,7 @@ export function renderTemplate(template, ctx) {
204
205
  error: ctx.error,
205
206
  event: ctx.event,
206
207
  source: ctx.source,
208
+ run: ctx.run,
207
209
  };
208
210
  const source = template ?? defaultTemplate(fullCtx);
209
211
  return source.replace(/\{\{\s*([A-Za-z0-9_.]+)\s*\}\}/g, (_m, key) => {
@@ -243,6 +245,7 @@ function normalizeV3Phase2(raw, opts) {
243
245
  name,
244
246
  description: optionalString(raw.description),
245
247
  authorizedBy: optionalString(raw.authorizedBy),
248
+ operatorPeers: normalizeOperatorPeers(raw.operatorPeers),
246
249
  createdAt,
247
250
  updatedAt,
248
251
  origin: normalizeOrigin(raw.origin),
@@ -269,18 +272,35 @@ function normalizeV3Phase2(raw, opts) {
269
272
  validateTriggerSemantics(definition);
270
273
  return definition;
271
274
  }
275
+ /** 操作主体列表:字符串数组、去重、截断到 16 项;保证落盘值可直接用于权限比对。 */
276
+ function normalizeOperatorPeers(value) {
277
+ if (value === undefined || value === null)
278
+ return undefined;
279
+ if (!Array.isArray(value))
280
+ throw new Error('operatorPeers must be an array of AID strings');
281
+ const peers = [];
282
+ for (const item of value) {
283
+ if (typeof item !== 'string' || !item.trim()) {
284
+ throw new Error('operatorPeers entries must be non-empty strings');
285
+ }
286
+ const peer = item.trim();
287
+ if (!peers.includes(peer))
288
+ peers.push(peer);
289
+ }
290
+ if (peers.length === 0)
291
+ return undefined;
292
+ if (peers.length > 16)
293
+ throw new Error('operatorPeers supports at most 16 entries');
294
+ return peers;
295
+ }
272
296
  function normalizeOrigin(value) {
273
297
  if (value === undefined)
274
298
  return undefined;
275
299
  if (!isObject(value))
276
300
  throw new Error('origin must be an object');
277
301
  const raw = value;
278
- const rawSession = normalizeSessionKind(raw.session, 'origin.session');
279
302
  const threadId = optionalString(raw.threadId);
280
- if (rawSession === 'main' && threadId)
281
- throw new Error('origin.threadId is not allowed when origin.session=main');
282
- if (rawSession === 'thread' && !threadId)
283
- throw new Error('origin.threadId is required when origin.session=thread');
303
+ const rawSession = inferSessionKind(threadId, raw.session, 'origin.session');
284
304
  const rawChannelId = requiredString(raw.channelId, 'origin.channelId');
285
305
  const peerId = optionalString(raw.peerId);
286
306
  const channelKey = requiredString(raw.channelKey, 'origin.channelKey');
@@ -572,12 +592,8 @@ function normalizeFeedbackTarget(value, label, agentAid) {
572
592
  if (!isObject(value))
573
593
  throw new Error(`${label} must be an object`);
574
594
  const raw = value;
575
- const session = normalizeSessionKind(raw.session, `${label}.session`);
576
595
  const threadId = optionalString(raw.threadId);
577
- if (session === 'main' && threadId)
578
- throw new Error(`${label}.threadId is not allowed when ${label}.session=main`);
579
- if (session === 'thread' && !threadId)
580
- throw new Error(`${label}.threadId is required when ${label}.session=thread`);
596
+ const session = inferSessionKind(threadId, raw.session, `${label}.session`);
581
597
  const channelKey = requiredString(raw.channelKey, `${label}.channelKey`);
582
598
  const channelId = requiredString(raw.channelId, `${label}.channelId`);
583
599
  const rawChatType = raw.chatType;
@@ -615,11 +631,24 @@ function normalizeFeedbackTemplate(value, label) {
615
631
  const raw = value;
616
632
  return { template: optionalTemplate(raw.template) };
617
633
  }
618
- function normalizeSessionKind(value, label) {
619
- const session = optionalString(value) ?? 'main';
620
- if (session !== 'main' && session !== 'thread')
621
- throw new Error(`${label} must be main or thread`);
622
- return session;
634
+ /**
635
+ * 从 threadId 自动推导 session 类型。用户填写的 session 必须与推导结果一致。
636
+ * - threadId 不存在 → 'main'
637
+ * - threadId 是主话题 id → 'main'
638
+ * - threadId 是话题 id → 'thread'
639
+ */
640
+ function inferSessionKind(threadId, rawSession, label) {
641
+ const inferred = threadId && isTopicThreadId(threadId) ? 'thread' : 'main';
642
+ const explicit = optionalString(rawSession);
643
+ if (explicit !== undefined) {
644
+ if (explicit !== 'main' && explicit !== 'thread') {
645
+ throw new Error(`${label} must be main or thread`);
646
+ }
647
+ if (explicit !== inferred) {
648
+ throw new Error(`${label}=${explicit} conflicts with threadId: ${threadId ? `"${threadId}" requires session=` + inferred : 'missing threadId requires session=main'}`);
649
+ }
650
+ }
651
+ return inferred;
623
652
  }
624
653
  function normalizeReliability(value) {
625
654
  if (value === undefined) {