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.
- package/CHANGELOG.md +36 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +10 -5
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +70 -26
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex-runner.js +363 -421
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +13 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +544 -239
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +111 -162
- package/dist/cli/diag.js +3 -3
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +13 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +81 -19
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +105 -40
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +364 -96
- package/dist/core/command/menu-protocol.js +43 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +61 -37
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +94 -16
- package/dist/core/message/message-bridge.js +140 -13
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +729 -118
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +76 -36
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +8 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +38 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +322 -207
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +3 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/dist/utils/windows-shell-trust.js +37 -0
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +23 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
package/dist/utils/npm-ops.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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(
|
|
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
|
|
98
|
+
* input; only the human-facing command is simplified here.
|
|
58
99
|
*/
|
|
59
100
|
export function toolInputForDisplay(toolName, input) {
|
|
60
|
-
if (!input ||
|
|
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
|
-
|
|
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
|
*/
|