evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import { resolvePaths } from '../paths.js';
3
3
  import { LogWriter } from './log-writer.js';
4
- import { classifyToolErrorCode, normalizeToolErrorCode } from '../core/permission/tool-error-code.js';
4
+ import { normalizeToolErrorClassification } from '../core/permission/tool-error-code.js';
5
5
  import { normalizePermissionMode } from '../core/permission/mode.js';
6
6
  import { buildToolLifecycleEventKey, normalizeToolCorrelation } from '../core/audit/event-key.js';
7
7
  let currentLevel = process.env.LOG_LEVEL || 'INFO';
@@ -114,21 +114,40 @@ export function normalizeStructuredLog(data) {
114
114
  ?? (isToolResult && isError === true ? 'error' : undefined)
115
115
  ?? (isToolResult && isError === false ? 'allow' : undefined)
116
116
  ?? (isToolUse ? 'pending' : undefined);
117
- const executed = data.executed ?? nested?.executed
117
+ const explicitExecuted = data.executed ?? nested?.executed;
118
+ const explicitExecutionState = data.executionState ?? data.execution_state
119
+ ?? nested?.executionState ?? nested?.execution_state;
120
+ const executed = explicitExecuted
118
121
  ?? (isToolResult ? true : isToolUse ? false : undefined);
119
- const executionState = data.executionState ?? data.execution_state
120
- ?? nested?.executionState ?? nested?.execution_state
122
+ const executionState = explicitExecutionState
121
123
  ?? (isToolResult ? (isError === true ? 'failed' : 'completed') : isToolUse ? 'requested' : undefined);
124
+ const exitCode = data.exitCode ?? data.exit_code ?? nested?.exitCode ?? nested?.exit_code;
122
125
  const explicitErrorCode = data.errorCode
123
126
  ?? data.error_code
124
127
  ?? nested?.errorCode
125
128
  ?? nested?.error_code;
126
- const errorCode = isToolResult && isError === true
127
- ? classifyToolErrorCode({ errorCode: explicitErrorCode, decisionSource })
128
- : explicitErrorCode;
129
- const classificationMissing = isToolResult && isError === true
130
- && !normalizeToolErrorCode(explicitErrorCode)
131
- && decisionSource !== 'policy' && decisionSource !== 'approval';
129
+ const explicitErrorDetailCode = data.errorDetailCode
130
+ ?? data.detailCode
131
+ ?? data.detail_code
132
+ ?? nested?.errorDetailCode
133
+ ?? nested?.detailCode
134
+ ?? nested?.detail_code;
135
+ const classification = isToolResult && isError === true
136
+ ? normalizeToolErrorClassification({
137
+ errorCode: explicitErrorCode,
138
+ errorDetailCode: explicitErrorDetailCode,
139
+ decisionSource,
140
+ executed: explicitExecuted,
141
+ executionState: explicitExecutionState,
142
+ exitCode,
143
+ policyCode: data.policyCode ?? nested?.policyCode,
144
+ })
145
+ : undefined;
146
+ const errorCode = classification?.errorCode ?? explicitErrorCode;
147
+ const errorDetailCode = classification?.errorDetailCode
148
+ ?? (typeof explicitErrorDetailCode === 'string' ? explicitErrorDetailCode : undefined);
149
+ const classificationMissing = classification?.classificationMissing || data.classificationMissing === true || nested?.classificationMissing === true;
150
+ const classificationConflict = classification?.classificationConflict || data.classificationConflict === true || nested?.classificationConflict === true;
132
151
  const lifecycleEventKey = lifecycleRecord
133
152
  ? (eventKey ?? buildToolLifecycleEventKey({
134
153
  sessionId,
@@ -155,7 +174,10 @@ export function normalizeStructuredLog(data) {
155
174
  ...(executed !== undefined ? { executed } : {}),
156
175
  ...(executionState ? { executionState } : {}),
157
176
  ...(errorCode ? { errorCode } : {}),
177
+ ...(errorDetailCode ? { errorDetailCode } : {}),
178
+ ...(exitCode !== undefined ? { exitCode } : {}),
158
179
  ...(classificationMissing ? { classificationMissing: true } : {}),
180
+ ...(classificationConflict ? { classificationConflict: true } : {}),
159
181
  ...(missingContextFields.length > 0 ? { contextMissing: missingContextFields } : {}),
160
182
  };
161
183
  }
@@ -11,6 +11,9 @@ import path from 'path';
11
11
  import fs from 'fs';
12
12
  import crypto from 'crypto';
13
13
  import { logger } from './logger.js';
14
+ import { agentRelationDir } from '../paths.js';
15
+ import { parsePeerKey } from '../core/relation/peer-identity.js';
16
+ import { isSameOrDescendant } from '../core/permission/protected-paths.js';
14
17
  // ── 常量 ──────────────────────────────────────────────────────────────────────
15
18
  const DEFAULT_MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10 MB
16
19
  const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
@@ -18,6 +21,14 @@ const ALLOWED_IMAGE_MIMES = new Set([
18
21
  'image/png', 'image/jpeg', 'image/gif', 'image/webp',
19
22
  ]);
20
23
  const UPLOADS_SUBDIR = '.evolcore/uploads';
24
+ const RELATION_ATTACHMENT_ID_RE = /^[a-f0-9]{32}$/;
25
+ const RELATION_ATTACHMENT_MAX_BYTES = DEFAULT_MAX_FILE_SIZE;
26
+ const RELATION_ATTACHMENT_EXTENSIONS = {
27
+ 'image/png': '.png',
28
+ 'image/jpeg': '.jpg',
29
+ 'image/gif': '.gif',
30
+ 'image/webp': '.webp',
31
+ };
21
32
  // ── SSRF 防护 ─────────────────────────────────────────────────────────────────
22
33
  /** 已知安全的 CDN 域名白名单 */
23
34
  const ALLOWED_CDN_HOSTS = new Set([
@@ -101,6 +112,79 @@ export async function validateImage(buffer, opts) {
101
112
  }
102
113
  return { mime: type.mime };
103
114
  }
115
+ function relationAttachmentsDir(selfAid, peerKey) {
116
+ parsePeerKey(peerKey);
117
+ return path.join(agentRelationDir(selfAid, peerKey), 'attachments');
118
+ }
119
+ /** Save one inbound image as a stable, peer-scoped attachment owned by the daemon. */
120
+ export function saveRelationAttachment(buffer, selfAid, peerKey, mimeType) {
121
+ const extension = RELATION_ATTACHMENT_EXTENSIONS[mimeType];
122
+ if (!extension)
123
+ throw new Error(`Unsupported relation attachment MIME type: ${mimeType}`);
124
+ if (buffer.length < 1 || buffer.length > RELATION_ATTACHMENT_MAX_BYTES) {
125
+ throw new Error(`Relation attachment must be between 1 byte and ${RELATION_ATTACHMENT_MAX_BYTES} bytes`);
126
+ }
127
+ const directory = relationAttachmentsDir(selfAid, peerKey);
128
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
129
+ if (process.platform !== 'win32')
130
+ fs.chmodSync(directory, 0o700);
131
+ const attachmentId = crypto.randomBytes(16).toString('hex');
132
+ const filename = `${attachmentId}${extension}`;
133
+ const filePath = path.join(directory, filename);
134
+ const metadataPath = path.join(directory, `${attachmentId}.json`);
135
+ const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
136
+ const metadata = {
137
+ version: 1, attachmentId, selfAid, peerKey, filename, mimeType,
138
+ sha256, size: buffer.length, createdAt: Date.now(),
139
+ };
140
+ fs.writeFileSync(filePath, buffer, { flag: 'wx', mode: 0o600 });
141
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata), { flag: 'wx', mode: 0o600 });
142
+ return { ...metadata, filePath };
143
+ }
144
+ /** Resolve an attachment only inside the authenticated Agent/peer relation. */
145
+ export function readRelationAttachment(selfAid, peerKey, attachmentSource) {
146
+ const attachmentId = RELATION_ATTACHMENT_ID_RE.test(attachmentSource)
147
+ ? attachmentSource
148
+ : path.basename(attachmentSource, path.extname(attachmentSource));
149
+ if (!RELATION_ATTACHMENT_ID_RE.test(attachmentId))
150
+ throw new Error('Invalid attachment ID');
151
+ const directory = relationAttachmentsDir(selfAid, peerKey);
152
+ const metadataPath = path.join(directory, `${attachmentId}.json`);
153
+ const metadataStat = fs.lstatSync(metadataPath);
154
+ if (!metadataStat.isFile() || metadataStat.isSymbolicLink())
155
+ throw new Error('Attachment metadata must be a regular file');
156
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
157
+ if (metadata.version !== 1 || metadata.attachmentId !== attachmentId
158
+ || metadata.selfAid !== selfAid || metadata.peerKey !== peerKey
159
+ || RELATION_ATTACHMENT_EXTENSIONS[metadata.mimeType] === undefined
160
+ || metadata.filename !== `${attachmentId}${RELATION_ATTACHMENT_EXTENSIONS[metadata.mimeType]}`) {
161
+ throw new Error('Attachment ownership metadata mismatch');
162
+ }
163
+ const filePath = path.join(directory, metadata.filename);
164
+ const stat = fs.lstatSync(filePath);
165
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== metadata.size
166
+ || stat.size < 1 || stat.size > RELATION_ATTACHMENT_MAX_BYTES) {
167
+ throw new Error('Attachment file is missing or invalid');
168
+ }
169
+ const realDirectory = fs.realpathSync(directory);
170
+ const realPath = fs.realpathSync(filePath);
171
+ if (!isSameOrDescendant(realPath, realDirectory))
172
+ throw new Error('Attachment path escaped its relation directory');
173
+ if (path.isAbsolute(attachmentSource) && fs.realpathSync(attachmentSource) !== realPath) {
174
+ throw new Error('Attachment path does not match the current relation');
175
+ }
176
+ const buffer = fs.readFileSync(filePath);
177
+ const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
178
+ if (sha256 !== metadata.sha256)
179
+ throw new Error('Attachment content hash mismatch');
180
+ return {
181
+ attachment: {
182
+ attachmentId, selfAid, peerKey, filePath: realPath,
183
+ mimeType: metadata.mimeType, sha256, size: buffer.length,
184
+ },
185
+ buffer,
186
+ };
187
+ }
104
188
  /**
105
189
  * 把已下载的附件 Buffer 转成入站图片条目(供 baseagent 视觉通道)。
106
190
  *
@@ -0,0 +1,97 @@
1
+ /**
2
+ * msg-diag: 消息生命周期诊断打点
3
+ * 各处理环节调用 diagLog(),写入专用 msg-diag.jsonl。
4
+ * ec watch diag 读取该文件,固定行实时展示各环节最新处理的消息。
5
+ */
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ function getLogPath() {
10
+ const home = process.env.EVOLCORE_HOME ?? path.join(os.homedir(), '.evolcore');
11
+ return path.join(home, 'logs', 'msg-diag.jsonl');
12
+ }
13
+ let _logPath = null;
14
+ let _fd = null;
15
+ const MAX_SIZE = 500 * 1024; // 500 KB
16
+ const TRIM_SIZE = 250 * 1024; // 超限后保留最后 250 KB
17
+ function trimIfNeeded(p) {
18
+ try {
19
+ const stat = fs.statSync(p);
20
+ if (stat.size <= MAX_SIZE)
21
+ return;
22
+ // 读最后 TRIM_SIZE 字节,从第一个完整行开始保留
23
+ const fd = fs.openSync(p, 'r');
24
+ const buf = Buffer.alloc(TRIM_SIZE);
25
+ fs.readSync(fd, buf, 0, TRIM_SIZE, stat.size - TRIM_SIZE);
26
+ fs.closeSync(fd);
27
+ const str = buf.toString('utf-8');
28
+ const firstNl = str.indexOf('\n');
29
+ const kept = firstNl >= 0 ? str.slice(firstNl + 1) : str;
30
+ fs.writeFileSync(p, kept, 'utf-8');
31
+ }
32
+ catch {
33
+ // 修剪失败不阻塞主流程
34
+ }
35
+ }
36
+ function getFd() {
37
+ try {
38
+ if (!_logPath)
39
+ _logPath = getLogPath();
40
+ const dir = path.dirname(_logPath);
41
+ if (!fs.existsSync(dir))
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ // 每次写前检查大小,超限则修剪并重新打开
44
+ if (_fd !== null) {
45
+ try {
46
+ const stat = fs.statSync(_logPath);
47
+ if (stat.size > MAX_SIZE) {
48
+ fs.closeSync(_fd);
49
+ _fd = null;
50
+ trimIfNeeded(_logPath);
51
+ }
52
+ }
53
+ catch {
54
+ fs.closeSync(_fd);
55
+ _fd = null;
56
+ }
57
+ }
58
+ if (_fd === null) {
59
+ _fd = fs.openSync(_logPath, 'a');
60
+ }
61
+ return _fd;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ function shortId(id) {
68
+ if (!id)
69
+ return '';
70
+ return id.split('.')[0];
71
+ }
72
+ function cut(text, n) {
73
+ if (!text)
74
+ return '';
75
+ const s = text.replace(/\r?\n/g, ' ').trim();
76
+ return s.length <= n ? s : s.slice(0, n - 1) + '…';
77
+ }
78
+ export function diagLog(stage, data) {
79
+ try {
80
+ const entry = {
81
+ stage,
82
+ ts: new Date().toISOString(),
83
+ aid: shortId(data.aid ?? ''),
84
+ from: shortId(data.from ?? ''),
85
+ to: shortId(data.to ?? ''),
86
+ msgId: data.msgId ?? '',
87
+ taskId: data.taskId,
88
+ preview: cut(data.text ?? '', 50),
89
+ };
90
+ const fd = getFd();
91
+ if (fd !== null)
92
+ fs.writeSync(fd, JSON.stringify(entry) + '\n');
93
+ }
94
+ catch {
95
+ // 诊断打点失败不影响主流程
96
+ }
97
+ }
@@ -94,6 +94,26 @@ function checkedWelcomePath(aid) {
94
94
  }
95
95
  return target;
96
96
  }
97
+ /**
98
+ * Welcome state is physically shared under data/channel-state, so the key
99
+ * must carry the owning Agent identity. Runtime channel instances already
100
+ * pass an effective key; accepting a raw instance name here keeps the helper
101
+ * usable in tests and embedded callers without weakening the boundary.
102
+ */
103
+ function effectiveWelcomeChannelKey(channelType, agentAid, channelName) {
104
+ const parts = channelName.split('#');
105
+ if (parts.length === 3) {
106
+ const [type, aid, name] = parts;
107
+ if (type !== channelType || aid !== agentAid || !name) {
108
+ throw new Error(`welcome channel key does not belong to Agent ${agentAid}: ${channelName}`);
109
+ }
110
+ return channelName;
111
+ }
112
+ if (parts.length !== 1 || !channelName) {
113
+ throw new Error(`invalid welcome channel name: ${channelName}`);
114
+ }
115
+ return `${channelType}#${agentAid}#${channelName}`;
116
+ }
97
117
  export function saveAgentWelcomeTemplate(aid, template) {
98
118
  const validation = validateWelcomeTemplate(template);
99
119
  if (!validation.valid)
@@ -322,6 +342,7 @@ export class FirstInteractionWelcomeManager {
322
342
  agentAid;
323
343
  channelName;
324
344
  greetedUsersFile;
345
+ effectiveChannelKey;
325
346
  greetedUsers = new Set();
326
347
  initializedCache = null; // 缓存 initialized 状态,避免重复 I/O
327
348
  constructor(channelType, agentAid, channelName) {
@@ -329,7 +350,8 @@ export class FirstInteractionWelcomeManager {
329
350
  this.agentAid = agentAid;
330
351
  this.channelName = channelName;
331
352
  // 每个 channel instance 独立的已欢迎用户记录
332
- this.greetedUsersFile = channelStatePath(channelName, 'welcome.jsonl');
353
+ this.effectiveChannelKey = effectiveWelcomeChannelKey(channelType, agentAid, channelName);
354
+ this.greetedUsersFile = channelStatePath(this.effectiveChannelKey, 'welcome.jsonl');
333
355
  this.loadGreetedUsers();
334
356
  }
335
357
  /**
@@ -403,7 +425,7 @@ export class FirstInteractionWelcomeManager {
403
425
  }
404
426
  }
405
427
  if (this.greetedUsers.size > 0) {
406
- logger.debug(`[WelcomeManager] Loaded ${this.greetedUsers.size} greeted users for ${this.channelName}`);
428
+ logger.debug(`[WelcomeManager] Loaded ${this.greetedUsers.size} greeted users for ${this.effectiveChannelKey}`);
407
429
  }
408
430
  }
409
431
  catch (e) {
@@ -431,10 +453,17 @@ export function initWelcomeManager(channelType, agentAid, channelName) {
431
453
  * @returns 是否发送了欢迎消息
432
454
  */
433
455
  export async function sendWelcomeIfNeeded(welcomeManager, userId, channelId, sendMessage, channelName) {
434
- void welcomeManager;
435
- void userId;
436
- void channelId;
437
- void sendMessage;
438
- void channelName;
439
- return false;
456
+ if (!welcomeManager || !welcomeManager.shouldGreet(userId))
457
+ return false;
458
+ try {
459
+ const welcomeText = welcomeManager.generateWelcomeText();
460
+ await sendMessage(channelId, welcomeText);
461
+ welcomeManager.markGreeted(userId);
462
+ logger.info(`[${channelName}] Sent first interaction welcome to ${userId}`);
463
+ return true;
464
+ }
465
+ catch (error) {
466
+ logger.warn(`[${channelName}] Failed to send first interaction welcome to ${userId}:`, error);
467
+ return false;
468
+ }
440
469
  }
@@ -0,0 +1,314 @@
1
+ # ECK 文档写作与维护指南
2
+
3
+ > **读者:人与 Agent 两类。** ECK 由人机共同维护——人做设计判断,Agent 做具体增改。
4
+ > 因此本文的规则要同时满足两个要求:人看得懂判据,Agent 能按判据执行。
5
+ >
6
+ > 相关文档:查阅文档见 `GUIDE.md`;manifest 注入机制见 `context-assembly.md`;路径与索引机制见 `$KITS_RULES/02-navigation.md`。
7
+
8
+ ---
9
+
10
+ ## 零、原则总览
11
+
12
+ | 原则 | 防什么 | 可自动排查 |
13
+ |------|--------|:---------:|
14
+ | §一 文件位置 | 放错目录破坏机器约定 | ❌ |
15
+ | §二 正交性 | 同一概念多处定义、随时间漂移 | 部分(grep) |
16
+ | §三 注入成本 | `rules/` 膨胀,每轮白烧 token | ❌ |
17
+ | §四 命名与路径 | 引用无法解析 | 部分 |
18
+ | §五 可发现性 | 文档存在但找不到 | ❌ |
19
+ | §六 引用完整性 | 悬空链接 / 语义失效 | 仅悬空可查 |
20
+ | §七 粒度阈值 | 无意义碎片化 | ❌ |
21
+ | §八 权威裁决 | 代码改了文档没改,不知信谁 | ❌ |
22
+ | §九 示例一致性 | 照抄示例失败 | ❌ |
23
+
24
+ **真正能自动排查的只有"悬空引用"一项**,其余全靠流程纪律——这是检查清单(§十)比看起来更重要的原因。
25
+
26
+ ---
27
+
28
+ ## 一、文件放哪里
29
+
30
+ `kits/` 下的目录有明确分工,放错位置会破坏约定或让文档失去可发现性:
31
+
32
+ | 目录 | 内容性质 | 谁读 | 能否放文档 |
33
+ |------|---------|------|-----------|
34
+ | `rules/` | 自动注入的机制骨架(01-06) | 每次会话自动加载 | ❌ 只放这 6 个 |
35
+ | `docs/` | 按需 Read 的知识文档 | Agent 按需 | ✅ **人读的文档都放这里** |
36
+ | `schemas/` | 代码消费的 schema 数据 | `schema-registry.ts` 扫描 | ❌ 只放 `*.schema.*.json` + `_meta.json` |
37
+ | `templates/` | prompt 组装模板 | 渲染引擎 | ❌ 只放模板 |
38
+ | `migrations/` | 迁移数据 | 迁移逻辑 | 仅 README 例外 |
39
+
40
+ **判据**:`schemas/`、`templates/` 是**机器读的数据目录**,命名和格式有程序依赖。人类阅读的说明文档一律放 `docs/` 下对应子目录。在数据目录里放 `.md` 会破坏命名约定,也让后来者误判目录职责。
41
+
42
+ `migrations/README.md` 是唯一被接受的反例——README 与其描述的数据同放是通用惯例,且它在子目录内,不污染顶层命名空间。
43
+
44
+ ### `templates/` 下的子目录
45
+
46
+ 模板按**被注入的会话类型**分子目录,不要平铺:
47
+
48
+ | 子目录 | 用途 | 谁引用 |
49
+ |------|------|--------|
50
+ | `system-fragments/` | 主会话的系统提示词片段(identity / session / commands 等);`memory.md` 例外,主会话与整理会话共用 | `eck_manifest.json`(`memory.md` 另被 `eck_manifest.memory.json` 引用) |
51
+ | `message-fragments/` | 消息信封渲染片段 | `eck_message_manifest.json` |
52
+ | `memory-consolidation/` | 整理会话(`sessionType=memory`)的 prompt | `eck_manifest.memory.json` |
53
+
54
+ **`memory-consolidation/` 的结构**:`workflow.md`(整理流程)+ 五份模式手册(`notes.md` / `generalize.md` / `promote.md` / `profile.md` / `forget.md`)。命令、权限、禁止事项不在这里,而在主会话与整理会话共用的 `$KITS_FRAGMENTS/memory.md`(按 `sessionType` 分支渲染)。manifest 里**逐个文件列段**,不能用目录段——`pattern` 不支持变量插值(`manifest-engine.ts` 的 `matchGlob` 只做 `*` → `.*`),目录段会把六份文件一次性全灌进去。
55
+
56
+ **这些文件不进 `docs/`**:它们是**每轮注入**的 prompt,不是按需 Read 的知识文档。放 `docs/` 会让人误以为可以惰性加载,改了却发现要么不生效、要么被重复注入。人读的机制说明在 `kits/docs/evolcore/memory-consolidation.md`。
57
+
58
+ ---
59
+
60
+ ## 二、正交:一个概念只有一个事实源
61
+
62
+ 这是本套文档体系最核心的规则(`00-导读.md` 已声明"每个概念只有一个唯一事实源"),执行时按下面的方法落地。
63
+
64
+ ### 先分类,再动笔
65
+
66
+ 写或改文档前,先判定它在讲哪一类内容:
67
+
68
+ | 类别 | 内容 | 举例 |
69
+ |------|------|------|
70
+ | **格式/机制** | 数据结构、字段定义、协议语义、权限模型 | `protected_headers` 有哪些字段 |
71
+ | **工具用法** | CLI 语法、参数、退出码、连接行为 | `ec menu-client --request-file` 怎么用 |
72
+ | **具体任务** | 某个入口/场景的完整操作步骤 | 通过 menu exec 取某个 schema |
73
+
74
+ **格式类只能有一个定义处**,其余文档引用它。工具类文档**不得重复定义格式**,只能写"格式见 XX"。
75
+
76
+ ### 判定重叠的实际做法
77
+
78
+ 遇到两份文档疑似重复时,逐条列出两边的章节,标出讲同一件事的部分:
79
+
80
+ ```
81
+ 内容项 A 文档 B 文档 处置
82
+ 消息类型定义 ✅ 完整 ✅ 部分 归 A,B 删掉改引用
83
+ CLI 参数语义 ✅ 完整 ❌ 归 B
84
+ 鉴权机制 ❌ ✅ 完整 归 A
85
+ ```
86
+
87
+ 只有**明确归属给某一方**的部分才能保留,另一方改为一句引用。做不到这一条,两份文档就会随时间继续漂移。
88
+
89
+ ### 常见的重叠陷阱
90
+
91
+ - 两份文档都写"默认值/缺省行为" → 归格式方
92
+ - 两份文档都举 JSON 示例 → 归格式方,工具方最多留最简的可复制样例
93
+ - 两份文档都写权限要求 → 归格式方(协议层),工具方只写"入口的 token/owner 校验"
94
+
95
+ ---
96
+
97
+ ## 三、注入成本:内容默认放 docs/
98
+
99
+ ECK 的 manifest 机制决定了**不同位置的内容有不同的成本量级**。机制完整说明见 `context-assembly.md`(唯一事实源,本文不重复定义),这里只给由此得出的写作判据。
100
+
101
+ ### 成本模型
102
+
103
+ | 位置 | 何时消耗 token | 成本量级 |
104
+ |------|--------------|---------|
105
+ | `rules/` | **每次对话都注入**(当前 6 文件约 18KB) | 每轮都付 |
106
+ | `docs/` | 仅被 agent Read 时 | 按需付,多数对话为零 |
107
+
108
+ ### 判据
109
+
110
+ > **内容默认放 `docs/`。只有"每个会话、每轮都需要,不注入就会做错事"的内容才升到 `rules/`。**
111
+
112
+ 写入 `rules/` 前自问:**这条信息如果不注入,agent 会在多少比例的对话里做错事?**
113
+
114
+ - 每轮都需要(如"不得调用 `ec restart`")→ 进 `rules/`
115
+ - 偶发场景才需要(如"Menu 消息有哪些字段")→ 留 `docs/`,靠 `INDEX.md` 触发词按需命中
116
+
117
+ 答不上来就别进 `rules/`。最常见的隐性浪费是把偶发查阅的内容塞进规则文件,之后每轮对话都在白烧 token。
118
+
119
+ ### 硬阈值
120
+
121
+ 来自 `context-assembly.md` 的限额机制:
122
+
123
+ - 单目录段:40KB(`$KITS_RULES` 当前约 18KB,**剩余约一半**)
124
+ - 总闸:50 文件 / 100KB(跨所有段累计)
125
+
126
+ 逼近阈值会触发截断——**后面的 section 直接不加载**,且只注入一行截断说明。这是静默降级,出错时不报错,只表现为"某段上下文莫名其妙没出现"。
127
+
128
+ ### 升到 rules/ 的代价
129
+
130
+ `rules/` 段是 `directory` 类型,加载**整个目录**。所以往 `rules/` 加一份文件不只是加了那份文件的成本,而是抬高了该目录的总字节数,逼近 40KB 上限。加规则文件前先确认现有占用(渲染报告里的 `manifest-*.md` 或直接量目录大小)。
131
+
132
+ ---
133
+
134
+ ## 四、命名与路径写法
135
+
136
+ ### 文件名
137
+
138
+ 与源码对应关系优先于名字的绝对准确。例如 `menu-protocol.md` 对应 `src/core/command/menu-protocol.ts`——保持同名比改成 `menu-app-protocol.md` 更有价值,因为读者能靠文件名在代码里定位实现。
139
+
140
+ ### 文档内引用路径
141
+
142
+ 三种写法,按场景选,**同一份文档内保持一致**:
143
+
144
+ | 场景 | 写法 | 示例 |
145
+ |------|------|------|
146
+ | 引用同目录文档 | 裸文件名 | `menu-protocol.md` |
147
+ | 引用别名内路径(`$KITS_*` / `$AGENT_DIR` 等) | `$别名/子路径` | `$KITS_DOCS/evolcore/foo.md` |
148
+ | 引用别名外路径 | 全限定 + 说明 | `$KITS/schemas/bar.md`(注明不在别名解析范围) |
149
+
150
+ **不要用 `../` 相对路径**跨目录引用。`$KITS_DOCS` = `kits/docs`,`../schemas/` 会跑出别名体系,读者(尤其是 agent)无法用路径规则解析。
151
+
152
+ **只有 `$KITS` / `$KITS_DOCS` / `$KITS_RULES` / `$KITS_TEMPLATES` / `$KITS_FRAGMENTS` / `$AGENT_DIR` / `$PERSONAL_DIR` / `$RELATIONS_DIR` / `$VENUES_DIR` 等注册别名可用于引用**。完整清单见 `$KITS_DOCS/path-registry.md`。写 `$ECK` / `$PEER_DIR` 这类未注册名,在 manifest 里会**导致整段被静默丢弃**。
153
+
154
+ ### 术语唯一
155
+
156
+ 同一个概念在整套文档里只能有一个名字。理由是**检索靠字符串匹配**——同一概念存在两种叫法,等于把一半可发现性让出去。
157
+
158
+ 裁决标准:**优先对齐代码里的标识符**。源码中的类型名、函数名、文件名是全系统唯一不会歧义的名字。例如"授权凭证"这种业务术语,如果有对应的源码类型名,以源码为准。
159
+
160
+ ---
161
+
162
+ ## 五、可发现性:新文档必须接进链路
163
+
164
+ 一份文档写完不等于能被找到。ECK 的检索链路是**单入口 + 多级 lazy Read**:
165
+
166
+ ```
167
+ 【自动注入】$KITS_RULES/06-channel.md
168
+ "遇到明确触发词 → 优先查 $KITS_DOCS/evolcore/INDEX.md"
169
+ │
170
+ ▼
171
+ 【按需 Read】INDEX.md(命令表 / 协议与集成表)
172
+ │
173
+ ▼
174
+ 【按需 Read】目标文档
175
+ │
176
+ └── 文档内回链到相邻文档(双向可达)
177
+ ```
178
+
179
+ ### 接入清单(新文档必做)
180
+
181
+ 1. **在 `INDEX.md` 注册**,选对表:
182
+ - 对应某个 `ec` 子命令 → 「命令表」,填**触发词列**(关键,agent 靠它命中)
183
+ - 描述机制/格式,无对应命令 → 「协议与集成文档」表
184
+ 2. **补触发词**。触发词要写用户/agent 可能说出的自然语言,不是文档标题的复述。对照现有条目:
185
+ - ✅ `远程诊断/远程 daemon 操作/daemon 日志/跨主机`
186
+ - ❌ `Menu 客户端命令文档`
187
+ 3. **双向回链**。相邻文档之间互相引用——A 提到 B 的地方链过去,B 的相关处链回来。
188
+ 4. **检查上游触发词**。`rules/06-channel.md` 里那句示例触发词列表如果不覆盖新话题,补上。
189
+
190
+ ### 反模式
191
+
192
+ - **双入口**:`commands` fragment(自动注入的能力卡)只列常用命令,不必把新命令加进去。加了会造成"能力卡一份、INDEX 一份"的双份维护,且 fragment 是精简卡片,不适合承载完整清单。
193
+ - **只加文档不加索引**:文档存在但没有任何自动注入的锚点指向它,agent 无从知晓。
194
+
195
+ ---
196
+
197
+ ## 六、引用完整性
198
+
199
+ **原则:任何引用都必须在任何时刻可解析。**
200
+
201
+ 失效分两种,性质完全不同:
202
+
203
+ | 类型 | 表现 | 可否自动排查 |
204
+ |------|------|:-----------:|
205
+ | **结构失效(悬空)** | 目标文件不存在、锚点不存在 | ✅ 可 |
206
+ | **语义失效** | 目标文件存在、链接有效,但**内容已经不对了** | ❌ 不可 |
207
+
208
+ ### 悬空排查
209
+
210
+ ```bash
211
+ # 列出所有 markdown 内链目标,逐个确认存在
212
+ git grep -h -o '\[.*\](\./[^)]*\.md)' -- kits/docs | sort -u
213
+ ```
214
+
215
+ ### 语义失效的预防
216
+
217
+ 改了某个概念,所有引用它的文档**必须列入同一次变更**。做法:
218
+
219
+ 1. 改动前先查清"谁引用了它"——`git grep -n "<概念关键字>" -- kits`
220
+ 2. 把命中的文档全部纳入本次改动范围,逐一核对
221
+ 3. 查不清的(概念名在正文里以同义词出现),至少在提交信息里记录未核对的文档
222
+
223
+ 这是唯一能防语义失效的手段。**不要指望后来者发现**——语义失效不会报错,只会在某次对话里表现为"agent 引用了过时的说法"。
224
+
225
+ ---
226
+
227
+ ## 七、粒度阈值
228
+
229
+ 正交讲"该分就分",但同样要防"不该分的分了"。
230
+
231
+ > **两半内容的发现触发词不同 → 拆;触发词相同、读者相同 → 合。**
232
+
233
+ 判据是**触发词**,不是篇幅。举例:
234
+
235
+ - `menu-client.md`(工具用法)与 `menu-protocol.md`(格式定义)**该拆**——用户说"远程看日志"和说"auth_method 是什么"是两类完全不同的需求,触发词不重叠
236
+ - 反之,如果两份文档都靠"menu 怎么工作"这个触发词命中,拆开就是纯粹的碎片化,只让读者多跳一次
237
+
238
+ 篇幅长不是拆分的理由。一份 500 行的单主题文档,比三份各 150 行、读者要在其间跳转的文档更容易维护。
239
+
240
+ ---
241
+
242
+ ## 八、权威裁决:冲突时以谁为准
243
+
244
+ 正交管"一个概念只有一个事实源",但没回答**冲突时以谁为准**。实际维护中这立刻会遇到:代码改了、文档没改,读者该信谁?
245
+
246
+ | 冲突类型 | 以谁为准 | 说明 |
247
+ |---------|---------|------|
248
+ | 行为、字段、参数、错误码等**实现事实** | **代码为准** | 文档跟随代码;文档中给出源码指针,让读者可自行核对 |
249
+ | 设计意图、取舍理由、"为什么这样" | **文档为准** | 代码是实现手段,意图只存在于文档 |
250
+
251
+ ### 文档之间冲突
252
+
253
+ **不要简单地"以后者为准"。** 回到 §二 正交:判定该概念归属哪一份,另一份删掉改引用。只在引用处保留一句指向。
254
+
255
+ 简单往后覆盖文档,会让两份继续各自漂移——下次改其中一份,冲突再次出现。
256
+
257
+ ---
258
+
259
+ ## 九、示例代码必须与实现一致
260
+
261
+ 文档里的示例会被直接照抄执行,写错比不写更糟。
262
+
263
+ **规则**:写或改示例前,**读一遍对应的源码**确认形态。特别是:
264
+
265
+ - **参数类型**(对象 vs `JSON.stringify` 字符串)
266
+ - **必填但易漏的字段**(如 Menu 消息的 `ttl: 60`)
267
+ - **签名/字段名**(`protected_headers` 字段的大小写与拼写)
268
+
269
+ 本次维护中发现的典型错误:示例写成 `payload: JSON.stringify({...})`,而实现是 `payload: {...}` 对象直传(`transport.ts` 直接读 `params.payload?.type`)——照抄会直接失败。
270
+
271
+ **示例后附依据**:在示例下方注明对应的源文件与行为,方便后续核对:
272
+
273
+ ```markdown
274
+ - `ttl: 60` 是 Menu 消息的固定约定(`src/aun/menu/transport.ts` 的 `MENU_MESSAGE_TTL_SECONDS`)。
275
+ ```
276
+
277
+ ---
278
+
279
+ ## 十、改动前后的检查清单
280
+
281
+ ### 改前
282
+
283
+ - [ ] 确认改动内容属于哪一类(格式 / 工具 / 具体任务)
284
+ - [ ] 搜索该内容是否已在别处定义(`git grep -n "<关键字段名>" -- kits`)
285
+ - [ ] 若已定义,判断归哪一方,另一方改为引用
286
+ - [ ] 若改的是概念定义,查清所有引用方并纳入本次变更(防语义失效,见 §六)
287
+ - [ ] 若往 `rules/` 加内容,先确认该目录当前占用(防注入成本失控,见 §三)
288
+
289
+ ### 改后
290
+
291
+ - [ ] 无 `../` 相对路径引用
292
+ - [ ] 无未注册别名(`$ECK` / `$PEER_DIR` / `$AGENT_INDEX` 等)
293
+ - [ ] 所有引用的目标文件**确实存在**(悬空引用排查,见 §六)
294
+ - [ ] 章节编号连续、锚点链接有效
295
+ - [ ] 示例代码与源码形态一致(见 §九)
296
+ - [ ] 新文档已注册进 `INDEX.md` 且带触发词
297
+ - [ ] 被改动文档的相邻文档有回链
298
+ - [ ] 改了 `ec memory` 的命令语法:`$KITS_FRAGMENTS/memory.md`、`$KITS_DOCS/evolcore/memory.md`、`src/cli/memory-command.ts` 的 `MEMORY_HELP` / `USAGE` 三处同步
299
+
300
+ ---
301
+
302
+ ## 十一、本次维护的案例(供参考)
303
+
304
+ 以下几处改动可作为规则的实际样本:
305
+
306
+ | 问题 | 违反的规则 | 处置 |
307
+ |------|-----------|------|
308
+ | `menu-client.md` 与 `menu-protocol.md` 都定义消息类型 | §二 正交 | client 收敛为工具手册,格式引用 protocol |
309
+ | `menu-protocol-cli-exec-frontend.md` 被引用但不存在 | §六 结构失效 | 改指向 `menu-protocol.md` |
310
+ | `schemas/menu-exec-schema-commands.md` 放在数据目录 | §一 文件位置 | `git mv` 到 `docs/evolcore/` |
311
+ | `INDEX.md` 用 `../schemas/...` | §四 路径写法 | 改为同目录裸文件名 |
312
+ | 章节顺序 `0,1,2,3,4,6,5` | §十 编号连续 | 交换 §5/§6 并修锚点 |
313
+ | 示例用 `JSON.stringify(payload)` | §九 示例一致性 | 改为对象直传 + 补 `ttl` |
314
+ | `menu-protocol.md` 无触发词入口 | §五 可发现性 | 新建「协议与集成文档」表并填触发词 |
@@ -1,5 +1,7 @@
1
1
  # 文档查阅指南
2
2
 
3
+ > 本文面向**查阅**文档的人。要**新增或修改** `kits/` 下的文档,见 `AUTHORING.md`。
4
+
3
5
  ## 查阅流程
4
6
 
5
7
  1. 先看 `$KITS_RULES`(自动加载的 6 个规则文件 01-06)了解机制骨架