evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -10,10 +10,18 @@ export async function cmdQueue(args) {
|
|
|
10
10
|
|
|
11
11
|
查询:
|
|
12
12
|
--agent <aid> 必填,目标 agent(如 mybot.agentid.pub)
|
|
13
|
+
--after <time> 只显示 adapter 接收时间晚于该值的消息(epoch ms 或 ISO)
|
|
14
|
+
--before <time> 只显示 adapter 接收时间早于该值的消息(epoch ms 或 ISO)
|
|
13
15
|
--showid 显示 messageId
|
|
14
16
|
--full 显示完整消息内容(不截断)
|
|
15
17
|
--format json 以 JSON 格式输出
|
|
16
18
|
|
|
19
|
+
JSON 时间字段:
|
|
20
|
+
receivedAt adapter 收到消息时间(epoch ms)
|
|
21
|
+
enqueuedAt 实际进入队列时间(epoch ms)
|
|
22
|
+
queueWaitMs 排队等待时长(ms)
|
|
23
|
+
processingStartedAt 开始处理时间(epoch ms,active)
|
|
24
|
+
|
|
17
25
|
操作:
|
|
18
26
|
--clear 清空该 agent 的所有待处理消息
|
|
19
27
|
--cancel <messageId> 取消指定消息
|
|
@@ -21,6 +29,7 @@ export async function cmdQueue(args) {
|
|
|
21
29
|
|
|
22
30
|
示例:
|
|
23
31
|
ec queue --agent mybot.agentid.pub
|
|
32
|
+
ec queue --agent mybot.agentid.pub --after 2026-08-19T12:00:00+08:00
|
|
24
33
|
ec queue --agent mybot.agentid.pub --showid
|
|
25
34
|
ec queue --agent mybot.agentid.pub --clear
|
|
26
35
|
ec queue --agent mybot.agentid.pub --cancel msg_d4e5f6
|
|
@@ -46,6 +55,12 @@ export async function cmdQueue(args) {
|
|
|
46
55
|
process.exit(1);
|
|
47
56
|
}
|
|
48
57
|
const formatJson = formatValue === 'json';
|
|
58
|
+
const after = parseQueueTime(getArgValue(args, '--after'), '--after');
|
|
59
|
+
const before = parseQueueTime(getArgValue(args, '--before'), '--before');
|
|
60
|
+
if (after !== undefined && before !== undefined && after >= before) {
|
|
61
|
+
console.error('❌ --after 必须早于 --before');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
49
64
|
const clear = args.includes('--clear');
|
|
50
65
|
const cancelIdx = args.indexOf('--cancel');
|
|
51
66
|
const cancelCandidate = cancelIdx >= 0 && cancelIdx + 1 < args.length ? args[cancelIdx + 1] : undefined;
|
|
@@ -80,6 +95,8 @@ export async function cmdQueue(args) {
|
|
|
80
95
|
action,
|
|
81
96
|
messageId: cancelMsgId,
|
|
82
97
|
sessionKey,
|
|
98
|
+
after,
|
|
99
|
+
before,
|
|
83
100
|
sessionId: managed,
|
|
84
101
|
delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
|
|
85
102
|
} : {
|
|
@@ -88,6 +105,8 @@ export async function cmdQueue(args) {
|
|
|
88
105
|
action,
|
|
89
106
|
messageId: cancelMsgId,
|
|
90
107
|
sessionKey,
|
|
108
|
+
after,
|
|
109
|
+
before,
|
|
91
110
|
});
|
|
92
111
|
if (!result) {
|
|
93
112
|
console.error('错误: 无法连接 evolcore 服务');
|
|
@@ -118,6 +137,16 @@ export async function cmdQueue(args) {
|
|
|
118
137
|
}
|
|
119
138
|
console.log(renderQueueItemsCli(items, showId, full, agent));
|
|
120
139
|
}
|
|
140
|
+
function parseQueueTime(value, flag) {
|
|
141
|
+
if (value === undefined)
|
|
142
|
+
return undefined;
|
|
143
|
+
const parsed = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
|
|
144
|
+
if (!Number.isFinite(parsed)) {
|
|
145
|
+
console.error(`❌ ${flag} 必须是 epoch 毫秒或 ISO 时间: ${value}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
return parsed;
|
|
149
|
+
}
|
|
121
150
|
function renderQueueItemsCli(items, showId, full, aid) {
|
|
122
151
|
const lines = [`${aid} — 队列 (${items.length} 条)`, ''];
|
|
123
152
|
if (items.length === 0) {
|
|
@@ -151,6 +180,12 @@ function renderQueueItemsCli(items, showId, full, aid) {
|
|
|
151
180
|
elapsed = sec >= 60 ? `${Math.floor(sec / 60)}m${sec % 60}s` : `${sec}s`;
|
|
152
181
|
}
|
|
153
182
|
parts.push(` ${elapsed.padEnd(elapsedColWidth)} `);
|
|
183
|
+
const wait = item.queueWaitMs == null ? '—' : formatDuration(item.queueWaitMs);
|
|
184
|
+
parts.push(` wait:${wait.padEnd(6)} `);
|
|
185
|
+
if (item.receivedAt != null)
|
|
186
|
+
parts.push(` recv:${formatQueueTime(item.receivedAt)} `);
|
|
187
|
+
if (item.enqueuedAt != null)
|
|
188
|
+
parts.push(` enq:${formatQueueTime(item.enqueuedAt)} `);
|
|
154
189
|
// 内容列
|
|
155
190
|
const content = full ? item.preview.replace(/\.\.\.$/, '') : item.preview;
|
|
156
191
|
parts.push(`"${content}"`);
|
|
@@ -158,3 +193,10 @@ function renderQueueItemsCli(items, showId, full, aid) {
|
|
|
158
193
|
}
|
|
159
194
|
return lines.join('\n');
|
|
160
195
|
}
|
|
196
|
+
function formatDuration(ms) {
|
|
197
|
+
const sec = Math.max(0, Math.round(ms / 1000));
|
|
198
|
+
return sec >= 60 ? `${Math.floor(sec / 60)}m${sec % 60}s` : `${sec}s`;
|
|
199
|
+
}
|
|
200
|
+
function formatQueueTime(timestamp) {
|
|
201
|
+
return new Date(timestamp).toISOString();
|
|
202
|
+
}
|
|
@@ -14,7 +14,6 @@ import { WEB_PACKAGE_NAME } from '../product.js';
|
|
|
14
14
|
import { shouldSuppressRealRestart } from '../utils/restart-safety.js';
|
|
15
15
|
import { rotateStdoutLog } from '../utils/log-writer.js';
|
|
16
16
|
import { inspectDataMigrationRequirement } from '../core/data-migration.js';
|
|
17
|
-
import { serviceProxyNeedsEcweb, startEcwebIfEnabled } from './daemon-commands.js';
|
|
18
17
|
const execFileAsync = promisify(execFile);
|
|
19
18
|
// 清理 Claude Code 环境变量,防止 SDK 认为是嵌套会话
|
|
20
19
|
function cleanEnv() {
|
|
@@ -194,22 +193,12 @@ export async function cmdRestartMonitor() {
|
|
|
194
193
|
await notifyDaemonOwners(p, `⚠️ ${WEB_PACKAGE_NAME} 升级失败,使用当前版本继续\n${ecwebUpgrade.error ?? ''}`.trim(), log);
|
|
195
194
|
break;
|
|
196
195
|
}
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
const ecwebStartedBeforeDaemon = serviceProxyNeedsEcweb(loadDaemonConfig())
|
|
201
|
-
? await startEcwebIfEnabled(p)
|
|
202
|
-
: false;
|
|
203
|
-
if (ecwebStartedBeforeDaemon)
|
|
204
|
-
log('ECWeb started before daemon');
|
|
196
|
+
// ECWeb is owned by the daemon. Starting it here used to race with the
|
|
197
|
+
// daemon's own launcher and ECWeb's stale-instance cleanup, which could
|
|
198
|
+
// leave ServiceProxy pointed at a dead PID.
|
|
205
199
|
// 启动并检测 ready signal
|
|
206
200
|
let started = await spawnAndWaitReady(p, log, READY_TIMEOUT);
|
|
207
201
|
if (started) {
|
|
208
|
-
// 没有 Service Proxy 时可以等 daemon ready 后再启动;有代理时已在上方启动,
|
|
209
|
-
// 避免重新拉起造成实例记录和反向隧道短暂失配。
|
|
210
|
-
if (!ecwebStartedBeforeDaemon && await startEcwebIfEnabled(p)) {
|
|
211
|
-
log('ECWeb started after daemon');
|
|
212
|
-
}
|
|
213
202
|
log('✓ Service restarted successfully');
|
|
214
203
|
archiveSelfHealLog(p, log);
|
|
215
204
|
// 发起会话内的“重启成功”通知由新进程自行发送,此处只负责失败/自愈告警。
|
package/dist/cli/task-context.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { normalizeCausation } from '../core/causation/context.js';
|
|
2
|
+
import { RUNTIME_LOCK_DIR_ENV } from '../core/runtime-lock.js';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
2
7
|
export const TASK_RUNTIME_CONTEXT_ENV = 'EVOLCORE_TASK_RUNTIME_CONTEXT';
|
|
8
|
+
export const SESSION_RUNTIME_DIR_ENV = 'EVOLCORE_SESSION_RUNTIME_DIR';
|
|
9
|
+
export { RUNTIME_LOCK_DIR_ENV };
|
|
10
|
+
const generatedProcessManagedTempDirs = new Set();
|
|
11
|
+
let processTempCleanupRegistered = false;
|
|
3
12
|
function isPlainObject(value) {
|
|
4
13
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
5
14
|
}
|
|
@@ -23,10 +32,167 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
23
32
|
peerType: optionalString(value.peerType),
|
|
24
33
|
peerRole: optionalString(value.peerRole),
|
|
25
34
|
threadId: optionalString(value.threadId),
|
|
35
|
+
sessionRuntimeDir: optionalAbsolutePath(value.sessionRuntimeDir),
|
|
36
|
+
runtimeLockDir: optionalAbsolutePath(value.runtimeLockDir),
|
|
26
37
|
handoffIds: handoffIds?.length ? handoffIds : undefined,
|
|
27
38
|
causation: normalizeCausation(value.causation),
|
|
28
39
|
};
|
|
29
40
|
}
|
|
41
|
+
function optionalAbsolutePath(value) {
|
|
42
|
+
if (typeof value !== 'string' || !path.isAbsolute(value))
|
|
43
|
+
return undefined;
|
|
44
|
+
return path.resolve(value);
|
|
45
|
+
}
|
|
46
|
+
function isPrivateDirectory(directory) {
|
|
47
|
+
try {
|
|
48
|
+
const resolved = path.resolve(directory);
|
|
49
|
+
const stat = fs.lstatSync(resolved);
|
|
50
|
+
if (!stat.isDirectory()
|
|
51
|
+
|| stat.isSymbolicLink()
|
|
52
|
+
|| (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
53
|
+
|| (stat.mode & 0o077) !== 0)
|
|
54
|
+
return false;
|
|
55
|
+
// A private leaf below a symlinked ancestor can still escape the managed
|
|
56
|
+
// namespace. Walk the existing ancestor chain and reject such paths while
|
|
57
|
+
// allowing the normal public system temp parent (for generated roots).
|
|
58
|
+
let current = resolved;
|
|
59
|
+
while (true) {
|
|
60
|
+
const ancestor = fs.lstatSync(current);
|
|
61
|
+
if (ancestor.isSymbolicLink())
|
|
62
|
+
return false;
|
|
63
|
+
const parent = path.dirname(current);
|
|
64
|
+
if (parent === current)
|
|
65
|
+
break;
|
|
66
|
+
current = parent;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Ensure the daemon has a private process-wide temporary root.
|
|
76
|
+
*
|
|
77
|
+
* Service managers commonly omit TMPDIR. In that case create a private
|
|
78
|
+
* directory below the platform temp root and publish it for all managed
|
|
79
|
+
* runners. An explicitly supplied value is accepted only when it is already
|
|
80
|
+
* a private directory owned by this process. A shared system directory such
|
|
81
|
+
* as `/tmp`, a symlink, or a group/world-readable path is replaced with a
|
|
82
|
+
* fresh private root instead of becoming a capability for managed children.
|
|
83
|
+
*/
|
|
84
|
+
export function ensureProcessManagedTempDir() {
|
|
85
|
+
const configured = process.env.TMPDIR?.trim();
|
|
86
|
+
if (configured) {
|
|
87
|
+
if (!path.isAbsolute(configured)) {
|
|
88
|
+
throw new Error('managed TMPDIR is unset or not absolute');
|
|
89
|
+
}
|
|
90
|
+
const resolved = path.resolve(configured);
|
|
91
|
+
if (isPrivateDirectory(resolved))
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'evolcore-managed-tmp-'));
|
|
95
|
+
try {
|
|
96
|
+
fs.chmodSync(directory, 0o700);
|
|
97
|
+
if (!isPrivateDirectory(directory)) {
|
|
98
|
+
throw new Error(`managed temp path is not a private directory: ${directory}`);
|
|
99
|
+
}
|
|
100
|
+
process.env.TMPDIR = directory;
|
|
101
|
+
generatedProcessManagedTempDirs.add(directory);
|
|
102
|
+
if (!processTempCleanupRegistered) {
|
|
103
|
+
processTempCleanupRegistered = true;
|
|
104
|
+
process.once('exit', () => {
|
|
105
|
+
for (const candidate of generatedProcessManagedTempDirs) {
|
|
106
|
+
if (!path.basename(candidate).startsWith('evolcore-managed-tmp-'))
|
|
107
|
+
continue;
|
|
108
|
+
try {
|
|
109
|
+
if (isPrivateDirectory(candidate))
|
|
110
|
+
fs.rmSync(candidate, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Best-effort cleanup; the directory is private and uniquely named.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return directory;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
try {
|
|
122
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
catch { }
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function isRunnerOwnedSessionRuntimeDir(directory) {
|
|
129
|
+
const resolved = path.resolve(directory);
|
|
130
|
+
const base = path.basename(resolved);
|
|
131
|
+
const parent = path.dirname(resolved);
|
|
132
|
+
// Codex allocates a private, direct child named with a 24-character digest
|
|
133
|
+
// below its private evolcore-codex-* parent. The runner has already checked
|
|
134
|
+
// canonical containment before returning this path to the response engine.
|
|
135
|
+
const isCodexRuntime = /^[a-f0-9]{24}$/.test(base)
|
|
136
|
+
&& /^evolcore-codex-(?:\d+|user)$/.test(path.basename(parent));
|
|
137
|
+
return isCodexRuntime && isPrivateDirectory(parent) && isPrivateDirectory(resolved);
|
|
138
|
+
}
|
|
139
|
+
/** Whether a task-provided runtime directory stays inside the process-managed
|
|
140
|
+
* TMPDIR and is safe to use for transient writes. */
|
|
141
|
+
export function isManagedSessionRuntimeDir(directory) {
|
|
142
|
+
const tmpDir = process.env.TMPDIR?.trim();
|
|
143
|
+
if (!directory || !path.isAbsolute(directory) || !tmpDir || !path.isAbsolute(tmpDir))
|
|
144
|
+
return false;
|
|
145
|
+
const root = path.resolve(tmpDir);
|
|
146
|
+
const resolved = path.resolve(directory);
|
|
147
|
+
const relative = path.relative(root, resolved);
|
|
148
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
|
|
149
|
+
return false;
|
|
150
|
+
return isPrivateDirectory(root) && isPrivateDirectory(resolved);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Create a private runtime directory below the process-provided TMPDIR.
|
|
154
|
+
* There is deliberately no os.tmpdir() fallback: managed sessions must not
|
|
155
|
+
* silently escape the session-owned temporary workspace.
|
|
156
|
+
*/
|
|
157
|
+
export function ensureSessionRuntimeDir(sessionId) {
|
|
158
|
+
const base = process.env.TMPDIR?.trim();
|
|
159
|
+
if (!base || !path.isAbsolute(base) || !sessionId)
|
|
160
|
+
return undefined;
|
|
161
|
+
const root = path.resolve(base);
|
|
162
|
+
const digest = crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
|
|
163
|
+
const directory = path.join(root, `evolcore-runtime-${digest}`);
|
|
164
|
+
try {
|
|
165
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
166
|
+
if (!isPrivateDirectory(root))
|
|
167
|
+
return undefined;
|
|
168
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
169
|
+
fs.mkdirSync(path.join(directory, 'locks'), { recursive: true, mode: 0o700 });
|
|
170
|
+
return isManagedSessionRuntimeDir(directory) ? directory : undefined;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Shared by tasks in one daemon runtime, while remaining outside each
|
|
177
|
+
* session's history/files area. The caller must still pass this path through
|
|
178
|
+
* the task environment before a sandboxed child can use it. */
|
|
179
|
+
export function ensureRuntimeLockDir(sessionRuntimeDir) {
|
|
180
|
+
if (!sessionRuntimeDir || !path.isAbsolute(sessionRuntimeDir))
|
|
181
|
+
return undefined;
|
|
182
|
+
if (!isManagedSessionRuntimeDir(sessionRuntimeDir) && !isRunnerOwnedSessionRuntimeDir(sessionRuntimeDir))
|
|
183
|
+
return undefined;
|
|
184
|
+
const parent = path.dirname(path.resolve(sessionRuntimeDir));
|
|
185
|
+
if (!isPrivateDirectory(parent))
|
|
186
|
+
return undefined;
|
|
187
|
+
const directory = path.join(parent, 'evolcore-locks');
|
|
188
|
+
try {
|
|
189
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
190
|
+
return isPrivateDirectory(directory) ? directory : undefined;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
30
196
|
export function runtimeRefMessageIdForMsgSend(args) {
|
|
31
197
|
const runtime = args.runtime;
|
|
32
198
|
if (!runtime)
|
|
@@ -76,5 +242,11 @@ export function buildTaskRuntimeEnv(ctx) {
|
|
|
76
242
|
return {
|
|
77
243
|
EVOLCORE_SESSION_ID: ctx.sessionId ?? '',
|
|
78
244
|
[TASK_RUNTIME_CONTEXT_ENV]: JSON.stringify(clean),
|
|
245
|
+
// Every managed child receives the session directory as TMPDIR. This keeps
|
|
246
|
+
// ordinary temporary files and runtime-only fallbacks out of shared agent
|
|
247
|
+
// or process directories, including for non-Codex runners.
|
|
248
|
+
...(clean.sessionRuntimeDir ? { TMPDIR: clean.sessionRuntimeDir } : {}),
|
|
249
|
+
...(clean.sessionRuntimeDir ? { [SESSION_RUNTIME_DIR_ENV]: clean.sessionRuntimeDir } : {}),
|
|
250
|
+
...(clean.runtimeLockDir ? { [RUNTIME_LOCK_DIR_ENV]: clean.runtimeLockDir } : {}),
|
|
79
251
|
};
|
|
80
252
|
}
|
|
@@ -113,6 +113,7 @@ Create 参数模式支持:
|
|
|
113
113
|
--permission <readonly|auto|request|bypass>(省略则继承当前身份配置)
|
|
114
114
|
--tz <时区> (仅 cron)
|
|
115
115
|
--target-channel <channelKey> --target-channel-id <ID> (AUN 可简写为 aun)
|
|
116
|
+
--target-chat-type <private|group> (AUN 目标必填;channelId 同时作为群聊 ID)
|
|
116
117
|
--target-session <main|thread> [--target-thread-id <threadId>]
|
|
117
118
|
--trigger-thread <by-run|by-trigger> (兼容旧值 per-run)
|
|
118
119
|
--name <名称>
|
|
@@ -263,6 +264,11 @@ async function createTrigger(args, json) {
|
|
|
263
264
|
if (!parsed.targetChannel || !parsed.targetChannelId) {
|
|
264
265
|
throw new Error('CLI flag 模式需要显式指定 --target-channel <channelKey> --target-channel-id <id>');
|
|
265
266
|
}
|
|
267
|
+
const targetIsAun = parsed.targetChannel === 'aun' || parsed.targetChannel.startsWith('aun#');
|
|
268
|
+
if (targetIsAun && !parsed.targetChatType) {
|
|
269
|
+
throw new Error('AUN 目标必须显式指定 --target-chat-type private 或 group');
|
|
270
|
+
}
|
|
271
|
+
const targetChatType = parsed.targetChatType;
|
|
266
272
|
const now = Date.now();
|
|
267
273
|
if (parsed.targetSession === 'thread' && !parsed.targetThreadId) {
|
|
268
274
|
throw new Error('--target-session thread 需要 --target-thread-id');
|
|
@@ -300,6 +306,7 @@ async function createTrigger(args, json) {
|
|
|
300
306
|
channelKey: parsed.targetChannel,
|
|
301
307
|
channelId: parsed.targetChannelId,
|
|
302
308
|
session: parsed.targetSession,
|
|
309
|
+
...(targetChatType ? { chatType: targetChatType } : {}),
|
|
303
310
|
...(parsed.targetSession === 'thread' ? { threadId: parsed.targetThreadId } : {}),
|
|
304
311
|
},
|
|
305
312
|
},
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
-
import { ConfigError, ConfigTarget, mutateConfig, read, validateConfig, } from './config-manager.js';
|
|
3
|
-
import { validAgentOwnerIds, validateAccessPolicyDomain, } from './access-policy-domain.js';
|
|
2
|
+
import { ConfigError, ConfigTarget, mutateConfig, read, validateConfig, validAgentOwnerIds, validateAccessPolicyDomain, } from './config-manager.js';
|
|
4
3
|
export class AccessPolicyError extends Error {
|
|
5
4
|
code;
|
|
6
5
|
data;
|
|
@@ -44,6 +44,13 @@ export function getManagementCommandPermissions(role) {
|
|
|
44
44
|
'role.policy.write': { allow: true, dangerous: true, scopes: ['agent'], constraints: { requireAgentAdmin: true } },
|
|
45
45
|
'ec.aid.show': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
46
46
|
'ec.aid.agentmd.put': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
47
|
+
'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
48
|
+
'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
49
|
+
'ec.fs.copy': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
50
|
+
'ec.fs.move': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
51
|
+
'ec.fs.remove': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
52
|
+
'ec.fs.getfacl': { allow: true, scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
53
|
+
'ec.fs.setfacl': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
47
54
|
'*': { allow: true },
|
|
48
55
|
// Reload is valid from an Agent's own channel and the daemon control plane.
|
|
49
56
|
// The control-channel gate still requires a daemon owner for cross-Agent reloads.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import fs from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
|
+
import { ensureManagedLockParent, resolveManagedLockPath } from '../core/runtime-lock.js';
|
|
15
16
|
import crypto from 'crypto';
|
|
16
17
|
import { resolvePaths, agentConfig as agentConfigPath, agentContactConfig, agentRelationConfig, agentDir, agentRelationsDir, agentRolesIndex, agentRoleConfig, } from '../paths.js';
|
|
17
18
|
import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
|
|
@@ -28,7 +29,27 @@ import { clearRoleStoreCache, isRoleName } from './role-store.js';
|
|
|
28
29
|
import { writeRoleDefinition, writeRoleRegistry } from './role-service.js';
|
|
29
30
|
import { mutateContactBookTransactionSync } from './contact-book-store.js';
|
|
30
31
|
import { DAEMON_SCHEMA_NAME } from '../product.js';
|
|
31
|
-
|
|
32
|
+
export function shouldFailFastForMissingOwners(env = process.env) {
|
|
33
|
+
const value = (env.EVOLCORE_REQUIRE_OWNERS || '').trim().toLowerCase();
|
|
34
|
+
return value === '1' || value === 'true' || value === 'yes';
|
|
35
|
+
}
|
|
36
|
+
export function validAgentOwnerIds(agent) {
|
|
37
|
+
return [...new Set((agent?.owners ?? [])
|
|
38
|
+
.map(value => String(value || '').trim())
|
|
39
|
+
.filter(isValidAid))];
|
|
40
|
+
}
|
|
41
|
+
export function validateAccessPolicyDomain(selfAid, agent) {
|
|
42
|
+
const access = agent.access;
|
|
43
|
+
if (!access)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (access.policyMode !== 'open' && validAgentOwnerIds(agent).length === 0) {
|
|
46
|
+
return {
|
|
47
|
+
code: 'restricted_mode_without_owner',
|
|
48
|
+
message: `Agent ${selfAid} cannot use ${access.policyMode} without an Owner`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
32
53
|
export var ConfigTarget;
|
|
33
54
|
(function (ConfigTarget) {
|
|
34
55
|
ConfigTarget["Process"] = "process";
|
|
@@ -773,12 +794,15 @@ function sortConfigValue(value) {
|
|
|
773
794
|
.map(([key, nested]) => [key, sortConfigValue(nested)]));
|
|
774
795
|
}
|
|
775
796
|
function acquireConfigMutationLock(file) {
|
|
776
|
-
const lockDir = `${file}.mutation.lock
|
|
797
|
+
const lockDir = resolveManagedLockPath('config', file, `${file}.mutation.lock`);
|
|
777
798
|
const ownerFile = path.join(lockDir, 'owner.json');
|
|
778
799
|
const token = crypto.randomBytes(16).toString('hex');
|
|
779
800
|
const candidate = `${lockDir}.candidate-${process.pid}-${token}`;
|
|
780
801
|
const candidateOwner = path.join(candidate, 'owner.json');
|
|
781
|
-
|
|
802
|
+
if (lockDir === `${file}.mutation.lock`)
|
|
803
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
804
|
+
else
|
|
805
|
+
ensureManagedLockParent(lockDir);
|
|
782
806
|
fs.mkdirSync(candidate);
|
|
783
807
|
fs.writeFileSync(candidateOwner, `${JSON.stringify({ pid: process.pid, token })}\n`, { mode: 0o600 });
|
|
784
808
|
let acquired = false;
|
|
@@ -6,6 +6,7 @@ import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
|
6
6
|
import { agentConfig, agentContactConfig, agentDir, resolvePaths } from '../paths.js';
|
|
7
7
|
import { atomicReadJson, atomicWrite, atomicWriteJson } from '../utils/atomic-write.js';
|
|
8
8
|
import { fileCache } from '../core/daemon-file-cache.js';
|
|
9
|
+
import { ensureManagedLockParent, resolveManagedLockPath } from '../core/runtime-lock.js';
|
|
9
10
|
import { ConfigTarget, notifyConfigWrite, validateConfig } from './config-manager.js';
|
|
10
11
|
import { parseContactAlias } from './contact-alias.js';
|
|
11
12
|
import { clearRoleStoreCache } from './role-store.js';
|
|
@@ -426,12 +427,16 @@ function readContactBookFromDisk(selfAid) {
|
|
|
426
427
|
}
|
|
427
428
|
}
|
|
428
429
|
function acquireAgentLock(selfAid) {
|
|
429
|
-
const
|
|
430
|
+
const legacyLockDir = path.join(agentDir(selfAid), LOCK_NAME);
|
|
431
|
+
const lockDir = resolveManagedLockPath('contact', selfAid, legacyLockDir);
|
|
430
432
|
const ownerFile = path.join(lockDir, 'owner.json');
|
|
431
433
|
const lockToken = crypto.randomBytes(16).toString('hex');
|
|
432
434
|
const candidateDir = `${lockDir}.candidate-${process.pid}-${lockToken}`;
|
|
433
435
|
const candidateOwnerFile = path.join(candidateDir, 'owner.json');
|
|
434
|
-
|
|
436
|
+
if (lockDir === legacyLockDir)
|
|
437
|
+
fs.mkdirSync(path.dirname(lockDir), { recursive: true });
|
|
438
|
+
else
|
|
439
|
+
ensureManagedLockParent(lockDir);
|
|
435
440
|
fs.mkdirSync(candidateDir);
|
|
436
441
|
fs.writeFileSync(candidateOwnerFile, `${JSON.stringify({
|
|
437
442
|
pid: process.pid,
|
|
@@ -123,8 +123,12 @@ export function resolvePeerRoleDetail(ctx) {
|
|
|
123
123
|
const winner = selectHighestRank(candidates, ctx.selfAid);
|
|
124
124
|
if (winner)
|
|
125
125
|
return resultFor(winner.role, winner.source, actor, ctx.selfAid);
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
// Group members get the normal interactive member policy when no explicit
|
|
127
|
+
// group/actor/default assignment exists. Private conversations retain the
|
|
128
|
+
// read-only visitor fallback.
|
|
129
|
+
const fallbackRole = ctx.chatType === 'group' ? 'member' : 'visitor';
|
|
130
|
+
return roleRegistryExists(ctx.selfAid) && roleExists(fallbackRole, ctx.selfAid)
|
|
131
|
+
? resultFor(fallbackRole, 'builtin-fallback', actor, ctx.selfAid)
|
|
128
132
|
: resultFor(null, 'none', actor, ctx.selfAid);
|
|
129
133
|
}
|
|
130
134
|
export function roleToSessionIdentity(role) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
4
|
+
import { assertRoleConfigV4Ready, assertRoleConfigV5Ready, ConfigError } from './config-manager.js';
|
|
5
|
+
/** Apply the one-off v4 role config migration before daemon startup. */
|
|
6
|
+
export async function ensureRoleConfigV4OnStartup() {
|
|
7
|
+
try {
|
|
8
|
+
assertRoleConfigV4Ready();
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (!(error instanceof ConfigError) || error.code !== 'ROLE_CONFIG_MIGRATION_REQUIRED')
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
const home = resolvePaths().root;
|
|
16
|
+
const script = path.join(getPackageRoot(), 'kits', 'migrations', 'migrate-role-config-v4.mjs');
|
|
17
|
+
try {
|
|
18
|
+
const migration = await import(pathToFileURL(script).href);
|
|
19
|
+
const result = migration.runMigration({ home, apply: true });
|
|
20
|
+
assertRoleConfigV4Ready();
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
25
|
+
throw new ConfigError('ROLE_CONFIG_MIGRATION_FAILED', `Automatic role config v4 migration failed: ${detail}\n` +
|
|
26
|
+
`Fix the reported config or run manually:\n` +
|
|
27
|
+
`node kits/migrations/migrate-role-config-v4.mjs --apply --home ${JSON.stringify(home)}`, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Apply the standalone v5 role registry migration before daemon startup. */
|
|
31
|
+
export async function ensureRoleConfigV5OnStartup() {
|
|
32
|
+
try {
|
|
33
|
+
assertRoleConfigV5Ready();
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!(error instanceof ConfigError) || error.code !== 'ROLE_CONFIG_V5_MIGRATION_REQUIRED')
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
const home = resolvePaths().root;
|
|
41
|
+
const script = path.join(getPackageRoot(), 'kits', 'migrations', 'migrate-role-config-v5.mjs');
|
|
42
|
+
try {
|
|
43
|
+
const migration = await import(pathToFileURL(script).href);
|
|
44
|
+
const result = migration.runMigration({ home, apply: true });
|
|
45
|
+
assertRoleConfigV5Ready();
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
50
|
+
throw new ConfigError('ROLE_CONFIG_V5_MIGRATION_FAILED', `Automatic standalone role migration failed: ${detail}\n` +
|
|
51
|
+
`node kits/migrations/migrate-role-config-v5.mjs --apply --home ${JSON.stringify(home)}`, { cause: error });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -113,7 +113,7 @@ function readRegistry(selfAid, diagnostics) {
|
|
|
113
113
|
code: 'INVALID_ROLE_ASSIGNMENT',
|
|
114
114
|
roleId: selected,
|
|
115
115
|
file: path.relative(resolvePaths().root, file),
|
|
116
|
-
message: `defaultRoles.${scene} references invalid or unregistered role ${selected}; using visitor fallback`,
|
|
116
|
+
message: `defaultRoles.${scene} references invalid or unregistered role ${selected}; using ${scene === 'group' ? 'member' : 'visitor'} fallback`,
|
|
117
117
|
});
|
|
118
118
|
defaultRoles[scene] = null;
|
|
119
119
|
}
|
|
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
|
|
3
3
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
4
4
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
5
|
-
export const AGENT_DELEGATION_COMMAND_TTL_MS = 60_000;
|
|
5
|
+
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
6
6
|
export class AgentDelegationRegistry {
|
|
7
7
|
grantsByHash = new Map();
|
|
8
8
|
activeHashBySession = new Map();
|
|
@@ -4,6 +4,22 @@ import { formatPeerKey } from '../relation/peer-identity.js';
|
|
|
4
4
|
import { authorizeCommand, authorizeResolvedConfigCommand } from './operation-authorizer.js';
|
|
5
5
|
import { auditCommandAuthorization } from './authorization-audit.js';
|
|
6
6
|
import { hasTrustedPrincipal } from './authenticated-actor.js';
|
|
7
|
+
import { resolveRuntimePermissionMode } from '../role/runtime-policy.js';
|
|
8
|
+
const CROSS_AGENT_READ_OPERATIONS = new Set([
|
|
9
|
+
'trigger.list',
|
|
10
|
+
'trigger.show',
|
|
11
|
+
'trigger.history',
|
|
12
|
+
]);
|
|
13
|
+
export function isCrossAgentTriggerOperationAllowed(input) {
|
|
14
|
+
if (input.control || input.taskAgentAid === input.targetAgentAid)
|
|
15
|
+
return true;
|
|
16
|
+
if (!input.taskAgentAid || !input.targetManagement)
|
|
17
|
+
return false;
|
|
18
|
+
return CROSS_AGENT_READ_OPERATIONS.has(input.operation);
|
|
19
|
+
}
|
|
20
|
+
export function isCrossAgentTriggerReadOperation(operation) {
|
|
21
|
+
return CROSS_AGENT_READ_OPERATIONS.has(operation);
|
|
22
|
+
}
|
|
7
23
|
export function buildAuthSubject(input) {
|
|
8
24
|
const chatType = input.chatType === 'group' ? 'group' : 'private';
|
|
9
25
|
const actorId = input.actorId;
|
|
@@ -27,6 +43,8 @@ export function buildAuthSubject(input) {
|
|
|
27
43
|
return {
|
|
28
44
|
selfAid: input.selfAid,
|
|
29
45
|
actorId,
|
|
46
|
+
requestId: input.requestId,
|
|
47
|
+
sessionId: input.sessionId,
|
|
30
48
|
channel: input.channel,
|
|
31
49
|
channelType: input.channelType,
|
|
32
50
|
channelId: input.channelId,
|
|
@@ -41,6 +59,8 @@ export function buildAuthSubject(input) {
|
|
|
41
59
|
allowAccess: isDaemonOwner || (suppliedRole
|
|
42
60
|
? checkRoleAccess(suppliedRole, input.selfAid)
|
|
43
61
|
: roleDetail.allowAccess && checkRoleAccess(role, input.selfAid)),
|
|
62
|
+
permissionMode: input.permissionMode
|
|
63
|
+
?? resolveRuntimePermissionMode({ selfAid: input.selfAid, role }).effectiveValue,
|
|
44
64
|
};
|
|
45
65
|
}
|
|
46
66
|
function isProcessOwner(input) {
|
|
@@ -144,6 +164,16 @@ function resolveRoleDetail(input) {
|
|
|
144
164
|
function auditDecision(params, decision) {
|
|
145
165
|
auditCommandAuthorization({
|
|
146
166
|
ts: Date.now(),
|
|
167
|
+
...params.auditMetadata,
|
|
168
|
+
correlationId: params.auditMetadata?.correlationId ?? params.auditMetadata?.requestId ?? params.subject.requestId,
|
|
169
|
+
requestId: params.auditMetadata?.requestId ?? params.subject.requestId,
|
|
170
|
+
sessionId: params.auditMetadata?.sessionId ?? params.subject.sessionId,
|
|
171
|
+
agentAid: params.auditMetadata?.agentAid ?? params.subject.selfAid,
|
|
172
|
+
permissionMode: params.auditMetadata?.permissionMode
|
|
173
|
+
?? (decision.allow ? decision.permissionMode ?? params.subject.permissionMode : params.subject.permissionMode),
|
|
174
|
+
toolName: params.auditMetadata?.toolName,
|
|
175
|
+
policyCode: params.auditMetadata?.policyCode ?? (decision.allow ? undefined : decision.code),
|
|
176
|
+
decisionSource: params.auditMetadata?.decisionSource ?? (decision.allow ? 'command' : 'policy'),
|
|
147
177
|
source: params.source,
|
|
148
178
|
operation: params.intent.operation,
|
|
149
179
|
scope: params.intent.scope,
|
|
@@ -161,6 +191,5 @@ function auditDecision(params, decision) {
|
|
|
161
191
|
reason: decision.allow ? undefined : decision.reason,
|
|
162
192
|
matchedRule: decision.matchedRule,
|
|
163
193
|
argsSummary: params.intent.args,
|
|
164
|
-
...params.auditMetadata,
|
|
165
194
|
});
|
|
166
195
|
}
|