evolcore 0.0.20 → 0.0.21
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 +22 -0
- package/README.md +58 -9
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +379 -108
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +402 -135
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +182 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +409 -88
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +69 -115
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +135 -17
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +150 -2
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-service.js +6 -2
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +486 -68
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +410 -54
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-policy.js +575 -21
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +253 -46
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/schemas/_meta.json +4 -2
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -27,6 +27,13 @@ export class StreamDebouncer {
|
|
|
27
27
|
* 否则新建窗口。timer 到期后自动 flush。
|
|
28
28
|
*/
|
|
29
29
|
submit(key, message, enqueue) {
|
|
30
|
+
// A per-call permission override is an authorization boundary. Flush any
|
|
31
|
+
// earlier ordinary input first, then enqueue the authorized call on its
|
|
32
|
+
// own so neither side can inherit or erase the override during merging.
|
|
33
|
+
if (message.executionPermissionOverride) {
|
|
34
|
+
const preceding = this.flush(key, enqueue);
|
|
35
|
+
return preceding ? preceding.then(() => enqueue(message)) : enqueue(message);
|
|
36
|
+
}
|
|
30
37
|
const { content, images, mentions, messageId, sourceMessageIds, replyContext, ...rest } = message;
|
|
31
38
|
return new Promise((resolve, reject) => {
|
|
32
39
|
const entry = { messageId, sourceMessageIds, content, images, mentions, replyContext, rest, resolve, reject };
|
|
@@ -79,7 +86,7 @@ export class StreamDebouncer {
|
|
|
79
86
|
flush(key, enqueue) {
|
|
80
87
|
const win = this.pending.get(key);
|
|
81
88
|
if (!win)
|
|
82
|
-
return;
|
|
89
|
+
return undefined;
|
|
83
90
|
this.pending.delete(key);
|
|
84
91
|
clearTimeout(win.timer);
|
|
85
92
|
clearTimeout(win.maxWaitTimer);
|
|
@@ -130,7 +137,7 @@ export class StreamDebouncer {
|
|
|
130
137
|
};
|
|
131
138
|
const resolves = entries.map(e => e.resolve);
|
|
132
139
|
const rejects = entries.map(e => e.reject);
|
|
133
|
-
enqueue(merged).then(() => resolves.forEach(r => r()), (e) => rejects.forEach(r => r(e)));
|
|
140
|
+
return enqueue(merged).then(() => resolves.forEach(r => r()), (e) => rejects.forEach(r => r(e)));
|
|
134
141
|
}
|
|
135
142
|
/** 当前挂起的 key 数量(用于测试/调试) */
|
|
136
143
|
get pendingCount() {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
|
|
14
14
|
import { buildModelRequestHeaders } from '../../agents/request-identity.js';
|
|
15
|
+
import { appendRequestPath, appendV1RequestPath } from '../../agents/request-identity.js';
|
|
15
16
|
import { activeBaseagent } from './config-scope.js';
|
|
16
17
|
import { resolveEffective } from '../../config/config-manager.js';
|
|
17
18
|
import { loadDefaults } from '../../config-store.js';
|
|
@@ -33,6 +34,7 @@ function resolveCreds(self, ba) {
|
|
|
33
34
|
apiKey: r.apiKey,
|
|
34
35
|
agentAid: self,
|
|
35
36
|
headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
37
|
+
queryParams: r.queryParams,
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
if (baseagent === 'ecagent') {
|
|
@@ -43,6 +45,7 @@ function resolveCreds(self, ba) {
|
|
|
43
45
|
apiKey: r.apiKey,
|
|
44
46
|
agentAid: self,
|
|
45
47
|
headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
48
|
+
queryParams: r.queryParams,
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
if (baseagent === 'gemini')
|
|
@@ -56,6 +59,7 @@ function resolveCreds(self, ba) {
|
|
|
56
59
|
apiKey: r.apiKey,
|
|
57
60
|
agentAid: self,
|
|
58
61
|
headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
62
|
+
queryParams: r.queryParams,
|
|
59
63
|
};
|
|
60
64
|
}
|
|
61
65
|
catch {
|
|
@@ -275,22 +279,26 @@ async function loadCatalog(self, baseagent) {
|
|
|
275
279
|
if (cliModels)
|
|
276
280
|
return { models: cliModels, source: 'codex-cli' };
|
|
277
281
|
}
|
|
278
|
-
const { baseUrl, apiKey, headers } = resolveCreds(self, baseagent);
|
|
282
|
+
const { baseUrl, apiKey, headers, queryParams } = resolveCreds(self, baseagent);
|
|
279
283
|
if (baseUrl) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
284
|
+
try {
|
|
285
|
+
const urls = [...new Set([
|
|
286
|
+
appendV1RequestPath(baseUrl, 'models', queryParams),
|
|
287
|
+
appendRequestPath(baseUrl, 'models', queryParams),
|
|
288
|
+
])];
|
|
289
|
+
// Probe compatible routes concurrently so a dead route does not add
|
|
290
|
+
// another full timeout before the other route is attempted.
|
|
291
|
+
const results = await Promise.all(urls.map(url => fetchModelList(url, apiKey, headers)));
|
|
292
|
+
const hitIndex = results.findIndex((models) => !!models);
|
|
293
|
+
if (hitIndex >= 0) {
|
|
294
|
+
return {
|
|
295
|
+
models: withAliases(results[hitIndex], baseagent),
|
|
296
|
+
source: hitIndex === 0 ? 'v1/models' : 'models',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// Invalid configured URLs behave like other unavailable gateways.
|
|
294
302
|
}
|
|
295
303
|
}
|
|
296
304
|
// This remote directory is Claude-only. Never use it for Codex or Gemini.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
|
|
2
2
|
import { buildModelRequestHeaders } from '../../agents/request-identity.js';
|
|
3
|
+
import { appendV1RequestPath } from '../../agents/request-identity.js';
|
|
3
4
|
import { resolveEffective, resolveEffectiveFieldWithSource } from '../../config/config-manager.js';
|
|
4
5
|
import { constrainResolvedModelForRole } from './model-permission.js';
|
|
5
6
|
import { getCatalog } from './model-catalog.js';
|
|
@@ -27,6 +28,7 @@ function resolveGatewayCreds(self, baseagent) {
|
|
|
27
28
|
baseUrl: resolved.baseUrl,
|
|
28
29
|
apiKey: resolved.apiKey,
|
|
29
30
|
headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
|
|
31
|
+
queryParams: resolved.queryParams,
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
if (baseagent === 'claude') {
|
|
@@ -36,6 +38,7 @@ function resolveGatewayCreds(self, baseagent) {
|
|
|
36
38
|
baseUrl: resolved.baseUrl,
|
|
37
39
|
apiKey: resolved.apiKey,
|
|
38
40
|
headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
|
|
41
|
+
queryParams: resolved.queryParams,
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
if (baseagent === 'ecagent') {
|
|
@@ -45,6 +48,7 @@ function resolveGatewayCreds(self, baseagent) {
|
|
|
45
48
|
baseUrl: resolved.baseUrl,
|
|
46
49
|
apiKey: resolved.apiKey,
|
|
47
50
|
headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
|
|
51
|
+
queryParams: resolved.queryParams,
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -64,15 +68,23 @@ async function responseError(resp) {
|
|
|
64
68
|
}
|
|
65
69
|
return String(message).slice(0, 80);
|
|
66
70
|
}
|
|
67
|
-
export async function probeModel(baseUrl, apiKey, modelId, timeoutMs, requestHeaders) {
|
|
68
|
-
|
|
71
|
+
export async function probeModel(baseUrl, apiKey, modelId, timeoutMs, requestHeaders, queryParams) {
|
|
72
|
+
let anthropicUrl;
|
|
73
|
+
let openaiUrl;
|
|
74
|
+
try {
|
|
75
|
+
anthropicUrl = appendV1RequestPath(baseUrl, 'messages', queryParams);
|
|
76
|
+
openaiUrl = appendV1RequestPath(baseUrl, 'chat/completions', queryParams);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return { ok: false, ms: 0, error: '网关地址无效', style: 'unknown' };
|
|
80
|
+
}
|
|
69
81
|
const authHeaders = apiKey
|
|
70
82
|
? { 'x-api-key': apiKey, Authorization: `Bearer ${apiKey}` }
|
|
71
83
|
: {};
|
|
72
84
|
const deadline = Date.now() + timeoutMs;
|
|
73
85
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
74
86
|
const anthropicStartedAt = Date.now();
|
|
75
|
-
const anthropic = await fetchWithTimeout(
|
|
87
|
+
const anthropic = await fetchWithTimeout(anthropicUrl, {
|
|
76
88
|
method: 'POST',
|
|
77
89
|
headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', ...authHeaders, ...requestHeaders },
|
|
78
90
|
body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
|
|
@@ -90,7 +102,7 @@ export async function probeModel(baseUrl, apiKey, modelId, timeoutMs, requestHea
|
|
|
90
102
|
return { ok: false, ms: Date.now() - anthropicStartedAt, error: '探测超时', style: 'unknown' };
|
|
91
103
|
}
|
|
92
104
|
const openaiStartedAt = Date.now();
|
|
93
|
-
const openai = await fetchWithTimeout(
|
|
105
|
+
const openai = await fetchWithTimeout(openaiUrl, {
|
|
94
106
|
method: 'POST',
|
|
95
107
|
headers: { 'Content-Type': 'application/json', ...authHeaders, ...requestHeaders },
|
|
96
108
|
body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
|
|
@@ -117,16 +129,22 @@ export async function runModelCheck(input) {
|
|
|
117
129
|
steps.push(step);
|
|
118
130
|
input.onStep?.(step);
|
|
119
131
|
};
|
|
120
|
-
const { baseUrl, apiKey, headers } = resolveGatewayCreds(input.self, input.baseagent);
|
|
132
|
+
const { baseUrl, apiKey, headers, queryParams } = resolveGatewayCreds(input.self, input.baseagent);
|
|
121
133
|
if (!baseUrl) {
|
|
122
134
|
log({ label: '网关地址', ok: false, detail: '未配置受支持的自定义网关,跳过 API 探测' });
|
|
123
135
|
}
|
|
124
136
|
else {
|
|
125
137
|
const startedAt = Date.now();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
let ping = null;
|
|
139
|
+
try {
|
|
140
|
+
ping = await fetchWithTimeout(appendV1RequestPath(baseUrl, 'models', queryParams), {
|
|
141
|
+
method: 'GET',
|
|
142
|
+
headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
|
|
143
|
+
}, 4000);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Report malformed URLs through the same failed-connectivity step.
|
|
147
|
+
}
|
|
130
148
|
const elapsed = Date.now() - startedAt;
|
|
131
149
|
log(ping
|
|
132
150
|
? { label: '网关连通', ok: true, detail: baseUrl, ms: elapsed }
|
|
@@ -169,7 +187,7 @@ export async function runModelCheck(input) {
|
|
|
169
187
|
];
|
|
170
188
|
for (let index = 0; index < targets.length; index += 2) {
|
|
171
189
|
const batch = await Promise.all(targets.slice(index, index + 2).map(async (target) => {
|
|
172
|
-
const result = await probeModel(baseUrl, apiKey, target.probe, 10_000, headers);
|
|
190
|
+
const result = await probeModel(baseUrl, apiKey, target.probe, 10_000, headers, queryParams);
|
|
173
191
|
return { id: target.display, ...result };
|
|
174
192
|
}));
|
|
175
193
|
for (const result of batch) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { hasTrustedPrincipal } from '../auth/authenticated-actor.js';
|
|
3
3
|
import { renderActionAsText } from '../interaction-router.js';
|
|
4
|
-
import { buildEnvelope, sendInteractionPayload } from '../message/message-utils.js';
|
|
4
|
+
import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from '../message/message-utils.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import { summarizeToolInput } from '../../utils/tool-summary.js';
|
|
7
7
|
import { createRootCausation, deriveCausation, normalizeCausation } from '../causation/context.js';
|
|
8
8
|
import { recordCausationSpan } from '../causation/audit.js';
|
|
9
9
|
import { checkDangerousCommand } from './tool-policy.js';
|
|
10
|
-
import { resolveProtectedCandidate } from '
|
|
10
|
+
import { resolveProtectedCandidate } from './protected-paths.js';
|
|
11
|
+
import { auditPermissionDecision } from '../auth/authorization-audit.js';
|
|
11
12
|
export async function requestDangerousCommandPermission(gateway, sessionId, toolName, input, sendPrompt, context, grantScope = 'default', mode = 'request') {
|
|
12
13
|
const dangerCheck = checkDangerousCommand(toolName, input);
|
|
13
14
|
if (!dangerCheck.isDangerous) {
|
|
@@ -18,6 +19,19 @@ export async function requestDangerousCommandPermission(gateway, sessionId, tool
|
|
|
18
19
|
}
|
|
19
20
|
if (!gateway || !sendPrompt) {
|
|
20
21
|
logger.warn(`[PermissionGateway] Dangerous operation denied because approval is unavailable: session=${sessionId} tool=${toolName}`);
|
|
22
|
+
auditPermissionDecision({
|
|
23
|
+
requestId: `perm-infra-${randomUUID()}`,
|
|
24
|
+
sessionId,
|
|
25
|
+
toolName,
|
|
26
|
+
decisionSource: 'infrastructure',
|
|
27
|
+
policyCode: 'approval_channel_unavailable',
|
|
28
|
+
reason: 'dangerous operation approval channel is unavailable',
|
|
29
|
+
permissionMode: context?.permissionMode,
|
|
30
|
+
agentAid: context?.selfAid,
|
|
31
|
+
agentName: context?.agentName,
|
|
32
|
+
role: context?.role,
|
|
33
|
+
taskId: context?.taskId,
|
|
34
|
+
});
|
|
21
35
|
return { matched: true, decision: 'deny' };
|
|
22
36
|
}
|
|
23
37
|
const decision = await gateway.requestPermission(sessionId, dangerCheck.cacheKey, input, sendPrompt, context, `⚠️ ${dangerCheck.command}`, dangerCheck.reason, grantScope, dangerCheck.kind === 'git-destructive' ? 'agent_manager' : undefined);
|
|
@@ -432,6 +446,21 @@ export class PermissionGateway {
|
|
|
432
446
|
if (sessionGrant)
|
|
433
447
|
this.addTemporaryGrant(pending, decisionCausation);
|
|
434
448
|
pending.resolve(decision);
|
|
449
|
+
if (!approved) {
|
|
450
|
+
auditPermissionDecision({
|
|
451
|
+
requestId,
|
|
452
|
+
sessionId,
|
|
453
|
+
toolName: pending.toolName,
|
|
454
|
+
decisionSource: 'approval',
|
|
455
|
+
policyCode: 'user_approval_denied',
|
|
456
|
+
reason: 'owner approval denied',
|
|
457
|
+
permissionMode: pending.permissionMode,
|
|
458
|
+
agentAid: pending.selfAid,
|
|
459
|
+
agentName: pending.agentName,
|
|
460
|
+
role: pending.role,
|
|
461
|
+
taskId: pending.taskId,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
435
464
|
void this.notifyPermissionState(pending, approved
|
|
436
465
|
? { state: 'approved', requestId, toolName: pending.toolName }
|
|
437
466
|
: {
|
|
@@ -446,7 +475,17 @@ export class PermissionGateway {
|
|
|
446
475
|
refs: { permissionRequestId: requestId, sessionId },
|
|
447
476
|
reason: approved ? undefined : 'denied',
|
|
448
477
|
});
|
|
449
|
-
this.eventBus?.publish({
|
|
478
|
+
this.eventBus?.publish({
|
|
479
|
+
type: 'permission:resolved',
|
|
480
|
+
sessionId,
|
|
481
|
+
requestId,
|
|
482
|
+
toolName: pending.toolName,
|
|
483
|
+
approved,
|
|
484
|
+
...(approved
|
|
485
|
+
? { decisionSource: 'approval' }
|
|
486
|
+
: { decisionSource: 'approval', policyCode: 'user_approval_denied' }),
|
|
487
|
+
causation: decisionCausation,
|
|
488
|
+
});
|
|
450
489
|
return true;
|
|
451
490
|
}
|
|
452
491
|
expireCrossSessionPermission(sessionId, requestId, reason, notifyState = true, cancelled = reason === 'cancelled') {
|
|
@@ -458,6 +497,21 @@ export class PermissionGateway {
|
|
|
458
497
|
this.crossSessionPending.delete(requestId);
|
|
459
498
|
this.syncManagementRevalidationTimer();
|
|
460
499
|
pending.resolve('deny');
|
|
500
|
+
const decisionSource = 'infrastructure';
|
|
501
|
+
const policyCode = cancelled ? 'approval_cancelled' : `approval_${reason}`;
|
|
502
|
+
auditPermissionDecision({
|
|
503
|
+
requestId,
|
|
504
|
+
sessionId,
|
|
505
|
+
toolName: pending.toolName,
|
|
506
|
+
decisionSource,
|
|
507
|
+
policyCode,
|
|
508
|
+
reason,
|
|
509
|
+
permissionMode: pending.permissionMode,
|
|
510
|
+
agentAid: pending.selfAid,
|
|
511
|
+
agentName: pending.agentName,
|
|
512
|
+
role: pending.role,
|
|
513
|
+
taskId: pending.taskId,
|
|
514
|
+
});
|
|
461
515
|
if (notifyState) {
|
|
462
516
|
const failure = permissionFailure(reason);
|
|
463
517
|
void this.notifyPermissionState(pending, {
|
|
@@ -481,6 +535,8 @@ export class PermissionGateway {
|
|
|
481
535
|
requestId,
|
|
482
536
|
toolName: pending.toolName,
|
|
483
537
|
reason,
|
|
538
|
+
decisionSource: 'infrastructure',
|
|
539
|
+
policyCode: `approval_${reason}`,
|
|
484
540
|
causation: decisionCausation,
|
|
485
541
|
});
|
|
486
542
|
}
|
|
@@ -492,6 +548,8 @@ export class PermissionGateway {
|
|
|
492
548
|
toolName: pending.toolName,
|
|
493
549
|
reason,
|
|
494
550
|
approved: false,
|
|
551
|
+
decisionSource: 'infrastructure',
|
|
552
|
+
policyCode: `approval_${reason}`,
|
|
495
553
|
causation: decisionCausation,
|
|
496
554
|
});
|
|
497
555
|
}
|
|
@@ -502,6 +560,18 @@ export class PermissionGateway {
|
|
|
502
560
|
const interactionRouter = context.interactionRouter;
|
|
503
561
|
if (!approval || !interactionRouter) {
|
|
504
562
|
await sendPrompt('当前会话权限不足,且没有可用的 owner 审批通道。');
|
|
563
|
+
auditPermissionDecision({
|
|
564
|
+
sessionId,
|
|
565
|
+
toolName: challenge.toolName,
|
|
566
|
+
decisionSource: 'infrastructure',
|
|
567
|
+
policyCode: 'approval_route_unavailable',
|
|
568
|
+
reason: 'owner approval routing context is unavailable',
|
|
569
|
+
permissionMode: context.permissionMode,
|
|
570
|
+
agentAid: context.selfAid,
|
|
571
|
+
agentName: context.agentName,
|
|
572
|
+
role: context.role,
|
|
573
|
+
taskId: context.taskId,
|
|
574
|
+
});
|
|
505
575
|
return 'deny';
|
|
506
576
|
}
|
|
507
577
|
const { id: requestId, toolName, toolInput, summary: displaySummary, reason } = challenge;
|
|
@@ -593,6 +663,10 @@ export class PermissionGateway {
|
|
|
593
663
|
approverChannelKey: route.approverChannelKey,
|
|
594
664
|
approverPrincipalId: route.approverPrincipalId,
|
|
595
665
|
selfAid: approval.selfAid,
|
|
666
|
+
agentName: context.agentName,
|
|
667
|
+
permissionMode: context.permissionMode,
|
|
668
|
+
role: context.role,
|
|
669
|
+
taskId: context.taskId,
|
|
596
670
|
originPrincipalId: approval.originPrincipalId,
|
|
597
671
|
originAuthRevision: approval.originAuthRevision,
|
|
598
672
|
reauthorizeApproval: approval.reauthorizeApproval,
|
|
@@ -661,7 +735,8 @@ export class PermissionGateway {
|
|
|
661
735
|
});
|
|
662
736
|
let sent = false;
|
|
663
737
|
try {
|
|
664
|
-
|
|
738
|
+
const receipt = await sendInteractionPayload(route.adapter, envelope, interaction, renderActionAsText(interaction), ownerReplyContext);
|
|
739
|
+
sent = isInteractionSendAccepted(receipt);
|
|
665
740
|
}
|
|
666
741
|
catch (error) {
|
|
667
742
|
logger.warn(`[PermissionGateway] Owner approval delivery failed: session=${sessionId} tool=${toolName} error=${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -700,6 +775,20 @@ export class PermissionGateway {
|
|
|
700
775
|
this.pending.delete(requestId);
|
|
701
776
|
this.syncManagementRevalidationTimer();
|
|
702
777
|
pending.resolve('deny');
|
|
778
|
+
const decisionSource = reason === 'denied' ? 'approval' : 'infrastructure';
|
|
779
|
+
auditPermissionDecision({
|
|
780
|
+
requestId,
|
|
781
|
+
sessionId,
|
|
782
|
+
toolName: pending.toolName,
|
|
783
|
+
decisionSource,
|
|
784
|
+
policyCode: `approval_${reason}`,
|
|
785
|
+
reason,
|
|
786
|
+
permissionMode: pending.permissionMode,
|
|
787
|
+
agentAid: pending.selfAid,
|
|
788
|
+
agentName: pending.agentName,
|
|
789
|
+
role: pending.role,
|
|
790
|
+
taskId: pending.taskId,
|
|
791
|
+
});
|
|
703
792
|
const decisionCausation = deriveCausation(pending.causation);
|
|
704
793
|
recordCausationSpan(decisionCausation, 'permission.decision', {
|
|
705
794
|
status: 'failed',
|
|
@@ -713,6 +802,8 @@ export class PermissionGateway {
|
|
|
713
802
|
toolName: pending.toolName,
|
|
714
803
|
reason,
|
|
715
804
|
approved: false,
|
|
805
|
+
decisionSource,
|
|
806
|
+
policyCode: `approval_${reason}`,
|
|
716
807
|
causation: decisionCausation,
|
|
717
808
|
});
|
|
718
809
|
return true;
|
|
@@ -725,6 +816,7 @@ export class PermissionGateway {
|
|
|
725
816
|
?? context?.approvalRouting?.approverPolicy
|
|
726
817
|
?? 'requester';
|
|
727
818
|
if (context?.approvalInteractionPolicy === 'deny') {
|
|
819
|
+
const denialRequestId = `perm-policy-${randomUUID()}`;
|
|
728
820
|
const anomaly = {
|
|
729
821
|
code: 'runtime_permission_denied',
|
|
730
822
|
severity: 'warning',
|
|
@@ -732,6 +824,12 @@ export class PermissionGateway {
|
|
|
732
824
|
occurredAt: Date.now(),
|
|
733
825
|
toolName,
|
|
734
826
|
policy: effectiveApproverPolicy,
|
|
827
|
+
policyCode: 'permission_mode_denied',
|
|
828
|
+
decisionSource: 'policy',
|
|
829
|
+
agentAid: context.selfAid,
|
|
830
|
+
sessionId,
|
|
831
|
+
requestId: denialRequestId,
|
|
832
|
+
permissionMode: context.permissionMode,
|
|
735
833
|
summary: structuredApprovalAuditSummary(toolInput),
|
|
736
834
|
effect: 'operation_skipped',
|
|
737
835
|
};
|
|
@@ -742,10 +840,35 @@ export class PermissionGateway {
|
|
|
742
840
|
logger.warn(`[PermissionGateway] Failed to record unattended denial: session=${sessionId} tool=${toolName} error=${error instanceof Error ? error.message : String(error)}`);
|
|
743
841
|
}
|
|
744
842
|
logger.info(`[PermissionGateway] Unattended runtime permission denied: session=${sessionId} tool=${toolName} policy=${effectiveApproverPolicy}`);
|
|
843
|
+
auditPermissionDecision({
|
|
844
|
+
requestId: denialRequestId,
|
|
845
|
+
sessionId,
|
|
846
|
+
toolName,
|
|
847
|
+
decisionSource: 'policy',
|
|
848
|
+
policyCode: 'permission_mode_denied',
|
|
849
|
+
reason: 'permission mode denied runtime approval',
|
|
850
|
+
permissionMode: context.permissionMode,
|
|
851
|
+
agentAid: context.selfAid,
|
|
852
|
+
agentName: context.agentName,
|
|
853
|
+
role: context.role,
|
|
854
|
+
taskId: context.taskId,
|
|
855
|
+
});
|
|
745
856
|
return 'deny';
|
|
746
857
|
}
|
|
747
858
|
if (!context?.userId) {
|
|
748
859
|
await sendPrompt('当前操作需要授权,但无法验证申请人身份。');
|
|
860
|
+
auditPermissionDecision({
|
|
861
|
+
sessionId,
|
|
862
|
+
toolName,
|
|
863
|
+
decisionSource: 'infrastructure',
|
|
864
|
+
policyCode: 'approval_identity_missing',
|
|
865
|
+
reason: 'approval requester identity is unavailable',
|
|
866
|
+
permissionMode: context?.permissionMode,
|
|
867
|
+
agentAid: context?.selfAid,
|
|
868
|
+
agentName: context?.agentName,
|
|
869
|
+
role: context?.role,
|
|
870
|
+
taskId: context?.taskId,
|
|
871
|
+
});
|
|
749
872
|
return 'deny';
|
|
750
873
|
}
|
|
751
874
|
const grantMatch = permissionGrantMatch(toolName, toolInput, matchOptions);
|
|
@@ -803,8 +926,23 @@ export class PermissionGateway {
|
|
|
803
926
|
toolName,
|
|
804
927
|
reason: route.reason,
|
|
805
928
|
approved: false,
|
|
929
|
+
decisionSource: 'infrastructure',
|
|
930
|
+
policyCode: 'approval_route_unavailable',
|
|
806
931
|
causation: decisionCausation,
|
|
807
932
|
});
|
|
933
|
+
auditPermissionDecision({
|
|
934
|
+
requestId,
|
|
935
|
+
sessionId,
|
|
936
|
+
toolName,
|
|
937
|
+
decisionSource: 'infrastructure',
|
|
938
|
+
policyCode: 'approval_route_unavailable',
|
|
939
|
+
reason: route.reason,
|
|
940
|
+
permissionMode: context.permissionMode,
|
|
941
|
+
agentAid: context.selfAid,
|
|
942
|
+
agentName: context.agentName,
|
|
943
|
+
role: context.role,
|
|
944
|
+
taskId: context.taskId,
|
|
945
|
+
});
|
|
808
946
|
return 'deny';
|
|
809
947
|
}
|
|
810
948
|
if (route.kind === 'handoff') {
|
|
@@ -857,6 +995,7 @@ export class PermissionGateway {
|
|
|
857
995
|
approverChannelKey: route.approverChannelKey,
|
|
858
996
|
approverPrincipalId: route.approverPrincipalId,
|
|
859
997
|
selfAid: context?.approvalRouting?.selfAid,
|
|
998
|
+
agentName: context?.agentName,
|
|
860
999
|
originPrincipalId: context?.approvalRouting?.originPrincipalId,
|
|
861
1000
|
originAuthRevision: context?.approvalRouting?.originAuthRevision,
|
|
862
1001
|
reauthorizeApproval: context?.approvalRouting?.reauthorizeApproval,
|
|
@@ -864,6 +1003,9 @@ export class PermissionGateway {
|
|
|
864
1003
|
interactionRouter: context?.interactionRouter,
|
|
865
1004
|
causation: requestCausation,
|
|
866
1005
|
permissionStateChanged: context?.permissionStateChanged,
|
|
1006
|
+
permissionMode: context?.permissionMode,
|
|
1007
|
+
role: context?.role,
|
|
1008
|
+
taskId: context?.taskId,
|
|
867
1009
|
};
|
|
868
1010
|
pending.timeout = setTimeout(() => {
|
|
869
1011
|
this.failPendingPermission(sessionId, requestId, 'expired');
|
|
@@ -925,7 +1067,7 @@ export class PermissionGateway {
|
|
|
925
1067
|
: '同操作授权 30 分钟';
|
|
926
1068
|
const fallbackText = `🔐 权限请求 - ${toolName}\n${displaySummary}${reasonLine}\n回复 /perm ${requestId} allow 允许本次 / /perm ${requestId} always ${temporaryGrantLabel} / /perm ${requestId} deny 拒绝`;
|
|
927
1069
|
const result = await sendInteractionPayload(context.adapter, envelope, interaction, fallbackText, context.replyContext);
|
|
928
|
-
interactionSent =
|
|
1070
|
+
interactionSent = isInteractionSendAccepted(result);
|
|
929
1071
|
}
|
|
930
1072
|
catch (err) {
|
|
931
1073
|
// sendInteractionPayload 已内部捕获,但保险起见再 try/catch
|
|
@@ -979,7 +1121,24 @@ export class PermissionGateway {
|
|
|
979
1121
|
refs: { permissionRequestId: requestId, sessionId },
|
|
980
1122
|
reason: normalizedDecision,
|
|
981
1123
|
});
|
|
982
|
-
|
|
1124
|
+
const decisionSource = 'approval';
|
|
1125
|
+
const policyCode = normalizedDecision === 'deny' ? 'user_approval_denied' : undefined;
|
|
1126
|
+
if (normalizedDecision === 'deny') {
|
|
1127
|
+
auditPermissionDecision({
|
|
1128
|
+
requestId,
|
|
1129
|
+
sessionId,
|
|
1130
|
+
toolName: pending.toolName,
|
|
1131
|
+
decisionSource,
|
|
1132
|
+
policyCode: 'user_approval_denied',
|
|
1133
|
+
reason: 'user approval denied',
|
|
1134
|
+
permissionMode: pending.permissionMode,
|
|
1135
|
+
agentAid: pending.selfAid,
|
|
1136
|
+
agentName: pending.agentName,
|
|
1137
|
+
role: pending.role,
|
|
1138
|
+
taskId: pending.taskId,
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
this.eventBus?.publish({ type: 'permission:resolved', sessionId, requestId, toolName: pending.toolName, approved: normalizedDecision !== 'deny', ...(policyCode ? { decisionSource, policyCode } : { decisionSource }), causation: decisionCausation });
|
|
983
1142
|
return true;
|
|
984
1143
|
}
|
|
985
1144
|
/**
|
|
@@ -1042,6 +1201,19 @@ export class PermissionGateway {
|
|
|
1042
1201
|
this.clearPendingResources(pending);
|
|
1043
1202
|
pending.interactionRouter?.cancel(requestId, reason);
|
|
1044
1203
|
pending.resolve('deny');
|
|
1204
|
+
auditPermissionDecision({
|
|
1205
|
+
requestId,
|
|
1206
|
+
sessionId,
|
|
1207
|
+
toolName: pending.toolName,
|
|
1208
|
+
decisionSource: 'infrastructure',
|
|
1209
|
+
policyCode: `approval_${reason}`,
|
|
1210
|
+
reason,
|
|
1211
|
+
permissionMode: pending.permissionMode,
|
|
1212
|
+
agentAid: pending.selfAid,
|
|
1213
|
+
agentName: pending.agentName,
|
|
1214
|
+
role: pending.role,
|
|
1215
|
+
taskId: pending.taskId,
|
|
1216
|
+
});
|
|
1045
1217
|
this.pending.delete(requestId);
|
|
1046
1218
|
const decisionCausation = deriveCausation(pending.causation);
|
|
1047
1219
|
recordCausationSpan(decisionCausation, 'permission.decision', {
|
|
@@ -1055,6 +1227,8 @@ export class PermissionGateway {
|
|
|
1055
1227
|
requestId,
|
|
1056
1228
|
toolName: pending.toolName,
|
|
1057
1229
|
reason,
|
|
1230
|
+
decisionSource: 'infrastructure',
|
|
1231
|
+
policyCode: `approval_${reason}`,
|
|
1058
1232
|
causation: decisionCausation,
|
|
1059
1233
|
});
|
|
1060
1234
|
}
|