evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -8,11 +8,26 @@
8
8
  */
9
9
  import fs from 'fs';
10
10
  import path from 'path';
11
+ import os from 'os';
11
12
  import { execFile, execFileSync } from 'child_process';
12
13
  import { promisify } from 'util';
13
14
  import { getPackageRoot } from '../paths.js';
15
+ import { DEFAULT_RUNTIME_HOME_DIRNAME, MAIN_PACKAGE_NAME, RUNTIME_HOME_ENV, TARGET_PACKAGE_ROOT_ENV, WEB_PACKAGE_NAME, } from '../product.js';
14
16
  import { isWindows } from './cross-platform.js';
17
+ import { withFileLock } from './atomic-write.js';
15
18
  const execFileAsync = promisify(execFile);
19
+ function globalInstallLockPath() {
20
+ const home = process.env[RUNTIME_HOME_ENV] || path.join(os.homedir(), DEFAULT_RUNTIME_HOME_DIRNAME);
21
+ return path.join(home, 'data', 'npm-global-install.lock');
22
+ }
23
+ /**
24
+ * The restart monitor may execute from a staged copy of the package while
25
+ * upgrading the real installation. Keep code loading rooted at getPackageRoot
26
+ * but resolve install layout and local versions from the package being served.
27
+ */
28
+ function targetPackageRoot() {
29
+ return process.env[TARGET_PACKAGE_ROOT_ENV] || getPackageRoot();
30
+ }
16
31
  /** The preferred public registry for update checks. */
17
32
  export const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
18
33
  function normalizeRegistry(registry = DEFAULT_NPM_REGISTRY) {
@@ -55,7 +70,7 @@ function resolveNpmInvocation() {
55
70
  * default. A command failure is handled separately by using the public source
56
71
  * directly, so a broken npm config cannot leave the upgrade without a retry.
57
72
  */
58
- async function resolveUpgradeRegistry(signal) {
73
+ export async function resolveUpgradeRegistry(signal) {
59
74
  const npm = resolveNpmInvocation();
60
75
  try {
61
76
  const result = await execFileAsync(npm.command, [...npm.argsPrefix, 'config', 'get', 'registry'], {
@@ -115,7 +130,7 @@ async function withGlobalInstallLock(action) {
115
130
  globalInstallTail = new Promise(resolve => { release = resolve; });
116
131
  await previous;
117
132
  try {
118
- await action();
133
+ await withFileLock(globalInstallLockPath(), action, 5 * 60_000);
119
134
  }
120
135
  finally {
121
136
  release();
@@ -191,6 +206,59 @@ export async function requireOptional(pkg, autoInstall = true) {
191
206
  return await import(pkg);
192
207
  }
193
208
  }
209
+ const PACKAGE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
210
+ export function isPackageVersion(value) {
211
+ if (typeof value !== 'string' || value.length > 256)
212
+ return false;
213
+ const match = PACKAGE_VERSION.exec(value);
214
+ return !!match && (!match[4] || match[4].split('.').every(id => !/^\d+$/.test(id) || id === '0' || !id.startsWith('0')));
215
+ }
216
+ export function normalizeVersionRegistry(value) {
217
+ if (typeof value !== 'string')
218
+ return null;
219
+ try {
220
+ const url = new URL(value);
221
+ if (!['https:', 'http:'].includes(url.protocol) || !url.hostname
222
+ || url.username || url.password || url.search || url.hash)
223
+ return null;
224
+ return url.href.replace(/\/+$/, '');
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ }
230
+ export function comparePackageVersions(a, b) {
231
+ if (!isPackageVersion(a) || !isPackageVersion(b))
232
+ throw new Error('Invalid package version');
233
+ const left = PACKAGE_VERSION.exec(a);
234
+ const right = PACKAGE_VERSION.exec(b);
235
+ for (let i = 1; i <= 3; i++) {
236
+ if (BigInt(left[i]) < BigInt(right[i]))
237
+ return -1;
238
+ if (BigInt(left[i]) > BigInt(right[i]))
239
+ return 1;
240
+ }
241
+ if (!left[4] || !right[4])
242
+ return left[4] === right[4] ? 0 : left[4] ? -1 : 1;
243
+ const la = left[4].split('.');
244
+ const ra = right[4].split('.');
245
+ for (let i = 0; i < Math.max(la.length, ra.length); i++) {
246
+ if (la[i] === ra[i])
247
+ continue;
248
+ if (la[i] === undefined)
249
+ return -1;
250
+ if (ra[i] === undefined)
251
+ return 1;
252
+ const ln = /^\d+$/.test(la[i]);
253
+ const rn = /^\d+$/.test(ra[i]);
254
+ if (ln && rn)
255
+ return BigInt(la[i]) < BigInt(ra[i]) ? -1 : 1;
256
+ if (ln !== rn)
257
+ return ln ? -1 : 1;
258
+ return la[i] < ra[i] ? -1 : 1;
259
+ }
260
+ return 0;
261
+ }
194
262
  /**
195
263
  * 比较两个 semver 版本号 (a.b.c 格式)
196
264
  * 返回 -1 (a < b), 0 (a == b), 1 (a > b)
@@ -216,12 +284,12 @@ export function compareVersions(a, b) {
216
284
  * 而 npm link 指向项目源码目录,其父目录不是 node_modules。
217
285
  */
218
286
  export function isLinkedInstall() {
219
- const pkgRoot = getPackageRoot();
287
+ const pkgRoot = targetPackageRoot();
220
288
  return path.basename(path.dirname(pkgRoot)) !== 'node_modules';
221
289
  }
222
290
  /** 获取本地 package.json 中的版本号 */
223
291
  export function getLocalVersion() {
224
- const pkgPath = path.join(getPackageRoot(), 'package.json');
292
+ const pkgPath = path.join(targetPackageRoot(), 'package.json');
225
293
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
226
294
  return pkg.version;
227
295
  }
@@ -276,6 +344,53 @@ async function fetchLatestVersion(pkg = 'evolcore', timeoutMs = 10000, signal, r
276
344
  signal?.removeEventListener('abort', abort);
277
345
  }
278
346
  }
347
+ /** Query an already selected source, retaining npm proxy/CA/auth behavior. */
348
+ export async function queryPackageVersion(pkg, selection, signal) {
349
+ const registry = normalizeVersionRegistry(selection.registry);
350
+ if (!registry)
351
+ return null;
352
+ const version = selection.useNpmCli
353
+ ? await checkLatestVersionViaNpm(pkg, 10000, signal, registry)
354
+ : await fetchLatestVersion(pkg, 10000, signal, registry);
355
+ return isPackageVersion(version) ? { version, registry } : null;
356
+ }
357
+ /** No registry lookup: callers must validate cache freshness before each install. */
358
+ export async function installExactPackageVersion(pkg, target, registry, resolveInstalled) {
359
+ if (![MAIN_PACKAGE_NAME, WEB_PACKAGE_NAME].includes(pkg)
360
+ || !isPackageVersion(target) || normalizeVersionRegistry(registry) !== registry) {
361
+ return { status: 'failed', error: 'Invalid cached install target' };
362
+ }
363
+ if (isLinkedInstall())
364
+ return { status: 'skipped' };
365
+ let result = { status: 'skipped' };
366
+ await withGlobalInstallLock(async () => {
367
+ const installed = resolveInstalled();
368
+ if (!installed || !isPackageVersion(installed.version))
369
+ return;
370
+ const from = installed.version;
371
+ if (comparePackageVersions(from, target) >= 0) {
372
+ result = { status: 'no-update', from };
373
+ return;
374
+ }
375
+ for (let attempt = 0; attempt < 2; attempt++) {
376
+ try {
377
+ await npmInstallGlobal(`${pkg}@${target}`, { registry });
378
+ const actual = resolveInstalled()?.version;
379
+ if (!actual || !isPackageVersion(actual) || comparePackageVersions(actual, target) < 0) {
380
+ throw new Error('Installed version did not reach the cached target');
381
+ }
382
+ result = { status: 'upgraded', from, to: actual };
383
+ return;
384
+ }
385
+ catch {
386
+ // npm stderr can contain private registry credentials. Persist a safe
387
+ // diagnostic; npm's existing permission/transport behavior is retained.
388
+ result = { status: 'failed', from, to: target, error: 'Exact package installation or version verification failed' };
389
+ }
390
+ }
391
+ });
392
+ return result;
393
+ }
279
394
  /**
280
395
  * Read the current npm registry first. If that read succeeds, npm itself is
281
396
  * used for the version query so proxy/CA/auth settings are preserved. If the
@@ -0,0 +1,244 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getPackageRoot, resolvePaths } from '../paths.js';
4
+ import { MAIN_PACKAGE_NAME, WEB_PACKAGE_NAME, TARGET_PACKAGE_ROOT_ENV } from '../product.js';
5
+ import { getLocalVersion, isLinkedInstall, resolveGlobalPkg, resolveUpgradeRegistry, queryPackageVersion, installExactPackageVersion, } from './npm-ops.js';
6
+ import { comparePackageVersions, isPackageVersion, normalizeVersionRegistry } from './npm-ops.js';
7
+ import { atomicReplaceJson, withFileLock } from './atomic-write.js';
8
+ export const VERSION_INFO_TTL_MS = 24 * 60 * 60 * 1000;
9
+ export const VERSION_INFO_PACKAGES = [MAIN_PACKAGE_NAME, WEB_PACKAGE_NAME, '@agentunion/fastaun'];
10
+ export const AUTO_UPGRADE_PACKAGES = [MAIN_PACKAGE_NAME, WEB_PACKAGE_NAME];
11
+ const empty = () => ({ $schema_version: 1, packages: {} });
12
+ const timestamp = (value) => typeof value === 'string' ? Date.parse(value) : NaN;
13
+ const allowed = (name) => VERSION_INFO_PACKAGES.includes(name);
14
+ const filePath = () => path.join(resolvePaths().dataDir, 'package-version-info.json');
15
+ const writeLockPath = () => `${filePath()}.lock`;
16
+ /** Read-only: corrupt/unknown formats never recover fields from partial data. */
17
+ export function readPackageVersionInfo() {
18
+ try {
19
+ const raw = JSON.parse(fs.readFileSync(filePath(), 'utf8'));
20
+ if (raw?.$schema_version !== 1 || !raw.packages || Array.isArray(raw.packages)
21
+ || typeof raw.packages !== 'object' || Object.keys(raw.packages).some(name => !allowed(name)))
22
+ return empty();
23
+ const result = empty();
24
+ for (const [name, value] of Object.entries(raw.packages)) {
25
+ const entry = value;
26
+ if (!entry || entry.packageName !== name || !['ok', 'unavailable', 'skipped', 'not-installed'].includes(entry.lastAttemptStatus)
27
+ || (entry.currentVersion !== null && !isPackageVersion(entry.currentVersion))
28
+ || (entry.latestVersion != null && !isPackageVersion(entry.latestVersion))
29
+ || (entry.registry !== undefined && normalizeVersionRegistry(entry.registry) !== entry.registry))
30
+ continue;
31
+ result.packages[name] = entry;
32
+ }
33
+ return result;
34
+ }
35
+ catch {
36
+ return empty();
37
+ }
38
+ }
39
+ /** Cooperating readers pause while a writer owns the Windows replace gate. */
40
+ export async function readPackageVersionInfoConsistent(timeoutMs = 30000) {
41
+ const deadline = Date.now() + timeoutMs;
42
+ while (fs.existsSync(writeLockPath())) {
43
+ if (Date.now() >= deadline)
44
+ return readPackageVersionInfo();
45
+ await new Promise(resolve => setTimeout(resolve, 15));
46
+ }
47
+ return readPackageVersionInfo();
48
+ }
49
+ export function isPackageVersionInfoFresh(entry, now = Date.now(), ttlMs = VERSION_INFO_TTL_MS, registry) {
50
+ if (!entry || !allowed(entry.packageName) || !isPackageVersion(entry.currentVersion)
51
+ || !isPackageVersion(entry.latestVersion) || !entry.registry
52
+ || normalizeVersionRegistry(entry.registry) !== entry.registry
53
+ || (registry !== undefined && entry.registry !== registry))
54
+ return false;
55
+ const checked = timestamp(entry.checkedAt);
56
+ const observed = timestamp(entry.currentVersionObservedAt);
57
+ return Number.isFinite(checked) && checked <= now && now - checked <= ttlMs
58
+ && Number.isFinite(observed) && observed <= now;
59
+ }
60
+ /** Snapshot groups have separate clocks so a stale query cannot undo an install. */
61
+ export async function mergePackageVersionInfo(patch) {
62
+ if (!allowed(patch.packageName))
63
+ throw new Error('Unmanaged package');
64
+ if (patch.currentVersion !== null && !isPackageVersion(patch.currentVersion))
65
+ throw new Error('Invalid installed version');
66
+ if (patch.checkedAt && (!isPackageVersion(patch.latestVersion)
67
+ || !patch.registry || normalizeVersionRegistry(patch.registry) !== patch.registry))
68
+ throw new Error('Invalid successful snapshot');
69
+ const file = filePath();
70
+ return withFileLock(writeLockPath(), async () => {
71
+ const data = readPackageVersionInfo();
72
+ const previous = data.packages[patch.packageName];
73
+ const entry = previous ? { ...previous } : {
74
+ packageName: patch.packageName, currentVersion: null, lastAttemptStatus: patch.lastAttemptStatus,
75
+ };
76
+ const newer = (next, old) => Number.isFinite(timestamp(next))
77
+ && (!Number.isFinite(timestamp(old)) || timestamp(old) > Date.now() || timestamp(next) >= timestamp(old));
78
+ if (newer(patch.currentVersionObservedAt, entry.currentVersionObservedAt)) {
79
+ entry.currentVersion = patch.currentVersion;
80
+ entry.currentVersionObservedAt = patch.currentVersionObservedAt;
81
+ }
82
+ if (newer(patch.checkedAt, entry.checkedAt)) {
83
+ entry.latestVersion = patch.latestVersion;
84
+ entry.checkedAt = patch.checkedAt;
85
+ entry.registry = patch.registry;
86
+ }
87
+ if (newer(patch.lastAttemptAt, entry.lastAttemptAt)) {
88
+ entry.lastAttemptStatus = patch.lastAttemptStatus;
89
+ entry.lastAttemptAt = patch.lastAttemptAt;
90
+ if (patch.lastError)
91
+ entry.lastError = patch.lastError;
92
+ else
93
+ delete entry.lastError;
94
+ }
95
+ if (patch.lastUpgrade && newer(patch.lastUpgrade.at, entry.lastUpgrade?.at))
96
+ entry.lastUpgrade = patch.lastUpgrade;
97
+ data.packages[patch.packageName] = entry;
98
+ await atomicReplaceJson(file, data);
99
+ return entry;
100
+ });
101
+ }
102
+ export function resolveVersionInfoPackage(name) {
103
+ if (!allowed(name))
104
+ throw new Error('Unmanaged package');
105
+ const root = process.env[TARGET_PACKAGE_ROOT_ENV] || getPackageRoot();
106
+ if (name === MAIN_PACKAGE_NAME)
107
+ return { version: getLocalVersion(), path: path.join(root, 'package.json') };
108
+ if (name === WEB_PACKAGE_NAME)
109
+ return resolveGlobalPkg(name);
110
+ const file = path.join(root, 'node_modules', '@agentunion', 'fastaun', 'package.json');
111
+ try {
112
+ return { version: JSON.parse(fs.readFileSync(file, 'utf8')).version, path: file };
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ function hasUpdate(entry, registry, now = Date.now()) {
119
+ return isPackageVersionInfoFresh(entry, now, VERSION_INFO_TTL_MS, registry)
120
+ && comparePackageVersions(entry.currentVersion, entry.latestVersion) < 0;
121
+ }
122
+ async function checkPackage(name, force, options) {
123
+ if (!allowed(name))
124
+ throw new Error('Unmanaged package');
125
+ const now = options.now ?? Date.now();
126
+ const at = new Date(now).toISOString();
127
+ const old = (await readPackageVersionInfoConsistent()).packages[name];
128
+ const observed = {
129
+ packageName: name, currentVersion: null, currentVersionObservedAt: at,
130
+ lastAttemptStatus: 'unavailable',
131
+ };
132
+ const log = (message) => options.log?.(`[${options.source ?? 'version-check'}] ${name}: ${message}`);
133
+ let source = 'refresh';
134
+ try {
135
+ const linked = isLinkedInstall();
136
+ const local = linked && name !== MAIN_PACKAGE_NAME ? null : resolveVersionInfoPackage(name);
137
+ if (local && !isPackageVersion(local.version))
138
+ throw new Error('Invalid installed package version');
139
+ observed.currentVersion = local?.version ?? null;
140
+ if (linked)
141
+ observed.lastAttemptStatus = 'skipped';
142
+ else if (!local)
143
+ observed.lastAttemptStatus = 'not-installed';
144
+ else {
145
+ const selection = options.selection ?? await resolveUpgradeRegistry(options.signal);
146
+ const registry = normalizeVersionRegistry(selection.registry);
147
+ if (!registry)
148
+ throw new Error('Registry configuration is not safe for version caching');
149
+ if (!force && isPackageVersionInfoFresh(old, now, VERSION_INFO_TTL_MS, registry)) {
150
+ source = 'cache';
151
+ observed.lastAttemptStatus = 'ok';
152
+ const entry = await mergePackageVersionInfo(observed);
153
+ log(`Using cached version information (age ${Math.floor((now - timestamp(old.checkedAt)) / 60000)}m)`);
154
+ return { packageName: name, status: 'ok', source, entry, hasUpdate: hasUpdate(entry, registry, now) };
155
+ }
156
+ log(force ? 'Refreshing version information' : 'Version information missing/invalid/expired; refreshing');
157
+ const checked = await queryPackageVersion(name, selection, options.signal);
158
+ if (!checked)
159
+ throw new Error('Version registry unavailable or returned an invalid version');
160
+ observed.latestVersion = checked.version;
161
+ observed.registry = checked.registry;
162
+ // Use request start, not completion: an older slow request cannot replace
163
+ // a later successful request, and query time consumes the TTL.
164
+ observed.checkedAt = at;
165
+ observed.lastAttemptStatus = 'ok';
166
+ }
167
+ }
168
+ catch {
169
+ observed.lastAttemptStatus = 'unavailable';
170
+ observed.lastError = 'Version refresh failed; continuing with current version';
171
+ log(observed.lastError);
172
+ }
173
+ observed.lastAttemptAt = at;
174
+ let entry = { ...old, ...observed };
175
+ try {
176
+ entry = await mergePackageVersionInfo(observed);
177
+ }
178
+ catch {
179
+ log('Version information could not be saved; continuing with current version');
180
+ return { packageName: name, source, entry, status: 'unavailable', hasUpdate: false, error: 'Version information could not be saved' };
181
+ }
182
+ return {
183
+ packageName: name, source, entry, status: observed.lastAttemptStatus,
184
+ hasUpdate: observed.lastAttemptStatus === 'ok' && hasUpdate(entry, observed.registry, now),
185
+ ...(observed.lastError ? { error: observed.lastError } : {}),
186
+ };
187
+ }
188
+ export async function refreshPackageVersionInfo(name, options = {}) {
189
+ return checkPackage(name, true, options);
190
+ }
191
+ export async function ensureFreshPackageVersionInfo(names = AUTO_UPGRADE_PACKAGES, options = {}) {
192
+ const selection = options.selection ?? (isLinkedInstall() ? undefined : await resolveUpgradeRegistry(options.signal));
193
+ return Promise.all(names.map(name => checkPackage(name, false, { ...options, selection })));
194
+ }
195
+ export async function refreshManagedPackageVersions(names = AUTO_UPGRADE_PACKAGES, options = {}) {
196
+ const selection = options.selection ?? (isLinkedInstall() ? undefined : await resolveUpgradeRegistry(options.signal));
197
+ return Promise.all(names.map(name => checkPackage(name, true, { ...options, selection })));
198
+ }
199
+ export async function recordPackageUpgrade(name, upgrade, log) {
200
+ try {
201
+ await mergePackageVersionInfo({
202
+ packageName: name, currentVersion: resolveVersionInfoPackage(name)?.version ?? null,
203
+ currentVersionObservedAt: new Date().toISOString(), lastAttemptStatus: 'unavailable', lastUpgrade: upgrade,
204
+ });
205
+ }
206
+ catch {
207
+ log?.(`${name}: Failed to record install outcome`);
208
+ }
209
+ }
210
+ /** Called only by the lifecycle coordinator, after shutdown. Never queries registry. */
211
+ export async function applyCachedPackageUpgrades(names = AUTO_UPGRADE_PACKAGES, options = {}) {
212
+ if (names.some(name => !AUTO_UPGRADE_PACKAGES.includes(name)))
213
+ throw new Error('Package is not automatically managed');
214
+ if (isLinkedInstall())
215
+ return names.map(() => ({ status: 'skipped' }));
216
+ const results = [];
217
+ for (const name of names) {
218
+ let result = { status: 'skipped' };
219
+ if (!AUTO_UPGRADE_PACKAGES.includes(name))
220
+ throw new Error('Package is not automatically managed');
221
+ try {
222
+ const entry = (await readPackageVersionInfoConsistent(0)).packages[name];
223
+ const registry = entry?.registry ? normalizeVersionRegistry(entry.registry) : null;
224
+ if (!registry || !isPackageVersionInfoFresh(entry, Date.now(), VERSION_INFO_TTL_MS, registry)) {
225
+ result = { status: 'skipped', error: 'No valid cached version information' };
226
+ }
227
+ else {
228
+ options.log?.(`Applying cached version information: ${name}@${entry.latestVersion}`);
229
+ result = await installExactPackageVersion(name, entry.latestVersion, registry, () => resolveVersionInfoPackage(name));
230
+ }
231
+ }
232
+ catch {
233
+ result = { status: 'failed', error: 'Cached upgrade could not acquire installation lock or read installed version' };
234
+ }
235
+ if (result.status === 'upgraded' || result.status === 'failed') {
236
+ await recordPackageUpgrade(name, {
237
+ from: result.from, to: result.to, at: new Date().toISOString(),
238
+ status: result.status === 'upgraded' ? 'installed' : 'failed', error: result.error,
239
+ }, options.log);
240
+ }
241
+ results.push(result);
242
+ }
243
+ return results;
244
+ }
@@ -51,19 +51,75 @@ function displayShellCommand(command) {
51
51
  const carrier = resolveCodexShellCarrierString(command);
52
52
  return carrier?.dialect === 'powershell' ? carrier.command : undefined;
53
53
  }
54
+ /** Hide an initial cd through its first unquoted && or ;, regardless of the directory. */
55
+ function displayBashCommand(command) {
56
+ const prefix = command.match(/^[ \t]*cd(?=[ \t;]|&&)/);
57
+ if (!prefix)
58
+ return command;
59
+ let quote;
60
+ for (let index = prefix[0].length; index < command.length; index++) {
61
+ const char = command[index];
62
+ if (quote === "'") {
63
+ if (char === quote)
64
+ quote = undefined;
65
+ continue;
66
+ }
67
+ if (char === '\\') {
68
+ index++;
69
+ continue;
70
+ }
71
+ // Nested shell expressions need a full shell parser; keep them visible.
72
+ if (char === '`' || (char === '$' && command[index + 1] === '('))
73
+ return command;
74
+ if (quote === '"') {
75
+ if (char === quote)
76
+ quote = undefined;
77
+ continue;
78
+ }
79
+ if (char === "'" || char === '"') {
80
+ quote = char;
81
+ continue;
82
+ }
83
+ if (char === ';' || (char === '&' && command[index + 1] === '&')) {
84
+ if (char === ';' && /[;&]/.test(command[index + 1] ?? ''))
85
+ return command;
86
+ const body = command.slice(index + (char === ';' ? 1 : 2)).trimStart();
87
+ return body || command;
88
+ }
89
+ // Do not skip over other commands, redirections, or shell comments.
90
+ if (/[\r\n|&<>()]/.test(char) || (char === '#' && /[ \t]/.test(command[index - 1])))
91
+ return command;
92
+ }
93
+ return command;
94
+ }
54
95
  /**
55
96
  * Build the tool arguments projected into activity/thought payloads.
56
97
  * Execution, permission checks, and audit logging keep using the original
57
- * input; only the human-facing Shell command is de-wrapped here.
98
+ * input; only the human-facing command is simplified here.
58
99
  */
59
100
  export function toolInputForDisplay(toolName, input) {
60
- if (!input || toolName !== 'Shell')
101
+ if (!input || typeof input.command !== 'string')
102
+ return input;
103
+ let command;
104
+ if (toolName === 'Shell') {
105
+ command = displayShellCommand(input.command) ?? input.command;
106
+ // Keep explicit shell invocations, but omit standard executable directories.
107
+ command = command.replace(/^(\s*)(['"]?)\/(?:usr\/)?bin\/(bash|sh)\2(?=\s|$)/, '$1$2$3$2');
108
+ }
109
+ else if (toolName === 'Bash') {
110
+ command = displayBashCommand(input.command);
111
+ }
112
+ else {
61
113
  return input;
62
- const command = displayShellCommand(input.command);
114
+ }
63
115
  if (!command || command === input.command)
64
116
  return input;
65
117
  return { ...input, command };
66
118
  }
119
+ /** Activity summaries use the same command projection as displayed arguments. */
120
+ export function summarizeToolInputForDisplay(toolName, input) {
121
+ return summarizeToolInput(toolName, toolInputForDisplay(toolName, input) ?? {});
122
+ }
67
123
  /**
68
124
  * 工具输入摘要(提取工具调用的可读描述,供权限审批和消息展示使用)
69
125
  */