@ai-agent-forge/plugin-memory 0.88.1 → 0.88.4

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 (99) hide show
  1. package/README.md +10 -4
  2. package/agent-forge.json +1 -1
  3. package/bin/agent-forge-memory-inference.js +8 -0
  4. package/dist/capability.d.ts +42 -59
  5. package/dist/capability.d.ts.map +1 -1
  6. package/dist/capability.js +263 -2055
  7. package/dist/capability.js.map +1 -1
  8. package/dist/entry.d.ts +11 -0
  9. package/dist/entry.d.ts.map +1 -1
  10. package/dist/entry.js +38 -2
  11. package/dist/entry.js.map +1 -1
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +7 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/inference-bin.d.ts +21 -0
  17. package/dist/inference-bin.d.ts.map +1 -0
  18. package/dist/inference-bin.js +85 -0
  19. package/dist/inference-bin.js.map +1 -0
  20. package/dist/memory/command-face.d.ts +86 -0
  21. package/dist/memory/command-face.d.ts.map +1 -0
  22. package/dist/memory/command-face.js +377 -0
  23. package/dist/memory/command-face.js.map +1 -0
  24. package/dist/memory/embedding-provider.d.ts.map +1 -1
  25. package/dist/memory/embedding-provider.js +14 -2
  26. package/dist/memory/embedding-provider.js.map +1 -1
  27. package/dist/memory/embedding-reranker.d.ts.map +1 -1
  28. package/dist/memory/embedding-reranker.js +14 -2
  29. package/dist/memory/embedding-reranker.js.map +1 -1
  30. package/dist/memory/enabling.d.ts +104 -0
  31. package/dist/memory/enabling.d.ts.map +1 -0
  32. package/dist/memory/enabling.js +321 -0
  33. package/dist/memory/enabling.js.map +1 -0
  34. package/dist/memory/hooks.d.ts +110 -0
  35. package/dist/memory/hooks.d.ts.map +1 -0
  36. package/dist/memory/hooks.js +478 -0
  37. package/dist/memory/hooks.js.map +1 -0
  38. package/dist/memory/inference-channel.d.ts +33 -0
  39. package/dist/memory/inference-channel.d.ts.map +1 -0
  40. package/dist/memory/inference-channel.js +101 -0
  41. package/dist/memory/inference-channel.js.map +1 -0
  42. package/dist/memory/inference-client.d.ts +25 -0
  43. package/dist/memory/inference-client.d.ts.map +1 -0
  44. package/dist/memory/inference-client.js +216 -0
  45. package/dist/memory/inference-client.js.map +1 -0
  46. package/dist/memory/inference-protocol.d.ts +95 -0
  47. package/dist/memory/inference-protocol.d.ts.map +1 -0
  48. package/dist/memory/inference-protocol.js +27 -0
  49. package/dist/memory/inference-protocol.js.map +1 -0
  50. package/dist/memory/inference-server.d.ts +70 -0
  51. package/dist/memory/inference-server.d.ts.map +1 -0
  52. package/dist/memory/inference-server.js +395 -0
  53. package/dist/memory/inference-server.js.map +1 -0
  54. package/dist/memory/inference-service-main.d.ts +31 -0
  55. package/dist/memory/inference-service-main.d.ts.map +1 -0
  56. package/dist/memory/inference-service-main.js +89 -0
  57. package/dist/memory/inference-service-main.js.map +1 -0
  58. package/dist/memory/ledger.d.ts +15 -0
  59. package/dist/memory/ledger.d.ts.map +1 -1
  60. package/dist/memory/ledger.js +35 -2
  61. package/dist/memory/ledger.js.map +1 -1
  62. package/dist/memory/model-cache-hygiene.d.ts +10 -0
  63. package/dist/memory/model-cache-hygiene.d.ts.map +1 -1
  64. package/dist/memory/model-cache-hygiene.js +15 -0
  65. package/dist/memory/model-cache-hygiene.js.map +1 -1
  66. package/dist/memory/preference-promotion.d.ts +0 -14
  67. package/dist/memory/preference-promotion.d.ts.map +1 -1
  68. package/dist/memory/preference-promotion.js +17 -0
  69. package/dist/memory/preference-promotion.js.map +1 -1
  70. package/dist/memory/recall-pipeline.d.ts +85 -0
  71. package/dist/memory/recall-pipeline.d.ts.map +1 -0
  72. package/dist/memory/recall-pipeline.js +678 -0
  73. package/dist/memory/recall-pipeline.js.map +1 -0
  74. package/dist/memory/retention-sweep.d.ts +59 -0
  75. package/dist/memory/retention-sweep.d.ts.map +1 -0
  76. package/dist/memory/retention-sweep.js +103 -0
  77. package/dist/memory/retention-sweep.js.map +1 -0
  78. package/dist/memory/store.d.ts +17 -2
  79. package/dist/memory/store.d.ts.map +1 -1
  80. package/dist/memory/store.js +18 -2
  81. package/dist/memory/store.js.map +1 -1
  82. package/dist/memory/vector-channel.d.ts +120 -0
  83. package/dist/memory/vector-channel.d.ts.map +1 -0
  84. package/dist/memory/vector-channel.js +495 -0
  85. package/dist/memory/vector-channel.js.map +1 -0
  86. package/dist/memory/vector-projection.d.ts +87 -0
  87. package/dist/memory/vector-projection.d.ts.map +1 -0
  88. package/dist/memory/vector-projection.js +215 -0
  89. package/dist/memory/vector-projection.js.map +1 -0
  90. package/dist/memory/write-face.d.ts +128 -0
  91. package/dist/memory/write-face.d.ts.map +1 -0
  92. package/dist/memory/write-face.js +504 -0
  93. package/dist/memory/write-face.js.map +1 -0
  94. package/dist/testing/memory-testkit.d.ts +2 -0
  95. package/dist/testing/memory-testkit.d.ts.map +1 -1
  96. package/dist/testing/memory-testkit.js +1 -0
  97. package/dist/testing/memory-testkit.js.map +1 -1
  98. package/package.json +7 -3
  99. package/plugin.json +1 -1
@@ -0,0 +1,70 @@
1
+ import type { MemoryEmbeddingProviderV1 } from "./embedding-provider.ts";
2
+ import type { MemoryRerankerV1 } from "./embedding-reranker.ts";
3
+ import type { MemoryVectorIndexV1 } from "./vector-index.ts";
4
+ /**
5
+ * 逐请求看门狗阈值出厂默认 (2026-10-06): 大于客户端请求超时 (装配缝 60s /
6
+ * 客户端缺省 10s) — 正常慢操作先被客户端放弃, 服务端不打断。诚实约束: JS 侧
7
+ * 超时不中断底层原生调用 (ONNX 线程仍占住资源), 语义是"串行队列恢复推进、
8
+ * 后续请求可用", 不是后端取消。
9
+ */
10
+ export declare const MEMORY_INFERENCE_REQUEST_WATCHDOG_MS = 120000;
11
+ /**
12
+ * 连接活跃度阈值出厂默认 (2026-10-07, codex remote-control 同款先例): 自最近
13
+ * 收到帧起超过该时长且无在途请求的连接视为陈旧, 不再阻止空闲退出 — 挂起不退
14
+ * 的客户端 (逻辑死、socket 活) 不会把服务模型永久按驻内存。无协议面新增:
15
+ * 活跃度由服务端按帧到达自行观测, 客户端零改动。
16
+ */
17
+ export declare const MEMORY_INFERENCE_CONNECTION_STALE_MS: number;
18
+ /** 服务端持有的唯一推理后端 (生产 = 真实装配; 测试 = deterministic mock)。 */
19
+ export interface MemoryInferenceBackendV1 {
20
+ readonly embedProvider: MemoryEmbeddingProviderV1;
21
+ readonly reranker?: MemoryRerankerV1;
22
+ readonly vectorIndex: MemoryVectorIndexV1;
23
+ }
24
+ export interface MemoryInferenceServerV1 {
25
+ /** 实际绑定端口 (port: 0 时为 OS 分配值 — 测试用; 生产固定端口即单例互斥)。 */
26
+ readonly port: number;
27
+ readonly host: string;
28
+ /**
29
+ * 优雅停机: 停止监听 → 排空串行队列 (在途响应写回) → 断开全部连接 → 关闭
30
+ * 索引句柄。幂等。
31
+ */
32
+ close(): Promise<void>;
33
+ /**
34
+ * 强制停机 (模拟进程死亡/紧急退出): 立即断开全部连接与在途请求 (客户端
35
+ * pending 全部结构化失败 — 设计 §4), 不等待队列排空。
36
+ */
37
+ destroy(): Promise<void>;
38
+ /** 服务完全关闭 (显式或空闲自杀) 后 resolve。 */
39
+ readonly closed: Promise<void>;
40
+ }
41
+ export interface MemoryInferenceServerOptions {
42
+ readonly backend: MemoryInferenceBackendV1;
43
+ /** 缺省 MEMORY_INFERENCE_DEFAULT_PORT; 0 = OS 分配 (测试)。同端口二次绑定 reject (单例互斥)。 */
44
+ readonly port?: number;
45
+ readonly host?: string;
46
+ /** 空闲自杀阈值 (无活跃连接且无在途请求)。缺省 0 = 不启用。出厂默认值登记产品默认值清单。 */
47
+ readonly idleTimeoutMs?: number;
48
+ /**
49
+ * 连接活跃度阈值 (2026-10-07): 见 MEMORY_INFERENCE_CONNECTION_STALE_MS。
50
+ * 缺省 10 分钟; 0 = 关闭 (任何连接都阻止空闲退出 — 原语义)。
51
+ */
52
+ readonly connectionStaleMs?: number;
53
+ /**
54
+ * 服务进程的 agent-dir 身份 (2026-10-06 串线修复): hello 握手回报给客户端,
55
+ * 会话装配据此拒绝跨 agent-dir 串线。缺省不上报 (旧版本 wire 兼容, 装配校验
56
+ * 按缺失拒绝); 生产服务经 runMemoryInferenceService 传入。
57
+ */
58
+ readonly agentDir?: string;
59
+ /**
60
+ * 逐请求看门狗阈值: 后端派发超过该时长即对该请求返回结构化 backend_error,
61
+ * 串行队列继续推进 (不中断底层原生调用 — 见
62
+ * MEMORY_INFERENCE_REQUEST_WATCHDOG_MS 注释)。缺省 120s; 测试注入小值。
63
+ */
64
+ readonly requestWatchdogMs?: number;
65
+ readonly signal?: AbortSignal;
66
+ /** 结构化错误面 (日志/诊断); 不抛出 — 服务端错误一律转为 error 响应或告警。 */
67
+ readonly onError?: (error: Error, context: string) => void;
68
+ }
69
+ export declare function createMemoryInferenceServer(options: MemoryInferenceServerOptions): Promise<MemoryInferenceServerV1>;
70
+ //# sourceMappingURL=inference-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inference-server.d.ts","sourceRoot":"","sources":["../../src/memory/inference-server.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAWhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,SAAU,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,QAAiB,CAAC;AAKnE,mGAAyD;AACzD,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC1C;AAED,MAAM,WAAW,uBAAuB;IACvC,2GAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,gEAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,oHAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,oIAAsD;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,6GAAmD;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3D;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAwWnH","sourcesContent":["/**\n * 记忆共享推理服务 — 服务端 (记忆共享推理服务设计 §3.1/§3.4/§3.5)。\n *\n * 常驻 sidecar 的服务端面: 持有唯一的推理后端 (embedProvider/reranker/\n * vectorIndex — 生产装配为真实 transformers + LanceDB, 测试注入 deterministic\n * mock), 经 TCP loopback 服务 `inference-protocol.ts` 的消息面。\n *\n * 串行化 (设计 §3.4): 所有请求经单条 promise 链按到达序执行 — LanceDB 表\n * 创建与 ONNX 管线未声明并发安全, 单用户量级下串行的吞吐足够, 正确性优先\n * (同一理由: 每进程一个 vectorQueue 的既有先例)。\n *\n * 逐请求看门狗 (2026-10-06, 设计 §3.4): 后端派发超过 requestWatchdogMs\n * (缺省 MEMORY_INFERENCE_REQUEST_WATCHDOG_MS) 即对该请求返回结构化\n * backend_error, 串行队列恢复推进 — 后端永不落定时 (如 ONNX 挂起) 其余请求\n * 不再排队等死。诚实约束: JS 侧超时不中断底层原生调用 (ONNX 线程仍占住),\n * 语义是\"队列恢复推进、后续请求可用\", 不是后端取消。\n *\n * 生命周期 (设计 §3.5): 空闲自杀 — 无活跃连接且无在途请求持续 idleTimeoutMs 后\n * 优雅关闭 (端口让出, 下一个使用者重新拉起); 新连接取消计时。活跃 = 有在途请求,\n * 或最近收到帧距今未超过连接活跃度阈值 (connectionStaleMs): 挂起不退的客户端\n * (逻辑死、socket 活) 不再把空闲退出永久按住, 由周期清扫重估。close() 供\n * 进程入口与测试显式停机。\n */\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport { FrameDecoder } from \"@agent-forge/protocol\";\nimport type { MemoryEmbeddingProviderV1 } from \"./embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"./embedding-reranker.ts\";\nimport {\n\tdecodeInferenceMessage,\n\tencodeInferenceMessage,\n\tMEMORY_INFERENCE_DEFAULT_HOST,\n\tMEMORY_INFERENCE_DEFAULT_PORT,\n\tMEMORY_INFERENCE_PROTOCOL_VERSION,\n\ttype MemoryInferenceHelloResultV1,\n\ttype MemoryInferenceRequestV1,\n\ttype MemoryInferenceResponseV1,\n} from \"./inference-protocol.ts\";\nimport type { MemoryVectorIndexV1 } from \"./vector-index.ts\";\n\n/**\n * 逐请求看门狗阈值出厂默认 (2026-10-06): 大于客户端请求超时 (装配缝 60s /\n * 客户端缺省 10s) — 正常慢操作先被客户端放弃, 服务端不打断。诚实约束: JS 侧\n * 超时不中断底层原生调用 (ONNX 线程仍占住资源), 语义是\"串行队列恢复推进、\n * 后续请求可用\", 不是后端取消。\n */\nexport const MEMORY_INFERENCE_REQUEST_WATCHDOG_MS = 120_000;\n\n/**\n * 连接活跃度阈值出厂默认 (2026-10-07, codex remote-control 同款先例): 自最近\n * 收到帧起超过该时长且无在途请求的连接视为陈旧, 不再阻止空闲退出 — 挂起不退\n * 的客户端 (逻辑死、socket 活) 不会把服务模型永久按驻内存。无协议面新增:\n * 活跃度由服务端按帧到达自行观测, 客户端零改动。\n */\nexport const MEMORY_INFERENCE_CONNECTION_STALE_MS = 10 * 60 * 1000;\n\n/** 陈旧连接清扫周期上限; 实际取 min(本值, 阈值/2), 测试注入短阈值即得短周期。 */\nconst CONNECTION_STALE_SWEEP_MAX_MS = 30_000;\n\n/** 服务端持有的唯一推理后端 (生产 = 真实装配; 测试 = deterministic mock)。 */\nexport interface MemoryInferenceBackendV1 {\n\treadonly embedProvider: MemoryEmbeddingProviderV1;\n\treadonly reranker?: MemoryRerankerV1;\n\treadonly vectorIndex: MemoryVectorIndexV1;\n}\n\nexport interface MemoryInferenceServerV1 {\n\t/** 实际绑定端口 (port: 0 时为 OS 分配值 — 测试用; 生产固定端口即单例互斥)。 */\n\treadonly port: number;\n\treadonly host: string;\n\t/**\n\t * 优雅停机: 停止监听 → 排空串行队列 (在途响应写回) → 断开全部连接 → 关闭\n\t * 索引句柄。幂等。\n\t */\n\tclose(): Promise<void>;\n\t/**\n\t * 强制停机 (模拟进程死亡/紧急退出): 立即断开全部连接与在途请求 (客户端\n\t * pending 全部结构化失败 — 设计 §4), 不等待队列排空。\n\t */\n\tdestroy(): Promise<void>;\n\t/** 服务完全关闭 (显式或空闲自杀) 后 resolve。 */\n\treadonly closed: Promise<void>;\n}\n\nexport interface MemoryInferenceServerOptions {\n\treadonly backend: MemoryInferenceBackendV1;\n\t/** 缺省 MEMORY_INFERENCE_DEFAULT_PORT; 0 = OS 分配 (测试)。同端口二次绑定 reject (单例互斥)。 */\n\treadonly port?: number;\n\treadonly host?: string;\n\t/** 空闲自杀阈值 (无活跃连接且无在途请求)。缺省 0 = 不启用。出厂默认值登记产品默认值清单。 */\n\treadonly idleTimeoutMs?: number;\n\t/**\n\t * 连接活跃度阈值 (2026-10-07): 见 MEMORY_INFERENCE_CONNECTION_STALE_MS。\n\t * 缺省 10 分钟; 0 = 关闭 (任何连接都阻止空闲退出 — 原语义)。\n\t */\n\treadonly connectionStaleMs?: number;\n\t/**\n\t * 服务进程的 agent-dir 身份 (2026-10-06 串线修复): hello 握手回报给客户端,\n\t * 会话装配据此拒绝跨 agent-dir 串线。缺省不上报 (旧版本 wire 兼容, 装配校验\n\t * 按缺失拒绝); 生产服务经 runMemoryInferenceService 传入。\n\t */\n\treadonly agentDir?: string;\n\t/**\n\t * 逐请求看门狗阈值: 后端派发超过该时长即对该请求返回结构化 backend_error,\n\t * 串行队列继续推进 (不中断底层原生调用 — 见\n\t * MEMORY_INFERENCE_REQUEST_WATCHDOG_MS 注释)。缺省 120s; 测试注入小值。\n\t */\n\treadonly requestWatchdogMs?: number;\n\treadonly signal?: AbortSignal;\n\t/** 结构化错误面 (日志/诊断); 不抛出 — 服务端错误一律转为 error 响应或告警。 */\n\treadonly onError?: (error: Error, context: string) => void;\n}\n\nexport function createMemoryInferenceServer(options: MemoryInferenceServerOptions): Promise<MemoryInferenceServerV1> {\n\tconst host = options.host ?? MEMORY_INFERENCE_DEFAULT_HOST;\n\tconst idleTimeoutMs = options.idleTimeoutMs ?? 0;\n\tconst connectionStaleMs = options.connectionStaleMs ?? MEMORY_INFERENCE_CONNECTION_STALE_MS;\n\tconst requestWatchdogMs = options.requestWatchdogMs ?? MEMORY_INFERENCE_REQUEST_WATCHDOG_MS;\n\tlet idleTimer: NodeJS.Timeout | undefined;\n\tconst sockets = new Set<Socket>();\n\t// 连接活跃度 (2026-10-07): 每连接最近帧到达时刻 + 在途请求数。活跃 = 有在途\n\t// 请求 (帧可能早于陈旧阈值 — 串行队列排队/后端慢不算挂起), 或最近帧未超阈。\n\tconst socketStates = new Map<Socket, { lastFrameAt: number; pendingRequests: number }>();\n\tlet closed = false;\n\tlet closeResolve: (() => void) | undefined;\n\tconst closedPromise = new Promise<void>((resolve) => {\n\t\tcloseResolve = resolve;\n\t});\n\t// 全局串行队列: 所有连接的全部请求按到达序执行 (设计 §3.4)。\n\tlet queueTail: Promise<void> = Promise.resolve();\n\tconst enqueue = (operation: () => Promise<void>): Promise<void> => {\n\t\tconst next = queueTail.then(operation, operation);\n\t\tqueueTail = next.catch(() => undefined);\n\t\treturn next;\n\t};\n\t// 逐请求看门狗 (2026-10-06): 包住单次后端派发, 超时即返回结构化 backend_error,\n\t// 让串行队列的当前位置落定 — 后端 promise 永不落定时 (如 ONNX 挂起) 后续请求\n\t// 不再排队等死。诚实约束: 超时只是不再等待, 底层原生调用无法被中断 (ONNX\n\t// 线程仍占住资源), 语义是\"队列恢复推进、后续请求可用\", 不是后端取消; 输掉\n\t// 竞速的 handleRequest 在后台落定, 其迟到结果被丢弃 (连接可能已亡, write\n\t// 错误走 socket error 处理)。\n\tconst withRequestWatchdog = (\n\t\trequest: MemoryInferenceRequestV1,\n\t\toperation: Promise<MemoryInferenceResponseV1>,\n\t): Promise<MemoryInferenceResponseV1> => {\n\t\tlet watchdog: NodeJS.Timeout | undefined;\n\t\tconst expired = new Promise<MemoryInferenceResponseV1>((resolve) => {\n\t\t\twatchdog = setTimeout(() => {\n\t\t\t\tresolve({\n\t\t\t\t\tkind: \"error\",\n\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\tcode: \"backend_error\",\n\t\t\t\t\tmessage: `memory inference backend timed out after ${requestWatchdogMs}ms (request kind=${request.kind})`,\n\t\t\t\t});\n\t\t\t}, requestWatchdogMs);\n\t\t\twatchdog.unref?.();\n\t\t});\n\t\treturn Promise.race([operation, expired]).finally(() => clearTimeout(watchdog));\n\t};\n\n\tconst server: Server = createServer((socket: Socket) => {\n\t\tsockets.add(socket);\n\t\tsocketStates.set(socket, { lastFrameAt: Date.now(), pendingRequests: 0 });\n\t\tif (idleTimer !== undefined) {\n\t\t\tclearTimeout(idleTimer);\n\t\t\tidleTimer = undefined;\n\t\t}\n\t\tconst decoder = new FrameDecoder();\n\t\t// 响应写回 (2026-10-06): 超过 16MB 帧/CBOR 上限的响应 (如 full 档大包\n\t\t// embed) 无法原样编码, 编码抛错若不接住会被 enqueue 的队列兜底 catch 吞\n\t\t// 掉 — 客户端吃满请求超时且根因不可见。回小体量结构化错误帧让客户端\n\t\t// 立刻拿到根因并收缩批量; 错误帧自身再失败只能断连 (socket 已不可写)。\n\t\tconst writeResponse = (response: MemoryInferenceResponseV1, endAfter: boolean): void => {\n\t\t\tlet payload = response;\n\t\t\tlet bytes: Uint8Array;\n\t\t\ttry {\n\t\t\t\tbytes = encodeInferenceMessage(payload);\n\t\t\t} catch (encodeError) {\n\t\t\t\toptions.onError?.(\n\t\t\t\t\tencodeError instanceof Error ? encodeError : new Error(String(encodeError)),\n\t\t\t\t\t\"inference-server:encode\",\n\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tpayload = {\n\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\trequestId: response.requestId,\n\t\t\t\t\t\tcode: \"backend_error\",\n\t\t\t\t\t\tmessage: `memory inference response exceeds the wire frame limit; shrink the batch (${\n\t\t\t\t\t\t\tencodeError instanceof Error ? encodeError.message : String(encodeError)\n\t\t\t\t\t\t})`,\n\t\t\t\t\t};\n\t\t\t\t\tbytes = encodeInferenceMessage(payload);\n\t\t\t\t} catch {\n\t\t\t\t\tsocket.destroy();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tsocket.write(bytes, endAfter ? () => socket.end() : undefined);\n\t\t};\n\t\tsocket.on(\"data\", (chunk: Buffer) => {\n\t\t\t// 任何字节到达都是活跃度证据 (坏帧连接随后即被断开, 无所谓)。\n\t\t\tconst socketState = socketStates.get(socket);\n\t\t\tif (socketState !== undefined) socketState.lastFrameAt = Date.now();\n\t\t\t// 帧层异常必须捕获 (复审 P2-1): 声明长度超限的帧在 data 监听器抛\n\t\t\t// FrameError 即 uncaughtException — 坏帧/合法超大帧可干死服务进程。\n\t\t\t// 先例: subagent/stdio-channel.ts 同款处理。\n\t\t\tlet frames: Uint8Array[];\n\t\t\ttry {\n\t\t\t\tframes = decoder.push(new Uint8Array(chunk));\n\t\t\t} catch (error) {\n\t\t\t\toptions.onError?.(error instanceof Error ? error : new Error(String(error)), \"inference-server:frame\");\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const frame of frames) {\n\t\t\t\tif (frame.byteLength === 0) continue;\n\t\t\t\tlet request: MemoryInferenceRequestV1;\n\t\t\t\ttry {\n\t\t\t\t\trequest = decodeInferenceMessage(frame) as MemoryInferenceRequestV1;\n\t\t\t\t} catch (error) {\n\t\t\t\t\toptions.onError?.(error instanceof Error ? error : new Error(String(error)), \"inference-server:decode\");\n\t\t\t\t\tsocket.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (socketState !== undefined) socketState.pendingRequests += 1;\n\t\t\t\tvoid enqueue(() =>\n\t\t\t\t\twithRequestWatchdog(request, handleRequest(request)).then(\n\t\t\t\t\t\t(response) => {\n\t\t\t\t\t\t\t// 优雅停机期间在途响应仍写回 (close 先排空队列再断连);\n\t\t\t\t\t\t\t// socket 已亡时 write 错误走 socket error 处理。\n\t\t\t\t\t\t\tif (response.kind === \"error\" && response.code === \"protocol_version\") {\n\t\t\t\t\t\t\t\twriteResponse(response, true);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\twriteResponse(response, false);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t),\n\t\t\t\t).finally(() => {\n\t\t\t\t\tif (socketState !== undefined) socketState.pendingRequests -= 1;\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\tconst drop = () => {\n\t\t\tsocket.destroy();\n\t\t};\n\t\tsocket.on(\"error\", (error) => {\n\t\t\toptions.onError?.(error, \"inference-server:socket\");\n\t\t\tdrop();\n\t\t});\n\t\tsocket.on(\"close\", () => {\n\t\t\tsockets.delete(socket);\n\t\t\tsocketStates.delete(socket);\n\t\t\tmaybeScheduleIdleExit();\n\t\t});\n\t});\n\n\t/**\n\t * 活跃连接数: 有在途请求, 或最近帧距现在未超过活跃度阈值的连接。\n\t * connectionStaleMs <= 0 时退化为全部连接 (原语义)。\n\t */\n\tconst activeConnectionCount = (): number => {\n\t\tif (connectionStaleMs <= 0) return sockets.size;\n\t\tconst nowMs = Date.now();\n\t\tlet active = 0;\n\t\tfor (const socket of sockets) {\n\t\t\tconst state = socketStates.get(socket);\n\t\t\tif (state === undefined) continue;\n\t\t\tif (state.pendingRequests > 0 || nowMs - state.lastFrameAt < connectionStaleMs) active += 1;\n\t\t}\n\t\treturn active;\n\t};\n\n\tconst handleRequest = async (request: MemoryInferenceRequestV1): Promise<MemoryInferenceResponseV1> => {\n\t\ttry {\n\t\t\tswitch (request.kind) {\n\t\t\t\tcase \"hello\": {\n\t\t\t\t\tif (request.protocolVersion !== MEMORY_INFERENCE_PROTOCOL_VERSION) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\t\tcode: \"protocol_version\",\n\t\t\t\t\t\t\tmessage: `inference protocol version mismatch: server=${MEMORY_INFERENCE_PROTOCOL_VERSION} client=${request.protocolVersion}`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// agentDir 身份上报 (2026-10-06 串线修复): 有则带 (生产经\n\t\t\t\t\t// runMemoryInferenceService 传入), 无则不带 — 直连本工厂的旧\n\t\t\t\t\t// 测试缝与升级过渡期的旧版本服务进程保持 wire 兼容, 会话装配\n\t\t\t\t\t// 按字段缺失拒绝 (设计 §3.2)。\n\t\t\t\t\tconst helloBase = {\n\t\t\t\t\t\tprotocolVersion: MEMORY_INFERENCE_PROTOCOL_VERSION,\n\t\t\t\t\t\tembeddingModelId: options.backend.embedProvider.modelId,\n\t\t\t\t\t\tdimensions: options.backend.embedProvider.dimensions,\n\t\t\t\t\t\trerankerModelId: options.backend.reranker?.modelId ?? null,\n\t\t\t\t\t};\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"ok\",\n\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\tresult:\n\t\t\t\t\t\t\toptions.agentDir === undefined\n\t\t\t\t\t\t\t\t? (helloBase as MemoryInferenceHelloResultV1)\n\t\t\t\t\t\t\t\t: { ...helloBase, agentDir: options.agentDir },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcase \"embed\": {\n\t\t\t\t\tconst vectors = await options.backend.embedProvider.embed(request.texts);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { vectors } };\n\t\t\t\t}\n\t\t\t\tcase \"rerank\": {\n\t\t\t\t\tif (options.backend.reranker === undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\t\tcode: \"backend_error\",\n\t\t\t\t\t\t\tmessage: \"inference server has no reranker\",\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst scores = await options.backend.reranker.rerank(request.query, request.docs);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { scores } };\n\t\t\t\t}\n\t\t\t\tcase \"queryKnn\": {\n\t\t\t\t\tconst hits = await options.backend.vectorIndex.queryKnn(request.vector, request.query);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { hits } };\n\t\t\t\t}\n\t\t\t\tcase \"queryFts\": {\n\t\t\t\t\tconst hits = await options.backend.vectorIndex.queryFts(request.queryText, request.query);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { hits } };\n\t\t\t\t}\n\t\t\t\tcase \"upsert\": {\n\t\t\t\t\tawait options.backend.vectorIndex.upsert(request.entries);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { pong: true } };\n\t\t\t\t}\n\t\t\t\tcase \"remove\": {\n\t\t\t\t\tawait options.backend.vectorIndex.remove(request.memoryIds);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { pong: true } };\n\t\t\t\t}\n\t\t\t\tcase \"listMemoryIds\": {\n\t\t\t\t\tconst ids = await options.backend.vectorIndex.listMemoryIds();\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { memoryIds: [...ids] } };\n\t\t\t\t}\n\t\t\t\tcase \"count\": {\n\t\t\t\t\tconst count = await options.backend.vectorIndex.count();\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { count } };\n\t\t\t\t}\n\t\t\t\tcase \"getStoredModelId\": {\n\t\t\t\t\tconst modelId = await options.backend.vectorIndex.getStoredModelId();\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { modelId: modelId ?? null } };\n\t\t\t\t}\n\t\t\t\tcase \"ping\":\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { pong: true } };\n\t\t\t\tdefault:\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\trequestId: (request as { requestId: number }).requestId,\n\t\t\t\t\t\tcode: \"bad_request\",\n\t\t\t\t\t\tmessage: `unknown inference request kind: ${(request as { kind: string }).kind}`,\n\t\t\t\t\t};\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"error\",\n\t\t\t\trequestId: request.requestId,\n\t\t\t\tcode: \"backend_error\",\n\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t};\n\n\tconst maybeScheduleIdleExit = (): void => {\n\t\tif (closed || idleTimeoutMs <= 0 || activeConnectionCount() > 0 || idleTimer !== undefined) return;\n\t\tidleTimer = setTimeout(() => {\n\t\t\tidleTimer = undefined;\n\t\t\tif (activeConnectionCount() > 0 || closed) return;\n\t\t\tvoid finishClose(\"idle\", true);\n\t\t}, idleTimeoutMs);\n\t\tidleTimer.unref?.();\n\t};\n\n\t// 陈旧连接清扫 (2026-10-07): 活跃→陈旧的转变没有事件 (挂起客户端不发帧也\n\t// 不断连), 靠周期扫描重估空闲退出。周期取 min(30s, 阈值/2) — 生产 30s,\n\t// 测试注入短阈值即得短周期。仅在空闲退出与活跃度阈值都启用时运转。\n\tlet staleSweepTimer: NodeJS.Timeout | undefined;\n\tconst startStaleSweep = (): void => {\n\t\tif (closed || idleTimeoutMs <= 0 || connectionStaleMs <= 0 || staleSweepTimer !== undefined) {\n\t\t\treturn;\n\t\t}\n\t\tconst sweepIntervalMs = Math.min(CONNECTION_STALE_SWEEP_MAX_MS, Math.max(1, Math.floor(connectionStaleMs / 2)));\n\t\tstaleSweepTimer = setInterval(() => {\n\t\t\tif (!closed) maybeScheduleIdleExit();\n\t\t}, sweepIntervalMs);\n\t\tstaleSweepTimer.unref?.();\n\t};\n\n\tlet finishPromise: Promise<void> | undefined;\n\tconst finishClose = (reason: string, drain: boolean): Promise<void> => {\n\t\t// 并发调用统一返回同一次停机 (复审 P3-5): 后到的 close/destroy/空闲触发\n\t\t// 不产生第二套停机语义, 先到者 (优雅或强制) 定夺。\n\t\tif (finishPromise !== undefined) return finishPromise;\n\t\tfinishPromise = (async () => {\n\t\t\tclosed = true;\n\t\t\tif (idleTimer !== undefined) {\n\t\t\t\tclearTimeout(idleTimer);\n\t\t\t\tidleTimer = undefined;\n\t\t\t}\n\t\t\tif (staleSweepTimer !== undefined) {\n\t\t\t\tclearInterval(staleSweepTimer);\n\t\t\t\tstaleSweepTimer = undefined;\n\t\t\t}\n\t\t\t// 停止接受新连接; 优雅模式先排空串行队列 (在途响应写回), 再断开全部\n\t\t\t// 连接 (客户端 pending 结构化失败 — 设计 §4); 强制模式立即断连。\n\t\t\tconst stopped = new Promise<void>((resolve) => server.close(() => resolve()));\n\t\t\tif (drain) await queueTail.catch(() => undefined);\n\t\t\tfor (const socket of sockets) {\n\t\t\t\t// 优雅路径用 end() (FIN): 内核把在途回程响应刷完再关, 客户端 pending\n\t\t\t\t// 先收到结果; 强制路径 destroy() 立即丢弃 (客户端 pending 结构化失败)。\n\t\t\t\tif (drain) socket.end();\n\t\t\t\telse socket.destroy();\n\t\t\t}\n\t\t\tsockets.clear();\n\t\t\tsocketStates.clear();\n\t\t\tawait stopped;\n\t\t\tif (drain) {\n\t\t\t\ttry {\n\t\t\t\t\tawait options.backend.vectorIndex.close();\n\t\t\t\t} catch (error) {\n\t\t\t\t\toptions.onError?.(\n\t\t\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\t\t`inference-server:close:${reason}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcloseResolve?.();\n\t\t})();\n\t\treturn finishPromise;\n\t};\n\n\toptions.signal?.addEventListener(\n\t\t\"abort\",\n\t\t() => {\n\t\t\tvoid finishClose(\"signal\", true);\n\t\t},\n\t\t{ once: true },\n\t);\n\n\treturn new Promise<MemoryInferenceServerV1>((resolve, reject) => {\n\t\tconst onListenError = (error: Error) => {\n\t\t\tserver.close();\n\t\t\treject(error);\n\t\t};\n\t\tserver.once(\"error\", onListenError);\n\t\tserver.listen(options.port ?? MEMORY_INFERENCE_DEFAULT_PORT, host, () => {\n\t\t\tserver.off(\"error\", onListenError);\n\t\t\tconst address = server.address();\n\t\t\tif (address === null || typeof address === \"string\") {\n\t\t\t\treject(new Error(\"inference server failed to bind a TCP port\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tserver.on(\"error\", (error) => {\n\t\t\t\toptions.onError?.(error, \"inference-server:server\");\n\t\t\t});\n\t\t\tconst api: MemoryInferenceServerV1 = {\n\t\t\t\tport: address.port,\n\t\t\t\thost,\n\t\t\t\tclose: () => finishClose(\"explicit\", true),\n\t\t\t\tdestroy: () => finishClose(\"destroy\", false),\n\t\t\t\tclosed: closedPromise,\n\t\t\t};\n\t\t\tresolve(api);\n\t\t\t// 已在无连接状态启动 (例如 signal 先行 abort) 时立刻评估空闲退出。\n\t\t\tmaybeScheduleIdleExit();\n\t\t\tstartStaleSweep();\n\t\t});\n\t});\n}\n"]}
@@ -0,0 +1,395 @@
1
+ /**
2
+ * 记忆共享推理服务 — 服务端 (记忆共享推理服务设计 §3.1/§3.4/§3.5)。
3
+ *
4
+ * 常驻 sidecar 的服务端面: 持有唯一的推理后端 (embedProvider/reranker/
5
+ * vectorIndex — 生产装配为真实 transformers + LanceDB, 测试注入 deterministic
6
+ * mock), 经 TCP loopback 服务 `inference-protocol.ts` 的消息面。
7
+ *
8
+ * 串行化 (设计 §3.4): 所有请求经单条 promise 链按到达序执行 — LanceDB 表
9
+ * 创建与 ONNX 管线未声明并发安全, 单用户量级下串行的吞吐足够, 正确性优先
10
+ * (同一理由: 每进程一个 vectorQueue 的既有先例)。
11
+ *
12
+ * 逐请求看门狗 (2026-10-06, 设计 §3.4): 后端派发超过 requestWatchdogMs
13
+ * (缺省 MEMORY_INFERENCE_REQUEST_WATCHDOG_MS) 即对该请求返回结构化
14
+ * backend_error, 串行队列恢复推进 — 后端永不落定时 (如 ONNX 挂起) 其余请求
15
+ * 不再排队等死。诚实约束: JS 侧超时不中断底层原生调用 (ONNX 线程仍占住),
16
+ * 语义是"队列恢复推进、后续请求可用", 不是后端取消。
17
+ *
18
+ * 生命周期 (设计 §3.5): 空闲自杀 — 无活跃连接且无在途请求持续 idleTimeoutMs 后
19
+ * 优雅关闭 (端口让出, 下一个使用者重新拉起); 新连接取消计时。活跃 = 有在途请求,
20
+ * 或最近收到帧距今未超过连接活跃度阈值 (connectionStaleMs): 挂起不退的客户端
21
+ * (逻辑死、socket 活) 不再把空闲退出永久按住, 由周期清扫重估。close() 供
22
+ * 进程入口与测试显式停机。
23
+ */
24
+ import { createServer } from "node:net";
25
+ import { FrameDecoder } from "@agent-forge/protocol";
26
+ import { decodeInferenceMessage, encodeInferenceMessage, MEMORY_INFERENCE_DEFAULT_HOST, MEMORY_INFERENCE_DEFAULT_PORT, MEMORY_INFERENCE_PROTOCOL_VERSION, } from "./inference-protocol.js";
27
+ /**
28
+ * 逐请求看门狗阈值出厂默认 (2026-10-06): 大于客户端请求超时 (装配缝 60s /
29
+ * 客户端缺省 10s) — 正常慢操作先被客户端放弃, 服务端不打断。诚实约束: JS 侧
30
+ * 超时不中断底层原生调用 (ONNX 线程仍占住资源), 语义是"串行队列恢复推进、
31
+ * 后续请求可用", 不是后端取消。
32
+ */
33
+ export const MEMORY_INFERENCE_REQUEST_WATCHDOG_MS = 120_000;
34
+ /**
35
+ * 连接活跃度阈值出厂默认 (2026-10-07, codex remote-control 同款先例): 自最近
36
+ * 收到帧起超过该时长且无在途请求的连接视为陈旧, 不再阻止空闲退出 — 挂起不退
37
+ * 的客户端 (逻辑死、socket 活) 不会把服务模型永久按驻内存。无协议面新增:
38
+ * 活跃度由服务端按帧到达自行观测, 客户端零改动。
39
+ */
40
+ export const MEMORY_INFERENCE_CONNECTION_STALE_MS = 10 * 60 * 1000;
41
+ /** 陈旧连接清扫周期上限; 实际取 min(本值, 阈值/2), 测试注入短阈值即得短周期。 */
42
+ const CONNECTION_STALE_SWEEP_MAX_MS = 30_000;
43
+ export function createMemoryInferenceServer(options) {
44
+ const host = options.host ?? MEMORY_INFERENCE_DEFAULT_HOST;
45
+ const idleTimeoutMs = options.idleTimeoutMs ?? 0;
46
+ const connectionStaleMs = options.connectionStaleMs ?? MEMORY_INFERENCE_CONNECTION_STALE_MS;
47
+ const requestWatchdogMs = options.requestWatchdogMs ?? MEMORY_INFERENCE_REQUEST_WATCHDOG_MS;
48
+ let idleTimer;
49
+ const sockets = new Set();
50
+ // 连接活跃度 (2026-10-07): 每连接最近帧到达时刻 + 在途请求数。活跃 = 有在途
51
+ // 请求 (帧可能早于陈旧阈值 — 串行队列排队/后端慢不算挂起), 或最近帧未超阈。
52
+ const socketStates = new Map();
53
+ let closed = false;
54
+ let closeResolve;
55
+ const closedPromise = new Promise((resolve) => {
56
+ closeResolve = resolve;
57
+ });
58
+ // 全局串行队列: 所有连接的全部请求按到达序执行 (设计 §3.4)。
59
+ let queueTail = Promise.resolve();
60
+ const enqueue = (operation) => {
61
+ const next = queueTail.then(operation, operation);
62
+ queueTail = next.catch(() => undefined);
63
+ return next;
64
+ };
65
+ // 逐请求看门狗 (2026-10-06): 包住单次后端派发, 超时即返回结构化 backend_error,
66
+ // 让串行队列的当前位置落定 — 后端 promise 永不落定时 (如 ONNX 挂起) 后续请求
67
+ // 不再排队等死。诚实约束: 超时只是不再等待, 底层原生调用无法被中断 (ONNX
68
+ // 线程仍占住资源), 语义是"队列恢复推进、后续请求可用", 不是后端取消; 输掉
69
+ // 竞速的 handleRequest 在后台落定, 其迟到结果被丢弃 (连接可能已亡, write
70
+ // 错误走 socket error 处理)。
71
+ const withRequestWatchdog = (request, operation) => {
72
+ let watchdog;
73
+ const expired = new Promise((resolve) => {
74
+ watchdog = setTimeout(() => {
75
+ resolve({
76
+ kind: "error",
77
+ requestId: request.requestId,
78
+ code: "backend_error",
79
+ message: `memory inference backend timed out after ${requestWatchdogMs}ms (request kind=${request.kind})`,
80
+ });
81
+ }, requestWatchdogMs);
82
+ watchdog.unref?.();
83
+ });
84
+ return Promise.race([operation, expired]).finally(() => clearTimeout(watchdog));
85
+ };
86
+ const server = createServer((socket) => {
87
+ sockets.add(socket);
88
+ socketStates.set(socket, { lastFrameAt: Date.now(), pendingRequests: 0 });
89
+ if (idleTimer !== undefined) {
90
+ clearTimeout(idleTimer);
91
+ idleTimer = undefined;
92
+ }
93
+ const decoder = new FrameDecoder();
94
+ // 响应写回 (2026-10-06): 超过 16MB 帧/CBOR 上限的响应 (如 full 档大包
95
+ // embed) 无法原样编码, 编码抛错若不接住会被 enqueue 的队列兜底 catch 吞
96
+ // 掉 — 客户端吃满请求超时且根因不可见。回小体量结构化错误帧让客户端
97
+ // 立刻拿到根因并收缩批量; 错误帧自身再失败只能断连 (socket 已不可写)。
98
+ const writeResponse = (response, endAfter) => {
99
+ let payload = response;
100
+ let bytes;
101
+ try {
102
+ bytes = encodeInferenceMessage(payload);
103
+ }
104
+ catch (encodeError) {
105
+ options.onError?.(encodeError instanceof Error ? encodeError : new Error(String(encodeError)), "inference-server:encode");
106
+ try {
107
+ payload = {
108
+ kind: "error",
109
+ requestId: response.requestId,
110
+ code: "backend_error",
111
+ message: `memory inference response exceeds the wire frame limit; shrink the batch (${encodeError instanceof Error ? encodeError.message : String(encodeError)})`,
112
+ };
113
+ bytes = encodeInferenceMessage(payload);
114
+ }
115
+ catch {
116
+ socket.destroy();
117
+ return;
118
+ }
119
+ }
120
+ socket.write(bytes, endAfter ? () => socket.end() : undefined);
121
+ };
122
+ socket.on("data", (chunk) => {
123
+ // 任何字节到达都是活跃度证据 (坏帧连接随后即被断开, 无所谓)。
124
+ const socketState = socketStates.get(socket);
125
+ if (socketState !== undefined)
126
+ socketState.lastFrameAt = Date.now();
127
+ // 帧层异常必须捕获 (复审 P2-1): 声明长度超限的帧在 data 监听器抛
128
+ // FrameError 即 uncaughtException — 坏帧/合法超大帧可干死服务进程。
129
+ // 先例: subagent/stdio-channel.ts 同款处理。
130
+ let frames;
131
+ try {
132
+ frames = decoder.push(new Uint8Array(chunk));
133
+ }
134
+ catch (error) {
135
+ options.onError?.(error instanceof Error ? error : new Error(String(error)), "inference-server:frame");
136
+ socket.destroy();
137
+ return;
138
+ }
139
+ for (const frame of frames) {
140
+ if (frame.byteLength === 0)
141
+ continue;
142
+ let request;
143
+ try {
144
+ request = decodeInferenceMessage(frame);
145
+ }
146
+ catch (error) {
147
+ options.onError?.(error instanceof Error ? error : new Error(String(error)), "inference-server:decode");
148
+ socket.end();
149
+ return;
150
+ }
151
+ if (socketState !== undefined)
152
+ socketState.pendingRequests += 1;
153
+ void enqueue(() => withRequestWatchdog(request, handleRequest(request)).then((response) => {
154
+ // 优雅停机期间在途响应仍写回 (close 先排空队列再断连);
155
+ // socket 已亡时 write 错误走 socket error 处理。
156
+ if (response.kind === "error" && response.code === "protocol_version") {
157
+ writeResponse(response, true);
158
+ return;
159
+ }
160
+ writeResponse(response, false);
161
+ }, () => undefined)).finally(() => {
162
+ if (socketState !== undefined)
163
+ socketState.pendingRequests -= 1;
164
+ });
165
+ }
166
+ });
167
+ const drop = () => {
168
+ socket.destroy();
169
+ };
170
+ socket.on("error", (error) => {
171
+ options.onError?.(error, "inference-server:socket");
172
+ drop();
173
+ });
174
+ socket.on("close", () => {
175
+ sockets.delete(socket);
176
+ socketStates.delete(socket);
177
+ maybeScheduleIdleExit();
178
+ });
179
+ });
180
+ /**
181
+ * 活跃连接数: 有在途请求, 或最近帧距现在未超过活跃度阈值的连接。
182
+ * connectionStaleMs <= 0 时退化为全部连接 (原语义)。
183
+ */
184
+ const activeConnectionCount = () => {
185
+ if (connectionStaleMs <= 0)
186
+ return sockets.size;
187
+ const nowMs = Date.now();
188
+ let active = 0;
189
+ for (const socket of sockets) {
190
+ const state = socketStates.get(socket);
191
+ if (state === undefined)
192
+ continue;
193
+ if (state.pendingRequests > 0 || nowMs - state.lastFrameAt < connectionStaleMs)
194
+ active += 1;
195
+ }
196
+ return active;
197
+ };
198
+ const handleRequest = async (request) => {
199
+ try {
200
+ switch (request.kind) {
201
+ case "hello": {
202
+ if (request.protocolVersion !== MEMORY_INFERENCE_PROTOCOL_VERSION) {
203
+ return {
204
+ kind: "error",
205
+ requestId: request.requestId,
206
+ code: "protocol_version",
207
+ message: `inference protocol version mismatch: server=${MEMORY_INFERENCE_PROTOCOL_VERSION} client=${request.protocolVersion}`,
208
+ };
209
+ }
210
+ // agentDir 身份上报 (2026-10-06 串线修复): 有则带 (生产经
211
+ // runMemoryInferenceService 传入), 无则不带 — 直连本工厂的旧
212
+ // 测试缝与升级过渡期的旧版本服务进程保持 wire 兼容, 会话装配
213
+ // 按字段缺失拒绝 (设计 §3.2)。
214
+ const helloBase = {
215
+ protocolVersion: MEMORY_INFERENCE_PROTOCOL_VERSION,
216
+ embeddingModelId: options.backend.embedProvider.modelId,
217
+ dimensions: options.backend.embedProvider.dimensions,
218
+ rerankerModelId: options.backend.reranker?.modelId ?? null,
219
+ };
220
+ return {
221
+ kind: "ok",
222
+ requestId: request.requestId,
223
+ result: options.agentDir === undefined
224
+ ? helloBase
225
+ : { ...helloBase, agentDir: options.agentDir },
226
+ };
227
+ }
228
+ case "embed": {
229
+ const vectors = await options.backend.embedProvider.embed(request.texts);
230
+ return { kind: "ok", requestId: request.requestId, result: { vectors } };
231
+ }
232
+ case "rerank": {
233
+ if (options.backend.reranker === undefined) {
234
+ return {
235
+ kind: "error",
236
+ requestId: request.requestId,
237
+ code: "backend_error",
238
+ message: "inference server has no reranker",
239
+ };
240
+ }
241
+ const scores = await options.backend.reranker.rerank(request.query, request.docs);
242
+ return { kind: "ok", requestId: request.requestId, result: { scores } };
243
+ }
244
+ case "queryKnn": {
245
+ const hits = await options.backend.vectorIndex.queryKnn(request.vector, request.query);
246
+ return { kind: "ok", requestId: request.requestId, result: { hits } };
247
+ }
248
+ case "queryFts": {
249
+ const hits = await options.backend.vectorIndex.queryFts(request.queryText, request.query);
250
+ return { kind: "ok", requestId: request.requestId, result: { hits } };
251
+ }
252
+ case "upsert": {
253
+ await options.backend.vectorIndex.upsert(request.entries);
254
+ return { kind: "ok", requestId: request.requestId, result: { pong: true } };
255
+ }
256
+ case "remove": {
257
+ await options.backend.vectorIndex.remove(request.memoryIds);
258
+ return { kind: "ok", requestId: request.requestId, result: { pong: true } };
259
+ }
260
+ case "listMemoryIds": {
261
+ const ids = await options.backend.vectorIndex.listMemoryIds();
262
+ return { kind: "ok", requestId: request.requestId, result: { memoryIds: [...ids] } };
263
+ }
264
+ case "count": {
265
+ const count = await options.backend.vectorIndex.count();
266
+ return { kind: "ok", requestId: request.requestId, result: { count } };
267
+ }
268
+ case "getStoredModelId": {
269
+ const modelId = await options.backend.vectorIndex.getStoredModelId();
270
+ return { kind: "ok", requestId: request.requestId, result: { modelId: modelId ?? null } };
271
+ }
272
+ case "ping":
273
+ return { kind: "ok", requestId: request.requestId, result: { pong: true } };
274
+ default:
275
+ return {
276
+ kind: "error",
277
+ requestId: request.requestId,
278
+ code: "bad_request",
279
+ message: `unknown inference request kind: ${request.kind}`,
280
+ };
281
+ }
282
+ }
283
+ catch (error) {
284
+ return {
285
+ kind: "error",
286
+ requestId: request.requestId,
287
+ code: "backend_error",
288
+ message: error instanceof Error ? error.message : String(error),
289
+ };
290
+ }
291
+ };
292
+ const maybeScheduleIdleExit = () => {
293
+ if (closed || idleTimeoutMs <= 0 || activeConnectionCount() > 0 || idleTimer !== undefined)
294
+ return;
295
+ idleTimer = setTimeout(() => {
296
+ idleTimer = undefined;
297
+ if (activeConnectionCount() > 0 || closed)
298
+ return;
299
+ void finishClose("idle", true);
300
+ }, idleTimeoutMs);
301
+ idleTimer.unref?.();
302
+ };
303
+ // 陈旧连接清扫 (2026-10-07): 活跃→陈旧的转变没有事件 (挂起客户端不发帧也
304
+ // 不断连), 靠周期扫描重估空闲退出。周期取 min(30s, 阈值/2) — 生产 30s,
305
+ // 测试注入短阈值即得短周期。仅在空闲退出与活跃度阈值都启用时运转。
306
+ let staleSweepTimer;
307
+ const startStaleSweep = () => {
308
+ if (closed || idleTimeoutMs <= 0 || connectionStaleMs <= 0 || staleSweepTimer !== undefined) {
309
+ return;
310
+ }
311
+ const sweepIntervalMs = Math.min(CONNECTION_STALE_SWEEP_MAX_MS, Math.max(1, Math.floor(connectionStaleMs / 2)));
312
+ staleSweepTimer = setInterval(() => {
313
+ if (!closed)
314
+ maybeScheduleIdleExit();
315
+ }, sweepIntervalMs);
316
+ staleSweepTimer.unref?.();
317
+ };
318
+ let finishPromise;
319
+ const finishClose = (reason, drain) => {
320
+ // 并发调用统一返回同一次停机 (复审 P3-5): 后到的 close/destroy/空闲触发
321
+ // 不产生第二套停机语义, 先到者 (优雅或强制) 定夺。
322
+ if (finishPromise !== undefined)
323
+ return finishPromise;
324
+ finishPromise = (async () => {
325
+ closed = true;
326
+ if (idleTimer !== undefined) {
327
+ clearTimeout(idleTimer);
328
+ idleTimer = undefined;
329
+ }
330
+ if (staleSweepTimer !== undefined) {
331
+ clearInterval(staleSweepTimer);
332
+ staleSweepTimer = undefined;
333
+ }
334
+ // 停止接受新连接; 优雅模式先排空串行队列 (在途响应写回), 再断开全部
335
+ // 连接 (客户端 pending 结构化失败 — 设计 §4); 强制模式立即断连。
336
+ const stopped = new Promise((resolve) => server.close(() => resolve()));
337
+ if (drain)
338
+ await queueTail.catch(() => undefined);
339
+ for (const socket of sockets) {
340
+ // 优雅路径用 end() (FIN): 内核把在途回程响应刷完再关, 客户端 pending
341
+ // 先收到结果; 强制路径 destroy() 立即丢弃 (客户端 pending 结构化失败)。
342
+ if (drain)
343
+ socket.end();
344
+ else
345
+ socket.destroy();
346
+ }
347
+ sockets.clear();
348
+ socketStates.clear();
349
+ await stopped;
350
+ if (drain) {
351
+ try {
352
+ await options.backend.vectorIndex.close();
353
+ }
354
+ catch (error) {
355
+ options.onError?.(error instanceof Error ? error : new Error(String(error)), `inference-server:close:${reason}`);
356
+ }
357
+ }
358
+ closeResolve?.();
359
+ })();
360
+ return finishPromise;
361
+ };
362
+ options.signal?.addEventListener("abort", () => {
363
+ void finishClose("signal", true);
364
+ }, { once: true });
365
+ return new Promise((resolve, reject) => {
366
+ const onListenError = (error) => {
367
+ server.close();
368
+ reject(error);
369
+ };
370
+ server.once("error", onListenError);
371
+ server.listen(options.port ?? MEMORY_INFERENCE_DEFAULT_PORT, host, () => {
372
+ server.off("error", onListenError);
373
+ const address = server.address();
374
+ if (address === null || typeof address === "string") {
375
+ reject(new Error("inference server failed to bind a TCP port"));
376
+ return;
377
+ }
378
+ server.on("error", (error) => {
379
+ options.onError?.(error, "inference-server:server");
380
+ });
381
+ const api = {
382
+ port: address.port,
383
+ host,
384
+ close: () => finishClose("explicit", true),
385
+ destroy: () => finishClose("destroy", false),
386
+ closed: closedPromise,
387
+ };
388
+ resolve(api);
389
+ // 已在无连接状态启动 (例如 signal 先行 abort) 时立刻评估空闲退出。
390
+ maybeScheduleIdleExit();
391
+ startStaleSweep();
392
+ });
393
+ });
394
+ }
395
+ //# sourceMappingURL=inference-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inference-server.js","sourceRoot":"","sources":["../../src/memory/inference-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 记忆共享推理服务 — 服务端 (记忆共享推理服务设计 §3.1/§3.4/§3.5)。\n *\n * 常驻 sidecar 的服务端面: 持有唯一的推理后端 (embedProvider/reranker/\n * vectorIndex — 生产装配为真实 transformers + LanceDB, 测试注入 deterministic\n * mock), 经 TCP loopback 服务 `inference-protocol.ts` 的消息面。\n *\n * 串行化 (设计 §3.4): 所有请求经单条 promise 链按到达序执行 — LanceDB 表\n * 创建与 ONNX 管线未声明并发安全, 单用户量级下串行的吞吐足够, 正确性优先\n * (同一理由: 每进程一个 vectorQueue 的既有先例)。\n *\n * 逐请求看门狗 (2026-10-06, 设计 §3.4): 后端派发超过 requestWatchdogMs\n * (缺省 MEMORY_INFERENCE_REQUEST_WATCHDOG_MS) 即对该请求返回结构化\n * backend_error, 串行队列恢复推进 — 后端永不落定时 (如 ONNX 挂起) 其余请求\n * 不再排队等死。诚实约束: JS 侧超时不中断底层原生调用 (ONNX 线程仍占住),\n * 语义是\"队列恢复推进、后续请求可用\", 不是后端取消。\n *\n * 生命周期 (设计 §3.5): 空闲自杀 — 无活跃连接且无在途请求持续 idleTimeoutMs 后\n * 优雅关闭 (端口让出, 下一个使用者重新拉起); 新连接取消计时。活跃 = 有在途请求,\n * 或最近收到帧距今未超过连接活跃度阈值 (connectionStaleMs): 挂起不退的客户端\n * (逻辑死、socket 活) 不再把空闲退出永久按住, 由周期清扫重估。close() 供\n * 进程入口与测试显式停机。\n */\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport { FrameDecoder } from \"@agent-forge/protocol\";\nimport type { MemoryEmbeddingProviderV1 } from \"./embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"./embedding-reranker.ts\";\nimport {\n\tdecodeInferenceMessage,\n\tencodeInferenceMessage,\n\tMEMORY_INFERENCE_DEFAULT_HOST,\n\tMEMORY_INFERENCE_DEFAULT_PORT,\n\tMEMORY_INFERENCE_PROTOCOL_VERSION,\n\ttype MemoryInferenceHelloResultV1,\n\ttype MemoryInferenceRequestV1,\n\ttype MemoryInferenceResponseV1,\n} from \"./inference-protocol.ts\";\nimport type { MemoryVectorIndexV1 } from \"./vector-index.ts\";\n\n/**\n * 逐请求看门狗阈值出厂默认 (2026-10-06): 大于客户端请求超时 (装配缝 60s /\n * 客户端缺省 10s) — 正常慢操作先被客户端放弃, 服务端不打断。诚实约束: JS 侧\n * 超时不中断底层原生调用 (ONNX 线程仍占住资源), 语义是\"串行队列恢复推进、\n * 后续请求可用\", 不是后端取消。\n */\nexport const MEMORY_INFERENCE_REQUEST_WATCHDOG_MS = 120_000;\n\n/**\n * 连接活跃度阈值出厂默认 (2026-10-07, codex remote-control 同款先例): 自最近\n * 收到帧起超过该时长且无在途请求的连接视为陈旧, 不再阻止空闲退出 — 挂起不退\n * 的客户端 (逻辑死、socket 活) 不会把服务模型永久按驻内存。无协议面新增:\n * 活跃度由服务端按帧到达自行观测, 客户端零改动。\n */\nexport const MEMORY_INFERENCE_CONNECTION_STALE_MS = 10 * 60 * 1000;\n\n/** 陈旧连接清扫周期上限; 实际取 min(本值, 阈值/2), 测试注入短阈值即得短周期。 */\nconst CONNECTION_STALE_SWEEP_MAX_MS = 30_000;\n\n/** 服务端持有的唯一推理后端 (生产 = 真实装配; 测试 = deterministic mock)。 */\nexport interface MemoryInferenceBackendV1 {\n\treadonly embedProvider: MemoryEmbeddingProviderV1;\n\treadonly reranker?: MemoryRerankerV1;\n\treadonly vectorIndex: MemoryVectorIndexV1;\n}\n\nexport interface MemoryInferenceServerV1 {\n\t/** 实际绑定端口 (port: 0 时为 OS 分配值 — 测试用; 生产固定端口即单例互斥)。 */\n\treadonly port: number;\n\treadonly host: string;\n\t/**\n\t * 优雅停机: 停止监听 → 排空串行队列 (在途响应写回) → 断开全部连接 → 关闭\n\t * 索引句柄。幂等。\n\t */\n\tclose(): Promise<void>;\n\t/**\n\t * 强制停机 (模拟进程死亡/紧急退出): 立即断开全部连接与在途请求 (客户端\n\t * pending 全部结构化失败 — 设计 §4), 不等待队列排空。\n\t */\n\tdestroy(): Promise<void>;\n\t/** 服务完全关闭 (显式或空闲自杀) 后 resolve。 */\n\treadonly closed: Promise<void>;\n}\n\nexport interface MemoryInferenceServerOptions {\n\treadonly backend: MemoryInferenceBackendV1;\n\t/** 缺省 MEMORY_INFERENCE_DEFAULT_PORT; 0 = OS 分配 (测试)。同端口二次绑定 reject (单例互斥)。 */\n\treadonly port?: number;\n\treadonly host?: string;\n\t/** 空闲自杀阈值 (无活跃连接且无在途请求)。缺省 0 = 不启用。出厂默认值登记产品默认值清单。 */\n\treadonly idleTimeoutMs?: number;\n\t/**\n\t * 连接活跃度阈值 (2026-10-07): 见 MEMORY_INFERENCE_CONNECTION_STALE_MS。\n\t * 缺省 10 分钟; 0 = 关闭 (任何连接都阻止空闲退出 — 原语义)。\n\t */\n\treadonly connectionStaleMs?: number;\n\t/**\n\t * 服务进程的 agent-dir 身份 (2026-10-06 串线修复): hello 握手回报给客户端,\n\t * 会话装配据此拒绝跨 agent-dir 串线。缺省不上报 (旧版本 wire 兼容, 装配校验\n\t * 按缺失拒绝); 生产服务经 runMemoryInferenceService 传入。\n\t */\n\treadonly agentDir?: string;\n\t/**\n\t * 逐请求看门狗阈值: 后端派发超过该时长即对该请求返回结构化 backend_error,\n\t * 串行队列继续推进 (不中断底层原生调用 — 见\n\t * MEMORY_INFERENCE_REQUEST_WATCHDOG_MS 注释)。缺省 120s; 测试注入小值。\n\t */\n\treadonly requestWatchdogMs?: number;\n\treadonly signal?: AbortSignal;\n\t/** 结构化错误面 (日志/诊断); 不抛出 — 服务端错误一律转为 error 响应或告警。 */\n\treadonly onError?: (error: Error, context: string) => void;\n}\n\nexport function createMemoryInferenceServer(options: MemoryInferenceServerOptions): Promise<MemoryInferenceServerV1> {\n\tconst host = options.host ?? MEMORY_INFERENCE_DEFAULT_HOST;\n\tconst idleTimeoutMs = options.idleTimeoutMs ?? 0;\n\tconst connectionStaleMs = options.connectionStaleMs ?? MEMORY_INFERENCE_CONNECTION_STALE_MS;\n\tconst requestWatchdogMs = options.requestWatchdogMs ?? MEMORY_INFERENCE_REQUEST_WATCHDOG_MS;\n\tlet idleTimer: NodeJS.Timeout | undefined;\n\tconst sockets = new Set<Socket>();\n\t// 连接活跃度 (2026-10-07): 每连接最近帧到达时刻 + 在途请求数。活跃 = 有在途\n\t// 请求 (帧可能早于陈旧阈值 — 串行队列排队/后端慢不算挂起), 或最近帧未超阈。\n\tconst socketStates = new Map<Socket, { lastFrameAt: number; pendingRequests: number }>();\n\tlet closed = false;\n\tlet closeResolve: (() => void) | undefined;\n\tconst closedPromise = new Promise<void>((resolve) => {\n\t\tcloseResolve = resolve;\n\t});\n\t// 全局串行队列: 所有连接的全部请求按到达序执行 (设计 §3.4)。\n\tlet queueTail: Promise<void> = Promise.resolve();\n\tconst enqueue = (operation: () => Promise<void>): Promise<void> => {\n\t\tconst next = queueTail.then(operation, operation);\n\t\tqueueTail = next.catch(() => undefined);\n\t\treturn next;\n\t};\n\t// 逐请求看门狗 (2026-10-06): 包住单次后端派发, 超时即返回结构化 backend_error,\n\t// 让串行队列的当前位置落定 — 后端 promise 永不落定时 (如 ONNX 挂起) 后续请求\n\t// 不再排队等死。诚实约束: 超时只是不再等待, 底层原生调用无法被中断 (ONNX\n\t// 线程仍占住资源), 语义是\"队列恢复推进、后续请求可用\", 不是后端取消; 输掉\n\t// 竞速的 handleRequest 在后台落定, 其迟到结果被丢弃 (连接可能已亡, write\n\t// 错误走 socket error 处理)。\n\tconst withRequestWatchdog = (\n\t\trequest: MemoryInferenceRequestV1,\n\t\toperation: Promise<MemoryInferenceResponseV1>,\n\t): Promise<MemoryInferenceResponseV1> => {\n\t\tlet watchdog: NodeJS.Timeout | undefined;\n\t\tconst expired = new Promise<MemoryInferenceResponseV1>((resolve) => {\n\t\t\twatchdog = setTimeout(() => {\n\t\t\t\tresolve({\n\t\t\t\t\tkind: \"error\",\n\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\tcode: \"backend_error\",\n\t\t\t\t\tmessage: `memory inference backend timed out after ${requestWatchdogMs}ms (request kind=${request.kind})`,\n\t\t\t\t});\n\t\t\t}, requestWatchdogMs);\n\t\t\twatchdog.unref?.();\n\t\t});\n\t\treturn Promise.race([operation, expired]).finally(() => clearTimeout(watchdog));\n\t};\n\n\tconst server: Server = createServer((socket: Socket) => {\n\t\tsockets.add(socket);\n\t\tsocketStates.set(socket, { lastFrameAt: Date.now(), pendingRequests: 0 });\n\t\tif (idleTimer !== undefined) {\n\t\t\tclearTimeout(idleTimer);\n\t\t\tidleTimer = undefined;\n\t\t}\n\t\tconst decoder = new FrameDecoder();\n\t\t// 响应写回 (2026-10-06): 超过 16MB 帧/CBOR 上限的响应 (如 full 档大包\n\t\t// embed) 无法原样编码, 编码抛错若不接住会被 enqueue 的队列兜底 catch 吞\n\t\t// 掉 — 客户端吃满请求超时且根因不可见。回小体量结构化错误帧让客户端\n\t\t// 立刻拿到根因并收缩批量; 错误帧自身再失败只能断连 (socket 已不可写)。\n\t\tconst writeResponse = (response: MemoryInferenceResponseV1, endAfter: boolean): void => {\n\t\t\tlet payload = response;\n\t\t\tlet bytes: Uint8Array;\n\t\t\ttry {\n\t\t\t\tbytes = encodeInferenceMessage(payload);\n\t\t\t} catch (encodeError) {\n\t\t\t\toptions.onError?.(\n\t\t\t\t\tencodeError instanceof Error ? encodeError : new Error(String(encodeError)),\n\t\t\t\t\t\"inference-server:encode\",\n\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tpayload = {\n\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\trequestId: response.requestId,\n\t\t\t\t\t\tcode: \"backend_error\",\n\t\t\t\t\t\tmessage: `memory inference response exceeds the wire frame limit; shrink the batch (${\n\t\t\t\t\t\t\tencodeError instanceof Error ? encodeError.message : String(encodeError)\n\t\t\t\t\t\t})`,\n\t\t\t\t\t};\n\t\t\t\t\tbytes = encodeInferenceMessage(payload);\n\t\t\t\t} catch {\n\t\t\t\t\tsocket.destroy();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tsocket.write(bytes, endAfter ? () => socket.end() : undefined);\n\t\t};\n\t\tsocket.on(\"data\", (chunk: Buffer) => {\n\t\t\t// 任何字节到达都是活跃度证据 (坏帧连接随后即被断开, 无所谓)。\n\t\t\tconst socketState = socketStates.get(socket);\n\t\t\tif (socketState !== undefined) socketState.lastFrameAt = Date.now();\n\t\t\t// 帧层异常必须捕获 (复审 P2-1): 声明长度超限的帧在 data 监听器抛\n\t\t\t// FrameError 即 uncaughtException — 坏帧/合法超大帧可干死服务进程。\n\t\t\t// 先例: subagent/stdio-channel.ts 同款处理。\n\t\t\tlet frames: Uint8Array[];\n\t\t\ttry {\n\t\t\t\tframes = decoder.push(new Uint8Array(chunk));\n\t\t\t} catch (error) {\n\t\t\t\toptions.onError?.(error instanceof Error ? error : new Error(String(error)), \"inference-server:frame\");\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const frame of frames) {\n\t\t\t\tif (frame.byteLength === 0) continue;\n\t\t\t\tlet request: MemoryInferenceRequestV1;\n\t\t\t\ttry {\n\t\t\t\t\trequest = decodeInferenceMessage(frame) as MemoryInferenceRequestV1;\n\t\t\t\t} catch (error) {\n\t\t\t\t\toptions.onError?.(error instanceof Error ? error : new Error(String(error)), \"inference-server:decode\");\n\t\t\t\t\tsocket.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (socketState !== undefined) socketState.pendingRequests += 1;\n\t\t\t\tvoid enqueue(() =>\n\t\t\t\t\twithRequestWatchdog(request, handleRequest(request)).then(\n\t\t\t\t\t\t(response) => {\n\t\t\t\t\t\t\t// 优雅停机期间在途响应仍写回 (close 先排空队列再断连);\n\t\t\t\t\t\t\t// socket 已亡时 write 错误走 socket error 处理。\n\t\t\t\t\t\t\tif (response.kind === \"error\" && response.code === \"protocol_version\") {\n\t\t\t\t\t\t\t\twriteResponse(response, true);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\twriteResponse(response, false);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t),\n\t\t\t\t).finally(() => {\n\t\t\t\t\tif (socketState !== undefined) socketState.pendingRequests -= 1;\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\tconst drop = () => {\n\t\t\tsocket.destroy();\n\t\t};\n\t\tsocket.on(\"error\", (error) => {\n\t\t\toptions.onError?.(error, \"inference-server:socket\");\n\t\t\tdrop();\n\t\t});\n\t\tsocket.on(\"close\", () => {\n\t\t\tsockets.delete(socket);\n\t\t\tsocketStates.delete(socket);\n\t\t\tmaybeScheduleIdleExit();\n\t\t});\n\t});\n\n\t/**\n\t * 活跃连接数: 有在途请求, 或最近帧距现在未超过活跃度阈值的连接。\n\t * connectionStaleMs <= 0 时退化为全部连接 (原语义)。\n\t */\n\tconst activeConnectionCount = (): number => {\n\t\tif (connectionStaleMs <= 0) return sockets.size;\n\t\tconst nowMs = Date.now();\n\t\tlet active = 0;\n\t\tfor (const socket of sockets) {\n\t\t\tconst state = socketStates.get(socket);\n\t\t\tif (state === undefined) continue;\n\t\t\tif (state.pendingRequests > 0 || nowMs - state.lastFrameAt < connectionStaleMs) active += 1;\n\t\t}\n\t\treturn active;\n\t};\n\n\tconst handleRequest = async (request: MemoryInferenceRequestV1): Promise<MemoryInferenceResponseV1> => {\n\t\ttry {\n\t\t\tswitch (request.kind) {\n\t\t\t\tcase \"hello\": {\n\t\t\t\t\tif (request.protocolVersion !== MEMORY_INFERENCE_PROTOCOL_VERSION) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\t\tcode: \"protocol_version\",\n\t\t\t\t\t\t\tmessage: `inference protocol version mismatch: server=${MEMORY_INFERENCE_PROTOCOL_VERSION} client=${request.protocolVersion}`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// agentDir 身份上报 (2026-10-06 串线修复): 有则带 (生产经\n\t\t\t\t\t// runMemoryInferenceService 传入), 无则不带 — 直连本工厂的旧\n\t\t\t\t\t// 测试缝与升级过渡期的旧版本服务进程保持 wire 兼容, 会话装配\n\t\t\t\t\t// 按字段缺失拒绝 (设计 §3.2)。\n\t\t\t\t\tconst helloBase = {\n\t\t\t\t\t\tprotocolVersion: MEMORY_INFERENCE_PROTOCOL_VERSION,\n\t\t\t\t\t\tembeddingModelId: options.backend.embedProvider.modelId,\n\t\t\t\t\t\tdimensions: options.backend.embedProvider.dimensions,\n\t\t\t\t\t\trerankerModelId: options.backend.reranker?.modelId ?? null,\n\t\t\t\t\t};\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"ok\",\n\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\tresult:\n\t\t\t\t\t\t\toptions.agentDir === undefined\n\t\t\t\t\t\t\t\t? (helloBase as MemoryInferenceHelloResultV1)\n\t\t\t\t\t\t\t\t: { ...helloBase, agentDir: options.agentDir },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcase \"embed\": {\n\t\t\t\t\tconst vectors = await options.backend.embedProvider.embed(request.texts);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { vectors } };\n\t\t\t\t}\n\t\t\t\tcase \"rerank\": {\n\t\t\t\t\tif (options.backend.reranker === undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\t\tcode: \"backend_error\",\n\t\t\t\t\t\t\tmessage: \"inference server has no reranker\",\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst scores = await options.backend.reranker.rerank(request.query, request.docs);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { scores } };\n\t\t\t\t}\n\t\t\t\tcase \"queryKnn\": {\n\t\t\t\t\tconst hits = await options.backend.vectorIndex.queryKnn(request.vector, request.query);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { hits } };\n\t\t\t\t}\n\t\t\t\tcase \"queryFts\": {\n\t\t\t\t\tconst hits = await options.backend.vectorIndex.queryFts(request.queryText, request.query);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { hits } };\n\t\t\t\t}\n\t\t\t\tcase \"upsert\": {\n\t\t\t\t\tawait options.backend.vectorIndex.upsert(request.entries);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { pong: true } };\n\t\t\t\t}\n\t\t\t\tcase \"remove\": {\n\t\t\t\t\tawait options.backend.vectorIndex.remove(request.memoryIds);\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { pong: true } };\n\t\t\t\t}\n\t\t\t\tcase \"listMemoryIds\": {\n\t\t\t\t\tconst ids = await options.backend.vectorIndex.listMemoryIds();\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { memoryIds: [...ids] } };\n\t\t\t\t}\n\t\t\t\tcase \"count\": {\n\t\t\t\t\tconst count = await options.backend.vectorIndex.count();\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { count } };\n\t\t\t\t}\n\t\t\t\tcase \"getStoredModelId\": {\n\t\t\t\t\tconst modelId = await options.backend.vectorIndex.getStoredModelId();\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { modelId: modelId ?? null } };\n\t\t\t\t}\n\t\t\t\tcase \"ping\":\n\t\t\t\t\treturn { kind: \"ok\", requestId: request.requestId, result: { pong: true } };\n\t\t\t\tdefault:\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"error\",\n\t\t\t\t\t\trequestId: (request as { requestId: number }).requestId,\n\t\t\t\t\t\tcode: \"bad_request\",\n\t\t\t\t\t\tmessage: `unknown inference request kind: ${(request as { kind: string }).kind}`,\n\t\t\t\t\t};\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"error\",\n\t\t\t\trequestId: request.requestId,\n\t\t\t\tcode: \"backend_error\",\n\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t};\n\n\tconst maybeScheduleIdleExit = (): void => {\n\t\tif (closed || idleTimeoutMs <= 0 || activeConnectionCount() > 0 || idleTimer !== undefined) return;\n\t\tidleTimer = setTimeout(() => {\n\t\t\tidleTimer = undefined;\n\t\t\tif (activeConnectionCount() > 0 || closed) return;\n\t\t\tvoid finishClose(\"idle\", true);\n\t\t}, idleTimeoutMs);\n\t\tidleTimer.unref?.();\n\t};\n\n\t// 陈旧连接清扫 (2026-10-07): 活跃→陈旧的转变没有事件 (挂起客户端不发帧也\n\t// 不断连), 靠周期扫描重估空闲退出。周期取 min(30s, 阈值/2) — 生产 30s,\n\t// 测试注入短阈值即得短周期。仅在空闲退出与活跃度阈值都启用时运转。\n\tlet staleSweepTimer: NodeJS.Timeout | undefined;\n\tconst startStaleSweep = (): void => {\n\t\tif (closed || idleTimeoutMs <= 0 || connectionStaleMs <= 0 || staleSweepTimer !== undefined) {\n\t\t\treturn;\n\t\t}\n\t\tconst sweepIntervalMs = Math.min(CONNECTION_STALE_SWEEP_MAX_MS, Math.max(1, Math.floor(connectionStaleMs / 2)));\n\t\tstaleSweepTimer = setInterval(() => {\n\t\t\tif (!closed) maybeScheduleIdleExit();\n\t\t}, sweepIntervalMs);\n\t\tstaleSweepTimer.unref?.();\n\t};\n\n\tlet finishPromise: Promise<void> | undefined;\n\tconst finishClose = (reason: string, drain: boolean): Promise<void> => {\n\t\t// 并发调用统一返回同一次停机 (复审 P3-5): 后到的 close/destroy/空闲触发\n\t\t// 不产生第二套停机语义, 先到者 (优雅或强制) 定夺。\n\t\tif (finishPromise !== undefined) return finishPromise;\n\t\tfinishPromise = (async () => {\n\t\t\tclosed = true;\n\t\t\tif (idleTimer !== undefined) {\n\t\t\t\tclearTimeout(idleTimer);\n\t\t\t\tidleTimer = undefined;\n\t\t\t}\n\t\t\tif (staleSweepTimer !== undefined) {\n\t\t\t\tclearInterval(staleSweepTimer);\n\t\t\t\tstaleSweepTimer = undefined;\n\t\t\t}\n\t\t\t// 停止接受新连接; 优雅模式先排空串行队列 (在途响应写回), 再断开全部\n\t\t\t// 连接 (客户端 pending 结构化失败 — 设计 §4); 强制模式立即断连。\n\t\t\tconst stopped = new Promise<void>((resolve) => server.close(() => resolve()));\n\t\t\tif (drain) await queueTail.catch(() => undefined);\n\t\t\tfor (const socket of sockets) {\n\t\t\t\t// 优雅路径用 end() (FIN): 内核把在途回程响应刷完再关, 客户端 pending\n\t\t\t\t// 先收到结果; 强制路径 destroy() 立即丢弃 (客户端 pending 结构化失败)。\n\t\t\t\tif (drain) socket.end();\n\t\t\t\telse socket.destroy();\n\t\t\t}\n\t\t\tsockets.clear();\n\t\t\tsocketStates.clear();\n\t\t\tawait stopped;\n\t\t\tif (drain) {\n\t\t\t\ttry {\n\t\t\t\t\tawait options.backend.vectorIndex.close();\n\t\t\t\t} catch (error) {\n\t\t\t\t\toptions.onError?.(\n\t\t\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\t\t`inference-server:close:${reason}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcloseResolve?.();\n\t\t})();\n\t\treturn finishPromise;\n\t};\n\n\toptions.signal?.addEventListener(\n\t\t\"abort\",\n\t\t() => {\n\t\t\tvoid finishClose(\"signal\", true);\n\t\t},\n\t\t{ once: true },\n\t);\n\n\treturn new Promise<MemoryInferenceServerV1>((resolve, reject) => {\n\t\tconst onListenError = (error: Error) => {\n\t\t\tserver.close();\n\t\t\treject(error);\n\t\t};\n\t\tserver.once(\"error\", onListenError);\n\t\tserver.listen(options.port ?? MEMORY_INFERENCE_DEFAULT_PORT, host, () => {\n\t\t\tserver.off(\"error\", onListenError);\n\t\t\tconst address = server.address();\n\t\t\tif (address === null || typeof address === \"string\") {\n\t\t\t\treject(new Error(\"inference server failed to bind a TCP port\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tserver.on(\"error\", (error) => {\n\t\t\t\toptions.onError?.(error, \"inference-server:server\");\n\t\t\t});\n\t\t\tconst api: MemoryInferenceServerV1 = {\n\t\t\t\tport: address.port,\n\t\t\t\thost,\n\t\t\t\tclose: () => finishClose(\"explicit\", true),\n\t\t\t\tdestroy: () => finishClose(\"destroy\", false),\n\t\t\t\tclosed: closedPromise,\n\t\t\t};\n\t\t\tresolve(api);\n\t\t\t// 已在无连接状态启动 (例如 signal 先行 abort) 时立刻评估空闲退出。\n\t\t\tmaybeScheduleIdleExit();\n\t\t\tstartStaleSweep();\n\t\t});\n\t});\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { TransformersEmbeddingLoadImpl } from "./embedding-provider.ts";
2
+ import type { TransformersRerankerLoadImpl } from "./embedding-reranker.ts";
3
+ import { type MemoryInferenceBackendV1, type MemoryInferenceServerV1 } from "./inference-server.ts";
4
+ /** 服务空闲自杀阈值出厂默认 (设计 §3.5; 登记产品默认值清单)。 */
5
+ export declare const MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS: number;
6
+ export interface MemoryInferenceServiceOptions {
7
+ readonly agentDir: string;
8
+ readonly mode: "light" | "full";
9
+ /** 缺省固定默认端口 (单例互斥); env 覆盖由 createMemoryInferenceServer 端口参数承担。 */
10
+ readonly port?: number;
11
+ /** 缺省 MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS。 */
12
+ readonly idleTimeoutMs?: number;
13
+ readonly signal?: AbortSignal;
14
+ /** 测试缝: 直接给定后端, 跳过真实装配 (模型检查一并跳过)。 */
15
+ readonly backend?: MemoryInferenceBackendV1;
16
+ readonly moduleEntryPaths?: {
17
+ readonly lancedb?: string;
18
+ readonly transformers?: string;
19
+ };
20
+ readonly loadImpls?: {
21
+ readonly embedding?: TransformersEmbeddingLoadImpl;
22
+ readonly reranker?: TransformersRerankerLoadImpl;
23
+ };
24
+ readonly log?: (line: string) => void;
25
+ }
26
+ /**
27
+ * 装配并启动共享推理服务。真实装配路径的失败 (模型缺失) 抛结构化错误 —
28
+ * 进程入口将其转为一行 stderr + 非零退出码; 已监听后返回 server 句柄。
29
+ */
30
+ export declare function runMemoryInferenceService(options: MemoryInferenceServiceOptions): Promise<MemoryInferenceServerV1>;
31
+ //# sourceMappingURL=inference-service-main.d.ts.map