evolcore 0.0.16 → 0.0.18
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 +46 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +132 -14
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +21 -6
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +23 -9
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +53 -12
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/task-context.js +46 -1
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +72 -9
- package/dist/core/message/pause-controller.js +53 -0
- package/dist/core/message/response-engine.js +98 -11
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +1 -1
- package/dist/index.js +357 -48
- package/dist/ipc.js +75 -4
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/error-utils.js +38 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
package/dist/ipc.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
2
3
|
import fs from 'fs';
|
|
3
4
|
import crypto from 'crypto';
|
|
4
5
|
import os from 'os';
|
|
5
6
|
import path from 'path';
|
|
6
7
|
import { logger } from './utils/logger.js';
|
|
8
|
+
import { withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
|
|
7
9
|
import { fileCache } from './core/daemon-file-cache.js';
|
|
8
10
|
import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
|
|
9
11
|
import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
|
|
@@ -27,11 +29,14 @@ const LOCAL_CONTROL_COMMANDS = new Set([
|
|
|
27
29
|
'wechat.contact-bind.register',
|
|
28
30
|
'queue-snapshot',
|
|
29
31
|
'aun-aid-stats-record-outbound',
|
|
32
|
+
'evolagent.list',
|
|
33
|
+
'evolagent.show',
|
|
30
34
|
'evolagent.reload',
|
|
31
35
|
'evolagent.load',
|
|
32
36
|
'evolagent.resync',
|
|
33
37
|
'menu.exec',
|
|
34
38
|
'monitor-snapshot',
|
|
39
|
+
'shutdown',
|
|
35
40
|
]);
|
|
36
41
|
function requiresLocalControlToken(cmd) {
|
|
37
42
|
// Managed bootstrap completion is authenticated by the session delegation
|
|
@@ -61,6 +66,7 @@ export class IpcServer {
|
|
|
61
66
|
aunAidStatsProvider;
|
|
62
67
|
aunAidStatsRecorder;
|
|
63
68
|
menuExecutor;
|
|
69
|
+
agentOperationExecutor;
|
|
64
70
|
statsProvider;
|
|
65
71
|
agentStatsProvider;
|
|
66
72
|
queueSnapshotProvider;
|
|
@@ -71,6 +77,7 @@ export class IpcServer {
|
|
|
71
77
|
triggerExecutor;
|
|
72
78
|
bootstrapCompleteExecutor;
|
|
73
79
|
agentReloadExecutor;
|
|
80
|
+
shutdownExecutor;
|
|
74
81
|
taskRuntimeContextProvider;
|
|
75
82
|
codexPreToolUseExecutor;
|
|
76
83
|
aunMsgSender;
|
|
@@ -121,6 +128,9 @@ export class IpcServer {
|
|
|
121
128
|
setMenuExecutor(executor) {
|
|
122
129
|
this.menuExecutor = executor;
|
|
123
130
|
}
|
|
131
|
+
setAgentOperationExecutor(executor) {
|
|
132
|
+
this.agentOperationExecutor = executor;
|
|
133
|
+
}
|
|
124
134
|
setConfigOperationExecutor(executor) {
|
|
125
135
|
this.configOperationExecutor = executor;
|
|
126
136
|
}
|
|
@@ -142,6 +152,10 @@ export class IpcServer {
|
|
|
142
152
|
setAgentReloadExecutor(executor) {
|
|
143
153
|
this.agentReloadExecutor = executor;
|
|
144
154
|
}
|
|
155
|
+
/** Inject the daemon's graceful shutdown lifecycle for local stop/restart. */
|
|
156
|
+
setShutdownExecutor(executor) {
|
|
157
|
+
this.shutdownExecutor = executor;
|
|
158
|
+
}
|
|
145
159
|
/** Inject AUN AID state aggregator for aun-aids IPC handler */
|
|
146
160
|
setAunAidProvider(provider) {
|
|
147
161
|
this.aunAidProvider = provider;
|
|
@@ -332,23 +346,31 @@ export class IpcServer {
|
|
|
332
346
|
return new Promise((resolve, reject) => {
|
|
333
347
|
this.server = net.createServer((conn) => {
|
|
334
348
|
let buf = '';
|
|
349
|
+
let handled = false;
|
|
350
|
+
let requestReceivedAtMono;
|
|
351
|
+
const requestAbort = new AbortController();
|
|
335
352
|
conn.on('data', async (data) => {
|
|
353
|
+
if (handled)
|
|
354
|
+
return;
|
|
355
|
+
requestReceivedAtMono ??= performance.now();
|
|
336
356
|
buf += data.toString();
|
|
337
357
|
// Simple newline-delimited JSON protocol
|
|
338
358
|
const idx = buf.indexOf('\n');
|
|
339
359
|
if (idx === -1)
|
|
340
360
|
return;
|
|
361
|
+
handled = true;
|
|
341
362
|
const line = buf.slice(0, idx);
|
|
342
363
|
buf = buf.slice(idx + 1);
|
|
343
364
|
try {
|
|
344
365
|
const cmd = JSON.parse(line);
|
|
345
|
-
const response = await this.handleCommand(cmd);
|
|
366
|
+
const response = await this.handleCommand(cmd, requestAbort.signal, requestReceivedAtMono);
|
|
346
367
|
conn.end(JSON.stringify(response) + '\n');
|
|
347
368
|
}
|
|
348
369
|
catch {
|
|
349
370
|
conn.end(JSON.stringify({ error: 'invalid request' }) + '\n');
|
|
350
371
|
}
|
|
351
372
|
});
|
|
373
|
+
conn.on('close', () => requestAbort.abort('Codex hook client disconnected'));
|
|
352
374
|
conn.on('error', () => { }); // ignore client errors
|
|
353
375
|
});
|
|
354
376
|
const onListenError = (err) => {
|
|
@@ -385,7 +407,7 @@ export class IpcServer {
|
|
|
385
407
|
catch { }
|
|
386
408
|
}
|
|
387
409
|
}
|
|
388
|
-
async handleCommand(cmd) {
|
|
410
|
+
async handleCommand(cmd, requestSignal, receivedAtMono = performance.now()) {
|
|
389
411
|
if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
|
|
390
412
|
return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
|
|
391
413
|
}
|
|
@@ -405,6 +427,7 @@ export class IpcServer {
|
|
|
405
427
|
threadId: cmd.threadId,
|
|
406
428
|
toolName: cmd.toolName,
|
|
407
429
|
toolInput: cmd.toolInput,
|
|
430
|
+
signal: requestSignal,
|
|
408
431
|
});
|
|
409
432
|
}
|
|
410
433
|
catch (error) {
|
|
@@ -413,6 +436,23 @@ export class IpcServer {
|
|
|
413
436
|
}
|
|
414
437
|
case 'ping':
|
|
415
438
|
return { pong: true, pid: process.pid, protocolVersion: 1 };
|
|
439
|
+
case 'shutdown': {
|
|
440
|
+
if (!this.shutdownExecutor)
|
|
441
|
+
return { ok: false, error: 'shutdown executor not configured' };
|
|
442
|
+
if (Number.isInteger(cmd.expectedPid) && cmd.expectedPid !== process.pid) {
|
|
443
|
+
return { ok: false, code: 'PID_MISMATCH', error: 'daemon pid changed before shutdown request' };
|
|
444
|
+
}
|
|
445
|
+
const reason = typeof cmd.reason === 'string' && cmd.reason.trim()
|
|
446
|
+
? cmd.reason.trim().slice(0, 80)
|
|
447
|
+
: 'ipc';
|
|
448
|
+
// Let the response flush before shutdown closes the IPC server/process.
|
|
449
|
+
setImmediate(() => {
|
|
450
|
+
Promise.resolve(this.shutdownExecutor?.(reason)).catch(error => {
|
|
451
|
+
logger.error(`[IPC] graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
return { ok: true, accepted: true, pid: process.pid };
|
|
455
|
+
}
|
|
416
456
|
case 'bind.begin': {
|
|
417
457
|
if (!this.bindExecutor)
|
|
418
458
|
return { ok: false, error: 'bind executor not configured' };
|
|
@@ -833,6 +873,18 @@ export class IpcServer {
|
|
|
833
873
|
}
|
|
834
874
|
return await this.fsOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
835
875
|
}
|
|
876
|
+
case 'agent.op': {
|
|
877
|
+
if (!this.agentOperationExecutor)
|
|
878
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'agent.op not configured' };
|
|
879
|
+
const { argv, sessionId, delegationToken } = cmd;
|
|
880
|
+
if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
|
|
881
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
882
|
+
}
|
|
883
|
+
if (delegationToken !== undefined && typeof delegationToken !== 'string') {
|
|
884
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
|
|
885
|
+
}
|
|
886
|
+
return await this.agentOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
887
|
+
}
|
|
836
888
|
case 'trigger.list':
|
|
837
889
|
case 'trigger.show':
|
|
838
890
|
case 'trigger.history':
|
|
@@ -944,17 +996,36 @@ export class IpcServer {
|
|
|
944
996
|
}
|
|
945
997
|
}
|
|
946
998
|
case 'menu.exec': {
|
|
947
|
-
|
|
999
|
+
const request = cmd.payload && typeof cmd.payload === 'object' && !Array.isArray(cmd.payload)
|
|
1000
|
+
? cmd.payload
|
|
1001
|
+
: { type: 'menu.invalid' };
|
|
1002
|
+
const flowContext = {
|
|
1003
|
+
source: 'ecweb-ipc',
|
|
1004
|
+
};
|
|
1005
|
+
logMenuRequestReceived(request, flowContext);
|
|
1006
|
+
if (!this.menuExecutor) {
|
|
1007
|
+
logMenuRequestCompleted(request, undefined, flowContext, {
|
|
1008
|
+
delivery: 'dropped',
|
|
1009
|
+
reason: 'menu-executor-not-configured',
|
|
1010
|
+
});
|
|
948
1011
|
return { ok: false, error: 'menu.exec not configured' };
|
|
1012
|
+
}
|
|
949
1013
|
try {
|
|
950
1014
|
const response = await this.menuExecutor(cmd.payload, {
|
|
951
1015
|
source: 'ecweb',
|
|
952
1016
|
actorAid: null,
|
|
953
1017
|
localDirect: true,
|
|
954
1018
|
});
|
|
955
|
-
|
|
1019
|
+
const timedResponse = withMenuProcessingTime(response, { receivedAtMono });
|
|
1020
|
+
logMenuRequestCompleted(request, timedResponse, flowContext, { delivery: 'returned' });
|
|
1021
|
+
return { ok: true, response: timedResponse };
|
|
956
1022
|
}
|
|
957
1023
|
catch (e) {
|
|
1024
|
+
logMenuRequestCompleted(request, undefined, flowContext, {
|
|
1025
|
+
delivery: 'failed',
|
|
1026
|
+
reason: 'handler-error',
|
|
1027
|
+
transportError: e,
|
|
1028
|
+
});
|
|
958
1029
|
return { ok: false, error: e?.message ?? String(e) };
|
|
959
1030
|
}
|
|
960
1031
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Atomic write
|
|
2
|
+
* Atomic write with a hot backup.
|
|
3
3
|
*
|
|
4
4
|
* 写入流程(写 foo.json):
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. 把新内容写到 foo.json__ (写入完成才有完整内容)
|
|
6
|
+
* 2. 保留当前 foo.json 为 foo.json_ (不移走正式文件)
|
|
7
7
|
* 3. 把 foo.json__ rename 为 foo.json (原子切换)
|
|
8
8
|
* foo.json_ 留到下次写入时被覆盖。
|
|
9
9
|
*
|
|
@@ -17,8 +17,32 @@ import fs from 'fs';
|
|
|
17
17
|
import path from 'path';
|
|
18
18
|
const HOT = '_'; // foo.json_ 旧版热备
|
|
19
19
|
const TMP = '__'; // foo.json__ 写入中
|
|
20
|
+
const HOT_BACKUP_FALLBACK_CODES = new Set(['EACCES', 'ENOSYS', 'ENOTSUP', 'EPERM', 'EXDEV']);
|
|
21
|
+
/** Preserve the previous version without making the live path disappear. */
|
|
22
|
+
function refreshHotBackup(filePath, hotPath) {
|
|
23
|
+
const backupTmp = `${hotPath}.${process.pid}.tmp`;
|
|
24
|
+
try {
|
|
25
|
+
fs.rmSync(backupTmp, { force: true });
|
|
26
|
+
try {
|
|
27
|
+
// A hard link is exact and avoids copying large protected state files.
|
|
28
|
+
fs.linkSync(filePath, backupTmp);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const code = error.code;
|
|
32
|
+
if (!HOT_BACKUP_FALLBACK_CODES.has(code ?? ''))
|
|
33
|
+
throw error;
|
|
34
|
+
fs.copyFileSync(filePath, backupTmp);
|
|
35
|
+
}
|
|
36
|
+
// The live target remains untouched until the new file is ready.
|
|
37
|
+
fs.rmSync(hotPath, { force: true });
|
|
38
|
+
fs.renameSync(backupTmp, hotPath);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
fs.rmSync(backupTmp, { force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
20
44
|
/**
|
|
21
|
-
* 原子写入 JSON
|
|
45
|
+
* 原子写入 JSON 文件(临时文件 + 热备)。dirname 不存在时会自动创建。
|
|
22
46
|
*
|
|
23
47
|
* @param filePath 目标文件
|
|
24
48
|
* @param content 完整内容(不含末尾换行也可,由调用方控制)
|
|
@@ -27,15 +51,25 @@ export function atomicWrite(filePath, content) {
|
|
|
27
51
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
28
52
|
const hot = filePath + HOT;
|
|
29
53
|
const tmp = filePath + TMP;
|
|
30
|
-
//
|
|
54
|
+
// Write the complete new version before changing either the live target or
|
|
55
|
+
// its backup. This keeps the live path present throughout the update, which
|
|
56
|
+
// matters to readers that project protected files into a sandbox.
|
|
57
|
+
fs.writeFileSync(tmp, content, 'utf-8');
|
|
58
|
+
// Preserve the old version while leaving the live target in place. A writer
|
|
59
|
+
// in another process may remove the target between existsSync and linkSync;
|
|
60
|
+
// in that case the new version still replaces it below and the old backup is
|
|
61
|
+
// simply unavailable for this write.
|
|
31
62
|
if (fs.existsSync(filePath)) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
try {
|
|
64
|
+
refreshHotBackup(filePath, hot);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code !== 'ENOENT')
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
35
70
|
}
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
// 步骤 3:tmp → 目标(原子)
|
|
71
|
+
// The final rename atomically replaces the old target without an absent-path
|
|
72
|
+
// window.
|
|
39
73
|
fs.renameSync(tmp, filePath);
|
|
40
74
|
}
|
|
41
75
|
export function atomicWriteJson(filePath, value) {
|
|
@@ -3,6 +3,32 @@ import path from 'path';
|
|
|
3
3
|
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
4
4
|
import { logger } from './logger.js';
|
|
5
5
|
import { isSandboxInitializationFailure } from '../core/permission/sandbox-runtime.js';
|
|
6
|
+
export const CLAUDE_STARTUP_ARGUMENTS_TOO_LONG = 'claude_startup_arguments_too_long';
|
|
7
|
+
/** Identify a Claude process-start failure without treating ordinary tool errors as startup failures. */
|
|
8
|
+
export function isClaudeStartupArgumentsTooLong(error) {
|
|
9
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
10
|
+
? String(error.code ?? '')
|
|
11
|
+
: '';
|
|
12
|
+
const message = error && typeof error === 'object' && 'message' in error
|
|
13
|
+
? String(error.message ?? '')
|
|
14
|
+
: String(error ?? '');
|
|
15
|
+
const text = `${code}\n${message}`.toLowerCase();
|
|
16
|
+
return text.includes(CLAUDE_STARTUP_ARGUMENTS_TOO_LONG)
|
|
17
|
+
|| text.includes('enametoolong')
|
|
18
|
+
|| text.includes('e2big')
|
|
19
|
+
|| text.includes('argument list too long')
|
|
20
|
+
|| text.includes('command line too long');
|
|
21
|
+
}
|
|
22
|
+
export function createClaudeStartupArgumentsTooLongError(error) {
|
|
23
|
+
const wrapped = new Error(`${CLAUDE_STARTUP_ARGUMENTS_TOO_LONG}: Claude 启动参数过长,任务未执行`);
|
|
24
|
+
wrapped.name = 'ClaudeStartupArgumentsTooLongError';
|
|
25
|
+
wrapped.code = CLAUDE_STARTUP_ARGUMENTS_TOO_LONG;
|
|
26
|
+
// Keep the original error available to diagnostics without echoing argv,
|
|
27
|
+
// settings contents, or absolute paths to users.
|
|
28
|
+
if (error !== undefined)
|
|
29
|
+
wrapped.cause = error;
|
|
30
|
+
return wrapped;
|
|
31
|
+
}
|
|
6
32
|
export var ErrorType;
|
|
7
33
|
(function (ErrorType) {
|
|
8
34
|
ErrorType["SDK_TIMEOUT"] = "sdk_timeout";
|
|
@@ -13,6 +39,7 @@ export var ErrorType;
|
|
|
13
39
|
ErrorType["CONTEXT_TOO_LONG"] = "context_too_long";
|
|
14
40
|
ErrorType["MODEL_UNAVAILABLE"] = "model_unavailable";
|
|
15
41
|
ErrorType["SANDBOX_INITIALIZATION_FAILED"] = "sandbox_initialization_failed";
|
|
42
|
+
ErrorType["CLAUDE_STARTUP_ARGUMENTS_TOO_LONG"] = "claude_startup_arguments_too_long";
|
|
16
43
|
ErrorType["UNKNOWN"] = "unknown";
|
|
17
44
|
})(ErrorType || (ErrorType = {}));
|
|
18
45
|
/**
|
|
@@ -251,6 +278,11 @@ function hasRetryableHttpStatus(text) {
|
|
|
251
278
|
}
|
|
252
279
|
export function classifyError(error) {
|
|
253
280
|
const msg = (error?.message || '').toLowerCase();
|
|
281
|
+
// Startup-size failures are deterministic local failures and must win over
|
|
282
|
+
// dictionary rules that would otherwise turn them into generic retries.
|
|
283
|
+
if (isClaudeStartupArgumentsTooLong(error)) {
|
|
284
|
+
return ErrorType.CLAUDE_STARTUP_ARGUMENTS_TOO_LONG;
|
|
285
|
+
}
|
|
254
286
|
// 字典优先 — 命中则直接返回
|
|
255
287
|
const rule = matchErrorRule(msg);
|
|
256
288
|
if (rule) {
|
|
@@ -314,6 +346,10 @@ export function isRetryableError(error) {
|
|
|
314
346
|
return false;
|
|
315
347
|
}
|
|
316
348
|
export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
349
|
+
if (isClaudeStartupArgumentsTooLong(error)) {
|
|
350
|
+
const prefix = includeEmoji ? '⚠️ ' : '';
|
|
351
|
+
return `${prefix}当前 Claude 启动参数过长,任务未执行`;
|
|
352
|
+
}
|
|
317
353
|
// terminalReason 提供更精确的错误提示(SDK 0.2.100+)
|
|
318
354
|
if (terminalReason) {
|
|
319
355
|
const prefix = includeEmoji ? '❌ ' : '';
|
|
@@ -335,6 +371,8 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
335
371
|
return `${prefix}权限被拒绝,操作已取消`;
|
|
336
372
|
case 'sandbox_initialization_failed':
|
|
337
373
|
return `${warnPrefix}只读执行沙箱启动失败(sandbox_initialization_failed),当前任务未执行;请检查 user namespace/seccomp 配置后重试`;
|
|
374
|
+
case 'claude_startup_arguments_too_long':
|
|
375
|
+
return `${warnPrefix}当前 Claude 启动参数过长,任务未执行`;
|
|
338
376
|
case 'aborted_streaming':
|
|
339
377
|
case 'aborted_tools':
|
|
340
378
|
return `${prefix}任务已中断`;
|
package/dist/utils/logger.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import { resolvePaths } from '../paths.js';
|
|
3
3
|
import { LogWriter } from './log-writer.js';
|
|
4
4
|
import { classifyToolErrorCode } from '../core/permission/tool-error-code.js';
|
|
5
|
+
import { buildToolLifecycleEventKey } from '../core/audit/event-key.js';
|
|
5
6
|
let currentLevel = process.env.LOG_LEVEL || 'INFO';
|
|
6
7
|
const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
|
|
7
8
|
const config = {
|
|
@@ -14,6 +15,7 @@ function closeWriters(current) {
|
|
|
14
15
|
current.main.close();
|
|
15
16
|
current.message?.close();
|
|
16
17
|
current.event?.close();
|
|
18
|
+
current.menu.close();
|
|
17
19
|
current.channelIn.close();
|
|
18
20
|
current.channelOut.close();
|
|
19
21
|
}
|
|
@@ -32,6 +34,7 @@ function getWriters() {
|
|
|
32
34
|
event: config.eventLog
|
|
33
35
|
? new LogWriter({ baseName: 'events', logDir, rotation: 'hourly', retention: { hours: 48 } })
|
|
34
36
|
: null,
|
|
37
|
+
menu: new LogWriter({ baseName: 'menu-protocol', logDir, rotation: 'hourly', retention: { hours: 48 } }),
|
|
35
38
|
channelIn: new LogWriter({ baseName: 'channel-in', logDir, rotation: 'hourly', retention: { hours: 48 } }),
|
|
36
39
|
channelOut: new LogWriter({ baseName: 'channel-out', logDir, rotation: 'hourly', retention: { hours: 48 } }),
|
|
37
40
|
};
|
|
@@ -48,6 +51,8 @@ export function normalizeStructuredLog(data) {
|
|
|
48
51
|
return data;
|
|
49
52
|
const nested = data.event && typeof data.event === 'object' ? data.event : undefined;
|
|
50
53
|
const eventType = data.type ?? nested?.type;
|
|
54
|
+
const eventKey = data.eventKey ?? data.event_key ?? nested?.eventKey ?? nested?.event_key;
|
|
55
|
+
const eventPhase = data.eventPhase ?? data.event_phase ?? nested?.eventPhase ?? nested?.event_phase;
|
|
51
56
|
const correlationId = data.correlationId
|
|
52
57
|
?? data.correlation_id
|
|
53
58
|
?? data.callId
|
|
@@ -80,6 +85,11 @@ export function normalizeStructuredLog(data) {
|
|
|
80
85
|
?? (isToolResult && isError === false ? 'allow' : undefined)
|
|
81
86
|
?? (isToolUse ? 'started' : undefined)
|
|
82
87
|
?? nested?.decision;
|
|
88
|
+
const executed = data.executed ?? nested?.executed
|
|
89
|
+
?? (isToolResult ? true : isToolUse ? false : undefined);
|
|
90
|
+
const executionState = data.executionState ?? data.execution_state
|
|
91
|
+
?? nested?.executionState ?? nested?.execution_state
|
|
92
|
+
?? (isToolResult ? (isError === true ? 'failed' : 'completed') : isToolUse ? 'requested' : undefined);
|
|
83
93
|
const explicitErrorCode = data.errorCode
|
|
84
94
|
?? data.error_code
|
|
85
95
|
?? nested?.errorCode
|
|
@@ -88,17 +98,29 @@ export function normalizeStructuredLog(data) {
|
|
|
88
98
|
? classifyToolErrorCode({ errorCode: explicitErrorCode, error, result })
|
|
89
99
|
: explicitErrorCode;
|
|
90
100
|
const lifecycleRecord = isToolUse || isToolResult;
|
|
101
|
+
const lifecycleEventKey = lifecycleRecord
|
|
102
|
+
? (eventKey ?? buildToolLifecycleEventKey({
|
|
103
|
+
sessionId,
|
|
104
|
+
callId: data.callId ?? data.call_id ?? data.toolUseId ?? data.tool_use_id ?? nested?.callId ?? nested?.call_id,
|
|
105
|
+
correlationId: data.correlationId ?? data.correlation_id ?? nested?.correlationId ?? nested?.correlation_id,
|
|
106
|
+
requestId: data.requestId ?? data.request_id ?? nested?.requestId ?? nested?.request_id,
|
|
107
|
+
}))
|
|
108
|
+
: undefined;
|
|
91
109
|
const missingContextFields = lifecycleRecord
|
|
92
110
|
? ['sessionId', 'agentAid', 'permissionMode'].filter(field => ({ sessionId, agentAid, permissionMode }[field] == null))
|
|
93
111
|
: [];
|
|
94
112
|
return {
|
|
95
113
|
...data,
|
|
114
|
+
...(lifecycleEventKey ? { eventKey: lifecycleEventKey } : {}),
|
|
115
|
+
...(eventPhase ? { eventPhase } : {}),
|
|
96
116
|
...(correlationId ? { correlationId } : {}),
|
|
97
117
|
...(sessionId ? { sessionId } : lifecycleRecord ? { sessionId: 'unknown' } : {}),
|
|
98
118
|
...(agentAid ? { agentAid } : lifecycleRecord ? { agentAid: 'unknown' } : {}),
|
|
99
119
|
...(permissionMode ? { permissionMode } : lifecycleRecord ? { permissionMode: 'unknown' } : {}),
|
|
100
120
|
...(toolName ? { toolName } : {}),
|
|
101
121
|
...(decision ? { decision } : {}),
|
|
122
|
+
...(executed !== undefined ? { executed } : {}),
|
|
123
|
+
...(executionState ? { executionState } : {}),
|
|
102
124
|
...(errorCode ? { errorCode } : {}),
|
|
103
125
|
...(missingContextFields.length > 0 ? { contextMissing: missingContextFields } : {}),
|
|
104
126
|
};
|
|
@@ -144,6 +166,11 @@ export const logger = {
|
|
|
144
166
|
return;
|
|
145
167
|
writer.write(JSON.stringify({ ts: localTimestamp(), ...normalizeStructuredLog(data) }));
|
|
146
168
|
},
|
|
169
|
+
menu: (data) => {
|
|
170
|
+
// Menu has a deliberately compact domain schema. Do not expand it with
|
|
171
|
+
// channel/tool lifecycle aliases used by the generic structured streams.
|
|
172
|
+
getWriters().menu.write(JSON.stringify({ ts: localTimestamp(), ...data }));
|
|
173
|
+
},
|
|
147
174
|
channelIn: (data) => {
|
|
148
175
|
getWriters().channelIn.write(JSON.stringify({ ts: localTimestamp(), ...normalizeStructuredLog(data) }));
|
|
149
176
|
},
|