evolcore 0.0.11 → 0.0.13

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 (164) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/bin/ec-safe-output.js +26 -7
  4. package/dist/agents/claude-runner.js +86 -7
  5. package/dist/agents/codex-app-server-client.js +174 -20
  6. package/dist/agents/codex-runner.js +375 -92
  7. package/dist/agents/ecagent-runner.js +39 -4
  8. package/dist/agents/gemini-runner.js +1 -0
  9. package/dist/aun/aid/client.js +11 -21
  10. package/dist/aun/aid/control-aid.js +36 -13
  11. package/dist/aun/aid/managed-operation.js +107 -0
  12. package/dist/aun/aid/store.js +13 -7
  13. package/dist/aun/group-fs-download.js +56 -0
  14. package/dist/aun/group-identity.js +1 -2
  15. package/dist/aun/msg/group.js +91 -44
  16. package/dist/aun/msg/index.js +1 -2
  17. package/dist/aun/msg/managed-operation.js +865 -0
  18. package/dist/aun/msg/mention-schema.js +20 -0
  19. package/dist/aun/msg/p2p.js +7 -0
  20. package/dist/aun/msg/upload.js +16 -1
  21. package/dist/aun/outbox.js +31 -2
  22. package/dist/aun/rpc/client.js +66 -0
  23. package/dist/aun/rpc/index.js +1 -2
  24. package/dist/aun/storage/{upload.js → client.js} +11 -2
  25. package/dist/aun/storage/index.js +1 -2
  26. package/dist/channels/aun.js +655 -240
  27. package/dist/channels/daemon.js +13 -0
  28. package/dist/cli/agent-command.js +56 -8
  29. package/dist/cli/agent.js +108 -31
  30. package/dist/cli/aun-commands.js +270 -69
  31. package/dist/cli/bench.js +12 -3
  32. package/dist/cli/cli-argv.js +10 -0
  33. package/dist/cli/config.js +9 -17
  34. package/dist/cli/ctl-command.js +1 -1
  35. package/dist/cli/daemon-commands.js +70 -55
  36. package/dist/cli/fs-command.js +79 -10
  37. package/dist/cli/handoff-command.js +3 -0
  38. package/dist/cli/index.js +30 -2
  39. package/dist/cli/init.js +77 -8
  40. package/dist/cli/managed-command-guard.js +41 -0
  41. package/dist/cli/model.js +11 -4
  42. package/dist/cli/queue-command.js +53 -1
  43. package/dist/cli/response.js +71 -0
  44. package/dist/cli/restart-monitor.js +7 -32
  45. package/dist/cli/trigger-command.js +11 -0
  46. package/dist/config/access-policy.js +1 -2
  47. package/dist/config/builtin-roles.js +9 -0
  48. package/dist/config/config-manager.js +21 -1
  49. package/dist/config/contact-book-store.js +11 -3
  50. package/dist/config/contact-request-service.js +161 -15
  51. package/dist/config/mention-mode.js +8 -24
  52. package/dist/config/peer-role-resolver.js +6 -2
  53. package/dist/config/role-migration-startup.js +53 -0
  54. package/dist/config/role-store.js +1 -1
  55. package/dist/core/auth/agent-delegation.js +1 -2
  56. package/dist/core/auth/auth-gateway.js +29 -1
  57. package/dist/core/auth/authorization-audit.js +15 -0
  58. package/dist/core/auth/operation-authorizer.js +31 -7
  59. package/dist/core/auth/operation-catalog.js +196 -25
  60. package/dist/core/bootstrap-messages.js +25 -0
  61. package/dist/core/bootstrap-service.js +64 -4
  62. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  63. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  64. package/dist/core/capability/skill-discovery.js +55 -0
  65. package/dist/core/channel-loader.js +1 -1
  66. package/dist/core/command/cli-intent-parser.js +9 -0
  67. package/dist/core/command/command-handler.js +650 -42
  68. package/dist/core/command/connect-menu.js +15 -0
  69. package/dist/core/command/group-menu.js +4 -4
  70. package/dist/core/command/menu-catalog.js +370 -0
  71. package/dist/core/command/menu-handler.js +230 -51
  72. package/dist/core/command/menu-protocol.js +94 -2
  73. package/dist/core/command/slash-gate.js +5 -2
  74. package/dist/core/command/slash-handler.js +99 -31
  75. package/dist/core/daemon-file-cache.js +2 -1
  76. package/dist/core/data-migration.js +9 -2
  77. package/dist/core/event-catalog.js +6 -0
  78. package/dist/core/evolagent.js +4 -2
  79. package/dist/core/handoff/dispatcher.js +5 -2
  80. package/dist/core/handoff/runtime.js +157 -22
  81. package/dist/core/handoff/store.js +29 -1
  82. package/dist/core/handoff/types.js +1 -0
  83. package/dist/core/interaction-router.js +10 -0
  84. package/dist/core/message/im-renderer.js +19 -0
  85. package/dist/core/message/logical-queue-bridge.js +1 -0
  86. package/dist/core/message/mention-schema.js +126 -0
  87. package/dist/core/message/message-bridge.js +298 -88
  88. package/dist/core/message/message-log.js +3 -0
  89. package/dist/core/message/message-queue.js +268 -39
  90. package/dist/core/message/message-utils.js +81 -4
  91. package/dist/core/message/response-engine.js +161 -77
  92. package/dist/core/message/stream-debouncer.js +10 -2
  93. package/dist/core/permission/approval-gateway.js +1 -0
  94. package/dist/core/permission/ec-command-parser.js +243 -18
  95. package/dist/core/permission/index.js +1 -1
  96. package/dist/core/permission/mode.js +15 -0
  97. package/dist/core/permission/readonly-shell-query.js +148 -8
  98. package/dist/core/permission/sandbox-runtime.js +43 -1
  99. package/dist/core/permission/tool-policy.js +322 -57
  100. package/dist/core/protected-paths.js +114 -4
  101. package/dist/core/session/session-manager.js +42 -4
  102. package/dist/core/session/session-mapper.js +20 -1
  103. package/dist/eck/detect.js +18 -1
  104. package/dist/eck/group-rules-sync.js +15 -44
  105. package/dist/eck/manifest-engine.js +39 -13
  106. package/dist/index.js +541 -180
  107. package/dist/ipc.js +232 -10
  108. package/dist/paths.js +3 -0
  109. package/dist/response-system/coordinator.js +6 -4
  110. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  111. package/dist/response-system/modes/single-session/index.js +14 -1
  112. package/dist/response-system/selector.js +6 -5
  113. package/dist/trigger/anomaly-store.js +4 -0
  114. package/dist/trigger/feedback.js +64 -11
  115. package/dist/trigger/manager.js +21 -1
  116. package/dist/trigger/parser.js +14 -2
  117. package/dist/trigger/scheduler.js +1 -0
  118. package/dist/trigger/script-executor.js +1 -0
  119. package/dist/trigger/validation.js +67 -6
  120. package/dist/utils/codex-app-server-registry.js +90 -0
  121. package/dist/utils/codex-cli.js +5 -1
  122. package/dist/utils/cross-platform.js +15 -0
  123. package/dist/utils/ecweb-supervisor.js +332 -0
  124. package/dist/utils/error-utils.js +10 -0
  125. package/dist/utils/npm-ops.js +5 -12
  126. package/dist/utils/tool-summary.js +11 -0
  127. package/kits/docs/channels/aun.md +1 -1
  128. package/kits/docs/context-assembly.md +2 -2
  129. package/kits/docs/evolcore/agent.md +11 -6
  130. package/kits/docs/evolcore/aid.md +14 -0
  131. package/kits/docs/evolcore/trigger.md +17 -1
  132. package/kits/docs/prompt-loading-architecture.md +1 -2
  133. package/kits/docs/venues/group.md +1 -1
  134. package/kits/eck_manifest.json +0 -12
  135. package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
  136. package/kits/rules/04-relation.md +1 -1
  137. package/kits/rules/05-venue.md +2 -2
  138. package/kits/schemas/role-config.schema.1.json +1 -0
  139. package/kits/schemas/single-session.schema.1.json +3 -3
  140. package/kits/schemas/single-session.schema.2.json +3 -3
  141. package/kits/templates/roles/admin.json +27 -0
  142. package/kits/templates/roles/member.json +34 -1
  143. package/kits/templates/roles/visitor.json +35 -3
  144. package/kits/templates/system-fragments/bootstrap.md +1 -1
  145. package/kits/templates/system-fragments/channel.md +1 -1
  146. package/kits/templates/system-fragments/session.md +1 -1
  147. package/package.json +2 -2
  148. package/dist/aun/msg/payload-type.js +0 -27
  149. package/dist/aun/rpc/caller.js +0 -42
  150. package/dist/aun/rpc/connection.js +0 -25
  151. package/dist/aun/storage/manage.js +0 -10
  152. package/dist/config/access-policy-domain.js +0 -18
  153. package/dist/config/config-batch-get.js +0 -11
  154. package/dist/config/owner-policy.js +0 -4
  155. package/dist/config/role-config-v4-startup.js +0 -32
  156. package/dist/config/role-config-v5-startup.js +0 -27
  157. package/dist/core/auth/trigger-authorization.js +0 -15
  158. package/dist/core/command/evol-menu-version-gate.js +0 -39
  159. package/dist/core/interaction-registration.js +0 -10
  160. package/dist/core/permission/execution-sandbox.js +0 -16
  161. package/dist/core/relation/peer-key.js +0 -1
  162. package/dist/core/session/session-key.js +0 -24
  163. package/dist/eck/baseagent-caps.js +0 -18
  164. package/dist/eck/rules-loader.js +0 -28
@@ -0,0 +1,332 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { resolvePaths } from '../paths.js';
5
+ import { WEB_PACKAGE_NAME } from '../product.js';
6
+ import { resolveGlobalPkg } from './npm-ops.js';
7
+ import { ecwebVersionRequirementError, resolveEcwebLaunchCommand } from './ecweb-utils.js';
8
+ import * as platform from './cross-platform.js';
9
+ import { getProcessStartTime } from './process-introspect.js';
10
+ const INSTANCE_FILE_RE = /^(?:ecweb|watch-web)-\d+\.json$/;
11
+ function sleep(ms) {
12
+ return new Promise(resolve => setTimeout(resolve, ms));
13
+ }
14
+ function isAlive(record) {
15
+ if (!Number.isInteger(record.pid) || record.pid <= 0)
16
+ return false;
17
+ // ECWeb versions before the supervisor used a different Linux boot-time
18
+ // calculation, so their `startedAt` can be off by seconds or minutes even
19
+ // while the PID and HTTP listener are healthy. The instance file is already
20
+ // scoped to this EVOLCORE_HOME; use liveness here and let the HTTP probe be
21
+ // the ownership/health check. PID/start-time validation remains in the
22
+ // explicit kill path below.
23
+ return platform.isProcessRunning(record.pid);
24
+ }
25
+ function readEntries() {
26
+ const dir = resolvePaths().instanceDir;
27
+ let files;
28
+ try {
29
+ files = fs.readdirSync(dir);
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ const entries = [];
35
+ for (const file of files) {
36
+ if (!INSTANCE_FILE_RE.test(file))
37
+ continue;
38
+ const filePath = path.join(dir, file);
39
+ try {
40
+ const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
41
+ if (Number.isInteger(value.pid) && value.pid > 0) {
42
+ entries.push({
43
+ filePath,
44
+ record: {
45
+ pid: value.pid,
46
+ port: Number.isInteger(value.port) ? value.port : 42705,
47
+ ...(typeof value.startedAt === 'number' ? { startedAt: value.startedAt } : {}),
48
+ },
49
+ });
50
+ }
51
+ }
52
+ catch {
53
+ // A partially-written instance file is ignored until the next check.
54
+ }
55
+ }
56
+ return entries;
57
+ }
58
+ function removeEntry(entry) {
59
+ try {
60
+ fs.unlinkSync(entry.filePath);
61
+ }
62
+ catch { }
63
+ }
64
+ function removeEntriesForPid(pid) {
65
+ for (const entry of readEntries()) {
66
+ if (entry.record.pid === pid)
67
+ removeEntry(entry);
68
+ }
69
+ }
70
+ async function probe(port) {
71
+ try {
72
+ const response = await fetch(`http://127.0.0.1:${port}/`, {
73
+ signal: AbortSignal.timeout(1500),
74
+ });
75
+ return response.ok;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Owns the ECWeb process for the lifetime of the daemon.
83
+ *
84
+ * The CLI may have started ECWeb moments earlier (ec start/restart/watch web).
85
+ * We first adopt a healthy registered instance and only spawn after a few
86
+ * seconds of failed discovery. This closes the race between the CLI launcher,
87
+ * ECWeb's own stale-instance cleanup, and daemon startup.
88
+ */
89
+ export function createEcwebSupervisor(options) {
90
+ const failureThreshold = Math.max(1, options.failureThreshold ?? 3);
91
+ const checkIntervalMs = Math.max(1000, options.checkIntervalMs ?? 5000);
92
+ let active = null;
93
+ let child = null;
94
+ let logFd = null;
95
+ let timer = null;
96
+ let failureCount = 0;
97
+ let startInFlight = null;
98
+ let stopping = false;
99
+ const logFile = path.join(resolvePaths().logs, 'ecweb-daemon.log');
100
+ const setActiveFromEntry = (entry, owned) => {
101
+ active = {
102
+ pid: entry.record.pid,
103
+ port: entry.record.port,
104
+ // Capture the current PID start time rather than trusting the record.
105
+ // Older ECWeb builds could write a clock-skewed `startedAt` value.
106
+ startedAt: getProcessStartTime(entry.record.pid)
107
+ ?? (typeof entry.record.startedAt === 'number' ? entry.record.startedAt : null),
108
+ owned,
109
+ };
110
+ };
111
+ const findHealthyInstance = async () => {
112
+ for (const entry of readEntries()) {
113
+ if (!isAlive(entry.record)) {
114
+ removeEntry(entry);
115
+ continue;
116
+ }
117
+ if (await probe(entry.record.port))
118
+ return entry;
119
+ }
120
+ return null;
121
+ };
122
+ const waitForHealthyInstance = async (timeoutMs) => {
123
+ const deadline = Date.now() + timeoutMs;
124
+ do {
125
+ const found = await findHealthyInstance();
126
+ if (found)
127
+ return found;
128
+ if (Date.now() >= deadline)
129
+ break;
130
+ await sleep(250);
131
+ } while (!stopping);
132
+ return null;
133
+ };
134
+ const closeLogStream = () => {
135
+ if (logFd === null)
136
+ return;
137
+ try {
138
+ fs.closeSync(logFd);
139
+ }
140
+ catch { }
141
+ logFd = null;
142
+ };
143
+ const killActive = async () => {
144
+ const current = active;
145
+ if (!current) {
146
+ // During the short window between spawn() and ECWeb writing its
147
+ // instance record there is no `active` entry yet, but the daemon still
148
+ // owns the child and must not leave it behind on shutdown.
149
+ const pendingPid = child?.pid;
150
+ if (pendingPid && platform.isProcessRunning(pendingPid)) {
151
+ try {
152
+ platform.killProcess(pendingPid, true);
153
+ }
154
+ catch { }
155
+ await platform.waitForProcessExit(pendingPid, 5000);
156
+ }
157
+ return;
158
+ }
159
+ const actual = getProcessStartTime(current.pid);
160
+ // Never kill a reused PID. `startedAt` was captured from the live process
161
+ // when it was adopted/spawned, so this remains safe even for legacy records
162
+ // whose on-disk timestamp used a different clock.
163
+ const sameProcess = current.startedAt !== null && actual !== null
164
+ ? current.startedAt === actual
165
+ : false;
166
+ if (sameProcess && platform.isProcessRunning(current.pid)) {
167
+ try {
168
+ platform.killProcess(current.pid, true);
169
+ }
170
+ catch { }
171
+ await platform.waitForProcessExit(current.pid, 5000);
172
+ }
173
+ removeEntriesForPid(current.pid);
174
+ active = null;
175
+ };
176
+ const spawnEcweb = async () => {
177
+ const root = resolvePaths().root;
178
+ const localEntry = path.join(root, 'ecweb', 'dist', 'index.js');
179
+ const globalPkg = resolveGlobalPkg(WEB_PACKAGE_NAME);
180
+ let launch = resolveEcwebLaunchCommand(['--home', root, '--port', String(options.port)], {
181
+ installedPkg: globalPkg,
182
+ });
183
+ let cwd = root;
184
+ let version = globalPkg?.version;
185
+ if (!launch && fs.existsSync(localEntry)) {
186
+ launch = {
187
+ command: process.execPath,
188
+ args: [localEntry, '--home', root, '--port', String(options.port)],
189
+ entry: localEntry,
190
+ source: 'package-bin',
191
+ };
192
+ cwd = path.dirname(path.dirname(localEntry));
193
+ try {
194
+ version = JSON.parse(fs.readFileSync(path.join(root, 'ecweb', 'package.json'), 'utf8')).version;
195
+ }
196
+ catch { }
197
+ }
198
+ if (!launch) {
199
+ options.logger.warn(`ECWeb 启动入口不存在,请安装 ${WEB_PACKAGE_NAME}@latest`);
200
+ return;
201
+ }
202
+ const versionError = ecwebVersionRequirementError(version);
203
+ if (versionError) {
204
+ options.logger.error(`ECWeb 未启动: ${versionError}`);
205
+ return;
206
+ }
207
+ fs.mkdirSync(resolvePaths().logs, { recursive: true });
208
+ fs.appendFileSync(logFile, `\n# daemon supervisor start ${new Date().toISOString()}\n`);
209
+ // `spawn()` accepts inherited file descriptors, but not a WriteStream
210
+ // whose fd has not been opened yet. Open once and pass the numeric fd to
211
+ // both stdout and stderr; the parent closes its copy after spawning.
212
+ logFd = fs.openSync(logFile, 'a');
213
+ const childLogFd = logFd;
214
+ let spawned;
215
+ try {
216
+ spawned = spawn(launch.command, launch.args, {
217
+ cwd,
218
+ detached: true,
219
+ stdio: ['ignore', childLogFd, childLogFd],
220
+ windowsHide: platform.isWindows,
221
+ env: { ...process.env, EVOLCORE_HOME: root },
222
+ });
223
+ }
224
+ catch (error) {
225
+ closeLogStream();
226
+ options.logger.error(`ECWeb 子进程创建失败: ${error?.message || error}`);
227
+ return;
228
+ }
229
+ closeLogStream();
230
+ child = spawned;
231
+ spawned.once('error', (error) => {
232
+ options.logger.error(`ECWeb 子进程创建失败: ${error.message}`);
233
+ });
234
+ spawned.once('exit', (code, signal) => {
235
+ options.logger.warn(`ECWeb 进程退出 (PID: ${spawned.pid ?? 'unknown'}, code=${code ?? 'null'}, signal=${signal ?? 'null'})`);
236
+ if (child === spawned)
237
+ child = null;
238
+ if (!stopping) {
239
+ active = null;
240
+ failureCount = failureThreshold;
241
+ }
242
+ closeLogStream();
243
+ });
244
+ spawned.unref();
245
+ if (!spawned.pid) {
246
+ closeLogStream();
247
+ options.logger.warn('ECWeb 启动失败:未取得子进程 PID');
248
+ return;
249
+ }
250
+ options.logger.info(`✓ ECWeb supervisor 已启动 (PID: ${spawned.pid}),日志: ${logFile}`);
251
+ // ECWeb writes its own instance record after binding. Do not fabricate a
252
+ // record here; the watchdog will adopt the real record on its next tick.
253
+ // Returning immediately keeps daemon startup independent of HTTP bind
254
+ // latency while ServiceProxy continues to wait for the instance file.
255
+ };
256
+ const ensureRunning = async () => {
257
+ if (stopping || startInFlight)
258
+ return;
259
+ const healthy = await findHealthyInstance();
260
+ if (healthy) {
261
+ const changed = !active || active.pid !== healthy.record.pid || active.port !== healthy.record.port;
262
+ setActiveFromEntry(healthy, active?.pid === healthy.record.pid ? active.owned : false);
263
+ failureCount = 0;
264
+ if (changed)
265
+ options.logger.info(`✓ ECWeb supervisor 接管现有实例 (PID: ${healthy.record.pid}, 端口: ${healthy.record.port})`);
266
+ return;
267
+ }
268
+ failureCount += 1;
269
+ if (failureCount < failureThreshold)
270
+ return;
271
+ // A manual `ec watch web` may be between killing the old process and
272
+ // registering the replacement. Re-scan immediately before spawning.
273
+ const replacement = await waitForHealthyInstance(1500);
274
+ if (replacement) {
275
+ setActiveFromEntry(replacement, false);
276
+ failureCount = 0;
277
+ return;
278
+ }
279
+ startInFlight = (async () => {
280
+ await killActive();
281
+ await spawnEcweb();
282
+ failureCount = 0;
283
+ })().finally(() => { startInFlight = null; });
284
+ await startInFlight;
285
+ };
286
+ return {
287
+ async start() {
288
+ if (stopping)
289
+ return;
290
+ // CLI/restart-monitor writes the instance record before the daemon is
291
+ // ready. Give that process enough time to bind before launching another.
292
+ const existing = await waitForHealthyInstance(1_500);
293
+ if (existing) {
294
+ setActiveFromEntry(existing, false);
295
+ options.logger.info(`✓ ECWeb supervisor 接管现有实例 (PID: ${existing.record.pid}, 端口: ${existing.record.port})`);
296
+ }
297
+ else {
298
+ failureCount = failureThreshold;
299
+ await ensureRunning();
300
+ }
301
+ timer = setInterval(() => {
302
+ void ensureRunning().catch((error) => {
303
+ options.logger.error(`ECWeb watchdog 检查失败: ${error?.message || error}`);
304
+ });
305
+ }, checkIntervalMs);
306
+ timer.unref?.();
307
+ },
308
+ async stop() {
309
+ if (stopping)
310
+ return;
311
+ stopping = true;
312
+ if (timer)
313
+ clearInterval(timer);
314
+ timer = null;
315
+ if (startInFlight) {
316
+ try {
317
+ await startInFlight;
318
+ }
319
+ catch { }
320
+ }
321
+ await killActive();
322
+ if (child) {
323
+ try {
324
+ child.removeAllListeners();
325
+ }
326
+ catch { }
327
+ child = null;
328
+ }
329
+ closeLogStream();
330
+ },
331
+ };
332
+ }
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getPackageRoot, resolvePaths } from '../paths.js';
4
4
  import { logger } from './logger.js';
5
+ import { isSandboxInitializationFailure } from '../core/permission/sandbox-runtime.js';
5
6
  export var ErrorType;
6
7
  (function (ErrorType) {
7
8
  ErrorType["SDK_TIMEOUT"] = "sdk_timeout";
@@ -11,6 +12,7 @@ export var ErrorType;
11
12
  ErrorType["STREAM_ERROR"] = "stream_error";
12
13
  ErrorType["CONTEXT_TOO_LONG"] = "context_too_long";
13
14
  ErrorType["MODEL_UNAVAILABLE"] = "model_unavailable";
15
+ ErrorType["SANDBOX_INITIALIZATION_FAILED"] = "sandbox_initialization_failed";
14
16
  ErrorType["UNKNOWN"] = "unknown";
15
17
  })(ErrorType || (ErrorType = {}));
16
18
  /**
@@ -269,6 +271,9 @@ export function classifyError(error) {
269
271
  || /api error: 404\b/.test(msg)) {
270
272
  return ErrorType.MODEL_UNAVAILABLE;
271
273
  }
274
+ if (isSandboxInitializationFailure(error)) {
275
+ return ErrorType.SANDBOX_INITIALIZATION_FAILED;
276
+ }
272
277
  if (msg.includes('401') || msg.includes('authentication_error') || msg.includes('unauthorized')) {
273
278
  return ErrorType.AUTH_ERROR;
274
279
  }
@@ -328,6 +333,8 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
328
333
  return `${prefix}工具执行失败,请检查操作或重试`;
329
334
  case 'permission_denied':
330
335
  return `${prefix}权限被拒绝,操作已取消`;
336
+ case 'sandbox_initialization_failed':
337
+ return `${warnPrefix}只读执行沙箱启动失败(sandbox_initialization_failed),当前任务未执行;请检查 user namespace/seccomp 配置后重试`;
331
338
  case 'aborted_streaming':
332
339
  case 'aborted_tools':
333
340
  return `${prefix}任务已中断`;
@@ -345,6 +352,9 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
345
352
  if (msg.includes('CONTEXT_COMPACT_FAILED') || isContextTooLongText(msg)) {
346
353
  return `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
347
354
  }
355
+ if (isSandboxInitializationFailure(error)) {
356
+ return `${warnPrefix}只读执行沙箱启动失败(sandbox_initialization_failed),当前任务未执行;请检查 user namespace/seccomp 配置后重试`;
357
+ }
348
358
  if (msg.includes('401') || msg.includes('authentication_error') || msg.toLowerCase().includes('unauthorized')) {
349
359
  return `${errPrefix}API Key 无效,请检查密钥配置。使用 /status 查看当前配置`;
350
360
  }
@@ -20,7 +20,7 @@ export async function npmInstallGlobal(pkg) {
20
20
  // Windows: run via cmd /c to avoid shell:true deprecation warning on Node 22.
21
21
  // Unix: npm directly, no shell needed.
22
22
  if (isWindows) {
23
- cmd = 'cmd';
23
+ cmd = process.env.ComSpec || path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe');
24
24
  args = ['/c', 'npm', 'install', '-g', pkg];
25
25
  }
26
26
  else {
@@ -28,14 +28,14 @@ export async function npmInstallGlobal(pkg) {
28
28
  args = ['install', '-g', pkg];
29
29
  }
30
30
  try {
31
- await execFileAsync(cmd, args, { timeout: 180000 });
31
+ await execFileAsync(cmd, args, { timeout: 180000, windowsHide: isWindows });
32
32
  }
33
33
  catch (e) {
34
34
  if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES')) {
35
35
  if (isWindows) {
36
36
  throw new Error('权限不足。请以管理员身份运行 PowerShell 或 CMD,然后重试');
37
37
  }
38
- await execFileAsync('sudo', ['npm', 'install', '-g', pkg], { timeout: 180000 });
38
+ await execFileAsync('sudo', ['npm', 'install', '-g', pkg], { timeout: 180000, windowsHide: false });
39
39
  }
40
40
  else {
41
41
  throw e;
@@ -103,7 +103,7 @@ export function resolveGlobalPkg(pkgName) {
103
103
  // 避免 "args 数组 + shell:true" 组合触发 Node DeprecationWarning,且无注入风险。
104
104
  const npmCmd = isWindows ? 'npm.cmd' : 'npm';
105
105
  const globalRoot = execSync(`${npmCmd} root -g`, {
106
- encoding: 'utf-8', timeout: 10000,
106
+ encoding: 'utf-8', timeout: 10000, windowsHide: true,
107
107
  }).trim();
108
108
  const pkgPath = path.join(globalRoot, ...pkgName.split('/'), 'package.json');
109
109
  if (fs.existsSync(pkgPath)) {
@@ -171,7 +171,7 @@ export async function tryUpgrade() {
171
171
  /**
172
172
  * 通用全局包升级流程:检查 → 比较 → 安装(失败重试一次)。
173
173
  * 仅在包已安装时检查升级,未安装则跳过(resolveFn 返回 null)。
174
- * fastaun / EC Web 等独立全局包共用此逻辑。
174
+ * 用于 EC Web 等真正独立于 EvolCore 的全局包。
175
175
  */
176
176
  export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
177
177
  if (isLinkedInstall()) {
@@ -201,10 +201,3 @@ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
201
201
  }
202
202
  return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
203
203
  }
204
- /**
205
- * AUN SDK 升级流程:检查 → 比较 → 安装
206
- * 仅在 SDK 已安装时检查升级,未安装则跳过。
207
- */
208
- export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
209
- return tryUpgradeGlobalPkg(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG);
210
- }
@@ -63,6 +63,17 @@ export function summarizeToolInput(toolName, input) {
63
63
  || input.url
64
64
  || '';
65
65
  }
66
+ /**
67
+ * Audit-safe tool summary. Keep the useful operation shape while masking
68
+ * credentials commonly embedded in shell commands or tool arguments.
69
+ */
70
+ export function summarizeToolInputForAudit(toolName, input) {
71
+ return summarizeToolInput(toolName, input)
72
+ .replace(/\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL)[A-Z0-9_]*)\s*=\s*(?:"[^"]*"|'[^']*'|\S+)/gi, '$1=[REDACTED]')
73
+ .replace(/\b(authorization\s*:\s*Bearer\s+)[^\s"',;]+/gi, '$1[REDACTED]')
74
+ .replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret|authorization|cookie)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, '$1$2[REDACTED]')
75
+ .replace(/\bBearer\s+[^\s,;]+/gi, 'Bearer [REDACTED]');
76
+ }
66
77
  const EDIT_PREVIEW_MAX_LINES = 14;
67
78
  const EDIT_PREVIEW_CONTEXT_LINES = 2;
68
79
  const EDIT_PREVIEW_INLINE_GAP_LINES = 4;
@@ -52,5 +52,5 @@ AUN keystore seed 由 EvolCore 使用固定应用值管理,不再提供配置
52
52
  - **E2EE 加密**:可选端到端加密,回复默认跟随对端消息加密状态(密文回密文,明文回明文);keystore seed 由 EvolCore 固定管理。
53
53
  - **断线重连**:SDK 内置退避策略自动重连。实时连接状态用 `ec watch aid` 查看(`ec aid` 是身份/证书管理,不显示连接状态)。
54
54
  - **群 ID 格式**:当前协议规范使用 `g-{slug}.issuer-domain` canonical 形式,输入也可接受本域简写 `g-{slug}` 或兼容形式 `g-{slug}@issuer-domain`。历史会话里可能仍出现 `group.{issuer}/{group_no}` 一类旧格式,运行时需兼容读取。
55
- - **群分发模式**:`dispatch_mode=broadcast|mention` 是群级配置,只决定哪些群消息进入 Agent LLM 上下文,不影响 AUN 协议层投递;被过滤消息仍应本地存档。
55
+ - **群 @ 处理策略**:`mention_mode=disabled|mention-only` 是群级配置,只决定哪些群消息进入 Agent LLM 上下文,不影响 AUN 协议层投递;被过滤消息仍应本地存档。
56
56
  - **身份体系**:对端以 AID 标识,关系层 peerKey 形如 `aun#alice.aid.pub`。
@@ -163,6 +163,7 @@ manifest 是 `{ "$schema_version": 1, "sections": [...] }`。每个 section:
163
163
 
164
164
  - 覆盖文件含 `"mode": "replace"` → **完全替换**,只用覆盖文件的 sections。
165
165
  - 否则 **patch 合并**:以 `id` 为键,覆盖文件里同 id 的字段浅合并进基础 section(`{...base, ...override}`),新 id 追加。
166
+ - 旧 patch 中的 `relation-group-profile` 已废弃并会被忽略;群与私聊 profile 统一通过 `peer-profile` 配置,避免同一文件重复加载。`mode: "replace"` 不做此兼容处理。
166
167
 
167
168
  合并后统一按 `order` 升序排序。改某段行为(如关掉某层、调顺序、换条件)优先用覆盖文件,不动基础 manifest。
168
169
 
@@ -295,7 +296,6 @@ coding 场景(无 channel/无身份)下,`chatType`、`channel`、`selfAid`
295
296
  | 40 | venue-fragment | fragment | chatType≠null | ✓ |
296
297
  | 41 | venue-chattype | `$KITS_DOCS/venues/{{chatType}}.md` | chatType≠null | ✗ |
297
298
  | 42 | venue-channel-chattype | `$KITS_DOCS/venues/{{channel}}-{{chatType}}.md` | chatType≠null | ✗ |
298
- | 43 | relation-group-profile | `$RELATIONS_DIR/{{peerKey}}/profile.md` | groupId≠null | ✗ |
299
299
  | 44 | venue-client | `$KITS_DOCS/venues/client-{{clientType}}.md` | clientType≠null | ✗ |
300
300
  | 50 | channel-layer | fragment | channel≠null | ✓ |
301
301
  | 55 | commands | fragment | channel≠null | ✓ |
@@ -322,7 +322,7 @@ $KITS_DOCS/venues/ 通用环境文档(随包发布,只读
322
322
  ```
323
323
 
324
324
  > **注意**:具体群/私聊实例的 profile 数据落**关系层** `$RELATIONS_DIR/<peerKey>/profile.md`,
325
- > `relation-group-profile` 段注入,不在 venues 目录。
325
+ > 统一由 `peer-profile` 段注入,不在 venues 目录;群不会再通过另一个 section 重复加载。
326
326
 
327
327
  ## 输出结构
328
328
 
@@ -17,9 +17,10 @@ ec agent <aid>
17
17
  ec agent new [aid]
18
18
 
19
19
  # 创建 agent(非交互式,自动化场景)
20
- ec agent new <aid> --non-interactive --project <绝对路径> \
21
- [--baseagent claude|codex|gemini] [--owner <aid>] \
22
- [--name "<显示名>"] [--description "<text>"] [--force]
20
+ ec agent new <aid> --non-interactive \
21
+ [--project <路径>] [--baseagent claude|codex|gemini|ecagent] \
22
+ [--owner <aid>] [--name "<显示名>"] [--description "<text>"] \
23
+ [--dry-run] [--force]
23
24
 
24
25
  # 启用 / 停用
25
26
  ec agent enable <aid>
@@ -44,9 +45,13 @@ ec agent delete <aid> [--purge]
44
45
 
45
46
  ## 非交互创建必填项
46
47
 
47
- `ec agent new <aid> --non-interactive` 时:
48
- - 必填:`--project <absolute path>`
49
- - 可选:`--baseagent`(默认 PATH 中第一个可用项)、`--owner`、`--name`、`--description`、`--force`(覆盖已有 config.json
48
+ `ec agent new <aid> --non-interactive` 时,只有 `<aid>` 必填:
49
+ - `--project` 缺省为当前目录,并自动转为绝对路径。
50
+ - `--baseagent` 优先使用 `agents/defaults.json`,否则检测本机可用基座。
51
+ - `--owner` 缺省使用 `daemon.json` 的第一个 owner;未配置时省略。
52
+ - `--name` / `--description` 缺省读取当前项目 `package.json`,再回退到目录名 / 空描述。
53
+ - `--dry-run` 只输出最终创建计划,不注册 AID、不写配置、不上传 `agent.md`。
54
+ - `--force` 覆盖已有 `config.json`;AID 密钥保留。
50
55
 
51
56
  ## 头像更新
52
57
 
@@ -2,6 +2,20 @@
2
2
 
3
3
  管理本地 AID 身份(证书、私钥、agent.md 名片),以及远程探测对端 AID。触发词:身份/证书/名片/探测对端/创建身份。
4
4
 
5
+ ## 运行边界
6
+
7
+ 本页列出独立终端中 `ec aid` 的完整 CLI 能力。在 EvolCore 托管 Agent 会话中,为防止会话枚举、创建、删除或切换身份,只允许当前 self AID 上的以下精确命令:
8
+
9
+ ```bash
10
+ ec aid show <当前 self AID> [--format json]
11
+ ec aid lookup <当前 self AID> [--format json]
12
+ ec aid agentmd get <当前 self AID> [--format json]
13
+ # 仅 request / bypass
14
+ ec aid agentmd put <当前 self AID> [--format json]
15
+ ```
16
+
17
+ 仅允许不带选项的基本形式,或在末尾附加一组完整的 `--format json`;不能使用 `--aun-path`、未知选项、管道、重定向或其他 AID。`show`、`lookup` 和 `agentmd get` 是 self-scoped 查询,可在所有 `permissionMode` 执行;`agentmd put` 会发布远端名片,属于写操作,仅允许 `request`、`bypass`,`readonly`、`auto` 和未知模式均拒绝。该身份范围边界早于模式审批,`bypass` 也不能跨越当前 self AID。`show` 会对自身身份做本地签名/验签自检,但不输出私钥;`lookup` 只探测已知的自身 AID;`get` 不改变身份或远端数据,但会下载并验签自身公开名片,同时刷新本地 AID 缓存。`list`、其他 AID 的 `show/lookup/agentmd get/put` 以及 `new/delete` 均拒绝。独立终端不受此托管会话限制。
18
+
5
19
  ## 子命令
6
20
 
7
21
  ```bash
@@ -132,11 +132,24 @@ Trigger V3 第二阶段使用 `execution.type` 区分执行位置:
132
132
  ```bash
133
133
  --target-channel <channelKey>
134
134
  --target-channel-id <channelId>
135
+ --target-chat-type <private|group>
135
136
  --target-session main
136
137
  --target-session thread --target-thread-id <threadId>
137
138
  ```
138
139
 
139
- AUN 渠道由 agent AID 隐式创建,不需要写入 agent 配置的 `channels[]`。CLI 和 Trigger V3.1 definition 均可将 `aun` 用作 `channelKey` 简写;保存前会按 `agentAid` 展开为 `aun#<agentAid>#main`。其他渠道仍需指定完整的实例 channel key。
140
+ AUN 渠道由 agent AID 隐式创建,不需要写入 agent 配置的 `channels[]`。CLI 和 Trigger V3.1 definition 均可将 `aun` 用作 `channelKey` 简写;保存前会按 `agentAid` 展开为 `aun#<agentAid>#main`。AUN 目标必须明确设置 `chatType`;`channelId` 在私聊时是对端 ID,在群聊时就是群 ID,不再额外保存 `groupId`。读取缺少 `chatType` 的旧 AUN Trigger definition 时,迁移逻辑会仅在该次迁移中按 `private` 补齐并回写;新建和编辑仍要求显式指定。其他渠道仍需指定完整的实例 channel key。
141
+
142
+ ```json
143
+ {
144
+ "strategy": "target",
145
+ "target": {
146
+ "channelKey": "aun",
147
+ "channelId": "peer.agentid.pub",
148
+ "chatType": "private",
149
+ "session": "main"
150
+ }
151
+ }
152
+ ```
140
153
 
141
154
  Trigger V3.1 feedback 是单策略:
142
155
 
@@ -307,6 +320,7 @@ ec trigger create --exec script --file ./trigger-dir --enable
307
320
  "target": {
308
321
  "channelKey": "<channelKey>",
309
322
  "channelId": "<channelId>",
323
+ "chatType": "private",
310
324
  "session": "main"
311
325
  }
312
326
  },
@@ -388,6 +402,7 @@ ec trigger create --exec script --file ./trigger-dir --enable
388
402
  "target": {
389
403
  "channelKey": "<channelKey>",
390
404
  "channelId": "<channelId>",
405
+ "chatType": "private",
391
406
  "session": "main"
392
407
  },
393
408
  "onReply": {
@@ -466,6 +481,7 @@ flag 模式不能配置过滤条件。需要根据事件 payload 过滤时,写
466
481
  "target": {
467
482
  "channelKey": "<channelKey>",
468
483
  "channelId": "<channelId>",
484
+ "chatType": "private",
469
485
  "session": "main"
470
486
  }
471
487
  },
@@ -69,7 +69,7 @@
69
69
  | **vars 粒度** | 会话级(一次构造,整批复用) | item 级(每条叠加 peerName/now/images) |
70
70
  | **模板空行** | 删除(stripBlankLines=true,紧凑) | 保留(stripBlankLines=false,消息多段) |
71
71
  | **content 注入** | N/A | 哨兵末步字面量注入(防二次解析) |
72
- | **缓存** | per-sessionId(跨消息复用文件内容) | per-renderMessageBody 调用(局部) |
72
+ | **缓存** | 随包 `kits/` 文件跨 session 缓存到 reload;personal/relations 等运行时文件按 mtime 刷新 | 随包模板缓存到 reload,消息数据不缓存 |
73
73
  | **debug 输出** | `eck-debug/context-*.md` `fragments-*.md` `manifest-*.md` `vars-*.json` | `eck-debug/msg-render-*.md` |
74
74
 
75
75
  ---
@@ -109,7 +109,6 @@
109
109
  | 40 | venue-fragment | chatType≠null | ✓ | 环境层 fragment |
110
110
  | 41 | venue-chattype | chatType≠null | ✗ | `venues/{{chatType}}.md` |
111
111
  | 42 | venue-channel-chattype | chatType≠null | ✗ | `venues/{{channel}}-{{chatType}}.md` |
112
- | 43 | venue-group-profile | groupId≠null | ✗ | 群 venue profile.md |
113
112
  | 44 | venue-client | clientType≠null | ✗ | `venues/client-{{clientType}}.md` |
114
113
  | 50 | channel-layer | channel≠null | ✓ | 渠道层 fragment |
115
114
  | 55 | commands | channel≠null | ✓ | 命令集能力卡 |
@@ -5,7 +5,7 @@
5
5
  ## 行为准则
6
6
  - `mentionMode: mention-only` — 仅响应明确 @ 自己的消息
7
7
  - `mentionMode: disabled` — 所有消息可见,自主判断是否参与。这不是叫你每条都回,而是你可以像真人一样:感兴趣的接话,不感兴趣的就跳过。回得短一点、快一点,让对话流动起来,不要一个人长篇大论堵住别人的空间
8
- - 查看 venue 档案(venue-group-profile)了解本群文化与策略
8
+ - 查看关系档案(peer-profile)了解本群文化与策略
9
9
  - 如果上下文中出现“群规则”,它来自当前群资源空间的本地同步缓存;群规则优先于通用群聊指引
10
10
  - 群发言会被多人看到,谨言慎行
11
11
  - 具体渠道(AUN/飞书/微信等)的群聊特有指引见对应渠道文档
@@ -127,18 +127,6 @@
127
127
  ] },
128
128
  "description": "渠道+场景文档(如 feishu-group)"
129
129
  },
130
- {
131
- "id": "relation-group-profile",
132
- "type": "file",
133
- "file": "$RELATIONS_DIR/{{peerKey}}/profile.md",
134
- "order": 43,
135
- "needsInjection": false,
136
- "when": { "and": [
137
- { "var": "groupId", "neq": null },
138
- { "var": "lifecycle", "neq": "bootstrapping" }
139
- ] },
140
- "description": "具体群关系文档"
141
- },
142
130
  {
143
131
  "id": "relation-group-rules",
144
132
  "type": "file",
@@ -49,8 +49,7 @@ function isExplicitGroupId(value) {
49
49
  return (id.startsWith('group.') && id.includes('/'))
50
50
  || /^\d+\.[a-z0-9.-]+$/i.test(id)
51
51
  || /^grp_[a-z0-9_-]+$/i.test(id)
52
- || /^g-[a-z0-9_-]+(?:[.@][a-z0-9.-]+)?$/i.test(id)
53
- || /(?:^|\.)groupid\.pub$/i.test(id);
52
+ || /^g-[a-z0-9_-]+(?:[.@][a-z0-9.-]+)?$/i.test(id);
54
53
  }
55
54
 
56
55
  function readJson(file) {
@@ -39,7 +39,7 @@ relations/
39
39
 
40
40
  | 条目 | 真实状态 |
41
41
  |------|----------|
42
- | `<peerKey>/profile.md` | manifest 声明了加载槽(`peer-profile`、`relation-group-profile`,缺失即跳过),但**没有任何代码写它**,磁盘上零文件。你可以手写一份,它会被加载 |
42
+ | `<peerKey>/profile.md` | manifest 通过统一的 `peer-profile` 槽加载(缺失即跳过),群聊和私聊不再重复声明;目前**没有任何代码写它**,可以手写一份供上下文加载 |
43
43
  | `_index/name_<name>.json` 名字反查索引 | 目录会被创建,但**无任何读写代码**,磁盘上全是空目录。"已知 name 反查 peerKey" 这条路走不通 |
44
44
  | `<peerKey>/history.jsonl` | 关系层无此实现(同名文件都属于 handoff / trigger / CC 自己的历史) |
45
45
  | `_trash/` | 只创建目录,没有 merge/split 重定向逻辑 |