@jjh32456/dsh-longlive 1.6.7 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/CHANGELOG.md +70 -1508
  2. package/LICENSE +1 -1
  3. package/QUICKSTART.md +34 -13
  4. package/README.en.md +18 -7
  5. package/README.md +34 -9
  6. package/SPEC.md +5 -5
  7. package/cordis.patch.yml +23 -9
  8. package/docs/ANSWER-GUARD.md +92 -0
  9. package/docs/ARCHITECTURE.md +4 -3
  10. package/docs/AUTO-RECALL-WIRING.md +1 -1
  11. package/docs/AUTO-RECALL.md +15 -9
  12. package/docs/CONFIG.md +168 -35
  13. package/docs/ERRORS.md +17 -5
  14. package/docs/FAQ.md +7 -7
  15. package/docs/FEATURES.md +96 -28
  16. package/docs/MIGRATION-1.6.0.md +2 -2
  17. package/docs/OPERATIONS.md +6 -6
  18. package/docs/PANEL-INTEGRATION.md +6 -0
  19. package/docs/PRIVACY.md +88 -0
  20. package/docs/QUICKSTART.md +5 -5
  21. package/docs/README.en.md +4 -2
  22. package/docs/RESIDENT-WATCHER.md +1 -1
  23. package/docs/SESSION-WATCH.md +6 -6
  24. package/docs/TROUBLESHOOTING.md +4 -4
  25. package/package.json +47 -117
  26. package/runbook.md +2 -2
  27. package/scripts/backfill-auto-cards.mjs +98 -0
  28. package/scripts/install-surgery-task.ps1 +5 -2
  29. package/scripts/lib/session-watch-fixture.mjs +1 -1
  30. package/scripts/longlive-doctor.mjs +6 -0
  31. package/scripts/migrate-180.mjs +163 -0
  32. package/scripts/recall-index-build.mjs +128 -0
  33. package/scripts/session-watch-drill.mjs +1 -1
  34. package/scripts/surgery-run.cmd +1 -1
  35. package/scripts/unattended-surgery.ps1 +5 -2
  36. package/scripts/verify-card-anchors.mjs +138 -0
  37. package/src/actions/audit.js +125 -0
  38. package/src/actions/evidence.js +122 -0
  39. package/src/actions/fs.js +162 -0
  40. package/src/activity/index.js +760 -0
  41. package/src/activity/model.js +351 -0
  42. package/src/activity/store.js +644 -0
  43. package/src/activity/thinking.js +176 -0
  44. package/src/adapters/constants.js +11 -0
  45. package/src/adapters/engram/data/aliases.json +51 -0
  46. package/src/adapters/engram/data/policy.json +8 -0
  47. package/src/adapters/engram/data/routing-rules.json +546 -0
  48. package/src/adapters/engram/index.js +235 -0
  49. package/src/adapters/registry.js +315 -0
  50. package/src/answer/audit.js +193 -0
  51. package/src/answer/evidence.js +30 -0
  52. package/src/answer/guard.js +1370 -0
  53. package/src/answer/index.js +1067 -0
  54. package/src/answer/query.js +111 -0
  55. package/src/answer/second-hop.mjs +101 -0
  56. package/src/auto-truncate.js +1 -1
  57. package/src/config.js +171 -47
  58. package/src/deps.js +6 -4
  59. package/src/features.js +182 -6
  60. package/src/heartbeat.js +156 -23
  61. package/src/identity/handover.js +52 -1
  62. package/src/identity/index.js +13 -5
  63. package/src/identity/inject-position.js +2 -2
  64. package/src/index.js +97 -7
  65. package/src/lifecycle/gap-clocks.js +328 -0
  66. package/src/lifecycle/index.js +269 -0
  67. package/src/live.js +22 -2
  68. package/src/memory/activity-capture.js +229 -0
  69. package/src/memory/anchors.js +194 -0
  70. package/src/memory/audit-records.js +222 -0
  71. package/src/memory/auto-cards.js +401 -0
  72. package/src/memory/capture.js +442 -0
  73. package/src/memory/card-anchors.js +203 -0
  74. package/src/memory/conflicts.js +106 -0
  75. package/src/memory/decay.js +311 -0
  76. package/src/memory/distill.js +471 -0
  77. package/src/memory/event-index.js +917 -0
  78. package/src/memory/evidence-chain.js +295 -0
  79. package/src/memory/evidence.js +145 -0
  80. package/src/memory/filter-runner.js +1 -1
  81. package/src/memory/filter.js +5 -1
  82. package/src/memory/forget-v2.js +410 -0
  83. package/src/memory/forget.js +71 -0
  84. package/src/memory/frames-activation.js +247 -0
  85. package/src/memory/fts-query.js +174 -0
  86. package/src/memory/governance.js +604 -0
  87. package/src/memory/index.js +1 -0
  88. package/src/memory/l2-gate.js +169 -0
  89. package/src/memory/lock.js +121 -0
  90. package/src/memory/migrate.js +139 -0
  91. package/src/memory/recall-v2.js +223 -0
  92. package/src/memory/recall.js +65 -2
  93. package/src/memory/records.js +425 -0
  94. package/src/memory/retention.js +128 -0
  95. package/src/memory/review.js +124 -0
  96. package/src/memory/trust.js +88 -0
  97. package/src/memory/version-chain.js +264 -0
  98. package/src/memory/wal.js +137 -0
  99. package/src/memory/working-frame.js +516 -0
  100. package/src/metrics/classify.mjs +5 -3
  101. package/src/metrics/index.mjs +21 -1
  102. package/src/metrics/prestep-latency.mjs +300 -0
  103. package/src/metrics/store.mjs +25 -0
  104. package/src/policy-profile.js +18 -3
  105. package/src/recall/anaphora.mjs +87 -0
  106. package/src/recall/cards.mjs +7 -0
  107. package/src/recall/embedding.mjs +8 -8
  108. package/src/recall/entities.mjs +112 -0
  109. package/src/recall/index.js +235 -19
  110. package/src/recall/inject-order.mjs +10 -8
  111. package/src/recall/privacy.mjs +105 -16
  112. package/src/recall/recall-core.mjs +116 -10
  113. package/src/recall/shared-visibility.js +302 -0
  114. package/src/recall/time-normalizer.mjs +196 -0
  115. package/src/role/activation.js +191 -0
  116. package/src/role/attention.js +318 -0
  117. package/src/role/base.js +36 -0
  118. package/src/role/channels.js +423 -0
  119. package/src/role/index.js +425 -0
  120. package/src/role/interfaces.js +115 -0
  121. package/src/role/personality.js +370 -0
  122. package/src/role/readers.js +151 -0
  123. package/src/role/relationships.js +477 -0
  124. package/src/role/store.js +202 -0
  125. package/src/role/writeprotect.js +373 -0
  126. package/src/router/aliases.json +176 -25
  127. package/src/router/background/agent-runner.mjs +1 -1
  128. package/src/router/background/anchor.mjs +1 -1
  129. package/src/router/background/audit.mjs +2 -2
  130. package/src/router/background/budget-ledger.mjs +2 -2
  131. package/src/router/background/host-sim.mjs +1 -1
  132. package/src/router/background/memory.mjs +4 -4
  133. package/src/router/background/queue.mjs +1 -1
  134. package/src/router/background/retry.mjs +1 -1
  135. package/src/router/background/router.mjs +1 -1
  136. package/src/router/background/stream.mjs +1 -1
  137. package/src/router/background/tool-policy.mjs +1 -1
  138. package/src/router/design-headings.json +5 -5
  139. package/src/router/face/config.mjs +1 -1
  140. package/src/router/face/identity.mjs +3 -3
  141. package/src/router/judge.mjs +8 -7
  142. package/src/router/policies/generic.json +21 -4
  143. package/src/router/route.js +109 -22
  144. package/src/router/router.mjs +10 -6
  145. package/src/router/routing-rules.json +260 -445
  146. package/src/router/run-sim.mjs +8 -8
  147. package/src/router/runtime.js +23 -12
  148. package/src/router/service/board.mjs +95 -15
  149. package/src/router/service/config.mjs +5 -5
  150. package/src/router/service/context.mjs +1 -1
  151. package/src/router/service/hint.mjs +14 -0
  152. package/src/router/service/queue.mjs +1 -1
  153. package/src/router/service/service.mjs +27 -11
  154. package/src/router/shadow.mjs +4 -3
  155. package/src/surgery-recovery.js +2 -2
  156. package/src/tool-output.js +2 -2
  157. package/src/tools.js +39 -7
  158. package/src/truncate-visible.js +2 -2
  159. package/src/ui/actions.js +1312 -0
  160. package/src/ui/panel-api.js +367 -0
  161. package/src/ui/panel-auth.js +251 -0
  162. package/src/ui/panel-integrity.js +243 -0
  163. package/src/ui/routes-readonly.js +695 -0
  164. package/src/ui/routes.js +3 -0
  165. package/docs/DEV.md +0 -279
  166. package/docs/PRESENCE-BUS-WIRING.md +0 -491
  167. package/docs/PRESENCE-BUS.md +0 -694
  168. package/docs/SURGERY.md +0 -186
  169. package/scripts/mark-core-cards.mjs +0 -289
  170. package/scripts/pack.mjs +0 -392
  171. package/scripts/router-proposals.mjs +0 -386
  172. package/src/router/fixtures/M1.json +0 -16
  173. package/src/router/fixtures/M2.json +0 -16
  174. package/src/router/fixtures/M3.json +0 -20
  175. package/src/router/fixtures/M4.json +0 -20
  176. package/src/router/fixtures/M5.json +0 -45
  177. package/src/router/fixtures/M6.json +0 -53
  178. package/src/router/fixtures/M7.json +0 -46
  179. package/src/router/fixtures/S1.json +0 -20
  180. package/src/router/fixtures/S2.json +0 -23
  181. package/src/router/fixtures/S3.json +0 -20
  182. package/src/router/fixtures/S4.json +0 -34
  183. package/src/router/fixtures/S5.json +0 -43
  184. package/src/router/fixtures/S6.json +0 -47
  185. package/src/router/fixtures/T1.json +0 -23
  186. package/src/router/fixtures/T2.json +0 -16
  187. package/src/router/fixtures/T3.json +0 -19
  188. package/src/router/fixtures/T4.json +0 -16
  189. package/src/router/fixtures/X1.json +0 -17
  190. package/src/router/fixtures/X2.json +0 -20
  191. package/src/router/gen-design-headings.mjs +0 -107
  192. package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
  193. package/test/auto-recall.test.mjs +0 -474
  194. package/test/auto-truncate.test.mjs +0 -931
  195. package/test/autobind.test.mjs +0 -385
  196. package/test/bus.test.mjs +0 -1572
  197. package/test/cli-guard.test.mjs +0 -198
  198. package/test/conclusion.test.mjs +0 -408
  199. package/test/config-schema.test.mjs +0 -204
  200. package/test/d6-output.test.mjs +0 -194
  201. package/test/d6.test.mjs +0 -395
  202. package/test/deps.test.mjs +0 -342
  203. package/test/doctor-setup.test.mjs +0 -297
  204. package/test/embedding-recall.test.mjs +0 -347
  205. package/test/feature-flags.test.mjs +0 -568
  206. package/test/filter-shadow.test.mjs +0 -170
  207. package/test/fixtures/baseline-0.8.0-registration.json +0 -556
  208. package/test/fixtures/d6-header-tools.json +0 -1
  209. package/test/fixtures/lock-child.mjs +0 -67
  210. package/test/fixtures/pre-step-container.mjs +0 -100
  211. package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
  212. package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
  213. package/test/fixtures/profile-session-longlive-config.json +0 -20
  214. package/test/fixtures/proposal-cas-child.mjs +0 -66
  215. package/test/host-engine-status.test.mjs +0 -307
  216. package/test/hot-truncate.test.mjs +0 -1349
  217. package/test/identity-card-marker.test.mjs +0 -307
  218. package/test/identity-router/run.mjs +0 -721
  219. package/test/integration-pre-step.test.mjs +0 -553
  220. package/test/live.test.mjs +0 -1020
  221. package/test/maintenance-schedule.test.mjs +0 -538
  222. package/test/memory.test.mjs +0 -2828
  223. package/test/metrics.test.mjs +0 -478
  224. package/test/orchestrator.test.mjs +0 -266
  225. package/test/p1_probe.mjs +0 -97
  226. package/test/packaging.test.mjs +0 -182
  227. package/test/portability.test.mjs +0 -343
  228. package/test/rc4-regressions.test.mjs +0 -562
  229. package/test/rc5-regressions.test.mjs +0 -197
  230. package/test/rc6-chinese.test.mjs +0 -394
  231. package/test/router-1100/apply-off.test.mjs +0 -175
  232. package/test/router-1100/budget-reconcile.test.mjs +0 -215
  233. package/test/router-1100/config-context.test.mjs +0 -211
  234. package/test/router-1100/helpers.mjs +0 -129
  235. package/test/router-1100/hint-envelope.test.mjs +0 -150
  236. package/test/router-1100/lock-crossproc.test.mjs +0 -206
  237. package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
  238. package/test/router-1100/router-apply.test.mjs +0 -372
  239. package/test/router-1100/router-fixb.test.mjs +0 -602
  240. package/test/router-1100/router-proposals.test.mjs +0 -405
  241. package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
  242. package/test/router-1100/router-security-cr.test.mjs +0 -333
  243. package/test/router-1100/service-lifecycle.test.mjs +0 -220
  244. package/test/router-1100/tools-contract.test.mjs +0 -164
  245. package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
  246. package/test/router-1100/v6-contract.test.mjs +0 -158
  247. package/test/router-1100/v6b-contract.test.mjs +0 -293
  248. package/test/router-1100/v6c-contract.test.mjs +0 -249
  249. package/test/router-1100/wal-rotation.test.mjs +0 -375
  250. package/test/router-1100/wp1-config.test.mjs +0 -170
  251. package/test/router-1100/wp1-default-off.test.mjs +0 -77
  252. package/test/router-1100/wp1-model-status.test.mjs +0 -90
  253. package/test/router-1100/wp1-status.test.mjs +0 -220
  254. package/test/router-1100/wp1-tools.test.mjs +0 -146
  255. package/test/router-1100/wp5-cli.test.mjs +0 -240
  256. package/test/router-1100/wp5-model-auto.test.mjs +0 -418
  257. package/test/router-1100/wp6-merge.test.mjs +0 -386
  258. package/test/router-1100/wp6-wake.test.mjs +0 -502
  259. package/test/router-background/audit.test.mjs +0 -37
  260. package/test/router-background/budget-ledger.test.mjs +0 -330
  261. package/test/router-background/cross-process.test.mjs +0 -222
  262. package/test/router-background/fixtures/cross-child.mjs +0 -85
  263. package/test/router-background/fixtures/real-shapes.mjs +0 -239
  264. package/test/router-background/router.test.mjs +0 -1044
  265. package/test/router-background/stream-retry.test.mjs +0 -358
  266. package/test/router-background/tool-agent.test.mjs +0 -384
  267. package/test/router-face/bridge.test.mjs +0 -169
  268. package/test/router-face/face.test.mjs +0 -316
  269. package/test/router-failclosed.test.mjs +0 -466
  270. package/test/router-identity.test.mjs +0 -384
  271. package/test/router-l1-l2.contract.test.mjs +0 -1075
  272. package/test/router-memory-tools.test.mjs +0 -262
  273. package/test/router-policy.test.mjs +0 -225
  274. package/test/router-runtime.test.mjs +0 -438
  275. package/test/router-shadow.test.mjs +0 -287
  276. package/test/router.test.mjs +0 -903
  277. package/test/run-rc.mjs +0 -299
  278. package/test/run.mjs +0 -2469
  279. package/test/safety-defaults.test.mjs +0 -179
  280. package/test/seeded-load.test.mjs +0 -436
  281. package/test/session-original.test.mjs +0 -338
  282. package/test/surgery-recovery.test.mjs +0 -936
  283. package/test/t7_drive.mjs +0 -151
  284. package/test/tool-output.test.mjs +0 -393
  285. package/test/tool-schema.test.mjs +0 -177
  286. package/test/tools_contract.test.mjs +0 -716
  287. package/test/truncate-visible.test.mjs +0 -1150
  288. package/test/ui-flags.test.mjs +0 -477
  289. package/test/v4-hardening.test.mjs +0 -427
@@ -0,0 +1,111 @@
1
+ // answer/tool.js — `answer_audit`:只读查询生成后审计。
2
+ //
3
+ // 只在 answerGuard.enabled 时注册(tools/list 默认仍是 22 个基线工具),
4
+ // 与 routerTools / d6Tools 一样由 registerTools 追加,绝不改变默认工具面。
5
+
6
+ import { auditFilePath, auditMetrics, readAuditRows } from './audit.js';
7
+
8
+ export const ANSWER_AUDIT_TOOL_NAME = 'answer_audit';
9
+
10
+ /**
11
+ * @param {string|null} memoryRoot
12
+ * @returns {object} tool definition consumed by src/tools.js registerTools
13
+ */
14
+ export function createAnswerTools(memoryRoot) {
15
+ const root = typeof memoryRoot === 'string' && memoryRoot.trim() !== '' ? memoryRoot.trim() : null;
16
+ return [{
17
+ name: ANSWER_AUDIT_TOOL_NAME,
18
+ description:
19
+ 'Query the post-generation answer audit written by the 1.6.5 answer guard ' +
20
+ '(`<memoryRoot>/.longlive/answer-audit.jsonl`). One row per audited turn: whether a ' +
21
+ 'history question was answered without a turn citation, whether the assistant claimed a ' +
22
+ 'tool it never called, numbers/dates absent from context and tool results, and nonexistent ' +
23
+ 'turn/card references. READ-ONLY: it never edits the assistant text or the session log. ' +
24
+ 'Filters: sessionId, turn, verdict (clean|flagged), code (HISTORY_CLAIM_NO_CITATION, ' +
25
+ 'ANSWERED_WITHOUT_EVIDENCE, FAKE_TOOL_CLAIM, UNGROUNDED_NUMBER, FAKE_TURN_REF, FAKE_CARD_REF), ' +
26
+ 'since (ISO time). Default returns the newest 20 rows plus byVerdict/byCode counts.',
27
+ parameters: {
28
+ sessionId: {
29
+ type: 'string',
30
+ description: 'Only rows for this session id.',
31
+ },
32
+ turn: {
33
+ type: 'integer',
34
+ description: 'Only rows for this 1-based turn number.',
35
+ },
36
+ verdict: {
37
+ type: 'string',
38
+ enum: ['clean', 'flagged'],
39
+ description: 'Only rows with this verdict.',
40
+ },
41
+ code: {
42
+ type: 'string',
43
+ description: 'Only rows containing this flag code.',
44
+ },
45
+ since: {
46
+ type: 'string',
47
+ description: 'Only rows at or after this ISO timestamp.',
48
+ },
49
+ limit: {
50
+ type: 'integer',
51
+ description: 'Maximum rows returned (default 20, max 500).',
52
+ },
53
+ summary: {
54
+ type: 'boolean',
55
+ description: 'Default true: include byVerdict / byCode / metrics. false returns rows only.',
56
+ },
57
+ },
58
+ output: {
59
+ schema: { type: 'object', additionalProperties: true },
60
+ render: (_args, value) => (typeof value === 'string' ? value : JSON.stringify(value, null, 2)),
61
+ },
62
+ invoke: (args = {}) => answerAudit(args, { memoryRoot: root }),
63
+ }];
64
+ }
65
+
66
+ /**
67
+ * 工具实现(也直接给测试用)。永远不抛。
68
+ * @param {object} args
69
+ * @param {{memoryRoot?:string|null,limit?:number}} config
70
+ */
71
+ export function answerAudit(args = {}, config = {}) {
72
+ const memoryRoot = typeof config.memoryRoot === 'string' && config.memoryRoot.trim() !== ''
73
+ ? config.memoryRoot.trim()
74
+ : null;
75
+ if (!memoryRoot) {
76
+ return {
77
+ ok: false,
78
+ code: 'E_NO_MEMORY_ROOT',
79
+ error: '没有可用的记忆根目录;审计文件位于 <memoryRoot>/.longlive/answer-audit.jsonl。',
80
+ path: '',
81
+ };
82
+ }
83
+ const limit = Number.isFinite(Number(args.limit))
84
+ ? Math.max(1, Math.min(500, Number(args.limit)))
85
+ : 20;
86
+ const read = readAuditRows(memoryRoot, {
87
+ limit,
88
+ sessionId: typeof args.sessionId === 'string' ? args.sessionId : undefined,
89
+ turn: Number.isFinite(Number(args.turn)) ? Number(args.turn) : undefined,
90
+ verdict: typeof args.verdict === 'string' ? args.verdict : undefined,
91
+ code: typeof args.code === 'string' ? args.code : undefined,
92
+ since: typeof args.since === 'string' ? args.since : undefined,
93
+ });
94
+ const base = {
95
+ ok: read.ok,
96
+ path: read.path || auditFilePath(memoryRoot),
97
+ total: read.total,
98
+ matched: read.matched,
99
+ corrupt: read.corrupt,
100
+ ...(read.error ? { note: read.error } : {}),
101
+ rows: read.rows,
102
+ };
103
+ if (args.summary === false) return base;
104
+ return {
105
+ ...base,
106
+ byVerdict: read.byVerdict,
107
+ byCode: read.byCode,
108
+ sessions: read.sessions,
109
+ metrics: auditMetrics(read.rows),
110
+ };
111
+ }
@@ -0,0 +1,101 @@
1
+ // src/answer/second-hop.mjs — B4:证据包字段覆盖不足 → 第二跳补读(最多 1 跳)
2
+ //
3
+ // §11.6-B4:证据包「字段覆盖检查」不足 → 补读第二跳(最多 1 跳)。
4
+ // 断言口径:构造缺 when 的证据 → 触发第二跳。本模块不猜内容:第二跳只放宽
5
+ // 证据检索阈值/条数并回读相邻轮,字段仍缺则如实返回 missingFieldsAfter。
6
+
7
+ export const SECOND_HOP_SCHEMA = 'second-hop/v1';
8
+ export const DEFAULT_REQUIRED_FIELDS = Object.freeze(['when']);
9
+
10
+ const WHEN_PATTERNS = [
11
+ /\d{4}[-/年]\d{1,2}[-/月]\d{1,2}/u,
12
+ /\d{4}[-/年]\d{1,2}月?/u,
13
+ /今天|今日|昨天|昨日|前天|明天|后天|上周|本周|上周[一二三四五六日天]|周[一二三四五六日天]/u,
14
+ /\d{1,2}\s*月\s*\d{1,2}\s*日/u,
15
+ /\d{1,2}[::]\d{2}/u,
16
+ /刚才|刚刚|之前|上次|上次那回/u,
17
+ ];
18
+
19
+ /** Check which required dimensions the evidence text covers. */
20
+ export function evidenceFieldCoverage(items, { requiredFields = DEFAULT_REQUIRED_FIELDS } = {}) {
21
+ const text = (Array.isArray(items) ? items : [])
22
+ .map((item) => [item?.user, item?.assistant].filter(Boolean).join('\n'))
23
+ .join('\n');
24
+ const present = {
25
+ when: WHEN_PATTERNS.some((pattern) => pattern.test(text)),
26
+ what: text.trim() !== '',
27
+ who: /[\u3400-\u9fffA-Za-z]{2,}/u.test(text),
28
+ };
29
+ const missingFields = (Array.isArray(requiredFields) ? requiredFields : DEFAULT_REQUIRED_FIELDS)
30
+ .filter((field) => present[field] !== true);
31
+ return { requiredFields: [...requiredFields], present, missingFields, covered: missingFields.length === 0 };
32
+ }
33
+
34
+ function turnNumbers(items) {
35
+ return (Array.isArray(items) ? items : []).map((item) => Number(item?.turn)).filter(Number.isFinite);
36
+ }
37
+
38
+ /**
39
+ * First hop + at most one second hop. `retrieve` is injected (pure/testable seam).
40
+ * Returns the first-hop result extended with `items` (first + added second-hop turns)
41
+ * and a `secondHop` receipt. Never exceeds one extra hop.
42
+ */
43
+ export function retrieveEvidenceWithSecondHop({
44
+ turns = [],
45
+ question = '',
46
+ currentTurn = 0,
47
+ maxItems = 3,
48
+ minScore = 1.8,
49
+ retrieve,
50
+ requiredFields = DEFAULT_REQUIRED_FIELDS,
51
+ relaxFactor = 0.6,
52
+ maxHop = 1,
53
+ } = {}) {
54
+ if (typeof retrieve !== 'function') throw new Error('E_SECOND_HOP_RETRIEVE: retrieve function required');
55
+ const first = retrieve({ turns, question, currentTurn, maxItems, minScore });
56
+ const coverageFirst = evidenceFieldCoverage(first?.items, { requiredFields });
57
+ if (coverageFirst.covered || maxHop < 1) {
58
+ return {
59
+ ...first,
60
+ secondHop: {
61
+ schema: SECOND_HOP_SCHEMA,
62
+ triggered: false,
63
+ reason: coverageFirst.covered ? 'covered' : 'max-hop',
64
+ missingFieldsFirst: coverageFirst.missingFields,
65
+ addedTurns: [],
66
+ missingFieldsAfter: coverageFirst.missingFields,
67
+ hop: 1,
68
+ maxHop,
69
+ },
70
+ };
71
+ }
72
+ const relaxedMinScore = Math.max(0, Number(minScore) * Number(relaxFactor));
73
+ const second = retrieve({
74
+ turns,
75
+ question,
76
+ currentTurn,
77
+ maxItems: Math.max(1, Number(maxItems) + 2),
78
+ minScore: relaxedMinScore,
79
+ });
80
+ const firstTurns = new Set(turnNumbers(first?.items));
81
+ const added = (Array.isArray(second?.items) ? second.items : []).filter((item) => !firstTurns.has(Number(item?.turn)));
82
+ const combined = [...(Array.isArray(first?.items) ? first.items : []), ...added].slice(0, Math.max(1, Number(maxItems) + 2));
83
+ const coverageAfter = evidenceFieldCoverage(combined, { requiredFields });
84
+ return {
85
+ ...first,
86
+ items: combined,
87
+ zeroHit: combined.length === 0,
88
+ secondHop: {
89
+ schema: SECOND_HOP_SCHEMA,
90
+ triggered: true,
91
+ reason: 'field-coverage',
92
+ missingFieldsFirst: coverageFirst.missingFields,
93
+ addedTurns: added.map((item) => Number(item.turn)).filter(Number.isFinite),
94
+ missingFieldsAfter: coverageAfter.missingFields,
95
+ filled: coverageAfter.covered,
96
+ hop: 2,
97
+ maxHop,
98
+ relaxedMinScore,
99
+ },
100
+ };
101
+ }
@@ -55,7 +55,7 @@ import {
55
55
  // `autoTruncate: {}` cannot cut a session by accident; `mode:'request'` also
56
56
  // requires enabled:true, and the runner additionally needs the watchdog.
57
57
  export const AUTO_TRUNCATE_DEFAULTS = Object.freeze({
58
- enabled: false,
58
+ enabled: true,
59
59
  mode: 'dryrun',
60
60
  tokensThreshold: 300_000,
61
61
  minTurns: 12,