evolcore 0.0.27 → 0.0.29-test.0

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 (195) hide show
  1. package/CHANGELOG.md +38 -15
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +29 -14
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +13 -2
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex/diagnostics.js +4 -3
  9. package/dist/agents/codex-runner.js +444 -106
  10. package/dist/agents/gemini-runner.js +35 -17
  11. package/dist/aun/group/history.js +130 -90
  12. package/dist/aun/group/operations.js +1 -3
  13. package/dist/aun/menu/client.js +1 -0
  14. package/dist/aun/menu/transport.js +5 -3
  15. package/dist/aun/msg/managed-operation.js +70 -0
  16. package/dist/aun/msg/p2p.js +1 -3
  17. package/dist/aun/msg/upload.js +2 -2
  18. package/dist/aun/outbox.js +1 -37
  19. package/dist/channels/aun.js +262 -86
  20. package/dist/channels/daemon.js +18 -1
  21. package/dist/channels/feishu.js +1 -2
  22. package/dist/cli/agent-command.js +23 -3
  23. package/dist/cli/agent-create.js +68 -2
  24. package/dist/cli/agent.js +12 -2
  25. package/dist/cli/aun-commands.js +87 -13
  26. package/dist/cli/cli-argv.js +26 -0
  27. package/dist/cli/daemon-commands.js +116 -149
  28. package/dist/cli/data-command.js +4 -4
  29. package/dist/cli/diag.js +1 -1
  30. package/dist/cli/handoff-command.js +2 -0
  31. package/dist/cli/index.js +18 -1
  32. package/dist/cli/managed-command-guard.js +10 -0
  33. package/dist/cli/memory-command.js +441 -0
  34. package/dist/cli/menu-client.js +4 -0
  35. package/dist/cli/restart-monitor.js +127 -23
  36. package/dist/cli/skill-command.js +41 -0
  37. package/dist/cli/task-context.js +2 -23
  38. package/dist/cli/trigger-command.js +9 -1
  39. package/dist/cli/watch-msg-diag.js +209 -0
  40. package/dist/config/config-field-policy.js +52 -3
  41. package/dist/config/config-manager.js +17 -4
  42. package/dist/config/contact-book-store.js +2 -2
  43. package/dist/config/mention-mode.js +7 -2
  44. package/dist/config/merge.js +28 -9
  45. package/dist/config/resolved-config-op.js +1 -1
  46. package/dist/config/runtime-env.js +137 -0
  47. package/dist/config/schema-registry.js +11 -1
  48. package/dist/config-store.js +10 -10
  49. package/dist/core/audit/event-key.js +3 -1
  50. package/dist/core/audit/log-integrity.js +4 -0
  51. package/dist/core/auth/agent-delegation.js +2 -1
  52. package/dist/core/auth/auth-gateway.js +13 -6
  53. package/dist/core/auth/authorization-audit.js +54 -19
  54. package/dist/core/auth/operation-authorizer.js +17 -0
  55. package/dist/core/auth/operation-catalog.js +9 -0
  56. package/dist/core/causation/context.js +5 -1
  57. package/dist/core/command/agent-control.js +1 -2
  58. package/dist/core/command/command-handler.js +80 -24
  59. package/dist/core/command/diag-service.js +292 -27
  60. package/dist/core/command/memory-slash.js +270 -0
  61. package/dist/core/command/menu-catalog.js +1 -0
  62. package/dist/core/command/menu-handler.js +331 -79
  63. package/dist/core/command/menu-protocol.js +45 -4
  64. package/dist/core/command/slash-gate.js +10 -10
  65. package/dist/core/command/slash-handler.js +230 -79
  66. package/dist/core/data-migration.js +282 -505
  67. package/dist/core/event-catalog.js +26 -0
  68. package/dist/core/evolagent-registry.js +6 -0
  69. package/dist/core/evolagent.js +0 -10
  70. package/dist/core/handoff/store.js +1 -20
  71. package/dist/core/inference/text-inference.js +57 -1
  72. package/dist/core/memory/consolidation-context.js +505 -0
  73. package/dist/core/memory/consolidation-ops-records.js +124 -0
  74. package/dist/core/memory/consolidation-trigger.js +394 -0
  75. package/dist/core/memory/index-manager.js +308 -0
  76. package/dist/core/memory/launch-consolidation.js +395 -0
  77. package/dist/core/memory/memory-admin.js +234 -0
  78. package/dist/core/memory/memory-document.js +100 -0
  79. package/dist/core/memory/memory-session-policy.js +41 -0
  80. package/dist/core/memory/memory-signature.js +257 -0
  81. package/dist/core/memory/memory-writer.js +432 -0
  82. package/dist/core/memory/staleness.js +117 -0
  83. package/dist/core/message/auto-mention-gate.js +454 -0
  84. package/dist/core/message/im-renderer.js +90 -20
  85. package/dist/core/message/message-bridge.js +333 -23
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/message-queue.js +114 -27
  88. package/dist/core/message/pending-hints.js +329 -161
  89. package/dist/core/message/response-engine.js +596 -90
  90. package/dist/core/permission/ec-command-parser.js +1044 -87
  91. package/dist/core/permission/protected-paths.js +376 -126
  92. package/dist/core/permission/sandbox-runtime.js +40 -12
  93. package/dist/core/permission/tool-error-code.js +96 -13
  94. package/dist/core/permission/tool-policy.js +277 -24
  95. package/dist/core/role/runtime-policy.js +3 -7
  96. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  97. package/dist/core/session/session-fs-store.js +0 -76
  98. package/dist/core/session/session-idle-monitor.js +355 -0
  99. package/dist/core/session/session-manager.js +482 -17
  100. package/dist/core/session/session-mapper.js +33 -16
  101. package/dist/core/session/session-renew.js +217 -155
  102. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  103. package/dist/core/skill/global-watcher.js +282 -0
  104. package/dist/core/skill/index-generator.js +84 -0
  105. package/dist/core/skill/startup.js +73 -0
  106. package/dist/core/skill/switch-resolver.js +115 -0
  107. package/dist/core/task/task-activity-store.js +219 -0
  108. package/dist/eck/kit-renderer.js +30 -3
  109. package/dist/eck/manifest-engine.js +26 -4
  110. package/dist/index.js +722 -171
  111. package/dist/ipc.js +564 -2
  112. package/dist/paths.js +30 -9
  113. package/dist/product.js +1 -0
  114. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  115. package/dist/response-system/modes/single-session/index.js +3 -0
  116. package/dist/trigger/feedback.js +1 -0
  117. package/dist/trigger/manager.js +0 -10
  118. package/dist/trigger/scheduler.js +11 -0
  119. package/dist/trigger/validation.js +22 -0
  120. package/dist/utils/atomic-write.js +98 -0
  121. package/dist/utils/avatar-upload.js +59 -18
  122. package/dist/utils/cross-platform.js +87 -16
  123. package/dist/utils/error-dict.json +6 -3
  124. package/dist/utils/error-utils.js +27 -0
  125. package/dist/utils/logger.js +32 -10
  126. package/dist/utils/media-cache.js +84 -0
  127. package/dist/utils/msg-diag.js +97 -0
  128. package/dist/utils/npm-ops.js +119 -4
  129. package/dist/utils/package-version-info.js +244 -0
  130. package/dist/utils/welcome.js +37 -8
  131. package/dist/utils/windows-shell-trust.js +37 -0
  132. package/kits/docs/AUTHORING.md +314 -0
  133. package/kits/docs/GUIDE.md +2 -0
  134. package/kits/docs/INDEX.md +2 -0
  135. package/kits/docs/aun/CHEATSHEET.md +4 -0
  136. package/kits/docs/aun/SDK.md +60 -0
  137. package/kits/docs/channels/aun.md +8 -0
  138. package/kits/docs/context-assembly.md +10 -6
  139. package/kits/docs/evolcore/INDEX.md +17 -3
  140. package/kits/docs/evolcore/agent-create.md +24 -0
  141. package/kits/docs/evolcore/agent.md +5 -2
  142. package/kits/docs/evolcore/config.md +1 -1
  143. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  144. package/kits/docs/evolcore/group.md +34 -18
  145. package/kits/docs/evolcore/memory-architecture.md +78 -0
  146. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  147. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  148. package/kits/docs/evolcore/memory-tags.md +63 -0
  149. package/kits/docs/evolcore/memory.md +110 -0
  150. package/kits/docs/evolcore/menu-client.md +27 -11
  151. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  152. package/kits/docs/evolcore/menu-protocol.md +213 -0
  153. package/kits/docs/evolcore/msg.md +2 -0
  154. package/kits/docs/evolcore/trigger.md +7 -5
  155. package/kits/docs/path-registry.md +6 -8
  156. package/kits/docs/prompt-loading-architecture.md +5 -3
  157. package/kits/docs/venues/group.md +5 -4
  158. package/kits/eck_manifest.json +294 -88
  159. package/kits/eck_manifest.memory.json +257 -0
  160. package/kits/rules/03-identity.md +8 -8
  161. package/kits/rules/05-venue.md +4 -2
  162. package/kits/rules/06-channel.md +1 -1
  163. package/kits/schemas/_meta.json +276 -47
  164. package/kits/schemas/agent-config.schema.12.json +38 -2
  165. package/kits/schemas/agent-config.schema.13.json +591 -0
  166. package/kits/schemas/daemon.schema.6.json +9 -9
  167. package/kits/schemas/defaults.schema.5.json +29 -0
  168. package/kits/schemas/relation-config.schema.8.json +14 -1
  169. package/kits/schemas/relation-config.schema.9.json +129 -0
  170. package/kits/schemas/role-config.schema.1.json +3 -3
  171. package/kits/skills/.switches.json +3 -0
  172. package/kits/skills/ec-skill/SKILL.md +217 -0
  173. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  174. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  175. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  176. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  177. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  178. package/kits/templates/decision/auto-mention.md +25 -0
  179. package/kits/templates/decision/session-renew.md +22 -0
  180. package/kits/templates/memory-consolidation/forget.md +104 -0
  181. package/kits/templates/memory-consolidation/generalize.md +76 -0
  182. package/kits/templates/memory-consolidation/notes.md +133 -0
  183. package/kits/templates/memory-consolidation/profile.md +58 -0
  184. package/kits/templates/memory-consolidation/promote.md +72 -0
  185. package/kits/templates/memory-consolidation/workflow.md +75 -0
  186. package/kits/templates/system-fragments/bootstrap.md +14 -5
  187. package/kits/templates/system-fragments/commands.md +1 -0
  188. package/kits/templates/system-fragments/group-history.md +6 -0
  189. package/kits/templates/system-fragments/identity.md +0 -3
  190. package/kits/templates/system-fragments/memory.md +79 -0
  191. package/kits/templates/system-fragments/reply.md +1 -0
  192. package/kits/templates/system-fragments/venue.md +1 -1
  193. package/package.json +4 -6
  194. package/dist/trigger/legacy-session-history.js +0 -19
  195. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -6,6 +6,7 @@ import { promisify } from 'util';
6
6
  import fs from 'fs';
7
7
  import { getProcessStartTime, parseCimDate } from './process-introspect.js';
8
8
  import { decodeWindowsOutput } from './windows-output.js';
9
+ import { resolveTrustedWindowsCmdPath } from './windows-shell-trust.js';
9
10
  const execFileAsync = promisify(execFile);
10
11
  const CLI_PROBE_FAILURE_BACKOFF_MS = 5_000;
11
12
  const CLI_PROBE_PACKAGES = {
@@ -17,9 +18,70 @@ const cliPathCache = new Map();
17
18
  const cliInstallationCache = new Map();
18
19
  const cliProbeCache = new Map();
19
20
  export const isWindows = process.platform === 'win32';
21
+ // Keep this in sync with the Windows command-line metacharacters. The whole
22
+ // `/c` expression is passed as one verbatim argument so paths with spaces and
23
+ // shim-forwarded values such as `%`, `!`, `&`, and quotes stay data.
24
+ const WINDOWS_CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
25
+ function escapeWindowsCmdCommand(value) {
26
+ return value.replace(WINDOWS_CMD_META_RE, '^$1');
27
+ }
28
+ function escapeWindowsCmdArgument(value) {
29
+ let escaped = String(value);
30
+ escaped = escaped.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
31
+ escaped = escaped.replace(/(?=(\\+?)?)\1$/g, '$1$1');
32
+ escaped = `"${escaped}"`;
33
+ return escaped.replace(WINDOWS_CMD_META_RE, '^$1');
34
+ }
35
+ /** Build one argv-based CLI launch without consulting the parent terminal. */
36
+ export function buildCliLaunchCommand(executable, args, options = {}) {
37
+ const windows = options.isWindows ?? isWindows;
38
+ const extension = windows ? path.win32.extname(executable).toLowerCase() : '';
39
+ const shim = extension === '.cmd' || extension === '.bat';
40
+ if (!shim) {
41
+ return {
42
+ command: executable,
43
+ args,
44
+ resolvedPath: executable,
45
+ launcherKind: 'native-exe',
46
+ launcherPath: executable,
47
+ };
48
+ }
49
+ const resolveWindowsCmd = options.resolveWindowsCmd ?? resolveTrustedWindowsCmdPath;
50
+ const launcherPath = resolveWindowsCmd(options.comSpec);
51
+ if (!launcherPath) {
52
+ throw Object.assign(new Error('未找到受信任的系统 cmd.exe,已拒绝启动 Windows CLI shim'), {
53
+ code: 'EACCES',
54
+ path: executable,
55
+ });
56
+ }
57
+ const shellCommand = [
58
+ escapeWindowsCmdCommand(executable),
59
+ ...args.map(escapeWindowsCmdArgument),
60
+ ].join(' ');
61
+ return {
62
+ command: launcherPath,
63
+ args: ['/d', '/s', '/v:off', '/c', `"${shellCommand}"`],
64
+ resolvedPath: executable,
65
+ launcherKind: extension === '.cmd' ? 'cmd-shim' : 'bat-shim',
66
+ launcherPath,
67
+ windowsVerbatimArguments: true,
68
+ };
69
+ }
20
70
  /** Resolve a CLI executable without spawning where/which. */
21
71
  export function resolveProbePath(command) {
22
- const pathKey = `${command}\0${process.cwd()}\0${process.env.PATH ?? ''}\0${process.env.PATHEXT ?? ''}`;
72
+ const extensions = isWindows
73
+ ? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(ext => /\.(com|exe|bat|cmd)$/i.test(ext))
74
+ : [''];
75
+ return resolveProbePathWithExtensions(command, extensions, 'executable');
76
+ }
77
+ /** Diagnose an incomplete npm installation without ever executing its PS1 shim. */
78
+ export function resolvePowerShellShimPath(command) {
79
+ if (!isWindows)
80
+ return null;
81
+ return resolveProbePathWithExtensions(command, ['.ps1'], 'powershell-shim');
82
+ }
83
+ function resolveProbePathWithExtensions(command, extensions, cacheKind) {
84
+ const pathKey = `${cacheKind}\0${command}\0${process.cwd()}\0${process.env.PATH ?? ''}\0${extensions.join(';')}`;
23
85
  const cachedPath = cliPathCache.get(pathKey);
24
86
  if (cachedPath && fs.existsSync(cachedPath))
25
87
  return cachedPath;
@@ -27,9 +89,6 @@ export function resolveProbePath(command) {
27
89
  const directories = (process.env.PATH ?? '').split(path.delimiter);
28
90
  if (isWindows)
29
91
  directories.unshift(process.cwd());
30
- const extensions = isWindows
31
- ? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(ext => /\.(com|exe|bat|cmd)$/i.test(ext))
32
- : [''];
33
92
  for (const directory of directories) {
34
93
  for (const extension of extensions) {
35
94
  const candidate = path.resolve(directory.replace(/^"|"$/g, ''), command + extension);
@@ -98,20 +157,25 @@ export function invalidateCliProbes() {
98
157
  function cliProbeTimeouts(command, options) {
99
158
  return options.timeouts?.length ? options.timeouts : command === 'codex' ? [3_000, 15_000] : [3_000];
100
159
  }
101
- function prepareCliProbe(command, args) {
102
- const executable = resolveProbePath(command);
160
+ function prepareCliProbe(command, args, options = {}) {
161
+ const executable = options.executable ?? resolveProbePath(command);
103
162
  if (!executable)
104
163
  throw Object.assign(new Error(`${command} CLI not found`), { code: 'ENOENT' });
105
- const identity = `${probeInstallationIdentity(command, executable)}|${process.env.ComSpec ?? ''}`;
164
+ const launchCommand = buildCliLaunchCommand(executable, args);
165
+ const launch = launchCommand;
166
+ const identity = `${probeInstallationIdentity(command, executable)}|${cliFileIdentity(launch.launcherPath)}`;
106
167
  const key = JSON.stringify([command, args]);
107
168
  let entry = cliProbeCache.get(key);
108
169
  if (!entry || entry.identity !== identity || (entry.retryAt !== undefined && Date.now() >= entry.retryAt)) {
109
170
  entry = { identity };
110
171
  cliProbeCache.set(key, entry);
111
172
  }
112
- const shim = isWindows && /\.(cmd|bat)$/i.test(executable);
113
- return { entry, command: shim ? process.env.ComSpec ?? 'cmd.exe' : executable,
114
- args: shim ? ['/d', '/s', '/c', executable, ...args] : args };
173
+ return {
174
+ entry,
175
+ command: launchCommand.command,
176
+ args: launchCommand.args,
177
+ launch,
178
+ };
115
179
  }
116
180
  function failCliProbe(entry, error) {
117
181
  entry.error = error;
@@ -119,7 +183,7 @@ function failCliProbe(entry, error) {
119
183
  throw error;
120
184
  }
121
185
  export function probeCliSync(command, args, options = {}) {
122
- const request = prepareCliProbe(command, args);
186
+ const request = prepareCliProbe(command, args, options);
123
187
  if (request.entry.output !== undefined)
124
188
  return request.entry.output;
125
189
  if (request.entry.error !== undefined)
@@ -127,10 +191,14 @@ export function probeCliSync(command, args, options = {}) {
127
191
  for (const [index, timeout] of cliProbeTimeouts(command, options).entries()) {
128
192
  const startedAt = Date.now();
129
193
  try {
130
- return request.entry.output = execFileSync(request.command, request.args, { encoding: 'utf8', timeout, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
194
+ return request.entry.output = execFileSync(request.command, request.args, {
195
+ encoding: 'utf8', timeout, windowsHide: true,
196
+ windowsVerbatimArguments: request.launch.windowsVerbatimArguments,
197
+ stdio: ['ignore', 'pipe', 'pipe'],
198
+ });
131
199
  }
132
200
  catch (error) {
133
- options.onFailure?.(args, startedAt, error, timeout);
201
+ options.onFailure?.(args, startedAt, error, timeout, request.launch);
134
202
  if (error.code !== 'ETIMEDOUT' || index === cliProbeTimeouts(command, options).length - 1)
135
203
  return failCliProbe(request.entry, error);
136
204
  }
@@ -138,7 +206,7 @@ export function probeCliSync(command, args, options = {}) {
138
206
  throw new Error('CLI probe exhausted');
139
207
  }
140
208
  export async function probeCli(command, args, options = {}) {
141
- const request = prepareCliProbe(command, args);
209
+ const request = prepareCliProbe(command, args, options);
142
210
  if (request.entry.output !== undefined)
143
211
  return request.entry.output;
144
212
  if (request.entry.error !== undefined)
@@ -151,7 +219,10 @@ export async function probeCli(command, args, options = {}) {
151
219
  const startedAt = Date.now();
152
220
  try {
153
221
  const output = await new Promise((resolve, reject) => {
154
- const child = execFile(request.command, request.args, { encoding: 'utf8', timeout, windowsHide: true }, (error, stdout, stderr) => {
222
+ const child = execFile(request.command, request.args, {
223
+ encoding: 'utf8', timeout, windowsHide: true,
224
+ windowsVerbatimArguments: request.launch.windowsVerbatimArguments,
225
+ }, (error, stdout, stderr) => {
155
226
  if (error) {
156
227
  if (error.killed && error.signal === 'SIGTERM' && error.code == null)
157
228
  error.code = 'ETIMEDOUT';
@@ -166,7 +237,7 @@ export async function probeCli(command, args, options = {}) {
166
237
  return request.entry.output = output;
167
238
  }
168
239
  catch (error) {
169
- options.onFailure?.(args, startedAt, error, timeout);
240
+ options.onFailure?.(args, startedAt, error, timeout, request.launch);
170
241
  if (error.code !== 'ETIMEDOUT' || index === timeouts.length - 1)
171
242
  return failCliProbe(request.entry, error);
172
243
  }
@@ -109,19 +109,22 @@
109
109
  "id": "api-error-502",
110
110
  "match": "api error: 502",
111
111
  "action": "retry",
112
- "type": "api_error"
112
+ "type": "api_error",
113
+ "message": "❌ API 网关异常,请稍后重试"
113
114
  },
114
115
  {
115
116
  "id": "api-error-503",
116
117
  "match": "api error: 503",
117
118
  "action": "retry",
118
- "type": "api_error"
119
+ "type": "api_error",
120
+ "message": "❌ API 服务暂时不可用,请稍后重试"
119
121
  },
120
122
  {
121
123
  "id": "api-error-504",
122
124
  "match": "api error: 504",
123
125
  "action": "retry",
124
- "type": "api_error"
126
+ "type": "api_error",
127
+ "message": "❌ API 请求超时,请稍后重试"
125
128
  },
126
129
  {
127
130
  "id": "not-valid-json",
@@ -25,6 +25,7 @@ export function isExecutionFailure(error) {
25
25
  return error instanceof ExecutionTimeoutError || error instanceof OrphanedTargetSessionError;
26
26
  }
27
27
  export const CLAUDE_STARTUP_ARGUMENTS_TOO_LONG = 'claude_startup_arguments_too_long';
28
+ export const CLAUDE_CODE_STARTUP_FAILED = 'claude_code_startup_failed';
28
29
  /** A completed provider response exhausted its output budget, rather than a transport failure. */
29
30
  export function isOutputTokenLimitError(error) {
30
31
  const record = error && typeof error === 'object' ? error : undefined;
@@ -58,6 +59,22 @@ export function createClaudeStartupArgumentsTooLongError(error) {
58
59
  wrapped.cause = error;
59
60
  return wrapped;
60
61
  }
62
+ export function isClaudeCodeStartupProcessError(error) {
63
+ const record = error && typeof error === 'object' ? error : undefined;
64
+ const code = String(record?.code ?? '').toLowerCase();
65
+ const message = String(record?.message ?? error ?? '').toLowerCase();
66
+ return code === 'eftype'
67
+ || message.includes('spawn eftype')
68
+ || message.includes('not a valid application for this os platform');
69
+ }
70
+ export function createClaudeCodeStartupError(error) {
71
+ const wrapped = new Error(`${CLAUDE_CODE_STARTUP_FAILED}: Claude Code SDK 本地运行时启动失败,任务未执行`);
72
+ wrapped.name = 'ClaudeCodeStartupError';
73
+ wrapped.code = CLAUDE_CODE_STARTUP_FAILED;
74
+ if (error !== undefined)
75
+ wrapped.cause = error;
76
+ return wrapped;
77
+ }
61
78
  export var ErrorType;
62
79
  (function (ErrorType) {
63
80
  ErrorType["SDK_TIMEOUT"] = "sdk_timeout";
@@ -71,6 +88,7 @@ export var ErrorType;
71
88
  ErrorType["SESSION_UNAVAILABLE"] = "session_unavailable";
72
89
  ErrorType["SANDBOX_INITIALIZATION_FAILED"] = "sandbox_initialization_failed";
73
90
  ErrorType["CLAUDE_STARTUP_ARGUMENTS_TOO_LONG"] = "claude_startup_arguments_too_long";
91
+ ErrorType["CLAUDE_CODE_STARTUP_FAILED"] = "claude_code_startup_failed";
74
92
  ErrorType["UNKNOWN"] = "unknown";
75
93
  })(ErrorType || (ErrorType = {}));
76
94
  /**
@@ -329,6 +347,9 @@ export function classifyError(error) {
329
347
  if (isClaudeStartupArgumentsTooLong(error)) {
330
348
  return ErrorType.CLAUDE_STARTUP_ARGUMENTS_TOO_LONG;
331
349
  }
350
+ if (error?.code === CLAUDE_CODE_STARTUP_FAILED) {
351
+ return ErrorType.CLAUDE_CODE_STARTUP_FAILED;
352
+ }
332
353
  // 字典优先 — 命中则直接返回
333
354
  const rule = matchErrorRule(msg);
334
355
  if (rule) {
@@ -410,6 +431,10 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
410
431
  const prefix = includeEmoji ? '⚠️ ' : '';
411
432
  return `${prefix}当前 Claude 启动参数过长,任务未执行`;
412
433
  }
434
+ if (error?.code === CLAUDE_CODE_STARTUP_FAILED) {
435
+ const prefix = includeEmoji ? '❌ ' : '';
436
+ return `${prefix}Claude Code SDK 本地运行时启动失败(${CLAUDE_CODE_STARTUP_FAILED}),当前任务未执行。请重新安装并对齐 Claude Code SDK 与 Windows 原生组件版本,然后重启 EvolCore 后重试`;
437
+ }
413
438
  // terminalReason 提供更精确的错误提示(SDK 0.2.100+)
414
439
  if (terminalReason) {
415
440
  const prefix = includeEmoji ? '❌ ' : '';
@@ -433,6 +458,8 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
433
458
  return `${warnPrefix}执行沙箱启动失败(sandbox_initialization_failed),当前操作未执行;请检查文件系统挂载规则及 user namespace/seccomp 配置`;
434
459
  case 'claude_startup_arguments_too_long':
435
460
  return `${warnPrefix}当前 Claude 启动参数过长,任务未执行`;
461
+ case 'claude_code_startup_failed':
462
+ return `${prefix}Claude Code SDK 本地运行时启动失败(${CLAUDE_CODE_STARTUP_FAILED}),当前任务未执行。请重新安装并对齐 Claude Code SDK 与 Windows 原生组件版本,然后重启 EvolCore 后重试`;
436
463
  case 'aborted_streaming':
437
464
  case 'aborted_tools':
438
465
  return `${prefix}任务已中断`;
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import { resolvePaths } from '../paths.js';
3
3
  import { LogWriter } from './log-writer.js';
4
- import { classifyToolErrorCode, normalizeToolErrorCode } from '../core/permission/tool-error-code.js';
4
+ import { normalizeToolErrorClassification } from '../core/permission/tool-error-code.js';
5
5
  import { normalizePermissionMode } from '../core/permission/mode.js';
6
6
  import { buildToolLifecycleEventKey, normalizeToolCorrelation } from '../core/audit/event-key.js';
7
7
  let currentLevel = process.env.LOG_LEVEL || 'INFO';
@@ -114,21 +114,40 @@ export function normalizeStructuredLog(data) {
114
114
  ?? (isToolResult && isError === true ? 'error' : undefined)
115
115
  ?? (isToolResult && isError === false ? 'allow' : undefined)
116
116
  ?? (isToolUse ? 'pending' : undefined);
117
- const executed = data.executed ?? nested?.executed
117
+ const explicitExecuted = data.executed ?? nested?.executed;
118
+ const explicitExecutionState = data.executionState ?? data.execution_state
119
+ ?? nested?.executionState ?? nested?.execution_state;
120
+ const executed = explicitExecuted
118
121
  ?? (isToolResult ? true : isToolUse ? false : undefined);
119
- const executionState = data.executionState ?? data.execution_state
120
- ?? nested?.executionState ?? nested?.execution_state
122
+ const executionState = explicitExecutionState
121
123
  ?? (isToolResult ? (isError === true ? 'failed' : 'completed') : isToolUse ? 'requested' : undefined);
124
+ const exitCode = data.exitCode ?? data.exit_code ?? nested?.exitCode ?? nested?.exit_code;
122
125
  const explicitErrorCode = data.errorCode
123
126
  ?? data.error_code
124
127
  ?? nested?.errorCode
125
128
  ?? nested?.error_code;
126
- const errorCode = isToolResult && isError === true
127
- ? classifyToolErrorCode({ errorCode: explicitErrorCode, decisionSource })
128
- : explicitErrorCode;
129
- const classificationMissing = isToolResult && isError === true
130
- && !normalizeToolErrorCode(explicitErrorCode)
131
- && decisionSource !== 'policy' && decisionSource !== 'approval';
129
+ const explicitErrorDetailCode = data.errorDetailCode
130
+ ?? data.detailCode
131
+ ?? data.detail_code
132
+ ?? nested?.errorDetailCode
133
+ ?? nested?.detailCode
134
+ ?? nested?.detail_code;
135
+ const classification = isToolResult && isError === true
136
+ ? normalizeToolErrorClassification({
137
+ errorCode: explicitErrorCode,
138
+ errorDetailCode: explicitErrorDetailCode,
139
+ decisionSource,
140
+ executed: explicitExecuted,
141
+ executionState: explicitExecutionState,
142
+ exitCode,
143
+ policyCode: data.policyCode ?? nested?.policyCode,
144
+ })
145
+ : undefined;
146
+ const errorCode = classification?.errorCode ?? explicitErrorCode;
147
+ const errorDetailCode = classification?.errorDetailCode
148
+ ?? (typeof explicitErrorDetailCode === 'string' ? explicitErrorDetailCode : undefined);
149
+ const classificationMissing = classification?.classificationMissing || data.classificationMissing === true || nested?.classificationMissing === true;
150
+ const classificationConflict = classification?.classificationConflict || data.classificationConflict === true || nested?.classificationConflict === true;
132
151
  const lifecycleEventKey = lifecycleRecord
133
152
  ? (eventKey ?? buildToolLifecycleEventKey({
134
153
  sessionId,
@@ -155,7 +174,10 @@ export function normalizeStructuredLog(data) {
155
174
  ...(executed !== undefined ? { executed } : {}),
156
175
  ...(executionState ? { executionState } : {}),
157
176
  ...(errorCode ? { errorCode } : {}),
177
+ ...(errorDetailCode ? { errorDetailCode } : {}),
178
+ ...(exitCode !== undefined ? { exitCode } : {}),
158
179
  ...(classificationMissing ? { classificationMissing: true } : {}),
180
+ ...(classificationConflict ? { classificationConflict: true } : {}),
159
181
  ...(missingContextFields.length > 0 ? { contextMissing: missingContextFields } : {}),
160
182
  };
161
183
  }
@@ -11,6 +11,9 @@ import path from 'path';
11
11
  import fs from 'fs';
12
12
  import crypto from 'crypto';
13
13
  import { logger } from './logger.js';
14
+ import { agentRelationDir } from '../paths.js';
15
+ import { parsePeerKey } from '../core/relation/peer-identity.js';
16
+ import { isSameOrDescendant } from '../core/permission/protected-paths.js';
14
17
  // ── 常量 ──────────────────────────────────────────────────────────────────────
15
18
  const DEFAULT_MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10 MB
16
19
  const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
@@ -18,6 +21,14 @@ const ALLOWED_IMAGE_MIMES = new Set([
18
21
  'image/png', 'image/jpeg', 'image/gif', 'image/webp',
19
22
  ]);
20
23
  const UPLOADS_SUBDIR = '.evolcore/uploads';
24
+ const RELATION_ATTACHMENT_ID_RE = /^[a-f0-9]{32}$/;
25
+ const RELATION_ATTACHMENT_MAX_BYTES = DEFAULT_MAX_FILE_SIZE;
26
+ const RELATION_ATTACHMENT_EXTENSIONS = {
27
+ 'image/png': '.png',
28
+ 'image/jpeg': '.jpg',
29
+ 'image/gif': '.gif',
30
+ 'image/webp': '.webp',
31
+ };
21
32
  // ── SSRF 防护 ─────────────────────────────────────────────────────────────────
22
33
  /** 已知安全的 CDN 域名白名单 */
23
34
  const ALLOWED_CDN_HOSTS = new Set([
@@ -101,6 +112,79 @@ export async function validateImage(buffer, opts) {
101
112
  }
102
113
  return { mime: type.mime };
103
114
  }
115
+ function relationAttachmentsDir(selfAid, peerKey) {
116
+ parsePeerKey(peerKey);
117
+ return path.join(agentRelationDir(selfAid, peerKey), 'attachments');
118
+ }
119
+ /** Save one inbound image as a stable, peer-scoped attachment owned by the daemon. */
120
+ export function saveRelationAttachment(buffer, selfAid, peerKey, mimeType) {
121
+ const extension = RELATION_ATTACHMENT_EXTENSIONS[mimeType];
122
+ if (!extension)
123
+ throw new Error(`Unsupported relation attachment MIME type: ${mimeType}`);
124
+ if (buffer.length < 1 || buffer.length > RELATION_ATTACHMENT_MAX_BYTES) {
125
+ throw new Error(`Relation attachment must be between 1 byte and ${RELATION_ATTACHMENT_MAX_BYTES} bytes`);
126
+ }
127
+ const directory = relationAttachmentsDir(selfAid, peerKey);
128
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
129
+ if (process.platform !== 'win32')
130
+ fs.chmodSync(directory, 0o700);
131
+ const attachmentId = crypto.randomBytes(16).toString('hex');
132
+ const filename = `${attachmentId}${extension}`;
133
+ const filePath = path.join(directory, filename);
134
+ const metadataPath = path.join(directory, `${attachmentId}.json`);
135
+ const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
136
+ const metadata = {
137
+ version: 1, attachmentId, selfAid, peerKey, filename, mimeType,
138
+ sha256, size: buffer.length, createdAt: Date.now(),
139
+ };
140
+ fs.writeFileSync(filePath, buffer, { flag: 'wx', mode: 0o600 });
141
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata), { flag: 'wx', mode: 0o600 });
142
+ return { ...metadata, filePath };
143
+ }
144
+ /** Resolve an attachment only inside the authenticated Agent/peer relation. */
145
+ export function readRelationAttachment(selfAid, peerKey, attachmentSource) {
146
+ const attachmentId = RELATION_ATTACHMENT_ID_RE.test(attachmentSource)
147
+ ? attachmentSource
148
+ : path.basename(attachmentSource, path.extname(attachmentSource));
149
+ if (!RELATION_ATTACHMENT_ID_RE.test(attachmentId))
150
+ throw new Error('Invalid attachment ID');
151
+ const directory = relationAttachmentsDir(selfAid, peerKey);
152
+ const metadataPath = path.join(directory, `${attachmentId}.json`);
153
+ const metadataStat = fs.lstatSync(metadataPath);
154
+ if (!metadataStat.isFile() || metadataStat.isSymbolicLink())
155
+ throw new Error('Attachment metadata must be a regular file');
156
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
157
+ if (metadata.version !== 1 || metadata.attachmentId !== attachmentId
158
+ || metadata.selfAid !== selfAid || metadata.peerKey !== peerKey
159
+ || RELATION_ATTACHMENT_EXTENSIONS[metadata.mimeType] === undefined
160
+ || metadata.filename !== `${attachmentId}${RELATION_ATTACHMENT_EXTENSIONS[metadata.mimeType]}`) {
161
+ throw new Error('Attachment ownership metadata mismatch');
162
+ }
163
+ const filePath = path.join(directory, metadata.filename);
164
+ const stat = fs.lstatSync(filePath);
165
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== metadata.size
166
+ || stat.size < 1 || stat.size > RELATION_ATTACHMENT_MAX_BYTES) {
167
+ throw new Error('Attachment file is missing or invalid');
168
+ }
169
+ const realDirectory = fs.realpathSync(directory);
170
+ const realPath = fs.realpathSync(filePath);
171
+ if (!isSameOrDescendant(realPath, realDirectory))
172
+ throw new Error('Attachment path escaped its relation directory');
173
+ if (path.isAbsolute(attachmentSource) && fs.realpathSync(attachmentSource) !== realPath) {
174
+ throw new Error('Attachment path does not match the current relation');
175
+ }
176
+ const buffer = fs.readFileSync(filePath);
177
+ const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
178
+ if (sha256 !== metadata.sha256)
179
+ throw new Error('Attachment content hash mismatch');
180
+ return {
181
+ attachment: {
182
+ attachmentId, selfAid, peerKey, filePath: realPath,
183
+ mimeType: metadata.mimeType, sha256, size: buffer.length,
184
+ },
185
+ buffer,
186
+ };
187
+ }
104
188
  /**
105
189
  * 把已下载的附件 Buffer 转成入站图片条目(供 baseagent 视觉通道)。
106
190
  *
@@ -0,0 +1,97 @@
1
+ /**
2
+ * msg-diag: 消息生命周期诊断打点
3
+ * 各处理环节调用 diagLog(),写入专用 msg-diag.jsonl。
4
+ * ec watch diag 读取该文件,固定行实时展示各环节最新处理的消息。
5
+ */
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ function getLogPath() {
10
+ const home = process.env.EVOLCORE_HOME ?? path.join(os.homedir(), '.evolcore');
11
+ return path.join(home, 'logs', 'msg-diag.jsonl');
12
+ }
13
+ let _logPath = null;
14
+ let _fd = null;
15
+ const MAX_SIZE = 500 * 1024; // 500 KB
16
+ const TRIM_SIZE = 250 * 1024; // 超限后保留最后 250 KB
17
+ function trimIfNeeded(p) {
18
+ try {
19
+ const stat = fs.statSync(p);
20
+ if (stat.size <= MAX_SIZE)
21
+ return;
22
+ // 读最后 TRIM_SIZE 字节,从第一个完整行开始保留
23
+ const fd = fs.openSync(p, 'r');
24
+ const buf = Buffer.alloc(TRIM_SIZE);
25
+ fs.readSync(fd, buf, 0, TRIM_SIZE, stat.size - TRIM_SIZE);
26
+ fs.closeSync(fd);
27
+ const str = buf.toString('utf-8');
28
+ const firstNl = str.indexOf('\n');
29
+ const kept = firstNl >= 0 ? str.slice(firstNl + 1) : str;
30
+ fs.writeFileSync(p, kept, 'utf-8');
31
+ }
32
+ catch {
33
+ // 修剪失败不阻塞主流程
34
+ }
35
+ }
36
+ function getFd() {
37
+ try {
38
+ if (!_logPath)
39
+ _logPath = getLogPath();
40
+ const dir = path.dirname(_logPath);
41
+ if (!fs.existsSync(dir))
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ // 每次写前检查大小,超限则修剪并重新打开
44
+ if (_fd !== null) {
45
+ try {
46
+ const stat = fs.statSync(_logPath);
47
+ if (stat.size > MAX_SIZE) {
48
+ fs.closeSync(_fd);
49
+ _fd = null;
50
+ trimIfNeeded(_logPath);
51
+ }
52
+ }
53
+ catch {
54
+ fs.closeSync(_fd);
55
+ _fd = null;
56
+ }
57
+ }
58
+ if (_fd === null) {
59
+ _fd = fs.openSync(_logPath, 'a');
60
+ }
61
+ return _fd;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ function shortId(id) {
68
+ if (!id)
69
+ return '';
70
+ return id.split('.')[0];
71
+ }
72
+ function cut(text, n) {
73
+ if (!text)
74
+ return '';
75
+ const s = text.replace(/\r?\n/g, ' ').trim();
76
+ return s.length <= n ? s : s.slice(0, n - 1) + '…';
77
+ }
78
+ export function diagLog(stage, data) {
79
+ try {
80
+ const entry = {
81
+ stage,
82
+ ts: new Date().toISOString(),
83
+ aid: shortId(data.aid ?? ''),
84
+ from: shortId(data.from ?? ''),
85
+ to: shortId(data.to ?? ''),
86
+ msgId: data.msgId ?? '',
87
+ taskId: data.taskId,
88
+ preview: cut(data.text ?? '', 50),
89
+ };
90
+ const fd = getFd();
91
+ if (fd !== null)
92
+ fs.writeSync(fd, JSON.stringify(entry) + '\n');
93
+ }
94
+ catch {
95
+ // 诊断打点失败不影响主流程
96
+ }
97
+ }