evolcore 0.0.33 → 0.0.35-test.0
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 +59 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +38 -4
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +93 -14
- package/dist/agents/codex/diagnostics.js +79 -22
- package/dist/agents/codex-runner.js +245 -47
- package/dist/agents/ecagent-runner.js +90 -27
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/agentmd.js +13 -2
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/msg/managed-operation.js +16 -0
- package/dist/aun/msg/p2p.js +18 -2
- package/dist/channels/aun.js +123 -7
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +39 -14
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +130 -23
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/getmeta-command.js +211 -0
- package/dist/cli/index.js +26 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/memory-command.js +200 -156
- package/dist/cli/thread-command.js +17 -7
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +25 -8
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +22 -3
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/channel-runtime.js +1 -1
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +145 -5
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +24 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/command/topic-slash.js +4 -7
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/event-catalog.js +14 -0
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +47 -2
- package/dist/core/memory/batch-meta.js +133 -0
- package/dist/core/memory/consolidation-context.js +132 -28
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation-trigger.js +15 -7
- package/dist/core/memory/consolidation.js +146 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +50 -19
- package/dist/core/message/aun-ipc-sender.js +19 -1
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +5 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +17 -0
- package/dist/core/message/response-engine.js +598 -50
- package/dist/core/permission/ec-command-parser.js +169 -44
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +60 -62
- package/dist/core/relation/peer-identity.js +87 -12
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +131 -6
- package/dist/core/session/session-title.js +26 -1
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +289 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +6 -1
- package/dist/core/thread/ipc-operation.js +33 -16
- package/dist/core/thread/thread-operation.js +62 -24
- package/dist/core/thread/thread-service.js +258 -51
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +17 -5
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +9 -10
- package/kits/docs/evolcore/fs.md +2 -2
- package/kits/docs/evolcore/group-collaboration.md +3 -3
- package/kits/docs/evolcore/group.md +14 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -16
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/stats.md +1 -1
- package/kits/docs/evolcore/thread-chain.md +30 -14
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.auxiliary.json +9 -0
- package/kits/eck_manifest.json +20 -20
- package/kits/eck_manifest.memory.json +20 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +13 -16
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +8 -5
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/thread-ops/SKILL.md +18 -18
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +12 -12
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +42 -7
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bash-description.md +9 -0
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
|
@@ -10,7 +10,7 @@ import { DEFAULT_PERMISSION_MODE } from '../types.js';
|
|
|
10
10
|
import { agentSessionsDir, resolveRoot } from '../paths.js';
|
|
11
11
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
12
12
|
import { normalizeExecutionPermissionMode, normalizePermissionMode } from '../core/permission/mode.js';
|
|
13
|
-
import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
13
|
+
import { checkBashDescription, checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
14
14
|
import { logger } from '../utils/logger.js';
|
|
15
15
|
import { ErrorType } from '../utils/error-utils.js';
|
|
16
16
|
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
@@ -18,6 +18,7 @@ import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
|
18
18
|
import { resolveEcagentConfig } from './baseagent.js';
|
|
19
19
|
import { buildModelRequestHeaders } from './request-identity.js';
|
|
20
20
|
import { appendRequestPath } from './request-identity.js';
|
|
21
|
+
import { resolveModelSpec } from '../stats/billing.js';
|
|
21
22
|
import { getManagedTaskTempDir } from '../cli/task-context.js';
|
|
22
23
|
import { unsettledInterruptError, withInterruptDeadline, hasTrustedFullAccessContext } from './runner-types.js';
|
|
23
24
|
const PROVIDER_ID = 'evolcore-gateway';
|
|
@@ -436,18 +437,23 @@ async function consumeOpenAiStream(output, body, model) {
|
|
|
436
437
|
throw new Error('[network] Gateway stream ended without a terminal event');
|
|
437
438
|
}
|
|
438
439
|
function createModel(id, baseUrl) {
|
|
439
|
-
const
|
|
440
|
+
const modelId = stripContextWindowSuffix(id);
|
|
441
|
+
const isClaude = modelId.toLowerCase().startsWith('claude-');
|
|
442
|
+
const fallback = isClaude
|
|
443
|
+
? { context_window: 200_000, max_output_tokens: 8_192 }
|
|
444
|
+
: { context_window: 128_000, max_output_tokens: 16_384 };
|
|
445
|
+
const spec = resolveModelSpec(resolveRoot(), id, undefined, fallback);
|
|
440
446
|
return {
|
|
441
|
-
id,
|
|
442
|
-
name:
|
|
447
|
+
id: modelId,
|
|
448
|
+
name: modelId,
|
|
443
449
|
api: 'openai-completions',
|
|
444
450
|
provider: PROVIDER_ID,
|
|
445
451
|
baseUrl,
|
|
446
452
|
reasoning: true,
|
|
447
453
|
input: ['text', 'image'],
|
|
448
454
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
449
|
-
contextWindow:
|
|
450
|
-
maxTokens:
|
|
455
|
+
contextWindow: spec.context_window,
|
|
456
|
+
maxTokens: spec.max_output_tokens,
|
|
451
457
|
compat: {
|
|
452
458
|
maxTokensField: 'max_tokens',
|
|
453
459
|
supportsUsageInStreaming: true,
|
|
@@ -464,7 +470,11 @@ function isWithin(root, candidate) {
|
|
|
464
470
|
const relative = path.relative(root, candidate);
|
|
465
471
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
466
472
|
}
|
|
467
|
-
|
|
473
|
+
/** 路径是否落在任一显式声明的根之下(含根本身)。 */
|
|
474
|
+
function isWithinAny(candidate, roots) {
|
|
475
|
+
return roots.some(root => isWithin(path.resolve(root), candidate));
|
|
476
|
+
}
|
|
477
|
+
export function resolveEcagentToolPath(projectPath, candidate, forWrite = false, bootstrapAgentMdPath, fullaccess = false, personalMdPath, sessionGrants) {
|
|
468
478
|
const root = fs.realpathSync(projectPath);
|
|
469
479
|
const resolved = path.resolve(root, candidate || '.');
|
|
470
480
|
const allowedBootstrapFile = bootstrapAgentMdPath
|
|
@@ -472,7 +482,15 @@ export function resolveEcagentToolPath(projectPath, candidate, forWrite = false,
|
|
|
472
482
|
: undefined;
|
|
473
483
|
const allowedPersonalFile = personalMdPath ? path.resolve(personalMdPath) : undefined;
|
|
474
484
|
const isManagedIdentityFile = resolved === allowedBootstrapFile || resolved === allowedPersonalFile;
|
|
475
|
-
|
|
485
|
+
// 系统会话:模块声明的可读根整体放行,可写文件逐个精确匹配(不含目录)。
|
|
486
|
+
// 声明之外、项目之外的路径仍然拒绝。声明里的路径可能在多个根之间借符号链接互串,
|
|
487
|
+
// 所以放行前统一按真实路径复核,确认真实位置仍落在同一个被授予的边界内。
|
|
488
|
+
// 写只看可写文件(逐个精确匹配),绝不由可读根推导;读只看可读根。
|
|
489
|
+
const sessionWrites = forWrite && (sessionGrants?.writeFiles ?? [])
|
|
490
|
+
.some(file => path.resolve(file) === resolved);
|
|
491
|
+
const sessionReads = !forWrite && isWithinAny(resolved, sessionGrants?.readRoots ?? []);
|
|
492
|
+
const isSessionTarget = sessionReads || sessionWrites;
|
|
493
|
+
if (!fullaccess && !isWithin(root, resolved) && !isManagedIdentityFile && !isSessionTarget) {
|
|
476
494
|
throw new Error(`Path is outside the project: ${candidate}`);
|
|
477
495
|
}
|
|
478
496
|
if (isManagedIdentityFile) {
|
|
@@ -494,7 +512,34 @@ export function resolveEcagentToolPath(projectPath, candidate, forWrite = false,
|
|
|
494
512
|
while (!fs.existsSync(cursor) && cursor !== path.dirname(cursor))
|
|
495
513
|
cursor = path.dirname(cursor);
|
|
496
514
|
const canonical = fs.realpathSync(cursor);
|
|
497
|
-
if (
|
|
515
|
+
if (fullaccess)
|
|
516
|
+
return resolved;
|
|
517
|
+
// 系统会话的写入目标与判定层一致:必须是普通文件,链接一律拒绝。
|
|
518
|
+
// 链接会让 resolved 与真实位置分离,放行等于把洞开到声明之外。
|
|
519
|
+
if (sessionWrites && fs.existsSync(resolved) && fs.lstatSync(resolved).isSymbolicLink()) {
|
|
520
|
+
throw new Error(`Refusing to write through a symbolic link: ${candidate}`);
|
|
521
|
+
}
|
|
522
|
+
// 可写文件尚未创建时,以父目录的真实路径为准;已存在时以文件自身的真实路径为准。
|
|
523
|
+
const canonicalSelf = fs.existsSync(resolved) ? fs.realpathSync(resolved) : canonical;
|
|
524
|
+
if (isSessionTarget) {
|
|
525
|
+
const grantedRoots = [
|
|
526
|
+
...(sessionReads ? sessionGrants?.readRoots ?? [] : []),
|
|
527
|
+
...(sessionWrites ? (sessionGrants?.writeFiles ?? []).filter(file => path.resolve(file) === resolved) : []),
|
|
528
|
+
];
|
|
529
|
+
const canonicalRoots = grantedRoots.flatMap(granted => {
|
|
530
|
+
const inside = fs.existsSync(granted) ? [fs.realpathSync(granted)] : [];
|
|
531
|
+
// 根自身可能尚未创建,或者位于尚未创建的上层目录中;先归到存在的祖先再比。
|
|
532
|
+
let ancestor = granted;
|
|
533
|
+
while (!fs.existsSync(ancestor) && ancestor !== path.dirname(ancestor))
|
|
534
|
+
ancestor = path.dirname(ancestor);
|
|
535
|
+
return [...inside, fs.realpathSync(ancestor)];
|
|
536
|
+
});
|
|
537
|
+
if (!canonicalRoots.some(granted => isWithin(granted, canonical) && isWithin(granted, canonicalSelf))) {
|
|
538
|
+
throw new Error(`Path resolves outside its declared grant: ${candidate}`);
|
|
539
|
+
}
|
|
540
|
+
return resolved;
|
|
541
|
+
}
|
|
542
|
+
if (!isWithin(root, canonical))
|
|
498
543
|
throw new Error(`Path resolves outside the project: ${candidate}`);
|
|
499
544
|
return resolved;
|
|
500
545
|
}
|
|
@@ -547,7 +592,7 @@ async function walkFiles(root, signal) {
|
|
|
547
592
|
function textResult(text, details = {}) {
|
|
548
593
|
return { content: [{ type: 'text', text: truncate(text) }], details };
|
|
549
594
|
}
|
|
550
|
-
function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullaccess = false, personalMdPath) {
|
|
595
|
+
function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullaccess = false, personalMdPath, sessionGrants) {
|
|
551
596
|
const read = {
|
|
552
597
|
name: 'Read', label: 'Read', description: 'Read a UTF-8 text file from the project.',
|
|
553
598
|
parameters: Type.Object({
|
|
@@ -556,7 +601,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
556
601
|
limit: Type.Optional(Type.Number({ minimum: 1 })),
|
|
557
602
|
}),
|
|
558
603
|
async execute(_id, params, signal) {
|
|
559
|
-
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess, personalMdPath);
|
|
604
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess, personalMdPath, sessionGrants);
|
|
560
605
|
const stat = await fs.promises.stat(file);
|
|
561
606
|
if (stat.size > MAX_READ_BYTES)
|
|
562
607
|
throw new Error(`File exceeds ${MAX_READ_BYTES} byte read limit`);
|
|
@@ -573,7 +618,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
573
618
|
name: 'Glob', label: 'Glob', description: 'Find project files matching a glob pattern.',
|
|
574
619
|
parameters: Type.Object({ pattern: Type.String(), path: Type.Optional(Type.String()) }),
|
|
575
620
|
async execute(_id, params, signal) {
|
|
576
|
-
const root = resolveEcagentToolPath(projectPath, params.path ?? '.', false, undefined, fullaccess);
|
|
621
|
+
const root = resolveEcagentToolPath(projectPath, params.path ?? '.', false, undefined, fullaccess, undefined, sessionGrants);
|
|
577
622
|
const matcher = globRegex(params.pattern.replaceAll('\\', '/'));
|
|
578
623
|
const files = (await walkFiles(root, signal))
|
|
579
624
|
.map(file => path.relative(root, file).replaceAll(path.sep, '/'))
|
|
@@ -589,7 +634,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
589
634
|
case_insensitive: Type.Optional(Type.Boolean()), literal: Type.Optional(Type.Boolean()),
|
|
590
635
|
}),
|
|
591
636
|
async execute(_id, params, signal) {
|
|
592
|
-
const root = resolveEcagentToolPath(projectPath, params.path ?? '.', false, undefined, fullaccess);
|
|
637
|
+
const root = resolveEcagentToolPath(projectPath, params.path ?? '.', false, undefined, fullaccess, undefined, sessionGrants);
|
|
593
638
|
const files = (await fs.promises.stat(root)).isFile() ? [root] : await walkFiles(root, signal);
|
|
594
639
|
const fileMatcher = params.glob ? globRegex(params.glob.replaceAll('\\', '/')) : undefined;
|
|
595
640
|
const flags = params.case_insensitive ? 'i' : '';
|
|
@@ -622,7 +667,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
622
667
|
name: 'Write', label: 'Write', description: 'Create or completely overwrite a project file.',
|
|
623
668
|
parameters: Type.Object({ file_path: Type.String(), content: Type.String() }), executionMode: 'sequential',
|
|
624
669
|
async execute(_id, params, signal) {
|
|
625
|
-
const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess, personalMdPath);
|
|
670
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess, personalMdPath, sessionGrants);
|
|
626
671
|
await fs.promises.mkdir(path.dirname(file), { recursive: true });
|
|
627
672
|
await fs.promises.writeFile(file, params.content, { encoding: 'utf8', signal });
|
|
628
673
|
return textResult(`Wrote ${Buffer.byteLength(params.content)} bytes to ${path.relative(projectPath, file)}`, { file });
|
|
@@ -635,7 +680,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
635
680
|
replace_all: Type.Optional(Type.Boolean()),
|
|
636
681
|
}), executionMode: 'sequential',
|
|
637
682
|
async execute(_id, params, signal) {
|
|
638
|
-
const file = resolveEcagentToolPath(projectPath, params.file_path,
|
|
683
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess, personalMdPath, sessionGrants);
|
|
639
684
|
const value = await fs.promises.readFile(file, { encoding: 'utf8', signal });
|
|
640
685
|
if (!params.old_string)
|
|
641
686
|
throw new Error('old_string must not be empty');
|
|
@@ -655,7 +700,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
|
|
|
655
700
|
name: 'Bash', label: 'Bash', description: 'Run a shell command in the project.',
|
|
656
701
|
parameters: Type.Object({
|
|
657
702
|
command: Type.String(), timeout: Type.Optional(Type.Number({ minimum: 1, maximum: 600_000 })),
|
|
658
|
-
description: Type.Optional(Type.String()),
|
|
703
|
+
description: Type.Optional(Type.String({ description: '<命令目标描述> [<风险等级>],风险等级为 低/中/高 之一,例如 "查看工作区未提交的改动 [低]"。缺失或格式不符时调用被拒。' })),
|
|
659
704
|
}), executionMode: 'sequential',
|
|
660
705
|
async execute(_id, params, signal) {
|
|
661
706
|
const result = await env.exec(params.command, {
|
|
@@ -724,7 +769,7 @@ export class EcagentRunner {
|
|
|
724
769
|
configuredHeaders: config.headers,
|
|
725
770
|
}),
|
|
726
771
|
};
|
|
727
|
-
this.model =
|
|
772
|
+
this.model = config.model;
|
|
728
773
|
this.effort = config.effort;
|
|
729
774
|
this.agentAid = runtime?.agentAid;
|
|
730
775
|
this.onSessionIdUpdate = callbacks?.onSessionIdUpdate;
|
|
@@ -734,8 +779,8 @@ export class EcagentRunner {
|
|
|
734
779
|
throw new Error('ecagent requires an EvolCore Agent AID');
|
|
735
780
|
return agentSessionsDir(this.agentAid);
|
|
736
781
|
}
|
|
737
|
-
setModel(model) { this.model =
|
|
738
|
-
getModel() { return this.model; }
|
|
782
|
+
setModel(model) { this.model = model; }
|
|
783
|
+
getModel() { return stripContextWindowSuffix(this.model); }
|
|
739
784
|
resolveModelId(model) { return model; }
|
|
740
785
|
setEffort(effort) { this.effort = effort; }
|
|
741
786
|
getEffort() { return this.effort; }
|
|
@@ -885,7 +930,7 @@ export class EcagentRunner {
|
|
|
885
930
|
models,
|
|
886
931
|
model,
|
|
887
932
|
thinkingLevel: normalizeThinkingLevel(effort),
|
|
888
|
-
tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath, fullaccess, request.modelOverride?.personalMdPath),
|
|
933
|
+
tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath, fullaccess, request.modelOverride?.personalMdPath, this.permissionContexts.get(request.sessionId)?.systemSessionGrants),
|
|
889
934
|
systemPrompt: [DEFAULT_SYSTEM_PROMPT, request.systemPromptAppend].filter(Boolean).join('\n\n'),
|
|
890
935
|
streamOptions: {
|
|
891
936
|
timeoutMs: this.config.timeoutMs,
|
|
@@ -1169,11 +1214,6 @@ export class EcagentRunner {
|
|
|
1169
1214
|
}
|
|
1170
1215
|
async authorizeTool(sessionId, projectPath, mode, toolName, input, turn) {
|
|
1171
1216
|
const permissionContext = this.permissionContexts.get(sessionId);
|
|
1172
|
-
if (mode === 'fullaccess') {
|
|
1173
|
-
return hasTrustedFullAccessContext(permissionContext)
|
|
1174
|
-
? {}
|
|
1175
|
-
: { block: true, reason: 'Untrusted fullaccess execution context' };
|
|
1176
|
-
}
|
|
1177
1217
|
const recordBlockedOperation = async (policyCode, operationInput = input, matchedPath) => {
|
|
1178
1218
|
const summary = summarizeToolInputForAudit(toolName, operationInput).slice(0, 512);
|
|
1179
1219
|
auditToolPreflightDenial({
|
|
@@ -1212,6 +1252,29 @@ export class EcagentRunner {
|
|
|
1212
1252
|
logger.warn(`[EcagentRunner] failed to record blocked operation: session=${sessionId} tool=${toolName} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1213
1253
|
}
|
|
1214
1254
|
};
|
|
1255
|
+
// Bash description 格式约定先于一切放行分支(含 fullaccess 与系统会话)。
|
|
1256
|
+
// TODO(bash-description): 与 claude-runner 同因——该约束缺少验证,先短路放行;
|
|
1257
|
+
// 恢复强制时把两个 runner 的开关一起打开。
|
|
1258
|
+
if (process.env.EVOLCORE_BASH_DESCRIPTION_ENFORCE === '1') {
|
|
1259
|
+
const descriptionCheck = checkBashDescription(toolName, input);
|
|
1260
|
+
if (descriptionCheck) {
|
|
1261
|
+
await recordBlockedOperation(descriptionCheck.policyCode);
|
|
1262
|
+
return { block: true, reason: descriptionCheck.message };
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
if (mode === 'fullaccess') {
|
|
1266
|
+
return hasTrustedFullAccessContext(permissionContext)
|
|
1267
|
+
? {}
|
|
1268
|
+
: { block: true, reason: 'Untrusted fullaccess execution context' };
|
|
1269
|
+
}
|
|
1270
|
+
// 系统会话:模块判定即最终结论,不进入 preflight / permissionMode 分支
|
|
1271
|
+
if (permissionContext?.systemSessionCheck) {
|
|
1272
|
+
const decision = permissionContext.systemSessionCheck(toolName, input);
|
|
1273
|
+
if (decision.allow)
|
|
1274
|
+
return {};
|
|
1275
|
+
await recordBlockedOperation(decision.code);
|
|
1276
|
+
return { block: true, reason: decision.reason };
|
|
1277
|
+
}
|
|
1215
1278
|
const policy = permissionContext?.policyHook?.(toolName, input, { turn });
|
|
1216
1279
|
if (policy?.block) {
|
|
1217
1280
|
await recordBlockedOperation(policy.policyCode ?? 'session_policy_hook');
|
|
@@ -1239,7 +1302,7 @@ export class EcagentRunner {
|
|
|
1239
1302
|
const checkedInput = preflight.input;
|
|
1240
1303
|
if (mode === 'readonly') {
|
|
1241
1304
|
const decision = checkReadonly(toolName, checkedInput, projectPath, {
|
|
1242
|
-
sessionId, managedTempDir: permissionContext?.managedTempDir, channel: permissionContext?.channel, peerId: permissionContext?.userId, role: permissionContext?.role,
|
|
1305
|
+
sessionId, managedTempDir: permissionContext?.managedTempDir, grantedReadRoots: permissionContext?.grantedReadRoots, channel: permissionContext?.channel, peerId: permissionContext?.userId, role: permissionContext?.role,
|
|
1243
1306
|
allowReadonlySourceDiagnostics: permissionContext?.allowReadonlySourceDiagnostics === true,
|
|
1244
1307
|
});
|
|
1245
1308
|
if (decision.behavior === 'deny') {
|
|
@@ -1351,7 +1414,7 @@ export class EcagentRunner {
|
|
|
1351
1414
|
? cached
|
|
1352
1415
|
: await this.openExistingRuntime(agentSessionId, cwd);
|
|
1353
1416
|
this.onCompactStart?.(sessionId);
|
|
1354
|
-
const compactModel =
|
|
1417
|
+
const compactModel = modelOverride?.model || this.model;
|
|
1355
1418
|
const compactEffort = modelOverride?.effortMode === 'model_default'
|
|
1356
1419
|
? undefined
|
|
1357
1420
|
: modelOverride?.effort ?? this.effort;
|
|
@@ -136,10 +136,9 @@ export function isOneMillionContextModel(model) {
|
|
|
136
136
|
return ONE_M_CONTEXT_MODEL_PREFIXES.some(prefix => base === prefix || base.startsWith(`${prefix}-`));
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
|
-
*
|
|
140
|
-
* every other model falls back to 200,000
|
|
141
|
-
* This is an operational guardrail, not a claim about
|
|
142
|
-
* advertised capacity.
|
|
139
|
+
* Hardcoded context-window fallback for models missing from the parameter table:
|
|
140
|
+
* known 1M models use 1,000,000; every other model falls back to 200,000.
|
|
141
|
+
* This is an operational guardrail, not a claim about advertised capacity.
|
|
143
142
|
*/
|
|
144
143
|
export function realContextWindowForModel(model) {
|
|
145
144
|
return isOneMillionContextModel(model) ? 1000000 : 200000;
|
|
@@ -159,8 +158,8 @@ export function autoCompactWindowForModel(_model) {
|
|
|
159
158
|
* at the right point for small-context models (200k/128k), where EvolCore's
|
|
160
159
|
* fixed 650k threshold would never trigger.
|
|
161
160
|
*/
|
|
162
|
-
export function sdkAutoCompactWindowForModel(model) {
|
|
163
|
-
const contextWindow = realContextWindowForModel(model);
|
|
161
|
+
export function sdkAutoCompactWindowForModel(model, resolvedContextWindow) {
|
|
162
|
+
const contextWindow = resolvedContextWindow ?? realContextWindowForModel(model);
|
|
164
163
|
const reserve = Math.min(10_000, Math.floor(contextWindow * 0.05));
|
|
165
164
|
return Math.min(650_000, Math.max(1, contextWindow - reserve));
|
|
166
165
|
}
|
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -179,8 +179,19 @@ export function parseAgentDisplayName(content) {
|
|
|
179
179
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
|
|
180
180
|
if (!frontmatter)
|
|
181
181
|
return undefined;
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
try {
|
|
183
|
+
const document = parseDocument(frontmatter);
|
|
184
|
+
if (document.errors.length > 0)
|
|
185
|
+
return undefined;
|
|
186
|
+
const fields = document.toJS();
|
|
187
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields))
|
|
188
|
+
return undefined;
|
|
189
|
+
const name = fields.name;
|
|
190
|
+
return typeof name === 'string' ? name.trim() || undefined : undefined;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
184
195
|
}
|
|
185
196
|
function displayNameKey(aid, aunPath) {
|
|
186
197
|
const root = path.resolve(aunPath ?? resolveRoot());
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -39,7 +39,7 @@ export function suppressSdkLogs() {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
// ==================== SDK package ====================
|
|
42
|
-
export const MIN_AUN_CORE_SDK = [0, 5,
|
|
42
|
+
export const MIN_AUN_CORE_SDK = [0, 5, 20];
|
|
43
43
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
44
44
|
function compareVersion(a, min) {
|
|
45
45
|
const parts = a.split('.').map(n => parseInt(n, 10));
|
package/dist/aun/aid/identity.js
CHANGED
|
@@ -224,7 +224,7 @@ export async function aidCreate(aid, opts) {
|
|
|
224
224
|
// 新注册流程:AIDStore.register → downloadCaRoot → load → authenticate
|
|
225
225
|
const store = await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
226
226
|
try {
|
|
227
|
-
const regResult = await store.register(aid);
|
|
227
|
+
const regResult = await store.register(aid, { type: 'agent' });
|
|
228
228
|
if (!regResult.ok) {
|
|
229
229
|
const e = new Error(regResult.error.message);
|
|
230
230
|
e.code = regResult.error.code;
|
|
@@ -597,6 +597,9 @@ export class ManagedMsgOperationError extends Error {
|
|
|
597
597
|
}
|
|
598
598
|
const HISTORY_VALUE_FLAGS = new Set([
|
|
599
599
|
'--session', '--limit', '--before', '--after', '--direction', '--format', '--channel-key', '--peer-id',
|
|
600
|
+
// 话题过滤是托管协作的常规用法(文档 `thread-chain.md` 明确写了
|
|
601
|
+
// `ec msg history --thread <id>`);只会收窄结果集,不扩大可读范围。
|
|
602
|
+
'--thread',
|
|
600
603
|
]);
|
|
601
604
|
function failMsg(code, reason) {
|
|
602
605
|
return { ok: false, code, reason };
|
|
@@ -720,6 +723,15 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
720
723
|
return failMsg('INVALID_ARGUMENT', '--after must be earlier than --before');
|
|
721
724
|
}
|
|
722
725
|
const target = channelKey === undefined ? String(positionals[0] || '').replace(/^@/, '') : undefined;
|
|
726
|
+
const threadId = values.get('--thread');
|
|
727
|
+
if (threadId !== undefined) {
|
|
728
|
+
if (kind !== 'history')
|
|
729
|
+
return failMsg('INVALID_ARGUMENT', '--thread is only supported for history');
|
|
730
|
+
if (channelKey !== undefined)
|
|
731
|
+
return failMsg('INVALID_ARGUMENT', '--thread cannot be combined with --channel-key/--peer-id');
|
|
732
|
+
if (!/^thread-[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(threadId))
|
|
733
|
+
return failMsg('INVALID_ARGUMENT', 'invalid --thread id');
|
|
734
|
+
}
|
|
723
735
|
const canonicalArgv = ['msg', kind, from];
|
|
724
736
|
if (target !== undefined)
|
|
725
737
|
canonicalArgv.push(target);
|
|
@@ -735,6 +747,8 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
735
747
|
canonicalArgv.push('--after', values.get('--after'));
|
|
736
748
|
if (values.has('--direction'))
|
|
737
749
|
canonicalArgv.push('--direction', values.get('--direction'));
|
|
750
|
+
if (threadId !== undefined)
|
|
751
|
+
canonicalArgv.push('--thread', threadId);
|
|
738
752
|
return {
|
|
739
753
|
ok: true,
|
|
740
754
|
command: {
|
|
@@ -749,6 +763,7 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
749
763
|
before,
|
|
750
764
|
after,
|
|
751
765
|
direction,
|
|
766
|
+
...(threadId !== undefined ? { threadId } : {}),
|
|
752
767
|
},
|
|
753
768
|
};
|
|
754
769
|
}
|
|
@@ -766,6 +781,7 @@ export async function executeManagedMsgOperation(command) {
|
|
|
766
781
|
before: command.before,
|
|
767
782
|
after: command.after,
|
|
768
783
|
direction: command.direction,
|
|
784
|
+
threadId: command.threadId,
|
|
769
785
|
})
|
|
770
786
|
: await msgOnline({
|
|
771
787
|
from: command.from,
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -279,8 +279,24 @@ export async function msgSend(args) {
|
|
|
279
279
|
catch { /* ignore */ }
|
|
280
280
|
}
|
|
281
281
|
// 2. 决定 chatmode(遵循来源1-3)
|
|
282
|
-
// 私聊:非 human 对端 → proactive
|
|
283
|
-
|
|
282
|
+
// 私聊:非 human 对端 → proactive;人类对端只有 owner/admin 才 interactive,
|
|
283
|
+
// 其余(含角色未知)按 proactive 保守处理。判定逻辑集中在 peer-mode。
|
|
284
|
+
const { chatmodeForPeerIdentity } = await import('../../core/message/peer-mode.js');
|
|
285
|
+
const { resolvePeerRoleDetail } = await import('../../config/peer-role-resolver.js');
|
|
286
|
+
let peerRole = null;
|
|
287
|
+
try {
|
|
288
|
+
peerRole = resolvePeerRoleDetail({
|
|
289
|
+
selfAid: args.from,
|
|
290
|
+
channelKey: `aun#${args.from}#main`,
|
|
291
|
+
channelType: 'aun',
|
|
292
|
+
chatType: 'private',
|
|
293
|
+
actorId: args.to,
|
|
294
|
+
conversationId: args.to,
|
|
295
|
+
peerType: peerIdentity.type,
|
|
296
|
+
}).effectiveRole;
|
|
297
|
+
}
|
|
298
|
+
catch { /* 角色解析失败时按未知处理(proactive) */ }
|
|
299
|
+
const chatmode = chatmodeForPeerIdentity(peerIdentity, peerRole);
|
|
284
300
|
// 3. 构建 payload
|
|
285
301
|
if (!payload) {
|
|
286
302
|
payload = buildSimplePayload(args.body);
|
package/dist/channels/aun.js
CHANGED
|
@@ -557,6 +557,24 @@ function setIfDefined(target, key, value) {
|
|
|
557
557
|
if (value !== undefined)
|
|
558
558
|
target[key] = value;
|
|
559
559
|
}
|
|
560
|
+
function truncateRawErrorForDisplay(raw) {
|
|
561
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
562
|
+
return undefined;
|
|
563
|
+
const text = raw.trim();
|
|
564
|
+
const htmlStart = text.toLowerCase().indexOf('<html');
|
|
565
|
+
return htmlStart >= 0 ? text.slice(0, htmlStart).trimEnd() : text;
|
|
566
|
+
}
|
|
567
|
+
function projectedRawError(raw, displayRaw) {
|
|
568
|
+
if (typeof raw !== 'string')
|
|
569
|
+
return raw;
|
|
570
|
+
return raw.trim() ? displayRaw : raw;
|
|
571
|
+
}
|
|
572
|
+
function appendRawErrorText(text, raw) {
|
|
573
|
+
const displayRaw = truncateRawErrorForDisplay(raw);
|
|
574
|
+
if (!displayRaw || text.includes(displayRaw))
|
|
575
|
+
return text;
|
|
576
|
+
return `${text}\n原因:${displayRaw}`;
|
|
577
|
+
}
|
|
560
578
|
/**
|
|
561
579
|
* 把 AUNChannel 投递的 opts 映射成渠道无关的 InboundMessage。
|
|
562
580
|
*
|
|
@@ -662,6 +680,8 @@ export class AUNChannel {
|
|
|
662
680
|
traceWriter = null;
|
|
663
681
|
eventBus = null;
|
|
664
682
|
queuedHandler = null;
|
|
683
|
+
/** `topic-renamed` 消费器,由 runtime 注入(见 setThreadRenameHandler)。 */
|
|
684
|
+
threadRenameHandler = null;
|
|
665
685
|
groupInviteHandler;
|
|
666
686
|
groupInviteConnectedHandler;
|
|
667
687
|
groupInviteDeliveryGate;
|
|
@@ -1391,6 +1411,44 @@ export class AUNChannel {
|
|
|
1391
1411
|
return sanitized;
|
|
1392
1412
|
return undefined;
|
|
1393
1413
|
}
|
|
1414
|
+
/**
|
|
1415
|
+
* 处理对端 `topic-renamed` 通知:校验字段后交给注入的改名消费器落索引,
|
|
1416
|
+
* 成功后发 `thread:renamed` 让前端刷新。全程不进入 Agent 消息队列。
|
|
1417
|
+
* 通知是尽力而为的,任何失败只记日志,不影响后续消息处理。
|
|
1418
|
+
*/
|
|
1419
|
+
applyInboundTopicRename(params) {
|
|
1420
|
+
const payload = params.payload && typeof params.payload === 'object' ? params.payload : null;
|
|
1421
|
+
const threadId = typeof payload?.threadId === 'string' ? payload.threadId : undefined;
|
|
1422
|
+
const name = this.sanitizeTopicName(payload?.topicName);
|
|
1423
|
+
const nameUpdatedAt = typeof payload?.nameUpdatedAt === 'number' && Number.isFinite(payload.nameUpdatedAt)
|
|
1424
|
+
? payload.nameUpdatedAt : undefined;
|
|
1425
|
+
const applier = this.threadRenameHandler;
|
|
1426
|
+
if (!applier || !threadId || !name || nameUpdatedAt === undefined || !params.peer) {
|
|
1427
|
+
logger.debug(`${this.logPrefix()} topic-renamed ignored: peer=${params.peer || '<none>'} thread=${threadId || '<none>'} hasName=${!!name}`);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
void applier({
|
|
1431
|
+
selfAid: params.selfAid, peer: params.peer, peerKind: params.peerKind,
|
|
1432
|
+
threadId, name, nameUpdatedAt,
|
|
1433
|
+
}).then(result => {
|
|
1434
|
+
if (!result.applied) {
|
|
1435
|
+
logger.debug(`${this.logPrefix()} topic-renamed not applied (${result.reason ?? 'unknown'}): thread=${threadId}`);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
this.eventBus?.publish?.({
|
|
1439
|
+
type: 'thread:renamed',
|
|
1440
|
+
sessionId: result.sessionId,
|
|
1441
|
+
peer: params.peer,
|
|
1442
|
+
threadId,
|
|
1443
|
+
name,
|
|
1444
|
+
nameUpdatedAt,
|
|
1445
|
+
by: params.senderAid ?? '',
|
|
1446
|
+
});
|
|
1447
|
+
logger.info(`${this.logPrefix()} topic-renamed applied: thread=${threadId} name=${JSON.stringify(name)}`);
|
|
1448
|
+
}).catch(error => {
|
|
1449
|
+
logger.warn(`${this.logPrefix()} topic-renamed failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1394
1452
|
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, groupId, label, topicStatus) {
|
|
1395
1453
|
if (!groupId)
|
|
1396
1454
|
throw new Error('buildGroupReplyContext requires groupId');
|
|
@@ -2519,6 +2577,14 @@ export class AUNChannel {
|
|
|
2519
2577
|
if (p2pPayloadType === 'text') {
|
|
2520
2578
|
import('../utils/msg-diag.js').then(m => m.diagLog('aun_in', { aid: this.config.aid, from: fromAid, to: this.config.aid, msgId: messageId, text })).catch(() => { });
|
|
2521
2579
|
}
|
|
2580
|
+
// topic-renamed:对端话题改名通知。只更新本端话题索引,不进 Agent 队列。
|
|
2581
|
+
if (p2pPayloadType === 'topic-renamed') {
|
|
2582
|
+
this.applyInboundTopicRename({
|
|
2583
|
+
payload, selfAid: this.config.aid, peer: fromAid, peerKind: 'private',
|
|
2584
|
+
senderAid: fromAid,
|
|
2585
|
+
});
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2522
2588
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
2523
2589
|
if (p2pPayloadType === 'action_card_reply') {
|
|
2524
2590
|
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
@@ -2650,6 +2716,14 @@ export class AUNChannel {
|
|
|
2650
2716
|
logger.debug(`${this.logPrefix()} Group dropped observer control envelope: mid=${messageId}`);
|
|
2651
2717
|
return;
|
|
2652
2718
|
}
|
|
2719
|
+
// topic-renamed:群内话题改名通知。只更新本端索引,不进 Agent 队列。
|
|
2720
|
+
if (inboundType === 'topic-renamed') {
|
|
2721
|
+
this.applyInboundTopicRename({
|
|
2722
|
+
payload, selfAid: this.config.aid, peer: groupId || senderAid, peerKind: 'group',
|
|
2723
|
+
senderAid,
|
|
2724
|
+
});
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2653
2727
|
const payloadMentionEntries = this.parsePayloadMentionsOrReject(payload, 'group.inbound', messageId);
|
|
2654
2728
|
if (!payloadMentionEntries)
|
|
2655
2729
|
return;
|
|
@@ -3760,6 +3834,13 @@ export class AUNChannel {
|
|
|
3760
3834
|
}, delayMs);
|
|
3761
3835
|
}
|
|
3762
3836
|
// ── Public API (same interface as before) ───────────────────
|
|
3837
|
+
/**
|
|
3838
|
+
* 对端发来的话题改名通知(`topic-renamed`)。由 runtime 注入,交给
|
|
3839
|
+
* ThreadService.applyRemoteRename 落本地索引;不进入 Agent 消息队列。
|
|
3840
|
+
*/
|
|
3841
|
+
setThreadRenameHandler(handler) {
|
|
3842
|
+
this.threadRenameHandler = handler;
|
|
3843
|
+
}
|
|
3763
3844
|
setEventBus(bus) {
|
|
3764
3845
|
// 重新订阅前先解掉旧的——避免 reload/重连后 listener 累积
|
|
3765
3846
|
if (this.eventBus && this.queuedHandler && typeof this.eventBus.unsubscribe === 'function') {
|
|
@@ -4606,7 +4687,23 @@ export class AUNChannel {
|
|
|
4606
4687
|
return { ok: false, error: 'AUN channel is not connected', code: 'AUN_NOT_CONNECTED' };
|
|
4607
4688
|
}
|
|
4608
4689
|
const info = await this.fetchPeerInfo(args.to);
|
|
4609
|
-
|
|
4690
|
+
// 与 p2p 发送路径共用同一判定:非人类 → proactive;人类需 owner/admin 才 interactive。
|
|
4691
|
+
const { chatmodeForPeerIdentity } = await import('../core/message/peer-mode.js');
|
|
4692
|
+
let peerRole = null;
|
|
4693
|
+
try {
|
|
4694
|
+
const { resolvePeerRoleDetail } = await import('../config/peer-role-resolver.js');
|
|
4695
|
+
peerRole = resolvePeerRoleDetail({
|
|
4696
|
+
selfAid: this.config.aid,
|
|
4697
|
+
channelKey: `aun#${this.config.aid}#main`,
|
|
4698
|
+
channelType: 'aun',
|
|
4699
|
+
chatType: 'private',
|
|
4700
|
+
actorId: args.to,
|
|
4701
|
+
conversationId: args.to,
|
|
4702
|
+
peerType: info.type ?? undefined,
|
|
4703
|
+
}).effectiveRole;
|
|
4704
|
+
}
|
|
4705
|
+
catch { /* 角色解析失败按未知处理 */ }
|
|
4706
|
+
const chatmode = chatmodeForPeerIdentity({ isMachine: info.type !== 'human', type: info.type ?? undefined }, peerRole);
|
|
4610
4707
|
if (!finalPayload.chatmode)
|
|
4611
4708
|
finalPayload.chatmode = chatmode;
|
|
4612
4709
|
const parentCausation = normalizeCausation(args.causation);
|
|
@@ -6415,32 +6512,41 @@ export class AUNChannelPlugin {
|
|
|
6415
6512
|
text: payload.text,
|
|
6416
6513
|
severity: 'info',
|
|
6417
6514
|
};
|
|
6515
|
+
setIfDefined(noticePayload, 'raw', payload.raw);
|
|
6418
6516
|
setIfDefined(noticePayload, 'subtype', payload.subtype);
|
|
6419
6517
|
await channel.sendReliableStructured(channelId, noticePayload, replyCtx, payload.text);
|
|
6420
6518
|
return;
|
|
6421
6519
|
}
|
|
6422
6520
|
case 'system.error': {
|
|
6521
|
+
const raw = truncateRawErrorForDisplay(payload.raw);
|
|
6522
|
+
const projectedRaw = projectedRawError(payload.raw, raw);
|
|
6423
6523
|
const errorPayload = {
|
|
6424
6524
|
type: payload.recoverable === true ? 'notice' : 'error',
|
|
6425
6525
|
...taskBase(),
|
|
6426
6526
|
subtype: payload.subtype,
|
|
6427
|
-
text: payload.
|
|
6527
|
+
text: payload.recoverable === true
|
|
6528
|
+
? payload.text
|
|
6529
|
+
: appendRawErrorText(payload.text, raw),
|
|
6530
|
+
...(projectedRaw !== undefined ? { raw: projectedRaw } : {}),
|
|
6531
|
+
...(payload.reason !== undefined ? { reason: payload.reason } : {}),
|
|
6428
6532
|
...(payload.recoverable === true
|
|
6429
6533
|
? { severity: 'warn' }
|
|
6430
|
-
: {
|
|
6534
|
+
: { recoverable: false }),
|
|
6431
6535
|
};
|
|
6432
6536
|
await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6433
6537
|
return;
|
|
6434
6538
|
}
|
|
6435
6539
|
case 'result.error': {
|
|
6540
|
+
const raw = truncateRawErrorForDisplay(payload.raw);
|
|
6541
|
+
const projectedRaw = projectedRawError(payload.raw, raw);
|
|
6436
6542
|
const errorPayload = {
|
|
6437
6543
|
type: 'error',
|
|
6438
6544
|
...taskBase(),
|
|
6439
|
-
|
|
6440
|
-
text: payload.text,
|
|
6441
|
-
message: payload.text,
|
|
6442
|
-
user_message: payload.text,
|
|
6545
|
+
text: appendRawErrorText(payload.text, raw),
|
|
6443
6546
|
};
|
|
6547
|
+
setIfDefined(errorPayload, 'subtype', payload.subtype);
|
|
6548
|
+
setIfDefined(errorPayload, 'reason', payload.reason);
|
|
6549
|
+
setIfDefined(errorPayload, 'raw', projectedRaw);
|
|
6444
6550
|
await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6445
6551
|
return;
|
|
6446
6552
|
}
|
|
@@ -6766,6 +6872,16 @@ export class AUNChannelPlugin {
|
|
|
6766
6872
|
},
|
|
6767
6873
|
registerHooks(hookCtx) {
|
|
6768
6874
|
channel.setEventBus(hookCtx.eventBus);
|
|
6875
|
+
// 话题改名通知消费器:直接改本端索引,不经过 Agent 队列。
|
|
6876
|
+
channel.setThreadRenameHandler(async (input) => {
|
|
6877
|
+
const { ThreadService } = await import('../core/thread/thread-service.js');
|
|
6878
|
+
const service = new ThreadService(hookCtx.sessionManager);
|
|
6879
|
+
const result = await service.applyRemoteRename(input);
|
|
6880
|
+
const session = result.applied
|
|
6881
|
+
? service.findInIndex(input.selfAid, input.peer, input.threadId)?.sessionId
|
|
6882
|
+
: undefined;
|
|
6883
|
+
return { applied: result.applied, reason: result.reason, sessionId: session };
|
|
6884
|
+
});
|
|
6769
6885
|
if (channel.setOnChannelDown) {
|
|
6770
6886
|
channel.setOnChannelDown(() => {
|
|
6771
6887
|
hookCtx.eventBus.publish({
|