evolcore 0.0.35 → 0.0.37

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 +42 -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 +177 -85
  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 +115 -36
  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
package/dist/cli/bench.js CHANGED
@@ -491,7 +491,7 @@ Options:
491
491
  const hex = crypto.randomBytes(4).toString('hex');
492
492
  const newAid = `bench-${hex}.${aidDomain}`;
493
493
  try {
494
- await aidCreate(newAid, { aunPath });
494
+ await aidCreate(newAid, { aunPath, type: 'service' });
495
495
  aids.push(newAid);
496
496
  if (!formatJson)
497
497
  console.log(ok(`创建 ${newAid}`));
@@ -1,4 +1,78 @@
1
1
  import { decodeEvolcoreInlineText } from '../core/permission/ec-command-parser.js';
2
+ export const TIME_ARG_FORMATS = 'epoch 毫秒/秒、ISO 8601、YYYY-MM-DD HH:mm:ss [+08:00]、YYYY/MM/DD HH:mm:ss、相对时间 30s/5m/2h/1d、now';
3
+ const RELATIVE_TIME_RE = /^(\d+)\s*(s|sec|secs|m|min|mins|h|hr|hrs|d|day|days)$/i;
4
+ const DATE_TIME_RE = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:(?:T|\s+)(\d{1,2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?)?\s*(Z|UTC|GMT|[+-]\d{2}(?::?\d{2})?)?$/i;
5
+ const MONTH_NAME_RE = /\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b/i;
6
+ const TIME_UNIT_MS = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 };
7
+ export function parseTimeArg(value, now = Date.now()) {
8
+ if (typeof value !== 'string')
9
+ return undefined;
10
+ const raw = value.trim();
11
+ if (!raw)
12
+ return undefined;
13
+ if (/^\d+$/.test(raw)) {
14
+ const parsed = Number(raw);
15
+ if (!Number.isSafeInteger(parsed))
16
+ return undefined;
17
+ return raw.length === 10 ? parsed * 1000 : parsed;
18
+ }
19
+ if (raw.toLowerCase() === 'now')
20
+ return now;
21
+ const relative = RELATIVE_TIME_RE.exec(raw);
22
+ if (relative) {
23
+ const result = now - Number(relative[1]) * TIME_UNIT_MS[relative[2][0].toLowerCase()];
24
+ return Number.isSafeInteger(result) && result >= 0 ? result : undefined;
25
+ }
26
+ const match = DATE_TIME_RE.exec(raw);
27
+ if (match)
28
+ return parseTimeDate(match);
29
+ if (MONTH_NAME_RE.test(raw)) {
30
+ const parsed = Date.parse(raw);
31
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
32
+ }
33
+ return undefined;
34
+ }
35
+ function parseTimeDate(match) {
36
+ const [, y, mo, d, h, mi, s, frac, zone] = match;
37
+ const year = Number(y), month = Number(mo), day = Number(d);
38
+ const hour = h === undefined ? 0 : Number(h);
39
+ const minute = mi === undefined ? 0 : Number(mi);
40
+ const second = s === undefined ? 0 : Number(s);
41
+ const ms = frac === undefined ? 0 : Number(frac.slice(0, 3).padEnd(3, '0'));
42
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59)
43
+ return undefined;
44
+ let result;
45
+ if (zone === undefined) {
46
+ const local = new Date(year, month - 1, day, hour, minute, second, ms);
47
+ if (local.getFullYear() !== year || local.getMonth() !== month - 1 || local.getDate() !== day)
48
+ return undefined;
49
+ result = local.getTime();
50
+ }
51
+ else {
52
+ const utc = Date.UTC(year, month - 1, day, hour, minute, second, ms);
53
+ const check = new Date(utc);
54
+ if (check.getUTCFullYear() !== year || check.getUTCMonth() !== month - 1 || check.getUTCDate() !== day)
55
+ return undefined;
56
+ const offset = parseTimeZoneOffset(zone);
57
+ if (offset === undefined)
58
+ return undefined;
59
+ result = utc - offset * 60_000;
60
+ }
61
+ return Number.isFinite(result) && result >= 0 ? result : undefined;
62
+ }
63
+ function parseTimeZoneOffset(zone) {
64
+ if (/^(z|utc|gmt)$/i.test(zone))
65
+ return 0;
66
+ const match = /^([+-])(\d{2}):?(\d{2})?$/.exec(zone);
67
+ if (!match)
68
+ return undefined;
69
+ const hours = Number(match[2]);
70
+ const minutes = match[3] === undefined ? 0 : Number(match[3]);
71
+ if (hours > 14 || minutes > 59)
72
+ return undefined;
73
+ const total = hours * 60 + minutes;
74
+ return match[1] === '-' ? -total : total;
75
+ }
2
76
  export function normalizeCliArgv(argv) {
3
77
  if (argv[0] === 'ec' && argv[1])
4
78
  return argv.slice(1);
@@ -4,7 +4,7 @@ import os from 'os';
4
4
  import { spawn, execFile } from 'child_process';
5
5
  import { promisify } from 'util';
6
6
  import readline from 'readline';
7
- import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath, asModeOverridePath } from '../paths.js';
7
+ import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath, asModeOverridePath, daemonControlDir } from '../paths.js';
8
8
  import { loadDefaults, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
9
9
  import { resolveEffective } from '../config/config-manager.js';
10
10
  import { resolveAnthropicConfig } from '../agents/baseagent.js';
@@ -14,9 +14,9 @@ import { isHelpFlag } from './help.js';
14
14
  import { cmdInit, needsControlAidInit, initTail } from './init.js';
15
15
  import * as platform from '../utils/cross-platform.js';
16
16
  import { EventBus } from '../core/event-bus.js';
17
- import { tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
17
+ import { resolveGlobalPkg } from '../utils/npm-ops.js';
18
18
  import { ensureFreshPackageVersionInfo } from '../utils/package-version-info.js';
19
- import { ecwebVersionRequirementError, resolveEcwebLaunchCommand, fetchRemotePairingCode } from '../utils/ecweb-utils.js';
19
+ import { ecwebVersionRequirementError, resolveEcwebLaunchCommand, resolveEcwebExternalUrl, fetchRemotePairingCode, } from '../utils/ecweb-utils.js';
20
20
  import { isHostChinese } from '../utils/locale.js';
21
21
  import { scanInstances, cleanupInstances, findOrphanProcesses } from '../utils/instance-registry.js';
22
22
  import { filterLogFiles, deriveLogTypes, computePreChecked, validateLogTypes, shortLogName as shortLogNameLocal, isWatchLogFile } from './watch-logs.js';
@@ -25,7 +25,8 @@ import { isNonAgentSessionChannel } from '../core/system-channels.js';
25
25
  import { printCodeStats } from './code-stats.js';
26
26
  import { getProcessStartTime, startTimeMatches } from '../utils/process-introspect.js';
27
27
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
28
- import { MAIN_CLI_BIN, RUNTIME_HOME_ENV, TARGET_PACKAGE_ROOT_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME, } from '../product.js';
28
+ import { MAIN_CLI_BIN, RESTART_REQUEST_ID_ENV, RUNTIME_HOME_ENV, TARGET_PACKAGE_ROOT_ENV, WEB_CLI_BIN, WEB_PACKAGE_NAME, } from '../product.js';
29
+ import { readRestartRequest, updateRestartRequest } from '../utils/restart-safety.js';
29
30
  import { rotateStdoutLog } from '../utils/log-writer.js';
30
31
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
31
32
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
@@ -33,6 +34,8 @@ import { computeBuildId } from '../utils/evolcore-version.js';
33
34
  import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '../agents/codex/app-server-registry.js';
34
35
  import { DEFAULT_ECWEB_PORT, ecwebService, setEcwebEnabled } from '../config/daemon-services.js';
35
36
  import { checkCodexProjectPath, inspectCodexManagedHook, ensureCodexManagedHook, runCodexHookLaunchDiagnostic, } from '../agents/codex/diagnostics.js';
37
+ import { isTopicThreadId } from '../core/thread/topic-id.js';
38
+ import { formatClaudeSandboxDependencies, formatCodexSandboxAvailability, inspectClaudeSandboxDependencies, inspectCodexSandboxAvailability, probeClaudeSandboxDependencies, probeCodexSandboxAvailability, } from '../core/permission/sandbox-runtime.js';
36
39
  const execFileAsync = promisify(execFile);
37
40
  export async function confirmCodexHookRebind(state, packageRoot) {
38
41
  console.log('⚠️ Codex 受管 Hook 绑定的另一套 EvolCore/Node 路径仍存在,需确认是否切换:');
@@ -132,8 +135,8 @@ function printNoSelfAgentHints(options) {
132
135
  const { daemonConfig, ecwebStarted, skipped } = options;
133
136
  console.log('\nℹ 未配置任何 self-agent,Control Plane 已启动。');
134
137
  console.log(' 命令行:ec agent new <aid>.example.com');
135
- if (daemonConfig.aid && (daemonConfig.owners?.length ?? 0) > 0) {
136
- console.log(' Evol App:可通过进程级菜单创建 agent');
138
+ if (daemonConfig.aid) {
139
+ console.log(' 使用 ec aid bind <ownerAid> 绑定协议 Owner 后,可通过进程级菜单创建 agent');
137
140
  }
138
141
  if (ecwebStarted) {
139
142
  console.log(' ECWeb:可通过控制台创建 agent');
@@ -296,7 +299,9 @@ async function requireFreshSourceBuild() {
296
299
  return await runSourceBuild();
297
300
  }
298
301
  async function launchPackageLifecycle(source, log, options = {}) {
299
- if (!options.prepared)
302
+ const correlated = options.requestId ?? process.env[RESTART_REQUEST_ID_ENV];
303
+ const upgradeJobId = correlated ? readRestartRequest(path.join(daemonControlDir(), 'restart-pending.json'), correlated)?.upgradeJobId : undefined;
304
+ if (!options.prepared && !upgradeJobId)
300
305
  await ensureFreshPackageVersionInfo(undefined, { source, log });
301
306
  const root = resolvePaths().root;
302
307
  const launch = platform.resolveMonitorLaunch(getPackageRoot(), path.join(root, 'data', 'monitor-stage'));
@@ -307,8 +312,15 @@ async function launchPackageLifecycle(source, log, options = {}) {
307
312
  platform.cleanupMonitorStage(launch.stageDir);
308
313
  return { ok: false, reason: 'RESTART_ENTRY_MISSING' };
309
314
  }
315
+ const requestId = options.requestId ?? process.env[RESTART_REQUEST_ID_ENV];
310
316
  const args = [...launch.args, 'restart-monitor', ...(options.clear ? ['--clear'] : [])];
311
- const env = { ...options.env, [RUNTIME_HOME_ENV]: root, ...launch.env, [AGENT_DELEGATION_TOKEN_ENV]: '' };
317
+ const env = {
318
+ ...options.env,
319
+ [RUNTIME_HOME_ENV]: root,
320
+ ...launch.env,
321
+ [AGENT_DELEGATION_TOKEN_ENV]: '',
322
+ ...(requestId ? { [RESTART_REQUEST_ID_ENV]: requestId } : {}),
323
+ };
312
324
  if (options.foreground) {
313
325
  return new Promise(resolve => {
314
326
  const child = spawn(process.execPath, [...args, '--foreground'], {
@@ -325,9 +337,23 @@ async function launchPackageLifecycle(source, log, options = {}) {
325
337
  });
326
338
  });
327
339
  }
328
- const result = await platform.launchMonitorAndConfirm(args, env, pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid === pid), pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid !== pid));
329
- if (!result.ok && launch.stageDir)
340
+ const result = await platform.launchMonitorAndConfirm(args, env, pid => requestId
341
+ ? readRestartRequest(path.join(root, 'data', 'daemon', 'control', 'restart-pending.json'), requestId)?.status === 'accepted'
342
+ || readRestartRequest(path.join(root, 'data', 'daemon', 'control', 'restart-pending.json'), requestId)?.status === 'ready'
343
+ : scanInstances().restartMonitors.some(item => item.alive && item.record.pid === pid), pid => scanInstances().restartMonitors.some(item => item.alive
344
+ && item.record.pid !== pid
345
+ && (!requestId || (item.record.requestId === requestId && item.record.phase === 'accepted'))), {
346
+ requestId,
347
+ logFilePath: requestId ? path.join(root, 'logs', `restart-launch-${requestId}.log`) : undefined,
348
+ onSpawn: pid => { if (requestId)
349
+ updateRestartRequest(path.join(root, 'data', 'daemon', 'control', 'restart-pending.json'), requestId, { launcherPid: pid }); },
350
+ isPendingInRegistry: () => !!requestId && scanInstances().restartMonitors.some(item => item.alive && item.record.requestId === requestId),
351
+ });
352
+ if (!result.ok && !result.pending && launch.stageDir)
330
353
  platform.cleanupMonitorStage(launch.stageDir);
354
+ if (result.pending) {
355
+ return { ok: false, reason: result.reason, pending: true, pid: result.pid };
356
+ }
331
357
  if (!result.ok) {
332
358
  for (const line of (result.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
333
359
  console.error(` | ${line}`);
@@ -351,8 +377,8 @@ function requireCompletedDataMigration(root) {
351
377
  * A failed upgrade is reported, but the currently installed version remains
352
378
  * usable and the caller continues with its normal lifecycle.
353
379
  */
354
- export async function runUpgradeChecks(upgradeFn, ecwebUpgradeFn, timeoutMs = 60_000) {
355
- if (!upgradeFn || !ecwebUpgradeFn) {
380
+ export async function runUpgradeChecks(upgradeFn, timeoutMs = 60_000) {
381
+ if (!upgradeFn) {
356
382
  await ensureFreshPackageVersionInfo(undefined, { source: 'start', log: console.log });
357
383
  return;
358
384
  }
@@ -379,10 +405,7 @@ export async function runUpgradeChecks(upgradeFn, ecwebUpgradeFn, timeoutMs = 60
379
405
  clearTimeout(timer);
380
406
  }
381
407
  };
382
- const [upgrade, ecwebUpgrade] = await Promise.all([
383
- runWithTimeout('EvolCore', upgradeFn),
384
- runWithTimeout(WEB_PACKAGE_NAME, ecwebUpgradeFn),
385
- ]);
408
+ const upgrade = await runWithTimeout('EvolCore', upgradeFn);
386
409
  switch (upgrade.status) {
387
410
  case 'upgraded':
388
411
  console.log(`✅ Upgraded: ${upgrade.from} → ${upgrade.to}`);
@@ -399,16 +422,6 @@ export async function runUpgradeChecks(upgradeFn, ecwebUpgradeFn, timeoutMs = 60
399
422
  console.log(`⚠ Upgrade failed${upgrade.from && upgrade.to ? ` (${upgrade.from} → ${upgrade.to})` : ''}, continuing with current version${upgrade.error ? `: ${upgrade.error}` : ''}`);
400
423
  break;
401
424
  }
402
- switch (ecwebUpgrade.status) {
403
- case 'upgraded':
404
- console.log(`✅ ${WEB_PACKAGE_NAME} upgraded: ${ecwebUpgrade.from} → ${ecwebUpgrade.to}`);
405
- break;
406
- case 'no-update':
407
- break;
408
- case 'failed':
409
- console.log(`⚠ ${WEB_PACKAGE_NAME} upgrade failed${ecwebUpgrade.from && ecwebUpgrade.to ? ` (${ecwebUpgrade.from} → ${ecwebUpgrade.to})` : ''}${ecwebUpgrade.error ? `: ${ecwebUpgrade.error}` : ''}`);
410
- break;
411
- }
412
425
  }
413
426
  // ==================== Commands ====================
414
427
  /**
@@ -608,8 +621,9 @@ export async function cmdStart(opts = {}) {
608
621
  return;
609
622
  // A stopped container may miss the daily upgrade trigger. Check once on
610
623
  // every real start so the service does not stay on an old installation.
611
- const versionInfo = await ensureFreshPackageVersionInfo(undefined, { source: 'start', log: console.log });
612
- if (opts.upgradeChecks)
624
+ const autoUpgrade = loadDaemonConfig().upgrade?.autoOnStart !== false;
625
+ const versionInfo = autoUpgrade ? await ensureFreshPackageVersionInfo(undefined, { source: 'start', log: console.log }) : [];
626
+ if (autoUpgrade && opts.upgradeChecks)
613
627
  await opts.upgradeChecks();
614
628
  // 旧配置自动迁移(daemon.json → 新结构)
615
629
  const { autoMigrateIfNeeded } = await import('../config-store.js');
@@ -644,7 +658,7 @@ export async function cmdStart(opts = {}) {
644
658
  // process is launched.
645
659
  if (!await ensureCodexHookBeforeLifecycle())
646
660
  return;
647
- // 控制 AID 门禁:缺 aid 且交互式 → 只补全控制 AID + owners(不重走 baseagent 向导)。
661
+ // 控制 AID 门禁:缺 aid 且交互式 → 只补全控制 AID + owner(不重走 baseagent 向导)。
648
662
  // 非 TTY(restart-monitor/systemd/管道)不补全(无法交互),只提示后继续启动,daemon 侧 warn 兜底。
649
663
  const daemonCfgStart = loadDaemonConfig();
650
664
  if (needsControlAidInit(daemonCfgStart.aid, !!process.stdin.isTTY)) {
@@ -747,8 +761,11 @@ export async function cmdStart(opts = {}) {
747
761
  prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts),
748
762
  });
749
763
  if (!launched.ok) {
750
- clearAsModeOverride(overrideFile);
751
- console.error(`❌ 启动事务未能建立:${launched.reason}`);
764
+ if (!launched.pending)
765
+ clearAsModeOverride(overrideFile);
766
+ console.error(launched.pending
767
+ ? `⚠ 启动状态暂不可确认(PID ${launched.pid ?? 'unknown'}):restart-monitor 仍在运行,可能继续执行。`
768
+ : `❌ 启动事务未能建立:${launched.reason}`);
752
769
  process.exitCode = 1;
753
770
  }
754
771
  else if (!opts.foreground) {
@@ -1097,9 +1114,20 @@ export async function cmdRestart(opts = {}) {
1097
1114
  // the monitor retains shutdown, orphan checks, build and rollback ownership.
1098
1115
  // restart-monitor performs the read-only preflight before stopping the
1099
1116
  // current daemon, then applies the migration during the stopped window.
1100
- const launched = await launchPackageLifecycle('restart', console.log, { clear: opts.clear });
1117
+ const requestId = process.env[RESTART_REQUEST_ID_ENV];
1118
+ if (requestId) {
1119
+ const pendingFile = path.join(daemonControlDir(), 'restart-pending.json');
1120
+ if (!readRestartRequest(pendingFile, requestId)) {
1121
+ console.error('❌ 重启请求已被替换或过期,服务未停止;请重新发起重启。');
1122
+ process.exitCode = 1;
1123
+ return;
1124
+ }
1125
+ }
1126
+ const launched = await launchPackageLifecycle('restart', console.log, { clear: opts.clear, requestId });
1101
1127
  if (!launched.ok) {
1102
- console.error(`❌ 重启中止:restart-monitor 未能启动(${launched.reason});当前服务未主动停止`);
1128
+ console.error(launched.pending
1129
+ ? `⚠ 重启状态暂不可确认(PID ${launched.pid ?? 'unknown'}):restart-monitor 仍在运行,可能继续执行;请勿立即重复发起。`
1130
+ : `❌ 重启中止:restart-monitor 未能启动(${launched.reason});当前服务未主动停止`);
1103
1131
  process.exitCode = 1;
1104
1132
  return;
1105
1133
  }
@@ -1385,7 +1413,7 @@ export async function cmdStatus() {
1385
1413
  console.log('📋 Recent Active Sessions:');
1386
1414
  for (const s of recentSessions) {
1387
1415
  const projectName = path.basename(s.projectPath);
1388
- const sessionType = s.threadId ? '话题会话' : '主会话';
1416
+ const sessionType = isTopicThreadId(s.threadId) ? '话题会话' : '主会话';
1389
1417
  const chatType = s.chatType === 'group' ? '群聊' : '单聊';
1390
1418
  const sessionName = displaySessionTitle(s.name);
1391
1419
  const timeAgo = formatTimeAgo(Date.now() - s.updatedAt);
@@ -2876,6 +2904,7 @@ async function waitForExistingEcweb(p, timeoutMs = 3_000) {
2876
2904
  * 某些 CLI 运行在隔离 PID namespace 中,看不到宿主 ECWeb PID 是正常情况。
2877
2905
  */
2878
2906
  async function printEcwebStatus(p) {
2907
+ let externalUrl = null;
2879
2908
  try {
2880
2909
  const cfg = loadDaemonConfig();
2881
2910
  if (ecwebService(cfg.services)?.enabled === false) {
@@ -2883,6 +2912,7 @@ async function printEcwebStatus(p) {
2883
2912
  console.log('🔭 ECWeb: 已禁用 (daemon.json → services[name=ecweb].enabled: false)');
2884
2913
  return;
2885
2914
  }
2915
+ externalUrl = resolveEcwebExternalUrl(cfg);
2886
2916
  }
2887
2917
  catch {
2888
2918
  // Agent tool sandboxes may mask daemon.json. Instance and HTTP probes below
@@ -2921,6 +2951,10 @@ async function printEcwebStatus(p) {
2921
2951
  const pidVisible = aliveEntries.some(entry => entry.filePath === readyEntry.filePath);
2922
2952
  const pidText = pidVisible ? `PID: ${readyEntry.record.pid}` : `PID: ${readyEntry.record.pid}(当前 namespace 不可见)`;
2923
2953
  console.log(`🔭 ECWeb: 运行中 (${pidText}) http://localhost:${readyEntry.record.port}`);
2954
+ if (externalUrl) {
2955
+ console.log(` 外网访问: ${externalUrl}`);
2956
+ console.log(` 命令行输入 ${MAIN_CLI_BIN} watch web/${WEB_CLI_BIN} 获取配对码`);
2957
+ }
2924
2958
  return;
2925
2959
  }
2926
2960
  const occupiedPids = new Set();
@@ -3016,7 +3050,7 @@ async function waitForPortRelease(port, timeoutMs) {
3016
3050
  return platform.findProcessByPort(port).length === 0;
3017
3051
  }
3018
3052
  /**
3019
- * Start the separately installed `ec-web` process for a configured runtime.
3053
+ * Start the `ec-web` dependency bundled with EvolCore for a configured runtime.
3020
3054
  * Kept public for restart-monitor, which launches the daemon directly and
3021
3055
  * therefore cannot rely on cmdStart's post-ready ECWeb startup path.
3022
3056
  */
@@ -3025,6 +3059,7 @@ export async function startEcwebIfEnabled(p, options = {}) {
3025
3059
  const service = ecwebService(cfg.services);
3026
3060
  if (!service?.enabled)
3027
3061
  return false;
3062
+ const externalUrl = resolveEcwebExternalUrl(cfg);
3028
3063
  // The daemon is the lifecycle owner. CLI callers normally only need to
3029
3064
  // ensure ECWeb is available, so adopt a healthy process instead of killing
3030
3065
  // it. `ec watch web` passes forceRestart explicitly for an intentional
@@ -3033,12 +3068,14 @@ export async function startEcwebIfEnabled(p, options = {}) {
3033
3068
  const existing = await waitForExistingEcweb(p);
3034
3069
  if (existing) {
3035
3070
  console.log(`✓ ECWeb 已在运行 (PID: ${existing.pid}) http://localhost:${existing.port}`);
3071
+ if (externalUrl)
3072
+ console.log(` 外网访问: ${externalUrl}`);
3036
3073
  return true;
3037
3074
  }
3038
3075
  }
3039
3076
  const installedPkg = resolveGlobalPkg(WEB_PACKAGE_NAME);
3040
3077
  if (!installedPkg) {
3041
- console.log(`⚠ EC Web 未安装,跳过启动(运行 npm i -g ${WEB_PACKAGE_NAME}@latest 后可用)`);
3078
+ console.log('⚠ 当前 EvolCore 安装未包含 EC Web,跳过启动;请升级 EvolCore 后重试。');
3042
3079
  return false;
3043
3080
  }
3044
3081
  const versionError = ecwebVersionRequirementError(installedPkg.version);
@@ -3064,7 +3101,7 @@ export async function startEcwebIfEnabled(p, options = {}) {
3064
3101
  const args = ['--home', p.root, '--port', String(port)];
3065
3102
  const launch = resolveEcwebLaunchCommand(args, { installedPkg });
3066
3103
  if (!launch) {
3067
- console.log(`⚠ EC Web 启动入口不存在,请重新安装: npm i -g ${WEB_PACKAGE_NAME}@latest`);
3104
+ console.log('⚠ 当前 EvolCore 安装中的 EC Web 启动入口不存在,请重新安装 EvolCore。');
3068
3105
  return false;
3069
3106
  }
3070
3107
  // 防御性检查:确保 command 和 args 有效
@@ -3113,6 +3150,8 @@ export async function startEcwebIfEnabled(p, options = {}) {
3113
3150
  }
3114
3151
  const actionLabel = wasRunning ? '已重启' : '启动成功';
3115
3152
  console.log(`✓ ECWeb ${actionLabel} (PID: ${pid}) http://localhost:${port}`);
3153
+ if (externalUrl)
3154
+ console.log(` 外网访问: ${externalUrl}`);
3116
3155
  if (pair) {
3117
3156
  const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
3118
3157
  console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,仅可配对一次,已配对过的浏览器无需重新配对)`);
@@ -3159,43 +3198,10 @@ async function printEcwebAccess(port) {
3159
3198
  }
3160
3199
  async function cmdWatchWeb() {
3161
3200
  const p = resolvePaths();
3162
- // 1. 检查安装
3163
- if (!platform.commandExists(WEB_CLI_BIN)) {
3164
- process.stdout.write(`📦 ${WEB_PACKAGE_NAME} 未安装。`);
3165
- if (process.stdin.isTTY) {
3166
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3167
- const ans = await new Promise(res => rl.question(' 立即安装?[Y/n] ', res));
3168
- rl.close();
3169
- if (ans.trim().toLowerCase() === 'n') {
3170
- process.exit(0);
3171
- }
3172
- }
3173
- else {
3174
- process.stdout.write(' 当前命令会自动安装。\n');
3175
- }
3176
- process.stdout.write('\n');
3177
- const { npmInstallGlobal } = await import('../utils/npm-ops.js');
3178
- try {
3179
- await npmInstallGlobal(WEB_PACKAGE_LATEST);
3180
- process.stdout.write(`✅ ${WEB_PACKAGE_NAME} 安装完成\n`);
3181
- }
3182
- catch (e) {
3183
- process.stderr.write(`❌ 安装失败: ${e?.stderr || e?.message || e}\n`);
3184
- process.exit(1);
3185
- }
3186
- }
3187
- else {
3188
- // 已安装:检查并自动升级独立的 EC Web 全局包。
3189
- const upgrade = await tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME);
3190
- switch (upgrade.status) {
3191
- case 'upgraded':
3192
- process.stdout.write(`✅ ${WEB_PACKAGE_NAME} 已升级: ${upgrade.from} → ${upgrade.to}\n`);
3193
- break;
3194
- case 'failed':
3195
- process.stdout.write(`⚠ ${WEB_PACKAGE_NAME} 升级失败 (${upgrade.from} → ${upgrade.to}),继续使用当前版本\n`);
3196
- break;
3197
- // no-update / skipped: 静默
3198
- }
3201
+ if (!resolveGlobalPkg(WEB_PACKAGE_NAME)) {
3202
+ process.stderr.write(`⚠ ${WEB_PACKAGE_NAME} 未随当前 EvolCore 包安装;请升级 EvolCore 后重试。\n`);
3203
+ process.exitCode = 1;
3204
+ return;
3199
3205
  }
3200
3206
  // 2. 启动(后台)并同步配置。默认行为是替换旧实例,确保新配置/新版静态资源生效。
3201
3207
  const cfg = loadDaemonConfig();
@@ -3262,6 +3268,19 @@ export async function cmdDiagnose(args = []) {
3262
3268
  try {
3263
3269
  // 2. 加载 self-agents
3264
3270
  const { agents, skipped } = loadAllAgents();
3271
+ const checkClaudeSandbox = args.includes('--claude-sandbox')
3272
+ || agents.some(agent => resolveEffective({ self: agent.aid }).active_baseagent === 'claude');
3273
+ if (checkClaudeSandbox) {
3274
+ const dependencies = inspectClaudeSandboxDependencies();
3275
+ const missing = dependencies.status === 'missing';
3276
+ (missing ? console.error : console.log)(`[diagnose] ${missing ? '❌' : '✓'} ${formatClaudeSandboxDependencies(dependencies)}`);
3277
+ hasError ||= missing;
3278
+ if (dependencies.status === 'ready') {
3279
+ const probe = probeClaudeSandboxDependencies(dependencies);
3280
+ (probe.ok ? console.log : console.error)(`[diagnose] ${probe.ok ? '✓' : '❌'} ${probe.detail}`);
3281
+ hasError ||= !probe.ok;
3282
+ }
3283
+ }
3265
3284
  if (agents.length === 0) {
3266
3285
  console.error(`[diagnose] ❌ 未配置 self-agent。请运行 \`ec aid new <name>\``);
3267
3286
  hasError = true;
@@ -3286,6 +3305,18 @@ export async function cmdDiagnose(args = []) {
3286
3305
  return false;
3287
3306
  }
3288
3307
  });
3308
+ const checkCodexSandbox = args.includes('--codex-sandbox') || codexAgents.length > 0;
3309
+ if (checkCodexSandbox) {
3310
+ const availability = inspectCodexSandboxAvailability();
3311
+ const missing = availability.status === 'missing';
3312
+ (missing ? console.error : console.log)(`[diagnose] ${missing ? '❌' : '✓'} ${formatCodexSandboxAvailability(availability)}`);
3313
+ hasError ||= missing;
3314
+ if (availability.status === 'ready') {
3315
+ const probe = probeCodexSandboxAvailability(availability);
3316
+ (probe.ok ? console.log : console.error)(`[diagnose] ${probe.ok ? '✓' : '❌'} ${probe.detail}`);
3317
+ hasError ||= !probe.ok;
3318
+ }
3319
+ }
3289
3320
  for (const agent of codexAgents) {
3290
3321
  try {
3291
3322
  const effective = resolveEffective({ self: agent.aid });
package/dist/cli/diag.js CHANGED
@@ -5,7 +5,7 @@ import { parseDiagArgs } from '../core/command/diag-service.js';
5
5
  import { wantsHelp } from './help.js';
6
6
  export async function cmdDiag(args) {
7
7
  if (wantsHelp(args)) {
8
- console.log('用法:ec diag <问题描述> [--owner <daemonOwner-aid>] [--format json]\n ec diag stop [--format json]\n未指定 --owner 时联系本机 daemon.json.owners[0];有效诊断期间再次执行会追加到原 case。');
8
+ console.log('用法:ec diag <问题描述> [--owner <daemonOwner-aid>] [--format json]\n ec diag stop [--format json]\n未指定 --owner 时联系本机 Control AID 的协议 Owner;有效诊断期间再次执行会追加到原 case。');
9
9
  return;
10
10
  }
11
11
  const argv = ['diag', ...args];
@@ -6,7 +6,7 @@ import { agentmdGet, getAidStore, isValidAid, loadClient, SLOT } from '../aun/ai
6
6
  import { ipcQuery } from '../ipc.js';
7
7
  import { resolvePaths } from '../paths.js';
8
8
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
9
- import { downloadGroupFsBytes } from '../aun/group/index.js';
9
+ import { DEFAULT_FS_CAT_BYTES, MAX_FS_CAT_BYTES, FsReadBudgetError, downloadGroupFsBytes, readPersonalFsBytesWithinBudget, } from '../aun/group/fs-download.js';
10
10
  class FsCliError extends Error {
11
11
  code;
12
12
  suggestion;
@@ -25,7 +25,7 @@ Commands:
25
25
  ls <AID>:<path> 列目录
26
26
  stat [-L] <AID>:<path> 查看节点元数据(默认不跟随软链;-L 跟随)
27
27
  lstat <AID>:<path> 查看节点本身(不跟随末级软链)
28
- cat <AID>:<path> 输出文本文件内容;二进制/大文件返回头部摘要
28
+ cat <AID>:<path> 预算内输出文本或二进制头部摘要;超出预算报错
29
29
  cp <local> <AID>:<path> 上传本地文件
30
30
  cp <AID>:<path> <local> 下载远程文件
31
31
  cp <AID>:<path> <AID>:<path> 远程复制(同后端)
@@ -72,7 +72,7 @@ Options:
72
72
  --size <expr> find 大小过滤,如 +1M
73
73
  --mtime <expr> find 修改时间过滤
74
74
  --head-bytes <n> cat 二进制头部字节数,默认 256
75
- --max-bytes <n> cat 最大直接输出字节数,默认 1048576
75
+ --max-bytes <n> cat 读取预算,默认及最高 131072;超限终止读取
76
76
  --aun-path <path> 指定 AUN keystore 根
77
77
  --app <name> 指定 AUN app slot
78
78
 
@@ -284,9 +284,9 @@ async function runManagedFs(args, formatJson) {
284
284
  }
285
285
  function parseCommonOptions(args) {
286
286
  const maxCatBytesRaw = getArgValue(args, '--max-bytes');
287
- const maxCatBytes = maxCatBytesRaw === undefined ? 1024 * 1024 : Number(maxCatBytesRaw);
288
- if (!Number.isFinite(maxCatBytes) || maxCatBytes <= 0) {
289
- throw new FsCliError('INVALID_ARGUMENT', `--max-bytes 必须是正数: ${maxCatBytesRaw}`);
287
+ const maxCatBytes = maxCatBytesRaw === undefined ? DEFAULT_FS_CAT_BYTES : Number(maxCatBytesRaw);
288
+ if (!Number.isSafeInteger(maxCatBytes) || maxCatBytes <= 0 || maxCatBytes > MAX_FS_CAT_BYTES) {
289
+ throw new FsCliError('INVALID_ARGUMENT', `--max-bytes 必须是 1 到 ${MAX_FS_CAT_BYTES} 的整数`);
290
290
  }
291
291
  const headBytesRaw = getArgValue(args, '--head-bytes');
292
292
  const headBytes = headBytesRaw === undefined ? 256 : Number(headBytesRaw);
@@ -404,31 +404,27 @@ async function fsCat(args, opts) {
404
404
  const route = await resolveRoute(target.aid, store);
405
405
  let statInfo;
406
406
  let bytes;
407
- let binaryHeadOnly = false;
408
407
  if (route.backend === 'group') {
409
408
  statInfo = await client.group.fs.stat(remoteRef(target));
410
409
  const size = numberFrom(statInfo, ['size', 'size_bytes', 'bytes']);
411
- const downloaded = await downloadGroupFsBytes(client, remoteRef(target));
410
+ if (size !== undefined && size > opts.maxCatBytes)
411
+ throw new FsReadBudgetError(opts.maxCatBytes);
412
+ const downloaded = await downloadGroupFsBytes(client, remoteRef(target), { maxBytes: opts.maxCatBytes });
412
413
  bytes = downloaded.bytes;
413
414
  statInfo = { ...statInfo, ...downloaded.ticket };
414
415
  statInfo = { ...statInfo, size: bytes.byteLength };
415
- if (size !== undefined && size > opts.maxCatBytes) {
416
- binaryHeadOnly = true;
417
- }
418
416
  }
419
417
  else {
420
418
  statInfo = await client.storage.stat(target.path, { owner: target.aid, token: opts.token });
421
419
  const size = numberFrom(statInfo, ['size', 'sizeBytes', 'size_bytes']);
422
- if (size !== undefined && size > opts.maxCatBytes) {
423
- bytes = await client.storage.readBytes(target.path, { owner: target.aid, token: opts.token, offset: 0, limit: opts.headBytes });
424
- binaryHeadOnly = true;
425
- }
426
- else {
427
- bytes = await client.storage.readBytes(target.path, { owner: target.aid, token: opts.token });
428
- }
420
+ if (size !== undefined && size > opts.maxCatBytes)
421
+ throw new FsReadBudgetError(opts.maxCatBytes);
422
+ bytes = await readPersonalFsBytesWithinBudget(client, target.path, opts.maxCatBytes, {
423
+ owner: target.aid, token: opts.token,
424
+ });
429
425
  }
430
426
  const binary = looksBinary(bytes, contentTypeFrom(statInfo));
431
- if (binary || binaryHeadOnly) {
427
+ if (binary) {
432
428
  const size = numberFrom(statInfo, ['size', 'sizeBytes', 'size_bytes']) ?? bytes.byteLength;
433
429
  const payload = {
434
430
  command: 'cat',
@@ -1532,6 +1528,9 @@ function outputError(error, formatJson) {
1532
1528
  function mapBackendError(error) {
1533
1529
  if (error instanceof FsCliError)
1534
1530
  return error;
1531
+ if (error instanceof FsReadBudgetError) {
1532
+ return new FsCliError(error.code, error.message, '请使用 ec fs cp 下载大文件。');
1533
+ }
1535
1534
  const code = String(error?.code ?? error?.name ?? 'BACKEND_ERROR');
1536
1535
  const message = String(error?.message || error);
1537
1536
  const upper = code.toUpperCase();