evolcore 0.0.3 → 0.0.4
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 +64 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +7 -5
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +249 -24
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-manager.js +25 -3
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +7 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- package/dist/channels/wecom-state.js +0 -191
|
@@ -55,10 +55,10 @@ function structuredApprovalAuditSummary(input) {
|
|
|
55
55
|
.slice(0, 16);
|
|
56
56
|
return fields.length > 0 ? `input_fields=${fields.join(',')}` : 'input_withheld';
|
|
57
57
|
}
|
|
58
|
-
function requiredApproverRole(policy
|
|
58
|
+
function requiredApproverRole(policy) {
|
|
59
59
|
if (policy === 'requester')
|
|
60
60
|
return 'requester';
|
|
61
|
-
if (policy === 'agent_owner'
|
|
61
|
+
if (policy === 'agent_owner')
|
|
62
62
|
return 'owner';
|
|
63
63
|
return 'manager';
|
|
64
64
|
}
|
|
@@ -148,13 +148,20 @@ export function planApprovalRoute(challenge, context) {
|
|
|
148
148
|
approverPrincipalId: actor.principalId,
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
const handoffCandidates = challenge.approverPolicy === 'agent_manager'
|
|
152
|
+
? Array.from(new Set([...admins, ...owners]))
|
|
153
|
+
: owners;
|
|
154
|
+
if (handoffCandidates.length === 0) {
|
|
155
|
+
return {
|
|
156
|
+
kind: 'unavailable',
|
|
157
|
+
reason: challenge.approverPolicy === 'agent_manager'
|
|
158
|
+
? 'no_agent_manager_configured'
|
|
159
|
+
: 'no_agent_owner_configured',
|
|
160
|
+
};
|
|
153
161
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
const approverId = owners[0];
|
|
162
|
+
// Manager handoffs prefer the configured admin pool for routine approvals;
|
|
163
|
+
// owner-only policies remain restricted to owners. Only one card is sent.
|
|
164
|
+
const approverId = handoffCandidates[0];
|
|
158
165
|
if (!approverId) {
|
|
159
166
|
return { kind: 'unavailable', reason: 'no_aun_owner_available' };
|
|
160
167
|
}
|
|
@@ -505,7 +512,7 @@ export class PermissionGateway {
|
|
|
505
512
|
grantScope,
|
|
506
513
|
approverPolicy: challenge.approverPolicy,
|
|
507
514
|
approvalRouteKind: route.kind,
|
|
508
|
-
requiredApproverRole: requiredApproverRole(challenge.approverPolicy
|
|
515
|
+
requiredApproverRole: requiredApproverRole(challenge.approverPolicy),
|
|
509
516
|
approverOperatorId: route.approverOperatorId,
|
|
510
517
|
approverChannelKey: route.approverChannelKey,
|
|
511
518
|
approverPrincipalId: route.approverPrincipalId,
|
|
@@ -763,7 +770,7 @@ export class PermissionGateway {
|
|
|
763
770
|
grantScope,
|
|
764
771
|
approverPolicy: challenge.approverPolicy,
|
|
765
772
|
approvalRouteKind: route.kind,
|
|
766
|
-
requiredApproverRole: requiredApproverRole(challenge.approverPolicy
|
|
773
|
+
requiredApproverRole: requiredApproverRole(challenge.approverPolicy),
|
|
767
774
|
approverOperatorId: route.approverOperatorId,
|
|
768
775
|
approverChannelKey: route.approverChannelKey,
|
|
769
776
|
approverPrincipalId: route.approverPrincipalId,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
const MAX_HEAD_LINES = 10_000;
|
|
1
|
+
const HEAD_EXECUTABLES = new Set(['head', '/bin/head', '/usr/bin/head']);
|
|
3
2
|
const SHELL_UNQUOTED_META = new Set([
|
|
4
3
|
';', '&', '|', '`', '<', '>', '$', '(', ')', '*', '?', '[', ']', '{', '}', '#',
|
|
5
4
|
]);
|
|
@@ -11,16 +10,36 @@ function trimShellSpacing(value) {
|
|
|
11
10
|
function isShellSpacing(value) {
|
|
12
11
|
return value === ' ' || value === '\t';
|
|
13
12
|
}
|
|
13
|
+
function isLiteralArgBoundary(value) {
|
|
14
|
+
return value === undefined || isShellSpacing(value);
|
|
15
|
+
}
|
|
16
|
+
function isShellParameterExpansionStart(value) {
|
|
17
|
+
return value === '(' || value === '{' || !!value && /[A-Za-z0-9_@*#?$!\-]/.test(value);
|
|
18
|
+
}
|
|
19
|
+
function doubleQuotedIssueAt(input, index) {
|
|
20
|
+
const char = input[index];
|
|
21
|
+
if (char === '`' || (char === '$' && isShellParameterExpansionStart(input[index + 1]))) {
|
|
22
|
+
return 'unsafe-expansion';
|
|
23
|
+
}
|
|
24
|
+
if (char !== '\\')
|
|
25
|
+
return null;
|
|
26
|
+
const next = input[index + 1];
|
|
27
|
+
if (next === undefined)
|
|
28
|
+
return 'invalid-quote';
|
|
29
|
+
return next === '\r' || next === '\n' ? 'structural-newline' : null;
|
|
30
|
+
}
|
|
14
31
|
/**
|
|
15
32
|
* Parse the deliberately small shell subset accepted by the privileged EC
|
|
16
33
|
* command path. The returned argv must describe one literal invocation: no
|
|
17
34
|
* expansion, glob, pipeline, command substitution, redirection, or comment.
|
|
18
35
|
* Anything outside this subset is never granted the privileged EC path.
|
|
19
36
|
*/
|
|
20
|
-
export function
|
|
37
|
+
export function parseLiteralShellCommand(command) {
|
|
21
38
|
const input = trimShellSpacing(command);
|
|
22
|
-
if (!input
|
|
23
|
-
return
|
|
39
|
+
if (!input)
|
|
40
|
+
return { ok: false, issue: 'empty-command' };
|
|
41
|
+
if (input.includes('\0'))
|
|
42
|
+
return { ok: false, issue: 'invalid-control-char' };
|
|
24
43
|
const argv = [];
|
|
25
44
|
let token = '';
|
|
26
45
|
let tokenStarted = false;
|
|
@@ -35,26 +54,28 @@ export function parseLiteralShellArgv(command) {
|
|
|
35
54
|
for (let index = 0; index < input.length; index++) {
|
|
36
55
|
const char = input[index];
|
|
37
56
|
if (quote === 'single') {
|
|
38
|
-
if (char === "'")
|
|
57
|
+
if (char === "'") {
|
|
58
|
+
if (!isLiteralArgBoundary(input[index + 1]))
|
|
59
|
+
return { ok: false, issue: 'invalid-quote' };
|
|
39
60
|
quote = null;
|
|
61
|
+
}
|
|
40
62
|
else
|
|
41
63
|
token += char;
|
|
42
64
|
continue;
|
|
43
65
|
}
|
|
44
66
|
if (quote === 'double') {
|
|
45
67
|
if (char === '"') {
|
|
68
|
+
if (!isLiteralArgBoundary(input[index + 1]))
|
|
69
|
+
return { ok: false, issue: 'invalid-quote' };
|
|
46
70
|
quote = null;
|
|
47
71
|
continue;
|
|
48
72
|
}
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
const issue = doubleQuotedIssueAt(input, index);
|
|
74
|
+
if (issue)
|
|
75
|
+
return { ok: false, issue };
|
|
51
76
|
if (char === '\\') {
|
|
52
77
|
const next = input[++index];
|
|
53
|
-
|
|
54
|
-
return null;
|
|
55
|
-
if (next === '$' || next === '`')
|
|
56
|
-
return null;
|
|
57
|
-
token += ['"', '\\'].includes(next) ? next : `\\${next}`;
|
|
78
|
+
token += ['"', '\\', '$', '`'].includes(next) ? next : `\\${next}`;
|
|
58
79
|
continue;
|
|
59
80
|
}
|
|
60
81
|
token += char;
|
|
@@ -70,8 +91,11 @@ export function parseLiteralShellArgv(command) {
|
|
|
70
91
|
// includes NBSP, line/paragraph separators, VT and FF, which the shell
|
|
71
92
|
// keeps inside the executable name; accepting those would misclassify a
|
|
72
93
|
// different executable as `ec`.
|
|
73
|
-
if (char === '\r' || char === '\n'
|
|
74
|
-
return
|
|
94
|
+
if (char === '\r' || char === '\n')
|
|
95
|
+
return { ok: false, issue: 'structural-newline' };
|
|
96
|
+
if (/\s/u.test(char) || char.charCodeAt(0) < 0x20 || char === '\x7f') {
|
|
97
|
+
return { ok: false, issue: 'invalid-control-char' };
|
|
98
|
+
}
|
|
75
99
|
if (char === "'") {
|
|
76
100
|
tokenStarted = true;
|
|
77
101
|
quote = 'single';
|
|
@@ -84,8 +108,10 @@ export function parseLiteralShellArgv(command) {
|
|
|
84
108
|
}
|
|
85
109
|
if (char === '\\') {
|
|
86
110
|
const next = input[++index];
|
|
87
|
-
if (next === undefined
|
|
88
|
-
return
|
|
111
|
+
if (next === undefined)
|
|
112
|
+
return { ok: false, issue: 'invalid-quote' };
|
|
113
|
+
if (next === '\r' || next === '\n')
|
|
114
|
+
return { ok: false, issue: 'structural-newline' };
|
|
89
115
|
tokenStarted = true;
|
|
90
116
|
token += next;
|
|
91
117
|
continue;
|
|
@@ -94,14 +120,20 @@ export function parseLiteralShellArgv(command) {
|
|
|
94
120
|
// peer keys such as `aun#alice` are ordinary literal arguments.
|
|
95
121
|
if ((SHELL_UNQUOTED_META.has(char) && (char !== '#' || !tokenStarted))
|
|
96
122
|
|| (char === '~' && !tokenStarted))
|
|
97
|
-
return
|
|
123
|
+
return { ok: false, issue: 'shell-composition' };
|
|
98
124
|
tokenStarted = true;
|
|
99
125
|
token += char;
|
|
100
126
|
}
|
|
101
127
|
if (quote)
|
|
102
|
-
return
|
|
128
|
+
return { ok: false, issue: 'invalid-quote' };
|
|
103
129
|
finishToken();
|
|
104
|
-
return argv.length > 0
|
|
130
|
+
return argv.length > 0
|
|
131
|
+
? { ok: true, argv }
|
|
132
|
+
: { ok: false, issue: 'empty-command' };
|
|
133
|
+
}
|
|
134
|
+
export function parseLiteralShellArgv(command) {
|
|
135
|
+
const result = parseLiteralShellCommand(command);
|
|
136
|
+
return result.ok ? result.argv : null;
|
|
105
137
|
}
|
|
106
138
|
function scanBoundedOutputOperators(command) {
|
|
107
139
|
const operators = [];
|
|
@@ -118,12 +150,10 @@ function scanBoundedOutputOperators(command) {
|
|
|
118
150
|
quote = null;
|
|
119
151
|
continue;
|
|
120
152
|
}
|
|
121
|
-
if (
|
|
153
|
+
if (doubleQuotedIssueAt(command, index))
|
|
122
154
|
return null;
|
|
123
155
|
if (char === '\\') {
|
|
124
|
-
|
|
125
|
-
if (next === undefined || next === '\r' || next === '\n' || next === '$' || next === '`')
|
|
126
|
-
return null;
|
|
156
|
+
index++;
|
|
127
157
|
}
|
|
128
158
|
continue;
|
|
129
159
|
}
|
|
@@ -171,21 +201,46 @@ function scanBoundedOutputOperators(command) {
|
|
|
171
201
|
}
|
|
172
202
|
return quote ? null : operators;
|
|
173
203
|
}
|
|
174
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Accept `head` as a generic, trusted stdout filter without mirroring its
|
|
206
|
+
* option grammar here. Non-option operands are the security boundary: they
|
|
207
|
+
* could make `head` read a file instead of the EC process' stdout.
|
|
208
|
+
*
|
|
209
|
+
* GNU/POSIX head only uses a separate value for -n/-c (and their long forms).
|
|
210
|
+
* All other dash-prefixed arguments are safe to leave to head itself; unknown
|
|
211
|
+
* or malformed options simply make head fail.
|
|
212
|
+
*/
|
|
213
|
+
function parseStdinOnlyHeadArgs(segment) {
|
|
175
214
|
const argv = parseLiteralShellArgv(segment);
|
|
176
|
-
if (!argv || (argv[0]
|
|
177
|
-
return null;
|
|
178
|
-
let value;
|
|
179
|
-
if (argv.length === 1)
|
|
180
|
-
value = String(DEFAULT_HEAD_LINES);
|
|
181
|
-
else if (argv.length === 2 && /^-\d+$/.test(argv[1]))
|
|
182
|
-
value = argv[1].slice(1);
|
|
183
|
-
else if (argv.length === 3 && argv[1] === '-n' && /^\d+$/.test(argv[2]))
|
|
184
|
-
value = argv[2];
|
|
185
|
-
else
|
|
215
|
+
if (!argv || !HEAD_EXECUTABLES.has(argv[0]))
|
|
186
216
|
return null;
|
|
187
|
-
const
|
|
188
|
-
|
|
217
|
+
const args = argv.slice(1);
|
|
218
|
+
let optionsEnded = false;
|
|
219
|
+
let expectsOptionValue = false;
|
|
220
|
+
for (const arg of args) {
|
|
221
|
+
if (expectsOptionValue) {
|
|
222
|
+
expectsOptionValue = false;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (optionsEnded) {
|
|
226
|
+
if (arg !== '-')
|
|
227
|
+
return null;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (arg === '--') {
|
|
231
|
+
optionsEnded = true;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (arg === '-')
|
|
235
|
+
continue;
|
|
236
|
+
if (!arg.startsWith('-'))
|
|
237
|
+
return null;
|
|
238
|
+
if (arg === '-n' || arg === '-c' || arg === '--lines' || arg === '--bytes') {
|
|
239
|
+
expectsOptionValue = true;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return args;
|
|
189
244
|
}
|
|
190
245
|
/**
|
|
191
246
|
* Parse the only shell wrapper allowed around a privileged literal process:
|
|
@@ -234,13 +289,13 @@ export function parseBoundedOutputShellCommand(command) {
|
|
|
234
289
|
else {
|
|
235
290
|
redirect = first;
|
|
236
291
|
}
|
|
237
|
-
let
|
|
292
|
+
let headArgs;
|
|
238
293
|
if (pipe) {
|
|
239
294
|
const headEnd = redirect?.start ?? input.length;
|
|
240
|
-
const
|
|
241
|
-
if (
|
|
295
|
+
const parsedArgs = parseStdinOnlyHeadArgs(trimShellSpacing(input.slice(pipe.end, headEnd)));
|
|
296
|
+
if (parsedArgs === null)
|
|
242
297
|
return null;
|
|
243
|
-
|
|
298
|
+
headArgs = parsedArgs;
|
|
244
299
|
}
|
|
245
300
|
let outputPath;
|
|
246
301
|
if (redirect) {
|
|
@@ -249,7 +304,7 @@ export function parseBoundedOutputShellCommand(command) {
|
|
|
249
304
|
return null;
|
|
250
305
|
outputPath = outputArgv[0];
|
|
251
306
|
}
|
|
252
|
-
return { argv, mergeStderr, ...(
|
|
307
|
+
return { argv, mergeStderr, ...(headArgs ? { headArgs } : {}), ...(outputPath ? { outputPath } : {}) };
|
|
253
308
|
}
|
|
254
309
|
/**
|
|
255
310
|
* Classify only the shell boundary around an EC process invocation. Runner
|
|
@@ -257,9 +312,9 @@ export function parseBoundedOutputShellCommand(command) {
|
|
|
257
312
|
* invocation may leave the sandbox, while shell composition may not.
|
|
258
313
|
*/
|
|
259
314
|
export function classifyEvolcoreShellCommand(command) {
|
|
260
|
-
const
|
|
261
|
-
if (
|
|
262
|
-
return
|
|
315
|
+
const literal = parseLiteralShellCommand(command);
|
|
316
|
+
if (literal.ok) {
|
|
317
|
+
return literal.argv[0] === 'ec'
|
|
263
318
|
? { kind: 'literal' }
|
|
264
319
|
: { kind: 'none' };
|
|
265
320
|
}
|
|
@@ -268,7 +323,7 @@ export function classifyEvolcoreShellCommand(command) {
|
|
|
268
323
|
return { kind: 'bounded-output', command: boundedOutput };
|
|
269
324
|
}
|
|
270
325
|
return EVOLCORE_COMMAND_POSITION_RE.test(command)
|
|
271
|
-
? { kind: 'composite' }
|
|
326
|
+
? { kind: 'composite', issue: literal.issue }
|
|
272
327
|
: { kind: 'none' };
|
|
273
328
|
}
|
|
274
329
|
function isDeterministicFilePath(value) {
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { logger } from '../../utils/logger.js';
|
|
5
5
|
import { resolveRoot } from '../../paths.js';
|
|
6
|
-
import { containsHClassReference, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, } from '../protected-paths.js';
|
|
6
|
+
import { containsHClassReference, containsLClassReference, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, } from '../protected-paths.js';
|
|
7
7
|
import { classifyEvolcoreShellCommand, parseBoundedOutputShellCommand, parseLiteralShellArgv, } from './ec-command-parser.js';
|
|
8
8
|
// 绝对禁止命令(所有角色、所有权限模式下都禁止,不可授权)
|
|
9
9
|
// 这些是系统级破坏操作,任何情况下都不应该允许
|
|
@@ -18,6 +18,15 @@ const READONLY_SHELL_EXECUTABLES = new Set([
|
|
|
18
18
|
'ls', '/bin/ls', '/usr/bin/ls',
|
|
19
19
|
'cat', '/bin/cat', '/usr/bin/cat',
|
|
20
20
|
]);
|
|
21
|
+
const L_CLASS_READONLY_SHELL_EXECUTABLES = new Set([
|
|
22
|
+
...READONLY_SHELL_EXECUTABLES,
|
|
23
|
+
'grep', '/bin/grep', '/usr/bin/grep',
|
|
24
|
+
'rg', '/bin/rg', '/usr/bin/rg',
|
|
25
|
+
'find', '/bin/find', '/usr/bin/find',
|
|
26
|
+
'sed', '/bin/sed', '/usr/bin/sed',
|
|
27
|
+
]);
|
|
28
|
+
const FIND_MUTATING_PRIMARY_RE = /^-(?:delete|exec|execdir|ok|okdir|fprint|fprint0|fprintf|fls)$/;
|
|
29
|
+
const RG_PROCESS_SPAWNING_OPTION_RE = /^--pre(?:=|$)|^--pre-glob(?:=|$)/;
|
|
21
30
|
// 危险操作(需要用户审批才能执行)
|
|
22
31
|
// 这些操作有合理使用场景,但需要用户明确授权
|
|
23
32
|
const DANGEROUS_PATTERNS = [
|
|
@@ -552,28 +561,50 @@ function collectFileChangePaths(value, output) {
|
|
|
552
561
|
}
|
|
553
562
|
function shellCommandReferencesLClass(command, options) {
|
|
554
563
|
const normalized = command.replace(/\\/g, '/');
|
|
555
|
-
|
|
556
|
-
const protectedRoot = `${root.replace(/\/+$/, '')}/data/triggers`;
|
|
557
|
-
if (normalized.includes(protectedRoot))
|
|
564
|
+
if (containsLClassReference(normalized))
|
|
558
565
|
return true;
|
|
559
|
-
const
|
|
560
|
-
return
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
566
|
+
const argv = parseLiteralShellArgv(command);
|
|
567
|
+
return argv?.slice(1).some(argument => {
|
|
568
|
+
if (argument === '-' || argument === '--' || argument.startsWith('-'))
|
|
569
|
+
return false;
|
|
570
|
+
return isLClassPath(argument, options);
|
|
571
|
+
}) ?? false;
|
|
564
572
|
}
|
|
565
|
-
function
|
|
573
|
+
function isVerifiedLClassReadOnlyShellCommand(command) {
|
|
574
|
+
const argv = parseLiteralShellArgv(command);
|
|
575
|
+
if (!argv || !L_CLASS_READONLY_SHELL_EXECUTABLES.has(argv[0]))
|
|
576
|
+
return false;
|
|
577
|
+
switch (argv[0].replace(/^.*\//, '')) {
|
|
578
|
+
case 'grep':
|
|
579
|
+
// grep never mutates its operands. Shell composition is rejected by the
|
|
580
|
+
// literal parser before this point.
|
|
581
|
+
return true;
|
|
582
|
+
case 'rg':
|
|
583
|
+
// ripgrep's --pre hooks may execute an arbitrary process.
|
|
584
|
+
return !argv.slice(1).some(argument => RG_PROCESS_SPAWNING_OPTION_RE.test(argument));
|
|
585
|
+
case 'find':
|
|
586
|
+
// Keep only the predicates/actions which cannot create, mutate, or
|
|
587
|
+
// execute files. Other find options remain fail-closed.
|
|
588
|
+
return !argv.slice(1).some(argument => FIND_MUTATING_PRIMARY_RE.test(argument));
|
|
589
|
+
case 'sed': {
|
|
590
|
+
// sed can write via -i or a `w` command. Permit only the bounded
|
|
591
|
+
// inspection form used for line-range reads: sed -n 'START[,END]p'.
|
|
592
|
+
const quiet = argv[1] === '-n' || argv[1] === '--quiet' || argv[1] === '--silent';
|
|
593
|
+
const program = quiet ? argv[2] : undefined;
|
|
594
|
+
return !!program
|
|
595
|
+
&& /^(?:\d+(?:,\d+)?|\$)?p$/.test(program)
|
|
596
|
+
// GNU sed accepts options after the script as well. Reject every
|
|
597
|
+
// later option so -i/-e/-f cannot be smuggled into this read form.
|
|
598
|
+
&& !argv.slice(3).some(argument => argument.startsWith('-'));
|
|
599
|
+
}
|
|
600
|
+
default:
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function isUnverifiedLClassShellCommand(command, options) {
|
|
566
605
|
if (!shellCommandReferencesLClass(command, options))
|
|
567
606
|
return false;
|
|
568
|
-
|
|
569
|
-
/(?:^|[;&|]\s*)\s*(?:rm|unlink|rmdir|mv|rename|truncate|touch|chmod|chown|chgrp|mkdir)\b/i,
|
|
570
|
-
/(?:^|[;&|]\s*)\s*(?:sed\s+[^;&|]*\s-i\b|perl\s+[^;&|]*\s-pi\b)/i,
|
|
571
|
-
/(?:^|[;&|]\s*)\s*tee\b/i,
|
|
572
|
-
/\bfind\b[^;&|]*(?:-delete|-exec\s+(?:rm|mv|chmod|chown)\b)/i,
|
|
573
|
-
/\bgit\s+(?:checkout|restore|clean|reset)\b/i,
|
|
574
|
-
/(?:^|[^<])>{1,2}\s*[^;&|\r\n]*data[\\/]triggers(?:[\\/]|$)/i,
|
|
575
|
-
];
|
|
576
|
-
return mutationPatterns.some(pattern => pattern.test(command));
|
|
607
|
+
return !isVerifiedLClassReadOnlyShellCommand(command);
|
|
577
608
|
}
|
|
578
609
|
function requestsFilesystemRoot(value) {
|
|
579
610
|
if (Array.isArray(value))
|
|
@@ -747,12 +778,12 @@ export function checkLClassWrite(toolName, input, context) {
|
|
|
747
778
|
else if (toolName === 'Bash') {
|
|
748
779
|
const command = typeof input.command === 'string' ? input.command.replace(/\\/g, '/') : '';
|
|
749
780
|
const pathOptions = { cwd: context?.projectPath, root: context?.root };
|
|
750
|
-
if (
|
|
751
|
-
logger.warn(`[L-Class Protection]
|
|
781
|
+
if (isUnverifiedLClassShellCommand(command, pathOptions)) {
|
|
782
|
+
logger.warn(`[L-Class Protection] Unverified L-class shell command denied: tool=${toolName} ` +
|
|
752
783
|
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
753
784
|
return {
|
|
754
785
|
behavior: 'deny',
|
|
755
|
-
message: '🔒
|
|
786
|
+
message: '🔒 L-class 路径仅允许单个可验证的只读命令。请拆分为单条 ls/cat/grep/rg/find 或 sed -n,或使用 Read;禁止管道、重定向、变量展开和命令拼接。修改必须通过受控的 EvolCore 命令',
|
|
756
787
|
};
|
|
757
788
|
}
|
|
758
789
|
collectFilesystemWriteGrantPaths(input.additionalPermissions, writeGrantPaths, projectRootWriteSubpaths);
|
|
@@ -791,7 +822,7 @@ export function checkLClassWrite(toolName, input, context) {
|
|
|
791
822
|
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
792
823
|
return {
|
|
793
824
|
behavior: 'deny',
|
|
794
|
-
message: `🔒 L-class 路径允许读取但禁止 Agent 直接修改、删除或移动\n\n文件:${filePath}\n\n💡
|
|
825
|
+
message: `🔒 L-class 路径允许读取但禁止 Agent 直接修改、删除或移动\n\n文件:${filePath}\n\n💡 请使用对应的 EvolCore 受控命令修改`,
|
|
795
826
|
};
|
|
796
827
|
}
|
|
797
828
|
return { behavior: 'allow' };
|
|
@@ -894,7 +925,7 @@ function prepareBoundedOutputInput(input, command, kind, context) {
|
|
|
894
925
|
cwd: path.resolve(context.projectPath),
|
|
895
926
|
argv: command.argv,
|
|
896
927
|
mergeStderr: command.mergeStderr,
|
|
897
|
-
...(command.
|
|
928
|
+
...(command.headArgs ? { headArgs: command.headArgs } : {}),
|
|
898
929
|
...(command.outputPath ? { outputPath: command.outputPath } : {}),
|
|
899
930
|
}), 'utf8').toString('base64url');
|
|
900
931
|
const rewrittenCommand = `${quotePosixShellArg(process.execPath)} ${quotePosixShellArg(helperPath)} ${quotePosixShellArg(payload)}`;
|
|
@@ -916,6 +947,20 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
916
947
|
return prepareBoundedOutputInput(input, ecCommand.command, 'ec', context);
|
|
917
948
|
}
|
|
918
949
|
if (ecCommand.kind === 'composite') {
|
|
950
|
+
if (ecCommand.issue === 'unsafe-expansion') {
|
|
951
|
+
return {
|
|
952
|
+
behavior: 'deny',
|
|
953
|
+
input,
|
|
954
|
+
message: '🔒 EC 双引号正文包含未转义的 Shell 展开;请将反引号写成 \\`,将 $()、${}、$变量中的 $ 写成 \\$。Shell 传给 ec 时会还原为原文字面量',
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (ecCommand.issue === 'invalid-quote') {
|
|
958
|
+
return {
|
|
959
|
+
behavior: 'deny',
|
|
960
|
+
input,
|
|
961
|
+
message: '🔒 EC 命令引号未闭合或存在错误嵌套;正文中的双引号请写成 \\"',
|
|
962
|
+
};
|
|
963
|
+
}
|
|
919
964
|
return {
|
|
920
965
|
behavior: 'deny',
|
|
921
966
|
input,
|
|
@@ -6,7 +6,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
6
6
|
/^agents\/defaults\.json$/,
|
|
7
7
|
/^agents\/[^/]+\/config\.json$/,
|
|
8
8
|
/^agents\/[^/]+\/contact\.json$/,
|
|
9
|
-
/^agents\/[^/]+\/
|
|
9
|
+
/^agents\/[^/]+\/contact-operations\.jsonl$/,
|
|
10
10
|
/^agents\/[^/]+\/relations\/[^/]+\/config\.json$/,
|
|
11
11
|
/^backups\/config(?:\/|$)/,
|
|
12
12
|
/^\.snapshots(?:\/|$)/,
|
|
@@ -29,17 +29,18 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
29
29
|
];
|
|
30
30
|
const H_CLASS_REFERENCE_PATTERNS = [
|
|
31
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"';&|<>)},])/,
|
|
32
|
+
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
34
33
|
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
35
34
|
/(?:^|[/\\\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
35
|
/(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
|
|
37
36
|
];
|
|
38
37
|
const L_CLASS_RELATIVE_PATTERNS = [
|
|
39
38
|
/^data\/triggers(?:\/|$)/,
|
|
39
|
+
/^agents\/[^/]+\/roles(?:\/|$)/,
|
|
40
40
|
];
|
|
41
41
|
const L_CLASS_REFERENCE_PATTERNS = [
|
|
42
42
|
/(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
43
|
+
/(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
43
44
|
];
|
|
44
45
|
function normalizeForComparison(value) {
|
|
45
46
|
const resolved = path.resolve(value);
|
|
@@ -127,8 +128,7 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
|
127
128
|
path.join('agents', 'defaults.json'),
|
|
128
129
|
path.join('agents', '*', 'config.json'),
|
|
129
130
|
path.join('agents', '*', 'contact.json'),
|
|
130
|
-
path.join('agents', '*', '
|
|
131
|
-
path.join('agents', '*', 'roles', '**'),
|
|
131
|
+
path.join('agents', '*', 'contact-operations.jsonl'),
|
|
132
132
|
path.join('agents', '*', 'relations', '*', 'config.json'),
|
|
133
133
|
path.join('backups', 'config'),
|
|
134
134
|
path.join('backups', 'config', '**'),
|
|
@@ -169,6 +169,7 @@ export function getLClassWritePatterns(root = resolveRoot()) {
|
|
|
169
169
|
return [
|
|
170
170
|
path.join(absoluteRoot, 'data', 'triggers'),
|
|
171
171
|
path.join(absoluteRoot, 'data', 'triggers', '**'),
|
|
172
|
+
path.join(absoluteRoot, 'agents', '*', 'roles', '**'),
|
|
172
173
|
];
|
|
173
174
|
}
|
|
174
175
|
const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
|
|
@@ -213,10 +214,9 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
213
214
|
if (!entry.isDirectory())
|
|
214
215
|
continue;
|
|
215
216
|
const agentRoot = path.join(agentsDir, entry.name);
|
|
216
|
-
for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_']) {
|
|
217
|
+
for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_', 'contact-operations.jsonl']) {
|
|
217
218
|
addExistingTarget(targets, path.join(agentRoot, fileName));
|
|
218
219
|
}
|
|
219
|
-
addExistingTarget(targets, path.join(agentRoot, 'roles'));
|
|
220
220
|
const relationsDir = path.join(agentRoot, 'relations');
|
|
221
221
|
try {
|
|
222
222
|
for (const relation of fs.readdirSync(relationsDir, { withFileTypes: true })) {
|
|
@@ -247,7 +247,17 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
247
247
|
}
|
|
248
248
|
export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
|
|
249
249
|
const targets = new Map();
|
|
250
|
-
|
|
250
|
+
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
251
|
+
addExistingTarget(targets, path.join(absoluteRoot, 'data', 'triggers'));
|
|
252
|
+
const agentsDir = path.join(absoluteRoot, 'agents');
|
|
253
|
+
try {
|
|
254
|
+
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
255
|
+
if (!entry.isDirectory())
|
|
256
|
+
continue;
|
|
257
|
+
addExistingTarget(targets, path.join(agentsDir, entry.name, 'roles'));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
251
261
|
return [...targets.values()];
|
|
252
262
|
}
|
|
253
263
|
export function buildClaudeProtectedFilesystem(root = resolveRoot()) {
|
|
@@ -309,7 +319,6 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
|
|
|
309
319
|
if (!value.trim())
|
|
310
320
|
return false;
|
|
311
321
|
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
312
|
-
const protectedRoot = path.join(root, 'data', 'triggers');
|
|
313
322
|
if (isLClassPath(value, { cwd: options.cwd, root }))
|
|
314
323
|
return true;
|
|
315
324
|
const normalized = value.replace(/\\/g, '/');
|
|
@@ -318,8 +327,24 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
|
|
|
318
327
|
? value
|
|
319
328
|
: normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
|
|
320
329
|
const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
|
|
321
|
-
|
|
322
|
-
|
|
330
|
+
if (isSameOrDescendant(root, candidate))
|
|
331
|
+
return true;
|
|
332
|
+
if (!isSameOrDescendant(candidate, root))
|
|
333
|
+
return false;
|
|
334
|
+
const relative = path.relative(root, candidate);
|
|
335
|
+
const segments = relative.split(path.sep).filter(Boolean);
|
|
336
|
+
if (segments[0] === 'data') {
|
|
337
|
+
if (segments.length === 1)
|
|
338
|
+
return true;
|
|
339
|
+
return segments[1] === 'triggers';
|
|
340
|
+
}
|
|
341
|
+
if (segments[0] === 'agents') {
|
|
342
|
+
// The agents root and each individual Agent root contain a roles/ subtree.
|
|
343
|
+
if (segments.length <= 2)
|
|
344
|
+
return true;
|
|
345
|
+
return segments[2] === 'roles';
|
|
346
|
+
}
|
|
347
|
+
return false;
|
|
323
348
|
}
|
|
324
349
|
export function workspaceContainsHClassPaths(projectPath, root = resolveRoot()) {
|
|
325
350
|
const project = resolveThroughExistingAncestor(projectPath);
|
|
@@ -9,6 +9,22 @@ import { isSystemControlChannel } from '../system-channels.js';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import fs from 'fs';
|
|
11
11
|
import os from 'os';
|
|
12
|
+
function newSessionStableMetadata(chatType, ...sources) {
|
|
13
|
+
const keys = chatType === 'group'
|
|
14
|
+
? ['channelKey', 'groupId', 'groupName', 'dispatchMode']
|
|
15
|
+
: ['channelKey', 'peerId', 'peerName', 'peerType'];
|
|
16
|
+
const metadata = {};
|
|
17
|
+
for (const source of sources) {
|
|
18
|
+
if (!source)
|
|
19
|
+
continue;
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const value = source[key];
|
|
22
|
+
if (value !== undefined && value !== null && value !== '')
|
|
23
|
+
metadata[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return metadata;
|
|
27
|
+
}
|
|
12
28
|
/** 判定用户是否为指定渠道的 admin */
|
|
13
29
|
/** 判定用户是否为指定渠道的 member */
|
|
14
30
|
export class SessionManager {
|
|
@@ -1098,18 +1114,20 @@ export class SessionManager {
|
|
|
1098
1114
|
}
|
|
1099
1115
|
}
|
|
1100
1116
|
}
|
|
1101
|
-
async createNewSession(channel, channelId, projectPath, name, baseagent) {
|
|
1117
|
+
async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata) {
|
|
1102
1118
|
const inheritedChatType = this.getActiveChatType(channel, channelId);
|
|
1103
1119
|
// Derive selfAID and channelType from existing sessions
|
|
1104
1120
|
const existingDir = this.findExistingChatDir(channel, channelId);
|
|
1105
1121
|
const identity = this.deriveChannelIdentity(channel, channelId);
|
|
1106
1122
|
let channelType = identity.channelType;
|
|
1107
1123
|
let selfAID = identity.selfAID;
|
|
1124
|
+
let activeMetadata;
|
|
1108
1125
|
if (existingDir) {
|
|
1109
1126
|
const active = readJsonFile(path.join(existingDir, 'active.json'));
|
|
1110
1127
|
if (active) {
|
|
1111
1128
|
channelType = active.channelType || channel;
|
|
1112
1129
|
selfAID = active.selfAID || '';
|
|
1130
|
+
activeMetadata = active.metadata;
|
|
1113
1131
|
// 兼容旧字段名
|
|
1114
1132
|
const activeBaseagent = active.baseagent || active.agentType;
|
|
1115
1133
|
if (!baseagent && activeBaseagent)
|
|
@@ -1130,7 +1148,7 @@ export class SessionManager {
|
|
|
1130
1148
|
baseagent,
|
|
1131
1149
|
sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
|
|
1132
1150
|
chatType: inheritedChatType,
|
|
1133
|
-
metadata:
|
|
1151
|
+
metadata: newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata),
|
|
1134
1152
|
name: name || '默认会话',
|
|
1135
1153
|
createdAt: Date.now(),
|
|
1136
1154
|
updatedAt: Date.now(),
|
|
@@ -1258,7 +1276,7 @@ export class SessionManager {
|
|
|
1258
1276
|
* 为超时后的独立话题创建新会话。保留聊天路由和稳定 metadata,
|
|
1259
1277
|
* 但不继承任何底层 agent session 或临时任务状态。
|
|
1260
1278
|
*/
|
|
1261
|
-
async createRenewedSession(sourceSession) {
|
|
1279
|
+
async createRenewedSession(sourceSession, context) {
|
|
1262
1280
|
const metadata = { ...(sourceSession.metadata || {}) };
|
|
1263
1281
|
delete metadata.agentSessions;
|
|
1264
1282
|
delete metadata.resumeAt;
|
|
@@ -1289,7 +1307,11 @@ export class SessionManager {
|
|
|
1289
1307
|
projectPath: session.projectPath,
|
|
1290
1308
|
name: session.name,
|
|
1291
1309
|
chatType: session.chatType,
|
|
1310
|
+
cause: 'renew',
|
|
1311
|
+
sourceSessionId: sourceSession.id,
|
|
1312
|
+
renewSource: context?.source,
|
|
1292
1313
|
timestamp: Date.now(),
|
|
1314
|
+
causation: context?.causation,
|
|
1293
1315
|
});
|
|
1294
1316
|
return session;
|
|
1295
1317
|
}
|
|
@@ -3,6 +3,8 @@ import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
|
3
3
|
export function sessionToFile(session) {
|
|
4
4
|
const metadata = {};
|
|
5
5
|
if (session.metadata) {
|
|
6
|
+
if (session.metadata.channelKey)
|
|
7
|
+
metadata.channelKey = session.metadata.channelKey;
|
|
6
8
|
if (session.metadata.peerId)
|
|
7
9
|
metadata.peerId = session.metadata.peerId;
|
|
8
10
|
if (session.metadata.peerName)
|