evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
package/dist/cli/model.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
|
|
14
14
|
import { ModelScopeError, normalizePeer, determineScope, activeBaseagent, writeScope, clearScope, } from '../core/model/config-scope.js';
|
|
15
15
|
import { resolveEffectiveFieldWithSource } from '../config/config-manager.js';
|
|
16
|
-
import { getCatalog, getModelInfo } from '../core/model/model-catalog.js';
|
|
16
|
+
import { getCatalog, getModelInfo, catalogEfforts, resolveCatalogModel, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
17
17
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole, } from '../core/model/model-permission.js';
|
|
18
18
|
import { resolvePaths } from '../paths.js';
|
|
19
19
|
import { ipcQuery } from '../ipc.js';
|
|
@@ -23,7 +23,7 @@ import { getFieldPermission } from '../config/roles.js';
|
|
|
23
23
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
|
|
24
24
|
import { formatModelCheck, runModelCheck } from '../core/model/model-diagnostics.js';
|
|
25
25
|
import { validateRuntimeStringFieldOverride } from '../core/role/runtime-policy.js';
|
|
26
|
-
const ALL_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
|
|
26
|
+
const ALL_EFFORTS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'auto'];
|
|
27
27
|
const SCOPE_LABEL = {
|
|
28
28
|
global: '全局', agent: 'agent级', role: '角色级', relation: '关系级',
|
|
29
29
|
};
|
|
@@ -298,7 +298,7 @@ async function cmdList(args, formatJson) {
|
|
|
298
298
|
const cat = await getCatalog(sel.self, ba);
|
|
299
299
|
const resolved = resolveModelConfig(effectiveSel, ba);
|
|
300
300
|
const allowedModelIds = role
|
|
301
|
-
? filterModelsForRole(role, ba, cat.models.map(m => m.id),
|
|
301
|
+
? filterModelsForRole(role, ba, cat.models.map(m => m.id), model => resolveCatalogModel(cat, model), sel.self)
|
|
302
302
|
: null;
|
|
303
303
|
const models = allowedModelIds
|
|
304
304
|
? allowedModelIds.map(id => cat.models.find(m => m.id === id) ?? { id })
|
|
@@ -308,15 +308,21 @@ async function cmdList(args, formatJson) {
|
|
|
308
308
|
effective: { model: resolved.model ?? null, source: resolved.source ?? null },
|
|
309
309
|
role: role ?? null,
|
|
310
310
|
catalogSource: cat.source,
|
|
311
|
+
freshness: cat.freshness,
|
|
312
|
+
revision: cat.revision,
|
|
313
|
+
fetchedAt: cat.fetchedAt,
|
|
314
|
+
diagnostics: cat.diagnostics,
|
|
311
315
|
models,
|
|
312
316
|
}, () => {
|
|
313
317
|
const lines = [];
|
|
314
318
|
lines.push(`当前生效: ${resolved.model ?? '(未设置,回落 SDK 默认)'}` +
|
|
315
319
|
(resolved.source ? ` (来源: ${SCOPE_LABEL[resolved.source]})` : ''));
|
|
316
320
|
lines.push('');
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
if (resolved.model && !cat.models.some(m => m.id === resolved.model))
|
|
322
|
+
lines.push('当前配置未列入目录');
|
|
323
|
+
lines.push(...cat.diagnostics.map(d => d.message));
|
|
324
|
+
const srcTag = cat.freshness === 'fallback' ? ` [${cat.source}/unverified]`
|
|
325
|
+
: cat.freshness === 'stale' ? ' [stale]' : '';
|
|
320
326
|
lines.push(`可用模型 (${models.length})${srcTag}:`);
|
|
321
327
|
for (const e of models) {
|
|
322
328
|
const live = resolved.model === e.id ? '✓' : ' ';
|
|
@@ -430,6 +436,7 @@ async function cmdUse(args, formatJson) {
|
|
|
430
436
|
const ba = activeBaseagent(sel.self);
|
|
431
437
|
// 校验模型在 catalog 中
|
|
432
438
|
const cat = await getCatalog(sel.self, ba);
|
|
439
|
+
assertCatalogSelectable(cat);
|
|
433
440
|
if (!cat.models.some(m => m.id === modelId)) {
|
|
434
441
|
fail(formatJson, 'UNKNOWN_MODEL', `模型不在目录中: ${modelId}(model list 查看可用模型)`);
|
|
435
442
|
}
|
|
@@ -439,14 +446,15 @@ async function cmdUse(args, formatJson) {
|
|
|
439
446
|
baseagent: ba,
|
|
440
447
|
requestedModel: modelId,
|
|
441
448
|
models: cat.models.map(m => m.id),
|
|
449
|
+
resolveModelId: model => resolveCatalogModel(cat, model),
|
|
442
450
|
selfAid: sel.self,
|
|
443
451
|
});
|
|
444
452
|
if (!decision.ok)
|
|
445
453
|
fail(formatJson, decision.code || 'MODEL_NOT_ALLOWED', decision.message || `model not allowed: ${modelId}`);
|
|
446
454
|
}
|
|
447
455
|
const effort = getArgValue(args, '--effort');
|
|
448
|
-
if (effort !== undefined && !
|
|
449
|
-
fail(formatJson, 'INVALID_EFFORT',
|
|
456
|
+
if (effort !== undefined && effort !== 'auto' && !catalogEfforts(cat, modelId, ba).includes(effort)) {
|
|
457
|
+
fail(formatJson, 'INVALID_EFFORT', `当前模型目录未声明支持推理强度: ${effort}`);
|
|
450
458
|
}
|
|
451
459
|
const effortVal = effort === 'auto' ? null : (effort ?? undefined);
|
|
452
460
|
if (isManagedTargetedWriteEnv(args)) {
|
|
@@ -492,6 +500,13 @@ async function cmdEffort(args, formatJson) {
|
|
|
492
500
|
const scope = determineWriteScope(sel, formatJson);
|
|
493
501
|
const ba = activeBaseagent(sel.self);
|
|
494
502
|
const val = level === 'auto' ? null : level;
|
|
503
|
+
if (val) {
|
|
504
|
+
const cat = await getCatalog(sel.self, ba);
|
|
505
|
+
assertCatalogSelectable(cat);
|
|
506
|
+
const model = resolveModelConfig(selectorWithRole(sel, explicitOrDerivedRole(args, sel)), ba).model;
|
|
507
|
+
if (!model || !catalogEfforts(cat, model, ba).includes(val))
|
|
508
|
+
fail(formatJson, 'INVALID_EFFORT', `当前模型目录未声明支持推理强度: ${val}`);
|
|
509
|
+
}
|
|
495
510
|
if (isManagedTargetedWriteEnv(args)) {
|
|
496
511
|
const effortKey = 'effort';
|
|
497
512
|
preflightManagedFieldOverride(sel, explicitOrDerivedRole(args, sel), `baseagents.${ba}.${effortKey}`, val, formatJson);
|
package/dist/cli/task-context.js
CHANGED
|
@@ -6,6 +6,7 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
export const TASK_RUNTIME_CONTEXT_ENV = 'EVOLCORE_TASK_RUNTIME_CONTEXT';
|
|
8
8
|
export const SESSION_RUNTIME_DIR_ENV = 'EVOLCORE_SESSION_RUNTIME_DIR';
|
|
9
|
+
export const DAEMON_RUNTIME_EPOCH_ENV = 'EVOLCORE_DAEMON_RUNTIME_EPOCH';
|
|
9
10
|
export { RUNTIME_LOCK_DIR_ENV };
|
|
10
11
|
const generatedProcessManagedTempDirs = new Set();
|
|
11
12
|
let processTempCleanupRegistered = false;
|
|
@@ -32,11 +33,13 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
32
33
|
peerName: optionalString(value.peerName),
|
|
33
34
|
peerType: optionalString(value.peerType),
|
|
34
35
|
peerRole: optionalString(value.peerRole),
|
|
36
|
+
permissionMode: optionalString(value.permissionMode),
|
|
35
37
|
processRole: optionalProcessRole(value.processRole),
|
|
36
38
|
dataScope: optionalDataScope(value.dataScope),
|
|
37
39
|
authorizedBy: optionalString(value.authorizedBy),
|
|
38
40
|
executionSource: value.executionSource === 'fullaccess-command' || value.executionSource === 'trigger'
|
|
39
41
|
? value.executionSource : undefined,
|
|
42
|
+
daemonRuntimeEpoch: optionalString(value.daemonRuntimeEpoch),
|
|
40
43
|
threadId: optionalString(value.threadId),
|
|
41
44
|
sessionRuntimeDir: optionalAbsolutePath(value.sessionRuntimeDir),
|
|
42
45
|
runtimeLockDir: optionalAbsolutePath(value.runtimeLockDir),
|
|
@@ -250,8 +253,18 @@ export function getManagedTaskTempDir(runtimeEnv) {
|
|
|
250
253
|
* silently escape the session-owned temporary workspace.
|
|
251
254
|
*/
|
|
252
255
|
export function ensureSessionRuntimeDir(sessionId) {
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
if (!sessionId)
|
|
257
|
+
return undefined;
|
|
258
|
+
let base = process.env.TMPDIR?.trim();
|
|
259
|
+
try {
|
|
260
|
+
if (!base || !path.isAbsolute(base) || !isPrivateDirectory(path.resolve(base))) {
|
|
261
|
+
base = ensureProcessManagedTempDir();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
if (!base || !path.isAbsolute(base))
|
|
255
268
|
return undefined;
|
|
256
269
|
const root = path.resolve(base);
|
|
257
270
|
const digest = crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
|
|
@@ -336,6 +349,7 @@ export function buildTaskRuntimeEnv(ctx) {
|
|
|
336
349
|
const clean = normalizeTaskRuntimeContext(ctx);
|
|
337
350
|
return {
|
|
338
351
|
EVOLCORE_SESSION_ID: ctx.sessionId ?? '',
|
|
352
|
+
...(ctx.daemonRuntimeEpoch ? { [DAEMON_RUNTIME_EPOCH_ENV]: ctx.daemonRuntimeEpoch } : {}),
|
|
339
353
|
[TASK_RUNTIME_CONTEXT_ENV]: JSON.stringify(clean),
|
|
340
354
|
// Every managed child receives the session directory as TMPDIR. This keeps
|
|
341
355
|
// ordinary temporary files and runtime-only fallbacks out of shared agent
|
|
@@ -32,7 +32,7 @@ export async function cmdTrigger(args) {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
if (process.env.EVOLCORE_SESSION_ID
|
|
35
|
-
&& (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path'))) {
|
|
35
|
+
&& (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path') || hasOption(rest, '--script-file'))) {
|
|
36
36
|
throw new Error('Managed tasks cannot read trigger definitions, prompts, or scripts from local files; use inline prompt content');
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
@@ -121,12 +121,20 @@ Create 参数模式支持:
|
|
|
121
121
|
通用输出参数:
|
|
122
122
|
--json | --format json
|
|
123
123
|
|
|
124
|
-
Update
|
|
124
|
+
Update 参数:
|
|
125
125
|
--once | --delay | --at | --cron | --every | --event [--tz]
|
|
126
|
-
--prompt <文本> | --prompt-file <路径> --name <名称>
|
|
126
|
+
--prompt <文本> | --prompt-file <路径> --script-file <路径> --name <名称>
|
|
127
|
+
--description <文本> (空字符串删除)
|
|
127
128
|
--model <模型> --effort <强度> --permission <权限模式|inherit>(inherit 清除覆盖)
|
|
128
129
|
--max-runs <次数> --max-duration <时长>
|
|
129
130
|
--concurrency <forbid|replace|allow> --missed-policy <skip|run-once|run-all>
|
|
131
|
+
--feedback <origin|target|silent>
|
|
132
|
+
--target-channel <channelKey> --target-channel-id <ID> (必须同时指定)
|
|
133
|
+
--target-chat-type <private|group> --target-session <main|thread> [--target-thread-id <threadId>]
|
|
134
|
+
--trigger-thread <by-run|by-trigger> --baseagent <name> (仅 trigger_session 之间)
|
|
135
|
+
--on-error <fail|retry> --noop-sentinel <文本>
|
|
136
|
+
--retry-attempts <次数> --retry-backoff <毫秒>
|
|
137
|
+
--script-runtime <node|python|bash> --script-args <JSON> --script-timeout <时长>
|
|
130
138
|
|
|
131
139
|
省略 --agent 时使用当前 agent AID;无会话上下文或跨身份操作时需要显式指定。
|
|
132
140
|
任务会话中的跨身份操作仅允许目标 Agent 上具备 owner/admin 权限的调用者执行 list/show/history;创建、修改、启停、删除和运行仍只允许当前 Agent。`);
|
|
@@ -399,19 +407,28 @@ function shellQuoteArg(value) {
|
|
|
399
407
|
}
|
|
400
408
|
async function updateTrigger(args, json) {
|
|
401
409
|
const agentAid = resolveAgentAid(args);
|
|
402
|
-
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file']);
|
|
410
|
+
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file', '--script-file']);
|
|
403
411
|
const usesPromptFile = hasOption(args, '--prompt-file');
|
|
412
|
+
const usesScriptFile = hasOption(args, '--script-file');
|
|
413
|
+
const scriptFilePath = usesScriptFile ? requireFlag(args, '--script-file') : undefined;
|
|
404
414
|
const { parseTriggerUpdateArgv } = await import('../trigger/parser.js');
|
|
405
415
|
const parsed = parseTriggerUpdateArgv(triggerId, withPromptFromFile(triggerUpdateArgs(args)), { allowLongPrompt: usesPromptFile });
|
|
406
416
|
if (!parsed.ok)
|
|
407
417
|
throw new Error(parsed.error);
|
|
418
|
+
const scriptFileRequested = parsed.value.scriptFile !== undefined;
|
|
419
|
+
const { scriptFile: _scriptFile, ...patchFields } = parsed.value;
|
|
420
|
+
const patch = scriptFileRequested ? { ...patchFields, scriptFile: true } : patchFields;
|
|
408
421
|
const expectedRevision = flagValue(args, '--if-revision');
|
|
422
|
+
const scriptFileBase64 = scriptFilePath
|
|
423
|
+
? fs.readFileSync(scriptFilePath).toString('base64')
|
|
424
|
+
: undefined;
|
|
409
425
|
const res = await request({
|
|
410
426
|
type: 'trigger.update',
|
|
411
427
|
agentAid,
|
|
412
428
|
triggerId,
|
|
413
|
-
patch
|
|
429
|
+
patch,
|
|
414
430
|
...(usesPromptFile ? { promptFile: true } : {}),
|
|
431
|
+
...(scriptFileBase64 !== undefined ? { scriptFileBase64 } : {}),
|
|
415
432
|
...(expectedRevision ? { expectedRevision } : {}),
|
|
416
433
|
}, 30_000);
|
|
417
434
|
if (json)
|
|
@@ -2,6 +2,7 @@ const BEHAVIOR_TOP_FIELDS = new Set([
|
|
|
2
2
|
'active_baseagent',
|
|
3
3
|
'chatmode',
|
|
4
4
|
'mentionMode',
|
|
5
|
+
'groupSlashRequireMentions',
|
|
5
6
|
'responseMode',
|
|
6
7
|
'responseModeParams',
|
|
7
8
|
'flush_delay',
|
|
@@ -110,6 +111,8 @@ export function resolveConfigFieldRule(fieldPath) {
|
|
|
110
111
|
return scalar(field, 'nonnegative-number');
|
|
111
112
|
if (field === 'mentionMode')
|
|
112
113
|
return scalar(field, 'mention-mode');
|
|
114
|
+
if (field === 'groupSlashRequireMentions')
|
|
115
|
+
return scalar(field, 'boolean');
|
|
113
116
|
if (field === 'show_activities')
|
|
114
117
|
return scalar(field, 'show-activities');
|
|
115
118
|
if (field === 'enable_rich_content')
|
|
@@ -933,7 +933,10 @@ function acquireConfigMutationLock(file) {
|
|
|
933
933
|
return () => releaseConfigMutationLock(lockDir, ownerFile, token);
|
|
934
934
|
}
|
|
935
935
|
catch (error) {
|
|
936
|
-
|
|
936
|
+
const lockMayAlreadyExist = error?.code === 'EEXIST'
|
|
937
|
+
|| error?.code === 'ENOTEMPTY'
|
|
938
|
+
|| (process.platform === 'win32' && error?.code === 'EPERM' && fs.existsSync(lockDir));
|
|
939
|
+
if (!lockMayAlreadyExist)
|
|
937
940
|
throw error;
|
|
938
941
|
if (reclaimStaleConfigMutationLock(lockDir, ownerFile))
|
|
939
942
|
continue;
|
|
@@ -957,10 +960,10 @@ function reclaimStaleConfigMutationLock(lockDir, ownerFile) {
|
|
|
957
960
|
pid = Number(JSON.parse(raw).pid);
|
|
958
961
|
}
|
|
959
962
|
catch {
|
|
960
|
-
return
|
|
963
|
+
return reclaimCorruptConfigMutationLock(lockDir);
|
|
961
964
|
}
|
|
962
965
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
963
|
-
return
|
|
966
|
+
return reclaimCorruptConfigMutationLock(lockDir);
|
|
964
967
|
try {
|
|
965
968
|
process.kill(pid, 0);
|
|
966
969
|
return false;
|
|
@@ -979,6 +982,24 @@ function reclaimStaleConfigMutationLock(lockDir, ownerFile) {
|
|
|
979
982
|
}
|
|
980
983
|
}
|
|
981
984
|
}
|
|
985
|
+
function reclaimCorruptConfigMutationLock(lockDir) {
|
|
986
|
+
try {
|
|
987
|
+
const stat = fs.statSync(lockDir);
|
|
988
|
+
if (Date.now() - stat.mtimeMs < CONFIG_MUTATION_LOCK_WAIT_MS)
|
|
989
|
+
return false;
|
|
990
|
+
const reclaimed = `${lockDir}.reclaimed-corrupt-${crypto.randomBytes(8).toString('hex')}`;
|
|
991
|
+
fs.renameSync(lockDir, reclaimed);
|
|
992
|
+
fs.rmSync(reclaimed, { recursive: true, force: true });
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
if (error?.code === 'ENOENT')
|
|
997
|
+
return true;
|
|
998
|
+
if (error?.code === 'EEXIST' || error?.code === 'ENOTEMPTY' || error?.code === 'EPERM')
|
|
999
|
+
return false;
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
982
1003
|
function releaseConfigMutationLock(lockDir, ownerFile, token) {
|
|
983
1004
|
try {
|
|
984
1005
|
const owner = JSON.parse(fs.readFileSync(ownerFile, 'utf8'));
|
|
@@ -1289,9 +1310,13 @@ function stripStaticOwnerField(config) {
|
|
|
1289
1310
|
return config;
|
|
1290
1311
|
if (!Object.prototype.hasOwnProperty.call(config, 'owners')
|
|
1291
1312
|
&& !Object.prototype.hasOwnProperty.call(config, 'admins')
|
|
1292
|
-
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1313
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1314
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions'))
|
|
1293
1315
|
return config;
|
|
1294
|
-
|
|
1316
|
+
// groupSlashRequireMentions is intentionally Agent-only. Relation/defaults
|
|
1317
|
+
// files allow forward-compatible unknown properties, so strip it before the
|
|
1318
|
+
// generic merge chain can treat a manually written value as an override.
|
|
1319
|
+
const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, ...rest } = config;
|
|
1295
1320
|
return rest;
|
|
1296
1321
|
}
|
|
1297
1322
|
function stripRelationRoleData(config) {
|
|
@@ -1330,6 +1355,7 @@ export function resolveEffective(sel, opts = {}) {
|
|
|
1330
1355
|
baseagents: config.baseagents,
|
|
1331
1356
|
chatmode: config.chatmode,
|
|
1332
1357
|
mentionMode: config.mentionMode,
|
|
1358
|
+
groupSlashRequireMentions: config.groupSlashRequireMentions ?? true,
|
|
1333
1359
|
model: config.model,
|
|
1334
1360
|
responseMode: config.responseMode,
|
|
1335
1361
|
responseModeParams: config.responseModeParams,
|
|
@@ -1361,6 +1387,7 @@ const EFFECTIVE_BEHAVIOR_FIELDS = [
|
|
|
1361
1387
|
'baseagents',
|
|
1362
1388
|
'chatmode',
|
|
1363
1389
|
'mentionMode',
|
|
1390
|
+
'groupSlashRequireMentions',
|
|
1364
1391
|
'flush_delay',
|
|
1365
1392
|
'debounce',
|
|
1366
1393
|
'show_activities',
|
|
@@ -14,6 +14,7 @@ const LOCK_NAME = '.contact-book-mutation.lock';
|
|
|
14
14
|
const JOURNAL_NAME = '.contact-book-mutation.json';
|
|
15
15
|
const LOCK_WAIT_MS = 5_000;
|
|
16
16
|
const LOCK_RETRY_MS = 20;
|
|
17
|
+
const CORRUPT_LOCK_STALE_MS = LOCK_WAIT_MS;
|
|
17
18
|
export const EMPTY_CONTACT_REVISION = crypto.createHash('sha256').update('').digest('hex');
|
|
18
19
|
export class ContactMutationError extends Error {
|
|
19
20
|
code;
|
|
@@ -454,7 +455,10 @@ function acquireAgentLock(selfAid) {
|
|
|
454
455
|
return () => releaseAgentLock(lockDir, ownerFile, lockToken);
|
|
455
456
|
}
|
|
456
457
|
catch (error) {
|
|
457
|
-
|
|
458
|
+
const lockMayAlreadyExist = error?.code === 'EEXIST'
|
|
459
|
+
|| error?.code === 'ENOTEMPTY'
|
|
460
|
+
|| (process.platform === 'win32' && error?.code === 'EPERM' && fs.existsSync(lockDir));
|
|
461
|
+
if (!lockMayAlreadyExist)
|
|
458
462
|
throw error;
|
|
459
463
|
if (removeStaleLock(lockDir, ownerFile))
|
|
460
464
|
continue;
|
|
@@ -478,10 +482,10 @@ function removeStaleLock(lockDir, ownerFile) {
|
|
|
478
482
|
pid = Number(JSON.parse(rawOwner).pid);
|
|
479
483
|
}
|
|
480
484
|
catch {
|
|
481
|
-
return
|
|
485
|
+
return reclaimCorruptLock(lockDir);
|
|
482
486
|
}
|
|
483
487
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
484
|
-
return
|
|
488
|
+
return reclaimCorruptLock(lockDir);
|
|
485
489
|
try {
|
|
486
490
|
process.kill(pid, 0);
|
|
487
491
|
return false;
|
|
@@ -503,6 +507,24 @@ function removeStaleLock(lockDir, ownerFile) {
|
|
|
503
507
|
}
|
|
504
508
|
}
|
|
505
509
|
}
|
|
510
|
+
function reclaimCorruptLock(lockDir) {
|
|
511
|
+
try {
|
|
512
|
+
const stat = fs.statSync(lockDir);
|
|
513
|
+
if (Date.now() - stat.mtimeMs < CORRUPT_LOCK_STALE_MS)
|
|
514
|
+
return false;
|
|
515
|
+
const staleClaim = `${lockDir}.reclaimed-corrupt-${crypto.randomBytes(8).toString('hex')}`;
|
|
516
|
+
fs.renameSync(lockDir, staleClaim);
|
|
517
|
+
fs.rmSync(staleClaim, { recursive: true, force: true });
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (error?.code === 'ENOENT')
|
|
522
|
+
return true;
|
|
523
|
+
if (error?.code === 'EEXIST' || error?.code === 'ENOTEMPTY' || error?.code === 'EPERM')
|
|
524
|
+
return false;
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
506
528
|
function releaseAgentLock(lockDir, ownerFile, lockToken) {
|
|
507
529
|
try {
|
|
508
530
|
const owner = JSON.parse(fs.readFileSync(ownerFile, 'utf8'));
|
|
@@ -342,8 +342,20 @@ function processIsAlive(pid) {
|
|
|
342
342
|
function assertDaemonStopped(home) {
|
|
343
343
|
const candidates = [];
|
|
344
344
|
const pidFile = path.join(home, 'daemon.pid');
|
|
345
|
-
|
|
346
|
-
|
|
345
|
+
try {
|
|
346
|
+
// daemon.pid was retired in favor of the instance registry. It may still
|
|
347
|
+
// exist as a stale legacy file, but a Windows Codex sandbox can also leave
|
|
348
|
+
// a directory at this path when protecting a not-yet-created file. Never
|
|
349
|
+
// feed a directory (or symlink) to readFileSync: that turns harmless
|
|
350
|
+
// legacy residue into an EISDIR startup failure.
|
|
351
|
+
const stat = fs.lstatSync(pidFile);
|
|
352
|
+
if (stat.isFile()) {
|
|
353
|
+
candidates.push({ pid: Number(fs.readFileSync(pidFile, 'utf8').trim()), source: pidFile });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
if (!isRecord(error) || error.code !== 'ENOENT')
|
|
358
|
+
throw error;
|
|
347
359
|
}
|
|
348
360
|
const instanceDir = path.join(home, 'data', 'instance');
|
|
349
361
|
if (fs.existsSync(instanceDir)) {
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
|
|
3
3
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
4
|
+
import { logger } from '../../utils/logger.js';
|
|
4
5
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
5
6
|
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
6
7
|
export class AgentDelegationRegistry {
|
|
8
|
+
runtimeEpoch = crypto.randomBytes(16).toString('hex');
|
|
7
9
|
grantsByHash = new Map();
|
|
8
10
|
activeHashBySession = new Map();
|
|
9
11
|
approvedCommands = new Map();
|
|
12
|
+
getRuntimeEpoch() {
|
|
13
|
+
return this.runtimeEpoch;
|
|
14
|
+
}
|
|
10
15
|
issue(input) {
|
|
11
16
|
if (input.executionIdentity && !isValidFullAccessExecutionIdentity(input.executionIdentity)) {
|
|
12
17
|
throw new Error('invalid fullaccess task execution identity');
|
|
@@ -24,6 +29,7 @@ export class AgentDelegationRegistry {
|
|
|
24
29
|
ok: false,
|
|
25
30
|
code: 'DELEGATION_REQUIRED',
|
|
26
31
|
reason: 'An active task delegation token is required',
|
|
32
|
+
diagnosticCause: 'missing_token',
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
const tokenHash = hashDelegationToken(token);
|
|
@@ -38,10 +44,19 @@ export class AgentDelegationRegistry {
|
|
|
38
44
|
: undefined;
|
|
39
45
|
if (approved)
|
|
40
46
|
return { ok: true, grant: approved };
|
|
47
|
+
const diagnosticCause = grant
|
|
48
|
+
? grant.sessionId !== sessionId
|
|
49
|
+
? 'session_mismatch'
|
|
50
|
+
: 'task_replaced'
|
|
51
|
+
: commandHash
|
|
52
|
+
? 'approved_command_unavailable'
|
|
53
|
+
: 'unknown_token';
|
|
54
|
+
logger.warn(`[AgentDelegation] validate failed: cause=${diagnosticCause} session=${sessionId} grantSession=${grant?.sessionId ?? 'none'} taskId=${grant?.taskId ?? 'none'} hasCommandHash=${!!commandHash} tokenKnown=${!!grant} activeForSession=${this.activeHashBySession.get(sessionId) ?? 'none'} grantTokenHash=${grant?.tokenHash ?? 'none'}`);
|
|
41
55
|
return {
|
|
42
56
|
ok: false,
|
|
43
57
|
code: 'INVALID_DELEGATION',
|
|
44
58
|
reason: 'The task delegation token is invalid, revoked, or belongs to another session',
|
|
59
|
+
diagnosticCause,
|
|
45
60
|
};
|
|
46
61
|
}
|
|
47
62
|
armApprovedCommand(input) {
|
|
@@ -192,6 +192,14 @@ function auditDecision(params, decision) {
|
|
|
192
192
|
permissionMode: params.auditMetadata?.permissionMode
|
|
193
193
|
?? (decision.allow ? decision.permissionMode ?? params.subject.permissionMode : params.subject.permissionMode),
|
|
194
194
|
toolName: params.auditMetadata?.toolName,
|
|
195
|
+
errorCode: params.auditMetadata?.errorCode
|
|
196
|
+
?? (decision.allow
|
|
197
|
+
? undefined
|
|
198
|
+
: params.auditMetadata?.decisionSource === 'approval'
|
|
199
|
+
? 'USER_DENIED'
|
|
200
|
+
: params.auditMetadata?.decisionSource === 'infrastructure'
|
|
201
|
+
? 'EXECUTION_FAILED'
|
|
202
|
+
: 'POLICY_DENIED'),
|
|
195
203
|
policyCode: params.auditMetadata?.policyCode ?? (decision.allow ? undefined : decision.code),
|
|
196
204
|
decisionSource: params.auditMetadata?.decisionSource ?? (decision.allow ? 'command' : 'policy'),
|
|
197
205
|
source: params.source,
|
|
@@ -2,7 +2,7 @@ import { logger } from '../../utils/logger.js';
|
|
|
2
2
|
import { LogWriter } from '../../utils/log-writer.js';
|
|
3
3
|
import { resolvePaths } from '../../paths.js';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
-
import { buildAuthorizationEventKey } from '../audit/event-key.js';
|
|
5
|
+
import { buildAuthorizationEventKey, buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
6
6
|
import { normalizeExecutionPermissionMode } from '../permission/mode.js';
|
|
7
7
|
export function auditCommandAuthorization(event) {
|
|
8
8
|
const shouldAudit = event.source === 'menu.cli' ||
|
|
@@ -95,10 +95,20 @@ export function auditFullAccessEvent(input) {
|
|
|
95
95
|
* denials otherwise only reach `logger.warn` and `TriggerExecutionAnomaly`.
|
|
96
96
|
*/
|
|
97
97
|
export function auditToolPreflightDenial(input) {
|
|
98
|
+
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
98
99
|
auditCommandAuthorization({
|
|
99
100
|
ts: Date.now(),
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
...(lifecycleId ? {
|
|
102
|
+
eventKey: buildToolLifecycleEventKey({
|
|
103
|
+
sessionId: input.sessionId,
|
|
104
|
+
callId: input.callId,
|
|
105
|
+
correlationId: input.correlationId,
|
|
106
|
+
requestId: input.requestId,
|
|
107
|
+
}),
|
|
108
|
+
eventPhase: 'preflight',
|
|
109
|
+
} : {}),
|
|
110
|
+
callId: input.callId ?? input.requestId,
|
|
111
|
+
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
102
112
|
source: 'agent-tool',
|
|
103
113
|
operation: 'tool.preflight.deny',
|
|
104
114
|
scope: 'filesystem',
|
|
@@ -107,6 +117,7 @@ export function auditToolPreflightDenial(input) {
|
|
|
107
117
|
executed: false,
|
|
108
118
|
executionState: 'blocked',
|
|
109
119
|
decisionSource: 'policy',
|
|
120
|
+
errorCode: 'POLICY_DENIED',
|
|
110
121
|
toolName: input.toolName,
|
|
111
122
|
policyCode: input.policyCode,
|
|
112
123
|
protectionClass: input.protectionClass ?? protectionClassForPolicy(input.policyCode),
|
|
@@ -135,9 +146,19 @@ export function auditToolPreflightDenial(input) {
|
|
|
135
146
|
* decision source make policy, user, and infrastructure denials distinct.
|
|
136
147
|
*/
|
|
137
148
|
export function auditCodexApprovalDecision(input) {
|
|
149
|
+
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
138
150
|
auditCommandAuthorization({
|
|
139
151
|
ts: Date.now(),
|
|
140
|
-
|
|
152
|
+
...(lifecycleId ? {
|
|
153
|
+
eventKey: buildToolLifecycleEventKey({
|
|
154
|
+
sessionId: input.sessionId,
|
|
155
|
+
callId: input.callId,
|
|
156
|
+
correlationId: input.correlationId,
|
|
157
|
+
requestId: input.requestId,
|
|
158
|
+
}),
|
|
159
|
+
eventPhase: 'approval',
|
|
160
|
+
} : {}),
|
|
161
|
+
callId: input.callId ?? input.correlationId ?? input.requestId,
|
|
141
162
|
source: 'agent-tool',
|
|
142
163
|
operation: 'codex.approval',
|
|
143
164
|
scope: input.toolName === 'PermissionGrant' ? 'agent' : 'filesystem',
|
|
@@ -146,8 +167,11 @@ export function auditCodexApprovalDecision(input) {
|
|
|
146
167
|
executed: false,
|
|
147
168
|
executionState: input.decision === 'allow' ? 'authorized' : 'blocked',
|
|
148
169
|
decisionSource: input.decisionSource,
|
|
170
|
+
errorCode: input.decision === 'deny'
|
|
171
|
+
? input.decisionSource === 'approval' ? 'USER_DENIED' : input.decisionSource === 'policy' ? 'POLICY_DENIED' : infrastructureErrorCode(input.policyCode)
|
|
172
|
+
: undefined,
|
|
149
173
|
requestId: input.requestId,
|
|
150
|
-
correlationId: input.correlationId ?? input.requestId,
|
|
174
|
+
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
151
175
|
sessionId: input.sessionId ?? 'unknown',
|
|
152
176
|
agentAid: input.agentAid ?? 'unknown',
|
|
153
177
|
agentName: input.agentName,
|
|
@@ -161,6 +185,9 @@ export function auditCodexApprovalDecision(input) {
|
|
|
161
185
|
taskId: input.taskId,
|
|
162
186
|
argsSummary: {
|
|
163
187
|
method: input.method,
|
|
188
|
+
...(input.summary ? { summary: input.summary } : {}),
|
|
189
|
+
...(input.threadId ? { threadId: input.threadId } : {}),
|
|
190
|
+
...(input.cwd ? { cwd: input.cwd } : {}),
|
|
164
191
|
...(input.parseFailure ? { parseFailure: input.parseFailure } : {}),
|
|
165
192
|
},
|
|
166
193
|
});
|
|
@@ -183,12 +210,18 @@ export function auditPermissionDecision(input) {
|
|
|
183
210
|
toolName: input.toolName,
|
|
184
211
|
policyCode: input.policyCode,
|
|
185
212
|
decisionSource: input.decisionSource,
|
|
213
|
+
errorCode: input.decisionSource === 'approval'
|
|
214
|
+
? 'USER_DENIED'
|
|
215
|
+
: input.decisionSource === 'policy'
|
|
216
|
+
? 'POLICY_DENIED'
|
|
217
|
+
: infrastructureErrorCode(input.policyCode),
|
|
186
218
|
source: 'agent-tool',
|
|
187
219
|
operation: 'permission.runtime',
|
|
188
220
|
scope: 'filesystem',
|
|
189
221
|
dangerous: false,
|
|
190
222
|
role: input.role ?? 'unknown',
|
|
191
223
|
taskId: input.taskId,
|
|
224
|
+
argsSummary: input.summary ? { summary: input.summary } : undefined,
|
|
192
225
|
decision: 'deny',
|
|
193
226
|
executed: false,
|
|
194
227
|
executionState: 'blocked',
|
|
@@ -205,6 +238,7 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
205
238
|
dangerous: false,
|
|
206
239
|
decision: 'deny',
|
|
207
240
|
decisionSource: 'infrastructure',
|
|
241
|
+
errorCode: infrastructureErrorCode(input.policyCode),
|
|
208
242
|
executed: false,
|
|
209
243
|
executionState: 'blocked',
|
|
210
244
|
callId: input.callId,
|
|
@@ -224,8 +258,26 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
224
258
|
: undefined,
|
|
225
259
|
});
|
|
226
260
|
}
|
|
261
|
+
function infrastructureErrorCode(policyCode) {
|
|
262
|
+
const code = String(policyCode ?? '').toLowerCase();
|
|
263
|
+
if (/delegat|carrier|not.?armed/.test(code))
|
|
264
|
+
return 'DELEGATION_FAILED';
|
|
265
|
+
if (/capability|sandbox|unavailable|workspace_unknown/.test(code))
|
|
266
|
+
return 'CAPABILITY_UNAVAILABLE';
|
|
267
|
+
if (/argument|canonical|quote|shell|parse|invalid/.test(code))
|
|
268
|
+
return 'INVALID_ARGUMENT';
|
|
269
|
+
return 'EXECUTION_FAILED';
|
|
270
|
+
}
|
|
227
271
|
function buildAuditRecord(event) {
|
|
228
272
|
const executed = event.executed ?? false;
|
|
273
|
+
const decisionSource = event.decisionSource ?? (event.decision === 'deny' ? 'policy' : 'command');
|
|
274
|
+
const errorCode = event.errorCode ?? (event.decision === 'deny'
|
|
275
|
+
? decisionSource === 'approval'
|
|
276
|
+
? 'USER_DENIED'
|
|
277
|
+
: decisionSource === 'policy'
|
|
278
|
+
? 'POLICY_DENIED'
|
|
279
|
+
: 'EXECUTION_FAILED'
|
|
280
|
+
: undefined);
|
|
229
281
|
const executionState = event.executionState
|
|
230
282
|
?? (event.decision === 'deny'
|
|
231
283
|
? 'blocked'
|
|
@@ -244,6 +296,7 @@ function buildAuditRecord(event) {
|
|
|
244
296
|
operation: event.operation,
|
|
245
297
|
})
|
|
246
298
|
: undefined),
|
|
299
|
+
eventPhase: event.eventPhase,
|
|
247
300
|
callId: event.callId,
|
|
248
301
|
correlationId: event.correlationId ?? event.requestId,
|
|
249
302
|
requestId: event.requestId,
|
|
@@ -253,10 +306,15 @@ function buildAuditRecord(event) {
|
|
|
253
306
|
permissionMode: normalizeAuditPermissionMode(event.permissionMode),
|
|
254
307
|
toolName: event.toolName,
|
|
255
308
|
approvalMethod: event.approvalMethod,
|
|
309
|
+
errorCode,
|
|
256
310
|
policyCode: event.policyCode ?? event.code,
|
|
257
311
|
protectionClass: event.protectionClass,
|
|
258
312
|
matchedPath: event.matchedPath,
|
|
259
|
-
decisionSource
|
|
313
|
+
decisionSource,
|
|
314
|
+
ecwebRequestId: event.ecwebRequestId,
|
|
315
|
+
targetAgentAid: redactIdentifier(event.targetAgentAid),
|
|
316
|
+
endpoint: event.endpoint,
|
|
317
|
+
logicalToolName: event.logicalToolName,
|
|
260
318
|
source: event.source,
|
|
261
319
|
operation: event.operation,
|
|
262
320
|
scope: event.scope,
|
|
@@ -343,11 +401,17 @@ function logAuditEvent(record) {
|
|
|
343
401
|
record.callId ? `call=${record.callId}` : null,
|
|
344
402
|
record.correlationId ? `correlation=${record.correlationId}` : null,
|
|
345
403
|
record.eventKey ? `eventKey=${record.eventKey}` : null,
|
|
404
|
+
record.eventPhase ? `eventPhase=${record.eventPhase}` : null,
|
|
346
405
|
record.sessionId ? `session=${record.sessionId}` : null,
|
|
347
406
|
record.agentName ? `agentName=${JSON.stringify(record.agentName)}` : null,
|
|
348
407
|
record.agentAid ? `agent=${record.agentAid}` : null,
|
|
349
408
|
record.permissionMode ? `permissionMode=${record.permissionMode}` : null,
|
|
350
409
|
record.toolName ? `tool=${record.toolName}` : null,
|
|
410
|
+
record.errorCode ? `errorCode=${record.errorCode}` : null,
|
|
411
|
+
record.ecwebRequestId ? `ecwebRequest=${record.ecwebRequestId}` : null,
|
|
412
|
+
record.targetAgentAid ? `targetAgent=${record.targetAgentAid}` : null,
|
|
413
|
+
record.endpoint ? `endpoint=${JSON.stringify(record.endpoint)}` : null,
|
|
414
|
+
record.logicalToolName ? `logicalTool=${record.logicalToolName}` : null,
|
|
351
415
|
record.policyCode ? `policy=${record.policyCode}` : null,
|
|
352
416
|
record.protectionClass ? `protectionClass=${record.protectionClass}` : null,
|
|
353
417
|
record.matchedPath ? `matchedPath=${JSON.stringify(record.matchedPath)}` : null,
|
|
@@ -38,6 +38,10 @@ const GROUP_MANAGEMENT_OPERATIONS = [
|
|
|
38
38
|
['ec.group.join', 'write-agent', ['agent'], 'Request to join a group'],
|
|
39
39
|
['ec.group.leave', 'write-agent', ['agent'], 'Leave a group'],
|
|
40
40
|
['ec.group.invite', 'write-agent', ['agent'], 'Invite group members'],
|
|
41
|
+
['ec.group.invites', 'read', ['agent'], 'List pending targeted group invitations'],
|
|
42
|
+
['ec.group.accept-invite', 'write-agent', ['agent'], 'Accept a targeted group invitation'],
|
|
43
|
+
['ec.group.reject-invite', 'write-agent', ['agent'], 'Reject a targeted group invitation'],
|
|
44
|
+
['ec.group.revoke-invite', 'write-agent', ['agent'], 'Revoke a targeted group invitation'],
|
|
41
45
|
['ec.group.kick', 'write-agent', ['agent'], 'Remove a group member'],
|
|
42
46
|
['ec.group.role', 'write-agent', ['agent'], 'Change a group member role'],
|
|
43
47
|
['ec.group.owner', 'write-agent', ['agent'], 'Transfer group ownership'],
|