@oyasmi/pipiclaw 0.5.1 → 0.5.2

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 (173) hide show
  1. package/dist/agent/channel-runner.d.ts +46 -0
  2. package/dist/agent/channel-runner.d.ts.map +1 -0
  3. package/dist/agent/channel-runner.js +434 -0
  4. package/dist/agent/channel-runner.js.map +1 -0
  5. package/dist/agent/index.d.ts +4 -0
  6. package/dist/agent/index.d.ts.map +1 -0
  7. package/dist/agent/index.js +3 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/agent/progress-formatter.d.ts +5 -0
  10. package/dist/agent/progress-formatter.d.ts.map +1 -0
  11. package/dist/agent/progress-formatter.js +53 -0
  12. package/dist/agent/progress-formatter.js.map +1 -0
  13. package/dist/agent/run-queue.d.ts +8 -0
  14. package/dist/agent/run-queue.d.ts.map +1 -0
  15. package/dist/agent/run-queue.js +27 -0
  16. package/dist/agent/run-queue.js.map +1 -0
  17. package/dist/agent/runner-factory.d.ts +4 -0
  18. package/dist/agent/runner-factory.d.ts.map +1 -0
  19. package/dist/agent/runner-factory.js +11 -0
  20. package/dist/agent/runner-factory.js.map +1 -0
  21. package/dist/agent/session-events.d.ts +15 -0
  22. package/dist/agent/session-events.d.ts.map +1 -0
  23. package/dist/agent/session-events.js +216 -0
  24. package/dist/agent/session-events.js.map +1 -0
  25. package/dist/agent/type-guards.d.ts +23 -0
  26. package/dist/agent/type-guards.d.ts.map +1 -0
  27. package/dist/agent/type-guards.js +107 -0
  28. package/dist/agent/type-guards.js.map +1 -0
  29. package/dist/agent/types.d.ts +161 -0
  30. package/dist/agent/types.d.ts.map +1 -0
  31. package/dist/agent/types.js +23 -0
  32. package/dist/agent/types.js.map +1 -0
  33. package/dist/agent.d.ts +2 -15
  34. package/dist/agent.d.ts.map +1 -1
  35. package/dist/agent.js +1 -781
  36. package/dist/agent.js.map +1 -1
  37. package/dist/context.d.ts +58 -14
  38. package/dist/context.d.ts.map +1 -1
  39. package/dist/context.js +50 -7
  40. package/dist/context.js.map +1 -1
  41. package/dist/index.d.ts +12 -12
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +12 -12
  44. package/dist/index.js.map +1 -1
  45. package/dist/main.js +5 -404
  46. package/dist/main.js.map +1 -1
  47. package/dist/memory/bootstrap.d.ts +7 -0
  48. package/dist/memory/bootstrap.d.ts.map +1 -0
  49. package/dist/memory/bootstrap.js +47 -0
  50. package/dist/memory/bootstrap.js.map +1 -0
  51. package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +2 -1
  52. package/dist/memory/candidates.d.ts.map +1 -0
  53. package/dist/{memory-candidates.js → memory/candidates.js} +34 -21
  54. package/dist/memory/candidates.js.map +1 -0
  55. package/dist/memory/chinese-words.d.ts +2 -0
  56. package/dist/memory/chinese-words.d.ts.map +1 -0
  57. package/dist/memory/chinese-words.js +210 -0
  58. package/dist/memory/chinese-words.js.map +1 -0
  59. package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +1 -1
  60. package/dist/memory/consolidation.d.ts.map +1 -0
  61. package/dist/{memory-consolidation.js → memory/consolidation.js} +27 -35
  62. package/dist/memory/consolidation.js.map +1 -0
  63. package/dist/{memory-files.d.ts → memory/files.d.ts} +1 -6
  64. package/dist/memory/files.d.ts.map +1 -0
  65. package/dist/{memory-files.js → memory/files.js} +12 -36
  66. package/dist/memory/files.js.map +1 -0
  67. package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +24 -6
  68. package/dist/memory/lifecycle.d.ts.map +1 -0
  69. package/dist/memory/lifecycle.js +247 -0
  70. package/dist/memory/lifecycle.js.map +1 -0
  71. package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
  72. package/dist/memory/recall.d.ts.map +1 -0
  73. package/dist/memory/recall.js +435 -0
  74. package/dist/memory/recall.js.map +1 -0
  75. package/dist/{session-memory.d.ts → memory/session.d.ts} +2 -1
  76. package/dist/memory/session.d.ts.map +1 -0
  77. package/dist/{session-memory.js → memory/session.js} +32 -62
  78. package/dist/memory/session.js.map +1 -0
  79. package/dist/runtime/bootstrap.d.ts +48 -0
  80. package/dist/runtime/bootstrap.d.ts.map +1 -0
  81. package/dist/runtime/bootstrap.js +451 -0
  82. package/dist/runtime/bootstrap.js.map +1 -0
  83. package/dist/runtime/delivery.d.ts.map +1 -0
  84. package/dist/{delivery.js → runtime/delivery.js} +1 -1
  85. package/dist/runtime/delivery.js.map +1 -0
  86. package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -0
  87. package/dist/runtime/dingtalk.d.ts.map +1 -0
  88. package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -27
  89. package/dist/runtime/dingtalk.js.map +1 -0
  90. package/dist/runtime/events.d.ts.map +1 -0
  91. package/dist/{events.js → runtime/events.js} +1 -1
  92. package/dist/runtime/events.js.map +1 -0
  93. package/dist/{store.d.ts → runtime/store.d.ts} +5 -0
  94. package/dist/runtime/store.d.ts.map +1 -0
  95. package/dist/{store.js → runtime/store.js} +60 -19
  96. package/dist/runtime/store.js.map +1 -0
  97. package/dist/shared/markdown-sections.d.ts +7 -0
  98. package/dist/shared/markdown-sections.d.ts.map +1 -0
  99. package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -3
  100. package/dist/shared/markdown-sections.js.map +1 -0
  101. package/dist/shared/text-utils.d.ts +10 -0
  102. package/dist/shared/text-utils.d.ts.map +1 -0
  103. package/dist/shared/text-utils.js +37 -0
  104. package/dist/shared/text-utils.js.map +1 -0
  105. package/dist/shared/type-guards.d.ts +6 -0
  106. package/dist/shared/type-guards.d.ts.map +1 -0
  107. package/dist/shared/type-guards.js +13 -0
  108. package/dist/shared/type-guards.js.map +1 -0
  109. package/dist/shared/types.d.ts +15 -0
  110. package/dist/shared/types.d.ts.map +1 -0
  111. package/dist/shared/types.js +2 -0
  112. package/dist/shared/types.js.map +1 -0
  113. package/dist/sidecar-worker.d.ts.map +1 -1
  114. package/dist/sidecar-worker.js +1 -7
  115. package/dist/sidecar-worker.js.map +1 -1
  116. package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +1 -1
  117. package/dist/subagents/discovery.d.ts.map +1 -0
  118. package/dist/{sub-agents.js → subagents/discovery.js} +3 -3
  119. package/dist/subagents/discovery.js.map +1 -0
  120. package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +3 -16
  121. package/dist/{tools/subagent.d.ts.map → subagents/tool.d.ts.map} +1 -1
  122. package/dist/{tools/subagent.js → subagents/tool.js} +17 -38
  123. package/dist/subagents/tool.js.map +1 -0
  124. package/dist/tools/index.d.ts +1 -1
  125. package/dist/tools/index.d.ts.map +1 -1
  126. package/dist/tools/index.js +1 -1
  127. package/dist/tools/index.js.map +1 -1
  128. package/docs/memory-audit.md +330 -0
  129. package/docs/memory-optimization-round2.md +319 -0
  130. package/package.json +1 -1
  131. package/dist/delivery.d.ts.map +0 -1
  132. package/dist/delivery.js.map +0 -1
  133. package/dist/dingtalk.d.ts.map +0 -1
  134. package/dist/dingtalk.js.map +0 -1
  135. package/dist/events.d.ts.map +0 -1
  136. package/dist/events.js.map +0 -1
  137. package/dist/markdown-sections.d.ts +0 -6
  138. package/dist/markdown-sections.d.ts.map +0 -1
  139. package/dist/markdown-sections.js.map +0 -1
  140. package/dist/memory-candidates.d.ts.map +0 -1
  141. package/dist/memory-candidates.js.map +0 -1
  142. package/dist/memory-consolidation.d.ts.map +0 -1
  143. package/dist/memory-consolidation.js.map +0 -1
  144. package/dist/memory-files.d.ts.map +0 -1
  145. package/dist/memory-files.js.map +0 -1
  146. package/dist/memory-lifecycle.d.ts.map +0 -1
  147. package/dist/memory-lifecycle.js +0 -150
  148. package/dist/memory-lifecycle.js.map +0 -1
  149. package/dist/memory-recall.d.ts.map +0 -1
  150. package/dist/memory-recall.js +0 -218
  151. package/dist/memory-recall.js.map +0 -1
  152. package/dist/session-memory-files.d.ts +0 -2
  153. package/dist/session-memory-files.d.ts.map +0 -1
  154. package/dist/session-memory-files.js +0 -2
  155. package/dist/session-memory-files.js.map +0 -1
  156. package/dist/session-memory.d.ts.map +0 -1
  157. package/dist/session-memory.js.map +0 -1
  158. package/dist/store.d.ts.map +0 -1
  159. package/dist/store.js.map +0 -1
  160. package/dist/sub-agents.d.ts.map +0 -1
  161. package/dist/sub-agents.js.map +0 -1
  162. package/dist/tools/subagent.js.map +0 -1
  163. package/docs/proj-review.md +0 -188
  164. package/docs/test-supplementation-plan.md +0 -553
  165. /package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -0
  166. /package/dist/{events.d.ts → runtime/events.d.ts} +0 -0
  167. /package/docs/{memory-rfc.md → specs/001-implement-memory/memory-rfc.md} +0 -0
  168. /package/docs/{subagent → specs/002-subagent}/pi-subagent-analyse.txt +0 -0
  169. /package/docs/{subagent → specs/002-subagent}/pi-subagent-design.txt +0 -0
  170. /package/docs/{subagent → specs/002-subagent}/pi-subagent-phase1-plan.txt +0 -0
  171. /package/docs/{improve-memory → specs/003-improve-memory}/design.md +0 -0
  172. /package/docs/{improve-memory → specs/003-improve-memory}/interfaces-and-tests.md +0 -0
  173. /package/docs/{improve-memory → specs/003-improve-memory}/spec.md +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAOhE,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,aAAa,CAAC;IAC7B,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;IACpD,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;CAC5D;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAE5E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAoBzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAOzE,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,aAAa,CAAC;IAC7B,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;IACpD,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;CAC5D;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAE5E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAoBzF"}
@@ -1,7 +1,7 @@
1
+ import { createSubAgentTool } from "../subagents/tool.js";
1
2
  import { createBashTool } from "./bash.js";
2
3
  import { createEditTool } from "./edit.js";
3
4
  import { createReadTool } from "./read.js";
4
- import { createSubAgentTool } from "./subagent.js";
5
5
  import { createWriteTool } from "./write.js";
6
6
  export function createPipiclawBaseTools(executor) {
7
7
  return [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAgB7C,MAAM,UAAU,uBAAuB,CAAC,QAAkB;IACzD,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;AAClH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAmC;IACtE,MAAM,SAAS,GAAG,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5D,OAAO;QACN,GAAG,SAAS;QACZ,kBAAkB,CAAC;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;YAClD,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;YACxD,cAAc,EAAE;gBACf,aAAa,EAAE,OAAO,CAAC,aAAa;gBACpC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,aAAa,CAAC,SAAS,EAAE;aACrG;SACD,CAAC;KACF,CAAC;AACH,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type { PipiclawMemoryRecallSettings } from \"../context.js\";\nimport type { Executor, SandboxConfig } from \"../sandbox.js\";\nimport type { SubAgentDiscoveryResult } from \"../sub-agents.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createSubAgentTool } from \"./subagent.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport interface CreatePipiclawToolsOptions {\n\texecutor: Executor;\n\tgetCurrentModel: () => Model<Api>;\n\tgetAvailableModels: () => Model<Api>[];\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tworkspaceDir: string;\n\tchannelDir: string;\n\tworkspacePath: string;\n\tchannelId: string;\n\tsandboxConfig: SandboxConfig;\n\tgetSubAgentDiscovery: () => SubAgentDiscoveryResult;\n\tgetMemoryRecallSettings: () => PipiclawMemoryRecallSettings;\n}\n\nexport function createPipiclawBaseTools(executor: Executor): AgentTool<any>[] {\n\treturn [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];\n}\n\nexport function createPipiclawTools(options: CreatePipiclawToolsOptions): AgentTool<any>[] {\n\tconst baseTools = createPipiclawBaseTools(options.executor);\n\treturn [\n\t\t...baseTools,\n\t\tcreateSubAgentTool({\n\t\t\texecutor: options.executor,\n\t\t\tgetCurrentModel: options.getCurrentModel,\n\t\t\tgetAvailableModels: options.getAvailableModels,\n\t\t\tresolveApiKey: options.resolveApiKey,\n\t\t\tworkspaceDir: options.workspaceDir,\n\t\t\tchannelDir: options.channelDir,\n\t\t\tgetSubAgentDiscovery: options.getSubAgentDiscovery,\n\t\t\tgetMemoryRecallSettings: options.getMemoryRecallSettings,\n\t\t\truntimeContext: {\n\t\t\t\tworkspacePath: options.workspacePath,\n\t\t\t\tchannelId: options.channelId,\n\t\t\t\tsandbox: options.sandboxConfig.type === \"host\" ? \"host\" : `docker:${options.sandboxConfig.container}`,\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAgB7C,MAAM,UAAU,uBAAuB,CAAC,QAAkB;IACzD,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;AAClH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAmC;IACtE,MAAM,SAAS,GAAG,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5D,OAAO;QACN,GAAG,SAAS;QACZ,kBAAkB,CAAC;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;YAClD,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;YACxD,cAAc,EAAE;gBACf,aAAa,EAAE,OAAO,CAAC,aAAa;gBACpC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,aAAa,CAAC,SAAS,EAAE;aACrG;SACD,CAAC;KACF,CAAC;AACH,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type { PipiclawMemoryRecallSettings } from \"../context.js\";\nimport type { Executor, SandboxConfig } from \"../sandbox.js\";\nimport type { SubAgentDiscoveryResult } from \"../subagents/discovery.js\";\nimport { createSubAgentTool } from \"../subagents/tool.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport interface CreatePipiclawToolsOptions {\n\texecutor: Executor;\n\tgetCurrentModel: () => Model<Api>;\n\tgetAvailableModels: () => Model<Api>[];\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tworkspaceDir: string;\n\tchannelDir: string;\n\tworkspacePath: string;\n\tchannelId: string;\n\tsandboxConfig: SandboxConfig;\n\tgetSubAgentDiscovery: () => SubAgentDiscoveryResult;\n\tgetMemoryRecallSettings: () => PipiclawMemoryRecallSettings;\n}\n\nexport function createPipiclawBaseTools(executor: Executor): AgentTool<any>[] {\n\treturn [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];\n}\n\nexport function createPipiclawTools(options: CreatePipiclawToolsOptions): AgentTool<any>[] {\n\tconst baseTools = createPipiclawBaseTools(options.executor);\n\treturn [\n\t\t...baseTools,\n\t\tcreateSubAgentTool({\n\t\t\texecutor: options.executor,\n\t\t\tgetCurrentModel: options.getCurrentModel,\n\t\t\tgetAvailableModels: options.getAvailableModels,\n\t\t\tresolveApiKey: options.resolveApiKey,\n\t\t\tworkspaceDir: options.workspaceDir,\n\t\t\tchannelDir: options.channelDir,\n\t\t\tgetSubAgentDiscovery: options.getSubAgentDiscovery,\n\t\t\tgetMemoryRecallSettings: options.getMemoryRecallSettings,\n\t\t\truntimeContext: {\n\t\t\t\tworkspacePath: options.workspacePath,\n\t\t\t\tchannelId: options.channelId,\n\t\t\t\tsandbox: options.sandboxConfig.type === \"host\" ? \"host\" : `docker:${options.sandboxConfig.container}`,\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
@@ -0,0 +1,330 @@
1
+ # Pipiclaw Memory 子系统深度审计报告
2
+
3
+ > **Date**: 2026-04-02
4
+ > **Scope**: `src/memory/` 全部文件 + 相关调用方
5
+ > **Version**: v0.5.1 post-refactoring (commit c4dc08b)
6
+
7
+ ---
8
+
9
+ ## Executive Summary
10
+
11
+ Pipiclaw 的三层记忆系统(SESSION / MEMORY / HISTORY)在实际使用中存在两个根本性问题:**"写不进去"** 和 **"读不出来"**。Consolidation 仅在 compaction/new-session 时触发,导致 MEMORY.md 和 HISTORY.md 几乎不更新;召回评分被 base priority 主导,token overlap 对最终排序影响极小,导致召回本质上是按来源类型排序而非按相关性排序。中文场景下问题尤为突出。
12
+
13
+ ---
14
+
15
+ ## 一、现状观察
16
+
17
+ | 文件 | 更新频率 | 用户感知 |
18
+ |------|---------|---------|
19
+ | SESSION.md | 频繁、及时 | 正常 |
20
+ | MEMORY.md | 偶尔 | 不足 |
21
+ | HISTORY.md | 极少 | 几乎无 |
22
+
23
+ ---
24
+
25
+ ## 二、核心问题诊断
26
+
27
+ ### 问题 1:Consolidation 触发时机过窄
28
+
29
+ **根因链**:
30
+
31
+ ```
32
+ MEMORY.md/HISTORY.md 更新 ← runInlineConsolidation()
33
+ ← 仅在 session_before_compact 和 session_before_switch(reason="new") 时触发
34
+ ← compaction 由 SDK 在上下文窗口满时触发
35
+ ← new-session 仅在频道首次激活时触发
36
+ ```
37
+
38
+ 对于钉钉场景,用户通常是短问答(1-3 轮),上下文窗口永远不会满,compaction 永远不触发。这意味着:
39
+
40
+ - **HISTORY.md**:几乎永远不会收到新的 history block
41
+ - **MEMORY.md**:几乎永远不会收到新的 memory entries
42
+ - 只有 SESSION.md 因为有独立的 `enqueueSessionMemoryUpdate()` 路径而能正常更新
43
+
44
+ **关键代码** (`src/memory/lifecycle.ts`):
45
+ ```typescript
46
+ // consolidation 仅在这两个事件中触发
47
+ case "session_before_compact":
48
+ case "session_before_switch": // 且 reason === "new"
49
+ await runInlineConsolidation(options);
50
+ ```
51
+
52
+ ### 问题 2:召回评分被 base priority 主导
53
+
54
+ **评分构成** (`src/memory/recall.ts:scoreCandidate`):
55
+
56
+ ```
57
+ base priority: 40 ~ 120(占绝对主导)
58
+ title overlap: 每匹配一个 token +10(标题通常很短,0~30)
59
+ content overlap: 每匹配一个 token +3(0~15)
60
+ path overlap: 每匹配一个 token +6(0~12)
61
+ recency boost: 0 ~ 8
62
+ ```
63
+
64
+ **实际效果**:一个完全不相关但来源是 session 的候选(score=100+)总是排在一个高度相关但来源是 history 的候选(score=40+几个 overlap)前面。**召回本质上是按来源类型排序,不是按相关性排序**。
65
+
66
+ **候选优先级梯度** (`src/memory/candidates.ts`):
67
+
68
+ ```
69
+ channel-session "current state" = 120
70
+ channel-session "next steps" = 115
71
+ channel-session "errors" = 110
72
+ channel-session "constraints" = 108
73
+ channel-session "user intent" = 105
74
+ channel-session (other) = 100
75
+ channel-memory (constraints) = 88
76
+ channel-memory (decisions) = 86
77
+ channel-memory (open loops) = 84
78
+ channel-memory (default) = 80
79
+ workspace-memory = 60
80
+ channel-history = 40
81
+ ```
82
+
83
+ session 到 history 的 priority 差距达 60-80 分,token overlap 几乎不可能弥补。
84
+
85
+ ### 问题 3:中文分词质量低
86
+
87
+ 当前实现用字符级 n-gram(bigram + trigram),没有词级分词:
88
+
89
+ ```
90
+ "如何优化记忆系统"
91
+ → bigrams: ["如何", "何优", "优化", "化记", "记忆", "忆系", "系统"]
92
+ → trigrams: ["如何优", "何优化", "优化记", "化记忆", "记忆系", "忆系统"]
93
+ ```
94
+
95
+ 问题:
96
+ - 产生大量无意义的跨词 n-gram(如"何优"、"化记"),增加噪音匹配
97
+ - 短查询(2-3 字)只产生 1-2 个 token,overlap 信号极弱
98
+ - 无法处理同义词/近义词(如"bug"和"缺陷")
99
+
100
+ ### 问题 4:Background maintenance 阈值过于保守
101
+
102
+ | 维护操作 | 当前阈值 | 实际达到难度 |
103
+ |---------|---------|------------|
104
+ | Memory cleanup | ≥8,000 字符 OR ≥6 个 update 块 | 需要 6 次 consolidation,而 consolidation 本身就很少触发 |
105
+ | History folding | ≥16,000 字符 OR ≥8 个 block,且总 block>4 | 几乎不可能达到 |
106
+
107
+ ### 问题 5:Inline consolidation 的 LLM 可靠性
108
+
109
+ 当前用一次 LLM 调用同时产生 `memoryEntries` 和 `historyBlock`。问题:
110
+ - LLM 在短对话时倾向于返回空的 `historyBlock`
111
+ - `hasMeaningfulMessages()` 要求 ≥2 条有意义消息,否则整个 consolidation 被跳过
112
+ - Rerank 超时仅 5 秒,内容截断到 300 字符,信息损失大
113
+
114
+ ---
115
+
116
+ ## 三、优化方案
117
+
118
+ ### 方案 A:新增空闲超时 consolidation 触发(P0)
119
+
120
+ **核心思路**:不再仅依赖 compaction/new-session,在对话"自然结束"时也触发 consolidation。
121
+
122
+ **实现方式**:在 `MemoryLifecycle` 中新增基于空闲超时的触发:
123
+
124
+ ```typescript
125
+ // lifecycle.ts 新增
126
+ const IDLE_CONSOLIDATION_TIMEOUT_MS = 60_000; // 对话空闲60秒后触发
127
+
128
+ private idleTimer: ReturnType<typeof setTimeout> | null = null;
129
+ private turnsSinceLastConsolidation = 0;
130
+
131
+ // 在 noteCompletedAssistantTurn() 中调用
132
+ private scheduleIdleConsolidation(): void {
133
+ this.clearIdleTimer();
134
+ this.idleTimer = setTimeout(() => {
135
+ void this.runIdleConsolidation();
136
+ }, IDLE_CONSOLIDATION_TIMEOUT_MS);
137
+ }
138
+
139
+ private clearIdleTimer(): void {
140
+ if (this.idleTimer) {
141
+ clearTimeout(this.idleTimer);
142
+ this.idleTimer = null;
143
+ }
144
+ }
145
+
146
+ private async runIdleConsolidation(): Promise<void> {
147
+ if (this.turnsSinceLastConsolidation < 2) return;
148
+ await runInlineConsolidation(this.buildConsolidationOptions());
149
+ await runBackgroundMaintenance(this.buildConsolidationOptions());
150
+ this.turnsSinceLastConsolidation = 0;
151
+ }
152
+ ```
153
+
154
+ **为什么有效**:钉钉场景下用户问完问题就离开,60 秒内没有新消息说明对话结束,此时触发 consolidation 可以可靠地将本轮对话的关键信息写入 MEMORY.md 和 HISTORY.md。
155
+
156
+ **预期效果**:HISTORY.md 和 MEMORY.md 从几乎不更新变为每轮对话结束后都更新。
157
+
158
+ ---
159
+
160
+ ### 方案 B:重新平衡召回评分(P0)
161
+
162
+ **核心思路**:归一化 token overlap 分数,使其与 base priority 在同一量级。
163
+
164
+ ```typescript
165
+ function scoreCandidate(queryTokens: string[], candidate: MemoryCandidate): number {
166
+ // 1. base priority 压缩到 0-20 范围(原始 40-120 → 归一化)
167
+ const normalizedPriority = (candidate.priority / 120) * 20;
168
+
169
+ // 2. token overlap 归一化到 0-50 范围
170
+ const titleOverlap = computeNormalizedOverlap(queryTokens, candidate.title);
171
+ const contentOverlap = computeNormalizedOverlap(queryTokens, candidate.content);
172
+ const pathOverlap = computeNormalizedOverlap(queryTokens, candidate.path);
173
+ const relevanceScore = titleOverlap * 25 + contentOverlap * 15 + pathOverlap * 10;
174
+
175
+ // 3. recency boost 保持 0-8
176
+ const recencyBoost = computeRecencyBoost(candidate.timestamp);
177
+
178
+ return normalizedPriority + relevanceScore + recencyBoost;
179
+ }
180
+
181
+ function computeNormalizedOverlap(queryTokens: string[], text: string): number {
182
+ if (queryTokens.length === 0) return 0;
183
+ const haystack = new Set(tokenize(text));
184
+ let matches = 0;
185
+ for (const token of queryTokens) {
186
+ if (haystack.has(token)) matches++;
187
+ }
188
+ return matches / queryTokens.length; // 归一化到 0-1
189
+ }
190
+ ```
191
+
192
+ **效果**:一个 history 候选如果与查询高度匹配(relevanceScore ≈ 40),可以超过一个不相关的 session 候选(normalizedPriority ≈ 17,relevanceScore ≈ 0)。
193
+
194
+ ---
195
+
196
+ ### 方案 C:改进中文分词(P1)
197
+
198
+ #### C1:轻量级词表分词(推荐)
199
+
200
+ 引入常见中文词表做正向最大匹配,作为 n-gram 的补充:
201
+
202
+ ```typescript
203
+ // 内置一个高频词表(约 2000-3000 个技术 + 通用词)
204
+ import { COMMON_WORDS } from "./chinese-words.js";
205
+
206
+ function tokenizeHanPart(part: string): string[] {
207
+ const tokens: string[] = [];
208
+ // 正向最大匹配分词
209
+ const words = forwardMaxMatch(part, COMMON_WORDS);
210
+ tokens.push(...words.filter(w => w.length >= 2));
211
+ // 保留 bigram 作为兜底(但移除 trigram 以减少噪音)
212
+ const chars = Array.from(part);
213
+ for (let i = 0; i <= chars.length - 2; i++) {
214
+ tokens.push(chars.slice(i, i + 2).join(""));
215
+ }
216
+ return tokens;
217
+ }
218
+ ```
219
+
220
+ #### C2:提升 Rerank 质量
221
+
222
+ ```typescript
223
+ // recall.ts 修改
224
+ const RERANK_CONTENT_CLIP = 800; // 从 300 提升到 800
225
+ const MEMORY_RECALL_RERANK_TIMEOUT_MS = 8_000; // 从 5s 提升到 8s
226
+ ```
227
+
228
+ 建议 **C1 + C2 组合使用**。
229
+
230
+ ---
231
+
232
+ ### 方案 D:降低 maintenance 阈值(P1)
233
+
234
+ ```typescript
235
+ // consolidation.ts
236
+ // 当前 → 建议
237
+ const MEMORY_CLEANUP_LENGTH_THRESHOLD = 8_000; // → 4_000
238
+ const MEMORY_UPDATE_BLOCK_THRESHOLD = 6; // → 3
239
+ const HISTORY_LENGTH_THRESHOLD = 16_000; // → 6_000
240
+ const HISTORY_BLOCK_THRESHOLD = 8; // → 4
241
+ const HISTORY_RECENT_BLOCKS_TO_KEEP = 4; // → 2
242
+ ```
243
+
244
+ **理由**:方案 A 实施后 consolidation 频率显著提升,MEMORY.md 和 HISTORY.md 增长加快,需要更积极的清理/折叠。
245
+
246
+ ---
247
+
248
+ ### 方案 E:Recall 默认配置调优(P1)
249
+
250
+ ```typescript
251
+ // channel-runner.ts 中的 recall 调用参数
252
+ // 当前 → 建议
253
+ maxCandidates: 8, // → 12(给 reranker 更多候选)
254
+ maxInjected: 3, // → 5(注入更多上下文)
255
+ maxChars: 3500, // → 5000(允许更长的回忆文本)
256
+ rerankWithModel: false, // → true(默认开启 LLM rerank)
257
+ ```
258
+
259
+ **注意**:开启默认 rerank 会增加约 1 次 sidecar LLM 调用/轮,但对提升召回质量非常值得。
260
+
261
+ ---
262
+
263
+ ### 方案 F:独立 MEMORY 和 HISTORY 的更新路径(P2)
264
+
265
+ 将 memoryEntries 提取做成确定性规则 + LLM 混合:
266
+
267
+ ```typescript
268
+ // 确定性提取:从 assistant 回复中提取结构化信息
269
+ function extractDeterministicMemoryEntries(messages: Message[]): string[] {
270
+ const entries: string[] = [];
271
+ // 提取用户明确要求记住的内容("记住..."、"以后...")
272
+ // 提取关键决策点("决定用..."、"选择...")
273
+ // 提取纠正/偏好表达("不要..."、"应该...")
274
+ return entries;
275
+ }
276
+ ```
277
+
278
+ 对 historyBlock 的 prompt 强化:
279
+
280
+ ```typescript
281
+ const INLINE_CONSOLIDATION_SYSTEM_PROMPT = `...
282
+ - historyBlock MUST NOT be empty. Even for short conversations,
283
+ summarize what was discussed in 1-2 sentences.
284
+ ...`;
285
+ ```
286
+
287
+ ---
288
+
289
+ ## 四、优先级总览
290
+
291
+ | 优先级 | 方案 | 预期效果 | 实施复杂度 |
292
+ |-------|------|---------|-----------|
293
+ | **P0** | A: 空闲触发 consolidation | HISTORY/MEMORY 从几乎不更新 → 每轮对话都更新 | 中 |
294
+ | **P0** | B: 评分重新平衡 | 召回从按来源排序 → 按相关性排序 | 低 |
295
+ | **P1** | C: 中文分词改进 | 中文召回准确率显著提升 | 中 |
296
+ | **P1** | D: 降低 maintenance 阈值 | MEMORY/HISTORY 及时整理 | 低 |
297
+ | **P1** | E: Recall 默认配置调优 | 更多相关上下文被注入 | 低 |
298
+ | **P2** | F: 独立更新路径 | HISTORY 可靠产出 | 高 |
299
+
300
+ ---
301
+
302
+ ## 五、实施建议
303
+
304
+ ### 第一阶段(立即)
305
+ 1. 实施方案 A(空闲触发)+ 方案 B(评分平衡)— 解决"写不进去"和"读不出来"两个根本问题
306
+ 2. 实施方案 D(降低阈值)— 配合方案 A 的效果
307
+
308
+ ### 第二阶段(跟进)
309
+ 3. 实施方案 C(中文分词)+ 方案 E(配置调优)— 进一步提升召回质量
310
+ 4. 为所有方案补充测试用例
311
+
312
+ ### 第三阶段(可选)
313
+ 5. 实施方案 F(独立路径)— 提升 consolidation 可靠性
314
+ 6. 添加 sidecar LLM 调用的 token 追踪(可观测性)
315
+
316
+ ---
317
+
318
+ ## 附录:关键文件参照
319
+
320
+ | 文件 | 行数 | 涉及方案 |
321
+ |------|------|---------|
322
+ | `src/memory/lifecycle.ts` | 294 | A |
323
+ | `src/memory/recall.ts` | 280 | B, C, E |
324
+ | `src/memory/consolidation.ts` | 328 | A, D, F |
325
+ | `src/memory/candidates.ts` | 170 | B |
326
+ | `src/memory/session.ts` | 290 | — |
327
+ | `src/memory/files.ts` | 193 | — |
328
+ | `src/agent/channel-runner.ts` | 495 | E |
329
+ | `src/sidecar-worker.ts` | 138 | — |
330
+
@@ -0,0 +1,319 @@
1
+ # Memory 子系统第二轮优化计划
2
+
3
+ > **Date**: 2026-04-02
4
+ > **Base**: 第一轮优化后 (commit 845de82)
5
+ > **Input**: memory-audit.md 审计报告 + 第一轮优化质量评估
6
+
7
+ ---
8
+
9
+ ## 一、第一轮优化回顾
10
+
11
+ ### 已落地(保留不动)
12
+
13
+ | 方案 | 实施质量 | 说明 |
14
+ |------|---------|------|
15
+ | 空闲触发 consolidation | A | 60s idle timer + snapshot + 自动重调度,设计精良 |
16
+ | 中文分词改进 | A | 正向最大匹配 + bigram 兜底 + 130 词表 |
17
+ | Rerank 参数提升 | A | timeout 8s, content clip 800 |
18
+ | 降低 maintenance 阈值 | A | memory 5K/4块, history 8K/5块/保留3 |
19
+ | Query Intent Detection | A | 6 种意图,中英双语,超出审计建议的创新 |
20
+ | Consolidation prompt 强化 | B- | 仅加了"prefer"措辞,未加 few-shot |
21
+
22
+ ### 未落地或需改进
23
+
24
+ | 方案 | 状态 | 本轮处理 |
25
+ |------|------|---------|
26
+ | Recall 默认配置调优 | 未实施 | 本轮实施 |
27
+ | 评分算法仍有 priority 主导倾向 | 部分改善 | 本轮精炼 |
28
+ | Intent seeding 可能注入无关候选 | 新发现 | 本轮修复 |
29
+ | 运算符优先级不清晰 | 新发现 | 本轮修复 |
30
+ | 中文词表覆盖不足 | 新发现 | 本轮扩充 |
31
+ | 排序逻辑重复 | 新发现 | 本轮提取 |
32
+
33
+ ---
34
+
35
+ ## 二、本轮优化内容
36
+
37
+ ### 2.1 [P0] 首轮强制注入 MEMORY.md(新策略)
38
+
39
+ **动机**:当前召回依赖 token overlap + intent matching,但 session 首轮对话中用户可能只说了一句模糊的话(如"你好"、"帮我看看那个问题"),几乎不会命中任何 memory 候选。LLM 在冷启动时对频道上下文完全遗忘。
40
+
41
+ **策略**:在每个 session 的第一轮对话中,无条件将 MEMORY.md 的前 3K 字符拼入 prompt,独立于召回机制。
42
+
43
+ **实现要点**:
44
+
45
+ 1. 在 `ChannelRunner` 中新增 `isFirstUserTurn` 状态标记:
46
+
47
+ ```typescript
48
+ // channel-runner.ts
49
+ private isFirstUserTurn = true;
50
+
51
+ // 在 run() 方法中,recall 之后:
52
+ if (this.isFirstUserTurn) {
53
+ const memorySnapshot = await this.buildFirstTurnMemorySnapshot();
54
+ if (memorySnapshot) {
55
+ promptText = `${memorySnapshot}\n\n${promptText}`;
56
+ }
57
+ this.isFirstUserTurn = false;
58
+ }
59
+ ```
60
+
61
+ 2. 构建注入文本:
62
+
63
+ ```typescript
64
+ private async buildFirstTurnMemorySnapshot(): Promise<string> {
65
+ const memoryPath = getChannelMemoryPath(this.channelDir);
66
+ const raw = await readOptionalFile(memoryPath);
67
+ if (!raw.trim()) return "";
68
+
69
+ // 如果 recall 已返回 channel-memory 候选,跳过注入避免重复
70
+ // (由调用方判断,此处只负责构建文本)
71
+
72
+ const clipped = clipText(raw, 3000, { headRatio: 1.0 }); // 纯取头部
73
+ return [
74
+ "<channel_memory_snapshot>",
75
+ "Below is the first 3K of this channel's durable memory (MEMORY.md).",
76
+ "Use it as background context for this conversation.",
77
+ "",
78
+ clipped,
79
+ "</channel_memory_snapshot>",
80
+ ].join("\n");
81
+ }
82
+ ```
83
+
84
+ 3. 与 recall 去重:如果 recall 结果已包含 `channel-memory` 来源的候选,则跳过强制注入。
85
+
86
+ **优势**:
87
+ - 解决冷启动遗忘问题,LLM 从第一句话就"记得"频道核心上下文
88
+ - 实现极简(约 20 行),几乎零风险
89
+ - MEMORY.md 头部经过 cleanup LLM 整理,天然是高价值信息(Identity/Preferences/Constraints)
90
+ - 与 recall 互补而非冲突:recall 解决"针对具体问题找相关记忆",强制注入解决"确保基础上下文始终在场"
91
+
92
+ **风险与缓解**:
93
+ - Token 预算占用约 1K-1.5K tokens — 钉钉短问答场景中上下文窗口充裕,影响可忽略
94
+ - 与 recall 结果重复 — 通过检查 recall 是否已返回 channel-memory 候选来去重
95
+
96
+ ---
97
+
98
+ ### 2.2 [P0] 实施 Recall 默认配置调优
99
+
100
+ **动机**:第一轮审计明确建议但未实施。当前默认值过于保守。
101
+
102
+ **改动位置**:`src/agent/channel-runner.ts` 中的 `recallRelevantMemory()` 调用参数,以及 `src/context.ts` 中的 `PipiclawMemoryRecallSettings` 默认值。
103
+
104
+ | 参数 | 当前值 | 目标值 | 理由 |
105
+ |------|-------|-------|------|
106
+ | maxCandidates | 8 | 12 | 给 reranker 更多候选,提升选择质量 |
107
+ | maxInjected | 3 | 5 | 注入更多上下文,覆盖更多相关信息 |
108
+ | maxChars | 3500 | 5000 | 允许更长的回忆文本 |
109
+ | rerankWithModel | false | true | 默认开启 LLM rerank,显著提升中文召回质量 |
110
+
111
+ ---
112
+
113
+ ### 2.3 [P1] 评分算法精炼
114
+
115
+ **问题**:当前 priority 范围 4-18,lexical 最高约 96。session(priority=18)和 history(priority=4)差距 14 分。当 query 与两个候选的词汇匹配度相同时,session 始终胜出。真正的"相关性优先"尚未实现。
116
+
117
+ **方案**:改为乘法加权,structural 作为调节因子而非独立加分项。
118
+
119
+ ```typescript
120
+ function scoreCandidate(...): ScoredCandidate | null {
121
+ // ... 计算 lexicalScore 和 structuralScore 同现有逻辑 ...
122
+
123
+ if (matchedTokens.size === 0 && exactBoost === 0 && intentBoost === 0) {
124
+ return null;
125
+ }
126
+
127
+ // 乘法加权:lexical 为主,structural 为调节
128
+ // structural 最高约 34(priority 18 + intent 10 + recency 6)
129
+ // 乘数范围约 1.0 ~ 1.34
130
+ const score = lexicalScore * (1 + structuralScore / 100);
131
+
132
+ return { candidate, score, lexicalMatchCount: matchedTokens.size, intentBoost };
133
+ }
134
+ ```
135
+
136
+ **效果**:
137
+ - 当 lexical = 0 时,score = 0 — 无词汇匹配的候选不会仅靠 structural 上位
138
+ - 当 lexical 相同时,structural 提供约 0-34% 的微调 — session 仍有优势但不再碾压
139
+ - 当 history 候选 lexical 远高于 session 候选时,history 可以反超
140
+
141
+ **注意**:`seedIntentCandidates()` 中 intentBoost > 0 但 lexicalMatchCount = 0 的候选,在乘法模式下 score 会变为 0。需要调整 seeding 逻辑(见 2.4)。
142
+
143
+ ---
144
+
145
+ ### 2.4 [P1] 约束 Intent Seeding
146
+
147
+ **问题**:`seedIntentCandidates()` 可注入 lexicalMatchCount=0 的候选。在 2.3 改为乘法加权后,这些候选 score 为 0,会被自然淘汰。但即使不改为乘法,纯 intent 注入也可能挤掉更相关的候选。
148
+
149
+ **方案**:对 intent seeding 增加最低门槛。
150
+
151
+ ```typescript
152
+ function seedIntentCandidates(
153
+ request: RecallRequest,
154
+ candidates: MemoryCandidate[],
155
+ existing: ScoredCandidate[],
156
+ intents: Set<QueryIntent>,
157
+ queryTokens: string[], // 新增参数
158
+ ): ScoredCandidate[] {
159
+ // ...
160
+ for (const { candidate, intentBoost } of intentCandidates) {
161
+ // 至少需要 1 个 query token 匹配,才允许 intent 注入
162
+ const matched = collectMatchingQueryTokens(
163
+ queryTokens,
164
+ [candidate.title, candidate.searchText ?? candidate.content],
165
+ );
166
+ if (matched.size === 0) continue;
167
+
168
+ seeded.push({ ... });
169
+ }
170
+ // ...
171
+ }
172
+ ```
173
+
174
+ **效果**:意图匹配 + 至少 1 个词汇匹配 = 允许注入。纯意图匹配但内容完全无关 = 不注入。
175
+
176
+ ---
177
+
178
+ ### 2.5 [P1] 扩充中文词表
179
+
180
+ **问题**:当前 130 词中包含较多 pipiclaw 内部术语("召回排序"、"排序失真"、"主导权重"),缺少大量通用技术词汇。
181
+
182
+ **方案**:
183
+
184
+ 1. **移除**过于特定的内部术语(约 20 个),如"排序失真"、"主导权重"、"轻量词表"等
185
+ 2. **新增**通用技术词汇(约 200 个),覆盖以下领域:
186
+
187
+ ```
188
+ 基础设施: 服务器、数据库、缓存、队列、网络、集群、容器、网关、负载均衡
189
+ 编程概念: 函数、变量、接口、类型、模块、组件、依赖、注入、继承、多态
190
+ 开发流程: 需求、设计、实现、测试、部署、发布、回滚、监控、告警、日志
191
+ Web/API: 请求、响应、认证、授权、会话、路由、中间件、过滤器、拦截器
192
+ 数据: 查询、索引、事务、迁移、备份、恢复、同步、异步、并发、锁
193
+ 工具: 编辑器、终端、浏览器、调试器、编译器、构建、打包、压缩
194
+ ```
195
+
196
+ 3. 目标词表规模约 300 个,保持文件 < 300 行
197
+
198
+ ---
199
+
200
+ ### 2.6 [P1] 修复代码质量问题
201
+
202
+ #### 2.6.1 运算符优先级明确化
203
+
204
+ **位置**:`src/memory/lifecycle.ts:109-113`
205
+
206
+ ```typescript
207
+ // 当前(行为正确但意图不清晰)
208
+ if (
209
+ canTriggerThresholdRefresh &&
210
+ this.turnsSinceSessionUpdate >= settings.minTurnsBetweenUpdate ||
211
+ (canTriggerThresholdRefresh && this.toolCallsSinceSessionUpdate >= settings.minToolCallsBetweenUpdate)
212
+ )
213
+
214
+ // 修改为
215
+ if (
216
+ canTriggerThresholdRefresh &&
217
+ (this.turnsSinceSessionUpdate >= settings.minTurnsBetweenUpdate ||
218
+ this.toolCallsSinceSessionUpdate >= settings.minToolCallsBetweenUpdate)
219
+ )
220
+ ```
221
+
222
+ #### 2.6.2 提取排序比较器
223
+
224
+ **位置**:`src/memory/recall.ts:487-501`
225
+
226
+ ```typescript
227
+ function compareScoredCandidates(a: ScoredCandidate, b: ScoredCandidate): number {
228
+ return (
229
+ b.score - a.score ||
230
+ b.lexicalMatchCount - a.lexicalMatchCount ||
231
+ b.candidate.priority - a.candidate.priority ||
232
+ a.candidate.title.localeCompare(b.candidate.title)
233
+ );
234
+ }
235
+
236
+ // 使用
237
+ const scored = filteredCandidates.map(...).filter(...).sort(compareScoredCandidates);
238
+ const shortlist = seedIntentCandidates(...).sort(compareScoredCandidates).slice(...);
239
+ ```
240
+
241
+ ---
242
+
243
+ ### 2.7 [P2] Consolidation prompt 强化
244
+
245
+ **问题**:当前 prompt 对 historyBlock 仅用"prefer"措辞,LLM 在短对话时仍可能返回空值。
246
+
247
+ **方案**:添加 few-shot 示例到 system prompt。
248
+
249
+ ```
250
+ Rules:
251
+ ...
252
+ - historyBlock: concise Markdown summarizing the conversation chunk for later recovery.
253
+ - For any conversation with at least one meaningful user question and assistant answer,
254
+ you MUST return a non-empty historyBlock with at least one bullet summarizing the exchange.
255
+ - Prefer short bullets and short paragraphs.
256
+
257
+ Example output for a short Q&A conversation:
258
+ {
259
+ "memoryEntries": ["User prefers dark mode in the dashboard"],
260
+ "historyBlock": "- User asked how to toggle dashboard theme; confirmed dark mode preference."
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ ### 2.8 [P2] Sidecar Token 追踪
267
+
268
+ **问题**:memory 系统通过 sidecar 发起的 LLM 调用(session memory update、inline consolidation、memory cleanup、history folding、LLM rerank)没有 token 使用量统计,无法评估实际 API 开销。
269
+
270
+ **方案**:
271
+
272
+ 1. `runSidecarTask` 返回值中增加 `usage?: { inputTokens: number; outputTokens: number }`
273
+ 2. `MemoryLifecycle` 中累计追踪每类操作的 token 消耗
274
+ 3. 在 background maintenance 日志中输出累计 token 消耗
275
+
276
+ ---
277
+
278
+ ## 三、实施顺序
279
+
280
+ ```
281
+ Phase 1(核心功能)
282
+ 2.1 首轮强制注入 MEMORY.md
283
+ 2.2 Recall 默认配置调优
284
+ 2.6 代码质量修复(运算符优先级 + 排序提取)
285
+
286
+ Phase 2(评分优化)
287
+ 2.3 评分乘法加权
288
+ 2.4 Intent seeding 约束
289
+ 2.5 中文词表扩充
290
+
291
+ Phase 3(可靠性)
292
+ 2.7 Consolidation prompt 强化
293
+ 2.8 Sidecar token 追踪
294
+ ```
295
+
296
+ ---
297
+
298
+ ## 四、验证标准
299
+
300
+ ### 功能验证
301
+
302
+ - [ ] 首轮对话发送模糊消息(如"你好"),prompt 中应包含 `<channel_memory_snapshot>` 标签
303
+ - [ ] 第二轮对话不再包含强制注入内容
304
+ - [ ] 当 recall 已返回 channel-memory 候选时,跳过强制注入
305
+ - [ ] rerankWithModel 默认启用,中文查询自动触发 rerank
306
+
307
+ ### 评分验证
308
+
309
+ - [ ] history 候选在 lexical 高度匹配时可以排在不相关 session 候选之前
310
+ - [ ] lexical = 0 的候选不会仅靠 structural 上位(乘法加权后)
311
+ - [ ] intent seeding 不会注入与 query 零词汇匹配的候选
312
+
313
+ ### 回归验证
314
+
315
+ - [ ] 全部现有测试通过
316
+ - [ ] 新增首轮注入的单元测试
317
+ - [ ] 新增 scoreCandidate 乘法加权的单元测试
318
+ - [ ] 新增中文短查询(2-3 字)的召回集成测试
319
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oyasmi/pipiclaw",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "An AI assistant runtime for coding and team workflows, with DingTalk AI Cards, sub-agents, memory, and scheduled events.",
5
5
  "type": "module",
6
6
  "bin": {