@wrongstack/core 0.287.0 → 0.291.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 (339) hide show
  1. package/dist/agent-status-tracker.d.ts +13 -0
  2. package/dist/agent-status-tracker.d.ts.map +1 -1
  3. package/dist/boot.d.ts.map +1 -1
  4. package/dist/chronicle/context.d.ts +13 -0
  5. package/dist/chronicle/context.d.ts.map +1 -0
  6. package/dist/chronicle/decision-adapter.d.ts +13 -0
  7. package/dist/chronicle/decision-adapter.d.ts.map +1 -0
  8. package/dist/chronicle/domain-adapter.d.ts +13 -0
  9. package/dist/chronicle/domain-adapter.d.ts.map +1 -0
  10. package/dist/chronicle/file-observer.d.ts +20 -0
  11. package/dist/chronicle/file-observer.d.ts.map +1 -0
  12. package/dist/chronicle/health-monitor.d.ts +11 -0
  13. package/dist/chronicle/health-monitor.d.ts.map +1 -0
  14. package/dist/chronicle/identity.d.ts +18 -0
  15. package/dist/chronicle/identity.d.ts.map +1 -0
  16. package/dist/chronicle/index.d.ts +16 -0
  17. package/dist/chronicle/index.d.ts.map +1 -0
  18. package/dist/chronicle/index.js +2555 -0
  19. package/dist/chronicle/index.js.map +7 -0
  20. package/dist/chronicle/journal.d.ts +81 -0
  21. package/dist/chronicle/journal.d.ts.map +1 -0
  22. package/dist/chronicle/process-adapter.d.ts +14 -0
  23. package/dist/chronicle/process-adapter.d.ts.map +1 -0
  24. package/dist/chronicle/prompt-manifest.d.ts +34 -0
  25. package/dist/chronicle/prompt-manifest.d.ts.map +1 -0
  26. package/dist/chronicle/provider-adapter.d.ts +13 -0
  27. package/dist/chronicle/provider-adapter.d.ts.map +1 -0
  28. package/dist/chronicle/query.d.ts +108 -0
  29. package/dist/chronicle/query.d.ts.map +1 -0
  30. package/dist/chronicle/rollup-adapter.d.ts +14 -0
  31. package/dist/chronicle/rollup-adapter.d.ts.map +1 -0
  32. package/dist/chronicle/stream-adapter.d.ts +13 -0
  33. package/dist/chronicle/stream-adapter.d.ts.map +1 -0
  34. package/dist/chronicle/tool-adapter.d.ts +15 -0
  35. package/dist/chronicle/tool-adapter.d.ts.map +1 -0
  36. package/dist/chronicle/types.d.ts +82 -0
  37. package/dist/chronicle/types.d.ts.map +1 -0
  38. package/dist/coordination/agent-monitor.d.ts +40 -0
  39. package/dist/coordination/agent-monitor.d.ts.map +1 -1
  40. package/dist/coordination/agent-subagent-runner.d.ts +7 -1
  41. package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
  42. package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
  43. package/dist/coordination/autonomous-coordinator.d.ts +2 -0
  44. package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
  45. package/dist/coordination/brain-monitor.d.ts +1 -1
  46. package/dist/coordination/brain.d.ts +3 -2
  47. package/dist/coordination/brain.d.ts.map +1 -1
  48. package/dist/coordination/collab-debug.d.ts +0 -9
  49. package/dist/coordination/collab-debug.d.ts.map +1 -1
  50. package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
  51. package/dist/coordination/dep-watcher.d.ts +2 -1
  52. package/dist/coordination/dep-watcher.d.ts.map +1 -1
  53. package/dist/coordination/director/director-collab.d.ts +1 -1
  54. package/dist/coordination/director/director-collab.d.ts.map +1 -1
  55. package/dist/coordination/director-tools.d.ts.map +1 -1
  56. package/dist/coordination/director.d.ts +10 -1
  57. package/dist/coordination/director.d.ts.map +1 -1
  58. package/dist/coordination/file-author-tracker.d.ts.map +1 -1
  59. package/dist/coordination/fleet-bus.d.ts.map +1 -1
  60. package/dist/coordination/fleet-event-validation.d.ts +0 -7
  61. package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
  62. package/dist/coordination/fleet-spawn.d.ts.map +1 -1
  63. package/dist/coordination/fleet-supervisor.d.ts +1 -1
  64. package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
  65. package/dist/coordination/fleet.d.ts +0 -44
  66. package/dist/coordination/fleet.d.ts.map +1 -1
  67. package/dist/coordination/global-mailbox.d.ts +7 -1
  68. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  69. package/dist/coordination/index.d.ts +5 -3
  70. package/dist/coordination/index.d.ts.map +1 -1
  71. package/dist/coordination/index.js +2426 -299
  72. package/dist/coordination/index.js.map +4 -4
  73. package/dist/coordination/knowledge-graph.d.ts +2 -1
  74. package/dist/coordination/knowledge-graph.d.ts.map +1 -1
  75. package/dist/coordination/large-answer-store.d.ts +0 -6
  76. package/dist/coordination/large-answer-store.d.ts.map +1 -1
  77. package/dist/coordination/mail-tools.d.ts +2 -2
  78. package/dist/coordination/mail-tools.d.ts.map +1 -1
  79. package/dist/coordination/mailbox-hooks.d.ts +2 -0
  80. package/dist/coordination/mailbox-hooks.d.ts.map +1 -1
  81. package/dist/coordination/mailbox-http-router.d.ts +43 -0
  82. package/dist/coordination/mailbox-http-router.d.ts.map +1 -0
  83. package/dist/coordination/mailbox-message-codec.d.ts +35 -0
  84. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
  85. package/dist/coordination/mailbox-tool.d.ts +1 -1
  86. package/dist/coordination/mailbox-tool.d.ts.map +1 -1
  87. package/dist/coordination/mailbox-types.d.ts +170 -10
  88. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  89. package/dist/coordination/mailbox.d.ts +1 -1
  90. package/dist/coordination/mailbox.d.ts.map +1 -1
  91. package/dist/coordination/package-author-tracker.d.ts.map +1 -1
  92. package/dist/coordination/provider-status-tracker.d.ts +236 -0
  93. package/dist/coordination/provider-status-tracker.d.ts.map +1 -0
  94. package/dist/coordination/subagent-budget.d.ts +2 -1
  95. package/dist/coordination/subagent-budget.d.ts.map +1 -1
  96. package/dist/core/agent-loop.d.ts +8 -3
  97. package/dist/core/agent-loop.d.ts.map +1 -1
  98. package/dist/core/agent-response.d.ts +5 -4
  99. package/dist/core/agent-response.d.ts.map +1 -1
  100. package/dist/core/agent-tools.d.ts +1 -1
  101. package/dist/core/agent-tools.d.ts.map +1 -1
  102. package/dist/core/agent.d.ts +4 -2
  103. package/dist/core/agent.d.ts.map +1 -1
  104. package/dist/core/context.d.ts +55 -1
  105. package/dist/core/context.d.ts.map +1 -1
  106. package/dist/core/fallback-model.d.ts +14 -8
  107. package/dist/core/fallback-model.d.ts.map +1 -1
  108. package/dist/core/fallback-profile-manager.d.ts +118 -0
  109. package/dist/core/fallback-profile-manager.d.ts.map +1 -0
  110. package/dist/core/fleet-pulse.d.ts +2 -1
  111. package/dist/core/fleet-pulse.d.ts.map +1 -1
  112. package/dist/core/iteration-limit.d.ts +2 -1
  113. package/dist/core/iteration-limit.d.ts.map +1 -1
  114. package/dist/core/mailbox-loop.d.ts +16 -1
  115. package/dist/core/mailbox-loop.d.ts.map +1 -1
  116. package/dist/core/model-availability-calendar.d.ts +27 -0
  117. package/dist/core/model-availability-calendar.d.ts.map +1 -0
  118. package/dist/core/provider-runner.d.ts.map +1 -1
  119. package/dist/core/system-prompt-builder.d.ts +2 -1
  120. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  121. package/dist/defaults/index.js +1920 -598
  122. package/dist/defaults/index.js.map +4 -4
  123. package/dist/execution/auto-compaction-middleware.d.ts +3 -2
  124. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  125. package/dist/execution/compaction-core.d.ts.map +1 -1
  126. package/dist/execution/council-brain.d.ts +28 -49
  127. package/dist/execution/council-brain.d.ts.map +1 -1
  128. package/dist/execution/council-orchestrator.d.ts +30 -0
  129. package/dist/execution/council-orchestrator.d.ts.map +1 -1
  130. package/dist/execution/enhance-recovery.d.ts +8 -0
  131. package/dist/execution/enhance-recovery.d.ts.map +1 -1
  132. package/dist/execution/error-handler.d.ts +2 -2
  133. package/dist/execution/error-handler.d.ts.map +1 -1
  134. package/dist/execution/eternal-autonomy.d.ts.map +1 -1
  135. package/dist/execution/index.js +2600 -1903
  136. package/dist/execution/index.js.map +4 -4
  137. package/dist/execution/intelligent-compactor.d.ts +0 -16
  138. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  139. package/dist/execution/model-runtime.d.ts.map +1 -1
  140. package/dist/execution/one-shot-llm.d.ts +9 -1
  141. package/dist/execution/one-shot-llm.d.ts.map +1 -1
  142. package/dist/execution/prompt-enhancer.d.ts +36 -0
  143. package/dist/execution/prompt-enhancer.d.ts.map +1 -1
  144. package/dist/execution/prompt-enhancer.js +158 -10
  145. package/dist/execution/prompt-enhancer.js.map +2 -2
  146. package/dist/execution/retry-policy.d.ts +0 -13
  147. package/dist/execution/retry-policy.d.ts.map +1 -1
  148. package/dist/execution/skill-loader.d.ts.map +1 -1
  149. package/dist/execution/tool-executor.d.ts.map +1 -1
  150. package/dist/goal/checkpoint.d.ts.map +1 -0
  151. package/dist/{autophase/auto-phase-planner.d.ts → goal/goal-planner.d.ts} +8 -8
  152. package/dist/goal/goal-planner.d.ts.map +1 -0
  153. package/dist/{autophase/auto-phase-runner.d.ts → goal/goal-runner.d.ts} +10 -10
  154. package/dist/goal/goal-runner.d.ts.map +1 -0
  155. package/dist/{autophase → goal}/index.d.ts +3 -3
  156. package/dist/goal/index.d.ts.map +1 -0
  157. package/dist/goal/phase-graph-builder.d.ts.map +1 -0
  158. package/dist/{autophase → goal}/phase-orchestrator.d.ts +2 -2
  159. package/dist/goal/phase-orchestrator.d.ts.map +1 -0
  160. package/dist/goal/phase-store.d.ts.map +1 -0
  161. package/dist/{autophase → goal}/types.d.ts +2 -2
  162. package/dist/goal/types.d.ts.map +1 -0
  163. package/dist/hq/auth-store.d.ts.map +1 -1
  164. package/dist/hq/commands.d.ts +2 -1
  165. package/dist/hq/commands.d.ts.map +1 -1
  166. package/dist/hq/exposure.d.ts +66 -0
  167. package/dist/hq/exposure.d.ts.map +1 -0
  168. package/dist/hq/index.d.ts +1 -0
  169. package/dist/hq/index.d.ts.map +1 -1
  170. package/dist/hq/index.js +190 -21
  171. package/dist/hq/index.js.map +4 -4
  172. package/dist/hq/mailbox-mapper.d.ts +13 -1
  173. package/dist/hq/mailbox-mapper.d.ts.map +1 -1
  174. package/dist/hq/protocol/browser.d.ts +9 -1
  175. package/dist/hq/protocol/browser.d.ts.map +1 -1
  176. package/dist/hq/protocol/client.d.ts +2 -2
  177. package/dist/hq/protocol/client.d.ts.map +1 -1
  178. package/dist/hq/protocol/core.d.ts.map +1 -1
  179. package/dist/hq/protocol/mailbox.d.ts +6 -0
  180. package/dist/hq/protocol/mailbox.d.ts.map +1 -1
  181. package/dist/hq/protocol/session.d.ts +4 -0
  182. package/dist/hq/protocol/session.d.ts.map +1 -1
  183. package/dist/hq/publisher.d.ts.map +1 -1
  184. package/dist/index.d.ts +34 -25
  185. package/dist/index.d.ts.map +1 -1
  186. package/dist/index.js +30287 -22214
  187. package/dist/index.js.map +4 -4
  188. package/dist/infrastructure/index.js +60 -5
  189. package/dist/infrastructure/index.js.map +2 -2
  190. package/dist/kernel/events/agent-events.d.ts +33 -1
  191. package/dist/kernel/events/agent-events.d.ts.map +1 -1
  192. package/dist/kernel/events/file-events.d.ts +65 -0
  193. package/dist/kernel/events/file-events.d.ts.map +1 -0
  194. package/dist/kernel/events/memory-events.d.ts +36 -0
  195. package/dist/kernel/events/memory-events.d.ts.map +1 -1
  196. package/dist/kernel/events/network-events.d.ts +60 -0
  197. package/dist/kernel/events/network-events.d.ts.map +1 -0
  198. package/dist/kernel/events/process-events.d.ts +44 -0
  199. package/dist/kernel/events/process-events.d.ts.map +1 -0
  200. package/dist/kernel/events/provider-events.d.ts +89 -0
  201. package/dist/kernel/events/provider-events.d.ts.map +1 -1
  202. package/dist/kernel/events/tool-events.d.ts +10 -0
  203. package/dist/kernel/events/tool-events.d.ts.map +1 -1
  204. package/dist/kernel/events/worktree-events.d.ts +1 -1
  205. package/dist/kernel/events.d.ts +14 -1
  206. package/dist/kernel/events.d.ts.map +1 -1
  207. package/dist/kernel/index.js +2 -1
  208. package/dist/kernel/index.js.map +2 -2
  209. package/dist/kernel/tokens.d.ts +3 -1
  210. package/dist/kernel/tokens.d.ts.map +1 -1
  211. package/dist/mailbox-attach.d.ts.map +1 -1
  212. package/dist/models/index.js.map +2 -2
  213. package/dist/observability/index.d.ts +3 -0
  214. package/dist/observability/index.d.ts.map +1 -1
  215. package/dist/observability/index.js +278 -0
  216. package/dist/observability/index.js.map +4 -4
  217. package/dist/observability/network-telemetry.d.ts +15 -0
  218. package/dist/observability/network-telemetry.d.ts.map +1 -0
  219. package/dist/observability/process-telemetry.d.ts +18 -0
  220. package/dist/observability/process-telemetry.d.ts.map +1 -0
  221. package/dist/observability/redact-command.d.ts +20 -0
  222. package/dist/observability/redact-command.d.ts.map +1 -0
  223. package/dist/plugins/auto-review-plugin.d.ts +56 -3
  224. package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
  225. package/dist/plugins/chimera-plugin.d.ts +164 -5
  226. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  227. package/dist/plugins/review-context-builder.d.ts +52 -0
  228. package/dist/plugins/review-context-builder.d.ts.map +1 -0
  229. package/dist/security/index.d.ts +1 -0
  230. package/dist/security/index.d.ts.map +1 -1
  231. package/dist/security/index.js +186 -9
  232. package/dist/security/index.js.map +4 -4
  233. package/dist/security/kanban-boundary.d.ts +10 -0
  234. package/dist/security/kanban-boundary.d.ts.map +1 -0
  235. package/dist/security/permission-policy.d.ts +1 -0
  236. package/dist/security/permission-policy.d.ts.map +1 -1
  237. package/dist/session-registry.d.ts +80 -0
  238. package/dist/session-registry.d.ts.map +1 -1
  239. package/dist/skills/index.js.map +2 -2
  240. package/dist/storage/config-loader.d.ts +7 -0
  241. package/dist/storage/config-loader.d.ts.map +1 -1
  242. package/dist/storage/file-session-writer.d.ts +20 -7
  243. package/dist/storage/file-session-writer.d.ts.map +1 -1
  244. package/dist/storage/goal-coordination.d.ts +40 -0
  245. package/dist/storage/goal-coordination.d.ts.map +1 -0
  246. package/dist/storage/goal-store.d.ts +9 -5
  247. package/dist/storage/goal-store.d.ts.map +1 -1
  248. package/dist/storage/index.d.ts +4 -2
  249. package/dist/storage/index.d.ts.map +1 -1
  250. package/dist/storage/index.js +1066 -226
  251. package/dist/storage/index.js.map +4 -4
  252. package/dist/storage/memory-consolidator.d.ts +49 -4
  253. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  254. package/dist/storage/memory-graph-backend.d.ts +11 -0
  255. package/dist/storage/memory-graph-backend.d.ts.map +1 -1
  256. package/dist/storage/provider-config-watcher.d.ts +19 -0
  257. package/dist/storage/provider-config-watcher.d.ts.map +1 -1
  258. package/dist/storage/session-event-bridge.d.ts +2 -2
  259. package/dist/storage/session-event-bridge.d.ts.map +1 -1
  260. package/dist/storage/session-resume-validation.d.ts +10 -0
  261. package/dist/storage/session-resume-validation.d.ts.map +1 -1
  262. package/dist/storage/session-rewind-apply.d.ts +53 -0
  263. package/dist/storage/session-rewind-apply.d.ts.map +1 -0
  264. package/dist/storage/session-rewinder.d.ts.map +1 -1
  265. package/dist/storage/session-store.d.ts.map +1 -1
  266. package/dist/tasking/index.js.map +1 -1
  267. package/dist/tasking/task-store.d.ts +1 -1
  268. package/dist/tools/council-tool.d.ts +3 -0
  269. package/dist/tools/council-tool.d.ts.map +1 -1
  270. package/dist/tools/fallback-manage-tools.d.ts +82 -0
  271. package/dist/tools/fallback-manage-tools.d.ts.map +1 -0
  272. package/dist/tools/index.d.ts +1 -0
  273. package/dist/tools/index.d.ts.map +1 -1
  274. package/dist/tools/index.js +3423 -192
  275. package/dist/tools/index.js.map +4 -4
  276. package/dist/tools/one-shot-llm-tool.d.ts +2 -0
  277. package/dist/tools/one-shot-llm-tool.d.ts.map +1 -1
  278. package/dist/types/config.d.ts +76 -7
  279. package/dist/types/config.d.ts.map +1 -1
  280. package/dist/types/file-event-record.d.ts +68 -0
  281. package/dist/types/file-event-record.d.ts.map +1 -0
  282. package/dist/types/index.d.ts +1 -0
  283. package/dist/types/index.d.ts.map +1 -1
  284. package/dist/types/index.js +13 -8
  285. package/dist/types/index.js.map +3 -3
  286. package/dist/types/memory.d.ts +12 -0
  287. package/dist/types/memory.d.ts.map +1 -1
  288. package/dist/types/multi-agent.d.ts +12 -1
  289. package/dist/types/multi-agent.d.ts.map +1 -1
  290. package/dist/types/one-shot-llm.d.ts +16 -7
  291. package/dist/types/one-shot-llm.d.ts.map +1 -1
  292. package/dist/types/provider.d.ts +1 -1
  293. package/dist/types/provider.d.ts.map +1 -1
  294. package/dist/types/session.d.ts +68 -2
  295. package/dist/types/session.d.ts.map +1 -1
  296. package/dist/types/tool-executor.d.ts +2 -0
  297. package/dist/types/tool-executor.d.ts.map +1 -1
  298. package/dist/types/tool.d.ts +5 -0
  299. package/dist/types/tool.d.ts.map +1 -1
  300. package/dist/utils/atomic-write.d.ts.map +1 -1
  301. package/dist/utils/index.d.ts +10 -10
  302. package/dist/utils/index.d.ts.map +1 -1
  303. package/dist/utils/index.js +497 -254
  304. package/dist/utils/index.js.map +4 -4
  305. package/dist/utils/message-invariants.d.ts +9 -0
  306. package/dist/utils/message-invariants.d.ts.map +1 -1
  307. package/dist/utils/safe-json.d.ts +1 -1
  308. package/dist/utils/safe-json.d.ts.map +1 -1
  309. package/dist/utils/term.d.ts +279 -0
  310. package/dist/utils/term.d.ts.map +1 -1
  311. package/dist/utils/tool-wire-compact.d.ts +9 -0
  312. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  313. package/dist/utils/wstack-paths.d.ts +12 -2
  314. package/dist/utils/wstack-paths.d.ts.map +1 -1
  315. package/dist/worktree/worktree-manager.d.ts +1 -1
  316. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  317. package/instructions/agents/ios.md +113 -0
  318. package/instructions/agents/search.md +1 -1
  319. package/instructions/llm/chimera-review.md +66 -9
  320. package/instructions/llm/memory-consolidator.md +4 -12
  321. package/instructions/llm/prompt-enhancer.md +3 -1
  322. package/instructions/sections/tool/common-patterns.md +3 -3
  323. package/instructions/system.md +40 -5
  324. package/package.json +7 -3
  325. package/skills/auto-review/SKILL.md +176 -0
  326. package/skills/mnemosyne/SKILL.md +156 -0
  327. package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
  328. package/dist/autophase/auto-phase-planner.d.ts.map +0 -1
  329. package/dist/autophase/auto-phase-runner.d.ts.map +0 -1
  330. package/dist/autophase/checkpoint.d.ts.map +0 -1
  331. package/dist/autophase/index.d.ts.map +0 -1
  332. package/dist/autophase/phase-graph-builder.d.ts.map +0 -1
  333. package/dist/autophase/phase-orchestrator.d.ts.map +0 -1
  334. package/dist/autophase/phase-store.d.ts.map +0 -1
  335. package/dist/autophase/types.d.ts.map +0 -1
  336. /package/dist/{autophase → goal}/checkpoint.d.ts +0 -0
  337. /package/dist/{autophase → goal}/phase-graph-builder.d.ts +0 -0
  338. /package/dist/{autophase → goal}/phase-store.d.ts +0 -0
  339. /package/instructions/{autophase → goal}/phase-planner.md +0 -0
@@ -147,6 +147,12 @@ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
147
147
  function isStdoutTTY() {
148
148
  return hasStdout() && Boolean(process.stdout.isTTY);
149
149
  }
150
+ var ESCAPE_TERMINATOR = Object.freeze({
151
+ BEL: "\x07",
152
+ // OSC 0 / OSC 2 (title)
153
+ ST: "\x1B\\"
154
+ // DCS / strict OSC
155
+ });
150
156
 
151
157
  // src/utils/color.ts
152
158
  var isColorTty = () => {
@@ -255,6 +261,16 @@ function expectDefined(value, label) {
255
261
  }
256
262
 
257
263
  // src/utils/message-invariants.ts
264
+ function hasMeaningfulContent(content) {
265
+ if (typeof content === "string") return content.trim().length > 0;
266
+ return content.some((block) => {
267
+ if (block.type === "text") return block.text.trim().length > 0;
268
+ if (block.type === "thinking") {
269
+ return block.thinking.trim().length > 0 || Boolean(block.signature);
270
+ }
271
+ return true;
272
+ });
273
+ }
258
274
  function repairToolUseAdjacency(messages) {
259
275
  const removedToolUses = [];
260
276
  const removedToolResults = [];
@@ -342,8 +358,7 @@ function mapContent(msg, fn) {
342
358
  return { ...msg, content: next };
343
359
  }
344
360
  function isEmptyMessage(msg) {
345
- if (typeof msg.content === "string") return msg.content.trim().length === 0;
346
- return msg.content.length === 0;
361
+ return !hasMeaningfulContent(msg.content);
347
362
  }
348
363
 
349
364
  // src/utils/regex-guard.ts
@@ -384,7 +399,7 @@ function compileUserRegex(pattern, flags) {
384
399
 
385
400
  // src/utils/safe-json.ts
386
401
  function safeParse(input, maxBytes = 5e6) {
387
- if (input.length > maxBytes) {
402
+ if (Buffer.byteLength(input, "utf8") > maxBytes) {
388
403
  return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
389
404
  }
390
405
  try {
@@ -504,6 +519,11 @@ function resolveWstackPaths(opts) {
504
519
  homeDir,
505
520
  configDir: globalRoot,
506
521
  globalConfig: path2.join(globalRoot, "config.json"),
522
+ profilesDir: path2.join(globalRoot, "profiles"),
523
+ profileConfig: (name) => {
524
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
525
+ return path2.join(globalRoot, "profiles", safe || "default", "config.json");
526
+ },
507
527
  secretsKey: path2.join(globalRoot, ".key"),
508
528
  globalMemory: path2.join(globalRoot, "memory.md"),
509
529
  globalSkills: path2.join(globalRoot, "skills"),
@@ -731,7 +751,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
731
751
  const dir = path3.dirname(targetPath);
732
752
  await fs2.mkdir(dir, { recursive: true });
733
753
  const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
734
- const timeoutMs = opts.timeoutMs ?? 5e3;
754
+ const timeoutMs = opts.timeoutMs ?? 15e3;
735
755
  const staleMs = opts.staleMs ?? 3e4;
736
756
  const started = Date.now();
737
757
  let handle;
@@ -741,6 +761,13 @@ async function withFileLock(targetPath, fn, opts = {}) {
741
761
  await handle.writeFile(`${process.pid}:${Date.now()}`);
742
762
  break;
743
763
  } catch (err) {
764
+ if (handle) {
765
+ await handle.close().catch(() => {
766
+ });
767
+ await fs2.unlink(lockPath).catch(() => {
768
+ });
769
+ handle = void 0;
770
+ }
744
771
  const code = err.code;
745
772
  if (code === "ENOENT") {
746
773
  await fs2.mkdir(dir, { recursive: true });
@@ -961,24 +988,24 @@ var FileSessionWriter = class _FileSessionWriter {
961
988
  /**
962
989
  * Scrub secrets out of conversation-turn events before they are observed
963
990
  * for the summary, written to the JSONL log, or surfaced on resume. Only
964
- * `user_input`, `llm_response`, and `context_snapshot` carry free-form
965
- * user/model text; other event types either have no secret-bearing content
966
- * or are already scrubbed upstream (tool results). Snapshot normalization
967
- * also strips transient token-cache fields even when no scrubber is set.
991
+ * Conversation events carry free-form user/model text. Snapshot and exact
992
+ * message-journal normalization also strips transient token-cache fields
993
+ * even when no scrubber is set.
968
994
  */
969
995
  scrubEvent(event) {
970
996
  const s = this.secretScrubber;
971
- if (event.type === "context_snapshot") {
997
+ const persistMessage = (message) => {
998
+ const { _estTokens: _ignored, ...persisted } = message;
972
999
  return {
973
- ...event,
974
- messages: event.messages.map((message) => {
975
- const { _estTokens: _ignored, ...persisted } = message;
976
- return {
977
- ...persisted,
978
- content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
979
- };
980
- })
1000
+ ...persisted,
1001
+ content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
981
1002
  };
1003
+ };
1004
+ if (event.type === "context_snapshot" || event.type === "messages_replaced") {
1005
+ return { ...event, messages: event.messages.map(persistMessage) };
1006
+ }
1007
+ if (event.type === "message_appended" || event.type === "message_updated") {
1008
+ return { ...event, message: persistMessage(event.message) };
982
1009
  }
983
1010
  if (!s) return event;
984
1011
  if (event.type === "user_input") {
@@ -1033,7 +1060,8 @@ var FileSessionWriter = class _FileSessionWriter {
1033
1060
  this.bufferSynchronousEvent(event);
1034
1061
  }
1035
1062
  recordFileObservation(input) {
1036
- if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs)) return;
1063
+ if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs))
1064
+ return;
1037
1065
  this.bufferSynchronousEvent({
1038
1066
  type: "file_observation",
1039
1067
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1195,12 +1223,7 @@ var FileSessionWriter = class _FileSessionWriter {
1195
1223
  const now = Date.now();
1196
1224
  if (now - this.lastAppendWarnAt > 5e3) {
1197
1225
  const suppressed = this.appendFailCount - 1;
1198
- const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
1199
- console.warn(
1200
- "[session] flush failed:",
1201
- toErrorMessage(err),
1202
- tail
1203
- );
1226
+ console.warn(JSON.stringify({ level: "warn", event: "session.flush_failed", sessionId: this.id, message: toErrorMessage(err), ...suppressed > 0 ? { suppressed } : {}, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
1204
1227
  this.lastAppendWarnAt = now;
1205
1228
  this.appendFailCount = 0;
1206
1229
  }
@@ -1220,6 +1243,43 @@ var FileSessionWriter = class _FileSessionWriter {
1220
1243
  });
1221
1244
  }
1222
1245
  }
1246
+ /**
1247
+ * Rebuild every accumulated summary counter by replaying the JSONL as it now
1248
+ * stands on disk.
1249
+ *
1250
+ * Only truncation needs this: the counters are fed by `observeForSummary` as
1251
+ * events go by and cannot un-count. Reading the whole file is fine here —
1252
+ * rewind is a rare, explicitly user-driven operation, and the alternative
1253
+ * (reversing each counter per discarded event) has to stay in lockstep with
1254
+ * observeForSummary forever.
1255
+ *
1256
+ * `title` is deliberately re-derived too: the first surviving user_input may
1257
+ * differ once earlier prompts are gone.
1258
+ */
1259
+ async recomputeSummaryFromDisk() {
1260
+ if (!this.filePath) return;
1261
+ const raw = await fsp.readFile(this.filePath, "utf8").catch(() => null);
1262
+ if (raw === null) return;
1263
+ this.iterationCount = 0;
1264
+ this.toolCallCount = 0;
1265
+ this.toolErrorCount = 0;
1266
+ this.toolBreakdown = {};
1267
+ this.fileChangeCount = 0;
1268
+ this.compactionCount = 0;
1269
+ this.outcome = void 0;
1270
+ this.openToolUses = /* @__PURE__ */ new Set();
1271
+ this.tokenIn = 0;
1272
+ this.tokenOut = 0;
1273
+ this.summary = { ...this.summary, title: "(empty session)", tokenTotal: 0 };
1274
+ for (const line of raw.split("\n")) {
1275
+ const trimmed = line.trim();
1276
+ if (trimmed.length === 0) continue;
1277
+ try {
1278
+ this.observeForSummary(JSON.parse(trimmed));
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ }
1223
1283
  observeForSummary(event) {
1224
1284
  if (event.type === "llm_response") {
1225
1285
  for (const block of event.content) {
@@ -1270,10 +1330,7 @@ var FileSessionWriter = class _FileSessionWriter {
1270
1330
  }
1271
1331
  async doClose() {
1272
1332
  if (this.pendingFileSnapshots.length > 0) {
1273
- await this.writeFileSnapshot(
1274
- this.activePromptIndex ?? 0,
1275
- [...this.pendingFileSnapshots]
1276
- );
1333
+ await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
1277
1334
  this.pendingFileSnapshots = [];
1278
1335
  }
1279
1336
  this.closed = true;
@@ -1393,7 +1450,7 @@ var FileSessionWriter = class _FileSessionWriter {
1393
1450
  * so post-checkpoint content is never read or parsed — O(1) memory instead
1394
1451
  * of O(N) JSON.parse calls over the full file.
1395
1452
  */
1396
- async truncateToCheckpoint(targetPromptIndex) {
1453
+ async truncateToCheckpoint(targetPromptIndex, revertedFiles = []) {
1397
1454
  if (!this.filePath) return 0;
1398
1455
  if (this.flushTimer) {
1399
1456
  clearTimeout(this.flushTimer);
@@ -1404,7 +1461,7 @@ var FileSessionWriter = class _FileSessionWriter {
1404
1461
  const CHUNK_SIZE = 65536;
1405
1462
  let fd;
1406
1463
  let fileOffset = 0;
1407
- let lineStartOffset = 0;
1464
+ let pendingLine = Buffer.alloc(0);
1408
1465
  let checkpointByteOffset = -1;
1409
1466
  let removedCount = 0;
1410
1467
  let targetCheckpointSeen = false;
@@ -1414,17 +1471,18 @@ var FileSessionWriter = class _FileSessionWriter {
1414
1471
  const buf = Buffer.alloc(CHUNK_SIZE);
1415
1472
  const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
1416
1473
  if (bytesRead === 0) break;
1474
+ const carriedBytes = pendingLine.length;
1475
+ const chunk = carriedBytes === 0 ? buf.subarray(0, bytesRead) : Buffer.concat([pendingLine, buf.subarray(0, bytesRead)]);
1476
+ const chunkFileOffset = fileOffset - carriedBytes;
1417
1477
  let chunkPos = 0;
1418
- while (chunkPos < bytesRead) {
1419
- const idx = buf.indexOf("\n", chunkPos);
1420
- if (idx === -1) {
1421
- lineStartOffset = fileOffset + chunkPos;
1422
- break;
1423
- }
1478
+ while (chunkPos < chunk.length) {
1479
+ const idx = chunk.indexOf("\n", chunkPos);
1480
+ if (idx === -1) break;
1481
+ const lineStartOffset = chunkFileOffset + chunkPos;
1424
1482
  if (checkpointByteOffset !== -1) {
1425
1483
  removedCount++;
1426
1484
  } else {
1427
- const lineBytes = buf.subarray(chunkPos, idx);
1485
+ const lineBytes = chunk.subarray(chunkPos, idx);
1428
1486
  const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
1429
1487
  if (line.trim()) {
1430
1488
  try {
@@ -1450,12 +1508,9 @@ var FileSessionWriter = class _FileSessionWriter {
1450
1508
  }
1451
1509
  }
1452
1510
  chunkPos = idx + 1;
1453
- lineStartOffset = fileOffset + chunkPos;
1454
1511
  }
1512
+ pendingLine = chunk.subarray(chunkPos);
1455
1513
  fileOffset += bytesRead;
1456
- if (chunkPos >= bytesRead) {
1457
- lineStartOffset = fileOffset;
1458
- }
1459
1514
  }
1460
1515
  } finally {
1461
1516
  await fd?.close();
@@ -1535,17 +1590,19 @@ var FileSessionWriter = class _FileSessionWriter {
1535
1590
  this.handle = await fsp.open(this.filePath, "a", 384).catch(() => this.handle);
1536
1591
  throw err;
1537
1592
  }
1593
+ await this.recomputeSummaryFromDisk();
1594
+ const reverted = [...revertedFiles];
1538
1595
  await this.append({
1539
1596
  type: "rewound",
1540
1597
  ts: (/* @__PURE__ */ new Date()).toISOString(),
1541
1598
  toPromptIndex: targetPromptIndex,
1542
- revertedFiles: []
1599
+ revertedFiles: reverted
1543
1600
  });
1544
1601
  this.activePromptIndex = targetPromptIndex;
1545
1602
  this.events?.emit("session.rewound", {
1546
1603
  sessionId: this.id,
1547
1604
  toPromptIndex: targetPromptIndex,
1548
- revertedFiles: [],
1605
+ revertedFiles: reverted,
1549
1606
  removedEvents: removedCount
1550
1607
  });
1551
1608
  return removedCount;
@@ -1586,7 +1643,11 @@ var FileSessionWriter = class _FileSessionWriter {
1586
1643
  ts: (/* @__PURE__ */ new Date()).toISOString(),
1587
1644
  context
1588
1645
  });
1589
- this.events?.emit("in_flight.started", { sessionId: this.id, context, ts: (/* @__PURE__ */ new Date()).toISOString() });
1646
+ this.events?.emit("in_flight.started", {
1647
+ sessionId: this.id,
1648
+ context,
1649
+ ts: (/* @__PURE__ */ new Date()).toISOString()
1650
+ });
1590
1651
  }
1591
1652
  /**
1592
1653
  * Close the in-flight marker. Idempotent in spirit
@@ -1597,10 +1658,7 @@ var FileSessionWriter = class _FileSessionWriter {
1597
1658
  */
1598
1659
  async clearInFlightMarker(reason) {
1599
1660
  if (this.pendingFileSnapshots.length > 0) {
1600
- await this.writeFileSnapshot(
1601
- this.activePromptIndex ?? 0,
1602
- [...this.pendingFileSnapshots]
1603
- );
1661
+ await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
1604
1662
  this.pendingFileSnapshots = [];
1605
1663
  }
1606
1664
  await this.append({
@@ -1608,7 +1666,11 @@ var FileSessionWriter = class _FileSessionWriter {
1608
1666
  ts: (/* @__PURE__ */ new Date()).toISOString(),
1609
1667
  reason
1610
1668
  });
1611
- this.events?.emit("in_flight.ended", { sessionId: this.id, reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
1669
+ this.events?.emit("in_flight.ended", {
1670
+ sessionId: this.id,
1671
+ reason,
1672
+ ts: (/* @__PURE__ */ new Date()).toISOString()
1673
+ });
1612
1674
  }
1613
1675
  };
1614
1676
 
@@ -1999,6 +2061,16 @@ function defaultRunGit(args, cwd) {
1999
2061
  });
2000
2062
  }
2001
2063
 
2064
+ // src/storage/session-id.ts
2065
+ function sanitizeModel(model) {
2066
+ return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2067
+ }
2068
+ function generateSessionId(startedAt, _model) {
2069
+ const date = startedAt.slice(0, 10);
2070
+ const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);
2071
+ return `${date}/sess_${ulid(seedTime)}`;
2072
+ }
2073
+
2002
2074
  // src/storage/session-resume-validation.ts
2003
2075
  import { createHash as createHash3 } from "node:crypto";
2004
2076
  import * as fsp3 from "node:fs/promises";
@@ -2108,15 +2180,14 @@ function formatResumeValidationNotice(validation, projectRoot) {
2108
2180
  ...shown
2109
2181
  ].join("\n");
2110
2182
  }
2111
-
2112
- // src/storage/session-id.ts
2113
- function sanitizeModel(model) {
2114
- return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2115
- }
2116
- function generateSessionId(startedAt, _model) {
2117
- const date = startedAt.slice(0, 10);
2118
- const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);
2119
- return `${date}/sess_${ulid(seedTime)}`;
2183
+ function formatInterruptedToolNotice(pendingToolUseCount) {
2184
+ if (!Number.isFinite(pendingToolUseCount) || pendingToolUseCount <= 0) return null;
2185
+ const plural = pendingToolUseCount === 1 ? "tool call was" : "tool calls were";
2186
+ return [
2187
+ "[SESSION RESUME INTERRUPTED WORK]",
2188
+ `The previous run left ${pendingToolUseCount} ${plural} in flight \u2014 no result was recorded before it stopped (crash or interrupt).`,
2189
+ "Those interrupted tool calls were removed from the restored conversation and were NOT re-executed. Re-run the work if it still needs doing."
2190
+ ].join("\n");
2120
2191
  }
2121
2192
 
2122
2193
  // src/storage/session-summary.ts
@@ -2162,18 +2233,27 @@ function isReplayableMessage(value) {
2162
2233
  const candidate = value;
2163
2234
  return (candidate.role === "user" || candidate.role === "assistant" || candidate.role === "system") && (typeof candidate.content === "string" || Array.isArray(candidate.content));
2164
2235
  }
2236
+ function trackMessageToolState(message, openToolUses) {
2237
+ if (!Array.isArray(message.content)) return;
2238
+ for (const block of message.content) {
2239
+ if (block.type === "tool_use") openToolUses.add(block.id);
2240
+ else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
2241
+ }
2242
+ }
2243
+ function replayableMessage(value, fallbackTs) {
2244
+ if (!isReplayableMessage(value)) return null;
2245
+ const { _estTokens: _ignored, ...message } = value;
2246
+ return message.ts === void 0 && fallbackTs ? { ...message, ts: fallbackTs } : message;
2247
+ }
2165
2248
  function applyContextSnapshot(target, openToolUses, snapshot) {
2166
2249
  if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;
2167
2250
  target.length = 0;
2168
2251
  openToolUses.clear();
2169
2252
  for (const raw of snapshot) {
2170
- const { _estTokens: _ignored, ...message } = raw;
2253
+ const message = replayableMessage(raw);
2254
+ if (!message) return false;
2171
2255
  target.push(message);
2172
- if (!Array.isArray(message.content)) continue;
2173
- for (const block of message.content) {
2174
- if (block.type === "tool_use") openToolUses.add(block.id);
2175
- else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
2176
- }
2256
+ trackMessageToolState(message, openToolUses);
2177
2257
  }
2178
2258
  return true;
2179
2259
  }
@@ -2326,10 +2406,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2326
2406
  handle = await fsp4.open(file, "a", 384);
2327
2407
  } catch (err) {
2328
2408
  this.emitError(id, file, "create", toErrorMessage(err), false);
2329
- throw new Error(
2330
- `Failed to open session file: ${toErrorMessage(err)}`,
2331
- { cause: err }
2332
- );
2409
+ throw new Error(`Failed to open session file: ${toErrorMessage(err)}`, { cause: err });
2333
2410
  }
2334
2411
  try {
2335
2412
  const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
@@ -2342,10 +2419,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
2342
2419
  this.emitWrite(id, file, "create", "success", Date.now() - t0);
2343
2420
  return writer;
2344
2421
  } catch (err) {
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
- ));
2422
+ await handle.close().catch(
2423
+ (e) => this.logWarn("Session handle close failed", {
2424
+ event: "session_store.handle_close_failed",
2425
+ message: e instanceof Error ? e.message : String(e)
2426
+ })
2427
+ );
2349
2428
  this.emitError(id, file, "create", toErrorMessage(err), true);
2350
2429
  throw err;
2351
2430
  }
@@ -2364,9 +2443,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2364
2443
  }
2365
2444
  }
2366
2445
  if (boundary === -1) {
2367
- throw new Error(
2368
- `Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`
2369
- );
2446
+ throw new Error(`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`);
2370
2447
  }
2371
2448
  }
2372
2449
  const parentPrefix = parent.events.slice(0, boundary + 1);
@@ -2413,7 +2490,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
2413
2490
  }
2414
2491
  async materializeWorkspaceCheckpoint(checkpoint, targetRoot) {
2415
2492
  if (!this.checkpointCas) {
2416
- throw new Error("Workspace checkpoint materialization requires a projectRoot-aware session store");
2493
+ throw new Error(
2494
+ "Workspace checkpoint materialization requires a projectRoot-aware session store"
2495
+ );
2417
2496
  }
2418
2497
  return this.checkpointCas.materialize(checkpoint, targetRoot);
2419
2498
  }
@@ -2421,34 +2500,44 @@ var DefaultSessionStore = class _DefaultSessionStore {
2421
2500
  const file = this.sessionPath(id, ".jsonl");
2422
2501
  const t0 = Date.now();
2423
2502
  const data = await this.load(id);
2424
- let resumedData = data;
2503
+ const noticeMessages = [];
2504
+ let resumeValidation;
2425
2505
  if (this.projectRoot) {
2426
2506
  try {
2427
- const resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);
2507
+ resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);
2428
2508
  const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);
2429
- resumedData = {
2430
- ...data,
2431
- resumeValidation,
2432
- ...notice ? {
2433
- messages: [
2434
- ...data.messages,
2435
- { role: "system", content: notice, ts: resumeValidation.checkedAt }
2436
- ]
2437
- } : {}
2438
- };
2509
+ if (notice) {
2510
+ noticeMessages.push({
2511
+ role: "system",
2512
+ content: notice,
2513
+ ts: resumeValidation.checkedAt
2514
+ });
2515
+ }
2439
2516
  } catch (err) {
2440
2517
  this.emitError(id, file, "resume_validation", toErrorMessage(err), true);
2441
2518
  }
2442
2519
  }
2520
+ const interruptedNotice = formatInterruptedToolNotice(data.pendingToolUseCount ?? 0);
2521
+ if (interruptedNotice) {
2522
+ noticeMessages.push({
2523
+ role: "system",
2524
+ content: interruptedNotice,
2525
+ ts: (/* @__PURE__ */ new Date()).toISOString()
2526
+ });
2527
+ }
2528
+ const resumedData = {
2529
+ ...data,
2530
+ ...resumeValidation ? { resumeValidation } : {},
2531
+ ...noticeMessages.length > 0 ? { messages: [...data.messages, ...noticeMessages] } : {}
2532
+ };
2443
2533
  let handle;
2444
2534
  try {
2445
2535
  handle = await fsp4.open(file, "a", 384);
2446
2536
  } catch (err) {
2447
2537
  this.emitError(id, file, "resume", toErrorMessage(err), false);
2448
- throw new Error(
2449
- `Failed to open session "${id}" for append: ${toErrorMessage(err)}`,
2450
- { cause: err }
2451
- );
2538
+ throw new Error(`Failed to open session "${id}" for append: ${toErrorMessage(err)}`, {
2539
+ cause: err
2540
+ });
2452
2541
  }
2453
2542
  try {
2454
2543
  const writer = new FileSessionWriter(
@@ -2476,10 +2565,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
2476
2565
  this.emitWrite(id, file, "resume", "success", Date.now() - t0);
2477
2566
  return { writer, data: resumedData };
2478
2567
  } catch (err) {
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
- ));
2568
+ await handle.close().catch(
2569
+ (e) => this.logWarn("Session handle close failed", {
2570
+ event: "session_store.handle_close_failed",
2571
+ message: e instanceof Error ? e.message : String(e)
2572
+ })
2573
+ );
2483
2574
  this.emitError(id, file, "resume", toErrorMessage(err), true);
2484
2575
  throw err;
2485
2576
  }
@@ -2530,6 +2621,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2530
2621
  let sessionForkedEvent;
2531
2622
  const messages = mode.full ? [] : void 0;
2532
2623
  const openToolUses = mode.full ? /* @__PURE__ */ new Set() : void 0;
2624
+ let exactJournalActive = false;
2533
2625
  let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2534
2626
  const stream = createReadStream(file, { encoding: "utf8" });
2535
2627
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
@@ -2554,20 +2646,59 @@ var DefaultSessionStore = class _DefaultSessionStore {
2554
2646
  sessionForkedEvent = ev;
2555
2647
  }
2556
2648
  if (mode.full && messages !== void 0 && openToolUses !== void 0) {
2557
- if (ev.type === "context_snapshot") {
2649
+ if (ev.type === "message_appended" && ev.version === 1) {
2650
+ const message = replayableMessage(ev.message, ev.ts);
2651
+ if (message) {
2652
+ if (!exactJournalActive) {
2653
+ messages.length = 0;
2654
+ openToolUses.clear();
2655
+ exactJournalActive = true;
2656
+ }
2657
+ messages.push(message);
2658
+ trackMessageToolState(message, openToolUses);
2659
+ } else {
2660
+ this.events?.emit("session.damaged", {
2661
+ sessionId: id,
2662
+ detail: "Ignored malformed message_appended event"
2663
+ });
2664
+ }
2665
+ } else if (ev.type === "message_updated" && ev.version === 1) {
2666
+ const message = replayableMessage(ev.message, ev.ts);
2667
+ if (message && exactJournalActive && ev.index >= 0 && ev.index < messages.length) {
2668
+ messages[ev.index] = message;
2669
+ openToolUses.clear();
2670
+ for (const current of messages) trackMessageToolState(current, openToolUses);
2671
+ } else {
2672
+ this.events?.emit("session.damaged", {
2673
+ sessionId: id,
2674
+ detail: `Ignored malformed message_updated event at index ${ev.index}`
2675
+ });
2676
+ }
2677
+ } else if (ev.type === "messages_replaced" && ev.version === 1) {
2678
+ if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
2679
+ exactJournalActive = true;
2680
+ } else {
2681
+ this.events?.emit("session.damaged", {
2682
+ sessionId: id,
2683
+ detail: "Ignored malformed messages_replaced event"
2684
+ });
2685
+ }
2686
+ } else if (ev.type === "context_snapshot") {
2558
2687
  if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
2559
2688
  this.events?.emit("session.damaged", {
2560
2689
  sessionId: id,
2561
2690
  detail: "Ignored malformed context_snapshot event"
2562
2691
  });
2563
2692
  }
2564
- } else if (ev.type === "user_input") {
2693
+ } else if (!exactJournalActive && ev.type === "user_input") {
2565
2694
  openToolUses.clear();
2566
2695
  messages.push({ role: "user", content: ev.content, ts: ev.ts });
2567
2696
  } else if (ev.type === "llm_response") {
2568
- messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
2569
- for (const b of ev.content) {
2570
- if (b.type === "tool_use") openToolUses.add(b.id);
2697
+ if (!exactJournalActive) {
2698
+ messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
2699
+ for (const b of ev.content) {
2700
+ if (b.type === "tool_use") openToolUses.add(b.id);
2701
+ }
2571
2702
  }
2572
2703
  usage = {
2573
2704
  input: usage.input + (ev.usage.input ?? 0),
@@ -2575,7 +2706,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2575
2706
  cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
2576
2707
  cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
2577
2708
  };
2578
- } else if (ev.type === "tool_result") {
2709
+ } else if (!exactJournalActive && ev.type === "tool_result") {
2579
2710
  if (!openToolUses.has(ev.id)) {
2580
2711
  this.events?.emit("session.damaged", {
2581
2712
  sessionId: id,
@@ -2647,7 +2778,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
2647
2778
  } : void 0
2648
2779
  };
2649
2780
  const toolCallEnds = extractToolCallEnds(events);
2650
- const data = { metadata: meta, events, messages: finalMessages, usage, toolCallEnds };
2781
+ const pendingToolUseCount = openToolUses && openToolUses.size > 0 ? openToolUses.size : void 0;
2782
+ const data = {
2783
+ metadata: meta,
2784
+ events,
2785
+ messages: finalMessages,
2786
+ usage,
2787
+ toolCallEnds,
2788
+ ...pendingToolUseCount !== void 0 ? { pendingToolUseCount } : {}
2789
+ };
2651
2790
  if (mode.full) {
2652
2791
  if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
2653
2792
  const oldest = this._loadCache.keys().next().value;
@@ -2663,7 +2802,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
2663
2802
  errorMsg = toErrorMessage(err);
2664
2803
  throw err;
2665
2804
  } finally {
2666
- this.emitRead(id, file, mode.full ? "load" : "load_events_only", outcome, Date.now() - t0, errorMsg);
2805
+ this.emitRead(
2806
+ id,
2807
+ file,
2808
+ mode.full ? "load" : "load_events_only",
2809
+ outcome,
2810
+ Date.now() - t0,
2811
+ errorMsg
2812
+ );
2667
2813
  if (cacheHit) {
2668
2814
  this.events?.emit("storage.cache_hit", {
2669
2815
  sessionId: id,
@@ -2865,7 +3011,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
2865
3011
  outcome = "failure";
2866
3012
  errorMsg = toErrorMessage(err);
2867
3013
  } finally {
2868
- this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
3014
+ this.emitWrite(
3015
+ "~compact~",
3016
+ this.indexFile,
3017
+ "compact",
3018
+ outcome,
3019
+ Date.now() - t0,
3020
+ void 0,
3021
+ errorMsg
3022
+ );
2869
3023
  }
2870
3024
  }
2871
3025
  /**
@@ -2937,7 +3091,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
2937
3091
  */
2938
3092
  async rebuildIndex() {
2939
3093
  const ids = await this.collectSessionIds(this.dir);
2940
- const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
3094
+ const summaries = await Promise.all(
3095
+ ids.map((id) => this.summaryFor(id).catch(() => null))
3096
+ );
2941
3097
  const valid = summaries.filter((s) => s !== null);
2942
3098
  const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
2943
3099
  await withFileLock(this.indexFile, async () => {
@@ -2978,7 +3134,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
2978
3134
  const shardKeys = [""];
2979
3135
  for (const entry of entries) {
2980
3136
  if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
2981
- if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments") continue;
3137
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
3138
+ continue;
2982
3139
  if (entry.isDirectory()) shardKeys.push(entry.name);
2983
3140
  }
2984
3141
  return shardKeys;
@@ -3100,10 +3257,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
3100
3257
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
3101
3258
  const msg = toErrorMessage(err);
3102
3259
  this.emitError(id, manifest, "summary_fallback", msg, true);
3103
- this.logWarn(
3104
- "Session manifest write failed",
3105
- { event: "session_store.manifest_write_failed", sessionId: id, message: msg }
3106
- );
3260
+ this.logWarn("Session manifest write failed", {
3261
+ event: "session_store.manifest_write_failed",
3262
+ sessionId: id,
3263
+ message: msg
3264
+ });
3107
3265
  });
3108
3266
  outcome = "failure";
3109
3267
  errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
@@ -3210,24 +3368,28 @@ var DefaultSessionStore = class _DefaultSessionStore {
3210
3368
  if (r.status === "rejected") {
3211
3369
  const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
3212
3370
  if (r.reason?.code !== "ENOENT") {
3213
- console.warn(JSON.stringify({
3214
- level: "warn",
3215
- event: "session_store.delete_failed",
3216
- sessionId: id,
3217
- message: msg,
3218
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3219
- }));
3371
+ console.warn(
3372
+ JSON.stringify({
3373
+ level: "warn",
3374
+ event: "session_store.delete_failed",
3375
+ sessionId: id,
3376
+ message: msg,
3377
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3378
+ })
3379
+ );
3220
3380
  }
3221
3381
  }
3222
3382
  }
3223
3383
  await fsp4.rm(sessDir, { recursive: true, force: true }).catch((err) => {
3224
- console.warn(JSON.stringify({
3225
- level: "warn",
3226
- event: "session_store.rmdir_failed",
3227
- sessionId: id,
3228
- message: toErrorMessage(err),
3229
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3230
- }));
3384
+ console.warn(
3385
+ JSON.stringify({
3386
+ level: "warn",
3387
+ event: "session_store.rmdir_failed",
3388
+ sessionId: id,
3389
+ message: toErrorMessage(err),
3390
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3391
+ })
3392
+ );
3231
3393
  });
3232
3394
  await this.writeTombstone(id);
3233
3395
  }
@@ -4516,6 +4678,43 @@ function isDirectory(candidate) {
4516
4678
  }
4517
4679
 
4518
4680
  // src/storage/memory-consolidator.ts
4681
+ function isSuperMemoryRecord(value) {
4682
+ return typeof value === "object" && value !== null && typeof value.text === "string";
4683
+ }
4684
+ function toPromptEntries(records, limit) {
4685
+ return records.filter(isSuperMemoryRecord).filter((record) => record.scope === "project").slice(0, limit).map((record) => ({
4686
+ scope: "project-memory",
4687
+ text: record.text,
4688
+ ts: record.updatedAt ?? record.createdAt ?? ""
4689
+ }));
4690
+ }
4691
+ function toSuperMemoryKind(type) {
4692
+ switch (type) {
4693
+ case "decision":
4694
+ case "convention":
4695
+ case "preference":
4696
+ case "anti_pattern":
4697
+ return type;
4698
+ case "reference":
4699
+ return "file_note";
4700
+ default:
4701
+ return "fact";
4702
+ }
4703
+ }
4704
+ function importanceFromPriority(priority) {
4705
+ switch (priority) {
4706
+ case "critical":
4707
+ return 0.95;
4708
+ case "high":
4709
+ return 0.8;
4710
+ case "medium":
4711
+ return 0.55;
4712
+ case "low":
4713
+ return 0.25;
4714
+ default:
4715
+ return 0.6;
4716
+ }
4717
+ }
4519
4718
  function buildConsolidationPrompt(finalText, iterations, existingEntries) {
4520
4719
  const existingBlock = existingEntries.length > 0 ? `
4521
4720
 
@@ -4531,6 +4730,7 @@ var SessionMemoryConsolidator = class {
4531
4730
  name = "session-memory-consolidator";
4532
4731
  owner = "core";
4533
4732
  memoryStore;
4733
+ superMemory;
4534
4734
  provider;
4535
4735
  model;
4536
4736
  minIterations;
@@ -4538,6 +4738,7 @@ var SessionMemoryConsolidator = class {
4538
4738
  oneShotOrchestrator;
4539
4739
  constructor(opts) {
4540
4740
  this.memoryStore = opts.memoryStore;
4741
+ this.superMemory = opts.superMemory;
4541
4742
  this.provider = opts.provider;
4542
4743
  this.model = opts.model;
4543
4744
  this.minIterations = opts.minIterations ?? 2;
@@ -4553,9 +4754,20 @@ var SessionMemoryConsolidator = class {
4553
4754
  const _finalText = result.finalText;
4554
4755
  const _iterations = result.iterations;
4555
4756
  const _model = this.model ?? ctx.model;
4757
+ const _sessionId = ctx.session.id;
4556
4758
  void (async () => {
4557
4759
  try {
4558
- const existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
4760
+ let existingEntries;
4761
+ if (this.superMemory) {
4762
+ const records = this.superMemory.listSuper ? await this.superMemory.listSuper(["active"]) : await this.superMemory.searchSuper?.("", {
4763
+ limit: this.maxExistingEntries,
4764
+ scope: "project",
4765
+ includeStatuses: ["active"]
4766
+ }) ?? [];
4767
+ existingEntries = toPromptEntries(records, this.maxExistingEntries);
4768
+ } else {
4769
+ existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
4770
+ }
4559
4771
  const prompt = buildConsolidationPrompt(
4560
4772
  _finalText,
4561
4773
  _iterations,
@@ -4568,8 +4780,7 @@ var SessionMemoryConsolidator = class {
4568
4780
  userPrompt: "Review the session and return memory operations as JSON.",
4569
4781
  model: _model ?? "deepseek-chat",
4570
4782
  maxTokens: 500,
4571
- timeoutMs: 15e3,
4572
- fallbackProfile: "memory"
4783
+ timeoutMs: 15e3
4573
4784
  });
4574
4785
  text = oneShotResult.text;
4575
4786
  } else {
@@ -4593,48 +4804,33 @@ var SessionMemoryConsolidator = class {
4593
4804
  const parsed = JSON.parse(jsonMatch[0]);
4594
4805
  if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
4595
4806
  let added = 0;
4596
- let edited = 0;
4597
- let deleted = 0;
4598
4807
  for (const op of parsed.operations) {
4599
- switch (op.action) {
4600
- case "add": {
4601
- if (op.text?.trim()) {
4602
- await this.memoryStore.remember(op.text.trim(), void 0, {
4603
- type: op.type,
4604
- tags: op.tags,
4605
- priority: op.priority
4606
- });
4607
- added++;
4608
- }
4609
- break;
4610
- }
4611
- case "edit": {
4612
- if (op.query && op.text?.trim()) {
4613
- await this.memoryStore.forget(op.query);
4614
- await this.memoryStore.remember(op.text.trim(), void 0, {
4615
- type: op.type,
4616
- tags: op.tags,
4617
- priority: op.priority
4618
- });
4619
- edited++;
4620
- }
4621
- break;
4622
- }
4623
- case "delete": {
4624
- if (op.query) {
4625
- const n = await this.memoryStore.forget(op.query);
4626
- deleted += n;
4627
- }
4628
- break;
4808
+ try {
4809
+ if (!op || typeof op !== "object" || op.action !== "add") continue;
4810
+ if (typeof op.text !== "string" || !op.text.trim()) continue;
4811
+ const memoryText = op.text.trim();
4812
+ if (this.superMemory) {
4813
+ await this.superMemory.rememberSuper({
4814
+ text: memoryText,
4815
+ scope: "project",
4816
+ kind: toSuperMemoryKind(op.type),
4817
+ tags: op.tags,
4818
+ importance: importanceFromPriority(op.priority),
4819
+ sources: [{ type: "session", sessionId: _sessionId }]
4820
+ });
4821
+ } else {
4822
+ await this.memoryStore.remember(memoryText, void 0, {
4823
+ type: op.type,
4824
+ tags: op.tags,
4825
+ priority: op.priority
4826
+ });
4629
4827
  }
4828
+ added++;
4829
+ } catch {
4630
4830
  }
4631
4831
  }
4632
- if (added > 0 || edited > 0 || deleted > 0) {
4633
- const parts = [];
4634
- if (added) parts.push(`${added} added`);
4635
- if (edited) parts.push(`${edited} edited`);
4636
- if (deleted) parts.push(`${deleted} deleted`);
4637
- process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
4832
+ if (added > 0) {
4833
+ process.stderr.write(`[memory] Session consolidation: ${added} added
4638
4834
  `);
4639
4835
  }
4640
4836
  } catch {
@@ -4785,6 +4981,15 @@ async function readProviderSnapshot(configPath, vault, warn) {
4785
4981
  if (typeof decrypted.baseUrl === "string") snapshot.baseUrl = decrypted.baseUrl;
4786
4982
  if (typeof decrypted.uiLocale === "string" && decrypted.uiLocale)
4787
4983
  snapshot.uiLocale = decrypted.uiLocale;
4984
+ if (Array.isArray(decrypted.fallbackModels)) snapshot.fallbackModels = decrypted.fallbackModels;
4985
+ if (decrypted.fallbackProfiles) snapshot.fallbackProfiles = decrypted.fallbackProfiles;
4986
+ if (Array.isArray(decrypted.favoriteModels)) snapshot.favoriteModels = decrypted.favoriteModels;
4987
+ if (typeof decrypted.favoriteModelsOnly === "boolean")
4988
+ snapshot.favoriteModelsOnly = decrypted.favoriteModelsOnly;
4989
+ if (decrypted.modelMatrix) snapshot.modelMatrix = decrypted.modelMatrix;
4990
+ if (typeof decrypted.fallbackAuto === "boolean") snapshot.fallbackAuto = decrypted.fallbackAuto;
4991
+ if (Array.isArray(decrypted.modelAvailabilitySchedule))
4992
+ snapshot.modelAvailabilitySchedule = decrypted.modelAvailabilitySchedule;
4788
4993
  return snapshot;
4789
4994
  }
4790
4995
  function serializeSnapshot(s) {
@@ -4792,7 +4997,14 @@ function serializeSnapshot(s) {
4792
4997
  providers: s.providers,
4793
4998
  apiKey: s.apiKey ?? null,
4794
4999
  baseUrl: s.baseUrl ?? null,
4795
- uiLocale: s.uiLocale ?? null
5000
+ uiLocale: s.uiLocale ?? null,
5001
+ fallbackModels: s.fallbackModels ?? null,
5002
+ fallbackProfiles: s.fallbackProfiles ?? null,
5003
+ favoriteModels: s.favoriteModels ?? null,
5004
+ favoriteModelsOnly: s.favoriteModelsOnly ?? null,
5005
+ modelMatrix: s.modelMatrix ?? null,
5006
+ fallbackAuto: s.fallbackAuto ?? null,
5007
+ modelAvailabilitySchedule: s.modelAvailabilitySchedule ?? null
4796
5008
  });
4797
5009
  }
4798
5010
  function watchProviderConfig(configPath, vault, onChange, opts = {}) {
@@ -5153,6 +5365,7 @@ var defaultIndexing = {
5153
5365
  };
5154
5366
  var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
5155
5367
  "version",
5368
+ "activeProfile",
5156
5369
  "model",
5157
5370
  "cwd",
5158
5371
  "context",
@@ -5175,6 +5388,7 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
5175
5388
  "fallbackProfiles",
5176
5389
  "favoriteModels",
5177
5390
  "favoriteModelsOnly",
5391
+ "modelAvailabilitySchedule",
5178
5392
  "fallbackAuto",
5179
5393
  "models",
5180
5394
  "modelMatrix",
@@ -5210,6 +5424,7 @@ var KNOWN_DENIED_IN_PROJECT = [
5210
5424
  ];
5211
5425
  var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
5212
5426
  "version",
5427
+ "activeProfile",
5213
5428
  "provider",
5214
5429
  "model",
5215
5430
  "apiKey",
@@ -5226,6 +5441,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
5226
5441
  "fallbackProfiles",
5227
5442
  "favoriteModels",
5228
5443
  "favoriteModelsOnly",
5444
+ "modelAvailabilitySchedule",
5229
5445
  "fallbackAuto",
5230
5446
  "hooks",
5231
5447
  "plugins",
@@ -5430,12 +5646,17 @@ var DefaultConfigLoader = class {
5430
5646
  let cfg = { ...BEHAVIOR_DEFAULTS };
5431
5647
  await this.ensureGlobalDefaults();
5432
5648
  const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
5433
- const [global, local, inProject] = await Promise.all([
5649
+ const [bootstrap, local, inProject] = await Promise.all([
5434
5650
  this.readJson(this.paths.globalConfig),
5435
5651
  this.readJson(this.paths.projectLocalConfig),
5436
5652
  inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
5437
5653
  ]);
5438
- cfg = deepMerge2(cfg, global);
5654
+ cfg = deepMerge2(cfg, bootstrap);
5655
+ const profileName = bootstrap.activeProfile ?? "default";
5656
+ const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
5657
+ if (Object.keys(profileCfg).length > 0) {
5658
+ cfg = deepMerge2(cfg, profileCfg);
5659
+ }
5439
5660
  cfg = deepMerge2(cfg, local);
5440
5661
  cfg = deepMerge2(
5441
5662
  cfg,
@@ -5461,10 +5682,11 @@ var DefaultConfigLoader = class {
5461
5682
  cfg = deepMerge2(cfg, patch);
5462
5683
  }
5463
5684
  } catch (err) {
5464
- this.logWarn(
5465
- "Config source load failed",
5466
- { event: "config.source_load_failed", source: src.name, message: toErrorMessage(err) }
5467
- );
5685
+ this.logWarn("Config source load failed", {
5686
+ event: "config.source_load_failed",
5687
+ source: src.name,
5688
+ message: toErrorMessage(err)
5689
+ });
5468
5690
  }
5469
5691
  }
5470
5692
  if (opts.cliFlags) {
@@ -5503,6 +5725,7 @@ var DefaultConfigLoader = class {
5503
5725
  try {
5504
5726
  await withFileLock(fp, async () => {
5505
5727
  let parsed;
5728
+ let fileExisted = true;
5506
5729
  try {
5507
5730
  const raw = await fs6.readFile(fp, "utf8");
5508
5731
  const result = safeParse(raw);
@@ -5523,25 +5746,115 @@ var DefaultConfigLoader = class {
5523
5746
  durationMs: Date.now() - t0,
5524
5747
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5525
5748
  });
5526
- this.logWarn(
5527
- "Config defaults read failed",
5528
- { event: "config.defaults_read_failed", path: fp, message: toErrorMessage(err) }
5529
- );
5749
+ this.logWarn("Config defaults read failed", {
5750
+ event: "config.defaults_read_failed",
5751
+ path: fp,
5752
+ message: toErrorMessage(err)
5753
+ });
5530
5754
  return;
5531
5755
  }
5756
+ fileExisted = false;
5532
5757
  parsed = {};
5533
5758
  }
5534
- const { value, changed } = fillMissingDefaults(
5535
- parsed,
5536
- BEHAVIOR_DEFAULTS
5537
- );
5538
- if (!changed) return;
5539
- await atomicWrite(fp, JSON.stringify(value, null, 2), { mode: 384 });
5759
+ const bootstrap = {
5760
+ version: 1,
5761
+ activeProfile: parsed.activeProfile ?? "default"
5762
+ };
5763
+ let needsBootstrapWrite = false;
5764
+ if (parsed.version !== 1) {
5765
+ needsBootstrapWrite = true;
5766
+ }
5767
+ if (parsed.activeProfile === void 0) {
5768
+ needsBootstrapWrite = true;
5769
+ }
5770
+ if (needsBootstrapWrite) {
5771
+ await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
5772
+ this.events?.emit("storage.write", {
5773
+ sessionId: "~config~",
5774
+ store: "config",
5775
+ filePath: fp,
5776
+ operation: "ensure_defaults",
5777
+ outcome: "success",
5778
+ durationMs: Date.now() - t0,
5779
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5780
+ });
5781
+ }
5782
+ const profileName = bootstrap.activeProfile ?? "default";
5783
+ const profileFp = this.paths.profileConfig(profileName);
5784
+ await this.ensureProfileConfig(profileFp, parsed, fileExisted);
5785
+ });
5786
+ } catch (err) {
5787
+ this.events?.emit("storage.error", {
5788
+ sessionId: "~config~",
5789
+ store: "config",
5790
+ filePath: fp,
5791
+ operation: "ensure_defaults",
5792
+ outcome: "failure",
5793
+ error: storageErrorString(err),
5794
+ recoverable: false,
5795
+ durationMs: Date.now() - t0,
5796
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5797
+ });
5798
+ this.logWarn("Config defaults write failed", {
5799
+ event: "config.defaults_write_failed",
5800
+ path: fp,
5801
+ message: toErrorMessage(err)
5802
+ });
5803
+ }
5804
+ }
5805
+ /**
5806
+ * Ensure a profile config file exists at the given path.
5807
+ * On first boot: migrate content from the old flat global config, or seed
5808
+ * with behavior defaults. Subsequent boots: fill any missing keys from
5809
+ * BEHAVIOR_DEFAULTS (keeping user settings intact).
5810
+ */
5811
+ async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
5812
+ const t0 = Date.now();
5813
+ try {
5814
+ await withFileLock(profileFp, async () => {
5815
+ let parsed;
5816
+ let existed = true;
5817
+ try {
5818
+ const raw = await fs6.readFile(profileFp, "utf8");
5819
+ const result = safeParse(raw);
5820
+ if (!result.ok || !isPlainRecord(result.value)) {
5821
+ this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
5822
+ event: "config.profile_parse_failed",
5823
+ path: profileFp
5824
+ });
5825
+ parsed = {};
5826
+ } else {
5827
+ parsed = result.value;
5828
+ }
5829
+ } catch (err) {
5830
+ if (err.code !== "ENOENT") {
5831
+ this.logWarn("Profile config read failed", {
5832
+ event: "config.profile_read_failed",
5833
+ path: profileFp,
5834
+ message: toErrorMessage(err)
5835
+ });
5836
+ return;
5837
+ }
5838
+ existed = false;
5839
+ parsed = {};
5840
+ }
5841
+ let seed;
5842
+ if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
5843
+ seed = { ...oldGlobalParsed };
5844
+ delete seed["activeProfile"];
5845
+ const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
5846
+ seed = filled.value;
5847
+ } else {
5848
+ const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
5849
+ if (!filled.changed) return;
5850
+ seed = filled.value;
5851
+ }
5852
+ await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
5540
5853
  this.events?.emit("storage.write", {
5541
5854
  sessionId: "~config~",
5542
5855
  store: "config",
5543
- filePath: fp,
5544
- operation: "ensure_defaults",
5856
+ filePath: profileFp,
5857
+ operation: "ensure_profile_defaults",
5545
5858
  outcome: "success",
5546
5859
  durationMs: Date.now() - t0,
5547
5860
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
@@ -5551,18 +5864,19 @@ var DefaultConfigLoader = class {
5551
5864
  this.events?.emit("storage.error", {
5552
5865
  sessionId: "~config~",
5553
5866
  store: "config",
5554
- filePath: fp,
5555
- operation: "ensure_defaults",
5867
+ filePath: profileFp,
5868
+ operation: "ensure_profile_defaults",
5556
5869
  outcome: "failure",
5557
5870
  error: storageErrorString(err),
5558
5871
  recoverable: false,
5559
5872
  durationMs: Date.now() - t0,
5560
5873
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5561
5874
  });
5562
- this.logWarn(
5563
- "Config defaults write failed",
5564
- { event: "config.defaults_write_failed", path: fp, message: toErrorMessage(err) }
5565
- );
5875
+ this.logWarn("Profile config defaults write failed", {
5876
+ event: "config.profile_write_failed",
5877
+ path: profileFp,
5878
+ message: toErrorMessage(err)
5879
+ });
5566
5880
  }
5567
5881
  }
5568
5882
  /**
@@ -5664,10 +5978,10 @@ var DefaultConfigLoader = class {
5664
5978
  error: storageErrorString(err),
5665
5979
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5666
5980
  });
5667
- this.logWarn(
5668
- "Config sync load failed",
5669
- { event: "config.sync_load_failed", message: toErrorMessage(err) }
5670
- );
5981
+ this.logWarn("Config sync load failed", {
5982
+ event: "config.sync_load_failed",
5983
+ message: toErrorMessage(err)
5984
+ });
5671
5985
  return null;
5672
5986
  }
5673
5987
  }
@@ -5696,10 +6010,11 @@ var DefaultConfigLoader = class {
5696
6010
  error: storageErrorString(err),
5697
6011
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5698
6012
  });
5699
- this.logWarn(
5700
- "Config read failed (stat)",
5701
- { event: "config.read_failed", path: file, message: toErrorMessage(err) }
5702
- );
6013
+ this.logWarn("Config read failed (stat)", {
6014
+ event: "config.read_failed",
6015
+ path: file,
6016
+ message: toErrorMessage(err)
6017
+ });
5703
6018
  return {};
5704
6019
  }
5705
6020
  let raw;
@@ -5717,10 +6032,11 @@ var DefaultConfigLoader = class {
5717
6032
  error: storageErrorString(err),
5718
6033
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5719
6034
  });
5720
- this.logWarn(
5721
- "Config read failed (read)",
5722
- { event: "config.read_failed", path: file, message: toErrorMessage(err) }
5723
- );
6035
+ this.logWarn("Config read failed (read)", {
6036
+ event: "config.read_failed",
6037
+ path: file,
6038
+ message: toErrorMessage(err)
6039
+ });
5724
6040
  }
5725
6041
  this.jsonCache.set(file, { mtimeMs: null, value: {} });
5726
6042
  return {};
@@ -5737,10 +6053,10 @@ var DefaultConfigLoader = class {
5737
6053
  error: "parse error or empty file",
5738
6054
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5739
6055
  });
5740
- this.logWarn(
5741
- "Config parse failed \u2014 falling back to defaults",
5742
- { event: "config.parse_failed", path: file }
5743
- );
6056
+ this.logWarn("Config parse failed \u2014 falling back to defaults", {
6057
+ event: "config.parse_failed",
6058
+ path: file
6059
+ });
5744
6060
  return {};
5745
6061
  }
5746
6062
  this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });
@@ -7701,9 +8017,9 @@ var SessionAnalyzer = class {
7701
8017
  };
7702
8018
 
7703
8019
  // src/session-registry.ts
8020
+ import { randomUUID as randomUUID5 } from "node:crypto";
7704
8021
  import * as fs12 from "node:fs/promises";
7705
8022
  import * as path17 from "node:path";
7706
- import { randomUUID as randomUUID5 } from "node:crypto";
7707
8023
  var REGISTRY_FILE = "session-registry.json";
7708
8024
  var HEARTBEAT_INTERVAL_MS = 5e3;
7709
8025
  var STALE_TIMEOUT_MS = 3e4;
@@ -8075,6 +8391,201 @@ var AGENT_REAP_MS = 3e4;
8075
8391
  var AGENT_SWEEP_INTERVAL_MS = 1e4;
8076
8392
  var PARTIAL_TEXT_CAP = 1200;
8077
8393
  var PARTIAL_FLUSH_THROTTLE_MS = 300;
8394
+ var RECENT_TOOL_LIMIT = 12;
8395
+ var RECENT_MAIL_LIMIT = 12;
8396
+ var TOOL_TEXT_CAP = 360;
8397
+ var TOUCHED_FILE_LIMIT = 200;
8398
+ var TASK_TEXT_CAP = 1200;
8399
+ var PROMPT_TEXT_CAP = 6e3;
8400
+ var TODO_TEXT_CAP = 360;
8401
+ var TODO_LIMIT = 32;
8402
+ function lineCount(value) {
8403
+ return value.length === 0 ? 0 : value.split(/\r?\n/).length;
8404
+ }
8405
+ function patchDelta(value) {
8406
+ let addedLines = 0;
8407
+ let removedLines = 0;
8408
+ const hunkStart = value.indexOf("@@");
8409
+ if (hunkStart === -1) return { addedLines, removedLines };
8410
+ for (const line of value.slice(hunkStart).split(/\r?\n/)) {
8411
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
8412
+ if (line.startsWith("+")) addedLines += 1;
8413
+ else if (line.startsWith("-")) removedLines += 1;
8414
+ }
8415
+ return { addedLines, removedLines };
8416
+ }
8417
+ function compactText(value) {
8418
+ return value.length <= TOOL_TEXT_CAP ? value : `${value.slice(0, TOOL_TEXT_CAP - 1)}\u2026`;
8419
+ }
8420
+ function boundedText(value, cap) {
8421
+ const trimmed = value.trim();
8422
+ return trimmed.length <= cap ? trimmed : `${trimmed.slice(0, cap - 1)}\u2026`;
8423
+ }
8424
+ function compactTodos(value) {
8425
+ if (!Array.isArray(value)) return void 0;
8426
+ const todos = [];
8427
+ for (const candidate of value) {
8428
+ if (typeof candidate !== "object" || candidate === null) continue;
8429
+ const todo = candidate;
8430
+ const id = typeof todo["id"] === "string" ? todo["id"].trim() : "";
8431
+ const content = typeof todo["content"] === "string" ? boundedText(todo["content"], TODO_TEXT_CAP) : "";
8432
+ const status = todo["status"];
8433
+ if (!id || !content || status !== "pending" && status !== "in_progress" && status !== "completed") {
8434
+ continue;
8435
+ }
8436
+ const activeForm = typeof todo["activeForm"] === "string" ? boundedText(todo["activeForm"], TODO_TEXT_CAP) : void 0;
8437
+ todos.push({ id, content, status, ...activeForm ? { activeForm } : {} });
8438
+ if (todos.length >= TODO_LIMIT) break;
8439
+ }
8440
+ return todos;
8441
+ }
8442
+ function compactToolInput(input) {
8443
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
8444
+ return typeof input === "string" ? { input: compactText(input) } : {};
8445
+ }
8446
+ const source = input;
8447
+ const safe = {};
8448
+ const safeKeys = [
8449
+ "file_path",
8450
+ "filePath",
8451
+ "path",
8452
+ "filename",
8453
+ "target_file",
8454
+ "targetFile",
8455
+ "line",
8456
+ "start_line",
8457
+ "startLine",
8458
+ "end_line",
8459
+ "endLine",
8460
+ "offset",
8461
+ "limit",
8462
+ "command",
8463
+ "cmd",
8464
+ "url",
8465
+ "href",
8466
+ "uri",
8467
+ "query",
8468
+ "pattern"
8469
+ ];
8470
+ for (const key of safeKeys) {
8471
+ const value = source[key];
8472
+ if (typeof value === "string") safe[key] = compactText(value);
8473
+ else if (typeof value === "number" || typeof value === "boolean") safe[key] = value;
8474
+ }
8475
+ const content = [source["content"], source["text"], source["data"]].find(
8476
+ (value) => typeof value === "string"
8477
+ );
8478
+ const oldText = [source["old_string"], source["oldString"], source["search"]].find(
8479
+ (value) => typeof value === "string"
8480
+ );
8481
+ const newText = [source["new_string"], source["newString"], source["replacement"]].find(
8482
+ (value) => typeof value === "string"
8483
+ );
8484
+ const patch = [source["patch"], source["diff"]].find(
8485
+ (value) => typeof value === "string"
8486
+ );
8487
+ const delta = patch ? patchDelta(patch) : void 0;
8488
+ return {
8489
+ ...Object.keys(safe).length > 0 ? { input: safe } : {},
8490
+ ...content !== void 0 ? { inputLines: lineCount(content) } : {},
8491
+ ...oldText !== void 0 ? { oldLines: lineCount(oldText) } : {},
8492
+ ...newText !== void 0 ? { newLines: lineCount(newText) } : {},
8493
+ ...delta && delta.addedLines > 0 ? { addedLines: delta.addedLines } : {},
8494
+ ...delta && delta.removedLines > 0 ? { removedLines: delta.removedLines } : {}
8495
+ };
8496
+ }
8497
+ function completedToolReceipt(payload, pending) {
8498
+ const completedAt = Date.now();
8499
+ const durationMs = Math.max(
8500
+ 0,
8501
+ payload.durationMs ?? completedAt - (pending?.startedAt ?? completedAt)
8502
+ );
8503
+ const compact = compactToolInput(payload.input ?? pending?.input);
8504
+ return {
8505
+ id: payload.id ?? `${payload.name}:${completedAt}`,
8506
+ name: payload.name,
8507
+ startedAt: pending?.startedAt ?? Math.max(0, completedAt - durationMs),
8508
+ completedAt,
8509
+ durationMs,
8510
+ ok: payload.ok !== false,
8511
+ ...compact,
8512
+ ...payload.output !== void 0 ? { output: compactText(payload.output) } : {},
8513
+ ...payload.outputLines !== void 0 ? { outputLines: payload.outputLines } : {},
8514
+ ...payload.outputBytes !== void 0 ? { outputBytes: payload.outputBytes } : {},
8515
+ ...payload.outputTokens !== void 0 ? { outputTokens: payload.outputTokens } : {}
8516
+ };
8517
+ }
8518
+ function emptyActivityTotals() {
8519
+ return {
8520
+ filesTouched: [],
8521
+ reads: 0,
8522
+ writes: 0,
8523
+ edits: 0,
8524
+ terminalCalls: 0,
8525
+ webCalls: 0,
8526
+ searches: 0,
8527
+ otherCalls: 0,
8528
+ mailReceived: 0,
8529
+ mailSent: 0,
8530
+ linesRead: 0,
8531
+ linesWritten: 0,
8532
+ linesAdded: 0,
8533
+ linesRemoved: 0
8534
+ };
8535
+ }
8536
+ function addMailTotal(current, direction) {
8537
+ const next = { ...current ?? emptyActivityTotals() };
8538
+ if (direction === "incoming") next.mailReceived += 1;
8539
+ else next.mailSent += 1;
8540
+ return next;
8541
+ }
8542
+ function toolActivityKind(name) {
8543
+ const normalized = name.toLowerCase().replace(/[.:/-]+/g, "_");
8544
+ if (/^(read|view|open_file|read_file)|file_read/.test(normalized)) return "read";
8545
+ if (/^(write|create|save)|file_write/.test(normalized)) return "write";
8546
+ if (/edit|update|patch|replace|apply_patch/.test(normalized)) return "edit";
8547
+ if (/bash|shell|terminal|exec|command|powershell|cmd/.test(normalized)) return "terminal";
8548
+ if (/fetch|browser|browse|http|url|web/.test(normalized)) return "web";
8549
+ if (/search|grep|find|glob|query/.test(normalized)) return "search";
8550
+ return "other";
8551
+ }
8552
+ function receiptFilePath(receipt) {
8553
+ if (!receipt.input || typeof receipt.input !== "object" || Array.isArray(receipt.input)) {
8554
+ return void 0;
8555
+ }
8556
+ const input = receipt.input;
8557
+ for (const key of ["file_path", "filePath", "path", "filename", "target_file", "targetFile"]) {
8558
+ const value = input[key];
8559
+ if (typeof value === "string" && value.trim()) return value.trim();
8560
+ }
8561
+ return void 0;
8562
+ }
8563
+ function addToolActivity(current, receipt) {
8564
+ const next = {
8565
+ ...current ?? emptyActivityTotals(),
8566
+ filesTouched: [...current?.filesTouched ?? []]
8567
+ };
8568
+ const kind = toolActivityKind(receipt.name);
8569
+ if (kind === "read") {
8570
+ next.reads += 1;
8571
+ next.linesRead += receipt.outputLines ?? 0;
8572
+ } else if (kind === "write") {
8573
+ next.writes += 1;
8574
+ next.linesWritten += receipt.inputLines ?? 0;
8575
+ } else if (kind === "edit") {
8576
+ next.edits += 1;
8577
+ next.linesAdded += receipt.addedLines ?? receipt.newLines ?? 0;
8578
+ next.linesRemoved += receipt.removedLines ?? receipt.oldLines ?? 0;
8579
+ } else if (kind === "terminal") next.terminalCalls += 1;
8580
+ else if (kind === "web") next.webCalls += 1;
8581
+ else if (kind === "search") next.searches += 1;
8582
+ else next.otherCalls += 1;
8583
+ const filePath = receiptFilePath(receipt);
8584
+ if (filePath && !next.filesTouched.includes(filePath) && next.filesTouched.length < TOUCHED_FILE_LIMIT) {
8585
+ next.filesTouched.push(filePath);
8586
+ }
8587
+ return next;
8588
+ }
8078
8589
  function clampPct(pct) {
8079
8590
  if (!Number.isFinite(pct)) return 0;
8080
8591
  return Math.max(0, Math.min(100, pct));
@@ -8092,6 +8603,7 @@ var AgentStatusTracker = class {
8092
8603
  // Leader tracking
8093
8604
  leaderStatus = "idle";
8094
8605
  leaderCurrentTool;
8606
+ leaderCurrentTask;
8095
8607
  leaderIterations = 0;
8096
8608
  leaderToolCalls = 0;
8097
8609
  leaderCostUsd = 0;
@@ -8101,10 +8613,22 @@ var AgentStatusTracker = class {
8101
8613
  leaderModel;
8102
8614
  leaderPartialText = "";
8103
8615
  leaderStartedAt;
8616
+ leaderRecentTools = [];
8617
+ leaderRecentMail = [];
8618
+ leaderTodos = [];
8619
+ leaderLatestPrompt;
8620
+ leaderLatestPromptAt;
8621
+ leaderActivity = emptyActivityTotals();
8622
+ leaderPendingTools = /* @__PURE__ */ new Map();
8623
+ subagentPendingTools = /* @__PURE__ */ new Map();
8624
+ seenIncomingMail = /* @__PURE__ */ new Set();
8625
+ seenOutgoingMail = /* @__PURE__ */ new Set();
8104
8626
  unsubscribers = [];
8105
8627
  onUpdate;
8106
8628
  sweepTimer = null;
8107
8629
  partialTimer = null;
8630
+ /** Serialize registry writes so concurrent flush() calls don't race. */
8631
+ flushPromise = null;
8108
8632
  constructor(opts) {
8109
8633
  this.events = opts.events;
8110
8634
  this.registry = opts.registry;
@@ -8127,6 +8651,13 @@ var AgentStatusTracker = class {
8127
8651
  this.markLeaderStarted(p?.at);
8128
8652
  this.captureLeaderContext(p?.ctx);
8129
8653
  if (p?.model) this.leaderModel = p.model;
8654
+ if (p?.inputText?.trim()) {
8655
+ const prompt = boundedText(p.inputText, PROMPT_TEXT_CAP);
8656
+ this.leaderLatestPrompt = prompt;
8657
+ this.leaderLatestPromptAt = p.at ? Date.parse(p.at) : Date.now();
8658
+ if (!Number.isFinite(this.leaderLatestPromptAt)) this.leaderLatestPromptAt = Date.now();
8659
+ this.leaderCurrentTask = boundedText(p.inputText, TASK_TEXT_CAP);
8660
+ }
8130
8661
  this.leaderStatus = "running";
8131
8662
  this.leaderIterations++;
8132
8663
  this.flush();
@@ -8155,6 +8686,7 @@ var AgentStatusTracker = class {
8155
8686
  this.captureLeaderContext(p?.ctx);
8156
8687
  this.leaderStatus = p?.status === "failed" ? "error" : "idle";
8157
8688
  this.leaderCurrentTool = void 0;
8689
+ this.leaderCurrentTask = void 0;
8158
8690
  this.leaderPartialText = "";
8159
8691
  if (this.leaderStatus === "idle") this.leaderStartedAt = void 0;
8160
8692
  this.flush();
@@ -8166,10 +8698,18 @@ var AgentStatusTracker = class {
8166
8698
  this.captureLeaderContext(p?.ctx);
8167
8699
  this.leaderStatus = "error";
8168
8700
  this.leaderCurrentTool = void 0;
8701
+ this.leaderCurrentTask = void 0;
8169
8702
  this.leaderPartialText = "";
8170
8703
  this.flush();
8171
8704
  })
8172
8705
  );
8706
+ this.unsubscribers.push(
8707
+ on("iteration.completed", (_event, payload) => {
8708
+ const p = payload;
8709
+ this.captureLeaderContext(p?.ctx);
8710
+ this.flush();
8711
+ })
8712
+ );
8173
8713
  this.unsubscribers.push(
8174
8714
  on("tool.started", (_event, payload) => {
8175
8715
  const p = payload;
@@ -8177,13 +8717,26 @@ var AgentStatusTracker = class {
8177
8717
  this.markLeaderStarted();
8178
8718
  this.leaderCurrentTool = p.name;
8179
8719
  this.leaderToolCalls++;
8720
+ this.leaderPendingTools.set(p.id ?? p.name, {
8721
+ name: p.name,
8722
+ input: p.input,
8723
+ startedAt: Date.now()
8724
+ });
8180
8725
  }
8181
8726
  this.leaderStatus = "running";
8182
8727
  this.flush();
8183
8728
  })
8184
8729
  );
8185
8730
  this.unsubscribers.push(
8186
- on("tool.executed", () => {
8731
+ on("tool.executed", (_event, payload) => {
8732
+ const p = payload;
8733
+ if (p?.name) {
8734
+ const key = p.id ?? p.name;
8735
+ const receipt = completedToolReceipt(p, this.leaderPendingTools.get(key));
8736
+ this.leaderRecentTools = [receipt, ...this.leaderRecentTools].slice(0, RECENT_TOOL_LIMIT);
8737
+ this.leaderActivity = addToolActivity(this.leaderActivity, receipt);
8738
+ this.leaderPendingTools.delete(key);
8739
+ }
8187
8740
  this.leaderCurrentTool = void 0;
8188
8741
  this.flush();
8189
8742
  })
@@ -8195,6 +8748,36 @@ var AgentStatusTracker = class {
8195
8748
  this.flush();
8196
8749
  })
8197
8750
  );
8751
+ const recordMail = (direction, payload) => {
8752
+ const p = payload;
8753
+ if (!p?.messageId) return;
8754
+ const seen = direction === "incoming" ? this.seenIncomingMail : this.seenOutgoingMail;
8755
+ if (seen.has(p.messageId)) return;
8756
+ seen.add(p.messageId);
8757
+ const receipt = {
8758
+ id: p.messageId,
8759
+ direction,
8760
+ from: p.from ?? "?",
8761
+ to: p.to ?? (direction === "incoming" ? "leader" : "?"),
8762
+ type: p.type ?? "note",
8763
+ subject: p.subject ?? "Message",
8764
+ at: Date.now()
8765
+ };
8766
+ const directAgentId = direction === "outgoing" ? p.from : p.to;
8767
+ const entry = directAgentId ? this.agents.get(directAgentId) : void 0;
8768
+ if (entry) {
8769
+ entry.recentMail = [receipt, ...entry.recentMail ?? []].slice(0, RECENT_MAIL_LIMIT);
8770
+ entry.activity = addMailTotal(entry.activity, direction);
8771
+ } else {
8772
+ this.leaderRecentMail = [receipt, ...this.leaderRecentMail].slice(0, RECENT_MAIL_LIMIT);
8773
+ this.leaderActivity = addMailTotal(this.leaderActivity, direction);
8774
+ }
8775
+ this.flush();
8776
+ };
8777
+ this.unsubscribers.push(
8778
+ on("mailbox.message_sent", (_event, payload) => recordMail("outgoing", payload)),
8779
+ on("mailbox.received", (_event, payload) => recordMail("incoming", payload))
8780
+ );
8198
8781
  this.unsubscribers.push(
8199
8782
  on("llm.stream_started", () => {
8200
8783
  this.markLeaderStarted();
@@ -8255,7 +8838,15 @@ var AgentStatusTracker = class {
8255
8838
  let entry = this.agents.get(id);
8256
8839
  if (!entry) {
8257
8840
  const now = (/* @__PURE__ */ new Date()).toISOString();
8258
- entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, startedAt: now, lastActivityAt: now };
8841
+ entry = {
8842
+ id,
8843
+ name: id,
8844
+ status: "idle",
8845
+ iterations: 0,
8846
+ toolCalls: 0,
8847
+ startedAt: now,
8848
+ lastActivityAt: now
8849
+ };
8259
8850
  this.agents.set(id, entry);
8260
8851
  }
8261
8852
  entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -8268,6 +8859,8 @@ var AgentStatusTracker = class {
8268
8859
  const entry = touch(p.subagentId);
8269
8860
  entry.name = p.name?.trim() || entry.name;
8270
8861
  if (p.model) entry.model = p.model;
8862
+ if (p.taskId) entry.taskId = p.taskId;
8863
+ if (p.description?.trim()) entry.currentTask = boundedText(p.description, TASK_TEXT_CAP);
8271
8864
  if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
8272
8865
  entry.status = "running";
8273
8866
  this.flush();
@@ -8288,19 +8881,41 @@ var AgentStatusTracker = class {
8288
8881
  if (!p?.subagentId) return;
8289
8882
  const entry = touch(p.subagentId);
8290
8883
  entry.status = "running";
8884
+ if (p.taskId) entry.taskId = p.taskId;
8885
+ if (p.description?.trim()) entry.currentTask = boundedText(p.description, TASK_TEXT_CAP);
8291
8886
  if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
8292
8887
  entry.iterations++;
8293
8888
  this.flush();
8294
8889
  })
8295
8890
  );
8891
+ this.unsubscribers.push(
8892
+ on("subagent.tool_started", (_e, payload) => {
8893
+ const p = payload;
8894
+ if (!p?.subagentId || !p.name) return;
8895
+ const entry = touch(p.subagentId);
8896
+ entry.status = "running";
8897
+ entry.currentTool = p.name;
8898
+ this.subagentPendingTools.set(`${p.subagentId}:${p.id ?? p.name}`, {
8899
+ name: p.name,
8900
+ input: p.input,
8901
+ startedAt: Date.now()
8902
+ });
8903
+ this.flush();
8904
+ })
8905
+ );
8296
8906
  this.unsubscribers.push(
8297
8907
  on("subagent.tool_executed", (_e, payload) => {
8298
8908
  const p = payload;
8299
- if (!p?.subagentId) return;
8909
+ if (!p?.subagentId || !p.name) return;
8300
8910
  const entry = touch(p.subagentId);
8301
8911
  entry.status = "running";
8302
8912
  if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
8303
- entry.currentTool = p.name;
8913
+ const key = `${p.subagentId}:${p.id ?? p.name}`;
8914
+ const receipt = completedToolReceipt(p, this.subagentPendingTools.get(key));
8915
+ entry.recentTools = [receipt, ...entry.recentTools ?? []].slice(0, RECENT_TOOL_LIMIT);
8916
+ entry.activity = addToolActivity(entry.activity, receipt);
8917
+ this.subagentPendingTools.delete(key);
8918
+ entry.currentTool = void 0;
8304
8919
  entry.toolCalls++;
8305
8920
  this.flush();
8306
8921
  })
@@ -8330,6 +8945,8 @@ var AgentStatusTracker = class {
8330
8945
  if (!entry) return;
8331
8946
  entry.status = p.status === "failed" || p.status === "timeout" ? "error" : "idle";
8332
8947
  entry.currentTool = void 0;
8948
+ entry.currentTask = void 0;
8949
+ entry.taskId = void 0;
8333
8950
  entry.partialText = void 0;
8334
8951
  if (typeof p.iterations === "number") entry.iterations = p.iterations;
8335
8952
  if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
@@ -8409,6 +9026,7 @@ var AgentStatusTracker = class {
8409
9026
  startedAt: this.leaderStartedAt,
8410
9027
  status: this.leaderStatus,
8411
9028
  currentTool: this.leaderCurrentTool,
9029
+ currentTask: this.leaderCurrentTask,
8412
9030
  iterations: this.leaderIterations,
8413
9031
  toolCalls: this.leaderToolCalls,
8414
9032
  costUsd: this.leaderCostUsd,
@@ -8417,6 +9035,12 @@ var AgentStatusTracker = class {
8417
9035
  ctxPct: this.leaderCtxPct,
8418
9036
  model: this.leaderModel,
8419
9037
  partialText: this.leaderPartialText || void 0,
9038
+ recentTools: this.leaderRecentTools,
9039
+ recentMail: this.leaderRecentMail,
9040
+ todos: this.leaderTodos,
9041
+ latestPrompt: this.leaderLatestPrompt,
9042
+ latestPromptAt: this.leaderLatestPromptAt,
9043
+ activity: this.leaderActivity,
8420
9044
  lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
8421
9045
  };
8422
9046
  const allAgents = [leaderEntry, ...this.agents.values()];
@@ -8428,12 +9052,21 @@ var AgentStatusTracker = class {
8428
9052
  });
8429
9053
  } catch {
8430
9054
  }
8431
- this.registry.updateAgents(allAgents).then(() => {
9055
+ const write = this.registry.updateAgents(allAgents);
9056
+ const chain = write.then(() => {
8432
9057
  try {
8433
9058
  this.onUpdate?.();
8434
9059
  } catch {
8435
9060
  }
8436
- }).catch(() => void 0);
9061
+ });
9062
+ if (this.flushPromise) {
9063
+ this.flushPromise = this.flushPromise.then(
9064
+ () => chain,
9065
+ () => chain
9066
+ ).catch(() => void 0);
9067
+ } else {
9068
+ this.flushPromise = chain.catch(() => void 0);
9069
+ }
8437
9070
  }
8438
9071
  currentSessionId() {
8439
9072
  return typeof this.sessionId === "function" ? this.sessionId() : this.sessionId;
@@ -8455,6 +9088,8 @@ var AgentStatusTracker = class {
8455
9088
  if (typeof ctx !== "object" || ctx === null) return;
8456
9089
  const c = ctx;
8457
9090
  if (typeof c.model === "string" && c.model.length > 0) this.leaderModel = c.model;
9091
+ const todos = compactTodos(c.todos);
9092
+ if (todos !== void 0) this.leaderTodos = todos;
8458
9093
  const metaLimit = c.meta?.["effectiveMaxContext"];
8459
9094
  const providerMax = c.provider?.capabilities?.maxContext;
8460
9095
  const maxContext = typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : void 0;
@@ -8610,9 +9245,6 @@ var DefaultSessionRewinder = class {
8610
9245
  const snapshotsToRevert = [];
8611
9246
  for (let i = targetIdx + 1; i < events.length; i++) {
8612
9247
  const event = expectDefined(events[i]);
8613
- if (event.type === "checkpoint") {
8614
- break;
8615
- }
8616
9248
  if (event.type === "file_snapshot") {
8617
9249
  const snapshotEvent = event;
8618
9250
  if (snapshotEvent.promptIndex >= checkpointIndex) {
@@ -8634,11 +9266,11 @@ var DefaultSessionRewinder = class {
8634
9266
  checkpoints.push({ promptIndex: event.promptIndex, ts: event.ts });
8635
9267
  }
8636
9268
  }
8637
- if (checkpoints.length === 0) {
9269
+ if (checkpoints.length === 0 || n <= 0) {
8638
9270
  return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };
8639
9271
  }
8640
9272
  checkpoints.sort((a, b) => b.promptIndex - a.promptIndex);
8641
- const targetIndex = checkpoints[n]?.promptIndex ?? 0;
9273
+ const targetIndex = checkpoints[n - 1]?.promptIndex ?? 0;
8642
9274
  const snapshotsToRevert = [];
8643
9275
  let shouldRevert = false;
8644
9276
  for (const event of events) {
@@ -8719,6 +9351,16 @@ async function revertSnapshots(snapshots, projectRoot) {
8719
9351
  return { revertedFiles, errors };
8720
9352
  }
8721
9353
 
9354
+ // src/storage/session-rewind-apply.ts
9355
+ async function applyRewindToConversation(opts) {
9356
+ const { session, state, sessionsDir, promptIndex, revertedFiles } = opts;
9357
+ const removedEvents = await session.truncateToCheckpoint(promptIndex, revertedFiles ?? []);
9358
+ const store = new DefaultSessionStore({ dir: sessionsDir });
9359
+ const data = await store.load(session.id);
9360
+ state.replaceMessages(data.messages);
9361
+ return { removedEvents, messageCount: data.messages.length };
9362
+ }
9363
+
8722
9364
  // src/storage/todos-checkpoint.ts
8723
9365
  import * as fsp9 from "node:fs/promises";
8724
9366
  async function loadTodosCheckpoint(filePath, events, traceId) {
@@ -10028,6 +10670,184 @@ function goalTag(goalFile) {
10028
10670
  return `${GOAL_BOARD_TAG_PREFIX}${goal}`;
10029
10671
  }
10030
10672
 
10673
+ // src/storage/goal-coordination.ts
10674
+ import { updateTask } from "@wrongstack/kanban";
10675
+ var DONE_MARKER = /\[done:\s*([^\]]+)\]/gi;
10676
+ var DONE_PREFIX = /^\s*(?:✅|\[[x✓]\]|\(done\))\s*/i;
10677
+ function parseCompletedGoalDeliverables(finalText, deliverables) {
10678
+ if (!finalText || deliverables.length === 0) return [];
10679
+ const seen = /* @__PURE__ */ new Set();
10680
+ const completed = [];
10681
+ for (const match of finalText.matchAll(DONE_MARKER)) {
10682
+ const raw = match[1]?.trim();
10683
+ if (!raw) continue;
10684
+ const numeric = /^\d+$/.test(raw) ? Number.parseInt(raw, 10) : void 0;
10685
+ let index = numeric === void 0 ? -1 : numeric === 0 ? 0 : numeric - 1;
10686
+ if (index < 0 || index >= deliverables.length) {
10687
+ const prefix = raw.toLowerCase();
10688
+ index = deliverables.findIndex(
10689
+ (item) => stripGoalDeliverableMarker(item).toLowerCase().startsWith(prefix)
10690
+ );
10691
+ }
10692
+ if (index < 0 || index >= deliverables.length || seen.has(index)) continue;
10693
+ const source = deliverables[index] ?? "";
10694
+ if (isGoalDeliverableComplete(source)) continue;
10695
+ seen.add(index);
10696
+ completed.push({ index, text: stripGoalDeliverableMarker(source) });
10697
+ }
10698
+ return completed;
10699
+ }
10700
+ function isGoalDeliverableComplete(value) {
10701
+ return DONE_PREFIX.test(value);
10702
+ }
10703
+ function stripGoalDeliverableMarker(value) {
10704
+ return value.replace(DONE_PREFIX, "").trim();
10705
+ }
10706
+ function applyGoalDeliverableCompletions(goal, completed) {
10707
+ if (completed.length === 0) return recomputeGoalProgress(goal);
10708
+ const completedIndices = new Set(completed.map((item) => item.index));
10709
+ let next = {
10710
+ ...goal,
10711
+ deliverables: (goal.deliverables ?? []).map(
10712
+ (item, index) => completedIndices.has(index) && !isGoalDeliverableComplete(item) ? `\u2705 ${item}` : item
10713
+ )
10714
+ };
10715
+ for (const item of completed) {
10716
+ next = appendJournal(next, {
10717
+ source: "deliverable",
10718
+ task: `Deliverable completed: ${item.text}`.slice(0, 240),
10719
+ status: "success",
10720
+ note: `[DONE: ${item.index + 1}]`
10721
+ });
10722
+ }
10723
+ return recomputeGoalProgress(next);
10724
+ }
10725
+ function recomputeGoalProgress(goal) {
10726
+ const deliverables = goal.deliverables ?? [];
10727
+ if (deliverables.length === 0) return goal;
10728
+ const done = deliverables.filter(isGoalDeliverableComplete).length;
10729
+ const progress = Math.round(done / deliverables.length * 100);
10730
+ return recordProgress(goal, progress, `${done}/${deliverables.length} deliverables complete`);
10731
+ }
10732
+ async function coordinateGoalIteration(options) {
10733
+ const current = await loadGoal(options.goalPath, options.events);
10734
+ if (!current) return null;
10735
+ const completed = parseCompletedGoalDeliverables(
10736
+ options.finalText,
10737
+ current.deliverables ?? []
10738
+ );
10739
+ let goal = applyGoalDeliverableCompletions(current, completed);
10740
+ await saveGoal(options.goalPath, goal, options.events);
10741
+ const kanbanUpdatedTaskIds = await refreshGoalKanban(
10742
+ options.projectRoot,
10743
+ goal,
10744
+ completed
10745
+ );
10746
+ goal = await recomputeGoalProgressFromKanban(options.projectRoot, goal);
10747
+ let reached = false;
10748
+ const allComplete = (goal.deliverables?.length ?? 0) > 0 && goal.deliverables?.every(isGoalDeliverableComplete) === true;
10749
+ if (allComplete && options.brain) {
10750
+ const decision = await options.brain.decide({
10751
+ id: `goal-reached-${goal.iterations}`,
10752
+ sessionId: options.sessionId,
10753
+ source: "system",
10754
+ question: "Have all goal deliverables been reached and verified?",
10755
+ context: [
10756
+ `Goal: ${goal.refinedGoal ?? goal.goal}`,
10757
+ `Progress: ${goal.progress ?? 0}%`,
10758
+ "Completed deliverables:",
10759
+ ...(goal.deliverables ?? []).map(
10760
+ (item, index) => `${index + 1}. ${stripGoalDeliverableMarker(item)}`
10761
+ )
10762
+ ].join("\n"),
10763
+ risk: "high",
10764
+ fallback: "continue",
10765
+ options: [
10766
+ {
10767
+ id: "goal_reached",
10768
+ label: "Goal reached",
10769
+ consequence: "Persist the completed goal and stop the eternal loop."
10770
+ },
10771
+ {
10772
+ id: "keep_working",
10773
+ label: "Keep working",
10774
+ consequence: "Leave the goal active for another iteration."
10775
+ }
10776
+ ]
10777
+ });
10778
+ reached = decision.type === "answer" && decision.optionId === "goal_reached";
10779
+ }
10780
+ if (reached) {
10781
+ const reachedAt = (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
10782
+ goal = appendJournal(
10783
+ {
10784
+ ...goal,
10785
+ goalState: "completed",
10786
+ engineState: "stopped",
10787
+ progress: 100,
10788
+ progressNote: "goal reached",
10789
+ reachedAt,
10790
+ reachedNote: "goal reached"
10791
+ },
10792
+ {
10793
+ source: "deliverable",
10794
+ task: "GOAL REACHED",
10795
+ status: "success",
10796
+ note: "goal reached"
10797
+ }
10798
+ );
10799
+ }
10800
+ await saveGoal(options.goalPath, goal, options.events);
10801
+ return { goal, completed, kanbanUpdatedTaskIds, reached };
10802
+ }
10803
+ async function resolveGoalBoard(projectRoot, goal) {
10804
+ const boardId = goal.kanbanBoardId;
10805
+ if (boardId) {
10806
+ const board = await findGoalKanbanBoard(projectRoot, boardId);
10807
+ if (board) return board;
10808
+ }
10809
+ return findGoalBoardByTag(projectRoot, goal);
10810
+ }
10811
+ async function refreshGoalKanban(projectRoot, goal, completed) {
10812
+ if (completed.length === 0) return [];
10813
+ const board = await resolveGoalBoard(projectRoot, goal);
10814
+ if (!board) return [];
10815
+ const doneColumn = board.columns.find((column) => column.title.toLowerCase() === "done");
10816
+ if (!doneColumn) return [];
10817
+ const completedTitles = new Set(completed.map((item) => normalizeTitle(item.text)));
10818
+ const updated = [];
10819
+ for (const task of board.tasks) {
10820
+ if (!completedTitles.has(normalizeTitle(task.title))) continue;
10821
+ const result = await updateTask(
10822
+ projectRoot,
10823
+ board.id,
10824
+ task.id,
10825
+ { columnId: doneColumn.id, status: "completed" },
10826
+ {
10827
+ actor: "goal-coordinator",
10828
+ note: "deliverable-completed: Updated from [DONE:] marker."
10829
+ }
10830
+ );
10831
+ if (result) updated.push(task.id);
10832
+ }
10833
+ return updated;
10834
+ }
10835
+ async function recomputeGoalProgressFromKanban(projectRoot, goal) {
10836
+ const board = await resolveGoalBoard(projectRoot, goal);
10837
+ if (!board || !goal.deliverables?.length) return recomputeGoalProgress(goal);
10838
+ const completedTitles = new Set(
10839
+ board.tasks.filter((task) => task.status === "completed").map((task) => normalizeTitle(task.title))
10840
+ );
10841
+ const deliverables = goal.deliverables.map((item) => {
10842
+ if (isGoalDeliverableComplete(item)) return item;
10843
+ return completedTitles.has(normalizeTitle(item)) ? `\u2705 ${item}` : item;
10844
+ });
10845
+ return recomputeGoalProgress({ ...goal, deliverables });
10846
+ }
10847
+ function normalizeTitle(value) {
10848
+ return stripGoalDeliverableMarker(value).replace(/\s+/g, " ").trim().toLowerCase();
10849
+ }
10850
+
10031
10851
  // src/storage/prompt-store.ts
10032
10852
  import { createHash as createHash7 } from "node:crypto";
10033
10853
  import * as fs14 from "node:fs/promises";
@@ -10646,6 +11466,9 @@ var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
10646
11466
  "user_input",
10647
11467
  "llm_response",
10648
11468
  "tool_result",
11469
+ "message_appended",
11470
+ "message_updated",
11471
+ "messages_replaced",
10649
11472
  "context_snapshot",
10650
11473
  "checkpoint",
10651
11474
  "file_snapshot",
@@ -10666,11 +11489,22 @@ var STANDARD_AUDIT_EVENTS = /* @__PURE__ */ new Set([
10666
11489
  "provider_retry",
10667
11490
  "provider_error",
10668
11491
  "skill_activated",
10669
- "skill_deactivated"
10670
- ]);
10671
- var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set([
10672
- "tool_progress"
11492
+ "skill_deactivated",
11493
+ // Resume markers and fleet lifecycle. These reach disk today only because
11494
+ // every producer appends to the raw SessionWriter and bypasses this bridge —
11495
+ // routed through it while absent from both sets, `isAllowed` would drop them
11496
+ // at the DEFAULT level and the resume timeline would lose them silently.
11497
+ "mode_changed",
11498
+ "agent_spawned",
11499
+ "agent_stopped",
11500
+ "agent_error",
11501
+ "task_created",
11502
+ "task_updated",
11503
+ "task_completed",
11504
+ "task_failed",
11505
+ "side_effect"
10673
11506
  ]);
11507
+ var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set(["tool_progress"]);
10674
11508
  function isAllowed(type, level) {
10675
11509
  if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;
10676
11510
  if (level === "minimal") return false;
@@ -10725,9 +11559,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
10725
11559
  async appendBatch(events) {
10726
11560
  const target = resolveWriter();
10727
11561
  if (!target || events.length === 0) return;
10728
- const allowed = events.filter(
10729
- (e) => isAllowed(e.type, currentLevel) && shouldSample(e)
10730
- );
11562
+ const allowed = events.filter((e) => isAllowed(e.type, currentLevel) && shouldSample(e));
10731
11563
  if (allowed.length === 0) return;
10732
11564
  try {
10733
11565
  await target.appendBatch(allowed);
@@ -10792,9 +11624,12 @@ export {
10792
11624
  ToolAuditLog,
10793
11625
  addPlanItem,
10794
11626
  appendJournal,
11627
+ applyGoalDeliverableCompletions,
11628
+ applyRewindToConversation,
10795
11629
  attachPlanCheckpoint,
10796
11630
  attachTodosCheckpoint,
10797
11631
  clearPlan,
11632
+ coordinateGoalIteration,
10798
11633
  createGoalKanbanBoard,
10799
11634
  createSessionEventBridge,
10800
11635
  deleteGoalKanbanBoard,
@@ -10815,6 +11650,7 @@ export {
10815
11650
  getSessionRegistry,
10816
11651
  goalFilePath,
10817
11652
  hasSessionRegistry,
11653
+ isGoalDeliverableComplete,
10818
11654
  listPlanTemplates,
10819
11655
  loadCompletedWorkCheckpoint,
10820
11656
  loadDirectorState,
@@ -10826,9 +11662,12 @@ export {
10826
11662
  mutatePlan,
10827
11663
  mutateTasks,
10828
11664
  parseAutonomyChoice,
11665
+ parseCompletedGoalDeliverables,
10829
11666
  parseEntries,
10830
11667
  parseProgressFromText,
10831
11668
  promptChecksum,
11669
+ readProviderSnapshot,
11670
+ recomputeGoalProgress,
10832
11671
  recordProgress,
10833
11672
  removePlanItem,
10834
11673
  resolveAuditLevel,
@@ -10842,6 +11681,7 @@ export {
10842
11681
  saveTodosCheckpoint,
10843
11682
  setPlanItemStatus,
10844
11683
  setProgress,
11684
+ stripGoalDeliverableMarker,
10845
11685
  summarizeUsage,
10846
11686
  watchProviderConfig
10847
11687
  };