evolcore 0.0.14 → 0.0.16
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 +29 -0
- package/README.md +1 -0
- package/dist/agents/claude-runner.js +119 -29
- package/dist/agents/codex-runner.js +39 -10
- package/dist/agents/ecagent-runner.js +4 -1
- package/dist/aun/msg/p2p.js +5 -5
- package/dist/channels/aun.js +73 -20
- package/dist/channels/daemon.js +15 -9
- package/dist/channels/feishu.js +6 -1
- package/dist/cli/init.js +7 -3
- package/dist/cli/task-context.js +8 -4
- package/dist/config/config-manager.js +1 -0
- package/dist/core/audit/log-integrity.js +149 -0
- package/dist/core/auth/authorization-audit.js +73 -3
- package/dist/core/command/slash-handler.js +1 -1
- package/dist/core/event-catalog.js +1 -0
- package/dist/core/message/response-engine.js +35 -21
- package/dist/core/permission/approval-gateway.js +99 -16
- package/dist/core/permission/tool-error-code.js +47 -0
- package/dist/core/permission/tool-policy.js +61 -2
- package/dist/index.js +8 -4
- package/dist/ipc.js +11 -6
- package/dist/paths.js +18 -1
- package/dist/trigger/scheduler.js +5 -4
- package/dist/utils/cross-platform.js +1 -24
- package/dist/utils/instance-registry.js +35 -27
- package/dist/utils/logger.js +41 -10
- package/dist/utils/windows-autostart.js +50 -9
- package/dist/utils/windows-output.js +36 -0
- package/kits/docs/evolcore/config.md +1 -0
- package/kits/schemas/agent-config.schema.10.json +6 -0
- package/kits/schemas/daemon.schema.1.json +1 -1
- package/kits/schemas/daemon.schema.2.json +1 -1
- package/kits/schemas/daemon.schema.3.json +1 -1
- package/kits/schemas/daemon.schema.4.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## 0.0.16 (2026-08-25)
|
|
7
|
+
|
|
8
|
+
### 任务与消息可靠性
|
|
9
|
+
|
|
10
|
+
- 统一任务总执行时限与 AUN 附件投递重试边界,补充可恢复失败的退避与错误上下文,避免无配置时误设总超时。
|
|
11
|
+
- 优化会话响应、事件总线和守护进程生命周期,完善任务结束、重试和清理时的状态收敛。
|
|
12
|
+
|
|
13
|
+
### Windows 与运行兼容性
|
|
14
|
+
|
|
15
|
+
- 强化 Windows 进程识别、登录自启状态探测和命令输出解码,避免误识别其它 Node 服务并改善失败诊断。
|
|
16
|
+
- 修正 Windows Claude 沙箱 settings 的受管临时文件生命周期,收紧运行目录校验,并为过长的 Unix socket 路径提供隔离的短路径回退。
|
|
17
|
+
|
|
18
|
+
### 审计与权限边界
|
|
19
|
+
|
|
20
|
+
- 增强结构化日志完整性检查、生命周期关联和重复记录识别,统一工具错误分类与审计上下文。
|
|
21
|
+
- 收紧只读诊断、受保护路径和运行时锁访问策略,仅允许显式授权的有界源码诊断。
|
|
22
|
+
|
|
23
|
+
## 0.0.15 (2026-08-25)
|
|
24
|
+
|
|
25
|
+
### Codex 文件授权
|
|
26
|
+
|
|
27
|
+
- 文件变更的临时授权按 canonical 化后的完整目标文件集合匹配,变更内容或顺序调整不再扩大授权范围。
|
|
28
|
+
- 统一 Codex 文件授权的交互反馈,明确区分同文件临时授权与同操作临时授权,并收紧跨会话授权契约。
|
|
29
|
+
|
|
30
|
+
### 运行安全与审计
|
|
31
|
+
|
|
32
|
+
- 为 Claude、Codex 和 ecagent 的工具预检拒绝统一补充结构化授权审计,便于追踪策略、请求和会话上下文。
|
|
33
|
+
- 规范 Codex 审批命令与委派校验,拒绝无法规范化的复合命令和空字节输入,并修正 Windows 受管临时目录的私有性校验。
|
|
34
|
+
|
|
6
35
|
## 0.0.14 (2026-08-24)
|
|
7
36
|
|
|
8
37
|
### Codex 受管运行与安全
|
package/README.md
CHANGED
|
@@ -162,6 +162,7 @@ ec init aun
|
|
|
162
162
|
"idleMonitor": {
|
|
163
163
|
"enabled": true, // 任务超时监控开关
|
|
164
164
|
"timeout": 120, // 超时阈值(秒),默认 120 秒
|
|
165
|
+
"maxExecutionTime": 7200, // 可选:任务总执行时限(秒);未配置、非数字或 <=0 时不限制
|
|
165
166
|
"safeModeThreshold": 3 // 连续超时 N 次后进入安全模式(设为 0 禁用安全模式)
|
|
166
167
|
},
|
|
167
168
|
"flushDelay": 4 // 工具活动消息聚合发送间隔(秒),默认 4 秒
|
|
@@ -25,6 +25,7 @@ import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClass
|
|
|
25
25
|
import { buildHClassGuardCommand, createSandboxInitializationError, isSandboxInitializationFailure, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
|
|
26
26
|
import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
|
|
27
27
|
import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
28
|
+
import { isManagedSessionRuntimeDir } from '../cli/task-context.js';
|
|
28
29
|
import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
|
|
29
30
|
export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
|
|
30
31
|
// Built-in tools execute inside the Claude runtime and are covered by the
|
|
@@ -243,6 +244,17 @@ function trustedRuntimeWritePaths(runtimeEnv) {
|
|
|
243
244
|
}
|
|
244
245
|
return [...new Set(paths)];
|
|
245
246
|
}
|
|
247
|
+
function resolveClaudeSettingsDirectory(runtimeEnv) {
|
|
248
|
+
const configured = runtimeEnv?.TMPDIR?.trim();
|
|
249
|
+
if (!configured || !path.isAbsolute(configured)) {
|
|
250
|
+
throw new Error('[ClaudeSandbox] managed TMPDIR is unavailable');
|
|
251
|
+
}
|
|
252
|
+
const directory = path.resolve(configured);
|
|
253
|
+
if (!isManagedSessionRuntimeDir(directory, configured)) {
|
|
254
|
+
throw new Error('[ClaudeSandbox] settings file directory is outside the managed TMPDIR');
|
|
255
|
+
}
|
|
256
|
+
return directory;
|
|
257
|
+
}
|
|
246
258
|
async function assertClaudeSettingSourcesHaveLiteralSandboxPaths(cwd, settingSources, managedSettings) {
|
|
247
259
|
if (process.platform !== 'linux' || settingSources.length === 0)
|
|
248
260
|
return;
|
|
@@ -601,6 +613,7 @@ export class AgentRunner {
|
|
|
601
613
|
activeMessageStreams = new Map();
|
|
602
614
|
interruptFns = new Map();
|
|
603
615
|
activeQueries = new Map();
|
|
616
|
+
sandboxSettingsFiles = new Map();
|
|
604
617
|
streamDone = new Map();
|
|
605
618
|
streamDoneResolvers = new Map();
|
|
606
619
|
onSessionIdUpdate;
|
|
@@ -612,6 +625,33 @@ export class AgentRunner {
|
|
|
612
625
|
/** 每个 session 最近的子进程 stderr 行(环形缓冲),用于子进程崩溃时还原真正原因 */
|
|
613
626
|
recentStderr = new Map();
|
|
614
627
|
static STDERR_BUFFER_MAX = 80;
|
|
628
|
+
trackSandboxSettingsFile(sessionId, filePath) {
|
|
629
|
+
const files = this.sandboxSettingsFiles.get(sessionId) ?? new Set();
|
|
630
|
+
files.add(filePath);
|
|
631
|
+
this.sandboxSettingsFiles.set(sessionId, files);
|
|
632
|
+
}
|
|
633
|
+
cleanupSandboxSettingsFile(sessionId, filePath) {
|
|
634
|
+
if (!filePath)
|
|
635
|
+
return;
|
|
636
|
+
try {
|
|
637
|
+
fs.rmSync(filePath, { force: true });
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
641
|
+
? String(error.code)
|
|
642
|
+
: 'unknown';
|
|
643
|
+
logger.warn(`[ClaudeSandbox] failed to remove transient settings file code=${code}`);
|
|
644
|
+
}
|
|
645
|
+
const files = this.sandboxSettingsFiles.get(sessionId);
|
|
646
|
+
files?.delete(filePath);
|
|
647
|
+
if (files?.size === 0)
|
|
648
|
+
this.sandboxSettingsFiles.delete(sessionId);
|
|
649
|
+
}
|
|
650
|
+
cleanupSandboxSettingsFiles(sessionId) {
|
|
651
|
+
for (const filePath of [...(this.sandboxSettingsFiles.get(sessionId) ?? [])]) {
|
|
652
|
+
this.cleanupSandboxSettingsFile(sessionId, filePath);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
615
655
|
constructor(apiKey, model, onSessionIdUpdate, baseUrl, config) {
|
|
616
656
|
this.apiKey = apiKey;
|
|
617
657
|
this.model = model || 'sonnet';
|
|
@@ -1366,7 +1406,7 @@ export class AgentRunner {
|
|
|
1366
1406
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
1367
1407
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
1368
1408
|
*/
|
|
1369
|
-
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false) {
|
|
1409
|
+
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile) {
|
|
1370
1410
|
let lastSessionId;
|
|
1371
1411
|
let hasTurnActivity = false;
|
|
1372
1412
|
let ignoredPreTurnResult = false;
|
|
@@ -1813,6 +1853,7 @@ export class AgentRunner {
|
|
|
1813
1853
|
}
|
|
1814
1854
|
finally {
|
|
1815
1855
|
this.recentStderr.delete(sessionId);
|
|
1856
|
+
this.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
1816
1857
|
}
|
|
1817
1858
|
}
|
|
1818
1859
|
async runQuery(sessionId, prompt, projectPath, initialClaudeSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
@@ -2001,6 +2042,7 @@ export class AgentRunner {
|
|
|
2001
2042
|
userId: permCtx?.userId,
|
|
2002
2043
|
role: permCtx?.role,
|
|
2003
2044
|
permissionMode: callPermissionMode,
|
|
2045
|
+
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
2004
2046
|
allowHostProcessCommands: true,
|
|
2005
2047
|
allowProtectedMetadata: false,
|
|
2006
2048
|
safeOutputHelperPath: path.join(getPackageRoot(), 'bin', 'ec-safe-output.js'),
|
|
@@ -2035,6 +2077,7 @@ export class AgentRunner {
|
|
|
2035
2077
|
peerId: permCtx?.userId,
|
|
2036
2078
|
role: permCtx?.role,
|
|
2037
2079
|
allowProtectedMetadata: false,
|
|
2080
|
+
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
2038
2081
|
};
|
|
2039
2082
|
const roResult = checkReadonly(toolName, toolInput, projectPath, readonlyContext);
|
|
2040
2083
|
if (roResult.behavior === 'deny') {
|
|
@@ -2123,6 +2166,7 @@ export class AgentRunner {
|
|
|
2123
2166
|
userId: permCtx?.userId,
|
|
2124
2167
|
role: permCtx?.role,
|
|
2125
2168
|
permissionMode: callPermissionMode,
|
|
2169
|
+
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
2126
2170
|
allowHostProcessCommands: true,
|
|
2127
2171
|
allowProtectedMetadata: false,
|
|
2128
2172
|
safeOutputHelperPath: path.join(getPackageRoot(), 'bin', 'ec-safe-output.js'),
|
|
@@ -2193,6 +2237,7 @@ export class AgentRunner {
|
|
|
2193
2237
|
peerId: permCtx?.userId,
|
|
2194
2238
|
role: permCtx?.role,
|
|
2195
2239
|
allowProtectedMetadata: false,
|
|
2240
|
+
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
2196
2241
|
};
|
|
2197
2242
|
const roResult = checkReadonly(toolName, input, projectPath, readonlyContext);
|
|
2198
2243
|
if (roResult.behavior === 'deny') {
|
|
@@ -2362,23 +2407,54 @@ export class AgentRunner {
|
|
|
2362
2407
|
stderr: handleClaudeStderr,
|
|
2363
2408
|
env: this.getAgentEnv(runtimeEnv, sessionId, callPermissionMode)
|
|
2364
2409
|
};
|
|
2410
|
+
// Native Windows has a small CreateProcess command-line limit. The SDK
|
|
2411
|
+
// normally serializes `sandbox` into inline `--settings` JSON, so preserve
|
|
2412
|
+
// the same flag-settings semantics while passing a short file path.
|
|
2413
|
+
let sandboxSettingsFile;
|
|
2414
|
+
const shouldUseSandboxSettingsFile = process.platform === 'win32' && sandboxOptions && sandboxOptions.enabled !== false;
|
|
2415
|
+
if (shouldUseSandboxSettingsFile) {
|
|
2416
|
+
try {
|
|
2417
|
+
const managedTmpDir = resolveClaudeSettingsDirectory(runtimeEnv);
|
|
2418
|
+
sandboxSettingsFile = path.join(managedTmpDir, `.claude-sandbox-${crypto.randomUUID()}.json`);
|
|
2419
|
+
const capabilitySettings = capabilityOptions.settings;
|
|
2420
|
+
const settingsContent = JSON.stringify({
|
|
2421
|
+
...(capabilitySettings && typeof capabilitySettings === 'object' && !Array.isArray(capabilitySettings)
|
|
2422
|
+
? capabilitySettings
|
|
2423
|
+
: {}),
|
|
2424
|
+
sandbox: sandboxOptions,
|
|
2425
|
+
});
|
|
2426
|
+
fs.writeFileSync(sandboxSettingsFile, settingsContent, { flag: 'wx', mode: 0o600 });
|
|
2427
|
+
this.trackSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2428
|
+
logger.info(`[ClaudeSandbox] Windows settings externalized settingsChars=${settingsContent.length} ` +
|
|
2429
|
+
`settingsPathChars=${sandboxSettingsFile.length}`);
|
|
2430
|
+
}
|
|
2431
|
+
catch (error) {
|
|
2432
|
+
this.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2433
|
+
throw new Error(`[ClaudeSandbox] failed to externalize Windows settings: ${error instanceof Error ? error.message : String(error)}`);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2365
2436
|
const createQuery = (promptInput, resumeSessionId, resumeAt) => {
|
|
2366
2437
|
if (useSettingSources) {
|
|
2438
|
+
const queryOptions = {
|
|
2439
|
+
...commonOptions,
|
|
2440
|
+
...(!resumeSessionId && callSessionTitle ? { title: callSessionTitle } : {}),
|
|
2441
|
+
settingSources: [...settingSources],
|
|
2442
|
+
systemPrompt: {
|
|
2443
|
+
type: 'preset',
|
|
2444
|
+
preset: 'claude_code',
|
|
2445
|
+
...(excludeDynamic ? { excludeDynamicSections: true } : {}),
|
|
2446
|
+
...(systemPromptAppend ? { append: systemPromptAppend } : {})
|
|
2447
|
+
},
|
|
2448
|
+
...(resumeSessionId ? { resume: resumeSessionId } : {}),
|
|
2449
|
+
...(resumeAt ? { resumeSessionAt: resumeAt } : {}),
|
|
2450
|
+
};
|
|
2451
|
+
if (sandboxSettingsFile) {
|
|
2452
|
+
delete queryOptions.sandbox;
|
|
2453
|
+
queryOptions.settings = sandboxSettingsFile;
|
|
2454
|
+
}
|
|
2367
2455
|
return query({
|
|
2368
2456
|
prompt: promptInput,
|
|
2369
|
-
options:
|
|
2370
|
-
...commonOptions,
|
|
2371
|
-
...(!resumeSessionId && callSessionTitle ? { title: callSessionTitle } : {}),
|
|
2372
|
-
settingSources: [...settingSources],
|
|
2373
|
-
systemPrompt: {
|
|
2374
|
-
type: 'preset',
|
|
2375
|
-
preset: 'claude_code',
|
|
2376
|
-
...(excludeDynamic ? { excludeDynamicSections: true } : {}),
|
|
2377
|
-
...(systemPromptAppend ? { append: systemPromptAppend } : {})
|
|
2378
|
-
},
|
|
2379
|
-
...(resumeSessionId ? { resume: resumeSessionId } : {}),
|
|
2380
|
-
...(resumeAt ? { resumeSessionAt: resumeAt } : {}),
|
|
2381
|
-
}
|
|
2457
|
+
options: queryOptions
|
|
2382
2458
|
});
|
|
2383
2459
|
}
|
|
2384
2460
|
else {
|
|
@@ -2408,22 +2484,27 @@ export class AgentRunner {
|
|
|
2408
2484
|
globalClaudeMd,
|
|
2409
2485
|
systemPromptAppend,
|
|
2410
2486
|
].filter(Boolean).join('\n\n');
|
|
2487
|
+
const queryOptions = {
|
|
2488
|
+
...commonOptions,
|
|
2489
|
+
...(!resumeSessionId && callSessionTitle ? { title: callSessionTitle } : {}),
|
|
2490
|
+
settingSources: [],
|
|
2491
|
+
...(resumeSessionId ? { resume: resumeSessionId } : {}),
|
|
2492
|
+
...(resumeAt ? { resumeSessionAt: resumeAt } : {}),
|
|
2493
|
+
...(fullAppend ? {
|
|
2494
|
+
systemPrompt: {
|
|
2495
|
+
type: 'preset',
|
|
2496
|
+
preset: 'claude_code',
|
|
2497
|
+
append: fullAppend
|
|
2498
|
+
}
|
|
2499
|
+
} : {}),
|
|
2500
|
+
};
|
|
2501
|
+
if (sandboxSettingsFile) {
|
|
2502
|
+
delete queryOptions.sandbox;
|
|
2503
|
+
queryOptions.settings = sandboxSettingsFile;
|
|
2504
|
+
}
|
|
2411
2505
|
return query({
|
|
2412
2506
|
prompt: promptInput,
|
|
2413
|
-
options:
|
|
2414
|
-
...commonOptions,
|
|
2415
|
-
...(!resumeSessionId && callSessionTitle ? { title: callSessionTitle } : {}),
|
|
2416
|
-
settingSources: [],
|
|
2417
|
-
...(resumeSessionId ? { resume: resumeSessionId } : {}),
|
|
2418
|
-
...(resumeAt ? { resumeSessionAt: resumeAt } : {}),
|
|
2419
|
-
...(fullAppend ? {
|
|
2420
|
-
systemPrompt: {
|
|
2421
|
-
type: 'preset',
|
|
2422
|
-
preset: 'claude_code',
|
|
2423
|
-
append: fullAppend
|
|
2424
|
-
}
|
|
2425
|
-
} : {}),
|
|
2426
|
-
}
|
|
2507
|
+
options: queryOptions
|
|
2427
2508
|
});
|
|
2428
2509
|
}
|
|
2429
2510
|
};
|
|
@@ -2455,6 +2536,7 @@ export class AgentRunner {
|
|
|
2455
2536
|
sdkStream = createQuery(msgStream);
|
|
2456
2537
|
}
|
|
2457
2538
|
catch (error) {
|
|
2539
|
+
this.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2458
2540
|
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2459
2541
|
if (isSandboxInitializationFailure(error, stderr))
|
|
2460
2542
|
throw createSandboxInitializationError(error, stderr);
|
|
@@ -2468,6 +2550,7 @@ export class AgentRunner {
|
|
|
2468
2550
|
sdkStream = createQuery(msgStream, agentSessionId, resumeAt);
|
|
2469
2551
|
}
|
|
2470
2552
|
catch (error) {
|
|
2553
|
+
this.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2471
2554
|
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2472
2555
|
if (isSandboxInitializationFailure(error, stderr))
|
|
2473
2556
|
throw createSandboxInitializationError(error, stderr);
|
|
@@ -2483,13 +2566,14 @@ export class AgentRunner {
|
|
|
2483
2566
|
this.interruptFns.set(sessionId, () => sdkStream.interrupt());
|
|
2484
2567
|
}
|
|
2485
2568
|
// 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
|
|
2486
|
-
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId);
|
|
2569
|
+
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile);
|
|
2487
2570
|
const self = this;
|
|
2488
2571
|
return (async function* () {
|
|
2489
2572
|
try {
|
|
2490
2573
|
yield* transformed;
|
|
2491
2574
|
}
|
|
2492
2575
|
finally {
|
|
2576
|
+
self.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2493
2577
|
self.streamDoneResolvers.get(sessionId)?.();
|
|
2494
2578
|
self.streamDoneResolvers.delete(sessionId);
|
|
2495
2579
|
self.streamDone.delete(sessionId);
|
|
@@ -2536,6 +2620,7 @@ export class AgentRunner {
|
|
|
2536
2620
|
}
|
|
2537
2621
|
this.interruptFns.delete(sessionId);
|
|
2538
2622
|
this.activeStreams.delete(sessionId);
|
|
2623
|
+
this.cleanupSandboxSettingsFiles(sessionId);
|
|
2539
2624
|
return { stillQueued, cancelledInputIds, closed: true };
|
|
2540
2625
|
}
|
|
2541
2626
|
hasActiveStream(sessionId) {
|
|
@@ -2553,6 +2638,7 @@ export class AgentRunner {
|
|
|
2553
2638
|
this.activeStreams.delete(sessionId);
|
|
2554
2639
|
this.interruptFns.delete(sessionId);
|
|
2555
2640
|
this.activeQueries.delete(sessionId);
|
|
2641
|
+
this.cleanupSandboxSettingsFiles(sessionId);
|
|
2556
2642
|
this.recentStderr.delete(sessionId);
|
|
2557
2643
|
}
|
|
2558
2644
|
injectUserMessage(sessionId, text) {
|
|
@@ -2657,6 +2743,7 @@ export class AgentRunner {
|
|
|
2657
2743
|
this.interruptFns.delete(sessionId);
|
|
2658
2744
|
this.activeQueries.delete(sessionId);
|
|
2659
2745
|
this.permissionContexts.delete(sessionId);
|
|
2746
|
+
this.cleanupSandboxSettingsFiles(sessionId);
|
|
2660
2747
|
}
|
|
2661
2748
|
async dispose() {
|
|
2662
2749
|
const interrupts = [...this.interruptFns.values()].map(async (interrupt) => {
|
|
@@ -2686,6 +2773,9 @@ export class AgentRunner {
|
|
|
2686
2773
|
this.streamDoneResolvers.clear();
|
|
2687
2774
|
this.permissionContexts.clear();
|
|
2688
2775
|
this.recentStderr.clear();
|
|
2776
|
+
for (const sessionId of [...this.sandboxSettingsFiles.keys()]) {
|
|
2777
|
+
this.cleanupSandboxSettingsFiles(sessionId);
|
|
2778
|
+
}
|
|
2689
2779
|
}
|
|
2690
2780
|
resolveSessionFile(agentSessionId, projectPath) {
|
|
2691
2781
|
const encodedProjectPath = encodePath(projectPath);
|
|
@@ -13,7 +13,7 @@ import { CodexAppServerClient } from './codex-app-server-client.js';
|
|
|
13
13
|
import { resolveOpenaiConfig } from './baseagent.js';
|
|
14
14
|
import { logger } from '../utils/logger.js';
|
|
15
15
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
16
|
-
import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
16
|
+
import { auditCodexApprovalDecision, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
17
17
|
import { isRetryableError } from '../utils/error-utils.js';
|
|
18
18
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
19
19
|
import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
@@ -1694,7 +1694,7 @@ export class CodexRunner {
|
|
|
1694
1694
|
logger.warn(`[CodexRunner] failed to record policy-hook denial: session=${sessionKey} tool=${toolName} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1695
1695
|
}
|
|
1696
1696
|
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1697
|
-
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'policy', policyResult.policyCode ?? 'session_policy_hook');
|
|
1697
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'policy', policyResult.policyCode ?? 'session_policy_hook', policyResult.reason ?? 'session policy denied approval');
|
|
1698
1698
|
return response;
|
|
1699
1699
|
}
|
|
1700
1700
|
const summary = this.summarizeAppServerRequest(request.method, params);
|
|
@@ -1703,7 +1703,7 @@ export class CodexRunner {
|
|
|
1703
1703
|
if (!workspacePath) {
|
|
1704
1704
|
logger.warn(`[CodexRunner] approval denied because thread workspace is unknown: method=${request.method} thread=${params.threadId ?? params.conversationId ?? '<missing>'}`);
|
|
1705
1705
|
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1706
|
-
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', 'approval_workspace_unknown');
|
|
1706
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', 'approval_workspace_unknown', 'thread workspace is unknown');
|
|
1707
1707
|
return response;
|
|
1708
1708
|
}
|
|
1709
1709
|
const operationCwd = this.resolvePermissionOperationCwd(params, workspacePath, toolName);
|
|
@@ -1738,7 +1738,7 @@ export class CodexRunner {
|
|
|
1738
1738
|
logger.warn(`[CodexRunner] failed to record infrastructure approval denial: session=${sessionKey} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1739
1739
|
}
|
|
1740
1740
|
const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1741
|
-
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode);
|
|
1741
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message);
|
|
1742
1742
|
return response;
|
|
1743
1743
|
};
|
|
1744
1744
|
if (managedEcIntent && !approvedCommand) {
|
|
@@ -1763,7 +1763,13 @@ export class CodexRunner {
|
|
|
1763
1763
|
}
|
|
1764
1764
|
}
|
|
1765
1765
|
const response = this.toAppServerApprovalResponse(request.method, decision, toolInput, unattended);
|
|
1766
|
-
this.logAppServerApprovalAudit(request, sessionKey, toolName, decision === 'deny' ? 'deny' : 'allow', decision === 'deny' ? (sessionMode === 'auto' || sessionMode === 'readonly' ? 'policy' : 'approval') : 'approval')
|
|
1766
|
+
this.logAppServerApprovalAudit(request, sessionKey, toolName, decision === 'deny' ? 'deny' : 'allow', decision === 'deny' ? (sessionMode === 'auto' || sessionMode === 'readonly' ? 'policy' : 'approval') : 'approval', decision === 'deny' && (sessionMode === 'auto' || sessionMode === 'readonly')
|
|
1767
|
+
? 'permission_mode_denied'
|
|
1768
|
+
: undefined, decision === 'deny'
|
|
1769
|
+
? (reason || (sessionMode === 'auto' || sessionMode === 'readonly'
|
|
1770
|
+
? `permission mode ${sessionMode} denied approval`
|
|
1771
|
+
: 'user approval denied'))
|
|
1772
|
+
: 'approval accepted');
|
|
1767
1773
|
logger.info(`[CodexRunner] app-server approval response id=${request.id} method=${request.method} decision=${decision} response=${JSON.stringify(response)}`);
|
|
1768
1774
|
return response;
|
|
1769
1775
|
}
|
|
@@ -1773,7 +1779,21 @@ export class CodexRunner {
|
|
|
1773
1779
|
throw error;
|
|
1774
1780
|
}
|
|
1775
1781
|
}
|
|
1776
|
-
logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode) {
|
|
1782
|
+
logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode, reason) {
|
|
1783
|
+
const context = this.permissionContexts.get(sessionKey);
|
|
1784
|
+
auditCodexApprovalDecision({
|
|
1785
|
+
requestId: request.id !== undefined ? String(request.id) : undefined,
|
|
1786
|
+
method: request.method,
|
|
1787
|
+
sessionId: sessionKey,
|
|
1788
|
+
agentAid: context?.selfAid,
|
|
1789
|
+
toolName,
|
|
1790
|
+
decision,
|
|
1791
|
+
decisionSource,
|
|
1792
|
+
policyCode,
|
|
1793
|
+
permissionMode: normalizePermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode).mode,
|
|
1794
|
+
role: context?.role,
|
|
1795
|
+
reason: reason ?? policyCode ?? (decision === 'allow' ? 'approval accepted' : 'approval denied'),
|
|
1796
|
+
});
|
|
1777
1797
|
logger.info(`[CodexRunner] app-server approval audit ${JSON.stringify({
|
|
1778
1798
|
requestId: request.id !== undefined ? String(request.id) : undefined,
|
|
1779
1799
|
method: request.method,
|
|
@@ -1782,6 +1802,7 @@ export class CodexRunner {
|
|
|
1782
1802
|
decision,
|
|
1783
1803
|
decisionSource,
|
|
1784
1804
|
...(policyCode ? { policyCode } : {}),
|
|
1805
|
+
reason: reason ?? policyCode ?? (decision === 'allow' ? 'approval accepted' : 'approval denied'),
|
|
1785
1806
|
executed: false,
|
|
1786
1807
|
})}`);
|
|
1787
1808
|
}
|
|
@@ -2200,6 +2221,7 @@ export class CodexRunner {
|
|
|
2200
2221
|
// readonly decision. The map entry alone is stale if the directory was
|
|
2201
2222
|
// replaced between preflight and the approval callback.
|
|
2202
2223
|
managedTempDir: this.getManagedTempDir(sessionKey),
|
|
2224
|
+
allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
|
|
2203
2225
|
} : undefined;
|
|
2204
2226
|
if (toolName === 'Bash')
|
|
2205
2227
|
return checkReadonly(toolName, input, projectPath, readonlyContext);
|
|
@@ -2315,12 +2337,12 @@ export class CodexRunner {
|
|
|
2315
2337
|
// per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
|
|
2316
2338
|
const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
2317
2339
|
const mode = normalizePermissionMode(rawMode).mode;
|
|
2318
|
-
const recordBlockedOperation = async (policyCode, operationInput = toolInput) => {
|
|
2340
|
+
const recordBlockedOperation = async (policyCode, operationInput = toolInput, denialReason = 'policy denied') => {
|
|
2319
2341
|
const summary = summarizeToolInputForAudit(toolName, operationInput).slice(0, 512);
|
|
2320
2342
|
auditToolPreflightDenial({
|
|
2321
2343
|
toolName,
|
|
2322
2344
|
policyCode,
|
|
2323
|
-
reason:
|
|
2345
|
+
reason: denialReason,
|
|
2324
2346
|
summary,
|
|
2325
2347
|
sessionId: sessionKey,
|
|
2326
2348
|
agentAid: permissionContext?.selfAid,
|
|
@@ -2364,6 +2386,7 @@ export class CodexRunner {
|
|
|
2364
2386
|
userId: permissionContext?.userId,
|
|
2365
2387
|
role: permissionContext?.role,
|
|
2366
2388
|
permissionMode: mode,
|
|
2389
|
+
allowReadonlySourceDiagnostics: permissionContext?.allowReadonlySourceDiagnostics === true,
|
|
2367
2390
|
projectPath: operationCwd,
|
|
2368
2391
|
workspacePath,
|
|
2369
2392
|
});
|
|
@@ -2371,7 +2394,7 @@ export class CodexRunner {
|
|
|
2371
2394
|
logger.warn(`[CodexRunner] tool preflight denied: session=${sessionKey} tool=${toolName} ` +
|
|
2372
2395
|
`policy=${preflight.policyCode ?? 'unknown'} reason=${preflight.message ?? 'policy denied'}`);
|
|
2373
2396
|
if (preflight.policyCode) {
|
|
2374
|
-
await recordBlockedOperation(preflight.policyCode);
|
|
2397
|
+
await recordBlockedOperation(preflight.policyCode, toolInput, preflight.message ?? 'tool preflight denied');
|
|
2375
2398
|
}
|
|
2376
2399
|
return 'deny';
|
|
2377
2400
|
}
|
|
@@ -2399,7 +2422,7 @@ export class CodexRunner {
|
|
|
2399
2422
|
if (mode === 'readonly') {
|
|
2400
2423
|
const readonly = this.checkCodexReadonly(toolName, checkedInput, operationCwd, sessionKey);
|
|
2401
2424
|
if (readonly.behavior === 'deny') {
|
|
2402
|
-
await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode', checkedInput);
|
|
2425
|
+
await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode', checkedInput, readonly.message);
|
|
2403
2426
|
return 'deny';
|
|
2404
2427
|
}
|
|
2405
2428
|
return 'allow';
|
|
@@ -2430,6 +2453,9 @@ export class CodexRunner {
|
|
|
2430
2453
|
if (requiresSandboxExpansionApproval) {
|
|
2431
2454
|
if (!this.permissionGateway || !permissionPrompt)
|
|
2432
2455
|
return 'deny';
|
|
2456
|
+
if (toolName === 'FileChange') {
|
|
2457
|
+
return this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope, undefined, { fileChangeCwd: workspacePath });
|
|
2458
|
+
}
|
|
2433
2459
|
return this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope);
|
|
2434
2460
|
}
|
|
2435
2461
|
if (mode === 'request' || mode === 'bypass') {
|
|
@@ -2925,6 +2951,7 @@ export class CodexRunner {
|
|
|
2925
2951
|
name: 'Shell',
|
|
2926
2952
|
result: item.aggregatedOutput ?? '',
|
|
2927
2953
|
isError: item.exitCode !== null && item.exitCode !== undefined ? item.exitCode !== 0 : item.status === 'failed',
|
|
2954
|
+
...(item.error?.code || item.error?.errorCode ? { errorCode: String(item.error.code ?? item.error.errorCode) } : {}),
|
|
2928
2955
|
callId: item.id,
|
|
2929
2956
|
durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
|
|
2930
2957
|
};
|
|
@@ -2936,6 +2963,7 @@ export class CodexRunner {
|
|
|
2936
2963
|
result: item.result,
|
|
2937
2964
|
isError: item.status === 'failed',
|
|
2938
2965
|
error: item.error?.message,
|
|
2966
|
+
...(item.error?.code || item.error?.errorCode ? { errorCode: String(item.error.code ?? item.error.errorCode) } : {}),
|
|
2939
2967
|
callId: item.id,
|
|
2940
2968
|
durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
|
|
2941
2969
|
};
|
|
@@ -2946,6 +2974,7 @@ export class CodexRunner {
|
|
|
2946
2974
|
name: item.namespace ? `${item.namespace}:${item.tool}` : item.tool,
|
|
2947
2975
|
result: item.contentItems,
|
|
2948
2976
|
isError: item.success === false || item.status === 'failed',
|
|
2977
|
+
...(item.error?.code || item.error?.errorCode ? { errorCode: String(item.error.code ?? item.error.errorCode) } : {}),
|
|
2949
2978
|
callId: item.id,
|
|
2950
2979
|
durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
|
|
2951
2980
|
};
|
|
@@ -924,7 +924,8 @@ export class EcagentRunner {
|
|
|
924
924
|
if (event.type === 'tool_execution_end') {
|
|
925
925
|
queue.push({
|
|
926
926
|
type: 'tool_result', name: event.toolName, result: extractToolResult(event.result),
|
|
927
|
-
isError: event.isError, error: event.isError ? extractToolResult(event.result) : undefined,
|
|
927
|
+
isError: event.isError, error: event.isError ? extractToolResult(event.result) : undefined,
|
|
928
|
+
callId: event.toolCallId,
|
|
928
929
|
});
|
|
929
930
|
return;
|
|
930
931
|
}
|
|
@@ -1006,6 +1007,7 @@ export class EcagentRunner {
|
|
|
1006
1007
|
userId: permissionContext?.userId,
|
|
1007
1008
|
role: permissionContext?.role,
|
|
1008
1009
|
permissionMode: mode,
|
|
1010
|
+
allowReadonlySourceDiagnostics: permissionContext?.allowReadonlySourceDiagnostics === true,
|
|
1009
1011
|
projectPath,
|
|
1010
1012
|
});
|
|
1011
1013
|
if (preflight.behavior === 'deny') {
|
|
@@ -1020,6 +1022,7 @@ export class EcagentRunner {
|
|
|
1020
1022
|
if (mode === 'readonly') {
|
|
1021
1023
|
const decision = checkReadonly(toolName, checkedInput, projectPath, {
|
|
1022
1024
|
sessionId, channel: permissionContext?.channel, peerId: permissionContext?.userId, role: permissionContext?.role,
|
|
1025
|
+
allowReadonlySourceDiagnostics: permissionContext?.allowReadonlySourceDiagnostics === true,
|
|
1023
1026
|
});
|
|
1024
1027
|
if (decision.behavior === 'deny') {
|
|
1025
1028
|
await recordBlockedOperation(decision.policyCode ?? 'readonly_mode', checkedInput);
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -11,20 +11,20 @@ import { ipcQuery } from '../../ipc.js';
|
|
|
11
11
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
|
|
12
12
|
import { loadDaemonConfig } from '../../config-store.js';
|
|
13
13
|
import { normalizeAunMentionEntries } from './mention-schema.js';
|
|
14
|
-
const DEFAULT_TASK_EXECUTION_MS = 60 * 60 * 1000;
|
|
15
14
|
const DAEMON_SEND_TIMEOUT_GRACE_MS = 10_000;
|
|
15
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 120_000;
|
|
16
16
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
17
17
|
export function daemonTaskSendIpcTimeoutMs(file = false) {
|
|
18
|
-
let
|
|
18
|
+
let idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS;
|
|
19
19
|
try {
|
|
20
20
|
const config = JSON.parse(fs.readFileSync(resolvePaths().daemonConfig, 'utf8'));
|
|
21
|
-
const seconds = config.idleMonitor?.
|
|
21
|
+
const seconds = config.idleMonitor?.timeout;
|
|
22
22
|
if (typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0) {
|
|
23
|
-
|
|
23
|
+
idleTimeoutMs = seconds * 1000;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
catch { }
|
|
27
|
-
return Math.min(MAX_TIMER_MS, Math.max(file ? 120_000 : 5_000,
|
|
27
|
+
return Math.min(MAX_TIMER_MS, Math.max(file ? 120_000 : 5_000, idleTimeoutMs + DAEMON_SEND_TIMEOUT_GRACE_MS));
|
|
28
28
|
}
|
|
29
29
|
export function resolveAunMessageEncrypt(explicit) {
|
|
30
30
|
if (typeof explicit === 'boolean')
|