evolcore 0.0.1 → 0.0.2
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 +813 -0
- package/LICENSE +21 -0
- package/MIGRATION-0.5.0.md +378 -0
- package/README.md +318 -14
- package/ROLE_ACCESS_CONTROL.md +174 -0
- package/assets/.env.template +4 -0
- package/assets/brand/evolcore/README.md +19 -0
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +14 -0
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +10 -0
- package/bin/ec-safe-output.js +161 -0
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent.js +163 -0
- package/dist/agents/claude-runner.js +2385 -0
- package/dist/agents/codex-app-server-client.js +448 -0
- package/dist/agents/codex-runner.js +2639 -0
- package/dist/agents/gemini-runner.js +666 -0
- package/dist/agents/runner-types.js +75 -0
- package/dist/aun/aid/agentmd.js +216 -0
- package/dist/aun/aid/client.js +132 -0
- package/dist/aun/aid/control-aid.js +91 -0
- package/dist/aun/aid/identity.js +518 -0
- package/dist/aun/aid/index.js +4 -0
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/group-identity.js +10 -0
- package/dist/aun/msg/group-index.js +6 -0
- package/dist/aun/msg/group.js +1231 -0
- package/dist/aun/msg/history.js +123 -0
- package/dist/aun/msg/index.js +5 -0
- package/dist/aun/msg/p2p.js +393 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +137 -0
- package/dist/aun/outbox.js +160 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +25 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/service-proxy.js +225 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +68 -0
- package/dist/channels/aun.js +4147 -0
- package/dist/channels/daemon.js +422 -0
- package/dist/channels/dingtalk.js +649 -0
- package/dist/channels/feishu.js +1789 -0
- package/dist/channels/qqbot.js +409 -0
- package/dist/channels/wechat.js +817 -0
- package/dist/channels/wecom.js +565 -0
- package/dist/cli/agent-command.js +641 -0
- package/dist/cli/agent.js +1059 -0
- package/dist/cli/aun-commands.js +1948 -0
- package/dist/cli/bench.js +1228 -0
- package/dist/cli/cli-argv.js +66 -0
- package/dist/cli/code-stats.js +329 -0
- package/dist/cli/command-log.js +82 -0
- package/dist/cli/config-selector.js +69 -0
- package/dist/cli/config.js +261 -0
- package/dist/cli/ctl-command.js +62 -0
- package/dist/cli/daemon-commands.js +2887 -0
- package/dist/cli/fs-command.js +1447 -0
- package/dist/cli/handoff-command.js +302 -0
- package/dist/cli/help.js +31 -0
- package/dist/cli/index.js +358 -0
- package/dist/cli/init-channel.js +1377 -0
- package/dist/cli/init.js +553 -0
- package/dist/cli/link-rules.js +240 -0
- package/dist/cli/model.js +590 -0
- package/dist/cli/net-check.js +723 -0
- package/dist/cli/queue-command.js +126 -0
- package/dist/cli/response.js +345 -0
- package/dist/cli/restart-monitor.js +456 -0
- package/dist/cli/stats.js +607 -0
- package/dist/cli/task-context.js +80 -0
- package/dist/cli/trigger-command.js +505 -0
- package/dist/cli/version.js +88 -0
- package/dist/cli/watch-logs.js +33 -0
- package/dist/cli/watch-msg.js +673 -0
- package/dist/config/boot-log.js +266 -0
- package/dist/config/builtin-role-templates.js +30 -0
- package/dist/config/builtin-roles.js +85 -0
- package/dist/config/config-batch-get.js +11 -0
- package/dist/config/config-field-policy.js +261 -0
- package/dist/config/config-manager.js +916 -0
- package/dist/config/config-operation-service.js +384 -0
- package/dist/config/contact-book.js +371 -0
- package/dist/config/gateway-config.js +858 -0
- package/dist/config/lifecycle.js +17 -0
- package/dist/config/mention-mode.js +27 -0
- package/dist/config/merge.js +161 -0
- package/dist/config/owner-policy.js +4 -0
- package/dist/config/peer-role-resolver.js +139 -0
- package/dist/config/resolved-config-op.js +483 -0
- package/dist/config/role-config-v4-startup.js +32 -0
- package/dist/config/role-config-v5-startup.js +27 -0
- package/dist/config/role-schema.js +105 -0
- package/dist/config/role-service.js +159 -0
- package/dist/config/role-store.js +204 -0
- package/dist/config/roles.js +55 -0
- package/dist/config/schema-registry.js +154 -0
- package/dist/config/snapshot.js +598 -0
- package/dist/config-store.js +503 -0
- package/dist/core/auth/agent-delegation.js +111 -0
- package/dist/core/auth/auth-gateway.js +166 -0
- package/dist/core/auth/authenticated-actor.js +6 -0
- package/dist/core/auth/authorization-audit.js +110 -0
- package/dist/core/auth/operation-authorizer.js +718 -0
- package/dist/core/auth/operation-catalog.js +675 -0
- package/dist/core/baseagent-loader.js +54 -0
- package/dist/core/bootstrap-service.js +175 -0
- package/dist/core/capability/capability-manager.js +316 -0
- package/dist/core/capability/providers/claude-capability-provider.js +176 -0
- package/dist/core/capability/providers/codex-capability-provider.js +148 -0
- package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
- package/dist/core/capability/types.js +27 -0
- package/dist/core/causation/audit.js +103 -0
- package/dist/core/causation/aun-association.js +111 -0
- package/dist/core/causation/context.js +93 -0
- package/dist/core/causation/index.js +4 -0
- package/dist/core/causation/types.js +2 -0
- package/dist/core/channel-loader.js +277 -0
- package/dist/core/command/agent-control.js +616 -0
- package/dist/core/command/cli-intent-parser.js +225 -0
- package/dist/core/command/command-handler.js +1638 -0
- package/dist/core/command/menu-handler.js +3354 -0
- package/dist/core/command/menu-protocol.js +247 -0
- package/dist/core/command/role-menu.js +1524 -0
- package/dist/core/command/slash-gate.js +148 -0
- package/dist/core/command/slash-handler.js +3056 -0
- package/dist/core/daemon-file-cache.js +216 -0
- package/dist/core/event-bus.js +32 -0
- package/dist/core/event-catalog.js +740 -0
- package/dist/core/evolagent-registry.js +546 -0
- package/dist/core/evolagent.js +331 -0
- package/dist/core/handoff/dispatcher.js +229 -0
- package/dist/core/handoff/mutex.js +46 -0
- package/dist/core/handoff/runtime.js +324 -0
- package/dist/core/handoff/store.js +537 -0
- package/dist/core/handoff/types.js +12 -0
- package/dist/core/inference/text-inference.js +173 -0
- package/dist/core/interaction-registration.js +10 -0
- package/dist/core/interaction-router.js +278 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/im-renderer.js +657 -0
- package/dist/core/message/items-formatter.js +76 -0
- package/dist/core/message/logical-queue-bridge.js +123 -0
- package/dist/core/message/message-bridge.js +803 -0
- package/dist/core/message/message-cache.js +56 -0
- package/dist/core/message/message-log.js +339 -0
- package/dist/core/message/message-processor.js +4 -0
- package/dist/core/message/message-queue.js +1436 -0
- package/dist/core/message/message-utils.js +70 -0
- package/dist/core/message/peer-mode.js +105 -0
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +33 -0
- package/dist/core/message/response-engine.js +3776 -0
- package/dist/core/message/response-snapshot.js +83 -0
- package/dist/core/message/stream-debouncer.js +130 -0
- package/dist/core/message/stream-idle-monitor.js +124 -0
- package/dist/core/model/config-scope.js +162 -0
- package/dist/core/model/field-scope.js +78 -0
- package/dist/core/model/model-catalog.js +227 -0
- package/dist/core/model/model-diagnostics.js +182 -0
- package/dist/core/model/model-permission.js +90 -0
- package/dist/core/permission/approval-gateway.js +1017 -0
- package/dist/core/permission/ec-command-parser.js +347 -0
- package/dist/core/permission/execution-sandbox.js +16 -0
- package/dist/core/permission/index.js +6 -0
- package/dist/core/permission/mode.js +24 -0
- package/dist/core/permission/sandbox-runtime.js +265 -0
- package/dist/core/permission/tool-policy.js +987 -0
- package/dist/core/permission/unix-socket-policy.js +99 -0
- package/dist/core/protected-paths.js +330 -0
- package/dist/core/relation/peer-identity.js +222 -0
- package/dist/core/relation/peer-key.js +1 -0
- package/dist/core/role/runtime-policy.js +141 -0
- package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
- package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
- package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
- package/dist/core/session/session-file-adapter.js +7 -0
- package/dist/core/session/session-file-health.js +45 -0
- package/dist/core/session/session-fs-store.js +232 -0
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +1587 -0
- package/dist/core/session/session-mapper.js +100 -0
- package/dist/core/session/session-renew.js +314 -0
- package/dist/core/session/session-title.js +128 -0
- package/dist/core/session/session-turn-coordinator.js +205 -0
- package/dist/core/session/session-turns.js +67 -0
- package/dist/core/system-channels.js +29 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/group-rules-sync.js +345 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/kit-renderer.js +359 -0
- package/dist/eck/manifest-engine.js +446 -0
- package/dist/eck/message-renderer.js +199 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +2870 -4
- package/dist/ipc.js +748 -0
- package/dist/paths.js +262 -0
- package/dist/product.js +18 -0
- package/dist/response-system/context-builder.js +71 -0
- package/dist/response-system/coordinator.js +117 -0
- package/dist/response-system/decision-executor.js +86 -0
- package/dist/response-system/engines/v1/index.js +21 -0
- package/dist/response-system/engines/v1/interactive-flow.js +27 -0
- package/dist/response-system/engines/v1/proactive-flow.js +137 -0
- package/dist/response-system/engines/v1/types.js +1 -0
- package/dist/response-system/extensions.js +41 -0
- package/dist/response-system/index.js +6 -0
- package/dist/response-system/modes/index.js +7 -0
- package/dist/response-system/modes/single-session/index.js +72 -0
- package/dist/response-system/queues/fifo-queue.js +44 -0
- package/dist/response-system/queues/index.js +6 -0
- package/dist/response-system/queues/lifo-queue.js +42 -0
- package/dist/response-system/queues/priority-queue.js +63 -0
- package/dist/response-system/registry.js +97 -0
- package/dist/response-system/resolver.js +37 -0
- package/dist/response-system/selector.js +23 -0
- package/dist/response-system/types.js +7 -0
- package/dist/stats/billing.js +151 -0
- package/dist/stats/budget.js +93 -0
- package/dist/stats/db.js +403 -0
- package/dist/stats/eck-vars.js +89 -0
- package/dist/stats/index.js +11 -0
- package/dist/stats/normalizer.js +80 -0
- package/dist/stats/price-resolver.js +138 -0
- package/dist/stats/query.js +763 -0
- package/dist/stats/role-budget.js +168 -0
- package/dist/stats/writer.js +151 -0
- package/dist/trigger/anomaly-store.js +258 -0
- package/dist/trigger/audit.js +152 -0
- package/dist/trigger/event-source.js +119 -0
- package/dist/trigger/feedback.js +685 -0
- package/dist/trigger/history.js +290 -0
- package/dist/trigger/manager.js +291 -0
- package/dist/trigger/parser.js +520 -0
- package/dist/trigger/patch.js +148 -0
- package/dist/trigger/scheduler.js +1600 -0
- package/dist/trigger/script-executor.js +155 -0
- package/dist/trigger/state.js +145 -0
- package/dist/trigger/types.js +1 -0
- package/dist/trigger/validation.js +621 -0
- package/dist/types.js +12 -0
- package/dist/utils/aid-bind.js +299 -0
- package/dist/utils/atomic-write.js +95 -0
- package/dist/utils/avatar-upload.js +123 -0
- package/dist/utils/cross-platform.js +297 -0
- package/dist/utils/ecweb-utils.js +73 -0
- package/dist/utils/error-dict.json +153 -0
- package/dist/utils/error-utils.js +349 -0
- package/dist/utils/instance-registry.js +437 -0
- package/dist/utils/locale.js +21 -0
- package/dist/utils/log-writer.js +224 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/markdown-to-plain-text.js +20 -0
- package/dist/utils/media-cache.js +271 -0
- package/dist/utils/model-prices.jsonl +17 -0
- package/dist/utils/npm-ops.js +210 -0
- package/dist/utils/process-introspect.js +133 -0
- package/dist/utils/process-tree-stats.js +271 -0
- package/dist/utils/project-path.js +74 -0
- package/dist/utils/stats.js +410 -0
- package/dist/utils/tool-summary.js +284 -0
- package/dist/utils/welcome.js +268 -0
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +65 -0
- package/kits/docs/aun/CHEATSHEET.md +19 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/aun.md +65 -0
- package/kits/docs/channels/feishu.md +56 -0
- package/kits/docs/context-assembly.md +366 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolcore/INDEX.md +66 -0
- package/kits/docs/evolcore/agent.md +69 -0
- package/kits/docs/evolcore/aid.md +49 -0
- package/kits/docs/evolcore/config.md +149 -0
- package/kits/docs/evolcore/ctl.md +46 -0
- package/kits/docs/evolcore/event.md +216 -0
- package/kits/docs/evolcore/fs-architecture.md +1215 -0
- package/kits/docs/evolcore/fs.md +101 -0
- package/kits/docs/evolcore/group-fs.md +17 -0
- package/kits/docs/evolcore/group-rules.md +226 -0
- package/kits/docs/evolcore/group.md +141 -0
- package/kits/docs/evolcore/model.md +47 -0
- package/kits/docs/evolcore/msg.md +130 -0
- package/kits/docs/evolcore/response.md +80 -0
- package/kits/docs/evolcore/rpc.md +35 -0
- package/kits/docs/evolcore/self-summary.md +29 -0
- package/kits/docs/evolcore/stats.md +70 -0
- package/kits/docs/evolcore/storage.md +49 -0
- package/kits/docs/evolcore/trigger.md +524 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +23 -0
- package/kits/docs/identity/identity-tools.md +26 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/docs/prompt-loading-architecture.md +266 -0
- package/kits/docs/venues/aun-group.md +45 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +25 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.auxiliary.json +43 -0
- package/kits/eck_manifest.json +191 -0
- package/kits/eck_message_manifest.json +63 -0
- package/kits/migrations/README-role-config-v4.md +32 -0
- package/kits/migrations/migrate-role-config-v4.mjs +623 -0
- package/kits/migrations/migrate-role-config-v5.mjs +346 -0
- package/kits/migrations/rename-config-file.mjs +99 -0
- package/kits/rules/01-overview.md +142 -0
- package/kits/rules/02-navigation.md +76 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +59 -0
- package/kits/rules/05-venue.md +44 -0
- package/kits/rules/06-channel.md +59 -0
- package/kits/schemas/_meta.json +29 -0
- package/kits/schemas/agent-config.schema.1.json +177 -0
- package/kits/schemas/agent-config.schema.2.json +239 -0
- package/kits/schemas/agent-config.schema.3.json +119 -0
- package/kits/schemas/agent-config.schema.4.json +208 -0
- package/kits/schemas/agent-config.schema.5.json +326 -0
- package/kits/schemas/contact-book.schema.1.json +36 -0
- package/kits/schemas/daemon.schema.1.json +90 -0
- package/kits/schemas/defaults.schema.1.json +81 -0
- package/kits/schemas/menu-exec-schema-commands.md +208 -0
- package/kits/schemas/migrations/README.md +28 -0
- package/kits/schemas/relation-config.schema.1.json +158 -0
- package/kits/schemas/relation-config.schema.2.json +73 -0
- package/kits/schemas/relation-config.schema.3.json +50 -0
- package/kits/schemas/relation-config.schema.4.json +47 -0
- package/kits/schemas/role-config.schema.1.json +200 -0
- package/kits/schemas/role-registry.schema.1.json +35 -0
- package/kits/schemas/single-session.schema.1.json +31 -0
- package/kits/templates/bootstrap-welcome.md +15 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
- package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/roles/admin.json +8 -0
- package/kits/templates/roles/member.json +40 -0
- package/kits/templates/roles/owner.json +8 -0
- package/kits/templates/roles/visitor.json +39 -0
- package/kits/templates/system-fragments/baseagent.md +14 -0
- package/kits/templates/system-fragments/bootstrap.md +16 -0
- package/kits/templates/system-fragments/channel.md +48 -0
- package/kits/templates/system-fragments/commands.md +26 -0
- package/kits/templates/system-fragments/identity.md +11 -0
- package/kits/templates/system-fragments/relation.md +19 -0
- package/kits/templates/system-fragments/session.md +53 -0
- package/kits/templates/system-fragments/venue.md +31 -0
- package/package.json +50 -15
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolvePaths } from '../../paths.js';
|
|
4
|
+
const MAX_SOCKET_SCAN_DEPTH = 16;
|
|
5
|
+
const MAX_SOCKET_SCAN_ENTRIES = 4096;
|
|
6
|
+
const MAX_ALLOWED_SOCKETS = 512;
|
|
7
|
+
function normalize(value) {
|
|
8
|
+
const resolved = path.resolve(value);
|
|
9
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
10
|
+
}
|
|
11
|
+
function sameOrDescendant(candidate, parent) {
|
|
12
|
+
const relative = path.relative(normalize(parent), normalize(candidate));
|
|
13
|
+
return relative === '' || (!relative.startsWith('..' + path.sep) && relative !== '..' && !path.isAbsolute(relative));
|
|
14
|
+
}
|
|
15
|
+
function realpathIfExists(value) {
|
|
16
|
+
try {
|
|
17
|
+
return fs.realpathSync(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return path.resolve(value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isSystemUnixSocketPath(socketPath) {
|
|
24
|
+
const normalized = normalize(socketPath);
|
|
25
|
+
const unixPath = normalized.split(path.sep).join('/');
|
|
26
|
+
if (unixPath === '/run' || unixPath.startsWith('/run/'))
|
|
27
|
+
return true;
|
|
28
|
+
if (unixPath === '/var/run' || unixPath.startsWith('/var/run/'))
|
|
29
|
+
return true;
|
|
30
|
+
if (/\/(?:docker|podman|containerd)(?:\.sock|\/)/i.test(unixPath))
|
|
31
|
+
return true;
|
|
32
|
+
if (/\/(?:dbus|systemd)(?:\.sock|\/)/i.test(unixPath))
|
|
33
|
+
return true;
|
|
34
|
+
if (/\/tmp\/ssh-[^/]+\/agent\.\d+$/i.test(unixPath))
|
|
35
|
+
return true;
|
|
36
|
+
if (/\/(?:gnupg|gpg-agent)(?:\/|\.sock|$)/i.test(unixPath))
|
|
37
|
+
return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function collectProjectUnixSockets(projectPath) {
|
|
41
|
+
const projectRoot = realpathIfExists(projectPath);
|
|
42
|
+
const sockets = [];
|
|
43
|
+
const seenDirs = new Set();
|
|
44
|
+
const stack = [{ dir: projectRoot, depth: 0 }];
|
|
45
|
+
let scanned = 0;
|
|
46
|
+
while (stack.length > 0 && scanned < MAX_SOCKET_SCAN_ENTRIES && sockets.length < MAX_ALLOWED_SOCKETS) {
|
|
47
|
+
const current = stack.pop();
|
|
48
|
+
let currentReal;
|
|
49
|
+
try {
|
|
50
|
+
currentReal = fs.realpathSync(current.dir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (seenDirs.has(currentReal) || !sameOrDescendant(currentReal, projectRoot))
|
|
56
|
+
continue;
|
|
57
|
+
seenDirs.add(currentReal);
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = fs.readdirSync(currentReal, { withFileTypes: true });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (++scanned > MAX_SOCKET_SCAN_ENTRIES || sockets.length >= MAX_ALLOWED_SOCKETS)
|
|
67
|
+
break;
|
|
68
|
+
const entryPath = path.join(currentReal, entry.name);
|
|
69
|
+
if (entry.isSymbolicLink())
|
|
70
|
+
continue;
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
if (current.depth < MAX_SOCKET_SCAN_DEPTH)
|
|
73
|
+
stack.push({ dir: entryPath, depth: current.depth + 1 });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!entry.name.endsWith('.sock'))
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
const stat = fs.lstatSync(entryPath);
|
|
80
|
+
if (stat.isSocket() && !isSystemUnixSocketPath(entryPath))
|
|
81
|
+
sockets.push(entryPath);
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return sockets;
|
|
87
|
+
}
|
|
88
|
+
export function buildClaudeUnixSocketAllowlist(projectPath, context = {}) {
|
|
89
|
+
const sockets = new Set();
|
|
90
|
+
for (const socketPath of collectProjectUnixSockets(projectPath)) {
|
|
91
|
+
sockets.add(socketPath);
|
|
92
|
+
}
|
|
93
|
+
if (context.role === 'owner' || context.role === 'admin') {
|
|
94
|
+
const instanceSocket = resolvePaths().instanceSocket;
|
|
95
|
+
if (!isSystemUnixSocketPath(instanceSocket))
|
|
96
|
+
sockets.add(instanceSocket);
|
|
97
|
+
}
|
|
98
|
+
return [...sockets];
|
|
99
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveRoot } from '../paths.js';
|
|
4
|
+
const H_CLASS_RELATIVE_PATTERNS = [
|
|
5
|
+
/^daemon\.json$/,
|
|
6
|
+
/^agents\/defaults\.json$/,
|
|
7
|
+
/^agents\/[^/]+\/config\.json$/,
|
|
8
|
+
/^agents\/[^/]+\/contact\.json$/,
|
|
9
|
+
/^agents\/[^/]+\/roles(?:\/|$)/,
|
|
10
|
+
/^agents\/[^/]+\/relations\/[^/]+\/config\.json$/,
|
|
11
|
+
/^backups\/config(?:\/|$)/,
|
|
12
|
+
/^\.snapshots(?:\/|$)/,
|
|
13
|
+
/^CA(?:\/|$)/,
|
|
14
|
+
/^(?:AIDs|aids)\/[^/]+\/(?:cert|keys)(?:\/|$)/,
|
|
15
|
+
/^\.device_id$/,
|
|
16
|
+
/^\.env$/,
|
|
17
|
+
/^\.seed(?:\.|$)/,
|
|
18
|
+
/^\.migrated-/,
|
|
19
|
+
/(?:^|\/)[^/]+\.json_$/,
|
|
20
|
+
/(?:^|\/)[^/]+\.json\.migrated$/,
|
|
21
|
+
/^agents\/defaults_\d+\.json$/,
|
|
22
|
+
/^\.lock$/,
|
|
23
|
+
/^daemon\.pid$/,
|
|
24
|
+
/^data\/causation-aun\.json(?:_|__)?$/,
|
|
25
|
+
/^data\/message-queue\.json(?:_|__|\.tmp-.*)?$/,
|
|
26
|
+
/^data\/instance\/control\.token$/,
|
|
27
|
+
/^data\/outbox(?:\/|$)/,
|
|
28
|
+
/^data\/handoff(?:\/|$)/,
|
|
29
|
+
];
|
|
30
|
+
const H_CLASS_REFERENCE_PATTERNS = [
|
|
31
|
+
/(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
|
|
32
|
+
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
33
|
+
/(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
34
|
+
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
35
|
+
/(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|instance[\\/]control\.token|outbox|handoff))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
36
|
+
/(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
|
|
37
|
+
];
|
|
38
|
+
const L_CLASS_RELATIVE_PATTERNS = [
|
|
39
|
+
/^data\/triggers(?:\/|$)/,
|
|
40
|
+
];
|
|
41
|
+
const L_CLASS_REFERENCE_PATTERNS = [
|
|
42
|
+
/(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
43
|
+
];
|
|
44
|
+
function normalizeForComparison(value) {
|
|
45
|
+
const resolved = path.resolve(value);
|
|
46
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
47
|
+
}
|
|
48
|
+
function resolveThroughExistingAncestor(value) {
|
|
49
|
+
const absolute = path.resolve(value);
|
|
50
|
+
const missing = [];
|
|
51
|
+
let cursor = absolute;
|
|
52
|
+
while (!fs.existsSync(cursor)) {
|
|
53
|
+
const parent = path.dirname(cursor);
|
|
54
|
+
if (parent === cursor)
|
|
55
|
+
return absolute;
|
|
56
|
+
missing.unshift(path.basename(cursor));
|
|
57
|
+
cursor = parent;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return path.join(fs.realpathSync(cursor), ...missing);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return absolute;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function isSameOrDescendant(candidate, parent) {
|
|
67
|
+
const normalizedCandidate = normalizeForComparison(candidate);
|
|
68
|
+
const normalizedParent = normalizeForComparison(parent);
|
|
69
|
+
const relative = path.relative(normalizedParent, normalizedCandidate);
|
|
70
|
+
return relative === '' || (!relative.startsWith('..' + path.sep) && relative !== '..' && !path.isAbsolute(relative));
|
|
71
|
+
}
|
|
72
|
+
export function resolveProtectedCandidate(value, cwd = process.cwd()) {
|
|
73
|
+
const absolute = path.isAbsolute(value) ? value : path.resolve(cwd, value);
|
|
74
|
+
return resolveThroughExistingAncestor(absolute);
|
|
75
|
+
}
|
|
76
|
+
export function isHClassPath(value, options = {}) {
|
|
77
|
+
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
78
|
+
const lexicalCandidate = path.isAbsolute(value)
|
|
79
|
+
? path.resolve(value)
|
|
80
|
+
: path.resolve(options.cwd ?? process.cwd(), value);
|
|
81
|
+
if (isSameOrDescendant(lexicalCandidate, root)) {
|
|
82
|
+
const lexicalRelative = path.relative(root, lexicalCandidate).split(path.sep).join('/');
|
|
83
|
+
if (H_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(lexicalRelative)))
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const candidate = resolveProtectedCandidate(value, options.cwd);
|
|
87
|
+
if (!isSameOrDescendant(candidate, root))
|
|
88
|
+
return false;
|
|
89
|
+
const relative = path.relative(root, candidate).split(path.sep).join('/');
|
|
90
|
+
return H_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
|
|
91
|
+
}
|
|
92
|
+
export function containsHClassReference(value) {
|
|
93
|
+
const normalized = value.replace(/\\/g, '/');
|
|
94
|
+
const unquoted = normalized.replace(/^["']|["']$/g, '').replace(/[;,) ]+$/, '');
|
|
95
|
+
if (H_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(unquoted)))
|
|
96
|
+
return true;
|
|
97
|
+
return H_CLASS_REFERENCE_PATTERNS.some(pattern => pattern.test(normalized));
|
|
98
|
+
}
|
|
99
|
+
/** L-class paths are readable by agents but may only be mutated by trusted daemon paths. */
|
|
100
|
+
export function isLClassPath(value, options = {}) {
|
|
101
|
+
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
102
|
+
const lexicalCandidate = path.isAbsolute(value)
|
|
103
|
+
? path.resolve(value)
|
|
104
|
+
: path.resolve(options.cwd ?? process.cwd(), value);
|
|
105
|
+
if (isSameOrDescendant(lexicalCandidate, root)) {
|
|
106
|
+
const lexicalRelative = path.relative(root, lexicalCandidate).split(path.sep).join('/');
|
|
107
|
+
if (L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(lexicalRelative)))
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const candidate = resolveProtectedCandidate(value, options.cwd);
|
|
111
|
+
if (!isSameOrDescendant(candidate, root))
|
|
112
|
+
return false;
|
|
113
|
+
const relative = path.relative(root, candidate).split(path.sep).join('/');
|
|
114
|
+
return L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
|
|
115
|
+
}
|
|
116
|
+
export function containsLClassReference(value) {
|
|
117
|
+
const normalized = value.replace(/\\/g, '/');
|
|
118
|
+
const unquoted = normalized.replace(/^["']|["']$/g, '').replace(/[;,) ]+$/, '');
|
|
119
|
+
if (L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(unquoted)))
|
|
120
|
+
return true;
|
|
121
|
+
return L_CLASS_REFERENCE_PATTERNS.some(pattern => pattern.test(normalized));
|
|
122
|
+
}
|
|
123
|
+
export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
124
|
+
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
125
|
+
const entries = [
|
|
126
|
+
'daemon.json',
|
|
127
|
+
path.join('agents', 'defaults.json'),
|
|
128
|
+
path.join('agents', '*', 'config.json'),
|
|
129
|
+
path.join('agents', '*', 'contact.json'),
|
|
130
|
+
path.join('agents', '*', 'roles'),
|
|
131
|
+
path.join('agents', '*', 'roles', '**'),
|
|
132
|
+
path.join('agents', '*', 'relations', '*', 'config.json'),
|
|
133
|
+
path.join('backups', 'config'),
|
|
134
|
+
path.join('backups', 'config', '**'),
|
|
135
|
+
'.snapshots',
|
|
136
|
+
path.join('.snapshots', '**'),
|
|
137
|
+
'CA',
|
|
138
|
+
path.join('CA', '**'),
|
|
139
|
+
path.join('AIDs', '*', 'cert'),
|
|
140
|
+
path.join('AIDs', '*', 'cert', '**'),
|
|
141
|
+
path.join('AIDs', '*', 'keys'),
|
|
142
|
+
path.join('AIDs', '*', 'keys', '**'),
|
|
143
|
+
path.join('aids', '*', 'cert'),
|
|
144
|
+
path.join('aids', '*', 'cert', '**'),
|
|
145
|
+
path.join('aids', '*', 'keys'),
|
|
146
|
+
path.join('aids', '*', 'keys', '**'),
|
|
147
|
+
'.device_id',
|
|
148
|
+
'.env',
|
|
149
|
+
'.seed',
|
|
150
|
+
'.seed.*',
|
|
151
|
+
'.migrated-*',
|
|
152
|
+
path.join('**', '*.json_'),
|
|
153
|
+
path.join('**', '*.json.migrated'),
|
|
154
|
+
path.join('agents', 'defaults_*.json'),
|
|
155
|
+
'.lock',
|
|
156
|
+
'daemon.pid',
|
|
157
|
+
path.join('data', 'causation-aun.json*'),
|
|
158
|
+
path.join('data', 'message-queue.json*'),
|
|
159
|
+
path.join('data', 'instance', 'control.token'),
|
|
160
|
+
path.join('data', 'outbox'),
|
|
161
|
+
path.join('data', 'outbox', '**'),
|
|
162
|
+
path.join('data', 'handoff'),
|
|
163
|
+
path.join('data', 'handoff', '**'),
|
|
164
|
+
];
|
|
165
|
+
return entries.map(entry => path.join(absoluteRoot, entry));
|
|
166
|
+
}
|
|
167
|
+
export function getLClassWritePatterns(root = resolveRoot()) {
|
|
168
|
+
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
169
|
+
return [
|
|
170
|
+
path.join(absoluteRoot, 'data', 'triggers'),
|
|
171
|
+
path.join(absoluteRoot, 'data', 'triggers', '**'),
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
|
|
175
|
+
const CLAUDE_GLOB_MAGIC = /[*?[\]]/;
|
|
176
|
+
function addExistingTarget(targets, value) {
|
|
177
|
+
try {
|
|
178
|
+
const stat = fs.lstatSync(value);
|
|
179
|
+
targets.set(path.resolve(value), {
|
|
180
|
+
path: path.resolve(value),
|
|
181
|
+
kind: stat.isDirectory() ? 'directory' : 'file',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch { }
|
|
185
|
+
}
|
|
186
|
+
export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
187
|
+
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
188
|
+
const targets = new Map();
|
|
189
|
+
for (const relative of [
|
|
190
|
+
'daemon.json', 'backups/config', '.snapshots', 'CA',
|
|
191
|
+
'.device_id', '.env', '.seed', '.lock', 'daemon.pid',
|
|
192
|
+
'data/causation-aun.json', 'data/message-queue.json', 'data/message-queue.json_', 'data/message-queue.json__',
|
|
193
|
+
'data/instance/control.token',
|
|
194
|
+
'data/outbox', 'data/handoff',
|
|
195
|
+
]) {
|
|
196
|
+
addExistingTarget(targets, path.join(absoluteRoot, relative));
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
for (const entry of fs.readdirSync(absoluteRoot)) {
|
|
200
|
+
if (entry.startsWith('.seed.') || entry.startsWith('.migrated-') || entry.endsWith('.json.migrated')) {
|
|
201
|
+
addExistingTarget(targets, path.join(absoluteRoot, entry));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch { }
|
|
206
|
+
const agentsDir = path.join(absoluteRoot, 'agents');
|
|
207
|
+
addExistingTarget(targets, path.join(agentsDir, 'defaults.json'));
|
|
208
|
+
try {
|
|
209
|
+
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
210
|
+
if (entry.name.match(/^defaults_\d+\.json$/) || entry.name.endsWith('.json_') || entry.name.endsWith('.json.migrated')) {
|
|
211
|
+
addExistingTarget(targets, path.join(agentsDir, entry.name));
|
|
212
|
+
}
|
|
213
|
+
if (!entry.isDirectory())
|
|
214
|
+
continue;
|
|
215
|
+
const agentRoot = path.join(agentsDir, entry.name);
|
|
216
|
+
for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_']) {
|
|
217
|
+
addExistingTarget(targets, path.join(agentRoot, fileName));
|
|
218
|
+
}
|
|
219
|
+
addExistingTarget(targets, path.join(agentRoot, 'roles'));
|
|
220
|
+
const relationsDir = path.join(agentRoot, 'relations');
|
|
221
|
+
try {
|
|
222
|
+
for (const relation of fs.readdirSync(relationsDir, { withFileTypes: true })) {
|
|
223
|
+
if (!relation.isDirectory())
|
|
224
|
+
continue;
|
|
225
|
+
for (const fileName of ['config.json', 'config.json_']) {
|
|
226
|
+
addExistingTarget(targets, path.join(relationsDir, relation.name, fileName));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch { }
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
for (const aidsName of ['AIDs', 'aids']) {
|
|
235
|
+
const aidsRoot = path.join(absoluteRoot, aidsName);
|
|
236
|
+
try {
|
|
237
|
+
for (const aid of fs.readdirSync(aidsRoot, { withFileTypes: true })) {
|
|
238
|
+
if (!aid.isDirectory())
|
|
239
|
+
continue;
|
|
240
|
+
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'cert'));
|
|
241
|
+
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'keys'));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch { }
|
|
245
|
+
}
|
|
246
|
+
return [...targets.values()];
|
|
247
|
+
}
|
|
248
|
+
export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
|
|
249
|
+
const targets = new Map();
|
|
250
|
+
addExistingTarget(targets, path.join(resolveThroughExistingAncestor(root), 'data', 'triggers'));
|
|
251
|
+
return [...targets.values()];
|
|
252
|
+
}
|
|
253
|
+
export function buildClaudeProtectedFilesystem(root = resolveRoot()) {
|
|
254
|
+
const hClassPaths = getExistingHClassMaskTargets(root).map(target => target.path);
|
|
255
|
+
const lClassPaths = getExistingLClassReadOnlyTargets(root).map(target => target.path);
|
|
256
|
+
const allPaths = [...new Set([...hClassPaths, ...lClassPaths])];
|
|
257
|
+
if (allPaths.length > CLAUDE_PROTECTED_TARGET_LIMIT) {
|
|
258
|
+
throw new Error(`[ClaudeSandbox] protected target limit exceeded: ${allPaths.length} > ${CLAUDE_PROTECTED_TARGET_LIMIT}`);
|
|
259
|
+
}
|
|
260
|
+
for (const target of allPaths) {
|
|
261
|
+
if (!path.isAbsolute(target)) {
|
|
262
|
+
throw new Error(`[ClaudeSandbox] protected target must be absolute: ${target}`);
|
|
263
|
+
}
|
|
264
|
+
if (CLAUDE_GLOB_MAGIC.test(target)) {
|
|
265
|
+
throw new Error(`[ClaudeSandbox] glob paths are unsupported on Linux sandbox projections: ${target}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
denyRead: [...new Set(hClassPaths)],
|
|
270
|
+
denyWrite: allPaths,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
export function buildCodexHClassFilesystemRules(root = resolveRoot()) {
|
|
274
|
+
return {
|
|
275
|
+
// Codex pre-expands unbounded deny globs on Linux/WSL/Windows. Without a
|
|
276
|
+
// bound, patterns such as **/*.json_ are accepted but may not be enforced.
|
|
277
|
+
glob_scan_max_depth: 64,
|
|
278
|
+
...Object.fromEntries(getHClassSandboxPatterns(root).map(pattern => [pattern, 'deny'])),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
export function buildCodexProtectedFilesystemRules(root = resolveRoot()) {
|
|
282
|
+
return {
|
|
283
|
+
...buildCodexHClassFilesystemRules(root),
|
|
284
|
+
...Object.fromEntries(getLClassWritePatterns(root).map(pattern => [pattern, 'read'])),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export function hClassGrantIncludesProtectedPath(value, options = {}) {
|
|
288
|
+
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
289
|
+
if (containsHClassReference(value))
|
|
290
|
+
return true;
|
|
291
|
+
const normalized = value.replace(/\\/g, '/');
|
|
292
|
+
const globIndex = normalized.search(/[*?[{]/);
|
|
293
|
+
const literalGrantRoot = globIndex < 0
|
|
294
|
+
? value
|
|
295
|
+
: normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
|
|
296
|
+
const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
|
|
297
|
+
if (isHClassPath(candidate, { root }))
|
|
298
|
+
return true;
|
|
299
|
+
if (isSameOrDescendant(root, candidate))
|
|
300
|
+
return true;
|
|
301
|
+
if (!isSameOrDescendant(candidate, root))
|
|
302
|
+
return false;
|
|
303
|
+
// A filesystem grant is a range, not a one-file allow. Every directory
|
|
304
|
+
// below the protected root can contain an H-class backup name (for example
|
|
305
|
+
// *.json_), so an overlay grant there could bypass the baseline deny rules.
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
export function lClassGrantIncludesProtectedPath(value, options = {}) {
|
|
309
|
+
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
310
|
+
const protectedRoot = path.join(root, 'data', 'triggers');
|
|
311
|
+
if (isLClassPath(value, { cwd: options.cwd, root }))
|
|
312
|
+
return true;
|
|
313
|
+
const normalized = value.replace(/\\/g, '/');
|
|
314
|
+
const globIndex = normalized.search(/[*?[{]/);
|
|
315
|
+
const literalGrantRoot = globIndex < 0
|
|
316
|
+
? value
|
|
317
|
+
: normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
|
|
318
|
+
const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
|
|
319
|
+
return isSameOrDescendant(candidate, protectedRoot)
|
|
320
|
+
|| isSameOrDescendant(protectedRoot, candidate);
|
|
321
|
+
}
|
|
322
|
+
export function workspaceContainsHClassPaths(projectPath, root = resolveRoot()) {
|
|
323
|
+
const project = resolveThroughExistingAncestor(projectPath);
|
|
324
|
+
const protectedRoot = resolveThroughExistingAncestor(root);
|
|
325
|
+
if (isSameOrDescendant(protectedRoot, project))
|
|
326
|
+
return true;
|
|
327
|
+
if (!isSameOrDescendant(project, protectedRoot))
|
|
328
|
+
return false;
|
|
329
|
+
return hClassGrantIncludesProtectedPath(project, { root: protectedRoot });
|
|
330
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PeerIdentityCache - 对端身份缓存管理
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 通过 agentmdSync 标准流程获取对端 agent.md(check → fetch if changed)
|
|
6
|
+
* 2. 仅在 agent.md 内容变化时重写 peer-identity.json
|
|
7
|
+
* 3. 支持入站和出站消息的身份查询
|
|
8
|
+
*
|
|
9
|
+
* 信源:对端的 agent.md(通过 AIDStore.checkAgentMd + downloadAgentMd,由 agentmdSync 封装)
|
|
10
|
+
* 判定规则:type !== 'human' → agent
|
|
11
|
+
* 缓存位置:$AGENT_DIR/relations/<channel>#<urlEncode(peerId)>/peer-identity.json
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import * as crypto from 'crypto';
|
|
16
|
+
import { logger } from '../../utils/logger.js';
|
|
17
|
+
import { agentMdPath } from '../../paths.js';
|
|
18
|
+
/**
|
|
19
|
+
* peerKey: 关系层路由键,格式 `<channelType>#<urlEncode(channelId)>`。
|
|
20
|
+
* 群聊场景下 channelId = groupId,所有发言者共用同一个 peerKey。
|
|
21
|
+
*/
|
|
22
|
+
export function formatPeerKey(channelType, channelId) {
|
|
23
|
+
return `${channelType}#${encodeURIComponent(channelId)}`;
|
|
24
|
+
}
|
|
25
|
+
export function parsePeerKey(key) {
|
|
26
|
+
const idx = key.indexOf('#');
|
|
27
|
+
if (idx <= 0)
|
|
28
|
+
throw new Error(`Invalid peer key: ${key}`);
|
|
29
|
+
return {
|
|
30
|
+
channelType: key.slice(0, idx),
|
|
31
|
+
channelId: decodeURIComponent(key.slice(idx + 1)),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 对端身份缓存管理器
|
|
36
|
+
*/
|
|
37
|
+
export class PeerIdentityCache {
|
|
38
|
+
/** 缓存最大时效:30 天 */
|
|
39
|
+
static CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
40
|
+
/**
|
|
41
|
+
* 获取 peer-identity.json 文件路径
|
|
42
|
+
*/
|
|
43
|
+
static getFilePath(channelType, peerId, agentDir) {
|
|
44
|
+
const peerKey = formatPeerKey(channelType, peerId);
|
|
45
|
+
return path.join(agentDir, 'relations', peerKey, 'peer-identity.json');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 从文件读取缓存
|
|
49
|
+
* @returns PeerIdentity | null(缓存不存在)
|
|
50
|
+
*/
|
|
51
|
+
static get(channelType, peerId, agentDir) {
|
|
52
|
+
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
55
|
+
return JSON.parse(content);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 检查缓存是否需要刷新
|
|
63
|
+
* @param maxAgeMs 最大缓存时间(默认 30 天)
|
|
64
|
+
* @returns true=需要刷新
|
|
65
|
+
*/
|
|
66
|
+
static needsRefresh(channelType, peerId, agentDir, maxAgeMs = this.CACHE_MAX_AGE_MS) {
|
|
67
|
+
const cached = this.get(channelType, peerId, agentDir);
|
|
68
|
+
if (!cached)
|
|
69
|
+
return true;
|
|
70
|
+
return Date.now() - cached.lastCheckedAt > maxAgeMs;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 从 agent.md 更新身份信息
|
|
74
|
+
* @param source 'agentmd'(验签通过)或 'agentmd-unverified'(内容可解析但验签未过)
|
|
75
|
+
* @param verifiedAt 验签通过时间戳;未验签传 0
|
|
76
|
+
*/
|
|
77
|
+
static updateFromAgentMd(channelType, peerId, agentDir, agentMd, verifiedAt, source = 'agentmd') {
|
|
78
|
+
const typeMatch = agentMd.match(/^type:\s*["']?([^"'\n]+?)["']?\s*$/m);
|
|
79
|
+
const nameMatch = agentMd.match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
80
|
+
const type = typeMatch?.[1] || 'unknown';
|
|
81
|
+
const isAgent = type !== 'human';
|
|
82
|
+
const name = nameMatch?.[1]?.trim();
|
|
83
|
+
const agentMdHash = 'sha256:' + crypto.createHash('sha256').update(agentMd, 'utf-8').digest('hex');
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const identity = {
|
|
86
|
+
aid: peerId,
|
|
87
|
+
type,
|
|
88
|
+
isAgent,
|
|
89
|
+
name,
|
|
90
|
+
agentMdHash,
|
|
91
|
+
agentMdUpdatedAt: now,
|
|
92
|
+
verifiedAt,
|
|
93
|
+
lastCheckedAt: now,
|
|
94
|
+
source,
|
|
95
|
+
};
|
|
96
|
+
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
97
|
+
try {
|
|
98
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
99
|
+
fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
|
|
100
|
+
logger.debug(`[PeerIdentityCache] Updated: ${channelType}#${peerId} type=${type} isAgent=${isAgent} source=${source}`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
logger.warn(`[PeerIdentityCache] Failed to write cache: ${filePath} err=${err}`);
|
|
104
|
+
}
|
|
105
|
+
return identity;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 仅更新 lastCheckedAt(内容未变时的轻量操作)
|
|
109
|
+
*/
|
|
110
|
+
static touchLastChecked(channelType, peerId, agentDir, cached) {
|
|
111
|
+
const updated = { ...cached, lastCheckedAt: Date.now() };
|
|
112
|
+
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
113
|
+
try {
|
|
114
|
+
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
115
|
+
}
|
|
116
|
+
catch { /* ignore */ }
|
|
117
|
+
return updated;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 标记为 unknown(验签失败或无 agent.md)
|
|
121
|
+
*/
|
|
122
|
+
static markUnknown(channelType, peerId, agentDir) {
|
|
123
|
+
const identity = {
|
|
124
|
+
aid: peerId,
|
|
125
|
+
type: 'unknown',
|
|
126
|
+
isAgent: true,
|
|
127
|
+
agentMdHash: '',
|
|
128
|
+
agentMdUpdatedAt: 0,
|
|
129
|
+
verifiedAt: 0,
|
|
130
|
+
lastCheckedAt: Date.now(),
|
|
131
|
+
source: 'unknown',
|
|
132
|
+
};
|
|
133
|
+
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
134
|
+
try {
|
|
135
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
136
|
+
fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
|
|
137
|
+
logger.debug(`[PeerIdentityCache] Marked unknown: ${channelType}#${peerId}`);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
logger.warn(`[PeerIdentityCache] Failed to write unknown cache: ${filePath} err=${err}`);
|
|
141
|
+
}
|
|
142
|
+
return identity;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 完整流程:缓存检查 → agentmdSync(check+fetch)→ 按 changed 决定是否重写
|
|
146
|
+
*
|
|
147
|
+
* @param channelType 渠道类型(如 'aun')
|
|
148
|
+
* @param peerId 对端 ID(AUN 是 AID)
|
|
149
|
+
* @param agentDir agent 数据根目录
|
|
150
|
+
* @param store AIDStore 实例(由调用方提供,负责 checkAgentMd + downloadAgentMd)
|
|
151
|
+
* @param forceRefresh 强制刷新(忽略缓存时效)
|
|
152
|
+
*/
|
|
153
|
+
static async resolve(channelType, peerId, agentDir, store, forceRefresh = false) {
|
|
154
|
+
// 1. 缓存检查
|
|
155
|
+
if (!forceRefresh && !this.needsRefresh(channelType, peerId, agentDir)) {
|
|
156
|
+
const cached = this.get(channelType, peerId, agentDir);
|
|
157
|
+
if (cached) {
|
|
158
|
+
logger.debug(`[PeerIdentityCache] Cache hit: ${channelType}#${peerId} type=${cached.type} age=${Math.floor((Date.now() - cached.lastCheckedAt) / 1000 / 60 / 60 / 24)}d`);
|
|
159
|
+
return cached;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// 2. 通过 agentmdSync 拉取(内部走 store.checkAgentMd → store.downloadAgentMd)
|
|
163
|
+
try {
|
|
164
|
+
logger.debug(`[PeerIdentityCache] Syncing agent.md: ${channelType}#${peerId}`);
|
|
165
|
+
const { agentmdSync } = await import('../../aun/aid/agentmd.js');
|
|
166
|
+
const result = await agentmdSync(peerId, { store });
|
|
167
|
+
const content = result.content;
|
|
168
|
+
if (!content) {
|
|
169
|
+
throw new Error('agent.md content unavailable');
|
|
170
|
+
}
|
|
171
|
+
// 验签通过 → 可信(source=agentmd);否则 type 仍解析但标记未验证
|
|
172
|
+
const verified = result.verification?.status === 'verified';
|
|
173
|
+
const source = verified ? 'agentmd' : 'agentmd-unverified';
|
|
174
|
+
if (!verified) {
|
|
175
|
+
logger.info(`[PeerIdentityCache] agent.md unverified for ${peerId}: status=${result.verification?.status ?? 'unknown'} reason=${result.verification?.reason ?? '-'} (type 仍按声明解析)`);
|
|
176
|
+
}
|
|
177
|
+
// 3. 比较 hash,内容与可信级别都未变时仅 touch
|
|
178
|
+
const newHash = 'sha256:' + crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
179
|
+
const cached = this.get(channelType, peerId, agentDir);
|
|
180
|
+
if (cached && cached.agentMdHash === newHash && cached.source === source) {
|
|
181
|
+
return this.touchLastChecked(channelType, peerId, agentDir, cached);
|
|
182
|
+
}
|
|
183
|
+
return this.updateFromAgentMd(channelType, peerId, agentDir, content, verified ? Date.now() : 0, source);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
// 4. agentmdSync 抛错(非网络失败——网络失败时它内部已 fallback 返回本地内容;
|
|
187
|
+
// 这里通常是无内容或解析异常)。兜底读本地文件,但无法重新验签,
|
|
188
|
+
// 故沿用缓存里已有的可信级别,绝不凭空升级为已验签。
|
|
189
|
+
const localPath = agentMdPath(peerId);
|
|
190
|
+
try {
|
|
191
|
+
if (fs.existsSync(localPath)) {
|
|
192
|
+
const localContent = fs.readFileSync(localPath, 'utf-8');
|
|
193
|
+
const cached = this.get(channelType, peerId, agentDir);
|
|
194
|
+
logger.info(`[PeerIdentityCache] Using local agent.md for ${peerId} (cached source=${cached?.source ?? 'none'})`);
|
|
195
|
+
const localHash = 'sha256:' + crypto.createHash('sha256').update(localContent, 'utf-8').digest('hex');
|
|
196
|
+
if (cached && cached.agentMdHash === localHash && (cached.source === 'agentmd' || cached.source === 'agentmd-unverified')) {
|
|
197
|
+
return this.touchLastChecked(channelType, peerId, agentDir, cached);
|
|
198
|
+
}
|
|
199
|
+
// 无匹配缓存可信级别 → 本地内容未经本次验签,标记为未验证
|
|
200
|
+
const fallbackSource = cached?.source === 'agentmd' ? 'agentmd' : 'agentmd-unverified';
|
|
201
|
+
return this.updateFromAgentMd(channelType, peerId, agentDir, localContent, cached?.verifiedAt ?? 0, fallbackSource);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch { /* ignore fs errors */ }
|
|
205
|
+
logger.warn(`[PeerIdentityCache] Failed to resolve: ${channelType}#${peerId} err=${err instanceof Error ? err.message : String(err)}`);
|
|
206
|
+
return this.markUnknown(channelType, peerId, agentDir);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 清除指定对端的缓存
|
|
211
|
+
*/
|
|
212
|
+
static clear(channelType, peerId, agentDir) {
|
|
213
|
+
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
214
|
+
try {
|
|
215
|
+
fs.unlinkSync(filePath);
|
|
216
|
+
logger.debug(`[PeerIdentityCache] Cleared: ${channelType}#${peerId}`);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// 文件不存在,忽略
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './peer-identity.js';
|