evolcore 0.0.11 → 0.0.13
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 +44 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +86 -7
- package/dist/agents/codex-app-server-client.js +174 -20
- package/dist/agents/codex-runner.js +375 -92
- package/dist/agents/ecagent-runner.js +39 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +91 -44
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +865 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +655 -240
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +108 -31
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +70 -55
- package/dist/cli/fs-command.js +79 -10
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/init.js +77 -8
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +53 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +7 -32
- package/dist/cli/trigger-command.js +11 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +9 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -2
- package/dist/core/auth/auth-gateway.js +29 -1
- package/dist/core/auth/authorization-audit.js +15 -0
- package/dist/core/auth/operation-authorizer.js +31 -7
- package/dist/core/auth/operation-catalog.js +196 -25
- package/dist/core/bootstrap-messages.js +25 -0
- package/dist/core/bootstrap-service.js +64 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +650 -42
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +370 -0
- package/dist/core/command/menu-handler.js +230 -51
- package/dist/core/command/menu-protocol.js +94 -2
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +99 -31
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +157 -22
- package/dist/core/handoff/store.js +29 -1
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +298 -88
- package/dist/core/message/message-log.js +3 -0
- package/dist/core/message/message-queue.js +268 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +161 -77
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +243 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +43 -1
- package/dist/core/permission/tool-policy.js +322 -57
- package/dist/core/protected-paths.js +114 -4
- package/dist/core/session/session-manager.js +42 -4
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +541 -180
- package/dist/ipc.js +232 -10
- package/dist/paths.js +3 -0
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +34 -1
- package/kits/templates/roles/visitor.json +35 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/command/evol-menu-version-gate.js +0 -39
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -11,7 +11,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
11
11
|
/^backups\/config(?:\/|$)/,
|
|
12
12
|
/^\.snapshots(?:\/|$)/,
|
|
13
13
|
/^CA(?:\/|$)/,
|
|
14
|
-
/^(?:AIDs|aids)\/[^/]+\/(?:cert|keys)(?:\/|$)/,
|
|
14
|
+
/^(?:AIDs|aids)\/[^/]+\/(?:cert|keys|private)(?:\/|$)/,
|
|
15
15
|
/^\.device_id$/,
|
|
16
16
|
/^\.env$/,
|
|
17
17
|
/^\.seed(?:\.|$)/,
|
|
@@ -33,11 +33,25 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
33
33
|
const H_CLASS_REFERENCE_PATTERNS = [
|
|
34
34
|
/(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
|
|
35
35
|
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
36
|
-
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
36
|
+
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
37
37
|
/(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|sessions|contact-book-audit\.jsonl|feishu-seen-[^/\\\s"']+\.jsonl|[a-z0-9_-]+-greeted-[^/\\\s"']+\.jsonl|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|daemon[\\/](?:message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|control|migrations)|channel-state|instance[\\/]control\.token|outbox|handoff)|agents[\\/][^/\\\s"']+[\\/](?:sessions|data[\\/](?:channels|handoff|outbox\.jsonl|contact-audit\.jsonl)))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
38
38
|
/(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
39
39
|
/(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
|
|
40
40
|
];
|
|
41
|
+
// H-class directory roots are kept separately from the full path matcher so
|
|
42
|
+
// an exact metadata query can distinguish a protected directory from a
|
|
43
|
+
// protected file. Descendants inherit the directory classification unless an
|
|
44
|
+
// existing regular file gives us a more precise answer.
|
|
45
|
+
const H_CLASS_DIRECTORY_RELATIVE_PATTERNS = [
|
|
46
|
+
/^backups\/config(?:\/|$)/,
|
|
47
|
+
/^\.snapshots(?:\/|$)/,
|
|
48
|
+
/^CA(?:\/|$)/,
|
|
49
|
+
/^(?:AIDs|aids)\/[^/]+\/(?:cert|keys|private)(?:\/|$)/,
|
|
50
|
+
/^data\/(?:triggers|sessions|outbox|handoff|channel-state)(?:\/|$)/,
|
|
51
|
+
/^data\/daemon\/(?:control|migrations)(?:\/|$)/,
|
|
52
|
+
/^agents\/[^/]+\/sessions(?:\/|$)/,
|
|
53
|
+
/^agents\/[^/]+\/data\/(?:channels|handoff)(?:\/|$)/,
|
|
54
|
+
];
|
|
41
55
|
const L_CLASS_RELATIVE_PATTERNS = [
|
|
42
56
|
/^agents\/[^/]+\/triggers(?:\/|$)/,
|
|
43
57
|
/^agents\/[^/]+\/roles(?:\/|$)/,
|
|
@@ -68,6 +82,22 @@ function resolveThroughExistingAncestor(value) {
|
|
|
68
82
|
return absolute;
|
|
69
83
|
}
|
|
70
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* H-class file names are normally leaves, but treating their descendants as
|
|
87
|
+
* protected as well is important when a malformed or externally-created
|
|
88
|
+
* directory uses a reserved file name (for example `config.json/child`).
|
|
89
|
+
* Such a path cannot expose content from the expected file, and fail-closed
|
|
90
|
+
* classification avoids turning that unusual filesystem state into a bypass.
|
|
91
|
+
*/
|
|
92
|
+
function matchesHClassRelative(relative) {
|
|
93
|
+
const segments = relative.split('/').filter(Boolean);
|
|
94
|
+
for (let end = segments.length; end > 0; end--) {
|
|
95
|
+
if (H_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(segments.slice(0, end).join('/')))) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
71
101
|
export function isSameOrDescendant(candidate, parent) {
|
|
72
102
|
const normalizedCandidate = normalizeForComparison(candidate);
|
|
73
103
|
const normalizedParent = normalizeForComparison(parent);
|
|
@@ -85,14 +115,89 @@ export function isHClassPath(value, options = {}) {
|
|
|
85
115
|
: path.resolve(options.cwd ?? process.cwd(), value);
|
|
86
116
|
if (isSameOrDescendant(lexicalCandidate, root)) {
|
|
87
117
|
const lexicalRelative = path.relative(root, lexicalCandidate).split(path.sep).join('/');
|
|
88
|
-
if (
|
|
118
|
+
if (matchesHClassRelative(lexicalRelative))
|
|
89
119
|
return true;
|
|
90
120
|
}
|
|
91
121
|
const candidate = resolveProtectedCandidate(value, options.cwd);
|
|
92
122
|
if (!isSameOrDescendant(candidate, root))
|
|
93
123
|
return false;
|
|
94
124
|
const relative = path.relative(root, candidate).split(path.sep).join('/');
|
|
95
|
-
return
|
|
125
|
+
return matchesHClassRelative(relative);
|
|
126
|
+
}
|
|
127
|
+
function hasPathSyntax(value) {
|
|
128
|
+
return /[\0\r\n$`*?\[\]{}<>|;&]/.test(value);
|
|
129
|
+
}
|
|
130
|
+
function lexicalCandidate(value, cwd = process.cwd()) {
|
|
131
|
+
return path.isAbsolute(value) ? path.resolve(value) : path.resolve(cwd, value);
|
|
132
|
+
}
|
|
133
|
+
function resolveWithoutFinalSymlink(value, cwd = process.cwd()) {
|
|
134
|
+
const absolute = lexicalCandidate(value, cwd);
|
|
135
|
+
const basename = path.basename(absolute);
|
|
136
|
+
const parent = resolveThroughExistingAncestor(path.dirname(absolute));
|
|
137
|
+
return basename ? path.join(parent, basename) : parent;
|
|
138
|
+
}
|
|
139
|
+
/** Resolve path ancestors while preserving the final symlink as a literal node. */
|
|
140
|
+
export function resolveProtectedCandidateWithoutFinalSymlink(value, cwd = process.cwd()) {
|
|
141
|
+
return resolveWithoutFinalSymlink(value, cwd);
|
|
142
|
+
}
|
|
143
|
+
function relativeWithinRoot(value, root) {
|
|
144
|
+
if (!isSameOrDescendant(value, root))
|
|
145
|
+
return undefined;
|
|
146
|
+
return path.relative(root, value).split(path.sep).join('/');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Classify one literal filesystem path without interpreting shell syntax.
|
|
150
|
+
* `followFinalSymlink=false` is used by stat/lstat policy so metadata queries
|
|
151
|
+
* describe the link itself rather than exposing the protected target.
|
|
152
|
+
*/
|
|
153
|
+
export function classifyProtectedPath(value, options = {}) {
|
|
154
|
+
if (typeof value !== 'string' || !value.trim() || hasPathSyntax(value))
|
|
155
|
+
return 'unknown';
|
|
156
|
+
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
157
|
+
const lexical = resolveWithoutFinalSymlink(value, options.cwd);
|
|
158
|
+
const followFinalSymlink = options.followFinalSymlink !== false;
|
|
159
|
+
const candidate = followFinalSymlink ? resolveProtectedCandidate(value, options.cwd) : lexical;
|
|
160
|
+
const relative = relativeWithinRoot(candidate, root);
|
|
161
|
+
if (relative === undefined)
|
|
162
|
+
return 'ordinary';
|
|
163
|
+
const hMatch = matchesHClassRelative(relative);
|
|
164
|
+
if (hMatch) {
|
|
165
|
+
let directory = H_CLASS_DIRECTORY_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
|
|
166
|
+
try {
|
|
167
|
+
const statTarget = followFinalSymlink ? candidate : lexical;
|
|
168
|
+
directory = fs.lstatSync(statTarget).isDirectory();
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Missing paths below a protected directory inherit that directory's
|
|
172
|
+
// class; missing exact H-class filenames remain H-class files.
|
|
173
|
+
}
|
|
174
|
+
return directory ? 'h-directory' : 'h-file';
|
|
175
|
+
}
|
|
176
|
+
const lMatch = L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
|
|
177
|
+
if (lMatch)
|
|
178
|
+
return 'l-path';
|
|
179
|
+
return 'ordinary';
|
|
180
|
+
}
|
|
181
|
+
/** Apply the H/L path-class × access-action matrix to one literal path. */
|
|
182
|
+
export function checkProtectedPathAccess(value, accessKind, options = {}) {
|
|
183
|
+
const pathClass = classifyProtectedPath(value, options);
|
|
184
|
+
if (pathClass === 'unknown')
|
|
185
|
+
return { pathClass, accessKind, allowed: false };
|
|
186
|
+
if (pathClass === 'h-file' || pathClass === 'h-directory') {
|
|
187
|
+
return { pathClass, accessKind, allowed: accessKind === 'metadata' };
|
|
188
|
+
}
|
|
189
|
+
if (pathClass === 'l-path') {
|
|
190
|
+
return {
|
|
191
|
+
pathClass,
|
|
192
|
+
accessKind,
|
|
193
|
+
allowed: accessKind !== 'write' && accessKind !== 'grant',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
pathClass,
|
|
198
|
+
accessKind,
|
|
199
|
+
allowed: accessKind !== 'write' && accessKind !== 'grant',
|
|
200
|
+
};
|
|
96
201
|
}
|
|
97
202
|
export function containsHClassReference(value) {
|
|
98
203
|
const normalized = value.replace(/\\/g, '/');
|
|
@@ -144,10 +249,14 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
|
144
249
|
path.join('AIDs', '*', 'cert', '**'),
|
|
145
250
|
path.join('AIDs', '*', 'keys'),
|
|
146
251
|
path.join('AIDs', '*', 'keys', '**'),
|
|
252
|
+
path.join('AIDs', '*', 'private'),
|
|
253
|
+
path.join('AIDs', '*', 'private', '**'),
|
|
147
254
|
path.join('aids', '*', 'cert'),
|
|
148
255
|
path.join('aids', '*', 'cert', '**'),
|
|
149
256
|
path.join('aids', '*', 'keys'),
|
|
150
257
|
path.join('aids', '*', 'keys', '**'),
|
|
258
|
+
path.join('aids', '*', 'private'),
|
|
259
|
+
path.join('aids', '*', 'private', '**'),
|
|
151
260
|
'.device_id',
|
|
152
261
|
'.env',
|
|
153
262
|
'.seed',
|
|
@@ -272,6 +381,7 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
272
381
|
continue;
|
|
273
382
|
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'cert'));
|
|
274
383
|
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'keys'));
|
|
384
|
+
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'private'));
|
|
275
385
|
}
|
|
276
386
|
}
|
|
277
387
|
catch { }
|
|
@@ -2,8 +2,7 @@ import { ensureDir } from '../../utils/atomic-write.js';
|
|
|
2
2
|
import { logger } from '../../utils/logger.js';
|
|
3
3
|
import { encodePath } from '../../utils/cross-platform.js';
|
|
4
4
|
import { chatDirPath, generateSessionId, formatTimestamp, atomicWriteJson, atomicWriteText, appendJsonl, readJsonFile, readLastJsonlLine, readAllJsonlLines, scanChatDirs, scanMetaFiles, ensureChatDir, readThreadIndex, writeThreadIndex, } from './session-fs-store.js';
|
|
5
|
-
import { sessionToFile, fileToSession } from './session-mapper.js';
|
|
6
|
-
import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
5
|
+
import { sessionToFile, fileToSession, formatSessionKey, DEFAULT_THREAD_ID } from './session-mapper.js';
|
|
7
6
|
import { tryParseChannelKey } from '../channel-loader.js';
|
|
8
7
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
9
8
|
import path from 'path';
|
|
@@ -11,7 +10,7 @@ import fs from 'fs';
|
|
|
11
10
|
import os from 'os';
|
|
12
11
|
function newSessionStableMetadata(chatType, ...sources) {
|
|
13
12
|
const keys = chatType === 'group'
|
|
14
|
-
? ['channelKey', 'groupId', 'groupName', '
|
|
13
|
+
? ['channelKey', 'groupId', 'groupName', 'mentionMode']
|
|
15
14
|
: ['channelKey', 'peerId', 'peerName', 'peerType'];
|
|
16
15
|
const metadata = {};
|
|
17
16
|
for (const source of sources) {
|
|
@@ -597,6 +596,14 @@ export class SessionManager {
|
|
|
597
596
|
mutated = true;
|
|
598
597
|
}
|
|
599
598
|
}
|
|
599
|
+
if (chatType === 'group' && metadata?.groupId) {
|
|
600
|
+
if (!session.metadata)
|
|
601
|
+
session.metadata = {};
|
|
602
|
+
if (session.metadata.groupId !== metadata.groupId) {
|
|
603
|
+
session.metadata.groupId = metadata.groupId;
|
|
604
|
+
mutated = true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
600
607
|
if (metadata?.peerType && session.metadata?.peerType !== metadata.peerType) {
|
|
601
608
|
if (!session.metadata)
|
|
602
609
|
session.metadata = {};
|
|
@@ -636,6 +643,9 @@ export class SessionManager {
|
|
|
636
643
|
if (metadata?.peerType) {
|
|
637
644
|
session.metadata.peerType = metadata.peerType;
|
|
638
645
|
}
|
|
646
|
+
if (chatType === 'group' && metadata?.groupId) {
|
|
647
|
+
session.metadata.groupId = metadata.groupId;
|
|
648
|
+
}
|
|
639
649
|
this.persistSession(session, 'sync');
|
|
640
650
|
return session;
|
|
641
651
|
}
|
|
@@ -702,11 +712,20 @@ export class SessionManager {
|
|
|
702
712
|
const existing = this.readMetaLatest(metaPath);
|
|
703
713
|
if (existing) {
|
|
704
714
|
const validSessionId = this.validateSessionFile(existing);
|
|
715
|
+
let mutated = false;
|
|
705
716
|
if (metadata) {
|
|
706
717
|
const creatorPeerId = existing.metadata?.peerId;
|
|
707
718
|
existing.metadata = { ...(existing.metadata || {}), ...metadata };
|
|
708
719
|
if (creatorPeerId && existing.metadata)
|
|
709
720
|
existing.metadata.peerId = creatorPeerId;
|
|
721
|
+
mutated = true;
|
|
722
|
+
}
|
|
723
|
+
if (chatType && existing.chatType !== chatType) {
|
|
724
|
+
logger.info(`[SessionManager] Updating thread chatType for session ${existing.id}: ${existing.chatType} -> ${chatType}`);
|
|
725
|
+
existing.chatType = chatType;
|
|
726
|
+
mutated = true;
|
|
727
|
+
}
|
|
728
|
+
if (mutated) {
|
|
710
729
|
this.persistSession(existing, 'none');
|
|
711
730
|
}
|
|
712
731
|
return { ...existing, agentSessionId: validSessionId };
|
|
@@ -1135,6 +1154,10 @@ export class SessionManager {
|
|
|
1135
1154
|
if (!baseagent) {
|
|
1136
1155
|
throw new Error(`[SessionManager] createNewSession: baseagent is empty for channel=${channel} channelId=${channelId}`);
|
|
1137
1156
|
}
|
|
1157
|
+
const metadata = newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata);
|
|
1158
|
+
if (identityMetadata?.sessionBoundaryOperationId) {
|
|
1159
|
+
metadata.sessionBoundaryOperationId = identityMetadata.sessionBoundaryOperationId;
|
|
1160
|
+
}
|
|
1138
1161
|
const session = {
|
|
1139
1162
|
id: generateSessionId(),
|
|
1140
1163
|
channel,
|
|
@@ -1146,7 +1169,7 @@ export class SessionManager {
|
|
|
1146
1169
|
baseagent,
|
|
1147
1170
|
sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
|
|
1148
1171
|
chatType: inheritedChatType,
|
|
1149
|
-
metadata
|
|
1172
|
+
metadata,
|
|
1150
1173
|
name: name || '默认会话',
|
|
1151
1174
|
createdAt: Date.now(),
|
|
1152
1175
|
updatedAt: Date.now(),
|
|
@@ -1163,6 +1186,21 @@ export class SessionManager {
|
|
|
1163
1186
|
});
|
|
1164
1187
|
return session;
|
|
1165
1188
|
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Create and activate one fresh main session for a durable system operation.
|
|
1191
|
+
* Replaying the same operation returns the already-created session.
|
|
1192
|
+
*/
|
|
1193
|
+
async ensureNewMainSessionForOperation(opts) {
|
|
1194
|
+
const active = this.getActiveSessionSync(opts.channel, opts.channelId);
|
|
1195
|
+
if (active?.metadata?.sessionBoundaryOperationId === opts.operationId) {
|
|
1196
|
+
return { session: active, created: false };
|
|
1197
|
+
}
|
|
1198
|
+
const session = await this.createNewSession(opts.channel, opts.channelId, opts.projectPath, undefined, opts.baseagent, {
|
|
1199
|
+
...(opts.identityMetadata ?? {}),
|
|
1200
|
+
sessionBoundaryOperationId: opts.operationId,
|
|
1201
|
+
});
|
|
1202
|
+
return { session, created: true };
|
|
1203
|
+
}
|
|
1166
1204
|
async createForkedSession(sourceSession, forkedAgentSessionId, name) {
|
|
1167
1205
|
const channelType = sourceSession.channelType || sourceSession.channel;
|
|
1168
1206
|
const threadId = sourceSession.threadId || '';
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { formatTimestamp } from './session-fs-store.js';
|
|
2
|
-
|
|
2
|
+
/** Default topic component used by the stable session routing key. */
|
|
3
|
+
export const DEFAULT_THREAD_ID = 'main';
|
|
4
|
+
export function formatSessionKey(channelType, channelId, threadId) {
|
|
5
|
+
const tid = threadId || DEFAULT_THREAD_ID;
|
|
6
|
+
return `${channelType}#${encodeURIComponent(channelId)}#${encodeURIComponent(tid)}`;
|
|
7
|
+
}
|
|
8
|
+
export function parseSessionKey(key) {
|
|
9
|
+
const first = key.indexOf('#');
|
|
10
|
+
if (first <= 0)
|
|
11
|
+
throw new Error(`Invalid session key: ${key}`);
|
|
12
|
+
const rest = key.slice(first + 1);
|
|
13
|
+
const second = rest.indexOf('#');
|
|
14
|
+
if (second <= 0)
|
|
15
|
+
throw new Error(`Invalid session key (missing threadId): ${key}`);
|
|
16
|
+
return {
|
|
17
|
+
channelType: key.slice(0, first),
|
|
18
|
+
channelId: decodeURIComponent(rest.slice(0, second)),
|
|
19
|
+
threadId: decodeURIComponent(rest.slice(second + 1)),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
3
22
|
export function sessionToFile(session) {
|
|
4
23
|
const metadata = {};
|
|
5
24
|
if (session.metadata) {
|
package/dist/eck/detect.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
|
|
3
|
+
export const BASEAGENT_CAPS = {
|
|
4
|
+
'claude-code': {
|
|
5
|
+
autoLoadsRules: true,
|
|
6
|
+
supportsSystemPrompt: true,
|
|
7
|
+
},
|
|
8
|
+
'claude': {
|
|
9
|
+
autoLoadsRules: true,
|
|
10
|
+
supportsSystemPrompt: true,
|
|
11
|
+
},
|
|
12
|
+
'codex': {
|
|
13
|
+
autoLoadsRules: false,
|
|
14
|
+
supportsSystemPrompt: true,
|
|
15
|
+
},
|
|
16
|
+
'gemini': {
|
|
17
|
+
autoLoadsRules: false,
|
|
18
|
+
supportsSystemPrompt: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
4
21
|
const MAX_DEPTH = 5;
|
|
5
22
|
export function resolveEckInjection(agentConfig, projectPath, kitsRulesPath) {
|
|
6
23
|
const caps = BASEAGENT_CAPS[agentConfig.baseAgent]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
3
|
import { TextDecoder } from 'util';
|
|
5
4
|
import { getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
|
|
5
|
+
import { downloadGroupFsBytes, GroupFsDownloadError } from '../aun/group-fs-download.js';
|
|
6
6
|
import { checkGroupIndex as checkGroupIndexRpc, getGroupIndex as getGroupIndexRpc } from '../aun/msg/group-index.js';
|
|
7
7
|
import { agentRelationsDir } from '../paths.js';
|
|
8
8
|
import { formatPeerKey } from '../core/relation/peer-identity.js';
|
|
@@ -220,7 +220,9 @@ function lazyGroupClient(selfAid) {
|
|
|
220
220
|
client: async () => {
|
|
221
221
|
if (client)
|
|
222
222
|
return client;
|
|
223
|
-
|
|
223
|
+
// This short-lived RPC client must never share the daemon's gateway
|
|
224
|
+
// cursor. A short connection can perform a background pull on connect.
|
|
225
|
+
store = await getAidStore({ slotId: SLOT.groupRules });
|
|
224
226
|
client = await loadClient(store, selfAid);
|
|
225
227
|
await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
226
228
|
return client;
|
|
@@ -242,39 +244,15 @@ function lazyGroupClient(selfAid) {
|
|
|
242
244
|
};
|
|
243
245
|
}
|
|
244
246
|
async function downloadGroupFileBytes(client, remoteRef) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
throw statusError('unreadable', 'group.fs.create_download_ticket returned invalid response');
|
|
248
|
-
}
|
|
249
|
-
const downloadUrl = stringField(ticket, ['download_url', 'url']);
|
|
250
|
-
if (!downloadUrl) {
|
|
251
|
-
throw statusError('unreadable', 'group.fs.create_download_ticket did not return download_url');
|
|
252
|
-
}
|
|
253
|
-
const bytes = await client.group.fs.lowlevel.httpGet(downloadUrl, bearerHeaders(client));
|
|
254
|
-
const expectedSha = stringField(ticket, ['sha256']);
|
|
255
|
-
if (expectedSha && sha256Hex(bytes).toLowerCase() !== expectedSha.toLowerCase()) {
|
|
256
|
-
throw statusError('unreadable', 'download hash verification failed');
|
|
247
|
+
try {
|
|
248
|
+
return (await downloadGroupFsBytes(client, remoteRef)).bytes;
|
|
257
249
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
function bearerHeaders(client) {
|
|
264
|
-
const token = accessTokenFromClient(client);
|
|
265
|
-
return token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
266
|
-
}
|
|
267
|
-
function accessTokenFromClient(client) {
|
|
268
|
-
const anyClient = client;
|
|
269
|
-
if (typeof anyClient.getAccessToken === 'function') {
|
|
270
|
-
const token = stringValue(anyClient.getAccessToken());
|
|
271
|
-
if (token)
|
|
272
|
-
return token;
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error instanceof GroupFsDownloadError) {
|
|
252
|
+
throw statusError('unreadable', error.message);
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
273
255
|
}
|
|
274
|
-
return stringValue(anyClient.accessToken)
|
|
275
|
-
|| stringValue(anyClient.access_token)
|
|
276
|
-
|| stringValue(anyClient._identity?.access_token)
|
|
277
|
-
|| stringValue(anyClient._sessionParams?.access_token);
|
|
278
256
|
}
|
|
279
257
|
function assertUtf8(bytes) {
|
|
280
258
|
new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
@@ -302,6 +280,10 @@ function classifyError(e) {
|
|
|
302
280
|
if (e instanceof StatusError)
|
|
303
281
|
return e.status;
|
|
304
282
|
const msg = errorMessage(e).toLowerCase();
|
|
283
|
+
// A peer cache miss is a signer/identity lookup failure, not a missing
|
|
284
|
+
// rules file.
|
|
285
|
+
if (msg.includes('peer not found in cache'))
|
|
286
|
+
return 'unreadable';
|
|
305
287
|
if (msg.includes('forbidden') || msg.includes('unauthorized') || msg.includes('permission'))
|
|
306
288
|
return 'forbidden';
|
|
307
289
|
if (msg.includes('not found') || msg.includes('does not exist'))
|
|
@@ -314,17 +296,6 @@ function errorMessage(e) {
|
|
|
314
296
|
function isRecord(x) {
|
|
315
297
|
return typeof x === 'object' && x !== null && !Array.isArray(x);
|
|
316
298
|
}
|
|
317
|
-
function stringValue(x) {
|
|
318
|
-
return typeof x === 'string' ? x : '';
|
|
319
|
-
}
|
|
320
|
-
function stringField(obj, keys) {
|
|
321
|
-
for (const k of keys) {
|
|
322
|
-
const v = obj[k];
|
|
323
|
-
if (typeof v === 'string')
|
|
324
|
-
return v;
|
|
325
|
-
}
|
|
326
|
-
return undefined;
|
|
327
|
-
}
|
|
328
299
|
function numberField(obj, keys) {
|
|
329
300
|
for (const k of keys) {
|
|
330
301
|
const v = obj[k];
|
|
@@ -55,6 +55,10 @@ function loadAndMergeManifest(filename) {
|
|
|
55
55
|
for (const s of base.sections)
|
|
56
56
|
merged.set(s.id, { ...s });
|
|
57
57
|
for (const s of override.sections) {
|
|
58
|
+
if (s.id === 'relation-group-profile' && merged.has('peer-profile')) {
|
|
59
|
+
logger.warn('[ManifestEngine] Ignoring deprecated patch section "relation-group-profile"; use "peer-profile" instead');
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
58
62
|
const existing = merged.get(s.id);
|
|
59
63
|
merged.set(s.id, existing ? { ...existing, ...s } : s);
|
|
60
64
|
}
|
|
@@ -320,15 +324,35 @@ function resolvePath(rawPath, vars) {
|
|
|
320
324
|
const r = resolvePathWithDiag(rawPath, vars);
|
|
321
325
|
return r.status === 'ok' ? r.resolved : null;
|
|
322
326
|
}
|
|
327
|
+
function isWithinDirectory(filePath, directory) {
|
|
328
|
+
const relative = path.relative(path.resolve(directory), path.resolve(filePath));
|
|
329
|
+
return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* 随包发布的 kits 内容只在 reload/重启后刷新;personal/relations 及用户自定义
|
|
333
|
+
* manifest 路径属于运行时数据,必须用 mtime 检查,否则同一 daemon 内会长期陈旧。
|
|
334
|
+
*/
|
|
335
|
+
function sectionCacheOptions(resolvedPath, vars) {
|
|
336
|
+
if (isWithinDirectory(resolvedPath, kitsDir())) {
|
|
337
|
+
return { policy: 'on-reload', group: 'kits' };
|
|
338
|
+
}
|
|
339
|
+
const personalDir = typeof vars.PERSONAL_DIR === 'string' ? vars.PERSONAL_DIR : null;
|
|
340
|
+
const selfAid = typeof vars.selfAid === 'string' ? vars.selfAid : null;
|
|
341
|
+
if (personalDir && selfAid && isWithinDirectory(resolvedPath, personalDir)) {
|
|
342
|
+
return { policy: 'mtime', group: `agent-files:${selfAid}` };
|
|
343
|
+
}
|
|
344
|
+
return { policy: 'mtime', group: 'eck-runtime' };
|
|
345
|
+
}
|
|
323
346
|
/** 加载 loop 的 child 子模板文件内容(路径解析 + fileCache)。找不到返回 null。 */
|
|
324
347
|
export function loadChildTemplate(childFile, vars) {
|
|
325
348
|
const resolved = resolvePath(childFile, vars);
|
|
326
349
|
if (!resolved)
|
|
327
350
|
return null;
|
|
328
|
-
return fileCache.getText(resolved,
|
|
351
|
+
return fileCache.getText(resolved, sectionCacheOptions(resolved, vars));
|
|
329
352
|
}
|
|
330
353
|
/**
|
|
331
|
-
* 返回 [filePath, rawContent][]
|
|
354
|
+
* 返回 [filePath, rawContent][]。kits 文件跨 session 缓存到 reload;运行时文件
|
|
355
|
+
* (personal/relations/用户自定义路径)每次按 mtime 检查。
|
|
332
356
|
* 若传入 overflowOut 且目录段触发单目录限额(maxFiles/maxBytes),
|
|
333
357
|
* 会把溢出信息写入 overflowOut.value(不传则不启用限额收集,行为同旧版但仍应用默认上限)。
|
|
334
358
|
*/
|
|
@@ -343,7 +367,7 @@ export function loadSectionFiles(section, vars, sessionCache, overflowOut) {
|
|
|
343
367
|
return [];
|
|
344
368
|
const maxFiles = section.maxFiles ?? DIR_MAX_FILES;
|
|
345
369
|
const maxBytes = section.maxBytes ?? DIR_MAX_BYTES;
|
|
346
|
-
const { files, overflow } = readDirectoryFiles(resolved, section.pattern, maxFiles, maxBytes);
|
|
370
|
+
const { files, overflow } = readDirectoryFiles(resolved, section.pattern, maxFiles, maxBytes, vars);
|
|
347
371
|
if (overflow && overflowOut)
|
|
348
372
|
overflowOut.value = overflow;
|
|
349
373
|
return files.map(([name, content]) => [path.join(resolved, name), content]);
|
|
@@ -351,31 +375,33 @@ export function loadSectionFiles(section, vars, sessionCache, overflowOut) {
|
|
|
351
375
|
return [];
|
|
352
376
|
}
|
|
353
377
|
function loadFileSection(filePath, vars, sessionCache) {
|
|
354
|
-
void sessionCache; // 内容跨 session
|
|
378
|
+
void sessionCache; // 内容跨 session 共享,统一走全局 fileCache
|
|
355
379
|
const resolved = resolvePath(filePath, vars);
|
|
356
380
|
if (!resolved)
|
|
357
381
|
return null;
|
|
358
|
-
|
|
359
|
-
// 不再按 session 重复缓存同一文件内容。
|
|
360
|
-
const content = fileCache.getText(resolved, { policy: 'on-reload', group: 'kits' });
|
|
382
|
+
const content = fileCache.getText(resolved, sectionCacheOptions(resolved, vars));
|
|
361
383
|
return content === null ? null : [resolved, content];
|
|
362
384
|
}
|
|
363
385
|
/**
|
|
364
386
|
* 读取目录下匹配文件,受单目录限额(maxFiles/maxBytes)约束。
|
|
365
387
|
* 返回已加载文件 + 溢出信息(超限时)。按 fileCache 缓存目录列表与文件内容。
|
|
366
388
|
*/
|
|
367
|
-
function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes) {
|
|
389
|
+
function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes, vars) {
|
|
368
390
|
const glob = pattern || '*.md';
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const names = fileCache.get(`${dirPath} ${glob}`, () => {
|
|
391
|
+
const cacheOptions = sectionCacheOptions(dirPath, vars);
|
|
392
|
+
const readNames = () => {
|
|
372
393
|
try {
|
|
373
394
|
return fs.readdirSync(dirPath).filter(f => matchGlob(f, glob)).sort();
|
|
374
395
|
}
|
|
375
396
|
catch {
|
|
376
397
|
return [];
|
|
377
398
|
}
|
|
378
|
-
}
|
|
399
|
+
};
|
|
400
|
+
// synthetic cache key 无法用文件 mtime 门控。kits 目录结构只在 reload 后刷新;
|
|
401
|
+
// 运行时目录直接重扫,确保新增/删除文件在同一 session 内可见。
|
|
402
|
+
const names = cacheOptions.policy === 'on-reload'
|
|
403
|
+
? fileCache.get(`${dirPath} ${glob}`, readNames, cacheOptions)
|
|
404
|
+
: readNames();
|
|
379
405
|
const out = [];
|
|
380
406
|
let usedBytes = 0;
|
|
381
407
|
let overflow;
|
|
@@ -387,7 +413,7 @@ function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes) {
|
|
|
387
413
|
break;
|
|
388
414
|
}
|
|
389
415
|
const fp = path.join(dirPath, f);
|
|
390
|
-
const content = fileCache.getText(fp,
|
|
416
|
+
const content = fileCache.getText(fp, sectionCacheOptions(fp, vars));
|
|
391
417
|
if (content === null)
|
|
392
418
|
continue;
|
|
393
419
|
// 字节限额(至少加载一个文件,避免单个超大文件导致空目录)
|