@wrongstack/core 0.285.0 → 0.287.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 (340) hide show
  1. package/dist/autophase/phase-orchestrator.d.ts.map +1 -1
  2. package/dist/boot.d.ts.map +1 -1
  3. package/dist/coordination/agent-monitor.d.ts +29 -0
  4. package/dist/coordination/agent-monitor.d.ts.map +1 -1
  5. package/dist/coordination/autonomous-brain.d.ts +0 -2
  6. package/dist/coordination/autonomous-brain.d.ts.map +1 -1
  7. package/dist/coordination/autonomous-coordinator.d.ts +4 -0
  8. package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
  9. package/dist/coordination/brain-ledger.d.ts +142 -0
  10. package/dist/coordination/brain-ledger.d.ts.map +1 -0
  11. package/dist/coordination/brain-monitor.d.ts +46 -2
  12. package/dist/coordination/brain-monitor.d.ts.map +1 -1
  13. package/dist/coordination/brain.d.ts +43 -0
  14. package/dist/coordination/brain.d.ts.map +1 -1
  15. package/dist/coordination/delegate-tool.d.ts +8 -14
  16. package/dist/coordination/delegate-tool.d.ts.map +1 -1
  17. package/dist/coordination/director/director-btw-notes.d.ts +20 -0
  18. package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
  19. package/dist/coordination/director/director-collab.d.ts +40 -0
  20. package/dist/coordination/director/director-collab.d.ts.map +1 -0
  21. package/dist/coordination/director.d.ts +28 -9
  22. package/dist/coordination/director.d.ts.map +1 -1
  23. package/dist/coordination/fleet-manager.d.ts +21 -0
  24. package/dist/coordination/fleet-manager.d.ts.map +1 -1
  25. package/dist/coordination/fleet.d.ts +6 -7
  26. package/dist/coordination/fleet.d.ts.map +1 -1
  27. package/dist/coordination/global-mailbox.d.ts +96 -21
  28. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  29. package/dist/coordination/icoordinator.d.ts +1 -3
  30. package/dist/coordination/icoordinator.d.ts.map +1 -1
  31. package/dist/coordination/index.d.ts +2 -1
  32. package/dist/coordination/index.d.ts.map +1 -1
  33. package/dist/coordination/index.js +2176 -806
  34. package/dist/coordination/index.js.map +4 -4
  35. package/dist/coordination/knowledge-graph.d.ts +13 -1
  36. package/dist/coordination/knowledge-graph.d.ts.map +1 -1
  37. package/dist/coordination/mail-tools.d.ts.map +1 -1
  38. package/dist/coordination/mailbox-actions.d.ts +12 -13
  39. package/dist/coordination/mailbox-actions.d.ts.map +1 -1
  40. package/dist/coordination/mailbox-constants.d.ts +64 -0
  41. package/dist/coordination/mailbox-constants.d.ts.map +1 -0
  42. package/dist/coordination/mailbox-events.d.ts +32 -0
  43. package/dist/coordination/mailbox-events.d.ts.map +1 -0
  44. package/dist/coordination/mailbox-message-codec.d.ts +28 -0
  45. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
  46. package/dist/coordination/mailbox-types.d.ts +130 -5
  47. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  48. package/dist/coordination/mailbox.d.ts +21 -2
  49. package/dist/coordination/mailbox.d.ts.map +1 -1
  50. package/dist/coordination/package-author-tracker.d.ts.map +1 -1
  51. package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
  52. package/dist/core/agent-loop.d.ts.map +1 -1
  53. package/dist/core/agent-response.d.ts +10 -1
  54. package/dist/core/agent-response.d.ts.map +1 -1
  55. package/dist/core/agent-tools.d.ts.map +1 -1
  56. package/dist/core/agent.d.ts +8 -0
  57. package/dist/core/agent.d.ts.map +1 -1
  58. package/dist/core/context.d.ts +25 -0
  59. package/dist/core/context.d.ts.map +1 -1
  60. package/dist/core/conversation-state.d.ts +6 -0
  61. package/dist/core/conversation-state.d.ts.map +1 -1
  62. package/dist/core/fallback-model.d.ts.map +1 -1
  63. package/dist/core/mailbox-loop.d.ts +12 -5
  64. package/dist/core/mailbox-loop.d.ts.map +1 -1
  65. package/dist/core/system-prompt-builder.d.ts +14 -6
  66. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  67. package/dist/defaults/index.d.ts +0 -1
  68. package/dist/defaults/index.d.ts.map +1 -1
  69. package/dist/defaults/index.js +1690 -1998
  70. package/dist/defaults/index.js.map +4 -4
  71. package/dist/execution/auto-compaction-middleware.d.ts +8 -1
  72. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  73. package/dist/execution/autonomous-runner.d.ts +7 -1
  74. package/dist/execution/autonomous-runner.d.ts.map +1 -1
  75. package/dist/execution/autonomy-brain.d.ts +72 -11
  76. package/dist/execution/autonomy-brain.d.ts.map +1 -1
  77. package/dist/execution/brain-chain.d.ts +53 -0
  78. package/dist/execution/brain-chain.d.ts.map +1 -0
  79. package/dist/execution/brain-evaluation.d.ts +66 -0
  80. package/dist/execution/brain-evaluation.d.ts.map +1 -0
  81. package/dist/execution/brain-runtime.d.ts +171 -0
  82. package/dist/execution/brain-runtime.d.ts.map +1 -0
  83. package/dist/execution/compaction-core.d.ts.map +1 -1
  84. package/dist/execution/council-brain.d.ts +79 -0
  85. package/dist/execution/council-brain.d.ts.map +1 -0
  86. package/dist/execution/council-orchestrator.d.ts +32 -0
  87. package/dist/execution/council-orchestrator.d.ts.map +1 -0
  88. package/dist/execution/council-personas.d.ts +19 -0
  89. package/dist/execution/council-personas.d.ts.map +1 -0
  90. package/dist/execution/council-profiles.d.ts +34 -0
  91. package/dist/execution/council-profiles.d.ts.map +1 -0
  92. package/dist/execution/council-prompts.d.ts +21 -0
  93. package/dist/execution/council-prompts.d.ts.map +1 -0
  94. package/dist/execution/council-resolution.d.ts +57 -0
  95. package/dist/execution/council-resolution.d.ts.map +1 -0
  96. package/dist/execution/design-kit-loader.d.ts.map +1 -1
  97. package/dist/execution/enhance-recovery.d.ts +35 -0
  98. package/dist/execution/enhance-recovery.d.ts.map +1 -0
  99. package/dist/execution/error-handler.d.ts.map +1 -1
  100. package/dist/execution/eternal-autonomy.d.ts +12 -0
  101. package/dist/execution/eternal-autonomy.d.ts.map +1 -1
  102. package/dist/execution/index.d.ts +11 -1
  103. package/dist/execution/index.d.ts.map +1 -1
  104. package/dist/execution/index.js +3076 -526
  105. package/dist/execution/index.js.map +4 -4
  106. package/dist/execution/intelligent-compactor.d.ts +18 -7
  107. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  108. package/dist/execution/one-shot-llm.d.ts +52 -0
  109. package/dist/execution/one-shot-llm.d.ts.map +1 -0
  110. package/dist/execution/parallel-eternal-engine.d.ts +12 -0
  111. package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
  112. package/dist/execution/prompt-enhancer.d.ts +23 -4
  113. package/dist/execution/prompt-enhancer.d.ts.map +1 -1
  114. package/dist/execution/prompt-enhancer.js +24 -6
  115. package/dist/execution/prompt-enhancer.js.map +2 -2
  116. package/dist/execution/selective-compactor.d.ts +4 -4
  117. package/dist/execution/strategy-compactor.d.ts +7 -0
  118. package/dist/execution/strategy-compactor.d.ts.map +1 -1
  119. package/dist/execution/tool-executor.d.ts +19 -5
  120. package/dist/execution/tool-executor.d.ts.map +1 -1
  121. package/dist/hooks/registry.d.ts.map +1 -1
  122. package/dist/hooks/runner.d.ts.map +1 -1
  123. package/dist/hooks/shell-executor.d.ts.map +1 -1
  124. package/dist/hq/alerts.d.ts +5 -0
  125. package/dist/hq/alerts.d.ts.map +1 -1
  126. package/dist/hq/auth-store.d.ts +38 -10
  127. package/dist/hq/auth-store.d.ts.map +1 -1
  128. package/dist/hq/commands.d.ts +8 -4
  129. package/dist/hq/commands.d.ts.map +1 -1
  130. package/dist/hq/cost-bridge.d.ts +5 -3
  131. package/dist/hq/cost-bridge.d.ts.map +1 -1
  132. package/dist/hq/factory.d.ts +3 -0
  133. package/dist/hq/factory.d.ts.map +1 -1
  134. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  135. package/dist/hq/index.d.ts +0 -1
  136. package/dist/hq/index.d.ts.map +1 -1
  137. package/dist/hq/index.js +1016 -283
  138. package/dist/hq/index.js.map +4 -4
  139. package/dist/hq/persistence.d.ts +42 -0
  140. package/dist/hq/persistence.d.ts.map +1 -1
  141. package/dist/hq/protocol/brain.d.ts +24 -0
  142. package/dist/hq/protocol/brain.d.ts.map +1 -0
  143. package/dist/hq/protocol/browser.d.ts +12 -0
  144. package/dist/hq/protocol/browser.d.ts.map +1 -0
  145. package/dist/hq/protocol/client.d.ts +69 -0
  146. package/dist/hq/protocol/client.d.ts.map +1 -0
  147. package/dist/hq/protocol/core.d.ts +130 -0
  148. package/dist/hq/protocol/core.d.ts.map +1 -0
  149. package/dist/hq/protocol/fleet.d.ts +73 -0
  150. package/dist/hq/protocol/fleet.d.ts.map +1 -0
  151. package/dist/hq/protocol/mailbox.d.ts +76 -0
  152. package/dist/hq/protocol/mailbox.d.ts.map +1 -0
  153. package/dist/hq/protocol/mcp.d.ts +51 -0
  154. package/dist/hq/protocol/mcp.d.ts.map +1 -0
  155. package/dist/hq/protocol/project.d.ts +26 -0
  156. package/dist/hq/protocol/project.d.ts.map +1 -0
  157. package/dist/hq/protocol/session.d.ts +153 -0
  158. package/dist/hq/protocol/session.d.ts.map +1 -0
  159. package/dist/hq/protocol/tool.d.ts +31 -0
  160. package/dist/hq/protocol/tool.d.ts.map +1 -0
  161. package/dist/hq/protocol.d.ts +10 -559
  162. package/dist/hq/protocol.d.ts.map +1 -1
  163. package/dist/hq/publisher.d.ts +4 -0
  164. package/dist/hq/publisher.d.ts.map +1 -1
  165. package/dist/index.d.ts +24 -8
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +7676 -3432
  168. package/dist/index.js.map +4 -4
  169. package/dist/infrastructure/index.js +13 -11
  170. package/dist/infrastructure/index.js.map +2 -2
  171. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  172. package/dist/kernel/events/agent-events.d.ts +283 -0
  173. package/dist/kernel/events/agent-events.d.ts.map +1 -0
  174. package/dist/kernel/events/brain-events.d.ts +176 -0
  175. package/dist/kernel/events/brain-events.d.ts.map +1 -0
  176. package/dist/kernel/events/fleet-events.d.ts +39 -0
  177. package/dist/kernel/events/fleet-events.d.ts.map +1 -0
  178. package/dist/kernel/events/memory-events.d.ts +165 -0
  179. package/dist/kernel/events/memory-events.d.ts.map +1 -0
  180. package/dist/kernel/events/provider-events.d.ts +98 -0
  181. package/dist/kernel/events/provider-events.d.ts.map +1 -0
  182. package/dist/kernel/events/sdd-events.d.ts +118 -0
  183. package/dist/kernel/events/sdd-events.d.ts.map +1 -0
  184. package/dist/kernel/events/session-events.d.ts +227 -0
  185. package/dist/kernel/events/session-events.d.ts.map +1 -0
  186. package/dist/kernel/events/tool-events.d.ts +185 -0
  187. package/dist/kernel/events/tool-events.d.ts.map +1 -0
  188. package/dist/kernel/events/worktree-events.d.ts +58 -0
  189. package/dist/kernel/events/worktree-events.d.ts.map +1 -0
  190. package/dist/kernel/events.d.ts +12 -1282
  191. package/dist/kernel/events.d.ts.map +1 -1
  192. package/dist/kernel/index.js +22 -2
  193. package/dist/kernel/index.js.map +2 -2
  194. package/dist/kernel/run-controller.d.ts +15 -0
  195. package/dist/kernel/run-controller.d.ts.map +1 -1
  196. package/dist/mailbox-attach.d.ts.map +1 -1
  197. package/dist/models/index.js +19 -6
  198. package/dist/models/index.js.map +2 -2
  199. package/dist/models/llm-selector.d.ts +7 -0
  200. package/dist/models/llm-selector.d.ts.map +1 -1
  201. package/dist/observability/index.js +16 -15
  202. package/dist/observability/index.js.map +2 -2
  203. package/dist/plugins/auto-review-plugin.d.ts +36 -0
  204. package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
  205. package/dist/plugins/chimera-plugin.d.ts +8 -0
  206. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  207. package/dist/security/index.d.ts +1 -0
  208. package/dist/security/index.d.ts.map +1 -1
  209. package/dist/security/index.js +260 -23
  210. package/dist/security/index.js.map +4 -4
  211. package/dist/security/permission-policy-schema.d.ts +77 -0
  212. package/dist/security/permission-policy-schema.d.ts.map +1 -0
  213. package/dist/security/permission-policy.d.ts +5 -0
  214. package/dist/security/permission-policy.d.ts.map +1 -1
  215. package/dist/security/secret-scrubber.d.ts.map +1 -1
  216. package/dist/security/secret-vault.d.ts +9 -0
  217. package/dist/security/secret-vault.d.ts.map +1 -1
  218. package/dist/session-registry.d.ts.map +1 -1
  219. package/dist/skills/index.js.map +2 -2
  220. package/dist/storage/config-loader.d.ts +10 -0
  221. package/dist/storage/config-loader.d.ts.map +1 -1
  222. package/dist/storage/goal-kanban.d.ts +69 -0
  223. package/dist/storage/goal-kanban.d.ts.map +1 -0
  224. package/dist/storage/goal-store.d.ts +1 -1
  225. package/dist/storage/goal-store.d.ts.map +1 -1
  226. package/dist/storage/index.d.ts +1 -1
  227. package/dist/storage/index.d.ts.map +1 -1
  228. package/dist/storage/index.js +572 -879
  229. package/dist/storage/index.js.map +4 -4
  230. package/dist/storage/memory-consolidator.d.ts +9 -1
  231. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  232. package/dist/storage/plan-store.d.ts +1 -1
  233. package/dist/storage/plan-store.d.ts.map +1 -1
  234. package/dist/storage/queue-store.d.ts +4 -0
  235. package/dist/storage/queue-store.d.ts.map +1 -1
  236. package/dist/storage/session-store.d.ts +9 -0
  237. package/dist/storage/session-store.d.ts.map +1 -1
  238. package/dist/storage/task-store.d.ts +1 -1
  239. package/dist/storage/task-store.d.ts.map +1 -1
  240. package/dist/storage/todos-checkpoint.d.ts +2 -2
  241. package/dist/storage/todos-checkpoint.d.ts.map +1 -1
  242. package/dist/tools/council-tool.d.ts +25 -0
  243. package/dist/tools/council-tool.d.ts.map +1 -0
  244. package/dist/tools/index.d.ts +2 -0
  245. package/dist/tools/index.d.ts.map +1 -1
  246. package/dist/tools/index.js +1884 -12
  247. package/dist/tools/index.js.map +4 -4
  248. package/dist/tools/one-shot-llm-tool.d.ts +51 -0
  249. package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
  250. package/dist/types/config.d.ts +236 -4
  251. package/dist/types/config.d.ts.map +1 -1
  252. package/dist/types/context-window.d.ts.map +1 -1
  253. package/dist/types/council.d.ts +134 -0
  254. package/dist/types/council.d.ts.map +1 -0
  255. package/dist/types/default-config.d.ts +3 -3
  256. package/dist/types/hooks.d.ts +14 -0
  257. package/dist/types/hooks.d.ts.map +1 -1
  258. package/dist/types/index.d.ts +2 -0
  259. package/dist/types/index.d.ts.map +1 -1
  260. package/dist/types/index.js +40 -8
  261. package/dist/types/index.js.map +3 -3
  262. package/dist/types/multi-agent.d.ts +8 -0
  263. package/dist/types/multi-agent.d.ts.map +1 -1
  264. package/dist/types/observability.d.ts +5 -0
  265. package/dist/types/observability.d.ts.map +1 -1
  266. package/dist/types/one-shot-llm.d.ts +128 -0
  267. package/dist/types/one-shot-llm.d.ts.map +1 -0
  268. package/dist/types/plugin.d.ts +6 -0
  269. package/dist/types/plugin.d.ts.map +1 -1
  270. package/dist/types/provider.d.ts +17 -0
  271. package/dist/types/provider.d.ts.map +1 -1
  272. package/dist/types/system-prompt.d.ts +2 -1
  273. package/dist/types/system-prompt.d.ts.map +1 -1
  274. package/dist/types/tool-executor.d.ts +10 -0
  275. package/dist/types/tool-executor.d.ts.map +1 -1
  276. package/dist/types/tool.d.ts +7 -0
  277. package/dist/types/tool.d.ts.map +1 -1
  278. package/dist/utils/atomic-write.d.ts.map +1 -1
  279. package/dist/utils/glob-expand.d.ts.map +1 -1
  280. package/dist/utils/incoming-images.d.ts +39 -0
  281. package/dist/utils/incoming-images.d.ts.map +1 -0
  282. package/dist/utils/index.d.ts +1 -0
  283. package/dist/utils/index.d.ts.map +1 -1
  284. package/dist/utils/index.js +156 -11
  285. package/dist/utils/index.js.map +4 -4
  286. package/dist/utils/tool-wire-compact.d.ts +1 -0
  287. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  288. package/dist/utils/wstack-paths.d.ts +20 -4
  289. package/dist/utils/wstack-paths.d.ts.map +1 -1
  290. package/dist/worktree/worktree-manager.d.ts +13 -4
  291. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  292. package/instructions/agents/browser.md +25 -16
  293. package/instructions/cli/next-task-predictor.md +5 -3
  294. package/instructions/coordination/subagent-baseline.md +17 -2
  295. package/instructions/leader-after-task.md +19 -8
  296. package/instructions/llm/council-judge.md +19 -0
  297. package/instructions/llm/council-voter.md +22 -0
  298. package/instructions/modes/architect.md +14 -9
  299. package/instructions/modes/audit-lite.md +9 -7
  300. package/instructions/modes/brief-system.md +12 -12
  301. package/instructions/modes/brief.md +7 -5
  302. package/instructions/modes/code-auditor.md +13 -8
  303. package/instructions/modes/code-reviewer.md +13 -8
  304. package/instructions/modes/debug-lite.md +7 -6
  305. package/instructions/modes/debugger.md +13 -9
  306. package/instructions/modes/devops.md +13 -9
  307. package/instructions/modes/plan-lite.md +7 -6
  308. package/instructions/modes/refactor-lite.md +7 -6
  309. package/instructions/modes/refactorer.md +14 -9
  310. package/instructions/modes/research-lite.md +8 -6
  311. package/instructions/modes/research-web.md +18 -55
  312. package/instructions/modes/review-lite.md +7 -6
  313. package/instructions/modes/teach-system.md +15 -15
  314. package/instructions/modes/teach.md +13 -12
  315. package/instructions/modes/test-lite.md +6 -5
  316. package/instructions/modes/tester.md +13 -9
  317. package/instructions/modes/ui-design.md +14 -8
  318. package/instructions/sections/tool/commit-hygiene.md +1 -7
  319. package/instructions/sections/tool/common-patterns.md +7 -1
  320. package/instructions/sections/tool/delegation-full.md +3 -31
  321. package/instructions/sections/tool/mailbox-compact.md +1 -1
  322. package/instructions/sections/tool/mailbox-full.md +6 -22
  323. package/instructions/system.md +294 -8
  324. package/package.json +2 -2
  325. package/skills/docker-deploy/SKILL.md +1 -1
  326. package/skills/mailbox-bridge/SKILL.md +16 -7
  327. package/skills/output-standards/SKILL.md +16 -16
  328. package/skills/wrongstack-mailbox/SKILL.md +57 -12
  329. package/dist/coordination/director-construction.d.ts +0 -90
  330. package/dist/coordination/director-construction.d.ts.map +0 -1
  331. package/dist/hq/agent-bridge.d.ts +0 -27
  332. package/dist/hq/agent-bridge.d.ts.map +0 -1
  333. package/dist/plugins/git-plugin.d.ts +0 -15
  334. package/dist/plugins/git-plugin.d.ts.map +0 -1
  335. package/dist/plugins/observability-plugin.d.ts +0 -19
  336. package/dist/plugins/observability-plugin.d.ts.map +0 -1
  337. package/dist/plugins/plan-plugin.d.ts +0 -16
  338. package/dist/plugins/plan-plugin.d.ts.map +0 -1
  339. package/dist/storage/memory-store.d.ts +0 -119
  340. package/dist/storage/memory-store.d.ts.map +0 -1
@@ -7,7 +7,8 @@ import { createInterface } from "node:readline";
7
7
 
8
8
  // src/utils/atomic-write.ts
9
9
  import { randomBytes as randomBytes2 } from "node:crypto";
10
- import * as fs from "node:fs/promises";
10
+ import * as fs2 from "node:fs/promises";
11
+ import { watch as watchDir } from "node:fs";
11
12
  import * as path3 from "node:path";
12
13
 
13
14
  // src/utils/child-env.ts
@@ -453,14 +454,34 @@ function ulid(seedTime = Date.now()) {
453
454
 
454
455
  // src/utils/wstack-paths.ts
455
456
  import { createHash } from "node:crypto";
457
+ import * as fs from "node:fs";
456
458
  import * as os from "node:os";
457
459
  import * as path2 from "node:path";
460
+ function canonicalProjectRoot(absRoot) {
461
+ const checkoutRoot = path2.resolve(absRoot);
462
+ const dotGit = path2.join(checkoutRoot, ".git");
463
+ try {
464
+ if (!fs.statSync(dotGit).isFile()) return checkoutRoot;
465
+ const gitDirLine = fs.readFileSync(dotGit, "utf8").trim();
466
+ const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
467
+ if (!match?.[1]) return checkoutRoot;
468
+ const gitDir = path2.resolve(checkoutRoot, match[1].trim());
469
+ const commonDirFile = path2.join(gitDir, "commondir");
470
+ if (!fs.statSync(commonDirFile).isFile()) return checkoutRoot;
471
+ const commonDir = path2.resolve(gitDir, fs.readFileSync(commonDirFile, "utf8").trim());
472
+ if (path2.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
473
+ return path2.dirname(commonDir);
474
+ } catch {
475
+ return checkoutRoot;
476
+ }
477
+ }
458
478
  function projectHash(absRoot) {
459
- return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
479
+ return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
460
480
  }
461
481
  function projectSlug(absRoot) {
462
- const base = slugify2(path2.basename(absRoot));
463
- const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
482
+ const identityRoot = canonicalProjectRoot(absRoot);
483
+ const base = slugify2(path2.basename(identityRoot));
484
+ const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
464
485
  return `${base}-${hash}`;
465
486
  }
466
487
  function slugify2(name) {
@@ -661,16 +682,16 @@ var FsError = class extends WrongStackError {
661
682
  // src/utils/atomic-write.ts
662
683
  async function atomicWrite(targetPath, content, opts = {}) {
663
684
  const dir = path3.dirname(targetPath);
664
- await fs.mkdir(dir, { recursive: true });
685
+ await fs2.mkdir(dir, { recursive: true });
665
686
  const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes2(6).toString("hex")}.tmp`);
666
687
  try {
667
688
  if (typeof content === "string") {
668
- await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
689
+ await fs2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
669
690
  } else {
670
- await fs.writeFile(tmp, content, { flag: "wx" });
691
+ await fs2.writeFile(tmp, content, { flag: "wx" });
671
692
  }
672
693
  try {
673
- const fh = await fs.open(tmp, "r+");
694
+ const fh = await fs2.open(tmp, "r+");
674
695
  try {
675
696
  await fh.sync();
676
697
  } finally {
@@ -680,35 +701,35 @@ async function atomicWrite(targetPath, content, opts = {}) {
680
701
  }
681
702
  let mode;
682
703
  try {
683
- const stat13 = await fs.stat(targetPath);
704
+ const stat13 = await fs2.stat(targetPath);
684
705
  mode = stat13.mode & 511;
685
706
  } catch {
686
707
  mode = opts.mode;
687
708
  }
688
709
  if (mode !== void 0) {
689
- await fs.chmod(tmp, mode);
710
+ await fs2.chmod(tmp, mode);
690
711
  }
691
712
  await renameWithRetry(tmp, targetPath);
692
713
  if (mode !== void 0 && process.platform === "win32") {
693
714
  try {
694
- await fs.chmod(targetPath, mode);
715
+ await fs2.chmod(targetPath, mode);
695
716
  } catch {
696
717
  }
697
718
  }
698
719
  } catch (err) {
699
720
  try {
700
- await fs.unlink(tmp);
721
+ await fs2.unlink(tmp);
701
722
  } catch {
702
723
  }
703
724
  throw err;
704
725
  }
705
726
  }
706
727
  async function ensureDir(dir) {
707
- await fs.mkdir(dir, { recursive: true });
728
+ await fs2.mkdir(dir, { recursive: true });
708
729
  }
709
730
  async function withFileLock(targetPath, fn, opts = {}) {
710
731
  const dir = path3.dirname(targetPath);
711
- await fs.mkdir(dir, { recursive: true });
732
+ await fs2.mkdir(dir, { recursive: true });
712
733
  const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
713
734
  const timeoutMs = opts.timeoutMs ?? 5e3;
714
735
  const staleMs = opts.staleMs ?? 3e4;
@@ -716,26 +737,27 @@ async function withFileLock(targetPath, fn, opts = {}) {
716
737
  let handle;
717
738
  for (; ; ) {
718
739
  try {
719
- handle = await fs.open(lockPath, "wx");
740
+ handle = await fs2.open(lockPath, "wx");
720
741
  await handle.writeFile(`${process.pid}:${Date.now()}`);
721
742
  break;
722
743
  } catch (err) {
723
744
  const code = err.code;
724
745
  if (code === "ENOENT") {
725
- await fs.mkdir(dir, { recursive: true });
746
+ await fs2.mkdir(dir, { recursive: true });
726
747
  continue;
727
748
  }
728
- if (code !== "EEXIST") throw err;
749
+ if (code !== "EEXIST" && code !== "EPERM") throw err;
729
750
  try {
730
- const stat13 = await fs.stat(lockPath);
751
+ const stat13 = await fs2.stat(lockPath);
731
752
  if (Date.now() - stat13.mtimeMs > staleMs) {
732
- await fs.unlink(lockPath);
753
+ await fs2.unlink(lockPath);
733
754
  continue;
734
755
  }
735
756
  } catch {
736
757
  continue;
737
758
  }
738
- if (Date.now() - started >= timeoutMs) {
759
+ const elapsed = Date.now() - started;
760
+ if (elapsed >= timeoutMs) {
739
761
  throw new FsError({
740
762
  message: `Timed out waiting for file lock: ${targetPath}`,
741
763
  code: "FS_ATOMIC_WRITE_FAILED",
@@ -743,7 +765,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
743
765
  context: { timeoutMs }
744
766
  });
745
767
  }
746
- await new Promise((resolve11) => setTimeout(resolve11, 25));
768
+ await waitForLockRelease(lockPath, timeoutMs - elapsed);
747
769
  }
748
770
  }
749
771
  try {
@@ -754,22 +776,66 @@ async function withFileLock(targetPath, fn, opts = {}) {
754
776
  } catch {
755
777
  }
756
778
  try {
757
- await fs.unlink(lockPath);
779
+ await fs2.unlink(lockPath);
758
780
  } catch {
759
781
  }
760
782
  }
761
783
  }
784
+ async function waitForLockRelease(lockPath, remainingMs) {
785
+ const parentDir = path3.dirname(lockPath);
786
+ const lockName = path3.basename(lockPath);
787
+ const intervalMs = Math.min(remainingMs, 100);
788
+ return new Promise((resolve11) => {
789
+ let settled = false;
790
+ let watcher = null;
791
+ const timer = setTimeout(() => {
792
+ settled = true;
793
+ watcher?.close();
794
+ resolve11();
795
+ }, intervalMs);
796
+ try {
797
+ watcher = watchDir(parentDir, (eventType, filename) => {
798
+ if (settled) return;
799
+ if (filename === lockName && (eventType === "rename" || eventType === "change")) {
800
+ settled = true;
801
+ clearTimeout(timer);
802
+ watcher?.close();
803
+ resolve11();
804
+ }
805
+ });
806
+ } catch {
807
+ clearTimeout(timer);
808
+ if (!settled) {
809
+ settled = true;
810
+ setTimeout(resolve11, Math.min(remainingMs, 25));
811
+ }
812
+ return;
813
+ }
814
+ fs2.access(lockPath).then(
815
+ () => {
816
+ },
817
+ () => {
818
+ if (!settled) {
819
+ settled = true;
820
+ clearTimeout(timer);
821
+ watcher?.close();
822
+ resolve11();
823
+ }
824
+ }
825
+ );
826
+ });
827
+ }
762
828
  var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
763
829
  async function renameWithRetry(from, to) {
764
830
  if (process.platform !== "win32") {
765
- await fs.rename(from, to);
831
+ await fs2.rename(from, to);
766
832
  return;
767
833
  }
768
834
  const delays = [10, 25, 60, 120, 250];
769
835
  let lastErr;
770
836
  for (let i = 0; i <= delays.length; i++) {
771
837
  try {
772
- await fs.rename(from, to);
838
+ await fs2.rename(from, to);
773
839
  return;
774
840
  } catch (err) {
775
841
  lastErr = err;
@@ -2137,6 +2203,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2137
2203
  projectRoot;
2138
2204
  checkpointCas;
2139
2205
  isSessionInUse;
2206
+ logger;
2140
2207
  /**
2141
2208
  * In-memory cache for load() results, keyed by session ID. The cache is
2142
2209
  * invalidated when the file's mtimeMs or size changes (indicating the
@@ -2162,6 +2229,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
2162
2229
  this.events = opts.events;
2163
2230
  this.secretScrubber = opts.secretScrubber;
2164
2231
  this.isSessionInUse = opts.isSessionInUse;
2232
+ this.logger = opts.logger;
2233
+ }
2234
+ /**
2235
+ * Emit a structured warning. Uses the configured Logger when available;
2236
+ * falls back to console.warn(JSON) so warnings are never silently dropped.
2237
+ */
2238
+ logWarn(msg, ctx) {
2239
+ if (this.logger) {
2240
+ this.logger.warn(msg, ctx);
2241
+ } else {
2242
+ console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
2243
+ }
2165
2244
  }
2166
2245
  /**
2167
2246
  * Clear the load() cache. Useful for testing or when the caller knows
@@ -2263,12 +2342,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
2263
2342
  this.emitWrite(id, file, "create", "success", Date.now() - t0);
2264
2343
  return writer;
2265
2344
  } catch (err) {
2266
- await handle.close().catch((e) => console.warn(JSON.stringify({
2267
- level: "warn",
2268
- event: "session_store.handle_close_failed",
2269
- message: e instanceof Error ? e.message : String(e),
2270
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2271
- })));
2345
+ await handle.close().catch((e) => this.logWarn(
2346
+ "Session handle close failed",
2347
+ { event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
2348
+ ));
2272
2349
  this.emitError(id, file, "create", toErrorMessage(err), true);
2273
2350
  throw err;
2274
2351
  }
@@ -2399,12 +2476,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
2399
2476
  this.emitWrite(id, file, "resume", "success", Date.now() - t0);
2400
2477
  return { writer, data: resumedData };
2401
2478
  } catch (err) {
2402
- await handle.close().catch((e) => console.warn(JSON.stringify({
2403
- level: "warn",
2404
- event: "session_store.handle_close_failed",
2405
- message: e instanceof Error ? e.message : String(e),
2406
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2407
- })));
2479
+ await handle.close().catch((e) => this.logWarn(
2480
+ "Session handle close failed",
2481
+ { event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
2482
+ ));
2408
2483
  this.emitError(id, file, "resume", toErrorMessage(err), true);
2409
2484
  throw err;
2410
2485
  }
@@ -3025,13 +3100,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
3025
3100
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
3026
3101
  const msg = toErrorMessage(err);
3027
3102
  this.emitError(id, manifest, "summary_fallback", msg, true);
3028
- console.warn(JSON.stringify({
3029
- level: "warn",
3030
- event: "session_store.manifest_write_failed",
3031
- sessionId: id,
3032
- message: msg,
3033
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3034
- }));
3103
+ this.logWarn(
3104
+ "Session manifest write failed",
3105
+ { event: "session_store.manifest_write_failed", sessionId: id, message: msg }
3106
+ );
3035
3107
  });
3036
3108
  outcome = "failure";
3037
3109
  errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
@@ -3393,10 +3465,19 @@ var QueueStore = class {
3393
3465
  // reject assigning an optional constructor parameter to these fields.
3394
3466
  events;
3395
3467
  traceId;
3468
+ logger;
3396
3469
  constructor(opts) {
3397
3470
  this.file = path8.join(opts.dir, "queue.json");
3398
3471
  this.events = opts.events;
3399
3472
  this.traceId = opts.traceId;
3473
+ this.logger = opts.logger;
3474
+ }
3475
+ logWarn(msg, ctx) {
3476
+ if (this.logger) {
3477
+ this.logger.warn(msg, ctx);
3478
+ } else {
3479
+ console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
3480
+ }
3400
3481
  }
3401
3482
  async write(items) {
3402
3483
  const t0 = Date.now();
@@ -3426,13 +3507,7 @@ var QueueStore = class {
3426
3507
  recoverable: false,
3427
3508
  ...this.traceId !== void 0 && { traceId: this.traceId }
3428
3509
  });
3429
- console.warn(JSON.stringify({
3430
- level: "warn",
3431
- event: "queue_store.write_failed",
3432
- path: this.file,
3433
- message: toErrorMessage(err),
3434
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3435
- }));
3510
+ this.logWarn("Queue store write failed", { event: "queue_store.write_failed", path: this.file, message: toErrorMessage(err) });
3436
3511
  }
3437
3512
  }
3438
3513
  async read() {
@@ -3464,13 +3539,7 @@ var QueueStore = class {
3464
3539
  recoverable: true,
3465
3540
  ...this.traceId !== void 0 && { traceId: this.traceId }
3466
3541
  });
3467
- console.warn(JSON.stringify({
3468
- level: "warn",
3469
- event: "queue_store.read_failed",
3470
- path: this.file,
3471
- message: toErrorMessage(err),
3472
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3473
- }));
3542
+ this.logWarn("Queue store read failed", { event: "queue_store.read_failed", path: this.file, message: toErrorMessage(err) });
3474
3543
  return [];
3475
3544
  }
3476
3545
  let parsed;
@@ -3543,13 +3612,7 @@ var QueueStore = class {
3543
3612
  recoverable: true,
3544
3613
  ...this.traceId !== void 0 && { traceId: this.traceId }
3545
3614
  });
3546
- console.warn(JSON.stringify({
3547
- level: "warn",
3548
- event: "queue_store.clear_failed",
3549
- path: this.file,
3550
- message: err.message,
3551
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3552
- }));
3615
+ this.logWarn("Queue store clear failed", { event: "queue_store.clear_failed", path: this.file, message: err.message });
3553
3616
  }
3554
3617
  }
3555
3618
  };
@@ -3703,7 +3766,7 @@ function mergeAdjacentText(blocks) {
3703
3766
 
3704
3767
  // src/storage/memory-backend.ts
3705
3768
  import { randomUUID as randomUUID2 } from "node:crypto";
3706
- import * as fs2 from "node:fs/promises";
3769
+ import * as fs3 from "node:fs/promises";
3707
3770
  import * as path10 from "node:path";
3708
3771
 
3709
3772
  // src/types/memory.ts
@@ -3871,7 +3934,7 @@ var FileMemoryBackend = class {
3871
3934
  }
3872
3935
  async getMtime(file) {
3873
3936
  try {
3874
- const stat13 = await fs2.stat(file);
3937
+ const stat13 = await fs3.stat(file);
3875
3938
  return stat13.mtimeMs;
3876
3939
  } catch {
3877
3940
  return 0;
@@ -3921,7 +3984,7 @@ var FileMemoryBackend = class {
3921
3984
  await ensureDir(path10.dirname(file));
3922
3985
  let existing = "";
3923
3986
  try {
3924
- existing = await fs2.readFile(file, "utf8");
3987
+ existing = await fs3.readFile(file, "utf8");
3925
3988
  } catch {
3926
3989
  }
3927
3990
  const id = `mem_${Date.now()}_${randomUUID2().slice(0, 8)}`;
@@ -3939,7 +4002,7 @@ ${line}`;
3939
4002
  return withFileLock(file, async () => {
3940
4003
  let existing;
3941
4004
  try {
3942
- existing = await fs2.readFile(file, "utf8");
4005
+ existing = await fs3.readFile(file, "utf8");
3943
4006
  } catch {
3944
4007
  return 0;
3945
4008
  }
@@ -3976,7 +4039,7 @@ ${line}`;
3976
4039
  async readAll(scope, filePath) {
3977
4040
  const file = this.resolveFile(filePath, scope);
3978
4041
  try {
3979
- return await fs2.readFile(file, "utf8");
4042
+ return await fs3.readFile(file, "utf8");
3980
4043
  } catch {
3981
4044
  return "";
3982
4045
  }
@@ -4000,7 +4063,7 @@ ${line}`;
4000
4063
  const file = this.resolveFile(filePath, scope);
4001
4064
  let existing;
4002
4065
  try {
4003
- existing = await fs2.readFile(file, "utf8");
4066
+ existing = await fs3.readFile(file, "utf8");
4004
4067
  } catch {
4005
4068
  return 0;
4006
4069
  }
@@ -4020,7 +4083,7 @@ ${line}`;
4020
4083
  const next = lines.join("\n");
4021
4084
  const backup = `${file}.bak.${Date.now()}`;
4022
4085
  try {
4023
- await fs2.copyFile(file, backup);
4086
+ await fs3.copyFile(file, backup);
4024
4087
  await pruneConsolidateBackups(file);
4025
4088
  } catch {
4026
4089
  }
@@ -4037,11 +4100,11 @@ async function pruneConsolidateBackups(file) {
4037
4100
  const dir = path10.dirname(file);
4038
4101
  const base = path10.basename(file);
4039
4102
  const prefix = `${base}.bak.`;
4040
- const backups = (await fs2.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
4103
+ const backups = (await fs3.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
4041
4104
  await Promise.all(
4042
4105
  backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {
4043
4106
  try {
4044
- await fs2.unlink(path10.join(dir, name));
4107
+ await fs3.unlink(path10.join(dir, name));
4045
4108
  } catch {
4046
4109
  }
4047
4110
  })
@@ -4056,582 +4119,8 @@ function parseEntries(raw, scope = "project-memory") {
4056
4119
  return entries.reverse();
4057
4120
  }
4058
4121
 
4059
- // src/storage/memory-store.ts
4060
- var MAX_BYTES_TOTAL = 32e3;
4061
- var DefaultMemoryStore = class {
4062
- files;
4063
- events;
4064
- traceId;
4065
- backend;
4066
- /**
4067
- * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
4068
- * `clear` are read-modify-write against a single file; without a lock,
4069
- * two concurrent calls on the same scope can read the same baseline and
4070
- * the later write silently drops the earlier entry. We chain each
4071
- * mutation onto the prior promise for the same scope so they run in
4072
- * issue order. Different scopes still proceed in parallel.
4073
- *
4074
- * The chain tracks only the last pending write. If a write fails, its
4075
- * error is caught and swallowed so the chain stays alive for subsequent
4076
- * calls. The error is stored in `writeErrors` so callers can learn about
4077
- * it on the next read operation.
4078
- */
4079
- writeChain = /* @__PURE__ */ new Map();
4080
- /** Last write error per scope — surfaced as warnings on the next readAll(). */
4081
- writeErrors = /* @__PURE__ */ new Map();
4082
- /**
4083
- * When the global root is a temporary directory (opencode, CI sandboxes),
4084
- * memory files are also mirrored to the project tree so they survive
4085
- * session cleanup. The primary path stays in the temp root for isolation;
4086
- * this backup ensures memory is never lost.
4087
- */
4088
- persistBackup;
4089
- backupDir;
4090
- /**
4091
- * Per-scope tracked byte sizes — incremented on `remember()`, decremented on
4092
- * `forget()`, recalculated after `consolidate()`. Eliminates the redundant
4093
- * readAll() call that previously checked the file size after every write.
4094
- */
4095
- _trackedByteSizes = {};
4096
- /** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
4097
- _scoreCache = /* @__PURE__ */ new Map();
4098
- /**
4099
- * Per-entry cached lowercase strings — lazily allocated once and reused
4100
- * across scoreRelevant() calls so repeated scoring of the same entries skips
4101
- * re-lowercasing. Keyed by entry object identity; cleared (set to null) on
4102
- * every mutation (remember/forget/consolidate/clear) via invalidateScoreCaches().
4103
- */
4104
- _cachedLower = null;
4105
- constructor(opts) {
4106
- this.files = {
4107
- "project-agents": opts.paths.inProjectAgentsFile,
4108
- "project-memory": opts.paths.projectMemory,
4109
- "user-memory": opts.paths.globalMemory
4110
- };
4111
- this.events = opts.events;
4112
- this.backend = opts.backend ?? new FileMemoryBackend({ paths: opts.paths });
4113
- const root = opts.paths.globalRoot.toLowerCase();
4114
- this.persistBackup = /[/\\](tmp|temp|cache)[/\\]/.test(root);
4115
- this.backupDir = this.persistBackup ? opts.paths.inProjectAgentsFile.replace(/AGENTS\.md$/, "memory-persist") : "";
4116
- }
4117
- /** Expose the backend for plugin introspection and advanced queries. */
4118
- getBackend() {
4119
- return this.backend;
4120
- }
4121
- async runSerialized(scope, work) {
4122
- const prior = this.writeChain.get(scope) ?? Promise.resolve();
4123
- const next = prior.catch((err) => {
4124
- this.writeErrors.set(scope, err);
4125
- }).then(() => work());
4126
- this.writeChain.set(scope, next);
4127
- try {
4128
- return await next;
4129
- } catch (err) {
4130
- this.writeErrors.set(scope, err);
4131
- throw err;
4132
- } finally {
4133
- if (this.writeChain.get(scope) === next) {
4134
- this.writeChain.delete(scope);
4135
- }
4136
- }
4137
- }
4138
- /**
4139
- * Recalculate the tracked byte size for a scope by re-reading the file and
4140
- * summing the serialized byte length of each line. Called after consolidate()
4141
- * (which modifies the file) to keep the tracker accurate.
4142
- */
4143
- async _recalcTrackedByteSize(scope) {
4144
- const raw = await this.backend.readAll(scope, this.files[scope]);
4145
- let total = 0;
4146
- for (const line of raw.split("\n")) {
4147
- if (line.trim()) total += Buffer.byteLength(line, "utf8");
4148
- }
4149
- this._trackedByteSizes[scope] = total;
4150
- return total;
4151
- }
4152
- async readAll() {
4153
- const parts = [];
4154
- for (const scope of ["project-agents", "project-memory", "user-memory"]) {
4155
- const writeErr = this.writeErrors.get(scope);
4156
- if (writeErr) {
4157
- parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
4158
- }
4159
- const t0 = Date.now();
4160
- const filePath = this.files[scope];
4161
- try {
4162
- const body = await this.backend.readAll(scope, filePath);
4163
- const dur = Date.now() - t0;
4164
- this.events?.emit("storage.read", {
4165
- sessionId: "~memory~",
4166
- store: "memory",
4167
- filePath,
4168
- operation: "readAll",
4169
- outcome: "success",
4170
- durationMs: dur,
4171
- ...this.traceId !== void 0 && { traceId: this.traceId }
4172
- });
4173
- if (body.trim()) parts.push(`## ${labelOf(scope)}
4174
-
4175
- ${body.trim()}`);
4176
- } catch (err) {
4177
- const dur = Date.now() - t0;
4178
- this.events?.emit("storage.read", {
4179
- sessionId: "~memory~",
4180
- store: "memory",
4181
- filePath,
4182
- operation: "readAll",
4183
- outcome: "failure",
4184
- durationMs: dur,
4185
- error: toErrorMessage(err),
4186
- ...this.traceId !== void 0 && { traceId: this.traceId }
4187
- });
4188
- throw err;
4189
- }
4190
- }
4191
- return parts.join("\n\n");
4192
- }
4193
- async read(scope) {
4194
- const t0 = Date.now();
4195
- const filePath = this.files[scope];
4196
- try {
4197
- const body = await this.backend.readAll(scope, filePath);
4198
- const dur = Date.now() - t0;
4199
- this.events?.emit("storage.read", {
4200
- sessionId: "~memory~",
4201
- store: "memory",
4202
- filePath,
4203
- operation: "read",
4204
- outcome: "success",
4205
- durationMs: dur,
4206
- ...this.traceId !== void 0 && { traceId: this.traceId }
4207
- });
4208
- return body;
4209
- } catch (err) {
4210
- const dur = Date.now() - t0;
4211
- this.events?.emit("storage.read", {
4212
- sessionId: "~memory~",
4213
- store: "memory",
4214
- filePath,
4215
- operation: "read",
4216
- outcome: "failure",
4217
- durationMs: dur,
4218
- error: toErrorMessage(err),
4219
- ...this.traceId !== void 0 && { traceId: this.traceId }
4220
- });
4221
- throw err;
4222
- }
4223
- }
4224
- /**
4225
- * List entries from a scope, newest first. Delegates to the backend
4226
- * so graph/semantic backends can return enriched or filtered results.
4227
- */
4228
- async list(scope = "project-memory", limit) {
4229
- return this.backend.list(scope, this.files[scope], limit);
4230
- }
4231
- /**
4232
- * Find memories related to the given text via graph traversal.
4233
- * Falls back to content search when no graph backend is available.
4234
- */
4235
- async findRelated(text, scope = "project-memory", limit = 5) {
4236
- if (this.backend.findRelated) {
4237
- return this.backend.findRelated(scope, this.files[scope], text, limit);
4238
- }
4239
- return this.search(text, scope, limit);
4240
- }
4241
- async search(query, scope = "project-memory", limit) {
4242
- return this.backend.search(scope, query, this.files[scope], limit);
4243
- }
4244
- async remember(text, scope = "project-memory", metadata) {
4245
- const ts = (/* @__PURE__ */ new Date()).toISOString();
4246
- return this.runSerialized(scope, async () => {
4247
- const entry = { scope, text, ts, ...metadata };
4248
- const filePath = this.files[scope];
4249
- const t0 = Date.now();
4250
- try {
4251
- await this.backend.remember(scope, entry, filePath);
4252
- this.invalidateScoreCaches();
4253
- const dur = Date.now() - t0;
4254
- this.events?.emit("storage.write", {
4255
- sessionId: "~memory~",
4256
- store: "memory",
4257
- filePath,
4258
- operation: "remember",
4259
- outcome: "success",
4260
- durationMs: dur,
4261
- ...this.traceId !== void 0 && { traceId: this.traceId }
4262
- });
4263
- } catch (err) {
4264
- const dur = Date.now() - t0;
4265
- this.events?.emit("storage.write", {
4266
- sessionId: "~memory~",
4267
- store: "memory",
4268
- filePath,
4269
- operation: "remember",
4270
- outcome: "failure",
4271
- durationMs: dur,
4272
- error: toErrorMessage(err),
4273
- ...this.traceId !== void 0 && { traceId: this.traceId }
4274
- });
4275
- throw err;
4276
- }
4277
- let trackedSize = this._trackedByteSizes[scope];
4278
- if (trackedSize === void 0) {
4279
- trackedSize = await this._recalcTrackedByteSize(scope);
4280
- }
4281
- if (trackedSize > MAX_BYTES_TOTAL) {
4282
- const removed = await this.backend.consolidate(scope, this.files[scope]);
4283
- if (removed > 0) {
4284
- this.events?.emit("memory.consolidated", {
4285
- scope,
4286
- removed
4287
- });
4288
- await this._recalcTrackedByteSize(scope);
4289
- }
4290
- }
4291
- this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
4292
- await this.mirrorBackup(scope);
4293
- this.events?.emit("memory.remembered", {
4294
- scope,
4295
- text,
4296
- ts,
4297
- type: entry.type,
4298
- tags: entry.tags,
4299
- priority: entry.priority
4300
- });
4301
- });
4302
- }
4303
- /**
4304
- * Drop the relevance caches after a mutation. Both the per-context score
4305
- * cache and the per-entry lowercase cache are keyed on entry objects that a
4306
- * mutation invalidates, so they must be cleared together when entries change.
4307
- */
4308
- invalidateScoreCaches() {
4309
- this._scoreCache.clear();
4310
- this._cachedLower = null;
4311
- }
4312
- /**
4313
- * Score and rank memories by relevance to the current context.
4314
- * Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.
4315
- */
4316
- async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
4317
- const all = await this.list(scope);
4318
- if (all.length === 0) return [];
4319
- const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
4320
- const now = Date.now();
4321
- const TTL_MS = 3e4;
4322
- const cached = this._scoreCache.get(ctxHash);
4323
- if (cached && cached.expiresAt > now && cached.entries === all) {
4324
- return cached.scored.slice(0, Math.min(limit, 15));
4325
- }
4326
- const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
4327
- const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
4328
- const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
4329
- this._cachedLower ??= /* @__PURE__ */ new WeakMap();
4330
- const lowerCache = this._cachedLower;
4331
- const scored = [];
4332
- for (const entry of all) {
4333
- let score = 0;
4334
- const reasons = [];
4335
- let cachedLower = lowerCache.get(entry);
4336
- if (!cachedLower) {
4337
- cachedLower = {
4338
- textLower: entry.text.toLowerCase(),
4339
- tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
4340
- };
4341
- lowerCache.set(entry, cachedLower);
4342
- }
4343
- const { textLower, tagsLower } = cachedLower;
4344
- let taskHits = 0;
4345
- for (const w of taskWords) {
4346
- if (textLower.includes(w)) {
4347
- taskHits++;
4348
- score += 2;
4349
- }
4350
- if (tagsLower.some((t) => t.includes(w))) {
4351
- taskHits++;
4352
- score += 3;
4353
- }
4354
- }
4355
- if (taskHits > 0) reasons.push(`task match (${taskHits})`);
4356
- let skillHits = 0;
4357
- for (const w of skillWords) {
4358
- if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
4359
- skillHits++;
4360
- }
4361
- }
4362
- score += skillHits;
4363
- if (skillHits > 0) reasons.push(`skill match (${skillHits})`);
4364
- for (const w of toolWords) {
4365
- if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
4366
- score += 1;
4367
- reasons.push(`tool mention: ${w}`);
4368
- }
4369
- }
4370
- switch (entry.priority) {
4371
- case "critical":
4372
- score += 5;
4373
- reasons.push("critical");
4374
- break;
4375
- case "high":
4376
- score += 3;
4377
- reasons.push("high priority");
4378
- break;
4379
- case "medium":
4380
- score += 1;
4381
- break;
4382
- case "low":
4383
- score -= 2;
4384
- reasons.push("low priority");
4385
- break;
4386
- }
4387
- switch (entry.type) {
4388
- case "decision":
4389
- score += 2;
4390
- reasons.push("decision");
4391
- break;
4392
- case "convention":
4393
- score += 2;
4394
- reasons.push("convention");
4395
- break;
4396
- case "anti_pattern":
4397
- score += 3;
4398
- reasons.push("anti-pattern");
4399
- break;
4400
- case "preference":
4401
- score += 1;
4402
- reasons.push("preference");
4403
- break;
4404
- case "reference":
4405
- break;
4406
- case "fact":
4407
- break;
4408
- }
4409
- const ageDays = (now - new Date(entry.ts).getTime()) / (1e3 * 60 * 60 * 24);
4410
- if (ageDays < 1) score += 1;
4411
- else if (ageDays > 30) score -= 1;
4412
- if (entry.confidence !== void 0 && entry.confidence < 0.5) {
4413
- score -= 2;
4414
- reasons.push("low confidence");
4415
- }
4416
- if (entry.lastAccessed) {
4417
- const hoursSinceAccess = (now - new Date(entry.lastAccessed).getTime()) / (1e3 * 60 * 60);
4418
- if (hoursSinceAccess < 1) score -= 1;
4419
- }
4420
- if (score > 0) {
4421
- scored.push({
4422
- ...entry,
4423
- score,
4424
- matchReason: reasons.join(", ") || "keyword match"
4425
- });
4426
- }
4427
- }
4428
- scored.sort((a, b) => b.score - a.score);
4429
- const threshold = 2;
4430
- const relevant = scored.filter(
4431
- (s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
4432
- );
4433
- this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
4434
- return relevant.slice(0, Math.min(limit, 15));
4435
- }
4436
- async forget(query, scope = "project-memory") {
4437
- return this.runSerialized(scope, async () => {
4438
- const filePath = this.files[scope];
4439
- const t0 = Date.now();
4440
- let removed = 0;
4441
- try {
4442
- removed = await this.backend.forget(scope, query, filePath);
4443
- this.invalidateScoreCaches();
4444
- const dur = Date.now() - t0;
4445
- this.events?.emit("storage.write", {
4446
- sessionId: "~memory~",
4447
- store: "memory",
4448
- filePath,
4449
- operation: "forget",
4450
- outcome: "success",
4451
- durationMs: dur,
4452
- ...this.traceId !== void 0 && { traceId: this.traceId }
4453
- });
4454
- } catch (err) {
4455
- const dur = Date.now() - t0;
4456
- this.events?.emit("storage.write", {
4457
- sessionId: "~memory~",
4458
- store: "memory",
4459
- filePath,
4460
- operation: "forget",
4461
- outcome: "failure",
4462
- durationMs: dur,
4463
- error: toErrorMessage(err),
4464
- ...this.traceId !== void 0 && { traceId: this.traceId }
4465
- });
4466
- throw err;
4467
- }
4468
- if (removed > 0) {
4469
- this.events?.emit("memory.forgotten", {
4470
- scope,
4471
- query,
4472
- removed
4473
- });
4474
- await this.mirrorBackup(scope);
4475
- await this._recalcTrackedByteSize(scope);
4476
- }
4477
- return removed;
4478
- });
4479
- }
4480
- async consolidate(scope) {
4481
- return this.runSerialized(scope, async () => {
4482
- const filePath = this.files[scope];
4483
- const t0 = Date.now();
4484
- let removed = 0;
4485
- try {
4486
- removed = await this.backend.consolidate(scope, filePath);
4487
- this.invalidateScoreCaches();
4488
- const dur = Date.now() - t0;
4489
- this.events?.emit("storage.write", {
4490
- sessionId: "~memory~",
4491
- store: "memory",
4492
- filePath,
4493
- operation: "consolidate",
4494
- outcome: "success",
4495
- durationMs: dur,
4496
- ...this.traceId !== void 0 && { traceId: this.traceId }
4497
- });
4498
- } catch (err) {
4499
- const dur = Date.now() - t0;
4500
- this.events?.emit("storage.write", {
4501
- sessionId: "~memory~",
4502
- store: "memory",
4503
- filePath,
4504
- operation: "consolidate",
4505
- outcome: "failure",
4506
- durationMs: dur,
4507
- error: toErrorMessage(err),
4508
- ...this.traceId !== void 0 && { traceId: this.traceId }
4509
- });
4510
- throw err;
4511
- }
4512
- if (removed > 0) {
4513
- this.events?.emit("memory.consolidated", {
4514
- scope,
4515
- removed
4516
- });
4517
- await this.mirrorBackup(scope);
4518
- await this._recalcTrackedByteSize(scope);
4519
- }
4520
- });
4521
- }
4522
- async clear(scope) {
4523
- if (scope) {
4524
- await this.runSerialized(scope, async () => {
4525
- const filePath = this.files[scope];
4526
- const t0 = Date.now();
4527
- try {
4528
- await this.backend.clear(scope, filePath);
4529
- this.invalidateScoreCaches();
4530
- const dur = Date.now() - t0;
4531
- this.events?.emit("storage.write", {
4532
- sessionId: "~memory~",
4533
- store: "memory",
4534
- filePath,
4535
- operation: "clear",
4536
- outcome: "success",
4537
- durationMs: dur,
4538
- ...this.traceId !== void 0 && { traceId: this.traceId }
4539
- });
4540
- } catch (err) {
4541
- const dur = Date.now() - t0;
4542
- this.events?.emit("storage.write", {
4543
- sessionId: "~memory~",
4544
- store: "memory",
4545
- filePath,
4546
- operation: "clear",
4547
- outcome: "failure",
4548
- durationMs: dur,
4549
- error: toErrorMessage(err),
4550
- ...this.traceId !== void 0 && { traceId: this.traceId }
4551
- });
4552
- throw err;
4553
- }
4554
- this.events?.emit("memory.cleared", { scope });
4555
- await this.mirrorBackup(scope);
4556
- this._trackedByteSizes[scope] = 0;
4557
- });
4558
- return;
4559
- }
4560
- await Promise.all(
4561
- ["project-agents", "project-memory", "user-memory"].map(
4562
- async (s) => this.runSerialized(s, async () => {
4563
- const filePath = this.files[s];
4564
- const t0 = Date.now();
4565
- try {
4566
- await this.backend.clear(s, filePath);
4567
- const dur = Date.now() - t0;
4568
- this.events?.emit("storage.write", {
4569
- sessionId: "~memory~",
4570
- store: "memory",
4571
- filePath,
4572
- operation: "clear",
4573
- outcome: "success",
4574
- durationMs: dur,
4575
- ...this.traceId !== void 0 && { traceId: this.traceId }
4576
- });
4577
- } catch (err) {
4578
- const dur = Date.now() - t0;
4579
- this.events?.emit("storage.write", {
4580
- sessionId: "~memory~",
4581
- store: "memory",
4582
- filePath,
4583
- operation: "clear",
4584
- outcome: "failure",
4585
- durationMs: dur,
4586
- error: toErrorMessage(err),
4587
- ...this.traceId !== void 0 && { traceId: this.traceId }
4588
- });
4589
- throw err;
4590
- }
4591
- this.events?.emit("memory.cleared", { scope: s });
4592
- await this.mirrorBackup(s);
4593
- })
4594
- )
4595
- );
4596
- this.invalidateScoreCaches();
4597
- }
4598
- /**
4599
- * Return a new MemoryStore proxy that carries `traceId` on every storage
4600
- * event. The original store is left unchanged — callers that need a
4601
- * trace-decorated view (e.g. session-run tools) receive the proxy while
4602
- * the singleton remains trace-free for boot-time use.
4603
- *
4604
- * The proxy implements the full `MemoryStore` interface; all other
4605
- * properties (backend, etc.) are delegated to the original store.
4606
- */
4607
- withTraceId(traceId) {
4608
- this.traceId = traceId;
4609
- return this;
4610
- }
4611
- async mirrorBackup(scope) {
4612
- if (!this.persistBackup || scope === "project-agents") return;
4613
- try {
4614
- const content = await this.backend.readAll(scope, this.files[scope]);
4615
- const { writeFile: writeFile7, mkdir: mkdir7 } = await import("node:fs/promises");
4616
- await mkdir7(this.backupDir, { recursive: true });
4617
- await writeFile7(`${this.backupDir}/${scope}.md`, content, "utf8");
4618
- } catch {
4619
- }
4620
- }
4621
- };
4622
- function labelOf(scope) {
4623
- switch (scope) {
4624
- case "project-agents":
4625
- return "Project AGENTS.md";
4626
- case "project-memory":
4627
- return "Project memory";
4628
- case "user-memory":
4629
- return "User memory";
4630
- }
4631
- }
4632
-
4633
4122
  // src/storage/memory-graph-backend.ts
4634
- import * as fs3 from "node:fs/promises";
4123
+ import * as fs4 from "node:fs/promises";
4635
4124
  var GraphMemoryBackend = class _GraphMemoryBackend {
4636
4125
  kind = "graph";
4637
4126
  file;
@@ -4799,7 +4288,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
4799
4288
  this.loadedScope = scope;
4800
4289
  this.loaded = true;
4801
4290
  try {
4802
- await fs3.unlink(this.graphFile);
4291
+ await fs4.unlink(this.graphFile);
4803
4292
  } catch {
4804
4293
  }
4805
4294
  }
@@ -4839,7 +4328,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
4839
4328
  async loadGraph(scope) {
4840
4329
  if (this.loaded && this.loadedScope === scope) return;
4841
4330
  try {
4842
- const raw = await fs3.readFile(this.graphFile, "utf8");
4331
+ const raw = await fs4.readFile(this.graphFile, "utf8");
4843
4332
  const data = JSON.parse(raw);
4844
4333
  this.nodes = new Map(data.nodes);
4845
4334
  this.edges = data.edges;
@@ -4861,10 +4350,10 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
4861
4350
  edges: this.edges
4862
4351
  };
4863
4352
  const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
4864
- await fs3.mkdir(dir, { recursive: true });
4353
+ await fs4.mkdir(dir, { recursive: true });
4865
4354
  const tmp = `${this.graphFile}.tmp`;
4866
- await fs3.writeFile(tmp, JSON.stringify(data));
4867
- await fs3.rename(tmp, this.graphFile);
4355
+ await fs4.writeFile(tmp, JSON.stringify(data));
4356
+ await fs4.rename(tmp, this.graphFile);
4868
4357
  } catch {
4869
4358
  }
4870
4359
  }
@@ -4982,7 +4471,7 @@ function simpleHash(s) {
4982
4471
  }
4983
4472
 
4984
4473
  // src/utils/instruction-file.ts
4985
- import { readFileSync, statSync } from "node:fs";
4474
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
4986
4475
  import * as path11 from "node:path";
4987
4476
  import { fileURLToPath } from "node:url";
4988
4477
  var textCache = /* @__PURE__ */ new Map();
@@ -4993,7 +4482,7 @@ function readBundledInstructionText(relativePath) {
4993
4482
  let resolved = "";
4994
4483
  for (const root of instructionRootCandidates()) {
4995
4484
  try {
4996
- resolved = readFileSync(path11.join(root, relativePath), "utf8").trimEnd();
4485
+ resolved = readFileSync2(path11.join(root, relativePath), "utf8").trimEnd();
4997
4486
  break;
4998
4487
  } catch {
4999
4488
  }
@@ -5020,7 +4509,7 @@ function instructionRootCandidates() {
5020
4509
  }
5021
4510
  function isDirectory(candidate) {
5022
4511
  try {
5023
- return statSync(candidate).isDirectory();
4512
+ return statSync2(candidate).isDirectory();
5024
4513
  } catch {
5025
4514
  return false;
5026
4515
  }
@@ -5046,12 +4535,14 @@ var SessionMemoryConsolidator = class {
5046
4535
  model;
5047
4536
  minIterations;
5048
4537
  maxExistingEntries;
4538
+ oneShotOrchestrator;
5049
4539
  constructor(opts) {
5050
4540
  this.memoryStore = opts.memoryStore;
5051
4541
  this.provider = opts.provider;
5052
4542
  this.model = opts.model;
5053
4543
  this.minIterations = opts.minIterations ?? 2;
5054
4544
  this.maxExistingEntries = opts.maxExistingEntries ?? 15;
4545
+ this.oneShotOrchestrator = opts.oneShotOrchestrator;
5055
4546
  }
5056
4547
  afterRun = (ctx, result) => {
5057
4548
  if (result.status !== "done") return;
@@ -5070,19 +4561,32 @@ var SessionMemoryConsolidator = class {
5070
4561
  _iterations,
5071
4562
  existingEntries
5072
4563
  );
5073
- const signal = AbortSignal.timeout(15e3);
5074
- const response = await provider.complete(
5075
- {
5076
- model: _model,
5077
- system: [{ type: "text", text: prompt }],
5078
- messages: [
5079
- { role: "user", content: "Review the session and return memory operations as JSON." }
5080
- ],
5081
- maxTokens: 500
5082
- },
5083
- { signal }
5084
- );
5085
- const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
4564
+ let text;
4565
+ if (this.oneShotOrchestrator) {
4566
+ const oneShotResult = await this.oneShotOrchestrator.call({
4567
+ system: prompt,
4568
+ userPrompt: "Review the session and return memory operations as JSON.",
4569
+ model: _model ?? "deepseek-chat",
4570
+ maxTokens: 500,
4571
+ timeoutMs: 15e3,
4572
+ fallbackProfile: "memory"
4573
+ });
4574
+ text = oneShotResult.text;
4575
+ } else {
4576
+ const signal = AbortSignal.timeout(15e3);
4577
+ const response = await provider.complete(
4578
+ {
4579
+ model: _model,
4580
+ system: [{ type: "text", text: prompt }],
4581
+ messages: [
4582
+ { role: "user", content: "Review the session and return memory operations as JSON." }
4583
+ ],
4584
+ maxTokens: 500
4585
+ },
4586
+ { signal }
4587
+ );
4588
+ text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
4589
+ }
5086
4590
  if (!text) return;
5087
4591
  const jsonMatch = text.match(/\{[\s\S]*\}/);
5088
4592
  if (!jsonMatch) return;
@@ -5212,7 +4716,7 @@ function deepFreeze(obj) {
5212
4716
 
5213
4717
  // src/storage/provider-config-watcher.ts
5214
4718
  import * as syncFs from "node:fs";
5215
- import * as fs4 from "node:fs/promises";
4719
+ import * as fs5 from "node:fs/promises";
5216
4720
  import * as path12 from "node:path";
5217
4721
 
5218
4722
  // src/security/config-secrets.ts
@@ -5247,7 +4751,7 @@ function walk(node, vault, transform) {
5247
4751
  }
5248
4752
  return out;
5249
4753
  }
5250
- var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
4754
+ var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
5251
4755
  var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
5252
4756
  function isSecretField(name) {
5253
4757
  const lc = name.toLowerCase();
@@ -5259,7 +4763,7 @@ function isSecretField(name) {
5259
4763
  async function readProviderSnapshot(configPath, vault, warn) {
5260
4764
  let raw;
5261
4765
  try {
5262
- raw = await fs4.readFile(configPath, "utf8");
4766
+ raw = await fs5.readFile(configPath, "utf8");
5263
4767
  } catch (err) {
5264
4768
  if (err.code !== "ENOENT") {
5265
4769
  warn?.(`Could not read ${configPath}: ${err.message}`);
@@ -5348,24 +4852,24 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5348
4852
  }
5349
4853
 
5350
4854
  // src/storage/config-loader.ts
5351
- import * as fs5 from "node:fs/promises";
4855
+ import * as fs6 from "node:fs/promises";
5352
4856
  import * as path13 from "node:path";
5353
4857
 
5354
4858
  // src/types/config.ts
5355
4859
  var DEFAULT_TUI_THINKING_WORD = "thinking";
5356
4860
 
5357
4861
  // src/types/context-window.ts
5358
- var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
4862
+ var DEFAULT_CONTEXT_WINDOW_MODE_ID = "frugal";
5359
4863
  var CONTEXT_WINDOW_MODES = Object.freeze([
5360
4864
  {
5361
4865
  id: "balanced",
5362
4866
  name: "Balanced",
5363
4867
  description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
5364
- thresholds: { warn: 0.6, soft: 0.75, hard: 0.9 },
4868
+ thresholds: { warn: 0.5, soft: 0.65, hard: 0.8 },
5365
4869
  aggressiveOn: "soft",
5366
- preserveK: 10,
5367
- eliseThreshold: 2e3,
5368
- targetLoad: 0.65
4870
+ preserveK: 8,
4871
+ eliseThreshold: 1e3,
4872
+ targetLoad: 0.55
5369
4873
  },
5370
4874
  {
5371
4875
  id: "frugal",
@@ -5416,7 +4920,7 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
5416
4920
  descriptionMode: Object.freeze({}),
5417
4921
  disabledTools: Object.freeze([]),
5418
4922
  autoExtendLimit: true,
5419
- restrictToProjectRoot: false,
4923
+ restrictToProjectRoot: true,
5420
4924
  loopDetection: Object.freeze({
5421
4925
  mode: "steer-then-cut",
5422
4926
  steerThreshold: 3,
@@ -5426,8 +4930,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
5426
4930
  })
5427
4931
  });
5428
4932
  var DEFAULT_CONTEXT_CONFIG = Object.freeze({
5429
- preserveK: 10,
5430
- eliseThreshold: 2e3
4933
+ preserveK: 8,
4934
+ eliseThreshold: 1e3
5431
4935
  });
5432
4936
  var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
5433
4937
  autoProceedDelayMs: 45e3
@@ -5519,12 +5023,11 @@ var BEHAVIOR_DEFAULTS = {
5519
5023
  mcpServers: {},
5520
5024
  fallbackAuto: true,
5521
5025
  maxConcurrent: 4,
5522
- // YOLO on by default: auto-approve non-denied tool calls without a per-call
5523
- // prompt. Users
5524
- // who want the per-call prompts set `yolo: false`. Existing installs whose
5525
- // config lacks this key pick it up via fillMissingDefaults; an explicit
5526
- // `false` is preserved by the deep-merge (opt-out respected).
5527
- yolo: true,
5026
+ // YOLO opt-in: users who want auto-approve behaviour set `yolo: true`.
5027
+ // Off by default so prompt injection in repository content, fetched
5028
+ // content, or mailbox messages cannot induce arbitrary shell calls or
5029
+ // write operations without human confirmation.
5030
+ yolo: false,
5528
5031
  nextPrediction: false,
5529
5032
  hints: true,
5530
5033
  debugStream: false,
@@ -5549,7 +5052,7 @@ var BEHAVIOR_DEFAULTS = {
5549
5052
  terminalTitleAnimation: true,
5550
5053
  // Mirrored from the top-level yolo default so the autonomy subsystem
5551
5054
  // (which reads autonomy.yolo) stays consistent with config.yolo.
5552
- yolo: true,
5055
+ yolo: false,
5553
5056
  streamFleet: true,
5554
5057
  chime: false,
5555
5058
  confirmExit: true,
@@ -5696,6 +5199,10 @@ var KNOWN_DENIED_IN_PROJECT = [
5696
5199
  key: "fleet",
5697
5200
  reason: "Fleet supervision knobs: a repo-committed config could enable autonomous subagent spawning/steering/termination and mailbox traffic on the victim machine."
5698
5201
  },
5202
+ {
5203
+ key: "brain",
5204
+ reason: "Brain decision-layer knobs: a repo-committed config could raise the autonomy risk ceiling, switch the Brain to headless (removing the human tier), or reroute Brain decisions to an attacker-chosen provider/model."
5205
+ },
5699
5206
  {
5700
5207
  key: "git",
5701
5208
  reason: "Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim's commit history (impersonation)."
@@ -5744,6 +5251,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
5744
5251
  "extensions",
5745
5252
  "acp",
5746
5253
  "fleet",
5254
+ "brain",
5747
5255
  "git"
5748
5256
  ]);
5749
5257
  function assertInProjectAllowListComplete() {
@@ -5895,6 +5403,7 @@ var DefaultConfigLoader = class {
5895
5403
  extraSources;
5896
5404
  events;
5897
5405
  traceId;
5406
+ logger;
5898
5407
  jsonCache = /* @__PURE__ */ new Map();
5899
5408
  constructor(opts) {
5900
5409
  this.paths = opts.paths;
@@ -5903,6 +5412,19 @@ var DefaultConfigLoader = class {
5903
5412
  this.extraSources = opts.sources ?? [];
5904
5413
  this.events = opts.events;
5905
5414
  this.traceId = opts.traceId;
5415
+ this.logger = opts.logger;
5416
+ }
5417
+ /**
5418
+ * Emit a structured warning. Uses the configured Logger when available;
5419
+ * falls back to console.warn(JSON) so warnings are never silently dropped
5420
+ * during early boot (before a Logger is constructed).
5421
+ */
5422
+ logWarn(msg, ctx) {
5423
+ if (this.logger) {
5424
+ this.logger.warn(msg, ctx);
5425
+ } else {
5426
+ console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
5427
+ }
5906
5428
  }
5907
5429
  async load(opts = {}) {
5908
5430
  let cfg = { ...BEHAVIOR_DEFAULTS };
@@ -5915,7 +5437,14 @@ var DefaultConfigLoader = class {
5915
5437
  ]);
5916
5438
  cfg = deepMerge2(cfg, global);
5917
5439
  cfg = deepMerge2(cfg, local);
5918
- cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
5440
+ cfg = deepMerge2(
5441
+ cfg,
5442
+ stripUnsafeInProjectFields(
5443
+ inProject,
5444
+ this.paths.inProjectConfig,
5445
+ this.logger ? (msg) => this.logger.warn(msg) : void 0
5446
+ )
5447
+ );
5919
5448
  for (const [key, fn] of Object.entries(ENV_MAP)) {
5920
5449
  const v = process.env[key];
5921
5450
  if (v) fn(cfg, v);
@@ -5932,14 +5461,9 @@ var DefaultConfigLoader = class {
5932
5461
  cfg = deepMerge2(cfg, patch);
5933
5462
  }
5934
5463
  } catch (err) {
5935
- console.warn(
5936
- JSON.stringify({
5937
- level: "warn",
5938
- event: "config.source_load_failed",
5939
- source: src.name,
5940
- message: toErrorMessage(err),
5941
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
5942
- })
5464
+ this.logWarn(
5465
+ "Config source load failed",
5466
+ { event: "config.source_load_failed", source: src.name, message: toErrorMessage(err) }
5943
5467
  );
5944
5468
  }
5945
5469
  }
@@ -5980,7 +5504,7 @@ var DefaultConfigLoader = class {
5980
5504
  await withFileLock(fp, async () => {
5981
5505
  let parsed;
5982
5506
  try {
5983
- const raw = await fs5.readFile(fp, "utf8");
5507
+ const raw = await fs6.readFile(fp, "utf8");
5984
5508
  const result = safeParse(raw);
5985
5509
  if (!result.ok || !isPlainRecord(result.value)) {
5986
5510
  return;
@@ -5999,14 +5523,9 @@ var DefaultConfigLoader = class {
5999
5523
  durationMs: Date.now() - t0,
6000
5524
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
6001
5525
  });
6002
- console.warn(
6003
- JSON.stringify({
6004
- level: "warn",
6005
- event: "config.defaults_read_failed",
6006
- path: fp,
6007
- message: toErrorMessage(err),
6008
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6009
- })
5526
+ this.logWarn(
5527
+ "Config defaults read failed",
5528
+ { event: "config.defaults_read_failed", path: fp, message: toErrorMessage(err) }
6010
5529
  );
6011
5530
  return;
6012
5531
  }
@@ -6040,14 +5559,9 @@ var DefaultConfigLoader = class {
6040
5559
  durationMs: Date.now() - t0,
6041
5560
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
6042
5561
  });
6043
- console.warn(
6044
- JSON.stringify({
6045
- level: "warn",
6046
- event: "config.defaults_write_failed",
6047
- path: fp,
6048
- message: toErrorMessage(err),
6049
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6050
- })
5562
+ this.logWarn(
5563
+ "Config defaults write failed",
5564
+ { event: "config.defaults_write_failed", path: fp, message: toErrorMessage(err) }
6051
5565
  );
6052
5566
  }
6053
5567
  }
@@ -6099,7 +5613,7 @@ var DefaultConfigLoader = class {
6099
5613
  const fp = this.paths.syncConfig;
6100
5614
  const t0 = Date.now();
6101
5615
  try {
6102
- const raw = await fs5.readFile(fp, "utf8");
5616
+ const raw = await fs6.readFile(fp, "utf8");
6103
5617
  const parsed = safeParse(raw);
6104
5618
  if (!parsed.ok || !parsed.value) {
6105
5619
  this.events?.emit("storage.read", {
@@ -6150,13 +5664,9 @@ var DefaultConfigLoader = class {
6150
5664
  error: storageErrorString(err),
6151
5665
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
6152
5666
  });
6153
- console.warn(
6154
- JSON.stringify({
6155
- level: "warn",
6156
- event: "config.sync_load_failed",
6157
- message: toErrorMessage(err),
6158
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6159
- })
5667
+ this.logWarn(
5668
+ "Config sync load failed",
5669
+ { event: "config.sync_load_failed", message: toErrorMessage(err) }
6160
5670
  );
6161
5671
  return null;
6162
5672
  }
@@ -6165,7 +5675,7 @@ var DefaultConfigLoader = class {
6165
5675
  const t0 = Date.now();
6166
5676
  let mtimeMs = null;
6167
5677
  try {
6168
- const stat13 = await fs5.stat(file);
5678
+ const stat13 = await fs6.stat(file);
6169
5679
  mtimeMs = stat13.mtimeMs;
6170
5680
  const cached = this.jsonCache.get(file);
6171
5681
  if (cached && cached.mtimeMs === mtimeMs) {
@@ -6186,20 +5696,15 @@ var DefaultConfigLoader = class {
6186
5696
  error: storageErrorString(err),
6187
5697
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
6188
5698
  });
6189
- console.warn(
6190
- JSON.stringify({
6191
- level: "warn",
6192
- event: "config.read_failed",
6193
- path: file,
6194
- message: toErrorMessage(err),
6195
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6196
- })
5699
+ this.logWarn(
5700
+ "Config read failed (stat)",
5701
+ { event: "config.read_failed", path: file, message: toErrorMessage(err) }
6197
5702
  );
6198
5703
  return {};
6199
5704
  }
6200
5705
  let raw;
6201
5706
  try {
6202
- raw = await fs5.readFile(file, "utf8");
5707
+ raw = await fs6.readFile(file, "utf8");
6203
5708
  } catch (err) {
6204
5709
  if (err.code !== "ENOENT") {
6205
5710
  this.events?.emit("storage.read", {
@@ -6212,14 +5717,9 @@ var DefaultConfigLoader = class {
6212
5717
  error: storageErrorString(err),
6213
5718
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
6214
5719
  });
6215
- console.warn(
6216
- JSON.stringify({
6217
- level: "warn",
6218
- event: "config.read_failed",
6219
- path: file,
6220
- message: toErrorMessage(err),
6221
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6222
- })
5720
+ this.logWarn(
5721
+ "Config read failed (read)",
5722
+ { event: "config.read_failed", path: file, message: toErrorMessage(err) }
6223
5723
  );
6224
5724
  }
6225
5725
  this.jsonCache.set(file, { mtimeMs: null, value: {} });
@@ -6237,14 +5737,9 @@ var DefaultConfigLoader = class {
6237
5737
  error: "parse error or empty file",
6238
5738
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
6239
5739
  });
6240
- console.warn(
6241
- JSON.stringify({
6242
- level: "warn",
6243
- event: "config.parse_failed",
6244
- path: file,
6245
- message: "invalid JSON \u2014 falling back to defaults for this layer",
6246
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6247
- })
5740
+ this.logWarn(
5741
+ "Config parse failed \u2014 falling back to defaults",
5742
+ { event: "config.parse_failed", path: file }
6248
5743
  );
6249
5744
  return {};
6250
5745
  }
@@ -6291,8 +5786,9 @@ var DefaultConfigLoader = class {
6291
5786
  }
6292
5787
  if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
6293
5788
  const known = listContextWindowModes().map((m) => m.id).join(", ");
6294
- console.warn(
6295
- `[config] Ignoring unknown context.mode "${c.mode}" (expected one of: ${known}); falling back to "${DEFAULT_CONTEXT_WINDOW_MODE_ID}".`
5789
+ this.logWarn(
5790
+ `Ignoring unknown context.mode "${c.mode}" \u2014 falling back to "${DEFAULT_CONTEXT_WINDOW_MODE_ID}"`,
5791
+ { event: "config.unknown_context_mode", mode: c.mode, known }
6296
5792
  );
6297
5793
  c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;
6298
5794
  }
@@ -6510,7 +6006,7 @@ function defaultIsPidAlive(pid) {
6510
6006
  }
6511
6007
 
6512
6008
  // src/storage/session-reader.ts
6513
- import * as fs6 from "node:fs/promises";
6009
+ import * as fs7 from "node:fs/promises";
6514
6010
  var DefaultSessionReader = class _DefaultSessionReader {
6515
6011
  store;
6516
6012
  eventCache = /* @__PURE__ */ new Map();
@@ -6528,7 +6024,7 @@ var DefaultSessionReader = class _DefaultSessionReader {
6528
6024
  const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
6529
6025
  let mtimeMs = null;
6530
6026
  try {
6531
- const stat13 = await fs6.stat(sessionPath);
6027
+ const stat13 = await fs7.stat(sessionPath);
6532
6028
  mtimeMs = stat13.mtimeMs;
6533
6029
  } catch {
6534
6030
  this.eventCache.delete(sessionId);
@@ -6892,7 +6388,7 @@ function renderPlainText(meta, events) {
6892
6388
 
6893
6389
  // src/storage/annotations-store.ts
6894
6390
  import { randomUUID as randomUUID3 } from "node:crypto";
6895
- import * as fs7 from "node:fs/promises";
6391
+ import * as fs8 from "node:fs/promises";
6896
6392
  var FILE_VERSION = 1;
6897
6393
  var MAX_TEXT_LENGTH = 2e3;
6898
6394
  var MAX_ANNOTATIONS = 1e3;
@@ -7119,7 +6615,7 @@ var AnnotationsStore = class {
7119
6615
  const fp = this.filePath(sessionId);
7120
6616
  let raw;
7121
6617
  try {
7122
- raw = await fs7.readFile(fp, "utf8");
6618
+ raw = await fs8.readFile(fp, "utf8");
7123
6619
  } catch (err) {
7124
6620
  if (err.code === "ENOENT") return null;
7125
6621
  throw err;
@@ -7156,7 +6652,7 @@ var AnnotationsStore = class {
7156
6652
  };
7157
6653
 
7158
6654
  // src/storage/replay-log-store.ts
7159
- import * as fs8 from "node:fs/promises";
6655
+ import * as fs9 from "node:fs/promises";
7160
6656
  import * as path15 from "node:path";
7161
6657
 
7162
6658
  // src/replay/hash.ts
@@ -7246,12 +6742,12 @@ var ReplayLogStore = class {
7246
6742
  const line = JSON.stringify(entry) + "\n";
7247
6743
  let offset2 = 0;
7248
6744
  try {
7249
- const stat13 = await fs8.stat(fp);
6745
+ const stat13 = await fs9.stat(fp);
7250
6746
  offset2 = stat13.size;
7251
6747
  } catch (err) {
7252
6748
  if (err.code !== "ENOENT") throw err;
7253
6749
  }
7254
- await fs8.appendFile(fp, line, "utf8");
6750
+ await fs9.appendFile(fp, line, "utf8");
7255
6751
  cache.set(hash, { entry, offset: offset2, length: Buffer.byteLength(line, "utf8") });
7256
6752
  this.diskCount.set(input.sessionId, currentCount + 1);
7257
6753
  this.events?.emit("storage.write", {
@@ -7379,7 +6875,7 @@ var ReplayLogStore = class {
7379
6875
  const scan = async (dir, prefix, depth) => {
7380
6876
  let entries;
7381
6877
  try {
7382
- entries = await fs8.readdir(dir, { withFileTypes: true });
6878
+ entries = await fs9.readdir(dir, { withFileTypes: true });
7383
6879
  } catch (err) {
7384
6880
  if (depth === 0 && err.code !== "ENOENT") {
7385
6881
  console.warn(JSON.stringify({
@@ -7417,7 +6913,7 @@ var ReplayLogStore = class {
7417
6913
  return sessionScopedPath(this.dir, sessionId, ".replay.jsonl");
7418
6914
  }
7419
6915
  async countEntries(filePath) {
7420
- const handle = await fs8.open(filePath, "r");
6916
+ const handle = await fs9.open(filePath, "r");
7421
6917
  const buffer = Buffer.allocUnsafe(64 * 1024);
7422
6918
  let count = 0;
7423
6919
  let hasNonWhitespace2 = false;
@@ -7458,7 +6954,7 @@ var ReplayLogStore = class {
7458
6954
  async readAll(sessionId) {
7459
6955
  const fp = this.filePath(sessionId);
7460
6956
  try {
7461
- const raw = await fs8.readFile(fp, "utf8");
6957
+ const raw = await fs9.readFile(fp, "utf8");
7462
6958
  const out = [];
7463
6959
  for (const line of raw.split("\n")) {
7464
6960
  if (!line.trim()) continue;
@@ -7494,7 +6990,7 @@ var ReplayLogStore = class {
7494
6990
  const fp = this.filePath(sessionId);
7495
6991
  cache = /* @__PURE__ */ new Map();
7496
6992
  try {
7497
- const handle = await fs8.open(fp, "r");
6993
+ const handle = await fs9.open(fp, "r");
7498
6994
  const CHUNK = 64 * 1024;
7499
6995
  const buffer = Buffer.alloc(CHUNK);
7500
6996
  let leftover = "";
@@ -7545,7 +7041,7 @@ var ReplayLogStore = class {
7545
7041
  async hydrateEntry(sessionId, hash, location) {
7546
7042
  if (location.entry) return location.entry;
7547
7043
  const fp = this.filePath(sessionId);
7548
- const handle = await fs8.open(fp, "r");
7044
+ const handle = await fs9.open(fp, "r");
7549
7045
  try {
7550
7046
  const buffer = Buffer.alloc(location.length);
7551
7047
  const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);
@@ -7572,7 +7068,7 @@ var ReplayLogStore = class {
7572
7068
  };
7573
7069
 
7574
7070
  // src/storage/session-recovery.ts
7575
- import * as fs9 from "node:fs/promises";
7071
+ import * as fs10 from "node:fs/promises";
7576
7072
  import * as path16 from "node:path";
7577
7073
  var SessionRecovery = class {
7578
7074
  constructor(dir) {
@@ -7599,7 +7095,7 @@ var SessionRecovery = class {
7599
7095
  const fp = this.filePath(sessionId);
7600
7096
  let stat13;
7601
7097
  try {
7602
- stat13 = await fs9.stat(fp);
7098
+ stat13 = await fs10.stat(fp);
7603
7099
  } catch (err) {
7604
7100
  if (err.code === "ENOENT") return null;
7605
7101
  return null;
@@ -7633,7 +7129,7 @@ var SessionRecovery = class {
7633
7129
  const fp = this.filePath(sessionId);
7634
7130
  let raw;
7635
7131
  try {
7636
- raw = await fs9.readFile(fp, "utf8");
7132
+ raw = await fs10.readFile(fp, "utf8");
7637
7133
  } catch (err) {
7638
7134
  if (err.code === "ENOENT") return null;
7639
7135
  return null;
@@ -7678,7 +7174,7 @@ var SessionRecovery = class {
7678
7174
  const collect = async (dir, prefix, depth) => {
7679
7175
  let entries;
7680
7176
  try {
7681
- entries = await fs9.readdir(dir, { withFileTypes: true });
7177
+ entries = await fs10.readdir(dir, { withFileTypes: true });
7682
7178
  } catch {
7683
7179
  return;
7684
7180
  }
@@ -7729,7 +7225,7 @@ function hasNonWhitespace(line) {
7729
7225
  }
7730
7226
  async function scanLatestLifecycleBoundary(filePath, size) {
7731
7227
  const CHUNK_SIZE = 64 * 1024;
7732
- const handle = await fs9.open(filePath, "r");
7228
+ const handle = await fs10.open(filePath, "r");
7733
7229
  let position = size;
7734
7230
  let laterLineFragment = Buffer.alloc(0);
7735
7231
  let latestBoundary = null;
@@ -7768,7 +7264,7 @@ async function scanLatestLifecycleBoundary(filePath, size) {
7768
7264
 
7769
7265
  // src/storage/tool-audit-log.ts
7770
7266
  import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
7771
- import * as fs10 from "node:fs/promises";
7267
+ import * as fs11 from "node:fs/promises";
7772
7268
  var GENESIS_PREV = "0".repeat(64);
7773
7269
  var DEFAULT_FSYNC_EVERY = 100;
7774
7270
  var ToolAuditLog = class {
@@ -7838,9 +7334,9 @@ var ToolAuditLog = class {
7838
7334
  isError: input.isError,
7839
7335
  index
7840
7336
  };
7841
- await fs10.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
7337
+ await fs11.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
7842
7338
  try {
7843
- const st = await fs10.stat(fp);
7339
+ const st = await fs11.stat(fp);
7844
7340
  this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
7845
7341
  } catch {
7846
7342
  }
@@ -7887,7 +7383,7 @@ var ToolAuditLog = class {
7887
7383
  const cachedStat = this.tailStat.get(sessionId);
7888
7384
  if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
7889
7385
  try {
7890
- const st = await fs10.stat(fp);
7386
+ const st = await fs11.stat(fp);
7891
7387
  if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
7892
7388
  return { prevHash: cachedHash, nextIndex: cachedIndex };
7893
7389
  }
@@ -7908,7 +7404,7 @@ var ToolAuditLog = class {
7908
7404
  this.tailHash.set(sessionId, prevHash);
7909
7405
  this.tailIndex.set(sessionId, nextIndex);
7910
7406
  try {
7911
- const st = await fs10.stat(fp);
7407
+ const st = await fs11.stat(fp);
7912
7408
  this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
7913
7409
  } catch {
7914
7410
  }
@@ -8029,7 +7525,7 @@ var ToolAuditLog = class {
8029
7525
  async readAll(sessionId) {
8030
7526
  const fp = this.filePath(sessionId);
8031
7527
  try {
8032
- const raw = await fs10.readFile(fp, "utf8");
7528
+ const raw = await fs11.readFile(fp, "utf8");
8033
7529
  const out = [];
8034
7530
  for (const line of raw.split("\n")) {
8035
7531
  if (!line.trim()) continue;
@@ -8067,7 +7563,7 @@ var ToolAuditLog = class {
8067
7563
  }
8068
7564
  async sync(sessionId, fp) {
8069
7565
  try {
8070
- const fh = await fs10.open(fp, "r+");
7566
+ const fh = await fs11.open(fp, "r+");
8071
7567
  try {
8072
7568
  await fh.sync();
8073
7569
  } finally {
@@ -8205,14 +7701,15 @@ var SessionAnalyzer = class {
8205
7701
  };
8206
7702
 
8207
7703
  // src/session-registry.ts
8208
- import * as fs11 from "node:fs/promises";
7704
+ import * as fs12 from "node:fs/promises";
8209
7705
  import * as path17 from "node:path";
8210
7706
  import { randomUUID as randomUUID5 } from "node:crypto";
8211
7707
  var REGISTRY_FILE = "session-registry.json";
8212
7708
  var HEARTBEAT_INTERVAL_MS = 5e3;
8213
7709
  var STALE_TIMEOUT_MS = 3e4;
8214
7710
  var CLOSING_GRACE_MS = 15e3;
8215
- var LOST_GRACE_MS = 3e5;
7711
+ var LOST_GRACE_MS = 3e4;
7712
+ var AGENT_STALE_MS = 5 * 6e4;
8216
7713
  var STALE_LOCK_MS = 1e4;
8217
7714
  var STALE_TMP_MS = 6e4;
8218
7715
  var MAX_STALE_TMP_FILES = 20;
@@ -8401,36 +7898,43 @@ var SessionRegistry = class {
8401
7898
  }
8402
7899
  async readAndPrune() {
8403
7900
  try {
8404
- const raw = await fs11.readFile(this.filePath, "utf8");
7901
+ const raw = await fs12.readFile(this.filePath, "utf8");
8405
7902
  const registry = parseRegistry(raw);
8406
7903
  const now = Date.now();
8407
7904
  let pruned = false;
8408
7905
  for (const [id, entry] of Object.entries(registry)) {
8409
- const heartbeatAge = now - new Date(entry.lastHeartbeatAt).getTime();
7906
+ const heartbeatAt = Date.parse(entry.lastHeartbeatAt);
7907
+ if (!Number.isFinite(heartbeatAt)) {
7908
+ delete registry[id];
7909
+ pruned = true;
7910
+ continue;
7911
+ }
7912
+ const heartbeatAge = now - heartbeatAt;
7913
+ const agents = Array.isArray(entry.agents) ? entry.agents : [];
7914
+ const liveAgents = agents.filter((agent) => {
7915
+ if (agent.id === "leader") return true;
7916
+ const lastActivityAt = Date.parse(agent.lastActivityAt);
7917
+ return Number.isFinite(lastActivityAt) && now - lastActivityAt <= AGENT_STALE_MS;
7918
+ });
7919
+ if (liveAgents.length !== agents.length || entry.agentCount !== liveAgents.length) {
7920
+ entry.agents = liveAgents;
7921
+ entry.agentCount = liveAgents.length;
7922
+ pruned = true;
7923
+ }
8410
7924
  if (entry.status === "closing" && heartbeatAge > CLOSING_GRACE_MS) {
8411
7925
  delete registry[id];
8412
7926
  pruned = true;
8413
7927
  continue;
8414
7928
  }
8415
- if (heartbeatAge > STALE_TIMEOUT_MS) {
8416
- const alive = pidAlive(entry.pid);
8417
- if (alive) {
8418
- if (entry.status !== "lost" && entry.status !== "closing" && entry.status !== "stale") {
8419
- entry.status = "lost";
8420
- pruned = true;
8421
- }
8422
- if (entry.status === "lost" && heartbeatAge > STALE_TIMEOUT_MS + LOST_GRACE_MS) {
8423
- delete registry[id];
8424
- pruned = true;
8425
- }
8426
- } else {
8427
- entry.status = "stale";
8428
- const startedAge = now - new Date(entry.startedAt).getTime();
8429
- if (startedAge > 5 * 6e4) {
8430
- delete registry[id];
8431
- pruned = true;
8432
- }
8433
- }
7929
+ if (heartbeatAge <= STALE_TIMEOUT_MS) continue;
7930
+ if (!pidAlive(entry.pid) || heartbeatAge > STALE_TIMEOUT_MS + LOST_GRACE_MS) {
7931
+ delete registry[id];
7932
+ pruned = true;
7933
+ continue;
7934
+ }
7935
+ if (entry.status !== "lost") {
7936
+ entry.status = "lost";
7937
+ pruned = true;
8434
7938
  }
8435
7939
  }
8436
7940
  if (pruned) {
@@ -8447,11 +7951,11 @@ var SessionRegistry = class {
8447
7951
  const retryDelayMs = 20;
8448
7952
  for (let attempt = 0; attempt < maxRetries; attempt++) {
8449
7953
  try {
8450
- await fs11.mkdir(path17.dirname(this.filePath), { recursive: true });
8451
- let lockHandle = await fs11.open(lockPath, "wx").catch(() => null);
7954
+ await fs12.mkdir(path17.dirname(this.filePath), { recursive: true });
7955
+ let lockHandle = await fs12.open(lockPath, "wx").catch(() => null);
8452
7956
  if (!lockHandle) {
8453
7957
  if (await this.breakStaleLock(lockPath)) {
8454
- lockHandle = await fs11.open(lockPath, "wx").catch(() => null);
7958
+ lockHandle = await fs12.open(lockPath, "wx").catch(() => null);
8455
7959
  }
8456
7960
  if (!lockHandle) {
8457
7961
  await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
@@ -8460,14 +7964,14 @@ var SessionRegistry = class {
8460
7964
  }
8461
7965
  try {
8462
7966
  await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
8463
- const raw = await fs11.readFile(this.filePath, "utf8").catch(() => "{}");
7967
+ const raw = await fs12.readFile(this.filePath, "utf8").catch(() => "{}");
8464
7968
  const registry = parseRegistry(raw);
8465
7969
  fn(registry);
8466
7970
  await this.writeAtomicLocked(registry);
8467
7971
  return;
8468
7972
  } finally {
8469
7973
  await lockHandle.close();
8470
- await fs11.unlink(lockPath).catch(() => void 0);
7974
+ await fs12.unlink(lockPath).catch(() => void 0);
8471
7975
  }
8472
7976
  } catch {
8473
7977
  return;
@@ -8484,16 +7988,16 @@ var SessionRegistry = class {
8484
7988
  async breakStaleLock(lockPath) {
8485
7989
  try {
8486
7990
  const [stat13, content] = await Promise.all([
8487
- fs11.stat(lockPath),
7991
+ fs12.stat(lockPath),
8488
7992
  /* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */
8489
- fs11.readFile(lockPath, "utf8").catch(() => "")
7993
+ fs12.readFile(lockPath, "utf8").catch(() => "")
8490
7994
  /* v8 ignore stop */
8491
7995
  ]);
8492
7996
  const ageMs = Date.now() - stat13.mtimeMs;
8493
7997
  const ownerPid = Number.parseInt(content.trim(), 10);
8494
7998
  const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
8495
7999
  if (ownerDead || ageMs > STALE_LOCK_MS) {
8496
- await fs11.unlink(lockPath).catch(() => void 0);
8000
+ await fs12.unlink(lockPath).catch(() => void 0);
8497
8001
  return true;
8498
8002
  }
8499
8003
  return false;
@@ -8516,16 +8020,16 @@ var SessionRegistry = class {
8516
8020
  `.${path17.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
8517
8021
  );
8518
8022
  try {
8519
- const handle = await fs11.open(tmp, "w");
8023
+ const handle = await fs12.open(tmp, "w");
8520
8024
  try {
8521
8025
  await handle.writeFile(JSON.stringify(registry, null, 2), "utf8");
8522
8026
  await handle.sync().catch(() => void 0);
8523
8027
  } finally {
8524
8028
  await handle.close();
8525
8029
  }
8526
- await fs11.rename(tmp, this.filePath);
8030
+ await fs12.rename(tmp, this.filePath);
8527
8031
  } catch (err) {
8528
- await fs11.unlink(tmp).catch(() => void 0);
8032
+ await fs12.unlink(tmp).catch(() => void 0);
8529
8033
  throw err;
8530
8034
  }
8531
8035
  }
@@ -8535,17 +8039,17 @@ var SessionRegistry = class {
8535
8039
  const base = path17.basename(this.filePath);
8536
8040
  const now = Date.now();
8537
8041
  const stale = [];
8538
- for (const name of await fs11.readdir(dir)) {
8042
+ for (const name of await fs12.readdir(dir)) {
8539
8043
  const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
8540
8044
  if (!isTemp) continue;
8541
- const stat13 = await fs11.stat(path17.join(dir, name)).catch(() => null);
8045
+ const stat13 = await fs12.stat(path17.join(dir, name)).catch(() => null);
8542
8046
  if (!stat13) continue;
8543
8047
  if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
8544
8048
  }
8545
8049
  stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
8546
8050
  await Promise.all(
8547
8051
  stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
8548
- await fs11.unlink(path17.join(dir, name)).catch(() => void 0);
8052
+ await fs12.unlink(path17.join(dir, name)).catch(() => void 0);
8549
8053
  })
8550
8054
  );
8551
8055
  } catch {
@@ -8961,7 +8465,7 @@ var AgentStatusTracker = class {
8961
8465
  };
8962
8466
 
8963
8467
  // src/fleet-notifier.ts
8964
- import * as fs12 from "node:fs/promises";
8468
+ import * as fs13 from "node:fs/promises";
8965
8469
  import * as path18 from "node:path";
8966
8470
  var INSTANCES_FILE = "webui-instances.json";
8967
8471
  var DISCOVERY_TTL_MS = 2500;
@@ -9024,7 +8528,7 @@ var FleetNotifier = class {
9024
8528
  }
9025
8529
  async discover() {
9026
8530
  try {
9027
- const raw = await fs12.readFile(path18.join(this.baseDir, INSTANCES_FILE), "utf8");
8531
+ const raw = await fs13.readFile(path18.join(this.baseDir, INSTANCES_FILE), "utf8");
9028
8532
  const data = JSON.parse(raw);
9029
8533
  const list = Array.isArray(data?.instances) ? data.instances : [];
9030
8534
  return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
@@ -9275,7 +8779,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
9275
8779
  return null;
9276
8780
  }
9277
8781
  }
9278
- async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
8782
+ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId, warn) {
9279
8783
  const t0 = Date.now();
9280
8784
  const payload = {
9281
8785
  version: 1,
@@ -9304,28 +8808,17 @@ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)
9304
8808
  error: toErrorMessage(err),
9305
8809
  recoverable: false
9306
8810
  });
9307
- console.warn(JSON.stringify({
9308
- level: "warn",
9309
- event: "todos_checkpoint.save_failed",
9310
- message: toErrorMessage(err),
9311
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9312
- }));
8811
+ (warn ?? ((m) => console.warn(JSON.stringify({ level: "warn", event: "todos_checkpoint.save_failed", message: m, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(toErrorMessage(err));
9313
8812
  }
9314
8813
  }
9315
- function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
8814
+ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId, warn) {
9316
8815
  let timer = null;
9317
8816
  let pending = null;
9318
8817
  let writeChain = Promise.resolve();
9319
8818
  const enqueueWrite = (todos) => {
9320
8819
  writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
9321
8820
  const msg = toErrorMessage(err);
9322
- console.error(JSON.stringify({
9323
- level: "error",
9324
- event: "todos_checkpoint.write_chain_failed",
9325
- sessionId,
9326
- message: msg,
9327
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9328
- }));
8821
+ (warn ?? ((m) => console.error(JSON.stringify({ level: "error", event: "todos_checkpoint.write_chain_failed", sessionId, message: m, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(msg);
9329
8822
  });
9330
8823
  return writeChain;
9331
8824
  };
@@ -9513,7 +9006,7 @@ async function loadPlan(filePath, events) {
9513
9006
  return null;
9514
9007
  }
9515
9008
  }
9516
- async function savePlan(filePath, plan, events) {
9009
+ async function savePlan(filePath, plan, events, warn) {
9517
9010
  const t0 = Date.now();
9518
9011
  try {
9519
9012
  await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
@@ -9535,10 +9028,14 @@ async function savePlan(filePath, plan, events) {
9535
9028
  error: toErrorMessage(err),
9536
9029
  recoverable: false
9537
9030
  });
9538
- console.warn(
9539
- "[plan-store] save failed:",
9540
- toErrorMessage(err)
9541
- );
9031
+ (warn ?? ((m) => console.warn(
9032
+ JSON.stringify({
9033
+ level: "warn",
9034
+ event: "plan_store.save_failed",
9035
+ message: m,
9036
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9037
+ })
9038
+ )))(toErrorMessage(err));
9542
9039
  return false;
9543
9040
  }
9544
9041
  }
@@ -9579,9 +9076,7 @@ function setPlanItemStatus(plan, idOrIndex, status) {
9579
9076
  const idx = matchIndex(plan, idOrIndex);
9580
9077
  if (idx === -1) return plan;
9581
9078
  const now = (/* @__PURE__ */ new Date()).toISOString();
9582
- const items = plan.items.map(
9583
- (it, i) => i === idx ? { ...it, status, updatedAt: now } : it
9584
- );
9079
+ const items = plan.items.map((it, i) => i === idx ? { ...it, status, updatedAt: now } : it);
9585
9080
  return { ...plan, items, updatedAt: now };
9586
9081
  }
9587
9082
  function clearPlan(plan) {
@@ -9836,7 +9331,7 @@ async function loadTasks(filePath, events, traceId) {
9836
9331
  return null;
9837
9332
  }
9838
9333
  }
9839
- async function saveTasks(filePath, tasks, events, traceId) {
9334
+ async function saveTasks(filePath, tasks, events, traceId, warn) {
9840
9335
  const t0 = Date.now();
9841
9336
  try {
9842
9337
  tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -9862,10 +9357,14 @@ async function saveTasks(filePath, tasks, events, traceId) {
9862
9357
  recoverable: false,
9863
9358
  ...traceId !== void 0 && { traceId }
9864
9359
  });
9865
- console.warn(
9866
- "[task-store] save failed:",
9867
- toErrorMessage(err)
9868
- );
9360
+ (warn ?? ((m) => console.warn(
9361
+ JSON.stringify({
9362
+ level: "warn",
9363
+ event: "task_store.save_failed",
9364
+ message: m,
9365
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9366
+ })
9367
+ )))(toErrorMessage(err));
9869
9368
  return false;
9870
9369
  }
9871
9370
  }
@@ -10093,7 +9592,7 @@ var MAX_JOURNAL_ENTRIES = 500;
10093
9592
  function goalFilePath(projectRoot) {
10094
9593
  return resolveWstackPaths({ projectRoot }).projectGoal;
10095
9594
  }
10096
- async function loadGoal(filePath, events) {
9595
+ async function loadGoal(filePath, events, warn) {
10097
9596
  const t0 = Date.now();
10098
9597
  let raw;
10099
9598
  try {
@@ -10124,13 +9623,9 @@ async function loadGoal(filePath, events) {
10124
9623
  try {
10125
9624
  const parsed = JSON.parse(raw);
10126
9625
  if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
10127
- console.warn(JSON.stringify({
10128
- level: "warn",
10129
- event: "goal_store.invalid_schema",
10130
- path: filePath,
10131
- message: "invalid schema \u2014 consider deleting and re-creating",
10132
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
10133
- }));
9626
+ (warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.invalid_schema", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
9627
+ "invalid schema \u2014 consider deleting and re-creating"
9628
+ );
10134
9629
  events?.emit("storage.read", {
10135
9630
  sessionId: "~boot~",
10136
9631
  store: "goal",
@@ -10152,13 +9647,9 @@ async function loadGoal(filePath, events) {
10152
9647
  });
10153
9648
  return parsed;
10154
9649
  } catch {
10155
- console.warn(JSON.stringify({
10156
- level: "warn",
10157
- event: "goal_store.parse_failed",
10158
- path: filePath,
10159
- message: "JSON parse failed \u2014 consider deleting and re-creating",
10160
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
10161
- }));
9650
+ (warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.parse_failed", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
9651
+ "JSON parse failed \u2014 consider deleting and re-creating"
9652
+ );
10162
9653
  events?.emit("storage.read", {
10163
9654
  sessionId: "~boot~",
10164
9655
  store: "goal",
@@ -10342,9 +9833,204 @@ function computeTrend(history) {
10342
9833
  return "steady";
10343
9834
  }
10344
9835
 
9836
+ // src/storage/goal-kanban.ts
9837
+ import {
9838
+ addColumn,
9839
+ addTask,
9840
+ createBoard,
9841
+ getBoard,
9842
+ listBoards,
9843
+ removeBoard
9844
+ } from "@wrongstack/kanban";
9845
+ var GOAL_BOARD_TAG_PREFIX = "goal:";
9846
+ var DEFAULT_COLUMNS = [
9847
+ { title: "Backlog", order: 0 },
9848
+ { title: "In Progress", order: 1 },
9849
+ { title: "Done", order: 2 }
9850
+ ];
9851
+ async function createGoalKanbanBoard(projectRoot, goalFile) {
9852
+ if (!projectRoot) return null;
9853
+ const existingId = goalFile.kanbanBoardId;
9854
+ if (existingId) {
9855
+ const existing = await getBoard(projectRoot, existingId).catch(() => null);
9856
+ if (existing) return existingId;
9857
+ }
9858
+ const displayGoal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, " ").trim();
9859
+ const titleSuffix = displayGoal.length > 80 ? displayGoal.slice(0, 77) + "\u2026" : displayGoal;
9860
+ const board = await createBoard(projectRoot, {
9861
+ title: `\u{1F3AF} ${titleSuffix}`,
9862
+ description: `Kanban board auto-created for goal: ${displayGoal}`,
9863
+ tags: [goalTag(goalFile)]
9864
+ });
9865
+ const backlogCol = board.columns.find(
9866
+ (c) => ["backlog", "todo", "open"].includes(c.id.toLowerCase())
9867
+ );
9868
+ const existingColumnIds = new Set(board.columns.map((c) => c.id));
9869
+ for (const col of DEFAULT_COLUMNS) {
9870
+ if (!existingColumnIds.has(col.title.toLowerCase().replace(/\s+/g, "-"))) {
9871
+ const result = await addColumn(projectRoot, board.id, { title: col.title });
9872
+ if (result?.column && backlogCol) {
9873
+ }
9874
+ }
9875
+ }
9876
+ const targetColumnId = board.columns[0]?.id ?? "backlog";
9877
+ if (goalFile.deliverables && goalFile.deliverables.length > 0) {
9878
+ for (const d of goalFile.deliverables) {
9879
+ const cleaned = d.replace(/^\[[x✓]\]|✅|\(done\)\s*/i, "").trim();
9880
+ if (cleaned) {
9881
+ await addTask(projectRoot, board.id, {
9882
+ title: cleaned,
9883
+ columnId: targetColumnId,
9884
+ description: `Deliverable for goal: ${displayGoal}`,
9885
+ priority: "medium"
9886
+ }).catch(() => {
9887
+ });
9888
+ }
9889
+ }
9890
+ }
9891
+ goalFile.kanbanBoardId = board.id;
9892
+ return board.id;
9893
+ }
9894
+ async function findGoalKanbanBoard(projectRoot, boardId) {
9895
+ if (!projectRoot || !boardId) return null;
9896
+ try {
9897
+ return await getBoard(projectRoot, boardId);
9898
+ } catch {
9899
+ return null;
9900
+ }
9901
+ }
9902
+ async function deleteGoalKanbanBoard(projectRoot, boardId) {
9903
+ if (!projectRoot || !boardId) return;
9904
+ await removeBoard(projectRoot, boardId).catch(() => {
9905
+ });
9906
+ }
9907
+ async function findGoalBoardByTag(projectRoot, goalFile) {
9908
+ if (!projectRoot) return null;
9909
+ const tag = goalTag(goalFile);
9910
+ const boards = await listBoards(projectRoot);
9911
+ const matched = boards.find((b) => b.tags?.includes(tag));
9912
+ if (!matched) return null;
9913
+ return getBoard(projectRoot, matched.id).catch(() => null);
9914
+ }
9915
+ function formatGoalKanbanPreview(goalFile, boardId, taskCount) {
9916
+ const lines = [];
9917
+ const displayGoal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, " ").trim();
9918
+ lines.push("");
9919
+ lines.push(color.bold(color.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")));
9920
+ lines.push(color.bold(color.cyan(" \u{1F3AF} GOAL KANBAN \u2014 Mission Board")));
9921
+ lines.push(color.bold(color.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")));
9922
+ lines.push("");
9923
+ lines.push(` ${color.bold("Mission:")} ${color.bold(displayGoal)}`);
9924
+ if (boardId) {
9925
+ lines.push(` ${color.dim(`Board: ${boardId.slice(0, 12)}\u2026`)}`);
9926
+ }
9927
+ if (typeof taskCount === "number") {
9928
+ lines.push(` ${color.dim(`${taskCount} task(s)`)}`);
9929
+ }
9930
+ lines.push("");
9931
+ const deliverables = goalFile.deliverables ?? [];
9932
+ const doneDeliverables = deliverables.filter(
9933
+ (d) => /^\[[x✓]\]|✅|\(done\)/i.test(d)
9934
+ ).length;
9935
+ const totalDeliverables = deliverables.length;
9936
+ if (totalDeliverables > 0) {
9937
+ const backlog = deliverables.filter(
9938
+ (d) => !/^\[[x✓]\]|✅|\(done\)|🔄|in.progress/i.test(d)
9939
+ );
9940
+ const inProgress = deliverables.filter((d) => /🔄|in.progress/i.test(d));
9941
+ const done = deliverables.filter((d) => /^\[[x✓]\]|✅|\(done\)/i.test(d));
9942
+ const renderColumn = (title, items, icon, accent) => {
9943
+ if (items.length === 0) {
9944
+ lines.push(` ${accent(color.bold(`${icon} ${title}`))} ${color.dim("(empty)")}`);
9945
+ return;
9946
+ }
9947
+ lines.push(` ${accent(color.bold(`${icon} ${title} (${items.length})`))}`);
9948
+ for (const item of items) {
9949
+ const cleaned = item.replace(/^\[[x✓]\]|✅|\(done\)|🔄|in.progress\s*/gi, "").trim();
9950
+ lines.push(` \u25CB ${cleaned}`);
9951
+ }
9952
+ };
9953
+ renderColumn("Backlog", backlog, "\u{1F4CB}", (s) => color.dim(s));
9954
+ renderColumn("In Progress", inProgress, "\u{1F504}", (s) => color.cyan(s));
9955
+ renderColumn("Done", done, "\u2705", (s) => color.green(s));
9956
+ lines.push("");
9957
+ lines.push(
9958
+ ` ${color.dim(`Progress: ${doneDeliverables}/${totalDeliverables} deliverables complete`)}`
9959
+ );
9960
+ }
9961
+ if (typeof goalFile.progress === "number") {
9962
+ const pct = Math.min(100, Math.max(0, Math.round(goalFile.progress)));
9963
+ const filled = Math.round(pct / 5);
9964
+ const empty = 20 - filled;
9965
+ const bar = color.green("\u2588".repeat(filled)) + color.dim("\u2591".repeat(empty));
9966
+ lines.push(` ${color.dim("Overall:")} ${bar} ${color.bold(`${pct}%`)}`);
9967
+ if (goalFile.progressNote) {
9968
+ lines.push(` ${color.dim(goalFile.progressNote)}`);
9969
+ }
9970
+ }
9971
+ if (typeof goalFile.progress === "undefined" && totalDeliverables > 0) {
9972
+ const pct = Math.round(doneDeliverables / totalDeliverables * 100);
9973
+ const filled = Math.round(pct / 5);
9974
+ const empty = 20 - filled;
9975
+ const bar = (pct > 0 ? color.green("\u2588".repeat(filled)) : "") + color.dim("\u2591".repeat(empty));
9976
+ lines.push(` ${color.dim("Overall:")} ${bar} ${color.bold(`${pct}%`)}`);
9977
+ }
9978
+ lines.push("");
9979
+ lines.push(color.bold(color.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")));
9980
+ return lines.join("\n");
9981
+ }
9982
+ function formatGoalEvent(goalFile, boardId) {
9983
+ const lines = [];
9984
+ lines.push("");
9985
+ lines.push(color.bold(color.green("\u250C\u2500\u2500\u2500 Goal Event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")));
9986
+ lines.push(color.bold(color.green("\u2502")) + " \u{1F3AF} Mission locked and ready");
9987
+ if (boardId) {
9988
+ lines.push(
9989
+ color.bold(color.green("\u2502")) + ` \u{1F4CB} Kanban board created: ${color.cyan(boardId.slice(0, 12) + "\u2026")}`
9990
+ );
9991
+ }
9992
+ const deliverables = goalFile.deliverables ?? [];
9993
+ lines.push(
9994
+ color.bold(color.green("\u2502")) + ` \u{1F4E6} ${deliverables.length} deliverable${deliverables.length !== 1 ? "s" : ""} extracted`
9995
+ );
9996
+ if (typeof goalFile.progress === "number") {
9997
+ lines.push(
9998
+ color.bold(color.green("\u2502")) + ` \u{1F4CA} Progress: ${goalFile.progress}%`
9999
+ );
10000
+ }
10001
+ lines.push(color.bold(color.green("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")));
10002
+ return lines.join("\n");
10003
+ }
10004
+ function formatGoalAutonomyChoice() {
10005
+ const lines = [];
10006
+ lines.push("");
10007
+ lines.push(color.bold(color.magenta("\u2550\u2550\u2550 Choose Autonomy Mode \u2550\u2550\u2550")));
10008
+ lines.push("");
10009
+ lines.push(
10010
+ ` ${color.bold("1")}. ${color.red("Autonomy Eternal")} ${color.dim("\u2014 Single-agent sense/decide/execute/reflect loop")}`
10011
+ );
10012
+ lines.push(
10013
+ ` ${color.bold("2")}. ${color.magenta("Eternal Parallel")} ${color.dim("\u2014 Multi-agent fan-out (4-8 subagents per tick)")}`
10014
+ );
10015
+ lines.push("");
10016
+ lines.push(` ${color.dim("Enter 1 or 2 (or press Enter to skip):")}`);
10017
+ return lines.join("\n");
10018
+ }
10019
+ function parseAutonomyChoice(input) {
10020
+ const trimmed = input.trim().toLowerCase();
10021
+ if (trimmed === "1" || trimmed === "eternal" || trimmed === "e") return "eternal";
10022
+ if (trimmed === "2" || trimmed === "parallel" || trimmed === "p") return "eternal-parallel";
10023
+ if (trimmed === "" || trimmed === "skip" || trimmed === "s" || trimmed === "0") return null;
10024
+ return null;
10025
+ }
10026
+ function goalTag(goalFile) {
10027
+ const goal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, "-").slice(0, 24).toLowerCase();
10028
+ return `${GOAL_BOARD_TAG_PREFIX}${goal}`;
10029
+ }
10030
+
10345
10031
  // src/storage/prompt-store.ts
10346
10032
  import { createHash as createHash7 } from "node:crypto";
10347
- import * as fs13 from "node:fs/promises";
10033
+ import * as fs14 from "node:fs/promises";
10348
10034
  import * as path20 from "node:path";
10349
10035
  var SCHEMA_VERSION = 2;
10350
10036
  function promptChecksum(content) {
@@ -10409,12 +10095,12 @@ var DefaultPromptStore = class {
10409
10095
  await ensureDir(this.dir);
10410
10096
  const entries = [];
10411
10097
  try {
10412
- const files = await fs13.readdir(this.dir);
10098
+ const files = await fs14.readdir(this.dir);
10413
10099
  for (const file of files) {
10414
10100
  if (!file.endsWith(".json")) continue;
10415
10101
  try {
10416
10102
  const raw = JSON.parse(
10417
- await fs13.readFile(path20.join(this.dir, file), "utf8")
10103
+ await fs14.readFile(path20.join(this.dir, file), "utf8")
10418
10104
  );
10419
10105
  const migrated = migratePromptEntry(raw.entry);
10420
10106
  if (migrated) entries.push(migrated);
@@ -10430,7 +10116,7 @@ var DefaultPromptStore = class {
10430
10116
  async get(id) {
10431
10117
  const file = path20.join(this.dir, `${id}.json`);
10432
10118
  try {
10433
- const raw = JSON.parse(await fs13.readFile(file, "utf8"));
10119
+ const raw = JSON.parse(await fs14.readFile(file, "utf8"));
10434
10120
  return migratePromptEntry(raw.entry);
10435
10121
  } catch {
10436
10122
  return null;
@@ -10445,7 +10131,7 @@ var DefaultPromptStore = class {
10445
10131
  async delete(id) {
10446
10132
  const file = path20.join(this.dir, `${id}.json`);
10447
10133
  try {
10448
- await fs13.unlink(file);
10134
+ await fs14.unlink(file);
10449
10135
  return true;
10450
10136
  } catch {
10451
10137
  return false;
@@ -10484,7 +10170,7 @@ var DefaultPromptStore = class {
10484
10170
  };
10485
10171
 
10486
10172
  // src/storage/prompt-usage-store.ts
10487
- import * as fs14 from "node:fs/promises";
10173
+ import * as fs15 from "node:fs/promises";
10488
10174
  import * as path21 from "node:path";
10489
10175
  var PromptUsageStore = class {
10490
10176
  constructor(file) {
@@ -10493,7 +10179,7 @@ var PromptUsageStore = class {
10493
10179
  file;
10494
10180
  async load() {
10495
10181
  try {
10496
- const raw = JSON.parse(await fs14.readFile(this.file, "utf8"));
10182
+ const raw = JSON.parse(await fs15.readFile(this.file, "utf8"));
10497
10183
  if (raw && typeof raw === "object" && raw.usage && typeof raw.usage === "object") {
10498
10184
  return raw.usage;
10499
10185
  }
@@ -10530,7 +10216,7 @@ var PromptUsageStore = class {
10530
10216
  };
10531
10217
 
10532
10218
  // src/storage/input-history-store.ts
10533
- import * as fs15 from "node:fs/promises";
10219
+ import * as fs16 from "node:fs/promises";
10534
10220
  import * as path22 from "node:path";
10535
10221
  var INPUT_HISTORY_DEFAULT_MAX = 100;
10536
10222
  var InputHistoryStore = class {
@@ -10553,7 +10239,7 @@ var InputHistoryStore = class {
10553
10239
  */
10554
10240
  async load() {
10555
10241
  try {
10556
- const raw = JSON.parse(await fs15.readFile(this.file, "utf8"));
10242
+ const raw = JSON.parse(await fs16.readFile(this.file, "utf8"));
10557
10243
  if (raw && typeof raw === "object" && Array.isArray(raw.entries) && raw.entries.every((e) => typeof e === "string")) {
10558
10244
  return raw.entries.slice(0, this.maxEntries);
10559
10245
  }
@@ -10610,7 +10296,7 @@ var InputHistoryStore = class {
10610
10296
  };
10611
10297
 
10612
10298
  // src/storage/cloud-sync.ts
10613
- import * as fs16 from "node:fs/promises";
10299
+ import * as fs17 from "node:fs/promises";
10614
10300
  import * as path23 from "node:path";
10615
10301
  import { createHash as createHash8 } from "node:crypto";
10616
10302
  var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
@@ -10694,7 +10380,7 @@ var CloudSync = class {
10694
10380
  lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
10695
10381
  localRev: rev
10696
10382
  };
10697
- await fs16.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
10383
+ await fs17.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
10698
10384
  this.state = syncState;
10699
10385
  return {
10700
10386
  ok: true,
@@ -10726,8 +10412,8 @@ var CloudSync = class {
10726
10412
  const rel = segments.slice(2).join("/");
10727
10413
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
10728
10414
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
10729
- await fs16.mkdir(path23.dirname(destPath), { recursive: true });
10730
- await fs16.writeFile(destPath, Buffer.from(blobData, "base64"));
10415
+ await fs17.mkdir(path23.dirname(destPath), { recursive: true });
10416
+ await fs17.writeFile(destPath, Buffer.from(blobData, "base64"));
10731
10417
  }
10732
10418
  const localRev = await this.hashLocalCategories(cfg.categories);
10733
10419
  const syncState = {
@@ -10736,7 +10422,7 @@ var CloudSync = class {
10736
10422
  lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
10737
10423
  localRev
10738
10424
  };
10739
- await fs16.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
10425
+ await fs17.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
10740
10426
  this.state = syncState;
10741
10427
  return {
10742
10428
  ok: true,
@@ -10755,7 +10441,7 @@ var CloudSync = class {
10755
10441
  }
10756
10442
  async loadState() {
10757
10443
  try {
10758
- const raw = await fs16.readFile(this.statePath, "utf8");
10444
+ const raw = await fs17.readFile(this.statePath, "utf8");
10759
10445
  this.state = JSON.parse(raw);
10760
10446
  } catch {
10761
10447
  this.state = null;
@@ -10833,17 +10519,17 @@ var CloudSync = class {
10833
10519
  const localPath = this.categoryToPath(cat);
10834
10520
  if (!localPath) continue;
10835
10521
  try {
10836
- const stat13 = await fs16.stat(localPath);
10522
+ const stat13 = await fs17.stat(localPath);
10837
10523
  if (stat13.isDirectory()) {
10838
10524
  const files = await this.walkDir(localPath, localPath);
10839
10525
  for (const file of files) {
10840
- const content = await fs16.readFile(file, "utf8");
10526
+ const content = await fs17.readFile(file, "utf8");
10841
10527
  const rel = path23.relative(localPath, file).replace(/\\/g, "/");
10842
10528
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
10843
10529
  hashes.push(content);
10844
10530
  }
10845
10531
  } else {
10846
- const content = await fs16.readFile(localPath, "utf8");
10532
+ const content = await fs17.readFile(localPath, "utf8");
10847
10533
  entries.push({ path: `data/${cat}`, content, mode: "100644" });
10848
10534
  hashes.push(content);
10849
10535
  }
@@ -10859,15 +10545,15 @@ var CloudSync = class {
10859
10545
  const localPath = this.categoryToPath(cat);
10860
10546
  if (!localPath) continue;
10861
10547
  try {
10862
- const stat13 = await fs16.stat(localPath);
10548
+ const stat13 = await fs17.stat(localPath);
10863
10549
  if (stat13.isDirectory()) {
10864
10550
  const files = await this.walkDir(localPath, localPath);
10865
10551
  for (const file of files) {
10866
- const content = await fs16.readFile(file);
10552
+ const content = await fs17.readFile(file);
10867
10553
  hashes.push(content.toString("base64") + file);
10868
10554
  }
10869
10555
  } else {
10870
- const content = await fs16.readFile(localPath);
10556
+ const content = await fs17.readFile(localPath);
10871
10557
  hashes.push(content.toString("base64") + localPath);
10872
10558
  }
10873
10559
  } catch {
@@ -10894,7 +10580,7 @@ var CloudSync = class {
10894
10580
  }
10895
10581
  async walkDir(dir, base) {
10896
10582
  const results = [];
10897
- const entries = await fs16.readdir(dir, { withFileTypes: true });
10583
+ const entries = await fs17.readdir(dir, { withFileTypes: true });
10898
10584
  for (const entry of entries) {
10899
10585
  const full = path23.join(dir, entry.name);
10900
10586
  if (entry.isDirectory()) {
@@ -11081,7 +10767,6 @@ export {
11081
10767
  DefaultAttachmentStore,
11082
10768
  DefaultConfigLoader,
11083
10769
  DefaultConfigStore,
11084
- DefaultMemoryStore,
11085
10770
  DefaultPromptStore,
11086
10771
  DefaultSessionReader,
11087
10772
  DefaultSessionRewinder,
@@ -11110,12 +10795,19 @@ export {
11110
10795
  attachPlanCheckpoint,
11111
10796
  attachTodosCheckpoint,
11112
10797
  clearPlan,
10798
+ createGoalKanbanBoard,
11113
10799
  createSessionEventBridge,
10800
+ deleteGoalKanbanBoard,
11114
10801
  deriveTodosFromPlanItem,
11115
10802
  emptyGoal,
11116
10803
  emptyPlan,
11117
10804
  emptyTaskFile,
10805
+ findGoalBoardByTag,
10806
+ findGoalKanbanBoard,
11118
10807
  formatGoal,
10808
+ formatGoalAutonomyChoice,
10809
+ formatGoalEvent,
10810
+ formatGoalKanbanPreview,
11119
10811
  formatPlan,
11120
10812
  formatPlanTemplates,
11121
10813
  generateSessionId,
@@ -11133,6 +10825,7 @@ export {
11133
10825
  migratePromptEntry,
11134
10826
  mutatePlan,
11135
10827
  mutateTasks,
10828
+ parseAutonomyChoice,
11136
10829
  parseEntries,
11137
10830
  parseProgressFromText,
11138
10831
  promptChecksum,