evolcore 0.0.10 → 0.0.12

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.
Files changed (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -9,7 +9,9 @@ import { requestDangerousCommandPermission } from '../core/permission/approval-g
9
9
  import { normalizePermissionMode } from '../core/permission/mode.js';
10
10
  import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
11
11
  import { logger } from '../utils/logger.js';
12
+ import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
12
13
  import { resolveEcagentConfig } from './baseagent.js';
14
+ import { buildModelRequestHeaders } from './request-identity.js';
13
15
  const PROVIDER_ID = 'evolcore-gateway';
14
16
  const DEFAULT_SYSTEM_PROMPT = `You are ecagent, the coding agent built into EvolCore.
15
17
  Work directly in the current project. Inspect relevant files before changing them, keep edits scoped, and verify the result.
@@ -252,6 +254,7 @@ function createOpenAiTransport(config) {
252
254
  authorization: `Bearer ${config.apiKey}`,
253
255
  'content-type': 'application/json',
254
256
  ...Object.fromEntries(Object.entries(options?.headers ?? {}).filter((entry) => entry[1] !== null)),
257
+ ...config.headers,
255
258
  },
256
259
  body: JSON.stringify(payload),
257
260
  signal: controller.signal,
@@ -678,7 +681,15 @@ export class EcagentRunner {
678
681
  onCompactStart;
679
682
  modelCache;
680
683
  constructor(config, callbacks) {
681
- this.config = config;
684
+ this.config = {
685
+ ...config,
686
+ headers: buildModelRequestHeaders({
687
+ baseagent: 'ecagent',
688
+ baseUrl: config.baseUrl,
689
+ agentAid: config.evolcoreAgentAid,
690
+ configuredHeaders: config.headers,
691
+ }),
692
+ };
682
693
  this.model = config.model;
683
694
  this.effort = config.effort;
684
695
  this.onSessionIdUpdate = callbacks?.onSessionIdUpdate;
@@ -714,7 +725,11 @@ export class EcagentRunner {
714
725
  try {
715
726
  const signal = AbortSignal.timeout(MODEL_LIST_TIMEOUT_MS);
716
727
  const response = await fetch(`${this.config.baseUrl.replace(/\/+$/, '')}/models`, {
717
- headers: { authorization: `Bearer ${this.config.apiKey}` }, signal,
728
+ headers: {
729
+ authorization: `Bearer ${this.config.apiKey}`,
730
+ ...this.config.headers,
731
+ },
732
+ signal,
718
733
  });
719
734
  if (!response.ok)
720
735
  throw new Error(`Failed to list ecagent models: HTTP ${response.status}`);
@@ -943,19 +958,43 @@ export class EcagentRunner {
943
958
  }
944
959
  async authorizeTool(sessionId, projectPath, mode, toolName, input) {
945
960
  const permissionContext = this.permissionContexts.get(sessionId);
961
+ const recordBlockedOperation = async (policyCode, operationInput = input) => {
962
+ try {
963
+ await permissionContext?.recordExecutionAnomaly?.({
964
+ code: 'operation_blocked',
965
+ severity: 'warning',
966
+ phase: 'execution',
967
+ occurredAt: Date.now(),
968
+ toolName,
969
+ policyCode,
970
+ summary: summarizeToolInputForAudit(toolName, operationInput).slice(0, 512),
971
+ effect: 'operation_skipped',
972
+ });
973
+ }
974
+ catch (error) {
975
+ logger.warn(`[EcagentRunner] failed to record blocked operation: session=${sessionId} tool=${toolName} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
976
+ }
977
+ };
946
978
  const policy = permissionContext?.policyHook?.(toolName, input);
947
- if (policy?.block)
979
+ if (policy?.block) {
980
+ await recordBlockedOperation(policy.policyCode ?? 'session_policy_hook');
948
981
  return { block: true, reason: policy.reason || '当前会话策略拒绝此工具调用' };
982
+ }
949
983
  const preflight = evaluateToolPreflight(toolName, input, {
950
984
  sessionId,
985
+ selfAid: permissionContext?.selfAid,
951
986
  channel: permissionContext?.channel,
952
987
  userId: permissionContext?.userId,
953
988
  role: permissionContext?.role,
954
989
  permissionMode: mode,
955
990
  projectPath,
956
991
  });
957
- if (preflight.behavior === 'deny')
992
+ if (preflight.behavior === 'deny') {
993
+ if (preflight.policyCode) {
994
+ await recordBlockedOperation(preflight.policyCode, input);
995
+ }
958
996
  return { block: true, reason: preflight.message };
997
+ }
959
998
  if (preflight.behavior === 'allow')
960
999
  return {};
961
1000
  const checkedInput = preflight.input;
@@ -963,11 +1002,19 @@ export class EcagentRunner {
963
1002
  const decision = checkReadonly(toolName, checkedInput, projectPath, {
964
1003
  sessionId, channel: permissionContext?.channel, peerId: permissionContext?.userId, role: permissionContext?.role,
965
1004
  });
966
- return decision.behavior === 'deny' ? { block: true, reason: decision.message } : {};
1005
+ if (decision.behavior === 'deny') {
1006
+ await recordBlockedOperation('readonly_mode', checkedInput);
1007
+ return { block: true, reason: decision.message };
1008
+ }
1009
+ return {};
967
1010
  }
968
1011
  const dangerous = checkDangerousCommand(toolName, checkedInput);
969
1012
  if (mode === 'auto') {
970
- return dangerous.isDangerous ? { block: true, reason: `危险操作已由 auto 模式拒绝:${dangerous.reason}` } : {};
1013
+ if (dangerous.isDangerous) {
1014
+ await recordBlockedOperation('auto_dangerous_command', checkedInput);
1015
+ return { block: true, reason: `危险操作已由 auto 模式拒绝:${dangerous.reason}` };
1016
+ }
1017
+ return {};
971
1018
  }
972
1019
  const prompt = permissionContext?.sendPrompt ?? this.sendPromptFn;
973
1020
  const decision = await requestDangerousCommandPermission(this.permissionGateway, sessionId, toolName, checkedInput, prompt, permissionContext, `ecagent:${mode}`, mode);
@@ -344,6 +344,7 @@ export class GeminiRunner {
344
344
  cwd: projectPath,
345
345
  stdio: ['pipe', 'pipe', 'pipe'],
346
346
  env,
347
+ windowsHide: process.platform === 'win32',
347
348
  });
348
349
  this.activeProcesses.set(sessionId, child);
349
350
  // Log stderr
@@ -0,0 +1,55 @@
1
+ import { logger } from '../utils/logger.js';
2
+ import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
3
+ export const EVOLCORE_REQUEST_HEADERS = {
4
+ version: 'X-EvolCore-Version',
5
+ agentAid: 'X-EvolCore-Agent-AID',
6
+ baseagent: 'X-EvolCore-Base-Agent',
7
+ };
8
+ const RESERVED_HEADER_NAMES = new Set(Object.values(EVOLCORE_REQUEST_HEADERS).map(name => name.toLowerCase()));
9
+ const warnedReservedHeaders = new Set();
10
+ function officialHostname(baseagent) {
11
+ return baseagent === 'claude' ? 'api.anthropic.com' : 'api.openai.com';
12
+ }
13
+ export function isOfficialModelEndpoint(baseagent, baseUrl) {
14
+ const configured = baseUrl?.trim();
15
+ if (!configured)
16
+ return true;
17
+ try {
18
+ return new URL(configured).hostname.toLowerCase() === officialHostname(baseagent);
19
+ }
20
+ catch {
21
+ // Invalid URLs fail at the request layer. Treat them as configured here so
22
+ // identity behavior does not silently change if URL validation is relaxed.
23
+ return false;
24
+ }
25
+ }
26
+ function configuredHeadersWithoutReserved(baseagent, configuredHeaders) {
27
+ const headers = {};
28
+ for (const [name, value] of Object.entries(configuredHeaders ?? {})) {
29
+ const normalizedName = name.toLowerCase();
30
+ if (!RESERVED_HEADER_NAMES.has(normalizedName)) {
31
+ headers[name] = value;
32
+ continue;
33
+ }
34
+ const warningKey = `${baseagent}:${normalizedName}`;
35
+ if (!warnedReservedHeaders.has(warningKey)) {
36
+ warnedReservedHeaders.add(warningKey);
37
+ logger.warn(`[RequestIdentity] Ignoring configured reserved header ${JSON.stringify(name)} for ${baseagent}; EvolCore manages this header at runtime`);
38
+ }
39
+ }
40
+ return headers;
41
+ }
42
+ export function buildModelRequestHeaders(input) {
43
+ const headers = configuredHeadersWithoutReserved(input.baseagent, input.configuredHeaders);
44
+ if (isOfficialModelEndpoint(input.baseagent, input.baseUrl))
45
+ return headers;
46
+ headers[EVOLCORE_REQUEST_HEADERS.version] = readInstalledEvolcoreVersion();
47
+ const agentAid = input.agentAid?.trim();
48
+ if (agentAid)
49
+ headers[EVOLCORE_REQUEST_HEADERS.agentAid] = agentAid;
50
+ headers[EVOLCORE_REQUEST_HEADERS.baseagent] = input.baseagent;
51
+ return headers;
52
+ }
53
+ export function _resetRequestIdentityWarningsForTests() {
54
+ warnedReservedHeaders.clear();
55
+ }
@@ -1,8 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { execFileSync } from 'child_process';
5
- import { isWindows } from '../../utils/cross-platform.js';
6
4
  /**
7
5
  * Suppress SDK console logs (DEBUG/INFO/WARN) in CLI context.
8
6
  * Call once at CLI entry point — NOT at module load time, to avoid
@@ -40,10 +38,9 @@ export function suppressSdkLogs() {
40
38
  return _origStderrWrite(chunk, encoding, callback);
41
39
  };
42
40
  }
43
- // ==================== Constants ====================
44
- export const MIN_AUN_CORE_SDK = [0, 4, 3];
41
+ // ==================== SDK package ====================
42
+ export const MIN_AUN_CORE_SDK = [0, 5, 6];
45
43
  export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
46
- // ==================== SDK & Environment ====================
47
44
  function compareVersion(a, min) {
48
45
  const parts = a.split('.').map(n => parseInt(n, 10));
49
46
  if (parts.length < 3 || parts.some(isNaN))
@@ -57,6 +54,7 @@ function compareVersion(a, min) {
57
54
  export function isAunSdkVersionOk(version) {
58
55
  return compareVersion(version, MIN_AUN_CORE_SDK);
59
56
  }
57
+ /** Resolve only the SDK bundled in EvolCore's own dependency tree. */
60
58
  export function resolveAunCoreSdkPkg() {
61
59
  try {
62
60
  let dir = path.dirname(fileURLToPath(import.meta.url));
@@ -73,28 +71,20 @@ export function resolveAunCoreSdkPkg() {
73
71
  dir = parent;
74
72
  }
75
73
  }
76
- catch { /* fall through */ }
77
- try {
78
- const npmCmd = isWindows ? 'npm.cmd' : 'npm';
79
- const globalRoot = execFileSync(npmCmd, ['root', '-g'], {
80
- encoding: 'utf-8', timeout: 10000, shell: isWindows,
81
- }).trim();
82
- const pkgPath = path.join(globalRoot, AUN_CORE_SDK_PKG, 'package.json');
83
- if (fs.existsSync(pkgPath)) {
84
- const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
85
- return { version: data.version, path: pkgPath };
86
- }
87
- }
88
- catch { /* not found */ }
74
+ catch { /* invalid or missing package metadata */ }
89
75
  return null;
90
76
  }
77
+ /**
78
+ * Compatibility guard retained for callers that explicitly check SDK readiness.
79
+ * The SDK is bundled with EvolCore and must never be installed independently.
80
+ */
91
81
  export async function ensureAunSdk() {
92
82
  const installed = resolveAunCoreSdkPkg();
93
83
  if (installed && isAunSdkVersionOk(installed.version))
94
84
  return;
95
- const { npmInstallGlobal } = await import('../../utils/npm-ops.js');
96
- console.log(`正在安装 ${AUN_CORE_SDK_PKG}@latest...`);
97
- await npmInstallGlobal(`${AUN_CORE_SDK_PKG}@latest`);
85
+ const expected = MIN_AUN_CORE_SDK.join('.');
86
+ const detected = installed?.version ?? 'missing';
87
+ throw new Error(`${AUN_CORE_SDK_PKG} ${detected} is not the bundled SDK expected by EvolCore (>= ${expected}); reinstall or upgrade EvolCore`);
98
88
  }
99
89
  export function isAunSdkReady() {
100
90
  const installed = resolveAunCoreSdkPkg();
@@ -0,0 +1,107 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { resolveRoot } from '../../paths.js';
4
+ import { agentmdGet, agentmdPut } from './agentmd.js';
5
+ import { aidLookup, aidShow } from './identity.js';
6
+ import { isValidAid } from './validation.js';
7
+ export class ManagedAidOperationError extends Error {
8
+ code;
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.code = code;
12
+ this.name = 'ManagedAidOperationError';
13
+ }
14
+ }
15
+ function normalizeAid(value) {
16
+ const aid = String(value || '').trim();
17
+ return aid.startsWith('@') ? aid.slice(1) : aid;
18
+ }
19
+ function hasOnlyOptionalJsonFormat(args, aidIndex) {
20
+ const suffix = args.slice(aidIndex + 1);
21
+ return suffix.length === 0 || (suffix.length === 2 && suffix[0] === '--format' && suffix[1] === 'json');
22
+ }
23
+ function parseAid(value) {
24
+ const aid = normalizeAid(value);
25
+ if (!aid)
26
+ return { ok: false, code: 'INVALID_ARGUMENT', reason: 'missing AID' };
27
+ if (!isValidAid(aid))
28
+ return { ok: false, code: 'INVALID_AID', reason: `invalid AID: ${aid}` };
29
+ return { ok: true, aid };
30
+ }
31
+ /**
32
+ * The daemon accepts only the self-scoped AID commands exposed to managed
33
+ * tasks. Parsing here is deliberately narrower than the standalone CLI.
34
+ */
35
+ export function resolveManagedAidOperation(argv) {
36
+ const args = argv[0] === 'aid' ? argv.slice(1) : argv;
37
+ const subcommand = args[0];
38
+ if ((subcommand === 'show' || subcommand === 'lookup') && hasOnlyOptionalJsonFormat(args, 1)) {
39
+ const parsedAid = parseAid(args[1]);
40
+ if (!parsedAid.ok)
41
+ return parsedAid;
42
+ return {
43
+ ok: true,
44
+ command: {
45
+ kind: subcommand,
46
+ aid: parsedAid.aid,
47
+ operationId: `ec.aid.${subcommand}`,
48
+ canonicalArgv: ['aid', subcommand, parsedAid.aid],
49
+ },
50
+ };
51
+ }
52
+ if (subcommand === 'agentmd' && (args[1] === 'get' || args[1] === 'put') && hasOnlyOptionalJsonFormat(args, 2)) {
53
+ const parsedAid = parseAid(args[2]);
54
+ if (!parsedAid.ok)
55
+ return parsedAid;
56
+ const kind = `agentmd.${args[1]}`;
57
+ return {
58
+ ok: true,
59
+ command: {
60
+ kind,
61
+ aid: parsedAid.aid,
62
+ operationId: `ec.aid.${kind}`,
63
+ canonicalArgv: ['aid', 'agentmd', args[1], parsedAid.aid],
64
+ },
65
+ };
66
+ }
67
+ return {
68
+ ok: false,
69
+ code: 'NOT_ALLOWED',
70
+ reason: 'Managed AID operations only allow exact self-scoped show, lookup, agentmd get, or agentmd put commands with an optional --format json suffix',
71
+ };
72
+ }
73
+ const defaultDependencies = () => ({
74
+ root: resolveRoot(),
75
+ aidShow,
76
+ aidLookup,
77
+ agentmdGet,
78
+ agentmdPut,
79
+ existsSync: fs.existsSync,
80
+ readFileSync: fs.readFileSync,
81
+ });
82
+ /** Execute a validated AID operation from the daemon-owned host filesystem. */
83
+ export async function executeManagedAidOperation(command, overrides = {}) {
84
+ const deps = { ...defaultDependencies(), ...overrides };
85
+ const aunPath = deps.root;
86
+ switch (command.kind) {
87
+ case 'show':
88
+ return { kind: command.kind, aid: command.aid, value: await deps.aidShow(command.aid, { aunPath }) };
89
+ case 'lookup':
90
+ return { kind: command.kind, aid: command.aid, value: await deps.aidLookup(command.aid) };
91
+ case 'agentmd.get':
92
+ return {
93
+ kind: command.kind,
94
+ aid: command.aid,
95
+ value: await deps.agentmdGet(command.aid, { aunPath, withVerification: true }),
96
+ };
97
+ case 'agentmd.put': {
98
+ const localPath = path.join(aunPath, 'AIDs', command.aid, 'agent.md');
99
+ if (!deps.existsSync(localPath)) {
100
+ throw new ManagedAidOperationError('LOCAL_AGENTMD_MISSING', `local agent.md not found: ${command.aid}`);
101
+ }
102
+ const content = deps.readFileSync(localPath, 'utf-8');
103
+ await deps.agentmdPut(content, { aid: command.aid, aunPath });
104
+ return { kind: command.kind, aid: command.aid, path: localPath };
105
+ }
106
+ }
107
+ }
@@ -13,6 +13,7 @@ import { readBestTaskRuntimeContext } from '../../cli/task-context.js';
13
13
  import { ipcQuery } from '../../ipc.js';
14
14
  import { resolvePaths } from '../../paths.js';
15
15
  import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
16
+ import { normalizeAunMentionEntries } from './mention-schema.js';
16
17
  function buildGroupPayload(body) {
17
18
  return body.mode === 'text' ? { type: 'text', text: body.text } : { ...body.payload };
18
19
  }
@@ -189,8 +190,18 @@ export async function groupSend(args) {
189
190
  break;
190
191
  }
191
192
  }
192
- if (args.mentions && args.mentions.length > 0) {
193
- payload.mentions = args.mentions;
193
+ const payloadMentions = Object.prototype.hasOwnProperty.call(payload, 'mentions')
194
+ ? normalizeAunMentionEntries(payload.mentions)
195
+ : undefined;
196
+ if (args.mentions !== undefined) {
197
+ const normalizedMentions = normalizeAunMentionEntries(args.mentions);
198
+ if (normalizedMentions.length > 0)
199
+ payload.mentions = normalizedMentions;
200
+ else if (payloadMentions !== undefined)
201
+ payload.mentions = payloadMentions;
202
+ }
203
+ else if (payloadMentions !== undefined) {
204
+ payload.mentions = payloadMentions;
194
205
  }
195
206
  applyGroupRoutingPayloadFields(payload, args, runtimeContext);
196
207
  const encrypt = resolveAunMessageEncrypt(args.encrypt);
@@ -305,8 +316,9 @@ export async function groupInfo(args) {
305
316
  }
306
317
  }
307
318
  export async function groupList(args) {
308
- const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
319
+ let conn;
309
320
  try {
321
+ conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
310
322
  const params = {};
311
323
  if (args.size !== undefined)
312
324
  params.size = args.size;
@@ -329,7 +341,8 @@ export async function groupList(args) {
329
341
  return formatRpcError(e);
330
342
  }
331
343
  finally {
332
- await conn.close();
344
+ if (conn)
345
+ await conn.close();
333
346
  }
334
347
  }
335
348
  export async function groupUpdate(args) {
@@ -675,7 +688,7 @@ export async function groupUpdateRules(args) {
675
688
  }
676
689
  }
677
690
  const GROUP_RULES_FILE_PATH = '/rules.md';
678
- const MAX_GROUP_RULES_BYTES = 4 * 1024;
691
+ export const MAX_GROUP_RULES_BYTES = 4 * 1024;
679
692
  const MTIME_TOLERANCE_MS = 1;
680
693
  export async function groupRulesFileGet(args) {
681
694
  return withAunClient(args.from, args, async (client) => {
@@ -742,13 +755,26 @@ export async function groupRulesFileGet(args) {
742
755
  }
743
756
  export async function groupRulesFileSet(args) {
744
757
  return withAunClient(args.from, args, async (client) => {
745
- assertLocalRulesFile(args.filePath);
746
- const upload = await client.group.fs.cp(args.filePath, `${args.groupId}:${GROUP_RULES_FILE_PATH}`, {
747
- force: true,
748
- overwrite: true,
749
- parents: true,
750
- contentType: 'text/markdown; charset=utf-8',
751
- });
758
+ if ((args.filePath === undefined) === (args.content === undefined)) {
759
+ throw new Error('规则文件必须且只能提供 filePath content');
760
+ }
761
+ let upload;
762
+ if (args.content !== undefined) {
763
+ const bytes = Buffer.from(args.content, 'utf8');
764
+ if (!args.content || bytes.byteLength > MAX_GROUP_RULES_BYTES || bytes.toString('utf8') !== args.content) {
765
+ throw new Error(`规则内容必须是非空 UTF-8,且不超过 ${MAX_GROUP_RULES_BYTES} bytes`);
766
+ }
767
+ upload = await uploadGroupRulesBytes(client, args.groupId, bytes);
768
+ }
769
+ else {
770
+ assertLocalRulesFile(args.filePath);
771
+ upload = await client.group.fs.cp(args.filePath, `${args.groupId}:${GROUP_RULES_FILE_PATH}`, {
772
+ force: true,
773
+ overwrite: true,
774
+ parents: true,
775
+ contentType: 'text/markdown; charset=utf-8',
776
+ });
777
+ }
752
778
  const published = await publishCurrentRulesFile(client, args.groupId, args.from);
753
779
  return { ...published, upload };
754
780
  });
@@ -984,6 +1010,49 @@ function assertLocalRulesFile(filePath) {
984
1010
  throw new Error(`规则文件不是有效 UTF-8: ${errorMessage(e)}`);
985
1011
  }
986
1012
  }
1013
+ async function uploadGroupRulesBytes(client, groupId, bytes) {
1014
+ const remotePath = `${groupId}:${GROUP_RULES_FILE_PATH}`;
1015
+ const base = {
1016
+ path: remotePath,
1017
+ size_bytes: bytes.byteLength,
1018
+ sha256: sha256Hex(bytes),
1019
+ content_type: 'text/markdown; charset=utf-8',
1020
+ force: true,
1021
+ parents: true,
1022
+ };
1023
+ const check = await client.call('group.fs.check_upload', base);
1024
+ if (isRecord(check) && check.within_limit === false) {
1025
+ throw new Error('规则文件超过 group.fs 上传限制');
1026
+ }
1027
+ if (isRecord(check) && (check.instant === true || check.dedup_hit === true || check.skip_upload === true)) {
1028
+ return client.call('group.fs.complete_upload', {
1029
+ ...base,
1030
+ skip_blob: true,
1031
+ ...(check.session_id !== undefined ? { session_id: check.session_id } : {}),
1032
+ });
1033
+ }
1034
+ const session = await client.call('group.fs.create_upload_session', base);
1035
+ if (!isRecord(session))
1036
+ throw new Error('group.fs.create_upload_session returned invalid response');
1037
+ const uploadUrl = stringField(session, ['upload_url', 'url']);
1038
+ if (!uploadUrl)
1039
+ throw new Error('group.fs.create_upload_session did not return upload_url');
1040
+ const headers = {};
1041
+ if (isRecord(session.headers)) {
1042
+ for (const [key, value] of Object.entries(session.headers)) {
1043
+ if (value !== undefined && value !== null)
1044
+ headers[key] = String(value);
1045
+ }
1046
+ }
1047
+ if (!Object.keys(headers).some(key => key.toLowerCase() === 'content-type')) {
1048
+ headers['Content-Type'] = base.content_type;
1049
+ }
1050
+ await client.group.fs.lowlevel.httpPut(uploadUrl, bytes, headers);
1051
+ return client.call('group.fs.complete_upload', {
1052
+ ...base,
1053
+ ...((session.session_id ?? session.id) !== undefined ? { session_id: session.session_id ?? session.id } : {}),
1054
+ });
1055
+ }
987
1056
  async function readGroupRulesBytes(client, groupId) {
988
1057
  const remoteRef = `${groupId}:${GROUP_RULES_FILE_PATH}`;
989
1058
  const ticket = await client.call('group.fs.create_download_ticket', { path: remoteRef });