evolcore 0.0.23 → 0.0.24
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 +50 -0
- package/README.md +3 -1
- package/bin/codex-managed-acl.ps1 +7 -2
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +17 -6
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +148 -13
- package/dist/agents/codex/app-server-client.js +18 -8
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +215 -0
- package/dist/agents/codex-runner.js +438 -59
- package/dist/agents/ecagent-runner.js +17 -3
- package/dist/agents/gemini-runner.js +60 -2
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +41 -0
- package/dist/aun/msg/managed-operation.js +6 -1
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +4 -5
- package/dist/channels/aun.js +147 -76
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +60 -20
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +148 -17
- package/dist/cli/index.js +2 -2
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/config/builtin-roles.js +2 -0
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +72 -19
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +10 -2
- package/dist/core/auth/remote-access-store.js +3 -2
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +87 -25
- package/dist/core/command/menu-catalog.js +3 -1
- package/dist/core/command/menu-handler.js +274 -70
- package/dist/core/command/menu-protocol.js +6 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +157 -48
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +73 -21
- package/dist/core/model/catalog-service.js +3 -1
- package/dist/core/model/model-catalog.js +10 -0
- package/dist/core/model/model-permission.js +4 -1
- package/dist/core/permission/approval-gateway.js +169 -33
- package/dist/core/permission/tool-policy.js +13 -6
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +174 -28
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/templates/roles/admin.json +2 -0
- package/package.json +7 -3
- package/AGENTS.md +0 -62
- package/dist/utils/codex-cli.js +0 -43
package/dist/index.js
CHANGED
|
@@ -100,6 +100,7 @@ import { MAIN_PACKAGE_NAME } from './product.js';
|
|
|
100
100
|
import fs from 'fs';
|
|
101
101
|
import crypto from 'crypto';
|
|
102
102
|
import { fileURLToPath } from 'url';
|
|
103
|
+
import * as platform from './utils/cross-platform.js';
|
|
103
104
|
const controlMenuDeduper = new MenuRequestDeduper();
|
|
104
105
|
/** 出站 payload 摘要(用于 channel-out.log) */
|
|
105
106
|
function summarizeOutboundPayload(payload) {
|
|
@@ -134,6 +135,7 @@ function summarizeOutboundPayload(payload) {
|
|
|
134
135
|
break;
|
|
135
136
|
case 'status.started':
|
|
136
137
|
case 'status.progress':
|
|
138
|
+
case 'status.notice':
|
|
137
139
|
case 'status.requires_action':
|
|
138
140
|
case 'status.queued':
|
|
139
141
|
case 'status.completed':
|
|
@@ -326,11 +328,21 @@ function seedUpgradeCheckTrigger(manager, owner) {
|
|
|
326
328
|
const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
|
|
327
329
|
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
328
330
|
const devMode = fs.existsSync(path.join(packageRoot, 'src', 'index.ts'));
|
|
331
|
+
// Resolve the launch argv at generation time: in a checkout this points the
|
|
332
|
+
// monitor at src/ through tsx, so a `npm run build` that wipes dist/ cannot
|
|
333
|
+
// take the watchdog down with it.
|
|
334
|
+
const monitorEntry = platform.resolveCliEntry(packageRoot);
|
|
335
|
+
const stageParent = path.join(resolvePaths().root, 'data', 'monitor-stage');
|
|
329
336
|
fs.writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
330
|
-
const
|
|
337
|
+
const fs = require('node:fs');
|
|
338
|
+
const path = require('node:path');
|
|
339
|
+
const { existsSync, readFileSync } = fs;
|
|
331
340
|
const { spawn } = require('node:child_process');
|
|
332
341
|
|
|
333
|
-
const CLI_PATH = ${JSON.stringify(
|
|
342
|
+
const CLI_PATH = ${JSON.stringify(monitorEntry.entry)};
|
|
343
|
+
const CLI_ARGS = ${JSON.stringify(monitorEntry.args)};
|
|
344
|
+
const PACKAGE_ROOT = ${JSON.stringify(packageRoot)};
|
|
345
|
+
const STAGE_PARENT = ${JSON.stringify(stageParent)};
|
|
334
346
|
const PACKAGE_JSON = ${JSON.stringify(packageJsonPath)};
|
|
335
347
|
const PACKAGE_NAME = ${JSON.stringify(MAIN_PACKAGE_NAME)};
|
|
336
348
|
const DEV_MODE = ${devMode ? 'true' : 'false'};
|
|
@@ -405,14 +417,60 @@ async function main() {
|
|
|
405
417
|
return;
|
|
406
418
|
}
|
|
407
419
|
|
|
420
|
+
// Installed mode: copy the monitor out of the package before it upgrades that
|
|
421
|
+
// same package. Mirrors prepareMonitorStage(); kept inline because this file
|
|
422
|
+
// is generated standalone CJS. Any failure degrades to launching in place —
|
|
423
|
+
// losing upgrade isolation beats losing the upgrade.
|
|
424
|
+
let launchArgs = CLI_ARGS;
|
|
425
|
+
const launchEnv = { ...process.env };
|
|
426
|
+
if (!DEV_MODE && STAGE_PARENT) {
|
|
427
|
+
try {
|
|
428
|
+
const stageDir = path.join(STAGE_PARENT, 'upgrade-' + process.pid + '-' + Date.now());
|
|
429
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
430
|
+
fs.cpSync(path.join(PACKAGE_ROOT, 'dist'), path.join(stageDir, 'dist'), { recursive: true });
|
|
431
|
+
if (existsSync(PACKAGE_JSON)) fs.copyFileSync(PACKAGE_JSON, path.join(stageDir, 'package.json'));
|
|
432
|
+
const modulesSource = path.join(PACKAGE_ROOT, 'node_modules');
|
|
433
|
+
if (existsSync(modulesSource)) {
|
|
434
|
+
const staged = path.join(stageDir, 'node_modules');
|
|
435
|
+
try {
|
|
436
|
+
// junction works unelevated on Windows; dir symlink elsewhere.
|
|
437
|
+
fs.symlinkSync(modulesSource, staged, process.platform === 'win32' ? 'junction' : 'dir');
|
|
438
|
+
fs.readdirSync(staged);
|
|
439
|
+
} catch (linkError) {
|
|
440
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
441
|
+
fs.cpSync(modulesSource, staged, { recursive: true });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const stagedEntry = path.join(stageDir, 'dist', 'cli', 'index.js');
|
|
445
|
+
if (existsSync(stagedEntry)) {
|
|
446
|
+
launchArgs = [stagedEntry];
|
|
447
|
+
launchEnv.EVOLCORE_TARGET_PACKAGE_ROOT = PACKAGE_ROOT;
|
|
448
|
+
launchEnv.EVOLCORE_MONITOR_STAGE = stageDir;
|
|
449
|
+
}
|
|
450
|
+
} catch (stageError) {
|
|
451
|
+
// Fall through with the in-place entry.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
408
455
|
try {
|
|
409
|
-
const child = spawn(process.execPath, [
|
|
456
|
+
const child = spawn(process.execPath, [...launchArgs, 'restart-monitor'], {
|
|
410
457
|
detached: true,
|
|
411
458
|
stdio: 'ignore',
|
|
412
459
|
windowsHide: process.platform === 'win32',
|
|
413
|
-
env:
|
|
460
|
+
env: launchEnv,
|
|
414
461
|
});
|
|
415
462
|
child.unref();
|
|
463
|
+
const pid = child.pid;
|
|
464
|
+
// The daemon is still running here — the monitor is what stops it. So a
|
|
465
|
+
// monitor that dies on load costs the upgrade, not the service; still,
|
|
466
|
+
// report it accurately instead of claiming success.
|
|
467
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
468
|
+
let alive = false;
|
|
469
|
+
try { process.kill(pid, 0); alive = true; } catch (e) { alive = false; }
|
|
470
|
+
if (!alive) {
|
|
471
|
+
emit('error', 'restart-monitor exited during startup; upgrade to ' + remoteVersion + ' not applied');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
416
474
|
emit('success', 'evolcore upgrade available: ' + localVersion + ' -> ' + remoteVersion + '; restart-monitor started');
|
|
417
475
|
} catch (error) {
|
|
418
476
|
emit('error', 'failed to start restart-monitor: ' + (error && error.message ? error.message : String(error)));
|
|
@@ -1066,7 +1124,7 @@ async function main() {
|
|
|
1066
1124
|
agentLoader.register(new GeminiAgentPlugin());
|
|
1067
1125
|
agentLoader.register(new EcagentAgentPlugin());
|
|
1068
1126
|
const creationErrors = [];
|
|
1069
|
-
const agentInstances = agentLoader.createAll(agentRegistry, {
|
|
1127
|
+
const agentInstances = await agentLoader.createAll(agentRegistry, {
|
|
1070
1128
|
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
1071
1129
|
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
1072
1130
|
},
|
|
@@ -1493,8 +1551,9 @@ async function main() {
|
|
|
1493
1551
|
}
|
|
1494
1552
|
if (definition.execution.effort && !actor.daemonPrivileged) {
|
|
1495
1553
|
const baseagent = definition.execution.baseagent
|
|
1496
|
-
?? agentRegistry.get(definition.agentAid)?.baseagent
|
|
1497
|
-
|
|
1554
|
+
?? agentRegistry.get(definition.agentAid)?.baseagent;
|
|
1555
|
+
if (!baseagent)
|
|
1556
|
+
throw new Error('cannot determine Trigger baseagent');
|
|
1498
1557
|
const effortDecision = validateRuntimeStringFieldOverride({
|
|
1499
1558
|
selfAid: definition.agentAid,
|
|
1500
1559
|
role: actor.role,
|
|
@@ -2154,6 +2213,7 @@ async function main() {
|
|
|
2154
2213
|
const pendingRouteContext = restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {};
|
|
2155
2214
|
const replyContext = {
|
|
2156
2215
|
...(pendingRouteContext ?? {}),
|
|
2216
|
+
...(pending.threadId ? { threadId: pending.threadId } : {}),
|
|
2157
2217
|
...(pending.rootId ? { replyToMessageId: pending.rootId, replyInThread: !!pending.threadId } : {}),
|
|
2158
2218
|
};
|
|
2159
2219
|
const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
|
|
@@ -2165,10 +2225,23 @@ async function main() {
|
|
|
2165
2225
|
...(restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {}),
|
|
2166
2226
|
replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
|
|
2167
2227
|
});
|
|
2228
|
+
// restart-monitor 会在回滚/升级时写入 outcome:必须如实告诉发起方这次跑的是
|
|
2229
|
+
// 哪个版本,否则 agent 会以为自己的改动或升级已经生效。
|
|
2230
|
+
const restartNotice = pending.outcome === 'rolled-back'
|
|
2231
|
+
? {
|
|
2232
|
+
text: `⚠️ 服务已重启,但运行的是上一个版本。${pending.outcomeReason ? `原因:${pending.outcomeReason}。` : ''}本次改动未生效,请检查后重试。`,
|
|
2233
|
+
subtype: 'restarted-rolled-back',
|
|
2234
|
+
}
|
|
2235
|
+
: pending.outcome === 'upgraded'
|
|
2236
|
+
? {
|
|
2237
|
+
text: `✅ 服务重启成功!${pending.outcomeReason ? `${pending.outcomeReason}` : ''}`,
|
|
2238
|
+
subtype: 'restarted-upgraded',
|
|
2239
|
+
}
|
|
2240
|
+
: { text: '✅ 服务重启成功!', subtype: 'restarted' };
|
|
2168
2241
|
await sendSystemPayload(adapter, envelope, {
|
|
2169
2242
|
kind: 'system.notice',
|
|
2170
|
-
text:
|
|
2171
|
-
subtype:
|
|
2243
|
+
text: restartNotice.text,
|
|
2244
|
+
subtype: restartNotice.subtype,
|
|
2172
2245
|
});
|
|
2173
2246
|
pendingRestartNoticeSent = true;
|
|
2174
2247
|
fs.rmSync(pendingFile, { force: true });
|
|
@@ -2658,9 +2731,18 @@ async function main() {
|
|
|
2658
2731
|
continue;
|
|
2659
2732
|
}
|
|
2660
2733
|
const evolName = owningAgent.aid;
|
|
2661
|
-
const baseagentName = session.baseagent
|
|
2662
|
-
|
|
2734
|
+
const baseagentName = typeof session.baseagent === 'string' ? session.baseagent.trim() : '';
|
|
2735
|
+
if (!baseagentName) {
|
|
2736
|
+
logger.error(`[Resume] session ${session.id}: missing baseagent binding, refusing implicit runner fallback`);
|
|
2737
|
+
sessionManager.clearProcessing(session.id);
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
// Recovery must resume on the exact Agent × baseagent runner recorded by
|
|
2741
|
+
// the Session. Never fall back to the primary runner: doing so can bind
|
|
2742
|
+
// an in-flight session to a different backend.
|
|
2743
|
+
const agent = agentMap.get(`${evolName}::${baseagentName}`);
|
|
2663
2744
|
if (!agent) {
|
|
2745
|
+
logger.error(`[Resume] session ${session.id}: runner unavailable for ${evolName}::${baseagentName}; refusing fallback`);
|
|
2664
2746
|
sessionManager.clearProcessing(session.id);
|
|
2665
2747
|
continue;
|
|
2666
2748
|
}
|
|
@@ -2727,11 +2809,12 @@ async function main() {
|
|
|
2727
2809
|
return owner?.aid === aid;
|
|
2728
2810
|
});
|
|
2729
2811
|
const welcome = aunInstance?.channel;
|
|
2730
|
-
const ownerAid = getFirstStaticAgentOwner(aid);
|
|
2731
2812
|
const owningAgent = agentRegistry.get(aid);
|
|
2732
|
-
const
|
|
2813
|
+
const firstOwnerAid = getFirstStaticAgentOwner(aid);
|
|
2814
|
+
const sessions = aunInstance && firstOwnerAid && owningAgent
|
|
2733
2815
|
? {
|
|
2734
|
-
ensurePostBootstrapMainSession: async () => {
|
|
2816
|
+
ensurePostBootstrapMainSession: async (owner) => {
|
|
2817
|
+
const ownerAid = owner || firstOwnerAid;
|
|
2735
2818
|
const channel = aunInstance.adapter.channelName;
|
|
2736
2819
|
const active = await sessionManager.getActiveSession(channel, ownerAid);
|
|
2737
2820
|
const ensured = await sessionManager.ensureNewMainSessionForOperation({
|
|
@@ -2739,7 +2822,7 @@ async function main() {
|
|
|
2739
2822
|
channelId: ownerAid,
|
|
2740
2823
|
projectPath: active?.projectPath || owningAgent.projectPath || defaultProjectPath,
|
|
2741
2824
|
baseagent: owningAgent.baseagent || active?.baseagent || primaryBaseagent,
|
|
2742
|
-
operationId: postBootstrapWelcomeOperationId(aid),
|
|
2825
|
+
operationId: postBootstrapWelcomeOperationId(aid, ownerAid),
|
|
2743
2826
|
identityMetadata: {
|
|
2744
2827
|
...(active?.metadata ?? {}),
|
|
2745
2828
|
channelKey: aunInstance.adapter.channelKey,
|
|
@@ -2851,15 +2934,18 @@ async function main() {
|
|
|
2851
2934
|
for (const runner of new Set(agentMap.values())) {
|
|
2852
2935
|
if (typeof runner?.evaluatePreToolUse !== 'function')
|
|
2853
2936
|
continue;
|
|
2854
|
-
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal, params.callId);
|
|
2937
|
+
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal, params.callId, params.sessionToken);
|
|
2855
2938
|
if (result.applicable)
|
|
2856
2939
|
return result;
|
|
2857
2940
|
}
|
|
2941
|
+
// A credential that no runner issued is forged or belongs to a thread
|
|
2942
|
+
// whose rollout is gone. Never allow it; uncredentialed calls were
|
|
2943
|
+
// already allowed by the IPC layer before reaching this executor.
|
|
2858
2944
|
return {
|
|
2859
2945
|
ok: false,
|
|
2860
2946
|
applicable: true,
|
|
2861
2947
|
decision: 'deny',
|
|
2862
|
-
reason: 'No active EvolCore Codex runner accepted this managed hook
|
|
2948
|
+
reason: 'No active EvolCore Codex runner accepted this managed hook credential',
|
|
2863
2949
|
};
|
|
2864
2950
|
});
|
|
2865
2951
|
const resolveManagedHandoffScope = async (params, operation) => {
|
|
@@ -3192,7 +3278,7 @@ async function main() {
|
|
|
3192
3278
|
const creationErrors = [];
|
|
3193
3279
|
const staged = candidate.config.enabled === false
|
|
3194
3280
|
? []
|
|
3195
|
-
: agentLoader.createForAgent(candidate, {
|
|
3281
|
+
: await agentLoader.createForAgent(candidate, {
|
|
3196
3282
|
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3197
3283
|
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3198
3284
|
},
|
|
@@ -3292,7 +3378,7 @@ async function main() {
|
|
|
3292
3378
|
throw new Error(agentRuntimeError);
|
|
3293
3379
|
}
|
|
3294
3380
|
const creationErrors = [];
|
|
3295
|
-
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
3381
|
+
const newAgentInstances = await agentLoader.createForAgent(agent, {
|
|
3296
3382
|
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3297
3383
|
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3298
3384
|
},
|
|
@@ -3483,7 +3569,7 @@ async function main() {
|
|
|
3483
3569
|
hotLoad: coreHotLoadAgent,
|
|
3484
3570
|
resync: coreResyncAgents,
|
|
3485
3571
|
create: async (options) => {
|
|
3486
|
-
const { agentCreateNonInteractive } = await import('./cli/agent.js');
|
|
3572
|
+
const { agentCreateNonInteractive } = await import('./cli/agent-create.js');
|
|
3487
3573
|
return await agentCreateNonInteractive(options, { hotLoadAgent: coreHotLoadAgent });
|
|
3488
3574
|
},
|
|
3489
3575
|
aunAids: () => channelInstances.flatMap((inst) => {
|
|
@@ -3732,12 +3818,20 @@ async function main() {
|
|
|
3732
3818
|
const aid = argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined;
|
|
3733
3819
|
if (!aid)
|
|
3734
3820
|
return fail('INVALID_ARGS', 'ec agent new requires an AID');
|
|
3735
|
-
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./cli/agent.js');
|
|
3821
|
+
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan, parseAgentOwnerArgs } = await import('./cli/agent-create.js');
|
|
3822
|
+
let ownerOptions;
|
|
3823
|
+
try {
|
|
3824
|
+
ownerOptions = parseAgentOwnerArgs(argv);
|
|
3825
|
+
}
|
|
3826
|
+
catch (error) {
|
|
3827
|
+
return fail('INVALID_ARGS', error instanceof Error ? error.message : String(error));
|
|
3828
|
+
}
|
|
3736
3829
|
const plan = resolveAgentCreatePlan({
|
|
3737
3830
|
aid,
|
|
3738
3831
|
project: value('--project'),
|
|
3739
3832
|
baseagent: value('--baseagent'),
|
|
3740
|
-
|
|
3833
|
+
...ownerOptions,
|
|
3834
|
+
handoff: value('--handoff'),
|
|
3741
3835
|
name: value('--name'),
|
|
3742
3836
|
description: value('--description'),
|
|
3743
3837
|
force: argv.includes('--force'),
|
|
@@ -3745,23 +3839,75 @@ async function main() {
|
|
|
3745
3839
|
const validationError = validateAgentCreatePlan(plan);
|
|
3746
3840
|
if (validationError)
|
|
3747
3841
|
return fail('INVALID_ARGS', validationError);
|
|
3842
|
+
if (argv.includes('--handoff')) {
|
|
3843
|
+
if (!delegation.grant.messageId)
|
|
3844
|
+
return fail('INVALID_ARGS', '--handoff requires an originating message');
|
|
3845
|
+
if (!plan.handoff?.trim())
|
|
3846
|
+
return fail('INVALID_ARGS', '--handoff requires non-empty context');
|
|
3847
|
+
if ((plan.owners?.[0] ?? plan.owner) !== taskAid) {
|
|
3848
|
+
return fail('INVALID_ARGS', '--handoff requires the creating Agent to be the first Owner');
|
|
3849
|
+
}
|
|
3850
|
+
if (plan.configExists)
|
|
3851
|
+
return fail('INVALID_ARGS', '--handoff requires a new Agent');
|
|
3852
|
+
}
|
|
3748
3853
|
if (argv.includes('--dry-run'))
|
|
3749
3854
|
return { ok: true, result: { ok: true, dryRun: true, plan } };
|
|
3855
|
+
let handoffId;
|
|
3856
|
+
if (plan.handoff) {
|
|
3857
|
+
try {
|
|
3858
|
+
const created = await handoffRuntime.createOutbound({
|
|
3859
|
+
selfAid: taskAid,
|
|
3860
|
+
to: aid,
|
|
3861
|
+
originSessionId: sessionId,
|
|
3862
|
+
originMessageId: delegation.grant.messageId,
|
|
3863
|
+
payload: { type: 'text', text: plan.handoff },
|
|
3864
|
+
encrypt: true,
|
|
3865
|
+
targetChatType: 'private',
|
|
3866
|
+
explicitReturnPolicy: 'required',
|
|
3867
|
+
contextOnly: true,
|
|
3868
|
+
originAuthorization: {
|
|
3869
|
+
actorId: delegation.grant.actorId,
|
|
3870
|
+
channelKey: delegation.grant.channel,
|
|
3871
|
+
channelType: delegation.grant.channelType,
|
|
3872
|
+
chatType: delegation.grant.chatType,
|
|
3873
|
+
peerKey: delegation.grant.peerKey,
|
|
3874
|
+
},
|
|
3875
|
+
causation: responseEngine.getTaskRuntimeContext(sessionId)?.causation,
|
|
3876
|
+
});
|
|
3877
|
+
handoffId = created.handoff?.handoff_id;
|
|
3878
|
+
if (!handoffId)
|
|
3879
|
+
return fail('INVALID_ARGS', '--handoff requires a different target session');
|
|
3880
|
+
}
|
|
3881
|
+
catch (error) {
|
|
3882
|
+
return fail('HANDOFF_PREPARE_FAILED', error instanceof Error ? error.message : String(error));
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3750
3885
|
const createOptions = {
|
|
3751
3886
|
aid: plan.aid,
|
|
3752
3887
|
project: plan.project,
|
|
3753
3888
|
baseagent: plan.baseagent,
|
|
3754
3889
|
owner: plan.owner,
|
|
3890
|
+
owners: plan.owners,
|
|
3755
3891
|
name: plan.name,
|
|
3756
3892
|
description: plan.description,
|
|
3757
3893
|
force: argv.includes('--force'),
|
|
3758
3894
|
};
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3895
|
+
try {
|
|
3896
|
+
const result = agentApplicationService
|
|
3897
|
+
? await agentApplicationService.create(createOptions)
|
|
3898
|
+
: await agentCreateNonInteractive(createOptions);
|
|
3899
|
+
if (!result.ok) {
|
|
3900
|
+
if (handoffId)
|
|
3901
|
+
handoffRuntime.store.discard(taskAid, handoffId, 'operation_failed');
|
|
3902
|
+
return fail(result.code ?? 'INTERNAL', result.error);
|
|
3903
|
+
}
|
|
3904
|
+
return { ok: true, result: { ...result, configPath: '[redacted]', ...(handoffId ? { handoff_id: handoffId } : {}) } };
|
|
3905
|
+
}
|
|
3906
|
+
catch (error) {
|
|
3907
|
+
if (handoffId)
|
|
3908
|
+
handoffRuntime.store.discard(taskAid, handoffId, 'operation_failed');
|
|
3909
|
+
return fail('INTERNAL', error instanceof Error ? error.message : String(error));
|
|
3910
|
+
}
|
|
3765
3911
|
}
|
|
3766
3912
|
if (operation === 'agent.delete') {
|
|
3767
3913
|
const result = agentApplicationService
|
package/dist/ipc.js
CHANGED
|
@@ -830,12 +830,21 @@ export class IpcServer {
|
|
|
830
830
|
|| typeof cmd.toolName !== 'string' || !cmd.toolName) {
|
|
831
831
|
return { ok: false, applicable: true, decision: 'deny', reason: 'invalid Codex hook request' };
|
|
832
832
|
}
|
|
833
|
+
// Unmanaged Codex processes carry no credential and are allowed
|
|
834
|
+
// without consulting any runner; see bin/codex-managed-hook.mjs.
|
|
835
|
+
const sessionToken = typeof cmd.sessionToken === 'string' && cmd.sessionToken
|
|
836
|
+
? cmd.sessionToken
|
|
837
|
+
: undefined;
|
|
838
|
+
if (!sessionToken) {
|
|
839
|
+
return { ok: true, applicable: true, decision: 'allow' };
|
|
840
|
+
}
|
|
833
841
|
try {
|
|
834
842
|
return await this.codexPreToolUseExecutor({
|
|
835
843
|
threadId: cmd.threadId,
|
|
836
844
|
toolName: cmd.toolName,
|
|
837
845
|
toolInput: cmd.toolInput,
|
|
838
846
|
callId: typeof cmd.callId === 'string' ? cmd.callId : undefined,
|
|
847
|
+
sessionToken,
|
|
839
848
|
signal: requestSignal,
|
|
840
849
|
});
|
|
841
850
|
}
|
|
@@ -1534,12 +1543,26 @@ export class IpcServer {
|
|
|
1534
1543
|
&& crypto.timingSafeEqual(provided, this.controlToken);
|
|
1535
1544
|
}
|
|
1536
1545
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1546
|
+
export class IpcTransportError extends Error {
|
|
1547
|
+
code;
|
|
1548
|
+
endpoint;
|
|
1549
|
+
constructor(code, endpoint) {
|
|
1550
|
+
const hint = code === 'EPERM' || code === 'EACCES'
|
|
1551
|
+
? '权限被拒绝;Windows 下请检查 daemon 与 CLI 是否使用相同用户和提升权限级别。'
|
|
1552
|
+
: code === 'ETIMEDOUT'
|
|
1553
|
+
? '等待 daemon 响应超时;请求可能已执行,请先检查状态再重试。'
|
|
1554
|
+
: code === 'ENOENT' || code === 'ECONNREFUSED'
|
|
1555
|
+
? 'daemon 未运行或 IPC 端点不存在,请先执行 ec start,再重试。'
|
|
1556
|
+
: '请检查 daemon 进程及 IPC 端点状态。';
|
|
1557
|
+
super(`daemon IPC 失败 (${code}): ${endpoint}。${hint}`);
|
|
1558
|
+
this.code = code;
|
|
1559
|
+
this.endpoint = endpoint;
|
|
1560
|
+
this.name = 'IpcTransportError';
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
/** Query the daemon; opt in to transport errors instead of the legacy null result. */
|
|
1541
1564
|
/** Pass `null` as timeoutMs to wait without a client-side deadline. */
|
|
1542
|
-
export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
1565
|
+
export async function ipcQuery(socketPath, cmd, timeoutMs = 3000, options = {}) {
|
|
1543
1566
|
const endpoint = normalizeIpcEndpoint(socketPath);
|
|
1544
1567
|
let socketCandidates = [endpoint];
|
|
1545
1568
|
if (isNamedPipe(endpoint)) {
|
|
@@ -1547,22 +1570,35 @@ export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
|
1547
1570
|
if (endpoint === paths.socket)
|
|
1548
1571
|
socketCandidates = getWindowsInstanceSocketPathCandidates(paths.root);
|
|
1549
1572
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1573
|
+
const startedAt = Date.now();
|
|
1574
|
+
let failure;
|
|
1575
|
+
for (const candidate of socketCandidates) {
|
|
1576
|
+
const remainingTimeout = timeoutMs === null
|
|
1577
|
+
? null
|
|
1578
|
+
: Math.max(0, timeoutMs - (Date.now() - startedAt));
|
|
1579
|
+
if (remainingTimeout === 0) {
|
|
1580
|
+
failure ??= new IpcTransportError('ETIMEDOUT', candidate);
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
try {
|
|
1560
1584
|
const response = await ipcQueryOnce(candidate, cmd, remainingTimeout);
|
|
1561
1585
|
if (response !== null)
|
|
1562
1586
|
return response;
|
|
1563
1587
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1588
|
+
catch (error) {
|
|
1589
|
+
if (!(error instanceof IpcTransportError))
|
|
1590
|
+
throw error;
|
|
1591
|
+
// A missing legacy pipe must not hide a permission failure on the canonical pipe.
|
|
1592
|
+
if (!failure || ['ENOENT', 'ECONNREFUSED'].includes(failure.code))
|
|
1593
|
+
failure = error;
|
|
1594
|
+
// Only connection failures are safe to retry against a legacy endpoint.
|
|
1595
|
+
if (!['ENOENT', 'ECONNREFUSED', 'EPERM', 'EACCES'].includes(error.code))
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (options.throwOnError && failure)
|
|
1600
|
+
throw failure;
|
|
1601
|
+
return null;
|
|
1566
1602
|
}
|
|
1567
1603
|
function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
1568
1604
|
let authenticatedCmd = cmd;
|
|
@@ -1587,7 +1623,7 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
|
1587
1623
|
? { daemonRuntimeEpoch: process.env[DAEMON_RUNTIME_EPOCH_ENV] }
|
|
1588
1624
|
: {}),
|
|
1589
1625
|
};
|
|
1590
|
-
return new Promise((resolve) => {
|
|
1626
|
+
return new Promise((resolve, reject) => {
|
|
1591
1627
|
const startedAtMono = performance.now();
|
|
1592
1628
|
const queryId = nextIpcQueryId(request);
|
|
1593
1629
|
const commandType = ipcLogToken(request.type);
|
|
@@ -1606,7 +1642,7 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
|
1606
1642
|
settled = true;
|
|
1607
1643
|
logPhase('timeout', ` timeout_ms=${timeoutMs}`);
|
|
1608
1644
|
conn.destroy();
|
|
1609
|
-
|
|
1645
|
+
reject(new IpcTransportError('ETIMEDOUT', socketPath));
|
|
1610
1646
|
}, timeoutMs);
|
|
1611
1647
|
conn.on('connect', () => {
|
|
1612
1648
|
if (settled)
|
|
@@ -1632,7 +1668,10 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
|
1632
1668
|
parse = 'invalid_json';
|
|
1633
1669
|
}
|
|
1634
1670
|
logPhase('response', ` bytes=${idx + 1} parse=${parse}`);
|
|
1635
|
-
|
|
1671
|
+
if (parse === 'invalid_json')
|
|
1672
|
+
reject(new IpcTransportError('EPROTO', socketPath));
|
|
1673
|
+
else
|
|
1674
|
+
resolve(response);
|
|
1636
1675
|
conn.destroy();
|
|
1637
1676
|
}
|
|
1638
1677
|
});
|
|
@@ -1643,7 +1682,15 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
|
1643
1682
|
if (timer)
|
|
1644
1683
|
clearTimeout(timer);
|
|
1645
1684
|
logPhase('error', ` code=${error.code ?? 'unknown'}`);
|
|
1646
|
-
|
|
1685
|
+
reject(new IpcTransportError(error.code ?? 'UNKNOWN', socketPath));
|
|
1686
|
+
});
|
|
1687
|
+
conn.on('close', () => {
|
|
1688
|
+
if (settled)
|
|
1689
|
+
return;
|
|
1690
|
+
settled = true;
|
|
1691
|
+
if (timer)
|
|
1692
|
+
clearTimeout(timer);
|
|
1693
|
+
reject(new IpcTransportError('ECONNRESET', socketPath));
|
|
1647
1694
|
});
|
|
1648
1695
|
});
|
|
1649
1696
|
}
|
package/dist/paths.js
CHANGED
|
@@ -129,11 +129,22 @@ export function agentHandoffDir(aid) {
|
|
|
129
129
|
return path.join(agentDataDir(aid), 'handoff');
|
|
130
130
|
}
|
|
131
131
|
export function agentOutboxPath(aid) {
|
|
132
|
-
return path.join(
|
|
132
|
+
return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'outbox.jsonl');
|
|
133
133
|
}
|
|
134
134
|
export function agentActivityOutboxPath(aid) {
|
|
135
|
+
return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'activity-outbox.jsonl');
|
|
136
|
+
}
|
|
137
|
+
/** Pre-v4 per-Agent outbox paths retained only for compatibility import. */
|
|
138
|
+
export function legacyAgentOutboxPath(aid) {
|
|
139
|
+
return path.join(agentDataDir(aid), 'outbox.jsonl');
|
|
140
|
+
}
|
|
141
|
+
export function legacyAgentActivityOutboxPath(aid) {
|
|
135
142
|
return path.join(agentDataDir(aid), 'activity-outbox.jsonl');
|
|
136
143
|
}
|
|
144
|
+
/** Earliest global outbox layout: data/outbox/<aid>.jsonl. */
|
|
145
|
+
export function legacyFlatOutboxPath(aid) {
|
|
146
|
+
return path.join(resolvePaths().outboxDir, `${aid}.jsonl`);
|
|
147
|
+
}
|
|
137
148
|
/** Directory-safe channel instance key used by connector runtime state. */
|
|
138
149
|
function encodePathKey(value) {
|
|
139
150
|
return encodeURIComponent(value);
|
|
@@ -283,6 +294,17 @@ function migrateFromAun() {
|
|
|
283
294
|
const markerPath = path.join(newRoot, MIGRATION_MARKER);
|
|
284
295
|
if (fs.existsSync(markerPath))
|
|
285
296
|
return;
|
|
297
|
+
// Test workers use isolated temporary homes. Copying the developer's
|
|
298
|
+
// machine-wide ~/.aun tree into every fresh fixture is both unnecessary and
|
|
299
|
+
// can make process-boundary tests exceed their timeout. Production roots
|
|
300
|
+
// retain the normal one-time migration behavior.
|
|
301
|
+
if (process.env.EVOLCORE_TEST === '1') {
|
|
302
|
+
try {
|
|
303
|
+
fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
|
|
304
|
+
}
|
|
305
|
+
catch { }
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
286
308
|
const oldRoot = path.join(os.homedir(), '.aun');
|
|
287
309
|
if (!fs.existsSync(oldRoot) || oldRoot === newRoot) {
|
|
288
310
|
try {
|
package/dist/stats/billing.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* billing.ts — 读 model-prices.jsonl
|
|
2
|
+
* billing.ts — 读 model-prices.jsonl,按模型参数中的 billing_fn 计算费用。
|
|
3
3
|
* 费用查询时实时计算,不存入 DB(价格可能变动)。
|
|
4
4
|
*/
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { getPackageRoot } from '../paths.js';
|
|
8
|
-
//
|
|
8
|
+
// 模型参数缓存(进程内,5 分钟 TTL)
|
|
9
|
+
let _parameterCache = null;
|
|
10
|
+
let _parameterCacheTs = 0;
|
|
9
11
|
let _priceCache = null;
|
|
10
12
|
let _priceCacheTs = 0;
|
|
11
13
|
const PRICE_CACHE_TTL = 5 * 60 * 1000;
|
|
@@ -44,6 +46,14 @@ function _loadJsonlMerged(evolcoreHome, filename) {
|
|
|
44
46
|
}
|
|
45
47
|
return results;
|
|
46
48
|
}
|
|
49
|
+
function loadModelParameters(evolcoreHome) {
|
|
50
|
+
if (_parameterCache && Date.now() - _parameterCacheTs < PRICE_CACHE_TTL)
|
|
51
|
+
return _parameterCache;
|
|
52
|
+
const params = _loadJsonlMerged(evolcoreHome, 'model-prices.jsonl');
|
|
53
|
+
_parameterCache = params;
|
|
54
|
+
_parameterCacheTs = Date.now();
|
|
55
|
+
return params;
|
|
56
|
+
}
|
|
47
57
|
let _aliasCache = null;
|
|
48
58
|
let _aliasCacheTs = 0;
|
|
49
59
|
function loadAliases(evolcoreHome) {
|
|
@@ -64,7 +74,8 @@ function loadPrices(evolcoreHome) {
|
|
|
64
74
|
const now = Date.now();
|
|
65
75
|
if (_priceCache && now - _priceCacheTs < PRICE_CACHE_TTL)
|
|
66
76
|
return _priceCache;
|
|
67
|
-
_priceCache =
|
|
77
|
+
_priceCache = loadModelParameters(evolcoreHome).filter((record) => (typeof record.billing_fn === 'string'
|
|
78
|
+
&& (record.currency === 'USD' || record.currency === 'CNY')));
|
|
68
79
|
_priceCacheTs = now;
|
|
69
80
|
return _priceCache;
|
|
70
81
|
}
|
|
@@ -140,13 +151,16 @@ export function calcCost(evolcoreHome, event) {
|
|
|
140
151
|
return {};
|
|
141
152
|
return fn(event, priceRow);
|
|
142
153
|
}
|
|
154
|
+
// ── Model Specs ──────────────────────────────────────────────────────────────
|
|
143
155
|
let _specCache = null;
|
|
144
156
|
let _specCacheTs = 0;
|
|
145
157
|
function loadSpecs(evolcoreHome) {
|
|
146
158
|
const now = Date.now();
|
|
147
159
|
if (_specCache && now - _specCacheTs < PRICE_CACHE_TTL)
|
|
148
160
|
return _specCache;
|
|
149
|
-
_specCache =
|
|
161
|
+
_specCache = loadModelParameters(evolcoreHome).filter((record) => (typeof record.context_window === 'number'
|
|
162
|
+
&& typeof record.max_input_tokens === 'number'
|
|
163
|
+
&& typeof record.max_output_tokens === 'number'));
|
|
150
164
|
_specCacheTs = now;
|
|
151
165
|
return _specCache;
|
|
152
166
|
}
|
|
@@ -69,8 +69,32 @@ export function atomicWrite(filePath, content) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
// The final rename atomically replaces the old target without an absent-path
|
|
72
|
-
// window.
|
|
73
|
-
|
|
72
|
+
// window. Windows H-class projections can keep the destination pathname
|
|
73
|
+
// open while a sandbox is starting; replacing that directory entry then
|
|
74
|
+
// fails with EPERM. In that narrow case, preserve the target inode and write
|
|
75
|
+
// in place. The hot backup above still provides the previous full version.
|
|
76
|
+
try {
|
|
77
|
+
fs.renameSync(tmp, filePath);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const code = error.code;
|
|
81
|
+
if (process.platform !== 'win32' || code !== 'EPERM')
|
|
82
|
+
throw error;
|
|
83
|
+
const detachedHot = `${hot}.${process.pid}.fallback.tmp`;
|
|
84
|
+
try {
|
|
85
|
+
// refreshHotBackup may have used a hard link. Detach that backup before
|
|
86
|
+
// the in-place write, otherwise updating the live inode would update the
|
|
87
|
+
// hot backup as well.
|
|
88
|
+
fs.copyFileSync(filePath, detachedHot);
|
|
89
|
+
fs.rmSync(hot, { force: true });
|
|
90
|
+
fs.renameSync(detachedHot, hot);
|
|
91
|
+
stableWrite(filePath, content);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
fs.rmSync(detachedHot, { force: true });
|
|
95
|
+
fs.rmSync(tmp, { force: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
74
98
|
}
|
|
75
99
|
export function atomicWriteJson(filePath, value) {
|
|
76
100
|
atomicWrite(filePath, JSON.stringify(value, null, 2) + '\n');
|