evolcore 0.0.23 → 0.0.25
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 +583 -516
- package/LICENSE +21 -21
- package/README.md +455 -453
- package/bin/codex-managed-acl.ps1 +160 -155
- package/bin/codex-managed-hook.mjs +209 -92
- package/bin/ec-safe-output.js +327 -245
- package/bin/ec.js +29 -29
- package/bin/install-codex-managed-hooks.mjs +555 -544
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +152 -14
- package/dist/agents/codex/app-server-client.js +18 -8
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +215 -0
- package/dist/agents/codex-runner.js +445 -63
- package/dist/agents/ecagent-runner.js +31 -16
- package/dist/agents/gemini-runner.js +82 -4
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/agentmd.js +56 -4
- package/dist/aun/aid/index.js +1 -0
- package/dist/aun/aid/managed-operation.js +80 -6
- package/dist/aun/aid/store.js +41 -0
- package/dist/aun/msg/managed-operation.js +6 -1
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +4 -5
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +147 -76
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +214 -89
- package/dist/cli/agent-create.js +576 -0
- package/dist/cli/agent.js +129 -676
- package/dist/cli/aun-commands.js +419 -313
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +20 -20
- package/dist/cli/contact.js +10 -10
- package/dist/cli/ctl-command.js +22 -20
- package/dist/cli/daemon-commands.js +163 -32
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +7 -7
- package/dist/cli/index.js +79 -79
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +11 -11
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +462 -38
- package/dist/cli/stats.js +161 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +3 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +20 -3
- package/dist/config/resolved-config-op.js +39 -1
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +72 -19
- package/dist/core/auth/operation-authorizer.js +125 -7
- package/dist/core/auth/operation-catalog.js +26 -2
- package/dist/core/auth/remote-access-store.js +3 -2
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +60 -26
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +173 -36
- package/dist/core/command/menu-catalog.js +4 -1
- package/dist/core/command/menu-handler.js +315 -75
- package/dist/core/command/menu-protocol.js +7 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +166 -57
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +85 -27
- package/dist/core/model/catalog-service.js +3 -1
- package/dist/core/model/model-catalog.js +10 -0
- package/dist/core/model/model-permission.js +4 -1
- package/dist/core/permission/approval-gateway.js +169 -33
- package/dist/core/permission/tool-policy.js +56 -10
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +327 -134
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/welcome.js +22 -22
- package/dist/utils/windows-autostart.js +1 -1
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- 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/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +365 -364
- 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 +71 -69
- package/kits/docs/evolcore/agent-create.md +170 -156
- package/kits/docs/evolcore/agent.md +83 -73
- package/kits/docs/evolcore/aid.md +64 -62
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +195 -195
- package/kits/docs/evolcore/contact.md +68 -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 +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +34 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +165 -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 +50 -50
- package/kits/docs/evolcore/trigger.md +575 -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 +270 -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 +25 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +195 -183
- 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 +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +39 -37
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -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 +427 -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 +201 -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 +96 -82
- package/kits/templates/roles/member.json +97 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +86 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +38 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +98 -94
- package/AGENTS.md +0 -62
- package/dist/utils/codex-cli.js +0 -43
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import { randomUUID } from 'crypto';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
6
|
import { resolveRoot } from '../../paths.js';
|
|
@@ -41,6 +42,14 @@ export function expandManagedTempPath(value, options = {}) {
|
|
|
41
42
|
}
|
|
42
43
|
const WRITE_INTENT_RE = /(?:^|[\s;&|])(?:mkdir|mktemp|touch|tee|cp|mv|install|chmod|chown|ln|rm)(?:[\s;&|]|$)|(?:^|[\s])(?:\d>>?|>>?)\s*/i;
|
|
43
44
|
const TEMP_TOKEN_RE = /(?:\$TMPDIR|\$\{TMPDIR\}|\/tmp(?:\/|$)|\/var\/tmp(?:\/|$)|\/dev\/shm(?:\/|$))[^\s'";&|<>)]*/g;
|
|
45
|
+
function windowsTempRoots(options) {
|
|
46
|
+
if (process.platform !== 'win32')
|
|
47
|
+
return [];
|
|
48
|
+
return [os.tmpdir(), options.env?.TEMP, options.env?.TMP, process.env.TEMP, process.env.TMP]
|
|
49
|
+
.filter((value) => typeof value === 'string' && path.isAbsolute(value))
|
|
50
|
+
.map(value => resolveProtectedCandidate(value))
|
|
51
|
+
.filter((value, index, values) => values.indexOf(value) === index);
|
|
52
|
+
}
|
|
44
53
|
/**
|
|
45
54
|
* Inspect shell write targets that look like temporary paths. Ordinary
|
|
46
55
|
* project writes are left to the existing workspace/H-class policy; any
|
|
@@ -52,19 +61,32 @@ export function checkManagedTempShellCommand(command, options = {}) {
|
|
|
52
61
|
const root = resolveManagedTempDir(options.env);
|
|
53
62
|
const normalized = substituteTmpDir(command, root);
|
|
54
63
|
const candidates = [...normalized.matchAll(TEMP_TOKEN_RE)].map(match => match[0].replace(/[.,:]+$/, ''));
|
|
55
|
-
|
|
64
|
+
// Windows has no portable `/tmp` spelling. Treat absolute paths below the
|
|
65
|
+
// host temporary roots as temporary operands too, so a sibling session
|
|
66
|
+
// directory cannot bypass the managed TMPDIR check.
|
|
67
|
+
if (process.platform === 'win32') {
|
|
68
|
+
const roots = windowsTempRoots(options);
|
|
69
|
+
const windowsCandidates = normalized.match(/[A-Za-z]:[\\/][^\s'";&|<>)]*/g) ?? [];
|
|
70
|
+
for (const candidate of windowsCandidates) {
|
|
71
|
+
const canonical = resolveProtectedCandidate(candidate.replace(/[.,:]+$/, ''), options.cwd);
|
|
72
|
+
if (roots.some(root => isSameOrDescendant(canonical, root)))
|
|
73
|
+
candidates.push(candidate.replace(/[.,:]+$/, ''));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
77
|
+
if (uniqueCandidates.length === 0)
|
|
56
78
|
return { kind: 'none' };
|
|
57
79
|
if (!root) {
|
|
58
|
-
return { kind: 'deny', paths:
|
|
80
|
+
return { kind: 'deny', paths: uniqueCandidates, reason: 'TMPDIR is unset or not an absolute managed path' };
|
|
59
81
|
}
|
|
60
|
-
const outside =
|
|
82
|
+
const outside = uniqueCandidates.filter(candidate => !isManagedTempPath(candidate, {
|
|
61
83
|
...options,
|
|
62
84
|
env: { ...(options.env ?? process.env), TMPDIR: root },
|
|
63
85
|
}));
|
|
64
86
|
if (outside.length > 0) {
|
|
65
87
|
return { kind: 'deny', paths: outside, reason: 'temporary write target is outside the session-managed TMPDIR' };
|
|
66
88
|
}
|
|
67
|
-
return { kind: 'allow', paths:
|
|
89
|
+
return { kind: 'allow', paths: uniqueCandidates };
|
|
68
90
|
}
|
|
69
91
|
/**
|
|
70
92
|
* Replace only already-validated temporary path operands before a second
|
|
@@ -2161,10 +2183,11 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2161
2183
|
: command;
|
|
2162
2184
|
const tempCheck = checkManagedTempShellCommand(commandForPolicy, managedTempOptions(context));
|
|
2163
2185
|
if (tempCheck.kind === 'deny') {
|
|
2186
|
+
const currentTmpDir = resolveManagedTempDir(managedTempOptions(context).env);
|
|
2164
2187
|
return {
|
|
2165
2188
|
behavior: 'deny',
|
|
2166
2189
|
input,
|
|
2167
|
-
message: `🔒 临时文件必须位于当前会话的 $TMPDIR
|
|
2190
|
+
message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内(当前值:${currentTmpDir ?? '<不可用>'}):${tempCheck.reason}`,
|
|
2168
2191
|
policyCode: 'temporary_path_outside_tmpdir',
|
|
2169
2192
|
};
|
|
2170
2193
|
}
|
|
@@ -2215,14 +2238,20 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2215
2238
|
const ownAid = !!managedAidArgv
|
|
2216
2239
|
&& !!context.selfAid
|
|
2217
2240
|
&& managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
|
|
2218
|
-
const
|
|
2241
|
+
const isManagedAidPublicRead = !!context.selfAid
|
|
2242
|
+
&& !!managedAidArgv
|
|
2219
2243
|
&& managedAidArgv[0] === 'ec'
|
|
2220
2244
|
&& managedAidArgv[1] === 'aid'
|
|
2221
|
-
&&
|
|
2222
|
-
&& ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
|
|
2245
|
+
&& ((managedAidArgv.length === 4 && managedAidArgv[2] === 'lookup')
|
|
2223
2246
|
|| (managedAidArgv.length === 5
|
|
2224
2247
|
&& managedAidArgv[2] === 'agentmd'
|
|
2225
2248
|
&& managedAidArgv[3] === 'get'));
|
|
2249
|
+
const isSelfManagedAidShow = !!managedAidArgv
|
|
2250
|
+
&& managedAidArgv.length === 4
|
|
2251
|
+
&& managedAidArgv[0] === 'ec'
|
|
2252
|
+
&& managedAidArgv[1] === 'aid'
|
|
2253
|
+
&& managedAidArgv[2] === 'show'
|
|
2254
|
+
&& ownAid;
|
|
2226
2255
|
const isSelfAgentMdPublish = !!managedAidArgv
|
|
2227
2256
|
&& managedAidArgv.length === 5
|
|
2228
2257
|
&& managedAidArgv[0] === 'ec'
|
|
@@ -2230,16 +2259,33 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2230
2259
|
&& managedAidArgv[2] === 'agentmd'
|
|
2231
2260
|
&& managedAidArgv[3] === 'put'
|
|
2232
2261
|
&& ownAid;
|
|
2262
|
+
const avatarFileIndex = managedAidArgv?.indexOf('--file') ?? -1;
|
|
2263
|
+
const avatarUrlIndex = managedAidArgv?.indexOf('--url') ?? -1;
|
|
2264
|
+
const avatarSourceIndex = avatarFileIndex >= 0 ? avatarFileIndex : avatarUrlIndex;
|
|
2265
|
+
const isSelfAvatarUpload = !!managedAidArgv
|
|
2266
|
+
&& (avatarSourceIndex === 4)
|
|
2267
|
+
&& managedAidArgv.length === 6
|
|
2268
|
+
&& managedAidArgv[0] === 'ec'
|
|
2269
|
+
&& managedAidArgv[1] === 'aid'
|
|
2270
|
+
&& managedAidArgv[2] === 'avatar'
|
|
2271
|
+
&& !!context.selfAid
|
|
2272
|
+
&& managedAidArgv[3]?.replace(/^@/, '') === context.selfAid.replace(/^@/, '')
|
|
2273
|
+
&& !!managedAidArgv[5]
|
|
2274
|
+
&& !managedAidArgv[5].startsWith('-');
|
|
2233
2275
|
const canPublishSelfAgentMd = isSelfAgentMdPublish
|
|
2234
2276
|
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
2235
|
-
const
|
|
2277
|
+
const canUploadSelfAvatar = isSelfAvatarUpload
|
|
2278
|
+
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
2279
|
+
const isAllowedManagedAidCommand = isManagedAidPublicRead || isSelfManagedAidShow || canPublishSelfAgentMd || canUploadSelfAvatar;
|
|
2236
2280
|
if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
|
|
2237
2281
|
return {
|
|
2238
2282
|
behavior: 'deny',
|
|
2239
2283
|
input,
|
|
2240
2284
|
message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
2241
2285
|
? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
|
|
2242
|
-
:
|
|
2286
|
+
: isSelfAvatarUpload && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
2287
|
+
? `🔒 ${context.permissionMode} 模式禁止上传头像;请切换到 request 或 bypass 后再执行 \`ec aid avatar <当前 self AID> (--file <path> 或 --url <https-url>)\`。`
|
|
2288
|
+
: '🔒 EvolCore 托管会话允许 `ec aid show <当前 self AID>`、`ec aid lookup <任意合法 AID>` 或 `ec aid agentmd get <任意合法 AID>`;`ec aid agentmd put` 及 `ec aid avatar` 还要求针对当前 self AID 且处于 request 或 bypass。可选后缀仅限 `--format json`。禁止列举本地身份、切换身份或对其它 AID 执行 show/put/avatar。',
|
|
2243
2289
|
policyCode: 'managed_ec_aid_scope_forbidden',
|
|
2244
2290
|
};
|
|
2245
2291
|
}
|
|
@@ -32,15 +32,20 @@ function loadSqlite() {
|
|
|
32
32
|
return sqliteModule;
|
|
33
33
|
}
|
|
34
34
|
export class CodexSessionFileAdapter {
|
|
35
|
+
codexHome;
|
|
35
36
|
baseagent = 'codex';
|
|
36
37
|
db = null;
|
|
37
38
|
dbInitialized = false;
|
|
39
|
+
rolloutPathCache = new Map();
|
|
40
|
+
constructor(codexHome = path.join(os.homedir(), '.codex')) {
|
|
41
|
+
this.codexHome = codexHome;
|
|
42
|
+
}
|
|
38
43
|
/**
|
|
39
44
|
* 动态发现最新的 state_*.sqlite 文件
|
|
40
45
|
* Codex 使用 sqlx 迁移,DB 文件名含版本号(state_5, state_6, ...)
|
|
41
46
|
*/
|
|
42
47
|
resolveStateDbPath() {
|
|
43
|
-
const codexHome =
|
|
48
|
+
const codexHome = this.codexHome;
|
|
44
49
|
if (!fs.existsSync(codexHome))
|
|
45
50
|
return null;
|
|
46
51
|
try {
|
|
@@ -94,11 +99,71 @@ export class CodexSessionFileAdapter {
|
|
|
94
99
|
// SDK 创建的 thread 不写 state_*.sqlite,但会持久化到 sessions 目录
|
|
95
100
|
return !!this.findRolloutFile(agentSessionId);
|
|
96
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* A row in state_*.sqlite is created before the first turn is necessarily
|
|
104
|
+
* recoverable. Require the rollout to contain both its own session metadata
|
|
105
|
+
* and the exact turn_context before EvolCore publishes a new binding.
|
|
106
|
+
*/
|
|
107
|
+
checkTurnPersisted(_projectPath, agentSessionId, turnId, _agentAid) {
|
|
108
|
+
const normalizedSessionId = agentSessionId.trim();
|
|
109
|
+
const normalizedTurnId = turnId.trim();
|
|
110
|
+
if (!normalizedSessionId || !normalizedTurnId)
|
|
111
|
+
return false;
|
|
112
|
+
let rolloutPath = null;
|
|
113
|
+
const db = this.getDb();
|
|
114
|
+
if (db) {
|
|
115
|
+
try {
|
|
116
|
+
const row = db.prepare('SELECT rollout_path FROM threads WHERE id = ? AND archived = 0').get(normalizedSessionId);
|
|
117
|
+
rolloutPath = this.resolveRolloutPath(row?.rollout_path, normalizedSessionId);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger.warn('[CodexAdapter] checkTurnPersisted DB query failed:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
rolloutPath ??= this.findRolloutFile(normalizedSessionId);
|
|
124
|
+
if (!rolloutPath)
|
|
125
|
+
return false;
|
|
126
|
+
try {
|
|
127
|
+
let hasMatchingSession = false;
|
|
128
|
+
let hasMatchingTurn = false;
|
|
129
|
+
const lines = fs.readFileSync(rolloutPath, 'utf-8').split('\n');
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (!line.trim())
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
const event = JSON.parse(line);
|
|
135
|
+
if (event?.type === 'session_meta') {
|
|
136
|
+
const persistedSessionId = event.payload?.session_id ?? event.payload?.id;
|
|
137
|
+
const version = event.payload?.version ?? event.payload?.cli_version;
|
|
138
|
+
const hasVersion = version !== undefined && version !== null && String(version).trim().length > 0;
|
|
139
|
+
if (persistedSessionId === normalizedSessionId && hasVersion)
|
|
140
|
+
hasMatchingSession = true;
|
|
141
|
+
}
|
|
142
|
+
else if (event?.type === 'turn_context' && event.payload?.turn_id === normalizedTurnId) {
|
|
143
|
+
hasMatchingTurn = true;
|
|
144
|
+
}
|
|
145
|
+
if (hasMatchingSession && hasMatchingTurn)
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// The last JSONL line may still be in flight; a later poll retries it.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
97
158
|
/**
|
|
98
159
|
* 在 ~/.codex/sessions/ 下递归查找含 agentSessionId 的 rollout JSONL 文件
|
|
99
160
|
*/
|
|
100
161
|
findRolloutFile(agentSessionId) {
|
|
101
|
-
const
|
|
162
|
+
const cached = this.rolloutPathCache.get(agentSessionId);
|
|
163
|
+
if (cached && fs.existsSync(cached))
|
|
164
|
+
return cached;
|
|
165
|
+
this.rolloutPathCache.delete(agentSessionId);
|
|
166
|
+
const sessionsDir = path.join(this.codexHome, 'sessions');
|
|
102
167
|
if (!fs.existsSync(sessionsDir))
|
|
103
168
|
return null;
|
|
104
169
|
try {
|
|
@@ -110,7 +175,9 @@ export class CodexSessionFileAdapter {
|
|
|
110
175
|
return found;
|
|
111
176
|
}
|
|
112
177
|
else if (entry.name.endsWith('.jsonl') && entry.name.includes(agentSessionId)) {
|
|
113
|
-
|
|
178
|
+
const rolloutPath = path.join(dir, entry.name);
|
|
179
|
+
this.rolloutPathCache.set(agentSessionId, rolloutPath);
|
|
180
|
+
return rolloutPath;
|
|
114
181
|
}
|
|
115
182
|
}
|
|
116
183
|
return null;
|
|
@@ -251,6 +318,7 @@ export class CodexSessionFileAdapter {
|
|
|
251
318
|
this.db = null;
|
|
252
319
|
}
|
|
253
320
|
this.dbInitialized = false;
|
|
321
|
+
this.rolloutPathCache.clear();
|
|
254
322
|
}
|
|
255
323
|
/**
|
|
256
324
|
* 从 rollout JSONL 读取第一条或最后一条 user_message
|
|
@@ -557,13 +557,29 @@ export class SessionManager {
|
|
|
557
557
|
}
|
|
558
558
|
const wasBound = !!normalizeBackendSessionId(current.agentSessionId);
|
|
559
559
|
const next = JSON.parse(JSON.stringify(current));
|
|
560
|
+
const previousBaseagent = next.baseagent;
|
|
561
|
+
if (input.baseagent !== undefined) {
|
|
562
|
+
const requested = input.baseagent.trim();
|
|
563
|
+
if (!requested) {
|
|
564
|
+
const error = new Error('backend rotation baseagent must be non-empty');
|
|
565
|
+
error.code = 'BASEAGENT_REQUIRED';
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
next.baseagent = requested;
|
|
569
|
+
}
|
|
560
570
|
next.agentSessionId = undefined;
|
|
561
571
|
const nextMetadata = { ...(next.metadata ?? {}) };
|
|
562
572
|
delete nextMetadata.resumeAt;
|
|
563
573
|
delete nextMetadata.agentSessions;
|
|
564
574
|
next.metadata = nextMetadata;
|
|
565
575
|
this.persistConfirmedTopicBoundary(current, found, next);
|
|
566
|
-
return {
|
|
576
|
+
return {
|
|
577
|
+
status: 'rotated',
|
|
578
|
+
sessionId: input.sessionId,
|
|
579
|
+
wasBound,
|
|
580
|
+
previousBaseagent,
|
|
581
|
+
baseagent: next.baseagent,
|
|
582
|
+
};
|
|
567
583
|
});
|
|
568
584
|
}
|
|
569
585
|
/**
|
|
@@ -830,7 +846,10 @@ export class SessionManager {
|
|
|
830
846
|
const sessionMetadata = { ...(metadata || {}) };
|
|
831
847
|
sessionMetadata.__sessionCreatedAt = Date.now();
|
|
832
848
|
const newIdentity = identity ?? this.resolveIdentity(channel, userId, chatType, identityConversationId);
|
|
833
|
-
const resolvedBaseagent = baseagent
|
|
849
|
+
const resolvedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
|
|
850
|
+
if (!resolvedBaseagent) {
|
|
851
|
+
throw new Error(`[SessionManager] getOrCreateSession: baseagent is empty for channel=${channel} channelId=${channelId}`);
|
|
852
|
+
}
|
|
834
853
|
const session = {
|
|
835
854
|
id: generateSessionId(),
|
|
836
855
|
channel,
|
|
@@ -857,6 +876,7 @@ export class SessionManager {
|
|
|
857
876
|
projectPath: defaultProjectPath,
|
|
858
877
|
name: session.name,
|
|
859
878
|
chatType: session.chatType,
|
|
879
|
+
baseagent: session.baseagent,
|
|
860
880
|
timestamp: Date.now(),
|
|
861
881
|
});
|
|
862
882
|
return session;
|
|
@@ -868,9 +888,9 @@ export class SessionManager {
|
|
|
868
888
|
if (!loaded)
|
|
869
889
|
return;
|
|
870
890
|
const { current } = loaded;
|
|
871
|
-
if (
|
|
872
|
-
const error = new Error('
|
|
873
|
-
error.code = '
|
|
891
|
+
if (updates.baseagent !== undefined) {
|
|
892
|
+
const error = new Error('session baseagent is immutable; use explicit agent switch or backend rotation');
|
|
893
|
+
error.code = 'BASEAGENT_IMMUTABLE';
|
|
874
894
|
throw error;
|
|
875
895
|
}
|
|
876
896
|
if (current.threadId && Object.prototype.hasOwnProperty.call(updates, 'agentSessionId')) {
|
|
@@ -878,8 +898,6 @@ export class SessionManager {
|
|
|
878
898
|
error.code = 'TOPIC_BACKEND_BINDING_PROTECTED';
|
|
879
899
|
throw error;
|
|
880
900
|
}
|
|
881
|
-
if (updates.baseagent !== undefined)
|
|
882
|
-
current.baseagent = updates.baseagent;
|
|
883
901
|
if (updates.chatType !== undefined)
|
|
884
902
|
current.chatType = updates.chatType;
|
|
885
903
|
if (updates.name !== undefined)
|
|
@@ -988,7 +1006,7 @@ export class SessionManager {
|
|
|
988
1006
|
const mainActive = readJsonFile(path.join(chatDir, 'active.json'));
|
|
989
1007
|
// 兼容旧字段名
|
|
990
1008
|
const mainBaseagent = mainActive?.baseagent || mainActive?.agentType;
|
|
991
|
-
const inheritedBaseagent =
|
|
1009
|
+
const inheritedBaseagent = mainBaseagent || baseagent;
|
|
992
1010
|
if (!inheritedBaseagent) {
|
|
993
1011
|
throw new Error('[SessionManager] getOrCreateThreadSession: baseagent is empty');
|
|
994
1012
|
}
|
|
@@ -1022,6 +1040,7 @@ export class SessionManager {
|
|
|
1022
1040
|
channelId,
|
|
1023
1041
|
projectPath,
|
|
1024
1042
|
name: session.name,
|
|
1043
|
+
baseagent: session.baseagent,
|
|
1025
1044
|
timestamp: Date.now(),
|
|
1026
1045
|
});
|
|
1027
1046
|
return session;
|
|
@@ -1072,6 +1091,7 @@ export class SessionManager {
|
|
|
1072
1091
|
channelId,
|
|
1073
1092
|
projectPath: newProjectPath,
|
|
1074
1093
|
name: session.name,
|
|
1094
|
+
baseagent: session.baseagent,
|
|
1075
1095
|
timestamp: Date.now(),
|
|
1076
1096
|
});
|
|
1077
1097
|
return session;
|
|
@@ -1180,6 +1200,7 @@ export class SessionManager {
|
|
|
1180
1200
|
channelId,
|
|
1181
1201
|
projectPath,
|
|
1182
1202
|
name: session.name,
|
|
1203
|
+
baseagent: session.baseagent,
|
|
1183
1204
|
timestamp: Date.now(),
|
|
1184
1205
|
});
|
|
1185
1206
|
return session;
|
|
@@ -1428,7 +1449,7 @@ export class SessionManager {
|
|
|
1428
1449
|
}
|
|
1429
1450
|
}
|
|
1430
1451
|
}
|
|
1431
|
-
async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata) {
|
|
1452
|
+
async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata, eventMetadata) {
|
|
1432
1453
|
const inheritedChatType = this.getActiveChatType(channel, channelId);
|
|
1433
1454
|
// Derive selfAID and channelType from existing sessions
|
|
1434
1455
|
const existingDir = this.findExistingChatDir(channel, channelId);
|
|
@@ -1480,6 +1501,8 @@ export class SessionManager {
|
|
|
1480
1501
|
channelId,
|
|
1481
1502
|
projectPath,
|
|
1482
1503
|
name: session.name,
|
|
1504
|
+
baseagent: session.baseagent,
|
|
1505
|
+
...eventMetadata,
|
|
1483
1506
|
timestamp: Date.now(),
|
|
1484
1507
|
});
|
|
1485
1508
|
return session;
|
|
@@ -1527,6 +1550,7 @@ export class SessionManager {
|
|
|
1527
1550
|
channelId: sourceSession.channelId,
|
|
1528
1551
|
projectPath: sourceSession.projectPath,
|
|
1529
1552
|
name: session.name,
|
|
1553
|
+
baseagent: session.baseagent,
|
|
1530
1554
|
timestamp: Date.now(),
|
|
1531
1555
|
});
|
|
1532
1556
|
return session;
|
|
@@ -1602,6 +1626,7 @@ export class SessionManager {
|
|
|
1602
1626
|
name: session.name,
|
|
1603
1627
|
chatType: session.chatType,
|
|
1604
1628
|
threadId: session.threadId,
|
|
1629
|
+
baseagent: session.baseagent,
|
|
1605
1630
|
timestamp: now,
|
|
1606
1631
|
});
|
|
1607
1632
|
return session;
|
|
@@ -1624,7 +1649,7 @@ export class SessionManager {
|
|
|
1624
1649
|
selfAID: sourceSession.selfAID,
|
|
1625
1650
|
projectPath: sourceSession.projectPath,
|
|
1626
1651
|
threadId: '',
|
|
1627
|
-
baseagent: sourceSession.baseagent,
|
|
1652
|
+
baseagent: context?.baseagent || sourceSession.baseagent,
|
|
1628
1653
|
sessionKey: formatSessionKey(sourceSession.channelType || sourceSession.channel, sourceSession.channelId, DEFAULT_THREAD_ID),
|
|
1629
1654
|
chatType: sourceSession.chatType || 'private',
|
|
1630
1655
|
metadata,
|
|
@@ -1642,6 +1667,7 @@ export class SessionManager {
|
|
|
1642
1667
|
projectPath: session.projectPath,
|
|
1643
1668
|
name: session.name,
|
|
1644
1669
|
chatType: session.chatType,
|
|
1670
|
+
baseagent: session.baseagent,
|
|
1645
1671
|
cause: 'renew',
|
|
1646
1672
|
sourceSessionId: sourceSession.id,
|
|
1647
1673
|
renewSource: context?.source,
|
|
@@ -1684,6 +1710,12 @@ export class SessionManager {
|
|
|
1684
1710
|
return false;
|
|
1685
1711
|
return adapter.checkExists(projectPath, agentSessionId, agentAid);
|
|
1686
1712
|
}
|
|
1713
|
+
checkSessionTurnPersisted(projectPath, agentSessionId, baseagent, turnId, agentAid) {
|
|
1714
|
+
const adapter = this.getFileAdapter(baseagent);
|
|
1715
|
+
if (!adapter?.checkTurnPersisted)
|
|
1716
|
+
return false;
|
|
1717
|
+
return adapter.checkTurnPersisted(projectPath, agentSessionId, turnId, agentAid);
|
|
1718
|
+
}
|
|
1687
1719
|
readSessionFirstMessage(projectPath, agentSessionId, baseagent, agentAid) {
|
|
1688
1720
|
const adapter = this.getFileAdapter(baseagent);
|
|
1689
1721
|
if (!adapter)
|
|
@@ -1725,10 +1757,13 @@ export class SessionManager {
|
|
|
1725
1757
|
}
|
|
1726
1758
|
return matched[0];
|
|
1727
1759
|
}
|
|
1728
|
-
async importCliSession(channel, channelId, projectPath, agentSessionId, baseagent
|
|
1760
|
+
async importCliSession(channel, channelId, projectPath, agentSessionId, baseagent, agentAid) {
|
|
1761
|
+
const resolvedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
|
|
1762
|
+
if (!resolvedBaseagent)
|
|
1763
|
+
throw new Error('[SessionManager] importCliSession: baseagent is required');
|
|
1729
1764
|
const inheritedChatType = this.getActiveChatType(channel, channelId);
|
|
1730
1765
|
const resolvedAgentAid = agentAid || this.deriveChannelIdentity(channel, channelId).selfAID;
|
|
1731
|
-
const fileInfo = this.getSessionFileInfo(projectPath, agentSessionId,
|
|
1766
|
+
const fileInfo = this.getSessionFileInfo(projectPath, agentSessionId, resolvedBaseagent, resolvedAgentAid);
|
|
1732
1767
|
const name = fileInfo.title || `CLI会话-${new Date().toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}`;
|
|
1733
1768
|
// Derive selfAID and channelType from existing sessions
|
|
1734
1769
|
const existingDir = this.findExistingChatDir(channel, channelId);
|
|
@@ -1750,7 +1785,7 @@ export class SessionManager {
|
|
|
1750
1785
|
selfAID,
|
|
1751
1786
|
projectPath,
|
|
1752
1787
|
threadId: '',
|
|
1753
|
-
baseagent,
|
|
1788
|
+
baseagent: resolvedBaseagent,
|
|
1754
1789
|
sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
|
|
1755
1790
|
chatType: inheritedChatType,
|
|
1756
1791
|
agentSessionId,
|
|
@@ -1767,6 +1802,7 @@ export class SessionManager {
|
|
|
1767
1802
|
channelId,
|
|
1768
1803
|
projectPath,
|
|
1769
1804
|
name,
|
|
1805
|
+
baseagent: session.baseagent,
|
|
1770
1806
|
timestamp: Date.now(),
|
|
1771
1807
|
});
|
|
1772
1808
|
return session;
|
|
@@ -191,6 +191,7 @@ export class SessionRenewService {
|
|
|
191
191
|
? await this.sessionManager.createRenewedSession(request.session, {
|
|
192
192
|
source,
|
|
193
193
|
causation: request.causation,
|
|
194
|
+
baseagent: request.currentBaseagent,
|
|
194
195
|
})
|
|
195
196
|
: request.session;
|
|
196
197
|
selected.identity = request.session.identity;
|