evolcore 0.0.8 → 0.0.10
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 +31 -0
- package/README.md +37 -3
- package/dist/agents/baseagent.js +111 -19
- package/dist/agents/claude-runner.js +30 -2
- package/dist/agents/codex-app-server-client.js +31 -0
- package/dist/agents/codex-runner.js +3 -0
- package/dist/aun/msg/group.js +9 -9
- package/dist/aun/msg/p2p.js +11 -6
- package/dist/aun/msg/upload.js +43 -29
- package/dist/aun/outbox.js +48 -5
- package/dist/channels/aun.js +199 -50
- package/dist/cli/aun-commands.js +10 -7
- package/dist/cli/daemon-commands.js +19 -4
- package/dist/cli/index.js +11 -1
- package/dist/cli/init.js +103 -4
- package/dist/cli/model.js +5 -5
- package/dist/config/builtin-role-templates.js +4 -3
- package/dist/config/config-field-policy.js +6 -2
- package/dist/config/config-manager.js +48 -11
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/role-schema.js +18 -3
- package/dist/config/role-service.js +3 -3
- package/dist/config/role-store.js +4 -3
- package/dist/config-store.js +4 -0
- package/dist/core/auth/operation-authorizer.js +3 -1
- package/dist/core/bootstrap-service.js +0 -12
- package/dist/core/channel-loader.js +5 -1
- package/dist/core/command/command-handler.js +1 -1
- package/dist/core/command/evol-menu-version-gate.js +3 -2
- package/dist/core/command/menu-handler.js +33 -22
- package/dist/core/command/menu-protocol.js +7 -4
- package/dist/core/command/slash-handler.js +4 -4
- package/dist/core/data-migration.js +79 -27
- package/dist/core/inference/text-inference.js +18 -5
- package/dist/core/message/message-bridge.js +17 -6
- package/dist/core/message/response-engine.js +22 -3
- package/dist/core/model/config-scope.js +3 -3
- package/dist/core/permission/tool-policy.js +18 -0
- package/dist/index.js +43 -26
- package/dist/ipc.js +1 -1
- package/dist/product.js +1 -0
- package/dist/utils/autostart.js +27 -0
- package/dist/utils/ecweb-utils.js +15 -2
- package/dist/utils/macos-autostart.js +179 -0
- package/dist/utils/windows-autostart.js +130 -0
- package/ecagent/dist/agent-loop.d.ts +1 -1
- package/ecagent/dist/agent-loop.js +1 -1
- package/ecagent/dist/agent.d.ts +1 -1
- package/ecagent/dist/agent.js +1 -1
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/compaction/branch-summarization.d.ts +1 -1
- package/ecagent/dist/harness/compaction/compaction.d.ts +1 -1
- package/ecagent/dist/harness/compaction/utils.d.ts +1 -1
- package/ecagent/dist/harness/messages.d.ts +1 -1
- package/ecagent/dist/harness/session/session.d.ts +1 -1
- package/ecagent/dist/harness/types.d.ts +1 -1
- package/ecagent/dist/proxy.d.ts +1 -1
- package/ecagent/dist/proxy.js +1 -1
- package/ecagent/dist/types.d.ts +1 -1
- package/kits/migrations/migrate-role-config-v5.mjs +27 -2
- package/kits/schemas/agent-config.schema.10.json +19 -1
- package/kits/schemas/daemon.schema.4.json +4 -0
- package/kits/schemas/defaults.schema.4.json +15 -1
- package/kits/schemas/relation-config.schema.8.json +22 -1
- package/kits/schemas/role-config.schema.1.json +2 -2
- package/package.json +2 -4
- package/ecagent/dist/agent-loop.d.ts.map +0 -1
- package/ecagent/dist/agent-loop.js.map +0 -1
- package/ecagent/dist/agent.d.ts.map +0 -1
- package/ecagent/dist/agent.js.map +0 -1
- package/ecagent/dist/harness/agent-harness.d.ts.map +0 -1
- package/ecagent/dist/harness/agent-harness.js.map +0 -1
- package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
- package/ecagent/dist/harness/compaction/branch-summarization.js.map +0 -1
- package/ecagent/dist/harness/compaction/compaction.d.ts.map +0 -1
- package/ecagent/dist/harness/compaction/compaction.js.map +0 -1
- package/ecagent/dist/harness/compaction/utils.d.ts.map +0 -1
- package/ecagent/dist/harness/compaction/utils.js.map +0 -1
- package/ecagent/dist/harness/env/nodejs.d.ts.map +0 -1
- package/ecagent/dist/harness/env/nodejs.js.map +0 -1
- package/ecagent/dist/harness/messages.d.ts.map +0 -1
- package/ecagent/dist/harness/messages.js.map +0 -1
- package/ecagent/dist/harness/prompt-templates.d.ts.map +0 -1
- package/ecagent/dist/harness/prompt-templates.js.map +0 -1
- package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +0 -1
- package/ecagent/dist/harness/session/jsonl-repo.js.map +0 -1
- package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +0 -1
- package/ecagent/dist/harness/session/jsonl-storage.js.map +0 -1
- package/ecagent/dist/harness/session/memory-repo.d.ts.map +0 -1
- package/ecagent/dist/harness/session/memory-repo.js.map +0 -1
- package/ecagent/dist/harness/session/memory-storage.d.ts.map +0 -1
- package/ecagent/dist/harness/session/memory-storage.js.map +0 -1
- package/ecagent/dist/harness/session/repo-utils.d.ts.map +0 -1
- package/ecagent/dist/harness/session/repo-utils.js.map +0 -1
- package/ecagent/dist/harness/session/session.d.ts.map +0 -1
- package/ecagent/dist/harness/session/session.js.map +0 -1
- package/ecagent/dist/harness/session/uuid.d.ts.map +0 -1
- package/ecagent/dist/harness/session/uuid.js.map +0 -1
- package/ecagent/dist/harness/skills.d.ts.map +0 -1
- package/ecagent/dist/harness/skills.js.map +0 -1
- package/ecagent/dist/harness/system-prompt.d.ts.map +0 -1
- package/ecagent/dist/harness/system-prompt.js.map +0 -1
- package/ecagent/dist/harness/types.d.ts.map +0 -1
- package/ecagent/dist/harness/types.js.map +0 -1
- package/ecagent/dist/harness/utils/shell-output.d.ts.map +0 -1
- package/ecagent/dist/harness/utils/shell-output.js.map +0 -1
- package/ecagent/dist/harness/utils/truncate.d.ts.map +0 -1
- package/ecagent/dist/harness/utils/truncate.js.map +0 -1
- package/ecagent/dist/index.d.ts.map +0 -1
- package/ecagent/dist/index.js.map +0 -1
- package/ecagent/dist/node.d.ts.map +0 -1
- package/ecagent/dist/node.js.map +0 -1
- package/ecagent/dist/proxy.d.ts.map +0 -1
- package/ecagent/dist/proxy.js.map +0 -1
- package/ecagent/dist/runtime/api/lazy.d.ts.map +0 -1
- package/ecagent/dist/runtime/api/lazy.js.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.d.ts.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.js.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +0 -1
- package/ecagent/dist/runtime/auth/context.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/context.js.map +0 -1
- package/ecagent/dist/runtime/auth/credential-store.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/credential-store.js.map +0 -1
- package/ecagent/dist/runtime/auth/helpers.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/helpers.js.map +0 -1
- package/ecagent/dist/runtime/auth/resolve.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/resolve.js.map +0 -1
- package/ecagent/dist/runtime/auth/types.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/types.js.map +0 -1
- package/ecagent/dist/runtime/compat.d.ts.map +0 -1
- package/ecagent/dist/runtime/compat.js.map +0 -1
- package/ecagent/dist/runtime/index.d.ts.map +0 -1
- package/ecagent/dist/runtime/index.js.map +0 -1
- package/ecagent/dist/runtime/models.d.ts.map +0 -1
- package/ecagent/dist/runtime/models.js.map +0 -1
- package/ecagent/dist/runtime/oauth.d.ts.map +0 -1
- package/ecagent/dist/runtime/oauth.js.map +0 -1
- package/ecagent/dist/runtime/providers/faux.d.ts.map +0 -1
- package/ecagent/dist/runtime/providers/faux.js.map +0 -1
- package/ecagent/dist/runtime/types.d.ts.map +0 -1
- package/ecagent/dist/runtime/types.js.map +0 -1
- package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/diagnostics.js.map +0 -1
- package/ecagent/dist/runtime/utils/event-stream.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/event-stream.js.map +0 -1
- package/ecagent/dist/runtime/utils/headers.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/headers.js.map +0 -1
- package/ecagent/dist/runtime/utils/json-parse.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/json-parse.js.map +0 -1
- package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/oauth/types.js.map +0 -1
- package/ecagent/dist/runtime/utils/overflow.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/overflow.js.map +0 -1
- package/ecagent/dist/runtime/utils/provider-env.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/provider-env.js.map +0 -1
- package/ecagent/dist/runtime/utils/retry.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/retry.js.map +0 -1
- package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/typebox-helpers.js.map +0 -1
- package/ecagent/dist/runtime/utils/validation.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/validation.js.map +0 -1
- package/ecagent/dist/types.d.ts.map +0 -1
- package/ecagent/dist/types.js.map +0 -1
- package/ecagent/package.json +0 -93
- package/ecagent/src/agent-loop.ts +0 -792
- package/ecagent/src/agent.ts +0 -575
- package/ecagent/src/harness/agent-harness.ts +0 -1029
- package/ecagent/src/harness/compaction/branch-summarization.ts +0 -261
- package/ecagent/src/harness/compaction/compaction.ts +0 -753
- package/ecagent/src/harness/compaction/utils.ts +0 -144
- package/ecagent/src/harness/env/nodejs.ts +0 -569
- package/ecagent/src/harness/messages.ts +0 -164
- package/ecagent/src/harness/prompt-templates.ts +0 -267
- package/ecagent/src/harness/session/jsonl-repo.ts +0 -179
- package/ecagent/src/harness/session/jsonl-storage.ts +0 -314
- package/ecagent/src/harness/session/memory-repo.ts +0 -50
- package/ecagent/src/harness/session/memory-storage.ts +0 -133
- package/ecagent/src/harness/session/repo-utils.ts +0 -51
- package/ecagent/src/harness/session/session.ts +0 -338
- package/ecagent/src/harness/session/uuid.ts +0 -54
- package/ecagent/src/harness/skills.ts +0 -375
- package/ecagent/src/harness/system-prompt.ts +0 -34
- package/ecagent/src/harness/types.ts +0 -838
- package/ecagent/src/harness/utils/shell-output.ts +0 -135
- package/ecagent/src/harness/utils/truncate.ts +0 -344
- package/ecagent/src/index.ts +0 -46
- package/ecagent/src/node.ts +0 -2
- package/ecagent/src/proxy.ts +0 -367
- package/ecagent/src/runtime/api/lazy.ts +0 -70
- package/ecagent/src/runtime/api/pi-messages.lazy.ts +0 -4
- package/ecagent/src/runtime/api/pi-messages.ts +0 -436
- package/ecagent/src/runtime/auth/context.ts +0 -45
- package/ecagent/src/runtime/auth/credential-store.ts +0 -47
- package/ecagent/src/runtime/auth/helpers.ts +0 -46
- package/ecagent/src/runtime/auth/resolve.ts +0 -141
- package/ecagent/src/runtime/auth/types.ts +0 -182
- package/ecagent/src/runtime/compat.ts +0 -158
- package/ecagent/src/runtime/index.ts +0 -31
- package/ecagent/src/runtime/models.ts +0 -452
- package/ecagent/src/runtime/oauth.ts +0 -1
- package/ecagent/src/runtime/providers/faux.ts +0 -538
- package/ecagent/src/runtime/types.ts +0 -718
- package/ecagent/src/runtime/utils/diagnostics.ts +0 -45
- package/ecagent/src/runtime/utils/event-stream.ts +0 -88
- package/ecagent/src/runtime/utils/headers.ts +0 -18
- package/ecagent/src/runtime/utils/json-parse.ts +0 -124
- package/ecagent/src/runtime/utils/oauth/types.ts +0 -79
- package/ecagent/src/runtime/utils/overflow.ts +0 -165
- package/ecagent/src/runtime/utils/provider-env.ts +0 -52
- package/ecagent/src/runtime/utils/retry.ts +0 -101
- package/ecagent/src/runtime/utils/typebox-helpers.ts +0 -24
- package/ecagent/src/runtime/utils/validation.ts +0 -310
- package/ecagent/src/types.ts +0 -430
- package/ecagent/tsconfig.build.json +0 -32
|
@@ -27,9 +27,11 @@ export function showActivitiesPolicy(mode, chatType, _identity) {
|
|
|
27
27
|
// ── ChannelLoader ──────────────────────────────────────────────────────────
|
|
28
28
|
export class ChannelLoader {
|
|
29
29
|
processDebug;
|
|
30
|
+
aunDefaultEncrypt;
|
|
30
31
|
plugins = new Map();
|
|
31
|
-
constructor(processDebug) {
|
|
32
|
+
constructor(processDebug, aunDefaultEncrypt) {
|
|
32
33
|
this.processDebug = processDebug;
|
|
34
|
+
this.aunDefaultEncrypt = aunDefaultEncrypt;
|
|
33
35
|
}
|
|
34
36
|
register(plugin) {
|
|
35
37
|
if (this.plugins.has(plugin.name)) {
|
|
@@ -51,6 +53,7 @@ export class ChannelLoader {
|
|
|
51
53
|
agentName: agent.aid,
|
|
52
54
|
defaultProjectPath: agent.config.projects?.defaultPath ?? process.cwd(),
|
|
53
55
|
debug: this.processDebug,
|
|
56
|
+
aunDefaultEncrypt: this.aunDefaultEncrypt,
|
|
54
57
|
};
|
|
55
58
|
// Build the full list of config instances to create.
|
|
56
59
|
// AUN is synthesised from agent.aid; any explicit aun entry in channels[] is skipped.
|
|
@@ -266,6 +269,7 @@ export function buildReloadHooks(deps) {
|
|
|
266
269
|
agentName: agent.aid ?? agent.config?.aid,
|
|
267
270
|
defaultProjectPath: agent.config?.projects?.defaultPath ?? process.cwd(),
|
|
268
271
|
debug: channelLoader.processDebug,
|
|
272
|
+
aunDefaultEncrypt: channelLoader.aunDefaultEncrypt,
|
|
269
273
|
};
|
|
270
274
|
const plugin = channelLoader.getPlugin(cfgInst.type);
|
|
271
275
|
if (!plugin)
|
|
@@ -372,7 +372,7 @@ export class CommandHandler {
|
|
|
372
372
|
const effortDecision = validateRuntimeStringFieldOverride({
|
|
373
373
|
selfAid: definition.agentAid,
|
|
374
374
|
role,
|
|
375
|
-
field: `baseagents.${base}
|
|
375
|
+
field: `baseagents.${base}.effort`,
|
|
376
376
|
value: definition.execution.effort,
|
|
377
377
|
});
|
|
378
378
|
if (!effortDecision.ok)
|
|
@@ -30,9 +30,10 @@ export function isEvolMenuVersionGateEnabled() {
|
|
|
30
30
|
export function isAunMenuTokenRequired() {
|
|
31
31
|
return loadDaemonConfig().aun?.menuTokenRequired === true;
|
|
32
32
|
}
|
|
33
|
-
export function evolMenuResponseTransportMetadata(
|
|
33
|
+
export function evolMenuResponseTransportMetadata() {
|
|
34
34
|
return {
|
|
35
|
-
|
|
35
|
+
// Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
|
|
36
|
+
encrypted: undefined,
|
|
36
37
|
protectedHeaders: { ec_version: readInstalledEvolcoreVersion() },
|
|
37
38
|
};
|
|
38
39
|
}
|
|
@@ -9,6 +9,7 @@ import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
|
9
9
|
import { buildEnvelope } from '../message/message-utils.js';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import fs from 'fs';
|
|
12
|
+
import os from 'os';
|
|
12
13
|
import crypto from 'crypto';
|
|
13
14
|
import { execFileSync } from 'child_process';
|
|
14
15
|
import { CronExpressionParser } from 'cron-parser';
|
|
@@ -452,9 +453,6 @@ function menuConfigSource(fieldPath, sel) {
|
|
|
452
453
|
return null;
|
|
453
454
|
}
|
|
454
455
|
function menuModelConfigFieldPath(target) {
|
|
455
|
-
if (target.field === 'effort' && target.baseagent === 'codex') {
|
|
456
|
-
return `baseagents.${target.baseagent}.reasoning`;
|
|
457
|
-
}
|
|
458
456
|
return target.fieldPath;
|
|
459
457
|
}
|
|
460
458
|
function menuChatmodeScope(args) {
|
|
@@ -1881,7 +1879,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1881
1879
|
const target = resolveCapabilityTarget.call(this, { channel, args, session, evolagent, fromControlChannel });
|
|
1882
1880
|
if ('error' in target)
|
|
1883
1881
|
return target;
|
|
1884
|
-
const identity = this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1885
1882
|
const data = queryCapabilityTypes(target.ctx, target.config, type);
|
|
1886
1883
|
return { data: sanitizeCapabilityQueryForRole(data, identity.role) };
|
|
1887
1884
|
}
|
|
@@ -2179,11 +2176,13 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2179
2176
|
if (!subject.isDaemonOwner) {
|
|
2180
2177
|
return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
|
|
2181
2178
|
}
|
|
2182
|
-
const owningAgent = this.getOwningAgent(channel);
|
|
2183
2179
|
const data = {
|
|
2184
2180
|
aid: loadDaemonConfig().aid ?? null,
|
|
2185
2181
|
pid: process.pid,
|
|
2186
|
-
|
|
2182
|
+
nodejs: process.version,
|
|
2183
|
+
hostname: os.hostname(),
|
|
2184
|
+
platform: process.platform,
|
|
2185
|
+
arch: process.arch,
|
|
2187
2186
|
uptime: Math.floor(process.uptime()),
|
|
2188
2187
|
};
|
|
2189
2188
|
try {
|
|
@@ -2204,14 +2203,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2204
2203
|
const ecwebPkg = resolveGlobalPkg(WEB_PACKAGE_NAME);
|
|
2205
2204
|
if (ecwebPkg?.version)
|
|
2206
2205
|
data.ecwebVersion = ecwebPkg.version;
|
|
2207
|
-
const channels = owningAgent?.channelInstanceNames?.() ?? [];
|
|
2208
|
-
if (channels.length) {
|
|
2209
|
-
// 将 channelKey 字符串(如 "feishu#aid#name")解析为对象 { type, instName }
|
|
2210
|
-
data.channels = channels.map((key) => {
|
|
2211
|
-
const parts = key.split('#');
|
|
2212
|
-
return { type: parts[0], instName: parts[2] || parts[0] };
|
|
2213
|
-
});
|
|
2214
|
-
}
|
|
2215
2206
|
// 收集主机级可用的所有 baseagent 类型(检测已安装的 CLI + 版本)
|
|
2216
2207
|
const baseagents = [];
|
|
2217
2208
|
for (const cmd of ['claude', 'gemini', 'codex']) {
|
|
@@ -2260,7 +2251,9 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2260
2251
|
if (cmdBase === '/perm') {
|
|
2261
2252
|
return { error: 'permissionMode 只能通过角色策略编辑器修改', code: 'ROLE_POLICY_MANAGED' };
|
|
2262
2253
|
}
|
|
2263
|
-
|
|
2254
|
+
// JSON Menu 入口在 validateMenuRequest 中要求 activity 显式 scope;保留
|
|
2255
|
+
// 底层 execMenuUpdate 的旧默认行为,兼容旧版 slash/内部直调调用方。
|
|
2256
|
+
const scopeError = validateConfigWriteScope(cmdBase, args, { includeActivity: false });
|
|
2264
2257
|
if (scopeError)
|
|
2265
2258
|
return { error: scopeError.message, code: scopeError.code };
|
|
2266
2259
|
args = withoutLegacySessionConfigArgs(args);
|
|
@@ -3517,26 +3510,43 @@ const SYSTEM_CONTROL_NAME_MAP = {
|
|
|
3517
3510
|
config: '/config', capability: '/capability', role: '/role',
|
|
3518
3511
|
group: '/group', connect: '/connect',
|
|
3519
3512
|
};
|
|
3513
|
+
function menuCommandToken(cmd) {
|
|
3514
|
+
if (typeof cmd !== 'string')
|
|
3515
|
+
return undefined;
|
|
3516
|
+
return cmd.trim().split(/\s+/, 1)[0] || undefined;
|
|
3517
|
+
}
|
|
3520
3518
|
function isProcessLevelMenu(name, cmd) {
|
|
3519
|
+
const cmdToken = menuCommandToken(cmd);
|
|
3521
3520
|
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config'
|
|
3522
|
-
||
|
|
3521
|
+
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config';
|
|
3523
3522
|
}
|
|
3524
3523
|
async function execMenuForSystemControl(payload, context) {
|
|
3525
3524
|
const id = payload?.id ?? '';
|
|
3526
3525
|
const name = payload?.name;
|
|
3527
|
-
|
|
3526
|
+
const compatCmd = menuCommandToken(payload?.cmd);
|
|
3527
|
+
// `menu.query(/file)` returns file metadata and is authorized as
|
|
3528
|
+
// `file.fetch`, while `menu.action(file/fetch)` sends the file. Neither
|
|
3529
|
+
// operation is available through Host/control: file operations must be
|
|
3530
|
+
// addressed to the current Agent AID so the Agent project boundary applies.
|
|
3531
|
+
const isFileFetch = (payload?.type === 'menu.query'
|
|
3532
|
+
|| (payload?.type === 'menu.action' && payload?.action === 'fetch'))
|
|
3533
|
+
&& (name === 'file' || compatCmd === '/file');
|
|
3534
|
+
if (isFileFetch) {
|
|
3535
|
+
return ecwebErr(id, name, 'NOT_SUPPORTED', 'Host/control 不支持 file.fetch,请通过目标 Agent AID 请求');
|
|
3536
|
+
}
|
|
3537
|
+
if (name === 'cli' || compatCmd === '/cli') {
|
|
3528
3538
|
const message = context.source === 'ecweb'
|
|
3529
3539
|
? 'cli 不在 ECWeb 控制范围'
|
|
3530
3540
|
: 'cli 不在控制 channel 范围';
|
|
3531
3541
|
return { type: 'menu.response', id, name, error: { code: 'NOT_SUPPORTED', message } };
|
|
3532
3542
|
}
|
|
3533
3543
|
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
|
|
3534
|
-
const isRoleRequest = name === 'role' ||
|
|
3535
|
-
const isConnectRequest = name === 'connect' ||
|
|
3544
|
+
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
3545
|
+
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|
|
3536
3546
|
const requiresAgentTarget = isRoleRequest || isConnectRequest || [
|
|
3537
3547
|
'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
|
|
3538
3548
|
].includes(name)
|
|
3539
|
-
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(
|
|
3549
|
+
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '');
|
|
3540
3550
|
const target = isProcessLevel
|
|
3541
3551
|
? { channel: SYSTEM_CONTROL_CHANNEL }
|
|
3542
3552
|
: resolveExternalMenuAgentChannel(this, payload, SYSTEM_CONTROL_CHANNEL, requiresAgentTarget);
|
|
@@ -3635,8 +3645,9 @@ export async function execMenuForControl(payload, peerId) {
|
|
|
3635
3645
|
if (validationError)
|
|
3636
3646
|
return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
|
|
3637
3647
|
const owners = loadDaemonConfig().owners ?? [];
|
|
3638
|
-
const
|
|
3639
|
-
const
|
|
3648
|
+
const compatCmd = menuCommandToken(payload?.cmd);
|
|
3649
|
+
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
3650
|
+
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|
|
3640
3651
|
if (!isRoleRequest && !isConnectRequest && !isProcessLevelOwner(peerId, owners)) {
|
|
3641
3652
|
return menuFailure({ id, ...(name ? { name } : {}) }, { code: 'ROLE_ACCESS_DENIED', message: '控制 channel 操作需要 owner 权限' });
|
|
3642
3653
|
}
|
|
@@ -15,6 +15,7 @@ const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
|
|
|
15
15
|
'mentionmode',
|
|
16
16
|
'group',
|
|
17
17
|
'permission',
|
|
18
|
+
'activity',
|
|
18
19
|
]);
|
|
19
20
|
const EXPLICIT_SCOPE_UPDATE_COMMANDS = new Set([
|
|
20
21
|
'/model',
|
|
@@ -24,18 +25,20 @@ const EXPLICIT_SCOPE_UPDATE_COMMANDS = new Set([
|
|
|
24
25
|
'/mentionmode',
|
|
25
26
|
'/group',
|
|
26
27
|
'/perm',
|
|
28
|
+
'/activity',
|
|
27
29
|
]);
|
|
28
30
|
function firstCommandToken(value) {
|
|
29
31
|
if (typeof value !== 'string')
|
|
30
32
|
return undefined;
|
|
31
33
|
return value.trim().split(/\s+/, 1)[0] || undefined;
|
|
32
34
|
}
|
|
33
|
-
function requiresExplicitConfigWriteScope(nameOrCmd) {
|
|
35
|
+
function requiresExplicitConfigWriteScope(nameOrCmd, includeActivity = true) {
|
|
34
36
|
const token = firstCommandToken(nameOrCmd);
|
|
35
|
-
return !!token && (EXPLICIT_SCOPE_UPDATE_NAMES.has(token) || EXPLICIT_SCOPE_UPDATE_COMMANDS.has(token))
|
|
37
|
+
return !!token && (EXPLICIT_SCOPE_UPDATE_NAMES.has(token) || EXPLICIT_SCOPE_UPDATE_COMMANDS.has(token))
|
|
38
|
+
&& (includeActivity || (token !== 'activity' && token !== '/activity'));
|
|
36
39
|
}
|
|
37
|
-
export function validateConfigWriteScope(nameOrCmd, args) {
|
|
38
|
-
if (!requiresExplicitConfigWriteScope(nameOrCmd))
|
|
40
|
+
export function validateConfigWriteScope(nameOrCmd, args, options) {
|
|
41
|
+
if (!requiresExplicitConfigWriteScope(nameOrCmd, options?.includeActivity ?? true))
|
|
39
42
|
return null;
|
|
40
43
|
const rawScope = args?.scope;
|
|
41
44
|
if (rawScope === undefined || rawScope === null || (typeof rawScope === 'string' && rawScope.trim().length === 0)) {
|
|
@@ -1146,7 +1146,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1146
1146
|
}
|
|
1147
1147
|
catch { }
|
|
1148
1148
|
const configuredModel = modelConfig.model;
|
|
1149
|
-
const configuredEffort = modelConfig
|
|
1149
|
+
const configuredEffort = modelConfig.effort;
|
|
1150
1150
|
const rawModels = hasModelSwitcher(modelAgent) ? await modelAgent.listModels() : [];
|
|
1151
1151
|
const models = hasModelSwitcher(modelAgent)
|
|
1152
1152
|
? filterModelsForRole(identity.role, fallbackBaseagent, rawModels, modelAgent.resolveModelId?.bind(modelAgent))
|
|
@@ -1468,7 +1468,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1468
1468
|
}
|
|
1469
1469
|
catch { }
|
|
1470
1470
|
const configuredModel = modelConfig.model;
|
|
1471
|
-
const configuredEffort = modelConfig
|
|
1471
|
+
const configuredEffort = modelConfig.effort;
|
|
1472
1472
|
const currentModel = hasModelSwitcher(effortAgent) ? (configuredModel || effortAgent.getModel()) : effortAgent.name;
|
|
1473
1473
|
const efforts = getAvailableEfforts(effortAgent, currentModel);
|
|
1474
1474
|
const currentEffort = configuredEffort ?? effortAgent.getEffort?.() ?? 'auto';
|
|
@@ -1530,7 +1530,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1530
1530
|
}
|
|
1531
1531
|
catch { }
|
|
1532
1532
|
const configuredModel = modelConfig.model;
|
|
1533
|
-
const configuredEffort = modelConfig
|
|
1533
|
+
const configuredEffort = modelConfig.effort;
|
|
1534
1534
|
const currentModel = hasModelSwitcher(effortAgent) ? (configuredModel || effortAgent.getModel()) : effortAgent.name;
|
|
1535
1535
|
const efforts = getAvailableEfforts(effortAgent, currentModel);
|
|
1536
1536
|
const currentEffort = configuredEffort ?? effortAgent.getEffort?.() ?? 'auto';
|
|
@@ -2210,7 +2210,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2210
2210
|
}
|
|
2211
2211
|
catch { }
|
|
2212
2212
|
const backendModel = modelConfig.model || newRunner.getModel?.() || newAgent?.model;
|
|
2213
|
-
const backendEffort = modelConfig
|
|
2213
|
+
const backendEffort = modelConfig.effort
|
|
2214
2214
|
?? newRunner.getEffort?.()
|
|
2215
2215
|
?? newAgent?.effort;
|
|
2216
2216
|
const backendBits = [newBaseagent, backendModel, backendEffort].filter(Boolean).join(' · ');
|
|
@@ -54,6 +54,14 @@ function operationTopologyKey(operation) {
|
|
|
54
54
|
sessionChannelKey: operation.sessionChannelKey,
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
function operationProofTopologyKey(operation) {
|
|
58
|
+
if (operation.kind !== 'contact-audit-partition')
|
|
59
|
+
return operationTopologyKey(operation);
|
|
60
|
+
return JSON.stringify({
|
|
61
|
+
kind: operation.kind,
|
|
62
|
+
source: operation.source,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
57
65
|
/**
|
|
58
66
|
* Manifests written before allowExistingDestinationMerge was introduced still
|
|
59
67
|
* need safe recovery. Re-derive that narrow permission from their immutable
|
|
@@ -125,26 +133,40 @@ function acknowledgeCompletedCopies(root, operations) {
|
|
|
125
133
|
if (manifest.planSignature === currentPlanSignature)
|
|
126
134
|
return;
|
|
127
135
|
for (const operation of manifest.operations ?? []) {
|
|
128
|
-
if (operation.kind !== 'copy'
|
|
136
|
+
if ((operation.kind !== 'copy' && operation.kind !== 'contact-audit-partition')
|
|
137
|
+
|| (operation.status !== 'committed' && operation.status !== 'skipped')
|
|
138
|
+
|| !operation.sourceHash)
|
|
129
139
|
continue;
|
|
130
|
-
const key =
|
|
131
|
-
if (!proofs.has(key))
|
|
132
|
-
proofs.set(key, {
|
|
140
|
+
const key = operationProofTopologyKey(operation);
|
|
141
|
+
if (!proofs.has(key)) {
|
|
142
|
+
proofs.set(key, {
|
|
143
|
+
migrationId: manifest.id,
|
|
144
|
+
sourceHash: operation.sourceHash,
|
|
145
|
+
destinations: operation.kind === 'contact-audit-partition'
|
|
146
|
+
? [...(operation.destinations ?? [])]
|
|
147
|
+
: operation.destination ? [operation.destination] : [],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
133
150
|
}
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
153
|
catch { }
|
|
137
154
|
for (const operation of operations) {
|
|
138
|
-
if (operation.kind !== 'copy'
|
|
155
|
+
if (operation.kind !== 'copy' && operation.kind !== 'contact-audit-partition')
|
|
139
156
|
continue;
|
|
140
|
-
const proof = proofs.get(
|
|
157
|
+
const proof = proofs.get(operationProofTopologyKey(operation));
|
|
141
158
|
if (!proof)
|
|
142
159
|
continue;
|
|
160
|
+
if (operation.kind === 'copy' && (!operation.destination || !fs.existsSync(operation.destination)))
|
|
161
|
+
continue;
|
|
162
|
+
if (operation.kind === 'contact-audit-partition' && proof.destinations.some(destination => !fs.existsSync(destination)))
|
|
163
|
+
continue;
|
|
143
164
|
try {
|
|
144
165
|
if (treeHash(operation.source, operation.exclude) !== proof.sourceHash)
|
|
145
166
|
continue;
|
|
146
167
|
operation.status = 'acknowledged';
|
|
147
168
|
operation.acknowledgedBy = proof.migrationId;
|
|
169
|
+
operation.sourceHash = proof.sourceHash;
|
|
148
170
|
}
|
|
149
171
|
catch { }
|
|
150
172
|
}
|
|
@@ -1419,6 +1441,18 @@ function atomicWriteText(target, content) {
|
|
|
1419
1441
|
function hashContent(content) {
|
|
1420
1442
|
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
1421
1443
|
}
|
|
1444
|
+
function mergeJsonlContent(existing, incoming, deduplicateExisting = false) {
|
|
1445
|
+
const existingLines = existing.split('\n').filter(line => line.trim());
|
|
1446
|
+
const merged = deduplicateExisting ? [...new Set(existingLines)] : [...existingLines];
|
|
1447
|
+
const lines = new Set(merged);
|
|
1448
|
+
for (const line of incoming.split('\n')) {
|
|
1449
|
+
if (!line.trim() || lines.has(line))
|
|
1450
|
+
continue;
|
|
1451
|
+
lines.add(line);
|
|
1452
|
+
merged.push(line);
|
|
1453
|
+
}
|
|
1454
|
+
return merged.length ? `${merged.join('\n')}\n` : '';
|
|
1455
|
+
}
|
|
1422
1456
|
function appendIssue(operation, issue) {
|
|
1423
1457
|
const issues = operation.issues ?? [];
|
|
1424
1458
|
if (!issues.includes(issue))
|
|
@@ -1457,20 +1491,18 @@ function executeContactAudit(manifest, operation, checkpoint) {
|
|
|
1457
1491
|
for (const [aid, lines] of groups) {
|
|
1458
1492
|
const destination = path.join(manifest.root, 'agents', aid, 'data', 'contact-audit.jsonl');
|
|
1459
1493
|
const content = lines.join('\n') + '\n';
|
|
1460
|
-
const
|
|
1494
|
+
const existing = fs.existsSync(destination) ? fs.readFileSync(destination, 'utf8') : undefined;
|
|
1495
|
+
if (existing !== undefined)
|
|
1496
|
+
strictJsonValidation(destination);
|
|
1497
|
+
const expectedContent = existing === undefined ? content : mergeJsonlContent(existing, content);
|
|
1498
|
+
const expectedHash = hashContent(expectedContent);
|
|
1461
1499
|
operation.destinations.push(destination);
|
|
1462
1500
|
operation.destinationHashes[destination] = expectedHash;
|
|
1463
|
-
if (
|
|
1464
|
-
const existing = fs.readFileSync(destination, 'utf8');
|
|
1501
|
+
if (existing !== undefined) {
|
|
1465
1502
|
if (hashContent(existing) !== expectedHash) {
|
|
1466
|
-
//
|
|
1467
|
-
//
|
|
1468
|
-
|
|
1469
|
-
if (!content.startsWith(existing)) {
|
|
1470
|
-
appendIssue(operation, `destination conflict: ${destination}`);
|
|
1471
|
-
continue;
|
|
1472
|
-
}
|
|
1473
|
-
atomicWriteText(destination, content);
|
|
1503
|
+
// The per-Agent audit may have advanced after an earlier migration.
|
|
1504
|
+
// Preserve its order and append only legacy records not already present.
|
|
1505
|
+
atomicWriteText(destination, expectedContent);
|
|
1474
1506
|
}
|
|
1475
1507
|
continue;
|
|
1476
1508
|
}
|
|
@@ -1600,15 +1632,7 @@ function trackMergeSources(operation) {
|
|
|
1600
1632
|
return sources;
|
|
1601
1633
|
}
|
|
1602
1634
|
function mergeJsonl(target, source) {
|
|
1603
|
-
|
|
1604
|
-
const merged = [...lines];
|
|
1605
|
-
for (const line of fs.readFileSync(source, 'utf8').split('\n')) {
|
|
1606
|
-
if (!line.trim() || lines.has(line))
|
|
1607
|
-
continue;
|
|
1608
|
-
lines.add(line);
|
|
1609
|
-
merged.push(line);
|
|
1610
|
-
}
|
|
1611
|
-
fs.writeFileSync(target, merged.length ? `${merged.join('\n')}\n` : '');
|
|
1635
|
+
fs.writeFileSync(target, mergeJsonlContent(fs.readFileSync(target, 'utf8'), fs.readFileSync(source, 'utf8'), true));
|
|
1612
1636
|
}
|
|
1613
1637
|
function copyTree(source, destination) {
|
|
1614
1638
|
const stat = fs.statSync(source);
|
|
@@ -1741,6 +1765,34 @@ export function readDataMigration(root, id) {
|
|
|
1741
1765
|
throw new Error(`migration manifest not found: ${id}`);
|
|
1742
1766
|
return manifest;
|
|
1743
1767
|
}
|
|
1768
|
+
function completedMigrationMatchesPlan(manifest, plan, planSignature) {
|
|
1769
|
+
if (manifest.state !== 'completed' || manifest.planSignature !== planSignature)
|
|
1770
|
+
return false;
|
|
1771
|
+
const proofs = new Map(manifest.operations.map(operation => [operationProofTopologyKey(operation), operation]));
|
|
1772
|
+
for (const operation of plan.operations.filter(item => item.status !== 'acknowledged')) {
|
|
1773
|
+
const proof = proofs.get(operationProofTopologyKey(operation));
|
|
1774
|
+
if (!proof || (proof.status !== 'committed' && proof.status !== 'skipped' && proof.status !== 'acknowledged'))
|
|
1775
|
+
return false;
|
|
1776
|
+
try {
|
|
1777
|
+
for (const source of operationSources(operation)) {
|
|
1778
|
+
const expected = proof.sourceHashes?.[source]
|
|
1779
|
+
?? (source === proof.source ? proof.sourceHash : undefined);
|
|
1780
|
+
if (!expected || treeHash(source, operation.exclude) !== expected)
|
|
1781
|
+
return false;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
catch {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
if ((operation.kind === 'copy' || operation.kind === 'merge-copy')
|
|
1788
|
+
&& (!operation.destination || !fs.existsSync(operation.destination)))
|
|
1789
|
+
return false;
|
|
1790
|
+
if (operation.kind === 'contact-audit-partition'
|
|
1791
|
+
&& (proof.destinations ?? []).some(destination => !fs.existsSync(destination)))
|
|
1792
|
+
return false;
|
|
1793
|
+
}
|
|
1794
|
+
return true;
|
|
1795
|
+
}
|
|
1744
1796
|
/**
|
|
1745
1797
|
* Startup gate for the explicit migration workflow. Legacy source files are
|
|
1746
1798
|
* deliberately retained after success, so their mere presence is not enough
|
|
@@ -1763,7 +1815,7 @@ export function inspectDataMigrationRequirement(root) {
|
|
|
1763
1815
|
if (!entry.isFile() || !entry.name.endsWith('.json'))
|
|
1764
1816
|
continue;
|
|
1765
1817
|
const manifest = readJson(path.join(migrationDir(root), entry.name));
|
|
1766
|
-
if (manifest
|
|
1818
|
+
if (!manifest || !completedMigrationMatchesPlan(manifest, plan, planSignature))
|
|
1767
1819
|
continue;
|
|
1768
1820
|
completedMigrationId = manifest.id;
|
|
1769
1821
|
break;
|
|
@@ -3,8 +3,20 @@ function apiEndpoint(baseUrl, resource) {
|
|
|
3
3
|
const configured = baseUrl?.trim();
|
|
4
4
|
if (!configured)
|
|
5
5
|
throw new Error('Direct text inference baseUrl is not configured');
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const parsed = new URL(configured);
|
|
7
|
+
const pathname = parsed.pathname.replace(/\/+$/, '');
|
|
8
|
+
parsed.pathname = pathname.endsWith('/v1')
|
|
9
|
+
? `${pathname}/${resource}`
|
|
10
|
+
: `${pathname}/v1/${resource}`;
|
|
11
|
+
return parsed.toString();
|
|
12
|
+
}
|
|
13
|
+
function withQueryParams(url, queryParams) {
|
|
14
|
+
if (!queryParams || Object.keys(queryParams).length === 0)
|
|
15
|
+
return url;
|
|
16
|
+
const parsed = new URL(url);
|
|
17
|
+
for (const [name, value] of Object.entries(queryParams))
|
|
18
|
+
parsed.searchParams.set(name, value);
|
|
19
|
+
return parsed.toString();
|
|
8
20
|
}
|
|
9
21
|
async function parseError(response) {
|
|
10
22
|
let raw = '';
|
|
@@ -28,10 +40,10 @@ export class AnthropicTextInferenceProvider {
|
|
|
28
40
|
this.config = config;
|
|
29
41
|
}
|
|
30
42
|
async completeText(request) {
|
|
31
|
-
const response = await fetch(apiEndpoint(this.config.baseUrl, 'messages'), {
|
|
43
|
+
const response = await fetch(withQueryParams(apiEndpoint(this.config.baseUrl, 'messages'), this.config.queryParams), {
|
|
32
44
|
method: 'POST',
|
|
33
45
|
signal: request.signal,
|
|
34
|
-
headers: this.headers(),
|
|
46
|
+
headers: { ...this.headers(), ...this.config.headers },
|
|
35
47
|
body: JSON.stringify({
|
|
36
48
|
model: request.model,
|
|
37
49
|
max_tokens: 256,
|
|
@@ -69,12 +81,13 @@ export class OpenAITextInferenceProvider {
|
|
|
69
81
|
this.config = config;
|
|
70
82
|
}
|
|
71
83
|
async completeText(request) {
|
|
72
|
-
const response = await fetch(apiEndpoint(this.config.baseUrl, 'responses'), {
|
|
84
|
+
const response = await fetch(withQueryParams(apiEndpoint(this.config.baseUrl, 'responses'), this.config.queryParams), {
|
|
73
85
|
method: 'POST',
|
|
74
86
|
signal: request.signal,
|
|
75
87
|
headers: {
|
|
76
88
|
'Content-Type': 'application/json',
|
|
77
89
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
90
|
+
...this.config.headers,
|
|
78
91
|
},
|
|
79
92
|
body: JSON.stringify({
|
|
80
93
|
model: request.model,
|
|
@@ -627,6 +627,7 @@ export class MessageBridge {
|
|
|
627
627
|
const menuControl = resolvedChannelType === 'aun'
|
|
628
628
|
? parseMenuControl(content)
|
|
629
629
|
: { isMenu: false };
|
|
630
|
+
let menuResponseWarning;
|
|
630
631
|
if (menuControl.isMenu) {
|
|
631
632
|
this.logMenuInbound(channelName, msg, menuControl);
|
|
632
633
|
if (!hasValidMenuId(menuControl)) {
|
|
@@ -649,7 +650,7 @@ export class MessageBridge {
|
|
|
649
650
|
await this.sendMenuResponse(adapter, channelName, msg, menuFailure(header, {
|
|
650
651
|
code: 'MENU_TOKEN_ENCRYPTION_REQUIRED',
|
|
651
652
|
message: 'Menu 请求必须使用 E2EE',
|
|
652
|
-
})
|
|
653
|
+
}));
|
|
653
654
|
return;
|
|
654
655
|
}
|
|
655
656
|
if (!isTokenRequest) {
|
|
@@ -666,6 +667,13 @@ export class MessageBridge {
|
|
|
666
667
|
}));
|
|
667
668
|
return;
|
|
668
669
|
}
|
|
670
|
+
if (!menuTokenRequired && tokenSupplied && !tokenValid) {
|
|
671
|
+
this.logMenuDiagnostic('token-invalid-optional', channelName, msg, menuControl, 'MENU_TOKEN_INVALID');
|
|
672
|
+
menuResponseWarning = {
|
|
673
|
+
code: 'MENU_TOKEN_INVALID',
|
|
674
|
+
message: 'Menu token 无效,请重新申请',
|
|
675
|
+
};
|
|
676
|
+
}
|
|
669
677
|
}
|
|
670
678
|
const validationError = validateMenuRequest(menuControl.request);
|
|
671
679
|
if (validationError) {
|
|
@@ -712,7 +720,7 @@ export class MessageBridge {
|
|
|
712
720
|
});
|
|
713
721
|
const identity = authSubject.identity;
|
|
714
722
|
if (menuControl.isMenu) {
|
|
715
|
-
await this.handleMenuControl(menuControl, channelName, resolvedChannelType, msg, adapter, authSubject);
|
|
723
|
+
await this.handleMenuControl(menuControl, channelName, resolvedChannelType, msg, adapter, authSubject, menuResponseWarning);
|
|
716
724
|
return;
|
|
717
725
|
}
|
|
718
726
|
// 普通消息保留原始日志;控制 payload 使用脱敏元数据日志。
|
|
@@ -1074,7 +1082,7 @@ export class MessageBridge {
|
|
|
1074
1082
|
throw { code: 'NOT_SUPPORTED', message: `Unsupported menu name: ${name}` };
|
|
1075
1083
|
return mapped;
|
|
1076
1084
|
}
|
|
1077
|
-
async handleMenuControl(parsed, channel, channelType, msg, adapter, authSubject) {
|
|
1085
|
+
async handleMenuControl(parsed, channel, channelType, msg, adapter, authSubject, responseWarning) {
|
|
1078
1086
|
if (!hasValidMenuId(parsed))
|
|
1079
1087
|
return;
|
|
1080
1088
|
const header = { id: parsed.id, ...(parsed.name?.trim() ? { name: parsed.name } : {}) };
|
|
@@ -1122,7 +1130,10 @@ export class MessageBridge {
|
|
|
1122
1130
|
}
|
|
1123
1131
|
if (deduped.replayed)
|
|
1124
1132
|
this.logMenuDiagnostic('request-replay', channel, msg, parsed);
|
|
1125
|
-
|
|
1133
|
+
const response = responseWarning && 'data' in deduped.value
|
|
1134
|
+
? { ...deduped.value, warning: responseWarning }
|
|
1135
|
+
: deduped.value;
|
|
1136
|
+
await this.sendMenuResponse(adapter, channel, msg, response);
|
|
1126
1137
|
const deferredEffect = afterResponse;
|
|
1127
1138
|
if (!deduped.replayed && deferredEffect) {
|
|
1128
1139
|
try {
|
|
@@ -1191,7 +1202,7 @@ export class MessageBridge {
|
|
|
1191
1202
|
return menuFailure(header, normalizeMenuError(error));
|
|
1192
1203
|
}
|
|
1193
1204
|
}
|
|
1194
|
-
async sendMenuResponse(adapter, channel, msg, response
|
|
1205
|
+
async sendMenuResponse(adapter, channel, msg, response) {
|
|
1195
1206
|
if (!adapter?.send) {
|
|
1196
1207
|
this.logMenuDiagnostic('transport-unavailable', channel, msg, {
|
|
1197
1208
|
isMenu: true,
|
|
@@ -1208,7 +1219,7 @@ export class MessageBridge {
|
|
|
1208
1219
|
...(msg.replyContext ?? {}),
|
|
1209
1220
|
metadata: {
|
|
1210
1221
|
...(msg.replyContext?.metadata ?? {}),
|
|
1211
|
-
...evolMenuResponseTransportMetadata(
|
|
1222
|
+
...evolMenuResponseTransportMetadata(),
|
|
1212
1223
|
},
|
|
1213
1224
|
};
|
|
1214
1225
|
const envelope = buildEnvelope({
|
|
@@ -890,6 +890,16 @@ export class ResponseEngine {
|
|
|
890
890
|
return;
|
|
891
891
|
renderer.addNotice(text, severity, subtype, true);
|
|
892
892
|
}
|
|
893
|
+
async cancelStaleRunnerStart(agent, sessionId, streamKey, turnLease, taskId) {
|
|
894
|
+
if (this.turnCoordinator.isCurrent(turnLease))
|
|
895
|
+
return false;
|
|
896
|
+
logger.info(`[ResponseEngine] Stale turn detected after agent.runQuery start; cancelling unconsumed stream: session=${sessionId} task=${taskId}`);
|
|
897
|
+
await agent.interrupt(sessionId).catch(interruptError => {
|
|
898
|
+
logger.warn(`[ResponseEngine] Failed to cancel stale runner start: session=${sessionId} task=${taskId} error=${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
|
|
899
|
+
});
|
|
900
|
+
agent.cleanupStream(streamKey);
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
893
903
|
async retryAfterContextRecovery(prompt, opts) {
|
|
894
904
|
const { streamKey, renderer, agent, session, absoluteProjectPath, effectiveSystemPrompt, modelOverride, runtimeEnv, resetTimer, shouldSuppress, proactive, turnLease, } = opts;
|
|
895
905
|
if (!session.agentSessionId || !canCompactAgent(agent)) {
|
|
@@ -911,6 +921,9 @@ export class ResponseEngine {
|
|
|
911
921
|
await this.emitOperationalNotice(renderer, '✅ 压缩完成,继续处理...', 'info', 'compact-retry');
|
|
912
922
|
await renderer.flush();
|
|
913
923
|
const retryStream = await agent.runQuery(session.id, prompt, absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
|
|
924
|
+
if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, turnLease.taskId)) {
|
|
925
|
+
throw new Error('RUNNER_START_SUPERSEDED');
|
|
926
|
+
}
|
|
914
927
|
agent.registerStream(streamKey, retryStream);
|
|
915
928
|
return await this.processEventStream(retryStream, session, agent, renderer, resetTimer, shouldSuppress, proactive, undefined, // 重试分支不调插件钩子
|
|
916
929
|
undefined, turnLease);
|
|
@@ -2331,7 +2344,7 @@ export class ResponseEngine {
|
|
|
2331
2344
|
try {
|
|
2332
2345
|
const selector = { self: selfAid || undefined, peerKey };
|
|
2333
2346
|
const modelResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.model`, selector, { cache: true });
|
|
2334
|
-
const effortResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}
|
|
2347
|
+
const effortResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.effort`, selector, { cache: true });
|
|
2335
2348
|
const modelDecision = constrainResolvedModelForRole({
|
|
2336
2349
|
role: peerRole,
|
|
2337
2350
|
baseagent: normalizedBaseagent.canonical,
|
|
@@ -2342,7 +2355,7 @@ export class ResponseEngine {
|
|
|
2342
2355
|
selfAid: selfAid || undefined,
|
|
2343
2356
|
});
|
|
2344
2357
|
const model = modelDecision.model;
|
|
2345
|
-
const effortField = `baseagents.${normalizedBaseagent.canonical}
|
|
2358
|
+
const effortField = `baseagents.${normalizedBaseagent.canonical}.effort`;
|
|
2346
2359
|
const effortDecision = resolveRuntimeStringField({
|
|
2347
2360
|
selfAid: selfAid || undefined,
|
|
2348
2361
|
role: peerRole,
|
|
@@ -2389,7 +2402,7 @@ export class ResponseEngine {
|
|
|
2389
2402
|
effectiveModelSource = triggerModelDecision.constrained ? 'role' : 'trigger';
|
|
2390
2403
|
}
|
|
2391
2404
|
if (triggerEffortOverride) {
|
|
2392
|
-
const effortField = `baseagents.${normalizedBaseagent.canonical}
|
|
2405
|
+
const effortField = `baseagents.${normalizedBaseagent.canonical}.effort`;
|
|
2393
2406
|
const triggerEffortDecision = resolveRuntimeStringField({
|
|
2394
2407
|
selfAid: selfAid || undefined,
|
|
2395
2408
|
role: peerRole,
|
|
@@ -2766,6 +2779,12 @@ export class ResponseEngine {
|
|
|
2766
2779
|
try {
|
|
2767
2780
|
logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt} apiRetries=${retryScheduler.apiRetryAttemptsMade} agentSessionId=${session.agentSessionId ?? 'none'}`);
|
|
2768
2781
|
const stream = await agent.runQuery(session.id, effectivePrompt, absoluteProjectPath, session.agentSessionId, renderResult?.images.length ? renderResult.images : message.images, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
|
|
2782
|
+
// The turn may be superseded while the runner is still creating its
|
|
2783
|
+
// backend query. Replay the interrupt after runQuery resolves, when
|
|
2784
|
+
// every runner is required to have an addressable cancellation handle.
|
|
2785
|
+
if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, taskId)) {
|
|
2786
|
+
throw new Error('RUNNER_START_SUPERSEDED');
|
|
2787
|
+
}
|
|
2769
2788
|
agent.registerStream(streamKey, stream);
|
|
2770
2789
|
streamRegistered = true;
|
|
2771
2790
|
if (shouldTimeoutRetryAttempt) {
|
|
@@ -82,9 +82,9 @@ export function activeBaseagent(self) {
|
|
|
82
82
|
catch { /* fall through */ }
|
|
83
83
|
return 'claude';
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
86
|
-
function effortField(
|
|
87
|
-
return
|
|
85
|
+
/** 所有 baseagent 统一使用 effort 表达推理强度。 */
|
|
86
|
+
function effortField(_ba) {
|
|
87
|
+
return 'effort';
|
|
88
88
|
}
|
|
89
89
|
// ── 写:单作用域 ──────────────────────────────────────────────────────
|
|
90
90
|
/** 写入指定作用域。patch 中 undefined 不动;显式 null 删除字段。 */
|