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
|
@@ -517,6 +517,7 @@ export class ResponseEngine {
|
|
|
517
517
|
/** 已主动发送开始提示时,忽略 runner 对同一次 compact 的回调。 */
|
|
518
518
|
announcedCompactStarts = new Set();
|
|
519
519
|
agentMap;
|
|
520
|
+
legacySingleRunnerMode;
|
|
520
521
|
primaryRunnerKey;
|
|
521
522
|
interruptedSessions = new Map(); // sessionId → reason ('new_message' | 'stop' | ...)
|
|
522
523
|
timeoutErrors = new Map();
|
|
@@ -552,27 +553,42 @@ export class ResponseEngine {
|
|
|
552
553
|
* session/config mismatch and must not silently route to a different backend.
|
|
553
554
|
*/
|
|
554
555
|
getAgent(channel, baseagent, selfAID) {
|
|
555
|
-
|
|
556
|
-
|
|
556
|
+
const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
|
|
557
|
+
// Keep the explicit single-backend map shape usable by embedders and
|
|
558
|
+
// focused tests. This is an exact key lookup, not a cross-agent fallback.
|
|
559
|
+
if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
|
|
560
|
+
return this.agentMap.get(requestedBaseagent);
|
|
561
|
+
}
|
|
562
|
+
// A caller that passed one runner explicitly has already selected the
|
|
563
|
+
// backend; this compatibility path does not exist for multi-agent maps.
|
|
564
|
+
if (this.legacySingleRunnerMode && this.agentMap.size === 1
|
|
565
|
+
&& this.agentMap.values().next().value?.name === requestedBaseagent) {
|
|
566
|
+
return this.agentMap.values().next().value;
|
|
567
|
+
}
|
|
568
|
+
if (selfAID && requestedBaseagent) {
|
|
569
|
+
const key = `${selfAID}::${requestedBaseagent}`;
|
|
557
570
|
if (this.agentMap.has(key))
|
|
558
571
|
return this.agentMap.get(key);
|
|
559
572
|
}
|
|
560
|
-
if (channel &&
|
|
573
|
+
if (channel && requestedBaseagent) {
|
|
561
574
|
const owner = this.agentRegistry?.resolveByChannel(channel);
|
|
562
575
|
const evolName = owner?.name || '<unknown>';
|
|
563
|
-
const key = `${evolName}::${
|
|
576
|
+
const key = `${evolName}::${requestedBaseagent}`;
|
|
564
577
|
if (this.agentMap.has(key))
|
|
565
578
|
return this.agentMap.get(key);
|
|
566
|
-
const singleRunnerKey = `<unknown>::${baseagent}`;
|
|
567
|
-
if (this.agentMap.has(singleRunnerKey))
|
|
568
|
-
return this.agentMap.get(singleRunnerKey);
|
|
569
579
|
if (owner) {
|
|
570
|
-
throw new BaseagentRunnerUnavailableError(evolName,
|
|
580
|
+
throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
|
|
571
581
|
}
|
|
582
|
+
// Control/test channels may have no registry owner. Resolve only when
|
|
583
|
+
// exactly one loaded runner advertises this baseagent; never choose the
|
|
584
|
+
// primary runner or the first map entry as a backend substitute.
|
|
585
|
+
const matches = [...this.agentMap.entries()]
|
|
586
|
+
.filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
|
|
587
|
+
if (matches.length === 1)
|
|
588
|
+
return matches[0][1];
|
|
589
|
+
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, [...new Set(matches.map(([runnerKey]) => runnerKey.slice(runnerKey.lastIndexOf('::') + 2)))]);
|
|
572
590
|
}
|
|
573
|
-
|
|
574
|
-
return this.agentMap.get(this.primaryRunnerKey);
|
|
575
|
-
return this.agentMap.values().next().value;
|
|
591
|
+
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
|
|
576
592
|
}
|
|
577
593
|
getTextInferenceProvider(channel, baseagent, selfAID) {
|
|
578
594
|
if (!baseagent)
|
|
@@ -648,11 +664,13 @@ export class ResponseEngine {
|
|
|
648
664
|
this.turnCoordinator = new SessionTurnCoordinator(sessionManager);
|
|
649
665
|
if (agentRunnerOrMap instanceof Map) {
|
|
650
666
|
this.agentMap = agentRunnerOrMap;
|
|
667
|
+
this.legacySingleRunnerMode = false;
|
|
651
668
|
this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
|
|
652
669
|
}
|
|
653
670
|
else {
|
|
654
671
|
// 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
|
|
655
672
|
this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
673
|
+
this.legacySingleRunnerMode = true;
|
|
656
674
|
this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
|
|
657
675
|
}
|
|
658
676
|
// 监听中断事件,标记被中断的 session
|
|
@@ -907,14 +925,10 @@ export class ResponseEngine {
|
|
|
907
925
|
if (owning)
|
|
908
926
|
owning.lastActivity = Date.now();
|
|
909
927
|
}
|
|
910
|
-
inferPrimaryBaseagent() {
|
|
911
|
-
const idx = this.primaryRunnerKey.lastIndexOf('::');
|
|
912
|
-
return idx >= 0 ? this.primaryRunnerKey.slice(idx + 2) : this.primaryRunnerKey || undefined;
|
|
913
|
-
}
|
|
914
928
|
ensureSessionBaseagent(session, fallback) {
|
|
915
|
-
|
|
916
|
-
if (!session.baseagent) {
|
|
917
|
-
session.
|
|
929
|
+
void fallback;
|
|
930
|
+
if (typeof session.baseagent !== 'string' || !session.baseagent.trim()) {
|
|
931
|
+
throw new Error(`[ResponseEngine] session ${session.id} has no baseagent binding; refusing implicit fallback`);
|
|
918
932
|
}
|
|
919
933
|
}
|
|
920
934
|
inferDirectSessionIdentity(message, owningAgent) {
|
|
@@ -1647,6 +1661,19 @@ export class ResponseEngine {
|
|
|
1647
1661
|
totalToolCalls: result.state.totalToolCalls,
|
|
1648
1662
|
lastToolName: result.state.lastToolName,
|
|
1649
1663
|
});
|
|
1664
|
+
const idleNotice = timeoutControl.idleNotice;
|
|
1665
|
+
if (idleNotice) {
|
|
1666
|
+
void Promise.resolve(idleNotice({
|
|
1667
|
+
action: result.action,
|
|
1668
|
+
idleSec: result.idleSec,
|
|
1669
|
+
message: result.message,
|
|
1670
|
+
totalEvents: result.state.totalEvents,
|
|
1671
|
+
totalToolCalls: result.state.totalToolCalls,
|
|
1672
|
+
lastToolName: result.state.lastToolName,
|
|
1673
|
+
})).catch(error => {
|
|
1674
|
+
logger.debug(`[ResponseEngine] Idle notice send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1650
1677
|
}
|
|
1651
1678
|
result = monitor.check();
|
|
1652
1679
|
}
|
|
@@ -2401,8 +2428,10 @@ export class ResponseEngine {
|
|
|
2401
2428
|
opts.title = '\u2705 \u6700\u7ec8\u56de\u590d:';
|
|
2402
2429
|
}
|
|
2403
2430
|
opts.metadata = withTaskMetadata(opts.metadata);
|
|
2404
|
-
|
|
2431
|
+
// status.notice is a diagnostic observation, not execution activity.
|
|
2432
|
+
if (payload.kind.startsWith('status.') && payload.kind !== 'status.notice') {
|
|
2405
2433
|
this.touchAgentActivity(channelKey);
|
|
2434
|
+
}
|
|
2406
2435
|
const enrichedEnvelope = withEnvelopeReplyContext(envelope, opts);
|
|
2407
2436
|
snapshot.pushOutbound(session.id, taskId, { kind: payload.kind, decision: 'sent' });
|
|
2408
2437
|
return await adapter.send(enrichedEnvelope, payload);
|
|
@@ -2413,6 +2442,19 @@ export class ResponseEngine {
|
|
|
2413
2442
|
renderer,
|
|
2414
2443
|
suppressActivities: shouldSuppress(),
|
|
2415
2444
|
});
|
|
2445
|
+
// Idle notify/warn are operational diagnostic notices. Keep them on the
|
|
2446
|
+
// current task's renderer so envelope, stale-turn, background, and
|
|
2447
|
+
// channel-specific suppression rules remain identical to other sends.
|
|
2448
|
+
if (timeoutControl) {
|
|
2449
|
+
timeoutControl.idleNotice = async ({ action, idleSec, message: idleMessage, totalEvents, totalToolCalls, lastToolName }) => {
|
|
2450
|
+
await renderer.sendStatusNotice(idleMessage, action === 'warn' ? 'warn' : 'info', `idle-${action}`, {
|
|
2451
|
+
idleSec,
|
|
2452
|
+
totalEvents,
|
|
2453
|
+
totalToolCalls,
|
|
2454
|
+
lastToolName,
|
|
2455
|
+
});
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2416
2458
|
renderer.addLifecycle('started');
|
|
2417
2459
|
if (isProactive) {
|
|
2418
2460
|
logger.info(`[ResponseEngine] proactive mode: outputs via thought.put task=${taskId}`);
|
|
@@ -3094,6 +3136,7 @@ export class ResponseEngine {
|
|
|
3094
3136
|
sessionId: session.id,
|
|
3095
3137
|
sessionName: session.name || undefined,
|
|
3096
3138
|
sessionCreatedAt: session.createdAt ? new Date(session.createdAt).toISOString() : undefined,
|
|
3139
|
+
managedTempDir: sessionRuntimeDir,
|
|
3097
3140
|
// 时区(把 ISO 时间戳转本地时间用)+ OS 环境
|
|
3098
3141
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || undefined,
|
|
3099
3142
|
tzOffset: currentTzOffset(),
|
|
@@ -3142,7 +3185,10 @@ export class ResponseEngine {
|
|
|
3142
3185
|
try {
|
|
3143
3186
|
const estTokens = (s) => s ? Math.ceil(s.length / 4) : 0;
|
|
3144
3187
|
const cbModel = effectiveModel || agentModel || 'unknown';
|
|
3145
|
-
|
|
3188
|
+
// 从统一模型参数表获取模型的真实 context_window
|
|
3189
|
+
const { resolveModelSpec } = await import('../../stats/billing.js');
|
|
3190
|
+
const modelSpec = resolveModelSpec(resolveRoot(), cbModel);
|
|
3191
|
+
const cbMaxTokens = modelSpec.context_window;
|
|
3146
3192
|
const systemPromptTokens = estTokens(options?.systemPromptAppend);
|
|
3147
3193
|
const kitTokens = estTokens(kitContext);
|
|
3148
3194
|
const totalEst = estTokens(effectiveSystemPrompt);
|
|
@@ -3315,6 +3361,8 @@ export class ResponseEngine {
|
|
|
3315
3361
|
selfAid,
|
|
3316
3362
|
peerKey,
|
|
3317
3363
|
issuedRole: peerRole,
|
|
3364
|
+
permissionMode: effectivePermissionMode,
|
|
3365
|
+
...(sessionRuntimeDir ? { sessionRuntimeDir } : {}),
|
|
3318
3366
|
...(fullAccessAuthorization ? { executionIdentity: fullAccessAuthorization } : {}),
|
|
3319
3367
|
});
|
|
3320
3368
|
runtimeEnv[AGENT_DELEGATION_TOKEN_ENV] = delegationToken;
|
|
@@ -4572,7 +4620,7 @@ export class ResponseEngine {
|
|
|
4572
4620
|
: undefined;
|
|
4573
4621
|
const owningAgent = this.agentRegistry?.resolveByChannel(message.channel);
|
|
4574
4622
|
const projectPath = owningAgent?.projectPath || process.cwd();
|
|
4575
|
-
const resolvedBaseagent = message.baseagent || owningAgent?.baseagent
|
|
4623
|
+
const resolvedBaseagent = message.baseagent || owningAgent?.baseagent;
|
|
4576
4624
|
// 优先复用 MessageBridge 入队前已解析的身份,避免二次独立解析产生角色漂移
|
|
4577
4625
|
// (曾观测到瞬时竞态下把 owner 误判为 none,导致合法用户被静默拒绝)。
|
|
4578
4626
|
// 仅当消息不经 MessageBridge.register()(如 trigger 直接投递)时才回退重新解析。
|
|
@@ -4762,6 +4810,10 @@ export class ResponseEngine {
|
|
|
4762
4810
|
// 每收到事件重置空闲超时
|
|
4763
4811
|
const toolName = event.type === 'tool_use' ? event.name : undefined;
|
|
4764
4812
|
resetTimer(event.type, toolName);
|
|
4813
|
+
// Provider reasoning/thinking is an internal liveness signal only.
|
|
4814
|
+
// It must never be projected as progress, activity, text, or transcript.
|
|
4815
|
+
if (event.type === 'heartbeat')
|
|
4816
|
+
continue;
|
|
4765
4817
|
if (event.type === 'input_accepted') {
|
|
4766
4818
|
inputAccepted = !turnLease || event.inputId === turnLease.inputId;
|
|
4767
4819
|
logger.debug(`[ResponseEngine] Input accepted: session=${session.id} input=${event.inputId}`);
|
|
@@ -43,7 +43,9 @@ export function parseModelList(json, _url) {
|
|
|
43
43
|
const rawId = typeof item === 'string' ? item : item?.id ?? item?.slug ?? item?.name ?? item?.model;
|
|
44
44
|
if (typeof rawId !== 'string' || !rawId.trim())
|
|
45
45
|
continue;
|
|
46
|
-
|
|
46
|
+
// 规范化模型 ID:去掉 Claude Code SDK 的上下文窗口后缀 [1m]/[200k]
|
|
47
|
+
// 这些后缀只在传给 claude baseagent 时由 applyContextWindow 添加
|
|
48
|
+
const id = rawId.trim().replace(/\[(?:1m|200k)\]$/i, '');
|
|
47
49
|
if (out.has(id))
|
|
48
50
|
continue;
|
|
49
51
|
const rawLevels = item?.reasoningLevels ?? item?.supportedReasoningEfforts ?? item?.supported_reasoning_levels;
|
|
@@ -106,6 +106,16 @@ export function resolveCatalogContext(self, ba) {
|
|
|
106
106
|
}
|
|
107
107
|
return context;
|
|
108
108
|
}
|
|
109
|
+
const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
|
|
110
|
+
/** Query the catalog for a host-level default connection, without Agent scope. */
|
|
111
|
+
export async function getBaseagentCatalog(baseagent, options = {}) {
|
|
112
|
+
if (!HOST_BASEAGENTS.includes(baseagent)) {
|
|
113
|
+
throw Object.assign(new Error(`Invalid baseagent: ${baseagent}. Valid: ${HOST_BASEAGENTS.join(', ')}`), { code: 'INVALID_ARGUMENT' });
|
|
114
|
+
}
|
|
115
|
+
const normalized = baseagent;
|
|
116
|
+
const snapshot = await getCatalog(undefined, normalized, options.refresh === true);
|
|
117
|
+
return { ...snapshot, baseagent: normalized };
|
|
118
|
+
}
|
|
109
119
|
/** Used by the IPC handler and the owner process. Never calls IPC recursively. */
|
|
110
120
|
export async function getLocalCatalog(self, ba, refresh = false) {
|
|
111
121
|
return service.get(resolveCatalogContext(self, ba), refresh);
|
|
@@ -7,12 +7,15 @@ export function isModelAllowedByPatterns(model, allowedModels) {
|
|
|
7
7
|
: model === pattern);
|
|
8
8
|
}
|
|
9
9
|
function normalizeBaseagent(baseagent) {
|
|
10
|
-
|
|
10
|
+
const normalized = typeof baseagent === 'string' ? baseagent.trim() : '';
|
|
11
|
+
return normalized || undefined;
|
|
11
12
|
}
|
|
12
13
|
export function getRoleModelPermission(role, baseagent, selfAid) {
|
|
13
14
|
const ba = normalizeBaseagent(baseagent);
|
|
14
15
|
if (!role)
|
|
15
16
|
return { allowOverride: false, constrained: true };
|
|
17
|
+
if (!ba)
|
|
18
|
+
return { allowOverride: false, constrained: true, allowedModels: [] };
|
|
16
19
|
const selected = getFieldPermission(role, `baseagents.${ba}.model`, selfAid);
|
|
17
20
|
if (!selected || !Array.isArray(selected.allowedModels)) {
|
|
18
21
|
return { allowOverride: true, constrained: false };
|
|
@@ -8,7 +8,7 @@ import { createRootCausation, deriveCausation, normalizeCausation } from '../cau
|
|
|
8
8
|
import { recordCausationSpan } from '../causation/audit.js';
|
|
9
9
|
import { checkDangerousCommand } from './tool-policy.js';
|
|
10
10
|
import { resolveProtectedCandidate } from './protected-paths.js';
|
|
11
|
-
import { auditPermissionDecision } from '../auth/authorization-audit.js';
|
|
11
|
+
import { auditPermissionDecision, auditPermissionLifecycle } from '../auth/authorization-audit.js';
|
|
12
12
|
export async function requestDangerousCommandPermission(gateway, sessionId, toolName, input, sendPrompt, context, grantScope = 'default', mode = 'request') {
|
|
13
13
|
const dangerCheck = checkDangerousCommand(toolName, input);
|
|
14
14
|
if (!dangerCheck.isDangerous) {
|
|
@@ -448,22 +448,40 @@ export class PermissionGateway {
|
|
|
448
448
|
if (sessionGrant)
|
|
449
449
|
this.addTemporaryGrant(pending, decisionCausation);
|
|
450
450
|
pending.resolve(decision);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
451
|
+
auditPermissionLifecycle({
|
|
452
|
+
phase: 'response',
|
|
453
|
+
requestId,
|
|
454
|
+
sessionId,
|
|
455
|
+
toolName: pending.toolName,
|
|
456
|
+
decision: approved ? 'allow' : 'deny',
|
|
457
|
+
decisionSource: 'approval',
|
|
458
|
+
policyCode: approved ? undefined : 'user_approval_denied',
|
|
459
|
+
reason: approved ? 'owner approval accepted' : 'owner approval denied',
|
|
460
|
+
permissionMode: pending.permissionMode,
|
|
461
|
+
agentAid: pending.selfAid,
|
|
462
|
+
agentName: pending.agentName,
|
|
463
|
+
role: pending.role,
|
|
464
|
+
taskId: pending.taskId,
|
|
465
|
+
summary: pending.auditSummary,
|
|
466
|
+
response: action,
|
|
467
|
+
});
|
|
468
|
+
auditPermissionLifecycle({
|
|
469
|
+
phase: 'final',
|
|
470
|
+
requestId,
|
|
471
|
+
sessionId,
|
|
472
|
+
toolName: pending.toolName,
|
|
473
|
+
decision: approved ? 'allow' : 'deny',
|
|
474
|
+
decisionSource: 'approval',
|
|
475
|
+
policyCode: approved ? undefined : 'user_approval_denied',
|
|
476
|
+
reason: approved ? 'owner approval accepted' : 'owner approval denied',
|
|
477
|
+
permissionMode: pending.permissionMode,
|
|
478
|
+
agentAid: pending.selfAid,
|
|
479
|
+
agentName: pending.agentName,
|
|
480
|
+
role: pending.role,
|
|
481
|
+
taskId: pending.taskId,
|
|
482
|
+
summary: pending.auditSummary,
|
|
483
|
+
response: action,
|
|
484
|
+
});
|
|
467
485
|
void this.notifyPermissionState(pending, approved
|
|
468
486
|
? { state: 'approved', requestId, toolName: pending.toolName }
|
|
469
487
|
: {
|
|
@@ -502,6 +520,23 @@ export class PermissionGateway {
|
|
|
502
520
|
pending.resolve('deny');
|
|
503
521
|
const decisionSource = 'infrastructure';
|
|
504
522
|
const policyCode = cancelled ? 'approval_cancelled' : `approval_${reason}`;
|
|
523
|
+
auditPermissionLifecycle({
|
|
524
|
+
phase: 'response',
|
|
525
|
+
requestId,
|
|
526
|
+
sessionId,
|
|
527
|
+
toolName: pending.toolName,
|
|
528
|
+
decision: 'deny',
|
|
529
|
+
decisionSource,
|
|
530
|
+
policyCode,
|
|
531
|
+
reason,
|
|
532
|
+
permissionMode: pending.permissionMode,
|
|
533
|
+
agentAid: pending.selfAid,
|
|
534
|
+
agentName: pending.agentName,
|
|
535
|
+
role: pending.role,
|
|
536
|
+
taskId: pending.taskId,
|
|
537
|
+
summary: pending.auditSummary,
|
|
538
|
+
response: reason,
|
|
539
|
+
});
|
|
505
540
|
auditPermissionDecision({
|
|
506
541
|
requestId,
|
|
507
542
|
sessionId,
|
|
@@ -565,7 +600,25 @@ export class PermissionGateway {
|
|
|
565
600
|
const auditSummary = summarizeToolInputForAudit(challenge.toolName, challenge.toolInput).slice(0, 512);
|
|
566
601
|
if (!approval || !interactionRouter) {
|
|
567
602
|
await sendPrompt('当前会话权限不足,且没有可用的 owner 审批通道。');
|
|
603
|
+
auditPermissionLifecycle({
|
|
604
|
+
phase: 'response',
|
|
605
|
+
requestId: challenge.id,
|
|
606
|
+
sessionId,
|
|
607
|
+
toolName: challenge.toolName,
|
|
608
|
+
decision: 'deny',
|
|
609
|
+
decisionSource: 'infrastructure',
|
|
610
|
+
policyCode: 'approval_route_unavailable',
|
|
611
|
+
reason: 'owner approval routing context is unavailable',
|
|
612
|
+
permissionMode: context.permissionMode,
|
|
613
|
+
agentAid: context.selfAid,
|
|
614
|
+
agentName: context.agentName,
|
|
615
|
+
role: context.role,
|
|
616
|
+
taskId: context.taskId,
|
|
617
|
+
summary: auditSummary,
|
|
618
|
+
response: 'unavailable',
|
|
619
|
+
});
|
|
568
620
|
auditPermissionDecision({
|
|
621
|
+
requestId: challenge.id,
|
|
569
622
|
sessionId,
|
|
570
623
|
toolName: challenge.toolName,
|
|
571
624
|
decisionSource: 'infrastructure',
|
|
@@ -783,6 +836,23 @@ export class PermissionGateway {
|
|
|
783
836
|
this.syncManagementRevalidationTimer();
|
|
784
837
|
pending.resolve('deny');
|
|
785
838
|
const decisionSource = reason === 'denied' ? 'approval' : 'infrastructure';
|
|
839
|
+
auditPermissionLifecycle({
|
|
840
|
+
phase: 'response',
|
|
841
|
+
requestId,
|
|
842
|
+
sessionId,
|
|
843
|
+
toolName: pending.toolName,
|
|
844
|
+
decision: 'deny',
|
|
845
|
+
decisionSource,
|
|
846
|
+
policyCode: `approval_${reason}`,
|
|
847
|
+
reason,
|
|
848
|
+
permissionMode: pending.permissionMode,
|
|
849
|
+
agentAid: pending.selfAid,
|
|
850
|
+
agentName: pending.agentName,
|
|
851
|
+
role: pending.role,
|
|
852
|
+
taskId: pending.taskId,
|
|
853
|
+
summary: pending.auditSummary,
|
|
854
|
+
response: reason,
|
|
855
|
+
});
|
|
786
856
|
auditPermissionDecision({
|
|
787
857
|
requestId,
|
|
788
858
|
sessionId,
|
|
@@ -915,6 +985,20 @@ export class PermissionGateway {
|
|
|
915
985
|
refs: { permissionRequestId: requestId, taskId: context.taskId, sessionId },
|
|
916
986
|
});
|
|
917
987
|
this.eventBus?.publish({ type: 'permission:requested', sessionId, requestId, toolName, input: displaySummary, causation: requestCausation });
|
|
988
|
+
auditPermissionLifecycle({
|
|
989
|
+
phase: 'request',
|
|
990
|
+
requestId,
|
|
991
|
+
sessionId,
|
|
992
|
+
toolName,
|
|
993
|
+
decision: 'pending',
|
|
994
|
+
decisionSource: 'approval',
|
|
995
|
+
permissionMode: context?.permissionMode,
|
|
996
|
+
agentAid: context?.selfAid,
|
|
997
|
+
agentName: context?.agentName,
|
|
998
|
+
role: context?.role,
|
|
999
|
+
taskId: context?.taskId,
|
|
1000
|
+
summary: auditSummary,
|
|
1001
|
+
});
|
|
918
1002
|
const route = planApprovalRoute(challenge, context);
|
|
919
1003
|
logger.info(`[PermissionGateway] approval_route request=${requestId} policy=${challenge.approverPolicy}`
|
|
920
1004
|
+ ` route=${route.kind}`
|
|
@@ -955,6 +1039,23 @@ export class PermissionGateway {
|
|
|
955
1039
|
taskId: context.taskId,
|
|
956
1040
|
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
957
1041
|
});
|
|
1042
|
+
auditPermissionLifecycle({
|
|
1043
|
+
phase: 'response',
|
|
1044
|
+
requestId,
|
|
1045
|
+
sessionId,
|
|
1046
|
+
toolName,
|
|
1047
|
+
decision: 'deny',
|
|
1048
|
+
decisionSource: 'infrastructure',
|
|
1049
|
+
policyCode: 'approval_route_unavailable',
|
|
1050
|
+
reason: route.reason,
|
|
1051
|
+
permissionMode: context.permissionMode,
|
|
1052
|
+
agentAid: context.selfAid,
|
|
1053
|
+
agentName: context.agentName,
|
|
1054
|
+
role: context.role,
|
|
1055
|
+
taskId: context.taskId,
|
|
1056
|
+
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1057
|
+
response: 'unavailable',
|
|
1058
|
+
});
|
|
958
1059
|
return 'deny';
|
|
959
1060
|
}
|
|
960
1061
|
if (route.kind === 'handoff') {
|
|
@@ -1125,6 +1226,40 @@ export class PermissionGateway {
|
|
|
1125
1226
|
pending.resolve(normalizedDecision === 'deny' ? 'deny' : 'allow');
|
|
1126
1227
|
this.pending.delete(requestId);
|
|
1127
1228
|
this.syncManagementRevalidationTimer();
|
|
1229
|
+
auditPermissionLifecycle({
|
|
1230
|
+
phase: 'response',
|
|
1231
|
+
requestId,
|
|
1232
|
+
sessionId,
|
|
1233
|
+
toolName: pending.toolName,
|
|
1234
|
+
decision: normalizedDecision === 'deny' ? 'deny' : 'allow',
|
|
1235
|
+
decisionSource: 'approval',
|
|
1236
|
+
policyCode: normalizedDecision === 'deny' ? 'user_approval_denied' : undefined,
|
|
1237
|
+
reason: normalizedDecision === 'deny' ? 'user approval denied' : 'user approval accepted',
|
|
1238
|
+
permissionMode: pending.permissionMode,
|
|
1239
|
+
agentAid: pending.selfAid,
|
|
1240
|
+
agentName: pending.agentName,
|
|
1241
|
+
role: pending.role,
|
|
1242
|
+
taskId: pending.taskId,
|
|
1243
|
+
summary: pending.auditSummary,
|
|
1244
|
+
response: normalizedDecision,
|
|
1245
|
+
});
|
|
1246
|
+
auditPermissionLifecycle({
|
|
1247
|
+
phase: 'final',
|
|
1248
|
+
requestId,
|
|
1249
|
+
sessionId,
|
|
1250
|
+
toolName: pending.toolName,
|
|
1251
|
+
decision: normalizedDecision === 'deny' ? 'deny' : 'allow',
|
|
1252
|
+
decisionSource: 'approval',
|
|
1253
|
+
policyCode: normalizedDecision === 'deny' ? 'user_approval_denied' : undefined,
|
|
1254
|
+
reason: normalizedDecision === 'deny' ? 'user approval denied' : 'user approval accepted',
|
|
1255
|
+
permissionMode: pending.permissionMode,
|
|
1256
|
+
agentAid: pending.selfAid,
|
|
1257
|
+
agentName: pending.agentName,
|
|
1258
|
+
role: pending.role,
|
|
1259
|
+
taskId: pending.taskId,
|
|
1260
|
+
summary: pending.auditSummary,
|
|
1261
|
+
response: normalizedDecision,
|
|
1262
|
+
});
|
|
1128
1263
|
const decisionCausation = deriveCausation(pending.causation);
|
|
1129
1264
|
if (normalizedDecision === 'always') {
|
|
1130
1265
|
this.addTemporaryGrant(pending, decisionCausation);
|
|
@@ -1136,22 +1271,6 @@ export class PermissionGateway {
|
|
|
1136
1271
|
});
|
|
1137
1272
|
const decisionSource = 'approval';
|
|
1138
1273
|
const policyCode = normalizedDecision === 'deny' ? 'user_approval_denied' : undefined;
|
|
1139
|
-
if (normalizedDecision === 'deny') {
|
|
1140
|
-
auditPermissionDecision({
|
|
1141
|
-
requestId,
|
|
1142
|
-
sessionId,
|
|
1143
|
-
toolName: pending.toolName,
|
|
1144
|
-
decisionSource,
|
|
1145
|
-
policyCode: 'user_approval_denied',
|
|
1146
|
-
reason: 'user approval denied',
|
|
1147
|
-
permissionMode: pending.permissionMode,
|
|
1148
|
-
agentAid: pending.selfAid,
|
|
1149
|
-
agentName: pending.agentName,
|
|
1150
|
-
role: pending.role,
|
|
1151
|
-
taskId: pending.taskId,
|
|
1152
|
-
summary: pending.auditSummary,
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
1274
|
this.eventBus?.publish({ type: 'permission:resolved', sessionId, requestId, toolName: pending.toolName, approved: normalizedDecision !== 'deny', ...(policyCode ? { decisionSource, policyCode } : { decisionSource }), causation: decisionCausation });
|
|
1156
1275
|
return true;
|
|
1157
1276
|
}
|
|
@@ -1215,6 +1334,23 @@ export class PermissionGateway {
|
|
|
1215
1334
|
this.clearPendingResources(pending);
|
|
1216
1335
|
pending.interactionRouter?.cancel(requestId, reason);
|
|
1217
1336
|
pending.resolve('deny');
|
|
1337
|
+
auditPermissionLifecycle({
|
|
1338
|
+
phase: 'response',
|
|
1339
|
+
requestId,
|
|
1340
|
+
sessionId,
|
|
1341
|
+
toolName: pending.toolName,
|
|
1342
|
+
decision: 'deny',
|
|
1343
|
+
decisionSource: 'infrastructure',
|
|
1344
|
+
policyCode: `approval_${reason}`,
|
|
1345
|
+
reason,
|
|
1346
|
+
permissionMode: pending.permissionMode,
|
|
1347
|
+
agentAid: pending.selfAid,
|
|
1348
|
+
agentName: pending.agentName,
|
|
1349
|
+
role: pending.role,
|
|
1350
|
+
taskId: pending.taskId,
|
|
1351
|
+
summary: pending.auditSummary,
|
|
1352
|
+
response: reason,
|
|
1353
|
+
});
|
|
1218
1354
|
auditPermissionDecision({
|
|
1219
1355
|
requestId,
|
|
1220
1356
|
sessionId,
|
|
@@ -2161,10 +2161,11 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2161
2161
|
: command;
|
|
2162
2162
|
const tempCheck = checkManagedTempShellCommand(commandForPolicy, managedTempOptions(context));
|
|
2163
2163
|
if (tempCheck.kind === 'deny') {
|
|
2164
|
+
const currentTmpDir = resolveManagedTempDir(managedTempOptions(context).env);
|
|
2164
2165
|
return {
|
|
2165
2166
|
behavior: 'deny',
|
|
2166
2167
|
input,
|
|
2167
|
-
message: `🔒 临时文件必须位于当前会话的 $TMPDIR
|
|
2168
|
+
message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内(当前值:${currentTmpDir ?? '<不可用>'}):${tempCheck.reason}`,
|
|
2168
2169
|
policyCode: 'temporary_path_outside_tmpdir',
|
|
2169
2170
|
};
|
|
2170
2171
|
}
|
|
@@ -2215,14 +2216,20 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2215
2216
|
const ownAid = !!managedAidArgv
|
|
2216
2217
|
&& !!context.selfAid
|
|
2217
2218
|
&& managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
|
|
2218
|
-
const
|
|
2219
|
+
const isManagedAidPublicRead = !!context.selfAid
|
|
2220
|
+
&& !!managedAidArgv
|
|
2219
2221
|
&& managedAidArgv[0] === 'ec'
|
|
2220
2222
|
&& managedAidArgv[1] === 'aid'
|
|
2221
|
-
&&
|
|
2222
|
-
&& ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
|
|
2223
|
+
&& ((managedAidArgv.length === 4 && managedAidArgv[2] === 'lookup')
|
|
2223
2224
|
|| (managedAidArgv.length === 5
|
|
2224
2225
|
&& managedAidArgv[2] === 'agentmd'
|
|
2225
2226
|
&& managedAidArgv[3] === 'get'));
|
|
2227
|
+
const isSelfManagedAidShow = !!managedAidArgv
|
|
2228
|
+
&& managedAidArgv.length === 4
|
|
2229
|
+
&& managedAidArgv[0] === 'ec'
|
|
2230
|
+
&& managedAidArgv[1] === 'aid'
|
|
2231
|
+
&& managedAidArgv[2] === 'show'
|
|
2232
|
+
&& ownAid;
|
|
2226
2233
|
const isSelfAgentMdPublish = !!managedAidArgv
|
|
2227
2234
|
&& managedAidArgv.length === 5
|
|
2228
2235
|
&& managedAidArgv[0] === 'ec'
|
|
@@ -2232,14 +2239,14 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2232
2239
|
&& ownAid;
|
|
2233
2240
|
const canPublishSelfAgentMd = isSelfAgentMdPublish
|
|
2234
2241
|
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
2235
|
-
const isAllowedManagedAidCommand =
|
|
2242
|
+
const isAllowedManagedAidCommand = isManagedAidPublicRead || isSelfManagedAidShow || canPublishSelfAgentMd;
|
|
2236
2243
|
if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
|
|
2237
2244
|
return {
|
|
2238
2245
|
behavior: 'deny',
|
|
2239
2246
|
input,
|
|
2240
2247
|
message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
2241
2248
|
? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
|
|
2242
|
-
: '🔒 EvolCore
|
|
2249
|
+
: '🔒 EvolCore 托管会话允许 `ec aid show <当前 self AID>`、`ec aid lookup <任意合法 AID>` 或 `ec aid agentmd get <任意合法 AID>`;`ec aid agentmd put` 还要求针对当前 self AID 且处于 request 或 bypass。可选后缀仅限 `--format json`。禁止列举本地身份、切换身份或对其它 AID 执行 show/put。',
|
|
2243
2250
|
policyCode: 'managed_ec_aid_scope_forbidden',
|
|
2244
2251
|
};
|
|
2245
2252
|
}
|