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,34 +5,88 @@ import { getExistingHClassMaskTargets, getExistingLClassReadOnlyTargets, isSameO
|
|
|
5
5
|
import { resolveRoot } from '../../paths.js';
|
|
6
6
|
let cachedBubblewrapPath;
|
|
7
7
|
let sandboxInitializationCircuit;
|
|
8
|
+
const sessionSandboxInitializationCircuits = new Map();
|
|
8
9
|
let sandboxCircuitNoticeAt = 0;
|
|
10
|
+
let sandboxInitializationFailureCounters = emptySandboxInitializationFailureCounters();
|
|
9
11
|
export const SANDBOX_INITIALIZATION_FAILED = 'sandbox_initialization_failed';
|
|
10
12
|
export const SANDBOX_INITIALIZATION_COOLDOWN_MS = 5 * 60 * 1000;
|
|
11
13
|
export const SANDBOX_CIRCUIT_NOTICE_INTERVAL_MS = 60 * 1000;
|
|
14
|
+
function emptySandboxInitializationFailureCounters() {
|
|
15
|
+
return {
|
|
16
|
+
total: 0,
|
|
17
|
+
applySeccomp: 0,
|
|
18
|
+
byKind: { uid_map: 0, gid_map: 0, seccomp: 0, user_namespace: 0, bubblewrap: 0, unknown: 0 },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Classify already recognized bootstrap evidence, never arbitrary tool output. */
|
|
22
|
+
function sandboxInitializationFailureKind(text) {
|
|
23
|
+
if (/\buid_map\b/i.test(text))
|
|
24
|
+
return 'uid_map';
|
|
25
|
+
if (/\bgid_map\b/i.test(text))
|
|
26
|
+
return 'gid_map';
|
|
27
|
+
if (/\bseccomp\b/i.test(text))
|
|
28
|
+
return 'seccomp';
|
|
29
|
+
if (/user namespace/i.test(text))
|
|
30
|
+
return 'user_namespace';
|
|
31
|
+
if (/\b(?:bwrap|bubblewrap)\b/i.test(text))
|
|
32
|
+
return 'bubblewrap';
|
|
33
|
+
return 'unknown';
|
|
34
|
+
}
|
|
35
|
+
export function getSandboxInitializationFailureCounters() {
|
|
36
|
+
return { ...sandboxInitializationFailureCounters, byKind: { ...sandboxInitializationFailureCounters.byKind } };
|
|
37
|
+
}
|
|
12
38
|
const SANDBOX_DIAGNOSTIC_TARGET_LIMIT = 256;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
39
|
+
/** Host bootstrap failures open a process circuit; tool-result failures only
|
|
40
|
+
* affect their originating session because tool output is not host attestation. */
|
|
41
|
+
export function recordSandboxInitializationFailure(error, stderr = [], now = Date.now(), sessionId) {
|
|
42
|
+
const wrapped = createSandboxInitializationError(error, stderr);
|
|
43
|
+
const evidence = [wrapped.message, ...(wrapped.stderr ?? [])].join('\n');
|
|
44
|
+
const kind = sandboxInitializationFailureKind(evidence);
|
|
45
|
+
sandboxInitializationFailureCounters.total++;
|
|
46
|
+
sandboxInitializationFailureCounters.byKind[kind]++;
|
|
47
|
+
if (/\bapply-seccomp\b/i.test(evidence))
|
|
48
|
+
sandboxInitializationFailureCounters.applySeccomp++;
|
|
49
|
+
const detail = wrapped.message.slice(0, 512);
|
|
50
|
+
getActiveSandboxInitializationFailure(now);
|
|
51
|
+
const previous = sessionId ? sessionSandboxInitializationCircuits.get(sessionId) : sandboxInitializationCircuit;
|
|
52
|
+
const circuit = previous
|
|
17
53
|
? {
|
|
18
|
-
...
|
|
54
|
+
...previous,
|
|
19
55
|
lastObservedAt: now,
|
|
20
|
-
occurrences:
|
|
56
|
+
occurrences: previous.occurrences + 1,
|
|
21
57
|
detail,
|
|
58
|
+
kind,
|
|
22
59
|
}
|
|
23
|
-
: { openedAt: now, lastObservedAt: now, occurrences: 1, detail };
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
60
|
+
: { openedAt: now, lastObservedAt: now, occurrences: 1, detail, kind };
|
|
61
|
+
if (sessionId)
|
|
62
|
+
sessionSandboxInitializationCircuits.set(sessionId, circuit);
|
|
63
|
+
else
|
|
64
|
+
sandboxInitializationCircuit = circuit;
|
|
65
|
+
return { ...circuit };
|
|
66
|
+
}
|
|
67
|
+
/** Expire old failures and return the process or requested session circuit. */
|
|
68
|
+
export function getActiveSandboxInitializationFailure(now = Date.now(), cooldownMs = SANDBOX_INITIALIZATION_COOLDOWN_MS, sessionId) {
|
|
69
|
+
for (const [id, circuit] of sessionSandboxInitializationCircuits) {
|
|
70
|
+
if (now - circuit.lastObservedAt >= cooldownMs)
|
|
71
|
+
sessionSandboxInitializationCircuits.delete(id);
|
|
72
|
+
}
|
|
73
|
+
if (sandboxInitializationCircuit && now - sandboxInitializationCircuit.lastObservedAt >= cooldownMs) {
|
|
31
74
|
sandboxInitializationCircuit = undefined;
|
|
32
75
|
sandboxCircuitNoticeAt = 0;
|
|
33
|
-
return undefined;
|
|
34
76
|
}
|
|
35
|
-
|
|
77
|
+
const circuit = sandboxInitializationCircuit ?? (sessionId ? sessionSandboxInitializationCircuits.get(sessionId) : undefined);
|
|
78
|
+
return circuit ? { ...circuit } : undefined;
|
|
79
|
+
}
|
|
80
|
+
/** Only recognize the complete native bootstrap diagnostic, never a quotation
|
|
81
|
+
* embedded in ordinary tool output. Tool-result evidence is session-scoped. */
|
|
82
|
+
export function isSandboxInitializationToolResult(content) {
|
|
83
|
+
const text = typeof content === 'string'
|
|
84
|
+
? content
|
|
85
|
+
: Array.isArray(content) && content.every(block => block?.type === 'text' && typeof block.text === 'string')
|
|
86
|
+
? content.map(block => block.text).join('\n')
|
|
87
|
+
: '';
|
|
88
|
+
return /^(?:Exit code [1-9]\d*\r?\n)?apply-seccomp: write \/proc\/self\/(?:uid|gid)_map: (?:Operation not permitted|Permission denied)\s*$/i
|
|
89
|
+
.test(text.trim());
|
|
36
90
|
}
|
|
37
91
|
/** Rate-limit human-readable warnings; structured audit still records every blocked task. */
|
|
38
92
|
export function shouldLogSandboxCircuitNotice(now = Date.now(), intervalMs = SANDBOX_CIRCUIT_NOTICE_INTERVAL_MS) {
|
|
@@ -47,11 +101,13 @@ export function isSandboxInitializationFailure(error, stderr = []) {
|
|
|
47
101
|
.join('\n')
|
|
48
102
|
.toLowerCase();
|
|
49
103
|
return text.includes(SANDBOX_INITIALIZATION_FAILED)
|
|
104
|
+
|| /fs sandbox helper failed(?: with status)?\b/.test(text)
|
|
50
105
|
|| /(?:apply-seccomp|seccomp)[\s\S]*(?:uid_map|gid_map|operation not permitted)/.test(text)
|
|
51
106
|
|| /(?:uid_map|gid_map)[\s\S]*(?:operation not permitted|permission denied)/.test(text)
|
|
52
107
|
|| /user namespace[\s\S]*(?:not permitted|unavailable|failed)/.test(text)
|
|
53
108
|
|| /bubblewrap[\s\S]*namespace[\s\S]*(?:failed|not permitted|permission denied)/.test(text)
|
|
54
|
-
|| /(?:^|\n)\s*bwrap:\s*(?:can't|cannot|unable to)[\s\S]*(?:bind|mount|mount table)/.test(text)
|
|
109
|
+
|| /(?:^|\n)\s*bwrap:\s*(?:can't|cannot|unable to)[\s\S]*(?:bind|mount|mount table)/.test(text)
|
|
110
|
+
|| /(?:^|\n)\s*bwrap:\s*(?:can't|cannot) create (?:file|directory) at [^\n]+: read-only file system/.test(text);
|
|
55
111
|
}
|
|
56
112
|
export function createSandboxInitializationError(error, stderr = []) {
|
|
57
113
|
if (error instanceof Error
|
|
@@ -77,6 +133,39 @@ export function createSandboxInitializationError(error, stderr = []) {
|
|
|
77
133
|
export function shouldFailIfClaudeSandboxUnavailable(platform = process.platform) {
|
|
78
134
|
return platform !== 'win32';
|
|
79
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* In an unprivileged user namespace (for example, root in an LXC container),
|
|
138
|
+
* dropping all capabilities before apply-seccomp creates its child namespace
|
|
139
|
+
* prevents that helper from writing a mapping for UID 0 (CAP_SETFCAP).
|
|
140
|
+
* Use the SDK's nested-sandbox compatibility mode only for confirmed remapped
|
|
141
|
+
* root. Unknown host state must not opt into weaker isolation.
|
|
142
|
+
*/
|
|
143
|
+
export function shouldEnableClaudeWeakerNestedSandbox(platform = process.platform, effectiveUid = typeof process.geteuid === 'function' ? process.geteuid() : undefined) {
|
|
144
|
+
if (platform !== 'linux' || effectiveUid !== 0)
|
|
145
|
+
return false;
|
|
146
|
+
let uidMap;
|
|
147
|
+
try {
|
|
148
|
+
uidMap = fs.readFileSync('/proc/self/uid_map', 'utf8').trim();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (!uidMap)
|
|
154
|
+
return false;
|
|
155
|
+
let remappedRoot = false;
|
|
156
|
+
for (const line of uidMap.split(/\r?\n/)) {
|
|
157
|
+
const fields = line.trim().match(/^(\d+)\s+(\d+)\s+(\d+)$/);
|
|
158
|
+
if (!fields)
|
|
159
|
+
return false;
|
|
160
|
+
const [inside, outside, count] = fields.slice(1).map(Number);
|
|
161
|
+
if (![inside, outside, count].every(Number.isSafeInteger)
|
|
162
|
+
|| count === 0 || inside + count > 0xffffffff || outside + count > 0xffffffff)
|
|
163
|
+
return false;
|
|
164
|
+
if (inside === 0 && outside !== 0)
|
|
165
|
+
remappedRoot = true;
|
|
166
|
+
}
|
|
167
|
+
return remappedRoot;
|
|
168
|
+
}
|
|
80
169
|
function isExecutableFile(candidate) {
|
|
81
170
|
if (!candidate)
|
|
82
171
|
return false;
|
|
@@ -561,7 +650,9 @@ export function buildBubblewrapCommand(executable, executableArgs, options) {
|
|
|
561
650
|
return { command: bubblewrapPath, args };
|
|
562
651
|
}
|
|
563
652
|
export function _resetSandboxRuntimeCache() {
|
|
653
|
+
sessionSandboxInitializationCircuits.clear();
|
|
564
654
|
cachedBubblewrapPath = undefined;
|
|
565
655
|
sandboxInitializationCircuit = undefined;
|
|
566
656
|
sandboxCircuitNoticeAt = 0;
|
|
657
|
+
sandboxInitializationFailureCounters = emptySandboxInitializationFailureCounters();
|
|
567
658
|
}
|
|
@@ -20,6 +20,21 @@ export function classifyToolErrorCode(input) {
|
|
|
20
20
|
return 'USER_DENIED';
|
|
21
21
|
return 'EXECUTION_FAILED';
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Map the internal error taxonomy to the intentionally small AUN activity
|
|
25
|
+
* contract. The detailed code remains available to local audit consumers.
|
|
26
|
+
*/
|
|
27
|
+
export function classifyAunToolErrorCode(input) {
|
|
28
|
+
const code = classifyToolErrorCode(input);
|
|
29
|
+
if (code === 'POLICY_DENIED'
|
|
30
|
+
|| code === 'ROLE_DENIED'
|
|
31
|
+
|| code === 'USER_DENIED'
|
|
32
|
+
|| code === 'APPROVAL_TIMEOUT'
|
|
33
|
+
|| code === 'DELEGATION_FAILED') {
|
|
34
|
+
return 'PERMISSION_DENIED';
|
|
35
|
+
}
|
|
36
|
+
return 'EXECUTION_FAILED';
|
|
37
|
+
}
|
|
23
38
|
/**
|
|
24
39
|
* Compatibility classifier for historical, unstructured logs only.
|
|
25
40
|
* Production event producers must use classifyToolErrorCode() and explicit
|
|
@@ -5,7 +5,7 @@ import { randomUUID } from 'crypto';
|
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import { resolveRoot } from '../../paths.js';
|
|
7
7
|
import { containsHClassReference, containsLClassReference, checkProtectedPathAccess, getExistingHClassMaskTargets, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, resolveProtectedCandidateWithoutFinalSymlink, } from './protected-paths.js';
|
|
8
|
-
import { classifyEvolcoreShellCommand, hasCodexCmdCarrierEcIntent,
|
|
8
|
+
import { classifyEvolcoreShellCommand, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, parseCodexToolCommand, parseBoundedOutputShellCommand, parseLiteralShellArgv, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from './ec-command-parser.js';
|
|
9
9
|
import { analyzeReadonlyShellQuery, } from './readonly-shell-query.js';
|
|
10
10
|
/** Resolve the session-owned temporary root supplied by the execution host. */
|
|
11
11
|
export function resolveManagedTempDir(env = process.env) {
|
|
@@ -2209,8 +2209,9 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2209
2209
|
}
|
|
2210
2210
|
const parserOptions = {
|
|
2211
2211
|
verifyShellExecutable: process.platform === 'win32',
|
|
2212
|
-
|
|
2213
|
-
|
|
2212
|
+
managedTempDir: context.managedTempDir,
|
|
2213
|
+
...(context.sessionCredential
|
|
2214
|
+
? { expectedCredentialToken: context.sessionCredential }
|
|
2214
2215
|
: {}),
|
|
2215
2216
|
};
|
|
2216
2217
|
const parsedCodex = context.sessionId ? parseCodexToolCommand(input, parserOptions) : null;
|
|
@@ -2222,12 +2223,12 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2222
2223
|
policyCode: 'ec_shell_untrusted_executable',
|
|
2223
2224
|
};
|
|
2224
2225
|
}
|
|
2225
|
-
if (context.sessionId && parsedCodex?.ok && parsedCodex.
|
|
2226
|
-
&& !context.
|
|
2226
|
+
if (context.sessionId && parsedCodex?.ok && parsedCodex.credentialToken
|
|
2227
|
+
&& !context.sessionCredential) {
|
|
2227
2228
|
return {
|
|
2228
2229
|
behavior: 'deny',
|
|
2229
2230
|
input,
|
|
2230
|
-
message: '🔒 PowerShell
|
|
2231
|
+
message: '🔒 PowerShell 命令中的会话凭据必须与当前托管 Codex thread 绑定;无法确认时拒绝执行。',
|
|
2231
2232
|
policyCode: 'delegation_token_unbound',
|
|
2232
2233
|
};
|
|
2233
2234
|
}
|
|
@@ -2292,7 +2293,7 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2292
2293
|
const shellDialect = stringShellCarrier?.dialect
|
|
2293
2294
|
?? (hasCodexCmdCarrierEcIntent(input) ? 'cmd' : undefined);
|
|
2294
2295
|
const hasPowerShellCarrierAssignment = stringShellCarrier?.dialect === 'powershell'
|
|
2295
|
-
&&
|
|
2296
|
+
&& hasPowerShellCredentialAssignment(stringShellCarrier.command);
|
|
2296
2297
|
const ecCommand = hasPowerShellCarrierAssignment && parsedCodex
|
|
2297
2298
|
? parsedCodex.ok && parsedCodex.argv[0] === 'ec'
|
|
2298
2299
|
? { kind: 'literal' }
|
|
@@ -2320,7 +2321,7 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2320
2321
|
}
|
|
2321
2322
|
if (ecCommand.issue === 'unsafe-expansion') {
|
|
2322
2323
|
const message = shellDialect === 'powershell'
|
|
2323
|
-
? '🔒 EC
|
|
2324
|
+
? '🔒 EC 命令包含无法安全解析的 PowerShell 展开;临时路径仅支持已登记的当前会话 $env:TMPDIR 或 ${env:TMPDIR},也可使用完整路径。纯文字请使用单引号,或用 PowerShell 反引号转义 $ 和正文内的双引号。'
|
|
2324
2325
|
: shellDialect === 'cmd'
|
|
2325
2326
|
? '🔒 EC 命令包含未转义的 cmd.exe 展开;请不要使用 %VAR%、!VAR! 或 ^ 转义。cmd.exe 传给 ec 时必须保持原文字面量'
|
|
2326
2327
|
: '🔒 EC 双引号正文包含未转义的 Shell 展开;请将反引号写成 \\`,将 $()、${}、$变量中的 $ 写成 \\$。Shell 传给 ec 时会还原为原文字面量';
|
|
@@ -37,6 +37,10 @@ export function parsePeerKey(key) {
|
|
|
37
37
|
export class PeerIdentityCache {
|
|
38
38
|
/** 缓存最大时效:30 天 */
|
|
39
39
|
static CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
40
|
+
static invalidationRevisions = new Map();
|
|
41
|
+
static nextInvalidationRevision = 0;
|
|
42
|
+
/** Missing agent.md is recoverable; do not retain this negative result for 30 days. */
|
|
43
|
+
static UNKNOWN_CACHE_MAX_AGE_MS = 5 * 60 * 1000;
|
|
40
44
|
/**
|
|
41
45
|
* 获取 peer-identity.json 文件路径
|
|
42
46
|
*/
|
|
@@ -58,6 +62,22 @@ export class PeerIdentityCache {
|
|
|
58
62
|
return null;
|
|
59
63
|
}
|
|
60
64
|
}
|
|
65
|
+
/** Expire only the freshness check; retain the last snapshot and its trust level. */
|
|
66
|
+
static invalidate(channelType, peerId, agentDir) {
|
|
67
|
+
const cached = this.get(channelType, peerId, agentDir);
|
|
68
|
+
if (!cached)
|
|
69
|
+
return;
|
|
70
|
+
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
71
|
+
this.invalidationRevisions.set(filePath, ++this.nextInvalidationRevision);
|
|
72
|
+
if (cached.lastCheckedAt === 0)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
fs.writeFileSync(filePath, JSON.stringify({ ...cached, lastCheckedAt: 0 }, null, 2), 'utf-8');
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.warn(`[PeerIdentityCache] Failed to invalidate ${channelType}#${peerId}: ${error}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
61
81
|
/**
|
|
62
82
|
* 检查缓存是否需要刷新
|
|
63
83
|
* @param maxAgeMs 最大缓存时间(默认 30 天)
|
|
@@ -67,7 +87,10 @@ export class PeerIdentityCache {
|
|
|
67
87
|
const cached = this.get(channelType, peerId, agentDir);
|
|
68
88
|
if (!cached)
|
|
69
89
|
return true;
|
|
70
|
-
|
|
90
|
+
const effectiveMaxAge = cached.source === 'unknown'
|
|
91
|
+
? Math.min(maxAgeMs, this.UNKNOWN_CACHE_MAX_AGE_MS)
|
|
92
|
+
: maxAgeMs;
|
|
93
|
+
return cached.lastCheckedAt === 0 || Date.now() - cached.lastCheckedAt > effectiveMaxAge;
|
|
71
94
|
}
|
|
72
95
|
/**
|
|
73
96
|
* 从 agent.md 更新身份信息
|
|
@@ -95,7 +118,7 @@ export class PeerIdentityCache {
|
|
|
95
118
|
};
|
|
96
119
|
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
97
120
|
try {
|
|
98
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
121
|
+
fs.mkdirSync(path.join(path.dirname(filePath), 'shared'), { recursive: true });
|
|
99
122
|
fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
|
|
100
123
|
logger.debug(`[PeerIdentityCache] Updated: ${channelType}#${peerId} type=${type} isAgent=${isAgent} source=${source}`);
|
|
101
124
|
}
|
|
@@ -132,7 +155,7 @@ export class PeerIdentityCache {
|
|
|
132
155
|
};
|
|
133
156
|
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
134
157
|
try {
|
|
135
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
158
|
+
fs.mkdirSync(path.join(path.dirname(filePath), 'shared'), { recursive: true });
|
|
136
159
|
fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
|
|
137
160
|
logger.debug(`[PeerIdentityCache] Marked unknown: ${channelType}#${peerId}`);
|
|
138
161
|
}
|
|
@@ -149,8 +172,13 @@ export class PeerIdentityCache {
|
|
|
149
172
|
* @param agentDir agent 数据根目录
|
|
150
173
|
* @param store AIDStore 实例(由调用方提供,负责 checkAgentMd + downloadAgentMd)
|
|
151
174
|
* @param forceRefresh 强制刷新(忽略缓存时效)
|
|
175
|
+
* @param aunPath AUN SDK 数据根目录;与关系缓存所在的 agentDir 分开
|
|
152
176
|
*/
|
|
153
|
-
static async resolve(channelType, peerId, agentDir, store, forceRefresh = false) {
|
|
177
|
+
static async resolve(channelType, peerId, agentDir, store, forceRefresh = false, aunPath) {
|
|
178
|
+
// AIDStore is authoritative for the SDK cache root. Callers that predate
|
|
179
|
+
// the explicit aunPath parameter must still fall back to that same root,
|
|
180
|
+
// otherwise the error path could read a different agent.md tree.
|
|
181
|
+
const resolvedAunPath = aunPath ?? store.aunPath;
|
|
154
182
|
// A malformed transport envelope must never turn into a relation key such
|
|
155
183
|
// as `aun#` or trigger an agent.md lookup for an empty AID. Keep the
|
|
156
184
|
// fallback in-memory so callers can safely render an unknown peer without
|
|
@@ -177,15 +205,36 @@ export class PeerIdentityCache {
|
|
|
177
205
|
return cached;
|
|
178
206
|
}
|
|
179
207
|
}
|
|
208
|
+
const cachePath = this.getFilePath(channelType, peerId, agentDir);
|
|
209
|
+
const revision = this.invalidationRevisions.get(cachePath) ?? 0;
|
|
210
|
+
const stale = this.get(channelType, peerId, agentDir);
|
|
180
211
|
// 2. 通过 agentmdSync 拉取(内部走 store.checkAgentMd → store.downloadAgentMd)
|
|
181
212
|
try {
|
|
182
213
|
logger.debug(`[PeerIdentityCache] Syncing agent.md: ${channelType}#${peerId}`);
|
|
183
214
|
const { agentmdSync } = await import('../../aun/aid/agentmd.js');
|
|
184
|
-
const
|
|
215
|
+
const invalidated = stale?.lastCheckedAt === 0 || revision > 0;
|
|
216
|
+
const result = await agentmdSync(peerId, {
|
|
217
|
+
store,
|
|
218
|
+
aunPath: resolvedAunPath,
|
|
219
|
+
forceRefresh: invalidated,
|
|
220
|
+
ttlDays: stale?.source === 'unknown' ? 0 : undefined,
|
|
221
|
+
});
|
|
222
|
+
if ((this.invalidationRevisions.get(cachePath) ?? 0) !== revision) {
|
|
223
|
+
// A newer event arrived during this read. Its next lookup must refresh.
|
|
224
|
+
return this.get(channelType, peerId, agentDir) ?? {
|
|
225
|
+
aid: peerId, type: 'unknown', isAgent: true, agentMdHash: '', agentMdUpdatedAt: 0,
|
|
226
|
+
verifiedAt: 0, lastCheckedAt: 0, source: 'unknown',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// A failed refresh may return old local bytes. Keep the invalidated
|
|
230
|
+
// snapshot so the next lookup retries instead of caching it for 30 days.
|
|
231
|
+
if (invalidated && !result.verification && stale)
|
|
232
|
+
return stale;
|
|
185
233
|
const content = result.content;
|
|
186
234
|
if (!content) {
|
|
187
235
|
throw new Error('agent.md content unavailable');
|
|
188
236
|
}
|
|
237
|
+
this.invalidationRevisions.delete(cachePath);
|
|
189
238
|
// 验签通过 → 可信(source=agentmd);否则 type 仍解析但标记未验证
|
|
190
239
|
const verified = result.verification?.status === 'verified';
|
|
191
240
|
const source = verified ? 'agentmd' : 'agentmd-unverified';
|
|
@@ -201,10 +250,12 @@ export class PeerIdentityCache {
|
|
|
201
250
|
return this.updateFromAgentMd(channelType, peerId, agentDir, content, verified ? Date.now() : 0, source);
|
|
202
251
|
}
|
|
203
252
|
catch (err) {
|
|
253
|
+
if (stale?.lastCheckedAt === 0)
|
|
254
|
+
return stale;
|
|
204
255
|
// 4. agentmdSync 抛错(非网络失败——网络失败时它内部已 fallback 返回本地内容;
|
|
205
256
|
// 这里通常是无内容或解析异常)。兜底读本地文件,但无法重新验签,
|
|
206
257
|
// 故沿用缓存里已有的可信级别,绝不凭空升级为已验签。
|
|
207
|
-
const localPath = agentMdPath(peerId);
|
|
258
|
+
const localPath = agentMdPath(peerId, resolvedAunPath);
|
|
208
259
|
try {
|
|
209
260
|
if (fs.existsSync(localPath)) {
|
|
210
261
|
const localContent = fs.readFileSync(localPath, 'utf-8');
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { agentRelationDir, agentRelationSharedDir, agentRelationsDir, resolvePaths } from '../../paths.js';
|
|
5
|
+
import { parsePeerKey } from './peer-identity.js';
|
|
6
|
+
import { logger } from '../../utils/logger.js';
|
|
7
|
+
export const RELATION_SHARED_FILE_ALLOWLIST = Object.freeze(['rules.md']);
|
|
8
|
+
function isAllowedSharedFile(relativePath) {
|
|
9
|
+
return RELATION_SHARED_FILE_ALLOWLIST.includes(relativePath);
|
|
10
|
+
}
|
|
11
|
+
function copyFileAtomically(source, target) {
|
|
12
|
+
const targetDir = path.dirname(target);
|
|
13
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
14
|
+
const temporary = path.join(targetDir, `.${path.basename(target)}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`);
|
|
15
|
+
try {
|
|
16
|
+
fs.copyFileSync(source, temporary);
|
|
17
|
+
const sourceStat = fs.statSync(source);
|
|
18
|
+
fs.utimesSync(temporary, sourceStat.atime, sourceStat.mtime);
|
|
19
|
+
fs.renameSync(temporary, target);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
try {
|
|
23
|
+
fs.rmSync(temporary, { force: true });
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function ensureRelationSharedDirectory(selfAid, peerKey) {
|
|
29
|
+
parsePeerKey(peerKey);
|
|
30
|
+
const sharedDir = agentRelationSharedDir(selfAid, peerKey);
|
|
31
|
+
fs.mkdirSync(sharedDir, { recursive: true });
|
|
32
|
+
return sharedDir;
|
|
33
|
+
}
|
|
34
|
+
export function syncRelationSharedFile(selfAid, peerKey, relativePath) {
|
|
35
|
+
if (!isAllowedSharedFile(relativePath)) {
|
|
36
|
+
throw Object.assign(new Error(`Relation shared file is not allowlisted: ${relativePath}`), {
|
|
37
|
+
code: 'SHARED_FILE_NOT_ALLOWED',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
parsePeerKey(peerKey);
|
|
41
|
+
const relationDir = agentRelationDir(selfAid, peerKey);
|
|
42
|
+
const source = path.join(relationDir, relativePath);
|
|
43
|
+
const target = path.join(ensureRelationSharedDirectory(selfAid, peerKey), relativePath);
|
|
44
|
+
let sourceStat;
|
|
45
|
+
try {
|
|
46
|
+
sourceStat = fs.statSync(source);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { copied: false, target };
|
|
50
|
+
}
|
|
51
|
+
if (!sourceStat.isFile())
|
|
52
|
+
return { copied: false, target };
|
|
53
|
+
try {
|
|
54
|
+
const targetStat = fs.statSync(target);
|
|
55
|
+
if (targetStat.isFile()
|
|
56
|
+
&& targetStat.size === sourceStat.size
|
|
57
|
+
&& Math.abs(targetStat.mtimeMs - sourceStat.mtimeMs) < 1) {
|
|
58
|
+
return { copied: false, target };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
copyFileAtomically(source, target);
|
|
63
|
+
return { copied: true, target };
|
|
64
|
+
}
|
|
65
|
+
export function migrateRelationSharedDirectories() {
|
|
66
|
+
const agentsDir = resolvePaths().agentsDir;
|
|
67
|
+
let relations = 0;
|
|
68
|
+
let copied = 0;
|
|
69
|
+
if (!fs.existsSync(agentsDir))
|
|
70
|
+
return { relations, copied };
|
|
71
|
+
for (const agentEntry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
72
|
+
if (!agentEntry.isDirectory())
|
|
73
|
+
continue;
|
|
74
|
+
const selfAid = agentEntry.name;
|
|
75
|
+
const relationsDir = agentRelationsDir(selfAid);
|
|
76
|
+
let relationEntries;
|
|
77
|
+
try {
|
|
78
|
+
relationEntries = fs.readdirSync(relationsDir, { withFileTypes: true });
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
for (const relationEntry of relationEntries) {
|
|
84
|
+
if (!relationEntry.isDirectory() || relationEntry.name.startsWith('_'))
|
|
85
|
+
continue;
|
|
86
|
+
try {
|
|
87
|
+
parsePeerKey(relationEntry.name);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
relations += 1;
|
|
93
|
+
try {
|
|
94
|
+
ensureRelationSharedDirectory(selfAid, relationEntry.name);
|
|
95
|
+
for (const file of RELATION_SHARED_FILE_ALLOWLIST) {
|
|
96
|
+
if (syncRelationSharedFile(selfAid, relationEntry.name, file).copied)
|
|
97
|
+
copied += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
logger.warn(`[RelationShared] failed self=${selfAid} peer=${relationEntry.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { relations, copied };
|
|
106
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { getRoleDefinition } from '../../config/roles.js';
|
|
2
2
|
import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
3
|
+
import { ConfigTarget, read } from '../../config/config-manager.js';
|
|
4
|
+
import { resolvePrincipal } from '../../config/contact-book.js';
|
|
5
|
+
import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
|
|
6
|
+
import { hasTrustedPrincipal } from '../auth/authenticated-actor.js';
|
|
3
7
|
const PERMISSION_MODE_AUTHORITY = {
|
|
4
8
|
readonly: 0,
|
|
5
9
|
auto: 1,
|
|
@@ -139,3 +143,63 @@ export function validateRuntimeStringFieldOverride(input) {
|
|
|
139
143
|
}
|
|
140
144
|
return { ok: true };
|
|
141
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Resolve the Agent-level host execution policy for one authenticated principal.
|
|
148
|
+
* The Agent flag is subordinate to daemon fullaccess and independent of role permissionMode values.
|
|
149
|
+
*/
|
|
150
|
+
export function agentFullAccessStatus(agentAid, principalId) {
|
|
151
|
+
const config = read(ConfigTarget.Agent, { self: agentAid }, { cache: false });
|
|
152
|
+
const daemon = loadDaemonConfig();
|
|
153
|
+
const enabled = config?.fullaccess?.enabled === true;
|
|
154
|
+
const hostEnabled = isFullAccessEnabled(daemon);
|
|
155
|
+
const eligible = !!principalId
|
|
156
|
+
&& !!config?.owners?.includes(principalId)
|
|
157
|
+
&& !!daemon.owners?.includes(principalId);
|
|
158
|
+
return { enabled, hostEnabled, eligible, effective: enabled && hostEnabled && eligible };
|
|
159
|
+
}
|
|
160
|
+
/** Create a trusted per-call claim for a direct private message. */
|
|
161
|
+
export function createAgentPolicyAuthorization(input) {
|
|
162
|
+
if (input.chatType !== 'private'
|
|
163
|
+
|| (input.source !== undefined && input.source !== 'user')
|
|
164
|
+
|| ['agent', 'ai', 'bot', 'service', 'group'].includes((input.peerType ?? '').toLowerCase())
|
|
165
|
+
|| !hasTrustedPrincipal(input.actor))
|
|
166
|
+
return undefined;
|
|
167
|
+
if (!agentFullAccessStatus(input.agentAid, input.actor.principalId).effective)
|
|
168
|
+
return undefined;
|
|
169
|
+
return {
|
|
170
|
+
permissionMode: 'fullaccess',
|
|
171
|
+
processRole: 'fullaccess-run',
|
|
172
|
+
dataScope: 'daemon',
|
|
173
|
+
source: 'agent-policy',
|
|
174
|
+
agentAid: input.agentAid,
|
|
175
|
+
authorizedBy: input.actor.principalId,
|
|
176
|
+
policyActor: { ...input.actor },
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Revalidate a claim before Runner calls, retries, and daemon IPC. */
|
|
180
|
+
export function assertAgentPolicyAuthorization(authorization) {
|
|
181
|
+
const actor = authorization.policyActor;
|
|
182
|
+
if (!authorization.agentAid || !hasTrustedPrincipal(actor)) {
|
|
183
|
+
throw new Error('Agent fullaccess policy lacks authenticated origin');
|
|
184
|
+
}
|
|
185
|
+
const current = resolvePrincipal(authorization.agentAid, actor.operatorChannelType, actor.operatorId, actor.operatorChannelKey);
|
|
186
|
+
if (!hasTrustedPrincipal({ ...actor, ...current }) || current.principalId !== authorization.authorizedBy) {
|
|
187
|
+
throw new Error('Agent fullaccess policy origin identity changed');
|
|
188
|
+
}
|
|
189
|
+
if (!agentFullAccessStatus(authorization.agentAid, authorization.authorizedBy).effective) {
|
|
190
|
+
throw new Error('Agent fullaccess policy disabled or current owner is no longer eligible');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Only the authenticated EC Web Agent settings may mutate this policy. */
|
|
194
|
+
export function agentFullAccessWriteError(input) {
|
|
195
|
+
if (!input.ecwebManagement) {
|
|
196
|
+
return 'Agent fullaccess can only be configured from ECWeb';
|
|
197
|
+
}
|
|
198
|
+
if (!input.daemonOwner || input.delegated) {
|
|
199
|
+
return 'Only authenticated ECWeb management may configure Agent fullaccess';
|
|
200
|
+
}
|
|
201
|
+
if (input.enabled && !isFullAccessEnabled()) {
|
|
202
|
+
return 'Host fullaccess is disabled';
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
@@ -17,28 +17,21 @@ export function resolveManagedLockPath(namespace, identity, legacyPath) {
|
|
|
17
17
|
}
|
|
18
18
|
function isTrustedRuntimeLockDir(candidate) {
|
|
19
19
|
const resolved = path.resolve(candidate);
|
|
20
|
-
if (path.basename(resolved) !== 'evolcore-locks')
|
|
21
|
-
return false;
|
|
22
20
|
const tmpRoot = process.env.TMPDIR?.trim();
|
|
23
21
|
if (!tmpRoot || !path.isAbsolute(tmpRoot))
|
|
24
22
|
return false;
|
|
25
23
|
const resolvedTmpRoot = path.resolve(tmpRoot);
|
|
26
|
-
const relative = path.relative(resolvedTmpRoot, resolved);
|
|
27
|
-
const isWithinTmp = relative !== '..'
|
|
28
|
-
&& !relative.startsWith(`..${path.sep}`)
|
|
29
|
-
&& !path.isAbsolute(relative);
|
|
30
|
-
// The daemon supplies each task either a generic evolcore-runtime-* child
|
|
31
|
-
// or Codex's private digest directory. Both share a sibling evolcore-locks
|
|
32
|
-
// directory so mutations from separate sessions still serialize. Do not
|
|
33
|
-
// accept arbitrary TMPDIR siblings supplied by a child process.
|
|
34
24
|
const tmpBase = path.basename(resolvedTmpRoot);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
const isSessionLockDir = /^ec-session-[a-f0-9]{24}$/.test(tmpBase)
|
|
26
|
+
&& path.basename(resolved) === 'locks'
|
|
27
|
+
&& path.dirname(resolved) === resolvedTmpRoot;
|
|
28
|
+
// The app-server process has no EvolCore session yet and retains its own
|
|
29
|
+
// process-lifetime lock namespace. Both process and session roots use the
|
|
30
|
+
// same `$TMPDIR/locks` layout.
|
|
31
|
+
const isProcessLockDir = /^evolcore-managed-tmp-/.test(tmpBase)
|
|
32
|
+
&& path.basename(resolved) === 'locks'
|
|
33
|
+
&& path.dirname(resolved) === resolvedTmpRoot;
|
|
34
|
+
if (!isSessionLockDir && !isProcessLockDir)
|
|
42
35
|
return false;
|
|
43
36
|
try {
|
|
44
37
|
// Windows Stats.mode does not describe ACL privacy. The managed runtime
|
|
@@ -53,15 +46,6 @@ function isTrustedRuntimeLockDir(candidate) {
|
|
|
53
46
|
return false;
|
|
54
47
|
if (!isWindows && (tmpStat.mode & 0o077) !== 0)
|
|
55
48
|
return false;
|
|
56
|
-
if (isManagedSessionSibling) {
|
|
57
|
-
const parentStat = fs.lstatSync(tmpParent);
|
|
58
|
-
if (!parentStat.isDirectory() || parentStat.isSymbolicLink())
|
|
59
|
-
return false;
|
|
60
|
-
if (!isWindows && typeof process.getuid === 'function' && parentStat.uid !== process.getuid())
|
|
61
|
-
return false;
|
|
62
|
-
if (!isWindows && (parentStat.mode & 0o077) !== 0)
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
49
|
const stat = fs.lstatSync(resolved);
|
|
66
50
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
67
51
|
return false;
|