evolcore 0.0.25 → 0.0.27
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 +632 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +203 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +172 -33
- package/dist/agents/codex/app-server-client.js +76 -31
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +434 -446
- package/dist/agents/ecagent-runner.js +174 -34
- package/dist/agents/gemini-runner.js +8 -7
- package/dist/agents/runner-types.js +75 -19
- package/dist/aun/aid/agentmd.js +151 -36
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +4 -4
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +162 -39
- package/dist/aun/{menu-client.js → menu/client.js} +85 -6
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +85 -19
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +19 -9
- package/dist/aun/msg/upload.js +43 -80
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +842 -318
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +212 -89
- package/dist/cli/agent-create.js +626 -11
- package/dist/cli/agent.js +125 -13
- package/dist/cli/aun-commands.js +487 -337
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +55 -33
- package/dist/cli/contact.js +72 -15
- package/dist/cli/ctl-command.js +24 -24
- package/dist/cli/daemon-commands.js +140 -63
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +70 -70
- package/dist/cli/handoff-command.js +46 -14
- package/dist/cli/index.js +91 -79
- package/dist/cli/init.js +34 -1
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +5 -1
- package/dist/cli/menu-client.js +51 -32
- package/dist/cli/model.js +3 -3
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +31 -31
- package/dist/cli/response.js +32 -32
- package/dist/cli/restart-monitor.js +103 -42
- package/dist/cli/stats.js +51 -51
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +74 -64
- package/dist/config/builtin-roles.js +11 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +160 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +97 -8
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +30 -7
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +7 -1
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +53 -49
- package/dist/core/auth/operation-authorizer.js +90 -7
- package/dist/core/auth/operation-catalog.js +145 -4
- package/dist/core/auth/remote-access-store.js +155 -10
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +111 -20
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +159 -65
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +46 -7
- package/dist/core/command/menu-handler.js +392 -100
- package/dist/core/command/menu-protocol.js +45 -6
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +123 -43
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +146 -34
- package/dist/core/message/message-bridge.js +163 -20
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +99 -41
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +755 -144
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +112 -41
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +108 -17
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +9 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +588 -217
- package/dist/ipc.js +193 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +61 -2
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +26 -14
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +196 -23
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +60 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +69 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +379 -367
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +76 -71
- package/kits/docs/evolcore/agent-create.md +202 -165
- package/kits/docs/evolcore/agent.md +99 -67
- package/kits/docs/evolcore/aid.md +68 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +241 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +244 -244
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +267 -152
- package/kits/docs/evolcore/menu-client.md +58 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +169 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +53 -50
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +623 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +42 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +217 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +166 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +76 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +65 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +451 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +215 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +101 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +90 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +53 -34
- package/kits/templates/system-fragments/channel.md +28 -64
- package/kits/templates/system-fragments/commands.md +36 -28
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/reply.md +56 -0
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +104 -98
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -5,6 +5,10 @@ import { currentVersion, fieldSchemaDefault, isSchemaName, listSchemaNames, list
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { resolvePaths, agentDir, agentRelationConfig } from '../paths.js';
|
|
8
|
+
import { roleMenuQuery, roleMenuUpdate } from '../core/command/role-menu.js';
|
|
9
|
+
import { parsePeerKey, PeerIdentityCache } from '../core/relation/peer-identity.js';
|
|
10
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
11
|
+
import { isUnsupportedBaseagentConfigField } from './config-field-policy.js';
|
|
8
12
|
export const AUTHENTICATED_CONFIG_ROLE_ENV = 'EVOLCORE_AUTHENTICATED_CONFIG_ROLE';
|
|
9
13
|
export function executeResolvedConfigCommand(command, options = {}) {
|
|
10
14
|
try {
|
|
@@ -23,11 +27,150 @@ export function executeResolvedConfigCommand(command, options = {}) {
|
|
|
23
27
|
return { ok: false, code: 'CONFIG_ERROR', error: error instanceof Error ? error.message : String(error) };
|
|
24
28
|
}
|
|
25
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Execute a config command that may use an asynchronous domain service.
|
|
32
|
+
* Relation role assignments are intentionally handled here instead of by
|
|
33
|
+
* mutateConfig(), so Role Menu remains the single write path for validation,
|
|
34
|
+
* revisions, journaling, and audit.
|
|
35
|
+
*/
|
|
36
|
+
export async function executeResolvedConfigCommandAsync(command, options = {}) {
|
|
37
|
+
try {
|
|
38
|
+
if (isRelationRoleCommand(command)) {
|
|
39
|
+
return await executeRelationRoleCommand(command, options);
|
|
40
|
+
}
|
|
41
|
+
return executeResolvedConfigCommand(command, options);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof ConfigError)
|
|
45
|
+
return { ok: false, code: error.code, error: error.message, ...(error.data !== undefined ? { data: error.data } : {}) };
|
|
46
|
+
const roleError = error;
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
code: roleError?.code || 'CONFIG_ERROR',
|
|
50
|
+
error: roleError?.message || String(error),
|
|
51
|
+
...(roleError?.data !== undefined ? { data: roleError.data } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isRelationRoleCommand(command) {
|
|
56
|
+
return command.kind === 'field'
|
|
57
|
+
&& command.field === 'role'
|
|
58
|
+
&& command.configScope === 'relation'
|
|
59
|
+
&& (command.operationId === 'role.relation.read'
|
|
60
|
+
|| command.operationId === 'role.assign'
|
|
61
|
+
|| command.operationId === 'role.revoke');
|
|
62
|
+
}
|
|
63
|
+
async function executeRelationRoleCommand(op, options) {
|
|
64
|
+
if (!op.self || !op.peerKey) {
|
|
65
|
+
return { ok: false, code: 'PEER_REQUIRED', error: 'role requires --self <aid> --peer <peer-aid>' };
|
|
66
|
+
}
|
|
67
|
+
const target = roleTargetFromPeerKey(op.self, op.peerKey);
|
|
68
|
+
const context = options.roleContext ?? {
|
|
69
|
+
self: op.self,
|
|
70
|
+
actorRole: options.role || 'owner',
|
|
71
|
+
actorRoleSource: 'local-cli',
|
|
72
|
+
isDaemonOwner: false,
|
|
73
|
+
fromControlChannel: false,
|
|
74
|
+
aunAvailable: true,
|
|
75
|
+
};
|
|
76
|
+
if (context.self !== op.self) {
|
|
77
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Role target does not match the receiving Agent' };
|
|
78
|
+
}
|
|
79
|
+
const targetArgs = { self: op.self, view: 'target', target };
|
|
80
|
+
const current = await roleMenuQuery(context, targetArgs);
|
|
81
|
+
const explicitRole = current?.assignment?.explicitRole
|
|
82
|
+
?? current?.assignment?.explicitDefaultRole
|
|
83
|
+
?? null;
|
|
84
|
+
const effectiveRole = current?.effectiveRole?.roleId
|
|
85
|
+
?? current?.baselineRole?.roleId
|
|
86
|
+
?? null;
|
|
87
|
+
const effectiveRoleSource = current?.effectiveRole?.source
|
|
88
|
+
?? current?.baselineRole?.source
|
|
89
|
+
?? 'none';
|
|
90
|
+
const roleDetails = { explicitRole, effectiveRole, effectiveRoleSource };
|
|
91
|
+
if (op.subcommand === 'get') {
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
subcommand: 'get',
|
|
95
|
+
field: op.field,
|
|
96
|
+
value: explicitRole,
|
|
97
|
+
scope: op.configScope,
|
|
98
|
+
roleDetails,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const nextValue = op.subcommand === 'unset' ? null : op.value;
|
|
102
|
+
const updated = await roleMenuUpdate(context, {
|
|
103
|
+
self: op.self,
|
|
104
|
+
resource: 'assignment',
|
|
105
|
+
target,
|
|
106
|
+
expectedPolicyRevision: current.policyRevision,
|
|
107
|
+
expectedRelationRevision: current.relationRevision,
|
|
108
|
+
}, JSON.stringify(nextValue));
|
|
109
|
+
const updatedExplicitRole = updated?.assignment?.explicitRole
|
|
110
|
+
?? updated?.assignment?.explicitDefaultRole
|
|
111
|
+
?? null;
|
|
112
|
+
const updatedEffectiveRole = updated?.effectiveRole?.roleId
|
|
113
|
+
?? updated?.baselineRole?.roleId
|
|
114
|
+
?? null;
|
|
115
|
+
const updatedEffectiveRoleSource = updated?.effectiveRole?.source
|
|
116
|
+
?? updated?.baselineRole?.source
|
|
117
|
+
?? 'none';
|
|
118
|
+
const updatedDetails = {
|
|
119
|
+
explicitRole: updatedExplicitRole,
|
|
120
|
+
effectiveRole: updatedEffectiveRole,
|
|
121
|
+
effectiveRoleSource: updatedEffectiveRoleSource,
|
|
122
|
+
};
|
|
123
|
+
if (op.subcommand === 'unset') {
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
subcommand: 'unset',
|
|
127
|
+
field: op.field,
|
|
128
|
+
removed: updated.changed === true,
|
|
129
|
+
scope: op.configScope,
|
|
130
|
+
roleDetails: updatedDetails,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
subcommand: 'set',
|
|
136
|
+
field: op.field,
|
|
137
|
+
value: updatedExplicitRole,
|
|
138
|
+
scope: op.configScope,
|
|
139
|
+
file: 'relation-config',
|
|
140
|
+
roleDetails: updatedDetails,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function roleTargetFromPeerKey(self, peerKey) {
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = parsePeerKey(peerKey);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new ConfigError('INVALID_SELECTOR', `Invalid relation peer key: ${peerKey}`);
|
|
150
|
+
}
|
|
151
|
+
if (parsed.channelType !== 'aun') {
|
|
152
|
+
throw new ConfigError('NOT_SUPPORTED', 'Relation role assignments currently support AUN peers only');
|
|
153
|
+
}
|
|
154
|
+
const relation = read(ConfigTarget.Relation, { self, peerKey });
|
|
155
|
+
const identity = PeerIdentityCache.get('aun', parsed.channelId, agentDir(self));
|
|
156
|
+
const verifiedType = identity?.aid === parsed.channelId && identity.source === 'agentmd' && identity.verifiedAt > 0
|
|
157
|
+
? identity.type : undefined;
|
|
158
|
+
const isGroup = relation?.targetKind ? relation.targetKind === 'group'
|
|
159
|
+
: verifiedType ? verifiedType === 'group' : isExplicitGroupId(parsed.channelId);
|
|
160
|
+
return isGroup
|
|
161
|
+
? { kind: 'group', groupId: parsed.channelId }
|
|
162
|
+
: { kind: 'private', peerAid: parsed.channelId };
|
|
163
|
+
}
|
|
26
164
|
export function executeResolvedConfigOperation(op, options = {}) {
|
|
27
165
|
try {
|
|
28
166
|
const missingAgent = selectedAgentError(op);
|
|
29
167
|
if (missingAgent)
|
|
30
168
|
return missingAgent;
|
|
169
|
+
if (op.configScope === 'agent'
|
|
170
|
+
&& (op.field === 'fullaccess' || op.field.startsWith('fullaccess.'))
|
|
171
|
+
&& op.subcommand !== 'get') {
|
|
172
|
+
return { ok: false, code: 'FORBIDDEN', error: 'Agent fullaccess can only be configured in ECWeb Agent settings' };
|
|
173
|
+
}
|
|
31
174
|
if (op.subcommand === 'get')
|
|
32
175
|
return executeGet(op, options);
|
|
33
176
|
if (op.subcommand === 'set')
|
|
@@ -222,6 +365,9 @@ function executeSchema(command) {
|
|
|
222
365
|
};
|
|
223
366
|
}
|
|
224
367
|
function executeGet(op, options) {
|
|
368
|
+
if (isUnsupportedBaseagentConfigField(op.field)) {
|
|
369
|
+
return { ok: false, code: 'UNSUPPORTED_FIELD', error: `Config field ${op.field} is not supported by this version` };
|
|
370
|
+
}
|
|
225
371
|
const selector = selectorFor(op, options);
|
|
226
372
|
if (op.configScope === 'process') {
|
|
227
373
|
const config = read(ConfigTarget.Process, selector) || {};
|
|
@@ -298,6 +444,13 @@ function getWithSchemaDefault(op, absent, note) {
|
|
|
298
444
|
return { ok: true, subcommand: 'get', field: op.field, value: absent, scope: op.configScope, ...nt };
|
|
299
445
|
}
|
|
300
446
|
function executeSet(op, options) {
|
|
447
|
+
if (op.field === 'role') {
|
|
448
|
+
return {
|
|
449
|
+
ok: false,
|
|
450
|
+
code: 'ROLE_POLICY_MANAGED',
|
|
451
|
+
error: 'Relation role assignments require the asynchronous role assignment service',
|
|
452
|
+
};
|
|
453
|
+
}
|
|
301
454
|
if (!op.route)
|
|
302
455
|
return { ok: false, code: 'UNKNOWN_FIELD', error: `Unknown config field: ${op.field}` };
|
|
303
456
|
const selector = selectorFor(op, options);
|
|
@@ -341,6 +494,13 @@ function executeSet(op, options) {
|
|
|
341
494
|
};
|
|
342
495
|
}
|
|
343
496
|
function executeUnset(op, options) {
|
|
497
|
+
if (op.field === 'role') {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
code: 'ROLE_POLICY_MANAGED',
|
|
501
|
+
error: 'Relation role assignments require the asynchronous role assignment service',
|
|
502
|
+
};
|
|
503
|
+
}
|
|
344
504
|
if (!op.route)
|
|
345
505
|
return { ok: false, code: 'UNKNOWN_FIELD', error: `Unknown config field: ${op.field}` };
|
|
346
506
|
const selector = selectorFor(op, options);
|
|
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
5
|
-
import { isExplicitGroupId } from '../aun/group
|
|
5
|
+
import { isExplicitGroupId } from '../aun/group/index.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';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentContactConfig } from '../paths.js';
|
|
2
2
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
3
|
-
import { isExplicitGroupId } from '../aun/group
|
|
3
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
4
4
|
import { ConfigTarget, read, validateConfig } from './config-manager.js';
|
|
5
5
|
import { ContactMutationError, contactRevision, mutateContactBookSync } from './contact-book-store.js';
|
|
6
6
|
import { formatContactAlias, normalizeContactChannelKey, parseContactAlias, } from './contact-alias.js';
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
3
|
-
import { isExplicitGroupId } from '../aun/group
|
|
3
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
4
4
|
import { agentConfig } from '../paths.js';
|
|
5
5
|
import { getContactSnapshot, resolveContactView } from './contact-book.js';
|
|
6
6
|
import { ContactMutationError } from './contact-book-store.js';
|
|
7
7
|
import { addContact, mutateContactWithOperation } from './contact-request-service.js';
|
|
8
|
+
import { expirePendingContactRequests, reviewContactRequest } from './contact-request-service.js';
|
|
9
|
+
import { listStaticAgentOwners } from './peer-role-resolver.js';
|
|
8
10
|
export function resolveContactCommand(argv) {
|
|
9
11
|
const args = argv[0] === 'contact' ? argv.slice(1) : [...argv];
|
|
10
12
|
const selfAid = String(argValue(args, '--self') || '').trim();
|
|
@@ -15,12 +17,41 @@ export function resolveContactCommand(argv) {
|
|
|
15
17
|
if (extra.length > 0)
|
|
16
18
|
return invalid('INVALID_ARGUMENT', `Unexpected contact arguments: ${extra.join(' ')}`);
|
|
17
19
|
if (command === 'list') {
|
|
18
|
-
if (subcommand !== undefined
|
|
19
|
-
return invalid('INVALID_ARGUMENT', '
|
|
20
|
+
if (subcommand !== undefined) {
|
|
21
|
+
return invalid('INVALID_ARGUMENT', 'contact list does not accept a positional argument');
|
|
20
22
|
}
|
|
23
|
+
const filter = args.includes('--blocked')
|
|
24
|
+
? 'blocked'
|
|
25
|
+
: args.includes('--pending')
|
|
26
|
+
? 'pending'
|
|
27
|
+
: args.includes('--all') ? 'all' : undefined;
|
|
28
|
+
if (!filter)
|
|
29
|
+
return invalid('INVALID_ARGUMENT', 'contact list requires --blocked, --pending, or --all');
|
|
21
30
|
return {
|
|
22
31
|
ok: true,
|
|
23
|
-
command: { kind: 'list-blocked', operationId: 'contact.read', selfAid, canonicalArgv: canonicalArgv(argv) },
|
|
32
|
+
command: { kind: 'list-blocked', operationId: 'contact.read', selfAid, filter, canonicalArgv: canonicalArgv(argv) },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (command === 'approve' || command === 'reject' || command === 'block-request') {
|
|
36
|
+
if (!isValidAid(subcommand || '') || isExplicitGroupId(subcommand || '')) {
|
|
37
|
+
return invalid('INVALID_AID', 'primaryId must be a valid individual AID');
|
|
38
|
+
}
|
|
39
|
+
const requestId = String(argValue(args, '--request-id') || '').trim();
|
|
40
|
+
if (!requestId)
|
|
41
|
+
return invalid('INVALID_ARGUMENT', `${command} requires --request-id <request-id>`);
|
|
42
|
+
const expectedContactRevision = argValue(args, '--contact-revision')?.trim() || undefined;
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
command: {
|
|
46
|
+
kind: 'review-request',
|
|
47
|
+
operationId: 'contact.review',
|
|
48
|
+
selfAid,
|
|
49
|
+
primaryId: subcommand,
|
|
50
|
+
requestId,
|
|
51
|
+
decision: command === 'approve' ? 'approve' : command === 'reject' ? 'reject' : 'block',
|
|
52
|
+
expectedContactRevision,
|
|
53
|
+
canonicalArgv: canonicalArgv(argv),
|
|
54
|
+
},
|
|
24
55
|
};
|
|
25
56
|
}
|
|
26
57
|
if (command === 'block' || command === 'unblock') {
|
|
@@ -62,13 +93,63 @@ export async function executeResolvedContactCommand(command, actor = 'local-cli'
|
|
|
62
93
|
}
|
|
63
94
|
try {
|
|
64
95
|
if (command.kind === 'list-blocked') {
|
|
96
|
+
await expirePendingContactRequests(command.selfAid);
|
|
65
97
|
const snapshot = getContactSnapshot(command.selfAid);
|
|
98
|
+
const ids = command.filter === 'blocked'
|
|
99
|
+
? [...snapshot.blockedIndex]
|
|
100
|
+
: [...snapshot.contacts.entries()]
|
|
101
|
+
.filter(([, entry]) => command.filter === 'all' || entry.status === 'pending')
|
|
102
|
+
.map(([id]) => id);
|
|
103
|
+
const items = ids.sort().map(id => contactItem(command.selfAid, id));
|
|
104
|
+
if (command.filter === 'blocked') {
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
operation: 'list-blocked',
|
|
108
|
+
selfAid: command.selfAid,
|
|
109
|
+
contactRevision: snapshot.contactRevision,
|
|
110
|
+
items,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
66
113
|
return {
|
|
67
114
|
ok: true,
|
|
68
|
-
operation: 'list-
|
|
115
|
+
operation: 'list-contacts',
|
|
69
116
|
selfAid: command.selfAid,
|
|
117
|
+
filter: command.filter,
|
|
70
118
|
contactRevision: snapshot.contactRevision,
|
|
71
|
-
items
|
|
119
|
+
items,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (command.kind === 'review-request') {
|
|
123
|
+
const expectedContactRevision = command.expectedContactRevision ?? getContactSnapshot(command.selfAid).contactRevision;
|
|
124
|
+
const reviewerAid = actor === 'local-cli'
|
|
125
|
+
? listStaticAgentOwners(command.selfAid)[0]
|
|
126
|
+
: actor;
|
|
127
|
+
if (!reviewerAid) {
|
|
128
|
+
return { ok: false, code: 'NO_OWNER', error: `Agent has no owner: ${command.selfAid}` };
|
|
129
|
+
}
|
|
130
|
+
const reviewed = await reviewContactRequest({
|
|
131
|
+
selfAid: command.selfAid,
|
|
132
|
+
primaryId: command.primaryId,
|
|
133
|
+
requestId: command.requestId,
|
|
134
|
+
expectedContactRevision,
|
|
135
|
+
approverId: reviewerAid,
|
|
136
|
+
actorId: reviewerAid,
|
|
137
|
+
decision: command.decision,
|
|
138
|
+
});
|
|
139
|
+
if (reviewed.code !== 'approved' && reviewed.code !== 'rejected' && reviewed.code !== 'blocked') {
|
|
140
|
+
const code = reviewed.code === 'request-expired' ? 'REQUEST_EXPIRED'
|
|
141
|
+
: reviewed.code === 'revision-conflict' ? 'CONFLICT'
|
|
142
|
+
: reviewed.code === 'forbidden' ? 'PERMISSION_DENIED' : 'REQUEST_STALE';
|
|
143
|
+
return { ok: false, code, error: reviewed.code, data: { contactRevision: reviewed.contactRevision } };
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
operation: 'review-request',
|
|
148
|
+
selfAid: command.selfAid,
|
|
149
|
+
primaryId: command.primaryId,
|
|
150
|
+
requestId: command.requestId,
|
|
151
|
+
result: reviewed.code,
|
|
152
|
+
contactRevision: reviewed.contactRevision,
|
|
72
153
|
};
|
|
73
154
|
}
|
|
74
155
|
if (command.kind === 'set-blocked') {
|
|
@@ -129,16 +210,24 @@ function stripOptions(args) {
|
|
|
129
210
|
const result = [];
|
|
130
211
|
for (let index = 0; index < args.length; index += 1) {
|
|
131
212
|
const arg = args[index];
|
|
132
|
-
if (arg === '--self' || arg === '--format') {
|
|
213
|
+
if (arg === '--self' || arg === '--format' || arg === '--request-id' || arg === '--contact-revision') {
|
|
133
214
|
index += 1;
|
|
134
215
|
continue;
|
|
135
216
|
}
|
|
136
|
-
if (arg === '--blocked')
|
|
217
|
+
if (arg === '--blocked' || arg === '--pending' || arg === '--all')
|
|
137
218
|
continue;
|
|
138
219
|
result.push(arg);
|
|
139
220
|
}
|
|
140
221
|
return result;
|
|
141
222
|
}
|
|
223
|
+
function contactItem(selfAid, primaryId) {
|
|
224
|
+
const view = resolveContactView(selfAid, primaryId);
|
|
225
|
+
const entry = getContactSnapshot(selfAid).contacts.get(primaryId);
|
|
226
|
+
return {
|
|
227
|
+
...view,
|
|
228
|
+
...(entry?.pendingRequest ? { pendingRequest: { ...entry.pendingRequest } } : {}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
142
231
|
function canonicalArgv(argv) {
|
|
143
232
|
return argv[0] === 'contact' ? [...argv] : ['contact', ...argv];
|
|
144
233
|
}
|
|
@@ -10,7 +10,7 @@ import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from
|
|
|
10
10
|
import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
|
|
11
11
|
import { resolvePrincipal } from './contact-book.js';
|
|
12
12
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
13
|
-
import { isExplicitGroupId } from '../aun/group
|
|
13
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
14
14
|
export function isAuthenticated(userId) {
|
|
15
15
|
return isValidAid(String(userId || '').trim());
|
|
16
16
|
}
|
|
@@ -97,11 +97,15 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
97
97
|
reason: 'permissionMode is defined by roles; use the role policy editor',
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
if (field === '
|
|
100
|
+
if (subcommand !== 'get' && (field === 'initialization' || field.startsWith('initialization.'))) {
|
|
101
|
+
return { ok: false, code: 'INITIALIZATION_MANAGED', reason: 'initialization is owned by the creation service' };
|
|
102
|
+
}
|
|
103
|
+
const isRelationRole = field === 'role';
|
|
104
|
+
if (isRelationRole && selector.scope !== 'relation') {
|
|
101
105
|
return {
|
|
102
106
|
ok: false,
|
|
103
|
-
code: '
|
|
104
|
-
reason:
|
|
107
|
+
code: 'RELATION_SELECTOR_REQUIRED',
|
|
108
|
+
reason: `config ${subcommand} role needs a relation target: --self <aid> --peer <peer-aid>`,
|
|
105
109
|
};
|
|
106
110
|
}
|
|
107
111
|
if (subcommand !== 'get' && (field === 'roles' || field.startsWith('roles.'))) {
|
|
@@ -129,12 +133,14 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
129
133
|
reason: `Config field ${field} does not support object writes`,
|
|
130
134
|
};
|
|
131
135
|
}
|
|
132
|
-
const commandScope = selector.scope === 'defaults' ? 'process' : selector.scope;
|
|
136
|
+
const commandScope = isRelationRole ? 'agent' : selector.scope === 'defaults' ? 'process' : selector.scope;
|
|
133
137
|
const isSafe = fieldRule.class === 'safe-scalar' || fieldRule.class === 'safe-readonly-object';
|
|
134
138
|
const isManagementOperation = commandScope === 'process' || !isSafe;
|
|
135
|
-
const operationId =
|
|
136
|
-
? subcommand === 'get' ? '
|
|
137
|
-
:
|
|
139
|
+
const operationId = isRelationRole
|
|
140
|
+
? subcommand === 'get' ? 'role.relation.read' : subcommand === 'set' ? 'role.assign' : 'role.revoke'
|
|
141
|
+
: isManagementOperation
|
|
142
|
+
? subcommand === 'get' ? 'config.read' : 'config.write'
|
|
143
|
+
: `config.${subcommand}`;
|
|
138
144
|
let route;
|
|
139
145
|
const routeRequired = subcommand !== 'get' || !isManagementOperation;
|
|
140
146
|
try {
|
|
@@ -173,6 +179,23 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
173
179
|
return parsedOwners;
|
|
174
180
|
value = parsedOwners.value;
|
|
175
181
|
}
|
|
182
|
+
else if (isRelationRole) {
|
|
183
|
+
const raw = rawValue.trim();
|
|
184
|
+
if (!raw)
|
|
185
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'Config field role cannot be empty' };
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(raw);
|
|
188
|
+
if (typeof parsed === 'string')
|
|
189
|
+
value = parsed;
|
|
190
|
+
else if (parsed === null)
|
|
191
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'config set role requires a role ID; use config unset role to revoke it' };
|
|
192
|
+
else
|
|
193
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'Config field role must be a role ID or null' };
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
value = raw;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
176
199
|
else if (fieldRule.class === 'safe-scalar') {
|
|
177
200
|
const parsedValue = parseConfigFieldValue(field, rawValue);
|
|
178
201
|
if (!parsedValue.ok)
|
package/dist/config-store.js
CHANGED
|
@@ -58,7 +58,7 @@ export function loadDaemonConfig() {
|
|
|
58
58
|
return validateDaemonConfig(normalizeProcessConfigCompat(normalizeDaemonServices(sanitized)), false);
|
|
59
59
|
}
|
|
60
60
|
export function isFullAccessEnabled(config = loadDaemonConfig()) {
|
|
61
|
-
return config.fullaccess?.enabled === true;
|
|
61
|
+
return config.fullaccess?.enabled === undefined || config.fullaccess.enabled === true;
|
|
62
62
|
}
|
|
63
63
|
let eckSnapshotsConfigCache;
|
|
64
64
|
/** Parse the process-level snapshot gate once for the current daemon lifecycle. */
|
|
@@ -48,6 +48,8 @@ function errorFrom(error, fallback = 'agent operation failed') {
|
|
|
48
48
|
*/
|
|
49
49
|
export class AgentApplicationService {
|
|
50
50
|
deps;
|
|
51
|
+
initialization;
|
|
52
|
+
setInitializationService(service) { this.initialization = service; }
|
|
51
53
|
constructor(deps) {
|
|
52
54
|
this.deps = deps;
|
|
53
55
|
}
|
|
@@ -85,13 +87,14 @@ export class AgentApplicationService {
|
|
|
85
87
|
unique_peer_count: stats?.uniquePeerCount ?? 0,
|
|
86
88
|
}
|
|
87
89
|
: null;
|
|
88
|
-
const config = agent.config;
|
|
90
|
+
const config = (loadAgent(aid) ?? agent.config);
|
|
89
91
|
const identity = readAgentMdIdentity(aid);
|
|
90
92
|
const paths = resolvePaths();
|
|
91
93
|
return {
|
|
92
94
|
ok: true,
|
|
93
95
|
aid,
|
|
94
96
|
status: info.status || 'stopped',
|
|
97
|
+
lifecycle: config?.lifecycle ?? null,
|
|
95
98
|
identity,
|
|
96
99
|
config: {
|
|
97
100
|
baseagent: info.baseagent || null,
|
|
@@ -211,6 +214,8 @@ export class AgentApplicationService {
|
|
|
211
214
|
}
|
|
212
215
|
if (!config)
|
|
213
216
|
return { ok: false, error: `Agent "${aid}" not found`, code: 'NOT_FOUND' };
|
|
217
|
+
if (!enabled)
|
|
218
|
+
await this.initialization?.cancel(aid);
|
|
214
219
|
const previousEnabled = config.enabled;
|
|
215
220
|
try {
|
|
216
221
|
config.enabled = enabled;
|
|
@@ -239,6 +244,7 @@ export class AgentApplicationService {
|
|
|
239
244
|
return { ok: true, aid, enabled, reloaded: result.ok };
|
|
240
245
|
}
|
|
241
246
|
async delete(aid, purge = false) {
|
|
247
|
+
await this.initialization?.cancel(aid);
|
|
242
248
|
const paths = resolvePaths();
|
|
243
249
|
const agentDir = path.join(paths.agentsDir, aid);
|
|
244
250
|
const configPath = path.join(agentDir, 'config.json');
|
|
@@ -7,16 +7,31 @@ function stableDigest(parts) {
|
|
|
7
7
|
const material = parts.map(part => compactPart(part) ?? '-').join('\u0000');
|
|
8
8
|
return crypto.createHash('sha256').update(material).digest('hex').slice(0, 20);
|
|
9
9
|
}
|
|
10
|
+
/** Preserve distinct source IDs; fill aliases only when the producer omitted them. */
|
|
11
|
+
export function normalizeToolCorrelation(input) {
|
|
12
|
+
const callId = compactPart(input.callId);
|
|
13
|
+
const correlationId = compactPart(input.correlationId);
|
|
14
|
+
const requestId = compactPart(input.requestId);
|
|
15
|
+
return {
|
|
16
|
+
callId: callId ?? correlationId ?? requestId,
|
|
17
|
+
correlationId: correlationId ?? callId ?? requestId,
|
|
18
|
+
requestId: requestId ?? callId ?? correlationId,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
10
21
|
/** Stable across source/mirror records for one tool-call lifecycle. */
|
|
11
22
|
export function buildToolLifecycleEventKey(input) {
|
|
23
|
+
const { callId } = normalizeToolCorrelation(input);
|
|
24
|
+
// No source ID means no safe join. Never merge unrelated anonymous calls.
|
|
25
|
+
if (!callId)
|
|
26
|
+
return `tool:unlinked:${crypto.randomUUID()}`;
|
|
12
27
|
return `tool:${stableDigest([
|
|
13
28
|
input.sessionId ?? 'unknown',
|
|
14
|
-
|
|
29
|
+
callId,
|
|
15
30
|
])}`;
|
|
16
31
|
}
|
|
17
32
|
/** Stable across repeated projections of one authorization decision. */
|
|
18
33
|
export function buildAuthorizationEventKey(input) {
|
|
19
|
-
const lifecycleId = input.callId
|
|
34
|
+
const lifecycleId = normalizeToolCorrelation(input).callId;
|
|
20
35
|
if (lifecycleId) {
|
|
21
36
|
return `${buildToolLifecycleEventKey({
|
|
22
37
|
sessionId: input.sessionId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { buildToolLifecycleEventKey, normalizeToolCorrelation } from './event-key.js';
|
|
4
5
|
// Logger rotation is hourly/daily, but the suffix is intentionally generic so
|
|
5
6
|
// this check also covers future rotation formats without changing the audit.
|
|
6
7
|
const STRUCTURED_LOG_RE = /^(?:daemon|events|channel-in|channel-out|messages|command-audit)(?:-[^.]+)?\.log$/;
|
|
@@ -33,6 +34,7 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
33
34
|
// daemon logs mix human-readable entries with structured audit lines.
|
|
34
35
|
// Ignore the former, but still validate malformed structured payloads.
|
|
35
36
|
if (MIXED_TEXT_LOG_RE.test(file)) {
|
|
37
|
+
raw = raw.replace(/^\[[^\]]+\] \[(?:DEBUG|INFO|WARN|ERROR)\] /, '');
|
|
36
38
|
if (raw.startsWith('[CommandAuditRecord] '))
|
|
37
39
|
raw = raw.slice('[CommandAuditRecord] '.length);
|
|
38
40
|
else if (!raw.startsWith('{'))
|
|
@@ -57,8 +59,9 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
57
59
|
issues.push({ file, line: index + 1, reason: 'missing_agent', key });
|
|
58
60
|
if (isLifecycleRecord(value) && !hasContextValue(lifecycleField(value, 'permissionMode')))
|
|
59
61
|
issues.push({ file, line: index + 1, reason: 'missing_permission_mode', key });
|
|
60
|
-
if (isCorrelatableRecord(value) && !key)
|
|
62
|
+
if (isCorrelatableRecord(value) && (!key || value.correlationMissing === true || key.startsWith('tool:unlinked:'))) {
|
|
61
63
|
issues.push({ file, line: index + 1, reason: 'missing_correlation' });
|
|
64
|
+
}
|
|
62
65
|
});
|
|
63
66
|
}
|
|
64
67
|
const seen = new Set();
|
|
@@ -79,16 +82,33 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
79
82
|
function structuredRecordKey(value) {
|
|
80
83
|
const nested = nestedRecord(value);
|
|
81
84
|
const eventKey = firstDefined(value, nested, ['eventKey', 'event_key']);
|
|
82
|
-
const eventPhase =
|
|
85
|
+
const eventPhase = recordPhase(value);
|
|
83
86
|
if (eventKey)
|
|
84
87
|
return `${String(eventKey)}${eventPhase ? `:${String(eventPhase)}` : ''}`;
|
|
88
|
+
if (isLifecycleRecord(value)) {
|
|
89
|
+
const correlation = normalizeToolCorrelation({
|
|
90
|
+
callId: firstDefined(value, nested, ['callId', 'call_id', 'toolUseId', 'tool_use_id']),
|
|
91
|
+
correlationId: firstDefined(value, nested, ['correlationId', 'correlation_id']),
|
|
92
|
+
requestId: firstDefined(value, nested, ['requestId', 'request_id']),
|
|
93
|
+
});
|
|
94
|
+
if (!correlation.callId)
|
|
95
|
+
return undefined;
|
|
96
|
+
return `${buildToolLifecycleEventKey({
|
|
97
|
+
sessionId: lifecycleField(value, 'sessionId'),
|
|
98
|
+
...correlation,
|
|
99
|
+
})}:${eventPhase}`;
|
|
100
|
+
}
|
|
85
101
|
const correlation = firstDefined(value, nested, [
|
|
86
102
|
'correlationId', 'correlation_id', 'callId', 'call_id', 'toolUseId', 'tool_use_id',
|
|
87
103
|
'requestId', 'request_id', 'msgId', 'operationId',
|
|
88
104
|
]);
|
|
89
105
|
if (!correlation)
|
|
90
106
|
return undefined;
|
|
91
|
-
return
|
|
107
|
+
return JSON.stringify([lifecycleField(value, 'sessionId'), canonicalEventType(value), correlation, eventPhase]);
|
|
108
|
+
}
|
|
109
|
+
function recordPhase(value) {
|
|
110
|
+
return firstDefined(value, nestedRecord(value), ['eventPhase', 'event_phase'])
|
|
111
|
+
?? (canonicalEventType(value) === 'tool_use' ? 'use' : canonicalEventType(value) === 'tool_result' ? 'result' : undefined);
|
|
92
112
|
}
|
|
93
113
|
function isLifecycleRecord(value) {
|
|
94
114
|
const type = canonicalEventType(value);
|
|
@@ -101,11 +121,13 @@ function isCorrelatableRecord(value) {
|
|
|
101
121
|
}
|
|
102
122
|
function stableHash(value) {
|
|
103
123
|
const nested = nestedRecord(value);
|
|
104
|
-
const eventKey =
|
|
124
|
+
const eventKey = structuredRecordKey(value);
|
|
105
125
|
const canonical = {
|
|
106
126
|
eventKey,
|
|
107
|
-
eventPhase:
|
|
127
|
+
eventPhase: recordPhase(value),
|
|
108
128
|
type: canonicalEventType(value),
|
|
129
|
+
operation: firstDefined(value, nested, ['operation']),
|
|
130
|
+
targetAgentAid: firstDefined(value, nested, ['targetAgentAid']),
|
|
109
131
|
toolName: firstDefined(value, nested, ['toolName', 'tool', 'name']),
|
|
110
132
|
callId: eventKey ? undefined : firstDefined(value, nested, ['callId', 'call_id', 'toolUseId', 'tool_use_id']),
|
|
111
133
|
correlationId: eventKey ? undefined : firstDefined(value, nested, ['correlationId', 'correlation_id']),
|
|
@@ -116,11 +138,15 @@ function stableHash(value) {
|
|
|
116
138
|
isError: firstDefined(value, nested, ['isError', 'is_error']) ?? (value.ok === false || nested?.ok === false ? true : undefined),
|
|
117
139
|
errorCode: firstDefined(value, nested, ['errorCode', 'error_code']),
|
|
118
140
|
decision: firstDefined(value, nested, ['decision']),
|
|
141
|
+
decisionSource: firstDefined(value, nested, ['decisionSource', 'decision_source']),
|
|
142
|
+
policyCode: firstDefined(value, nested, ['policyCode', 'policy_code']),
|
|
119
143
|
executed: firstDefined(value, nested, ['executed']),
|
|
120
144
|
executionState: firstDefined(value, nested, ['executionState', 'execution_state']),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
// A lifecycle stage may be mirrored without its large input/output body.
|
|
146
|
+
// Keep decision/classification differences, not projection payload size.
|
|
147
|
+
input: eventKey && isLifecycleRecord(value) ? undefined : firstDefined(value, nested, ['input']),
|
|
148
|
+
result: eventKey && isLifecycleRecord(value) ? undefined : firstDefined(value, nested, ['result', 'content']),
|
|
149
|
+
error: eventKey && isLifecycleRecord(value) ? undefined : firstDefined(value, nested, ['error', 'errorMessage']),
|
|
124
150
|
};
|
|
125
151
|
return crypto.createHash('sha256').update(JSON.stringify(canonical)).digest('hex').slice(0, 16);
|
|
126
152
|
}
|