@goondocks/myco 0.20.2 → 0.21.1

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 (216) hide show
  1. package/bin/myco-run +68 -7
  2. package/dist/agent-eval-YK2VP2S4.js +356 -0
  3. package/dist/agent-eval-YK2VP2S4.js.map +1 -0
  4. package/dist/{agent-run-X25Q2A6T.js → agent-run-GEJBD2YD.js} +10 -8
  5. package/dist/{agent-run-X25Q2A6T.js.map → agent-run-GEJBD2YD.js.map} +1 -1
  6. package/dist/{agent-tasks-7B6OFERB.js → agent-tasks-5XSRGTRX.js} +10 -8
  7. package/dist/{agent-tasks-7B6OFERB.js.map → agent-tasks-5XSRGTRX.js.map} +1 -1
  8. package/dist/{chunk-OD4AA7PV.js → chunk-53RPGOEN.js} +56 -8
  9. package/dist/chunk-53RPGOEN.js.map +1 -0
  10. package/dist/chunk-54SXG5HF.js +26 -0
  11. package/dist/chunk-54SXG5HF.js.map +1 -0
  12. package/dist/{chunk-XATDZX7U.js → chunk-6ALVMIB4.js} +19 -5
  13. package/dist/{chunk-XATDZX7U.js.map → chunk-6ALVMIB4.js.map} +1 -1
  14. package/dist/{chunk-FLLBJLHM.js → chunk-6C6QZ4PM.js} +9 -5
  15. package/dist/chunk-6C6QZ4PM.js.map +1 -0
  16. package/dist/{chunk-CCRGY3QW.js → chunk-AUIXX33A.js} +24 -95
  17. package/dist/chunk-AUIXX33A.js.map +1 -0
  18. package/dist/chunk-CISWUP5W.js +101 -0
  19. package/dist/chunk-CISWUP5W.js.map +1 -0
  20. package/dist/{chunk-MYOZLMB2.js → chunk-DTWUHHFI.js} +576 -20
  21. package/dist/chunk-DTWUHHFI.js.map +1 -0
  22. package/dist/chunk-EEOJWLMP.js +582 -0
  23. package/dist/chunk-EEOJWLMP.js.map +1 -0
  24. package/dist/{chunk-XG5RRUYF.js → chunk-ENZR5NG7.js} +2 -2
  25. package/dist/{chunk-6RFZWV4R.js → chunk-FCJ5JV54.js} +1 -1
  26. package/dist/{chunk-6RFZWV4R.js.map → chunk-FCJ5JV54.js.map} +1 -1
  27. package/dist/{chunk-US4LNCAT.js → chunk-IPPMYQ2Y.js} +5 -1
  28. package/dist/chunk-IPPMYQ2Y.js.map +1 -0
  29. package/dist/{chunk-VVNL26WX.js → chunk-KTTSXYEK.js} +22 -10
  30. package/dist/chunk-KTTSXYEK.js.map +1 -0
  31. package/dist/chunk-LQIPXVDH.js +17 -0
  32. package/dist/chunk-LQIPXVDH.js.map +1 -0
  33. package/dist/{chunk-DCSGJ7W4.js → chunk-N2DGFACQ.js} +3 -3
  34. package/dist/chunk-N7Z3LUEZ.js +858 -0
  35. package/dist/chunk-N7Z3LUEZ.js.map +1 -0
  36. package/dist/{chunk-2PDWCDKY.js → chunk-NFO7BRCO.js} +10 -7
  37. package/dist/{chunk-2PDWCDKY.js.map → chunk-NFO7BRCO.js.map} +1 -1
  38. package/dist/{chunk-Q36VMZST.js → chunk-OTQH5KZW.js} +89 -38
  39. package/dist/chunk-OTQH5KZW.js.map +1 -0
  40. package/dist/chunk-OUJSQSKE.js +113 -0
  41. package/dist/chunk-OUJSQSKE.js.map +1 -0
  42. package/dist/chunk-OZ3FBAK5.js +50 -0
  43. package/dist/chunk-OZ3FBAK5.js.map +1 -0
  44. package/dist/chunk-QATYARI5.js +408 -0
  45. package/dist/chunk-QATYARI5.js.map +1 -0
  46. package/dist/{chunk-KESLPBKV.js → chunk-QDLVIW2O.js} +4 -4
  47. package/dist/{chunk-5XIVBO25.js → chunk-QLLBJEM7.js} +6 -2
  48. package/dist/chunk-QLLBJEM7.js.map +1 -0
  49. package/dist/{chunk-EVDQKYCG.js → chunk-RQSJLWP4.js} +13 -2
  50. package/dist/chunk-RQSJLWP4.js.map +1 -0
  51. package/dist/{chunk-BPRIYNLE.js → chunk-TKAJ3JVF.js} +3 -3
  52. package/dist/chunk-TSM6VESW.js +25 -0
  53. package/dist/chunk-TSM6VESW.js.map +1 -0
  54. package/dist/{chunk-6X2ERTQV.js → chunk-USVFEWYL.js} +6 -4
  55. package/dist/{chunk-6X2ERTQV.js.map → chunk-USVFEWYL.js.map} +1 -1
  56. package/dist/{chunk-JZGN33AY.js → chunk-VRI56337.js} +4 -4
  57. package/dist/chunk-X2IRGXGF.js +14103 -0
  58. package/dist/chunk-X2IRGXGF.js.map +1 -0
  59. package/dist/{chunk-FMRZ26U5.js → chunk-X3IGT5RV.js} +5 -2
  60. package/dist/{chunk-FMRZ26U5.js.map → chunk-X3IGT5RV.js.map} +1 -1
  61. package/dist/{chunk-KHT24OWC.js → chunk-YDUOSRGD.js} +8 -94
  62. package/dist/{chunk-KHT24OWC.js.map → chunk-YDUOSRGD.js.map} +1 -1
  63. package/dist/{chunk-NGROSFOH.js → chunk-Z66IT5KL.js} +14 -9
  64. package/dist/chunk-Z66IT5KL.js.map +1 -0
  65. package/dist/{cli-GGPWH4UO.js → cli-HSLIG7EX.js} +50 -43
  66. package/dist/cli-HSLIG7EX.js.map +1 -0
  67. package/dist/{client-YXQUTXVZ.js → client-Z43DNLJH.js} +4 -4
  68. package/dist/{config-OMCYHG2S.js → config-VC4ACP42.js} +6 -4
  69. package/dist/{config-OMCYHG2S.js.map → config-VC4ACP42.js.map} +1 -1
  70. package/dist/{detect-PXNM6TA7.js → detect-7NUD5B5R.js} +2 -2
  71. package/dist/{detect-providers-5KOPZ7J2.js → detect-providers-ILLQZROY.js} +4 -4
  72. package/dist/{doctor-5JXJ36KA.js → doctor-HJCWHAU4.js} +49 -16
  73. package/dist/doctor-HJCWHAU4.js.map +1 -0
  74. package/dist/executor-DO6QFC6G.js +45 -0
  75. package/dist/{init-LMYOVZAV.js → init-4KVK7W2E.js} +16 -14
  76. package/dist/{init-LMYOVZAV.js.map → init-4KVK7W2E.js.map} +1 -1
  77. package/dist/{installer-FS257JRZ.js → installer-N4UTEACX.js} +6 -4
  78. package/dist/{llm-TH4NLIRM.js → llm-AGVEF5XD.js} +5 -4
  79. package/dist/{loader-CQYTFHEW.js → loader-LX7TFRM6.js} +5 -3
  80. package/dist/{loader-NOMBJUPW.js → loader-UDNUMEDA.js} +5 -3
  81. package/dist/{main-YTBVRTBI.js → main-4J4QZZTZ.js} +2518 -656
  82. package/dist/main-4J4QZZTZ.js.map +1 -0
  83. package/dist/{open-HG2DX6RN.js → open-7TXJQM3H.js} +10 -8
  84. package/dist/{open-HG2DX6RN.js.map → open-7TXJQM3H.js.map} +1 -1
  85. package/dist/{post-compact-JSECI44W.js → post-compact-7AEFVCZS.js} +8 -8
  86. package/dist/{post-tool-use-POGPTJBA.js → post-tool-use-TZINWWDH.js} +11 -9
  87. package/dist/post-tool-use-TZINWWDH.js.map +1 -0
  88. package/dist/{post-tool-use-failure-OT7BFWQW.js → post-tool-use-failure-TCFEU2GI.js} +8 -8
  89. package/dist/{pre-compact-OXVODKH4.js → pre-compact-LO2VZCGR.js} +8 -8
  90. package/dist/{provider-check-43LAMSMH.js → provider-check-ZEV5P4KM.js} +4 -4
  91. package/dist/{registry-U4CHXK6R.js → registry-F3THYC5M.js} +6 -4
  92. package/dist/{remove-N7ZPELFU.js → remove-F77AAALE.js} +12 -10
  93. package/dist/{remove-N7ZPELFU.js.map → remove-F77AAALE.js.map} +1 -1
  94. package/dist/{restart-ADG5GBTB.js → restart-UEFDPMLT.js} +11 -9
  95. package/dist/{restart-ADG5GBTB.js.map → restart-UEFDPMLT.js.map} +1 -1
  96. package/dist/{search-AHZEUNRR.js → search-NHNVUAQQ.js} +11 -9
  97. package/dist/{search-AHZEUNRR.js.map → search-NHNVUAQQ.js.map} +1 -1
  98. package/dist/{server-AGVYZVP5.js → server-AZJSTQEK.js} +369 -270
  99. package/dist/server-AZJSTQEK.js.map +1 -0
  100. package/dist/{session-6IU4AXYP.js → session-3HLC5KOD.js} +11 -9
  101. package/dist/{session-6IU4AXYP.js.map → session-3HLC5KOD.js.map} +1 -1
  102. package/dist/{session-end-FT27DWYZ.js → session-end-FS46UARX.js} +7 -7
  103. package/dist/session-start-46KPFV2H.js +134 -0
  104. package/dist/session-start-46KPFV2H.js.map +1 -0
  105. package/dist/{setup-llm-77MP4I2G.js → setup-llm-JMWSNQ2C.js} +11 -9
  106. package/dist/{setup-llm-77MP4I2G.js.map → setup-llm-JMWSNQ2C.js.map} +1 -1
  107. package/dist/src/agent/definitions/agent.yaml +9 -5
  108. package/dist/src/agent/definitions/tasks/cortex-instructions.yaml +115 -0
  109. package/dist/src/agent/definitions/tasks/cortex-prompt-builder.yaml +67 -0
  110. package/dist/src/agent/definitions/tasks/digest-only.yaml +1 -1
  111. package/dist/src/agent/definitions/tasks/extract-only.yaml +1 -1
  112. package/dist/src/agent/definitions/tasks/review-session.yaml +10 -39
  113. package/dist/src/agent/definitions/tasks/skill-evolve.yaml +181 -25
  114. package/dist/src/agent/definitions/tasks/skill-generate.yaml +21 -7
  115. package/dist/src/agent/definitions/tasks/skill-survey.yaml +2 -6
  116. package/dist/src/agent/definitions/tasks/supersession-sweep.yaml +1 -1
  117. package/dist/src/agent/definitions/tasks/title-summary.yaml +12 -19
  118. package/dist/src/agent/definitions/tasks/{full-intelligence.yaml → vault-evolve.yaml} +74 -129
  119. package/dist/src/agent/definitions/tasks/vault-seed.yaml +370 -0
  120. package/dist/src/agent/prompts/agent.md +12 -38
  121. package/dist/src/cli.js +1 -1
  122. package/dist/src/daemon/main.js +1 -1
  123. package/dist/src/hooks/post-tool-use.js +1 -1
  124. package/dist/src/hooks/session-end.js +1 -1
  125. package/dist/src/hooks/session-start.js +1 -1
  126. package/dist/src/hooks/stop.js +1 -1
  127. package/dist/src/hooks/user-prompt-submit.js +1 -1
  128. package/dist/src/mcp/server.js +1 -1
  129. package/dist/src/symbionts/manifests/claude-code.yaml +4 -0
  130. package/dist/src/symbionts/manifests/opencode.yaml +7 -0
  131. package/dist/src/symbionts/manifests/pi.yaml +22 -0
  132. package/dist/src/symbionts/templates/agents-starter.md +1 -1
  133. package/dist/src/symbionts/templates/pi/package.json +6 -0
  134. package/dist/src/symbionts/templates/pi/plugin.ts +559 -0
  135. package/dist/{stats-NVPWOYTE.js → stats-MKMETHMA.js} +11 -9
  136. package/dist/{stats-NVPWOYTE.js.map → stats-MKMETHMA.js.map} +1 -1
  137. package/dist/{stop-ZPIKVLH4.js → stop-OUEX6KA4.js} +7 -7
  138. package/dist/{stop-failure-2PX67YJC.js → stop-failure-2BWVNZEG.js} +8 -8
  139. package/dist/{subagent-start-UUE6EHQD.js → subagent-start-J4VV6DEE.js} +8 -8
  140. package/dist/{subagent-stop-KQWWWPE6.js → subagent-stop-JMLVEPIA.js} +8 -8
  141. package/dist/{task-completed-WMHOFQ7B.js → task-completed-65CHMMKA.js} +8 -8
  142. package/dist/{team-LRZ6GTQK.js → team-U2LDKIS4.js} +7 -5
  143. package/dist/{turns-YFNI5CQC.js → turns-HU2CTZAP.js} +2 -2
  144. package/dist/ui/assets/index-BUGor9dk.js +842 -0
  145. package/dist/ui/assets/index-_OP4ifzH.css +1 -0
  146. package/dist/ui/index.html +2 -2
  147. package/dist/{update-O6V4RC4W.js → update-ZSHVXWSQ.js} +12 -10
  148. package/dist/{update-O6V4RC4W.js.map → update-ZSHVXWSQ.js.map} +1 -1
  149. package/dist/{user-prompt-submit-N36KUPHI.js → user-prompt-submit-APMO6FVU.js} +10 -9
  150. package/dist/{user-prompt-submit-N36KUPHI.js.map → user-prompt-submit-APMO6FVU.js.map} +1 -1
  151. package/dist/{verify-LXPV7NYG.js → verify-R76ZFJSZ.js} +8 -5
  152. package/dist/{verify-LXPV7NYG.js.map → verify-R76ZFJSZ.js.map} +1 -1
  153. package/dist/{version-XMPPJQHR.js → version-TXPPS3L5.js} +2 -2
  154. package/dist/version-TXPPS3L5.js.map +1 -0
  155. package/package.json +3 -1
  156. package/skills/myco/SKILL.md +16 -1
  157. package/skills/myco/references/cli-usage.md +1 -1
  158. package/skills/myco-rules/SKILL.md +94 -0
  159. package/skills/{rules → myco-rules}/references/rules-bad-example.md +1 -1
  160. package/skills/{rules → myco-rules}/references/rules-good-example.md +1 -1
  161. package/dist/chunk-4YFKBL3F.js +0 -195
  162. package/dist/chunk-4YFKBL3F.js.map +0 -1
  163. package/dist/chunk-5XIVBO25.js.map +0 -1
  164. package/dist/chunk-CCRGY3QW.js.map +0 -1
  165. package/dist/chunk-CUDIZJY7.js +0 -36
  166. package/dist/chunk-CUDIZJY7.js.map +0 -1
  167. package/dist/chunk-EVDQKYCG.js.map +0 -1
  168. package/dist/chunk-FLLBJLHM.js.map +0 -1
  169. package/dist/chunk-MYOZLMB2.js.map +0 -1
  170. package/dist/chunk-NGROSFOH.js.map +0 -1
  171. package/dist/chunk-OD4AA7PV.js.map +0 -1
  172. package/dist/chunk-Q36VMZST.js.map +0 -1
  173. package/dist/chunk-US4LNCAT.js.map +0 -1
  174. package/dist/chunk-UYMFCYBF.js +0 -2326
  175. package/dist/chunk-UYMFCYBF.js.map +0 -1
  176. package/dist/chunk-VVNL26WX.js.map +0 -1
  177. package/dist/cli-GGPWH4UO.js.map +0 -1
  178. package/dist/doctor-5JXJ36KA.js.map +0 -1
  179. package/dist/executor-HWW2QNZQ.js +0 -2472
  180. package/dist/executor-HWW2QNZQ.js.map +0 -1
  181. package/dist/main-YTBVRTBI.js.map +0 -1
  182. package/dist/post-tool-use-POGPTJBA.js.map +0 -1
  183. package/dist/server-AGVYZVP5.js.map +0 -1
  184. package/dist/session-start-LAFICHII.js +0 -189
  185. package/dist/session-start-LAFICHII.js.map +0 -1
  186. package/dist/src/agent/definitions/tasks/graph-maintenance.yaml +0 -93
  187. package/dist/ui/assets/index-C2JuNtRB.css +0 -1
  188. package/dist/ui/assets/index-JLVaQKV2.js +0 -832
  189. package/skills/myco-curate/SKILL.md +0 -86
  190. package/skills/rules/SKILL.md +0 -214
  191. /package/dist/{chunk-XG5RRUYF.js.map → chunk-ENZR5NG7.js.map} +0 -0
  192. /package/dist/{chunk-DCSGJ7W4.js.map → chunk-N2DGFACQ.js.map} +0 -0
  193. /package/dist/{chunk-KESLPBKV.js.map → chunk-QDLVIW2O.js.map} +0 -0
  194. /package/dist/{chunk-BPRIYNLE.js.map → chunk-TKAJ3JVF.js.map} +0 -0
  195. /package/dist/{chunk-JZGN33AY.js.map → chunk-VRI56337.js.map} +0 -0
  196. /package/dist/{client-YXQUTXVZ.js.map → client-Z43DNLJH.js.map} +0 -0
  197. /package/dist/{detect-PXNM6TA7.js.map → detect-7NUD5B5R.js.map} +0 -0
  198. /package/dist/{detect-providers-5KOPZ7J2.js.map → detect-providers-ILLQZROY.js.map} +0 -0
  199. /package/dist/{installer-FS257JRZ.js.map → executor-DO6QFC6G.js.map} +0 -0
  200. /package/dist/{llm-TH4NLIRM.js.map → installer-N4UTEACX.js.map} +0 -0
  201. /package/dist/{loader-CQYTFHEW.js.map → llm-AGVEF5XD.js.map} +0 -0
  202. /package/dist/{loader-NOMBJUPW.js.map → loader-LX7TFRM6.js.map} +0 -0
  203. /package/dist/{provider-check-43LAMSMH.js.map → loader-UDNUMEDA.js.map} +0 -0
  204. /package/dist/{post-compact-JSECI44W.js.map → post-compact-7AEFVCZS.js.map} +0 -0
  205. /package/dist/{post-tool-use-failure-OT7BFWQW.js.map → post-tool-use-failure-TCFEU2GI.js.map} +0 -0
  206. /package/dist/{pre-compact-OXVODKH4.js.map → pre-compact-LO2VZCGR.js.map} +0 -0
  207. /package/dist/{registry-U4CHXK6R.js.map → provider-check-ZEV5P4KM.js.map} +0 -0
  208. /package/dist/{team-LRZ6GTQK.js.map → registry-F3THYC5M.js.map} +0 -0
  209. /package/dist/{session-end-FT27DWYZ.js.map → session-end-FS46UARX.js.map} +0 -0
  210. /package/dist/{stop-ZPIKVLH4.js.map → stop-OUEX6KA4.js.map} +0 -0
  211. /package/dist/{stop-failure-2PX67YJC.js.map → stop-failure-2BWVNZEG.js.map} +0 -0
  212. /package/dist/{subagent-start-UUE6EHQD.js.map → subagent-start-J4VV6DEE.js.map} +0 -0
  213. /package/dist/{subagent-stop-KQWWWPE6.js.map → subagent-stop-JMLVEPIA.js.map} +0 -0
  214. /package/dist/{task-completed-WMHOFQ7B.js.map → task-completed-65CHMMKA.js.map} +0 -0
  215. /package/dist/{turns-YFNI5CQC.js.map → team-U2LDKIS4.js.map} +0 -0
  216. /package/dist/{version-XMPPJQHR.js.map → turns-HU2CTZAP.js.map} +0 -0
@@ -1,11 +1,14 @@
1
1
  import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
+ import {
3
+ enumerateMatrixCells
4
+ } from "./chunk-54SXG5HF.js";
2
5
  import {
3
6
  DaemonLogger,
4
7
  LEVEL_ORDER
5
8
  } from "./chunk-3WOS4TAR.js";
6
9
  import {
7
10
  withTaskConfig
8
- } from "./chunk-US4LNCAT.js";
11
+ } from "./chunk-IPPMYQ2Y.js";
9
12
  import {
10
13
  EMBEDDABLE_TABLES,
11
14
  EMBEDDABLE_TEXT_COLUMNS,
@@ -15,16 +18,19 @@ import {
15
18
  getEmbeddingQueueDepth,
16
19
  getUnembedded,
17
20
  markEmbedded
18
- } from "./chunk-JZGN33AY.js";
21
+ } from "./chunk-VRI56337.js";
19
22
  import {
23
+ deleteSecrets,
20
24
  getTeamPackageVersion,
21
25
  loadSecrets,
22
26
  readJsonConfig,
23
27
  readSecrets,
24
28
  resolveVaultConfigPath,
25
29
  writeSecret
26
- } from "./chunk-XATDZX7U.js";
30
+ } from "./chunk-6ALVMIB4.js";
27
31
  import {
32
+ DEFAULT_OPENAI_URL,
33
+ DEFAULT_OPENROUTER_URL,
28
34
  SKILL_SURVEY_TASK,
29
35
  buildTaskInstruction,
30
36
  closeOpenBatches,
@@ -33,19 +39,24 @@ import {
33
39
  countNotifications,
34
40
  countRuns,
35
41
  countSkillRecords,
42
+ countWriteIntents,
43
+ countWriteIntentsByTool,
44
+ countWriteIntentsByToolForEvaluation,
36
45
  createBatchLineage,
46
+ createLocalOpenAIBackend,
37
47
  deleteCandidate,
38
48
  deleteSkillRecordCascade,
39
49
  dismissAllNotifications,
40
- errorMessage,
41
50
  findBatchByPromptPrefix,
42
51
  getAllDomains,
43
52
  getCandidate,
44
- getDigestExtract,
45
- getEntity,
46
53
  getGraphForNode,
47
54
  getLatestBatch,
55
+ getLatestOpenBatch,
56
+ getLatestResumableRunForTask,
48
57
  getLatestRunId,
58
+ getLocalOpenAIBackendDefaultBaseUrl,
59
+ getLocalOpenAIBackendLabel,
49
60
  getNotification,
50
61
  getRun,
51
62
  getSkillRecord,
@@ -54,47 +65,59 @@ import {
54
65
  hasConfiguredProvider,
55
66
  incrementActivityCount,
56
67
  incrementSkillUsageCount,
68
+ inferLocalOpenAIBackendKind,
57
69
  insertBatchStateless,
58
70
  insertResolutionEvent,
59
71
  isInstructionRequiredTask,
60
72
  listBatchesBySession,
61
73
  listCandidatesWithCount,
62
- listDigestExtracts,
63
- listEntities,
64
74
  listLineageForSkill,
65
75
  listNotifications,
66
76
  listReports,
67
77
  listRuns,
78
+ listRunsForEvaluation,
68
79
  listSkillRecords,
69
80
  listSkillRecordsWithCount,
81
+ listWriteIntentTools,
82
+ listWriteIntents,
70
83
  markAllRead,
84
+ markRunningRunsInterrupted,
71
85
  notify,
72
86
  populateBatchResponses,
73
87
  register,
88
+ runAgent,
74
89
  setResponseSummary,
90
+ tryParseJson,
75
91
  updateCandidate,
76
92
  updateNotificationStatus
77
- } from "./chunk-UYMFCYBF.js";
93
+ } from "./chunk-X2IRGXGF.js";
94
+ import {
95
+ parseCheckpointState,
96
+ runDurationMs
97
+ } from "./chunk-QATYARI5.js";
78
98
  import {
79
99
  fullTextSearch,
80
- hydrateSearchResults
81
- } from "./chunk-5XIVBO25.js";
100
+ hydrateSearchResults,
101
+ sanitizeFtsQuery
102
+ } from "./chunk-QLLBJEM7.js";
82
103
  import {
83
104
  copyTaskToUser,
84
105
  deleteUserTask,
85
106
  loadAllTasks,
86
107
  validateTaskName,
87
108
  writeUserTask
88
- } from "./chunk-6X2ERTQV.js";
109
+ } from "./chunk-USVFEWYL.js";
89
110
  import {
90
- AgentTaskSchema,
91
111
  registerAgent,
92
112
  resolveDefinitionsDir,
93
113
  taskFromParsed
94
- } from "./chunk-CCRGY3QW.js";
114
+ } from "./chunk-AUIXX33A.js";
115
+ import {
116
+ errorMessage
117
+ } from "./chunk-LQIPXVDH.js";
95
118
  import {
96
119
  listTurnsByRun
97
- } from "./chunk-6RFZWV4R.js";
120
+ } from "./chunk-FCJ5JV54.js";
98
121
  import {
99
122
  cleanupStagedSkill,
100
123
  listStaleStagingDirs
@@ -102,7 +125,11 @@ import {
102
125
  import {
103
126
  Anthropic,
104
127
  createEmbeddingProvider
105
- } from "./chunk-KHT24OWC.js";
128
+ } from "./chunk-YDUOSRGD.js";
129
+ import {
130
+ OPENAI_API_KEY_ENV,
131
+ OPENROUTER_API_KEY_ENV
132
+ } from "./chunk-CISWUP5W.js";
106
133
  import {
107
134
  getMachineId
108
135
  } from "./chunk-ENWBFX7F.js";
@@ -111,26 +138,42 @@ import {
111
138
  cleanStaleBuffers,
112
139
  listBufferSessionIds
113
140
  } from "./chunk-V7XG6V6C.js";
114
- import "./chunk-POEPHBQK.js";
115
- import "./chunk-KESLPBKV.js";
141
+ import "./chunk-QDLVIW2O.js";
116
142
  import "./chunk-SAKJMNSR.js";
117
143
  import {
118
144
  SymbiontInstaller
119
- } from "./chunk-Q36VMZST.js";
145
+ } from "./chunk-OTQH5KZW.js";
120
146
  import {
121
147
  checkLocalProvider
122
- } from "./chunk-BPRIYNLE.js";
148
+ } from "./chunk-TKAJ3JVF.js";
123
149
  import {
124
150
  LmStudioBackend,
125
151
  OllamaBackend
126
- } from "./chunk-FMRZ26U5.js";
152
+ } from "./chunk-X3IGT5RV.js";
127
153
  import {
154
+ composeSessionStartContext,
155
+ shouldInjectSessionStartDigest
156
+ } from "./chunk-OZ3FBAK5.js";
157
+ import {
158
+ buildCortexInstructionsInput,
128
159
  countSpores,
160
+ deletePlan,
161
+ getCortexInstructions,
162
+ getPlan,
163
+ getPlanByLogicalKey,
129
164
  getSpore,
130
165
  insertSpore,
166
+ listDigestExtracts,
167
+ listDigestRevisions,
168
+ listPlans,
169
+ listPlansBySession,
131
170
  listSpores,
132
- updateSporeStatus
133
- } from "./chunk-4YFKBL3F.js";
171
+ resolveInstructionDelivery,
172
+ rollbackDigestExtract,
173
+ shouldInjectCortex,
174
+ updateSporeStatus,
175
+ upsertPlan
176
+ } from "./chunk-N7Z3LUEZ.js";
134
177
  import {
135
178
  backfillUnsynced,
136
179
  closeSession,
@@ -153,20 +196,29 @@ import {
153
196
  pruneOld,
154
197
  reactivateSessionIfCompleted,
155
198
  retryDeadLettered,
156
- syncRow,
157
199
  updateSession,
158
200
  upsertSession
159
- } from "./chunk-EVDQKYCG.js";
201
+ } from "./chunk-RQSJLWP4.js";
160
202
  import {
161
203
  evaluateSessionCaptureRules,
162
204
  readTranscriptMeta
163
205
  } from "./chunk-XL75KZGI.js";
206
+ import {
207
+ PLAN_STATUSES
208
+ } from "./chunk-EEOJWLMP.js";
164
209
  import {
165
210
  EMBEDDING_DIMENSIONS,
166
211
  REST_SETTABLE_STATUSES,
167
212
  SCHEMA_VERSION,
168
- createSchema
169
- } from "./chunk-MYOZLMB2.js";
213
+ TRANSCRIPT_SOURCE_PREFIX,
214
+ buildPathPlanLogicalKey,
215
+ buildPlanId,
216
+ buildSessionPlanLogicalKey,
217
+ buildSessionTagPlanLogicalKey,
218
+ createSchema,
219
+ humanizePlanToken,
220
+ normalizePlanSourcePath
221
+ } from "./chunk-DTWUHHFI.js";
170
222
  import {
171
223
  CONFIG_FILENAME,
172
224
  MycoConfigSchema,
@@ -179,7 +231,11 @@ import {
179
231
  updateConfig,
180
232
  updateLocalConfig,
181
233
  updateTeamConfig
182
- } from "./chunk-OD4AA7PV.js";
234
+ } from "./chunk-53RPGOEN.js";
235
+ import {
236
+ AgentTaskSchema
237
+ } from "./chunk-OUJSQSKE.js";
238
+ import "./chunk-POEPHBQK.js";
183
239
  import {
184
240
  closeDatabase,
185
241
  getDatabase,
@@ -191,14 +247,14 @@ import {
191
247
  } from "./chunk-ZXZPJJN3.js";
192
248
  import {
193
249
  resolveCliEntryPath
194
- } from "./chunk-VVNL26WX.js";
250
+ } from "./chunk-KTTSXYEK.js";
195
251
  import {
196
252
  getPluginVersion
197
- } from "./chunk-XG5RRUYF.js";
253
+ } from "./chunk-ENZR5NG7.js";
198
254
  import {
199
255
  loadManifests,
200
256
  resolvePackageRoot
201
- } from "./chunk-NGROSFOH.js";
257
+ } from "./chunk-Z66IT5KL.js";
202
258
  import {
203
259
  findPackageRoot
204
260
  } from "./chunk-LPUQPDC2.js";
@@ -210,6 +266,7 @@ import {
210
266
  DEFAULT_AGENT_ID,
211
267
  DEFAULT_LIST_LIMIT,
212
268
  DEFAULT_RELEASE_CHANNEL,
269
+ DEFAULT_SYMBIONT_NAME,
213
270
  EMBEDDING_BATCH_SIZE,
214
271
  EXCLUDED_SPORE_STATUSES,
215
272
  FEED_DEFAULT_LIMIT,
@@ -243,8 +300,10 @@ import {
243
300
  SYNC_PROTOCOL_VERSION,
244
301
  TEAM_API_KEY_SECRET,
245
302
  TEAM_HEALTH_TIMEOUT_MS,
303
+ TEAM_REQUEST_TIMEOUT_MS,
246
304
  TEAM_SEARCH_TIMEOUT_MS,
247
305
  TEAM_SOURCE_PREFIX,
306
+ TEAM_SYNC_TIMEOUT_MS,
248
307
  UPDATE_CHECK_CACHE_PATH,
249
308
  UPDATE_CHECK_INTERVAL_HOURS,
250
309
  UPDATE_CONFIG_PATH,
@@ -257,7 +316,7 @@ import {
257
316
  USER_TASK_SOURCE,
258
317
  epochSeconds,
259
318
  estimateTokens
260
- } from "./chunk-FLLBJLHM.js";
319
+ } from "./chunk-6C6QZ4PM.js";
261
320
  import {
262
321
  LOG_KINDS,
263
322
  kindToComponent
@@ -428,13 +487,24 @@ var DaemonServer = class {
428
487
  uptime: process.uptime()
429
488
  }
430
489
  }));
490
+ this.registerRoute("GET", "/api/version", async () => ({
491
+ body: { version: this.version }
492
+ }));
431
493
  }
432
494
  async handleRequest(req, res) {
433
495
  const match = this.router.match(req.method, req.url);
434
496
  if (match) {
497
+ const rejection = validateLoopbackRequest(req, this.port);
498
+ if (rejection) {
499
+ res.writeHead(rejection.status, { "Content-Type": "application/json" });
500
+ res.end(JSON.stringify({ error: rejection.error }));
501
+ return;
502
+ }
435
503
  this.onRequest?.();
504
+ const versionHeader = { "X-Myco-Api-Version": this.version };
436
505
  try {
437
- const body = req.method === "POST" || req.method === "PUT" || req.method === "PATCH" ? await readBody(req) : void 0;
506
+ const needsBody = req.method === "POST" || req.method === "PUT" || req.method === "PATCH" || req.method === "DELETE";
507
+ const body = needsBody ? await readBody(req) : void 0;
438
508
  const result = await match.handler({
439
509
  body,
440
510
  query: match.query,
@@ -443,11 +513,11 @@ var DaemonServer = class {
443
513
  });
444
514
  const status = result.status ?? DEFAULT_STATUS;
445
515
  if (Buffer.isBuffer(result.body)) {
446
- res.writeHead(status, result.headers ?? {});
516
+ res.writeHead(status, { ...versionHeader, ...result.headers });
447
517
  res.end(result.body);
448
518
  return;
449
519
  }
450
- const headers = { "Content-Type": "application/json", ...result.headers };
520
+ const headers = { "Content-Type": "application/json", ...versionHeader, ...result.headers };
451
521
  res.writeHead(status, headers);
452
522
  res.end(JSON.stringify(result.body));
453
523
  } catch (error) {
@@ -455,7 +525,7 @@ var DaemonServer = class {
455
525
  path: req.url,
456
526
  error: error.message
457
527
  });
458
- res.writeHead(500, { "Content-Type": "application/json" });
528
+ res.writeHead(500, { "Content-Type": "application/json", ...versionHeader });
459
529
  res.end(JSON.stringify({ error: error.message }));
460
530
  }
461
531
  return;
@@ -578,6 +648,36 @@ function readBody(req) {
578
648
  req.on("error", reject);
579
649
  });
580
650
  }
651
+ function validateLoopbackRequest(req, port) {
652
+ const host = req.headers.host;
653
+ if (host && !isLoopbackHost(host, port)) {
654
+ return { status: 403, error: "forbidden_host" };
655
+ }
656
+ const origin = req.headers.origin;
657
+ if (origin && !isLoopbackOrigin(origin, port)) {
658
+ return { status: 403, error: "forbidden_origin" };
659
+ }
660
+ const method = (req.method ?? "").toUpperCase();
661
+ const isMutating = method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
662
+ if (!isMutating) return null;
663
+ const contentLengthHeader = req.headers["content-length"];
664
+ const contentLength = contentLengthHeader ? Number(contentLengthHeader) : NaN;
665
+ const hasBody = Number.isFinite(contentLength) ? contentLength > 0 : (req.headers["transfer-encoding"] ?? "").length > 0;
666
+ if (!hasBody) return null;
667
+ const contentType = (req.headers["content-type"] ?? "").toLowerCase();
668
+ if (!contentType.includes("application/json")) {
669
+ return { status: 415, error: "unsupported_media_type" };
670
+ }
671
+ return null;
672
+ }
673
+ function isLoopbackHost(host, port) {
674
+ const portStr = String(port);
675
+ return host === `127.0.0.1:${portStr}` || host === `localhost:${portStr}`;
676
+ }
677
+ function isLoopbackOrigin(origin, port) {
678
+ const portStr = String(port);
679
+ return origin === `http://127.0.0.1:${portStr}` || origin === `http://localhost:${portStr}`;
680
+ }
581
681
 
582
682
  // src/daemon/lifecycle.ts
583
683
  var SessionRegistry = class {
@@ -2599,7 +2699,7 @@ var TeamSyncClient = class _TeamSyncClient {
2599
2699
  content_hash: data.content_hash ?? null
2600
2700
  };
2601
2701
  })
2602
- });
2702
+ }, { timeoutMs: TEAM_SYNC_TIMEOUT_MS });
2603
2703
  return res;
2604
2704
  }
2605
2705
  /**
@@ -2699,17 +2799,25 @@ var TeamSyncClient = class _TeamSyncClient {
2699
2799
  "Content-Type": "application/json"
2700
2800
  };
2701
2801
  }
2702
- async request(method, path23, body) {
2703
- const res = await this.fetchFn(`${this.workerUrl}${path23}`, {
2704
- method,
2705
- headers: this.headers(),
2706
- body: body !== void 0 ? JSON.stringify(body) : void 0
2707
- });
2708
- if (!res.ok) {
2709
- const text = await res.text().catch(() => "");
2710
- throw new Error(`Team sync request ${method} ${path23} failed: ${res.status} ${text}`);
2802
+ async request(method, path25, body, options = {}) {
2803
+ const timeoutMs = options.timeoutMs ?? TEAM_REQUEST_TIMEOUT_MS;
2804
+ const controller = new AbortController();
2805
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2806
+ try {
2807
+ const res = await this.fetchFn(`${this.workerUrl}${path25}`, {
2808
+ method,
2809
+ headers: this.headers(),
2810
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
2811
+ signal: controller.signal
2812
+ });
2813
+ if (!res.ok) {
2814
+ const text = await res.text().catch(() => "");
2815
+ throw new Error(`Team sync request ${method} ${path25} failed: ${res.status} ${text}`);
2816
+ }
2817
+ return res.json();
2818
+ } finally {
2819
+ clearTimeout(timer);
2711
2820
  }
2712
- return res.json();
2713
2821
  }
2714
2822
  };
2715
2823
 
@@ -2791,13 +2899,17 @@ function createTeamHandlers(deps) {
2791
2899
  try {
2792
2900
  pendingCount = countPending();
2793
2901
  deadLetterCount = countDeadLettered();
2794
- } catch {
2902
+ } catch (err) {
2903
+ const detail = errorMessage(err);
2904
+ logger.warn("team-sync.outbox.count-failed", "team-outbox counts unavailable", { error: detail });
2795
2905
  }
2796
2906
  let collectiveStatus = null;
2797
2907
  if (client && config.team.enabled) {
2798
2908
  try {
2799
2909
  collectiveStatus = await client.getCollectiveStatus();
2800
- } catch {
2910
+ } catch (err) {
2911
+ const detail = errorMessage(err);
2912
+ logger.warn("team-sync.collective.status-failed", "Collective status unavailable", { error: detail });
2801
2913
  collectiveStatus = null;
2802
2914
  }
2803
2915
  }
@@ -2840,7 +2952,7 @@ function createTeamHandlers(deps) {
2840
2952
  return { body: { retried: count } };
2841
2953
  }
2842
2954
  async function handleUpgradeWorker(_req) {
2843
- const { upgradeWorker } = await import("./team-LRZ6GTQK.js");
2955
+ const { upgradeWorker } = await import("./team-U2LDKIS4.js");
2844
2956
  logger.info("team-sync.upgrade.start", "Starting worker upgrade");
2845
2957
  const result = upgradeWorker(vaultDir);
2846
2958
  if (!result.success) {
@@ -2878,7 +2990,7 @@ function createTeamHandlers(deps) {
2878
2990
  logger.info("team-sync.mcp-token.rotated", "MCP access token rotated");
2879
2991
  return { body: { token } };
2880
2992
  } catch (err) {
2881
- const message = err instanceof Error ? err.message : String(err);
2993
+ const message = errorMessage(err);
2882
2994
  logger.error("team-sync.mcp-token.rotate-failed", "MCP token rotation failed", { error: message });
2883
2995
  return {
2884
2996
  status: 500,
@@ -3232,7 +3344,7 @@ function createSkillRecordDeleteHandler(deps) {
3232
3344
  logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to remove skill directory", { name: record.name, error: String(err) });
3233
3345
  }
3234
3346
  try {
3235
- const { syncSkillSymlinks } = await import("./installer-FS257JRZ.js");
3347
+ const { syncSkillSymlinks } = await import("./installer-N4UTEACX.js");
3236
3348
  syncSkillSymlinks(projectRoot, record.name, { remove: true });
3237
3349
  } catch (err) {
3238
3350
  logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to remove skill symlinks", { name: record.name, error: String(err) });
@@ -3451,6 +3563,10 @@ async function handleGetProgress(tracker, token) {
3451
3563
 
3452
3564
  // src/daemon/api/models.ts
3453
3565
  var MODEL_LIST_TIMEOUT_MS = 5e3;
3566
+ var REMOTE_MODELS_ENDPOINT = "/models";
3567
+ var OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
3568
+ var OPENROUTER_DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
3569
+ var REMOTE_PROVIDER_TIMEOUT_MS = 5e3;
3454
3570
  var ANTHROPIC_MODELS = [
3455
3571
  "claude-sonnet-4-6",
3456
3572
  "claude-opus-4-6",
@@ -3476,31 +3592,78 @@ function filterLlmModels(models) {
3476
3592
  return !EMBEDDING_PATTERNS.some((p) => name.includes(p));
3477
3593
  });
3478
3594
  }
3479
- async function handleGetModels(req) {
3595
+ var REMOTE_PROVIDER_DEFAULTS = {
3596
+ openai: OPENAI_DEFAULT_BASE_URL,
3597
+ openrouter: OPENROUTER_DEFAULT_BASE_URL
3598
+ };
3599
+ var REMOTE_PROVIDER_ENV_VARS = {
3600
+ openai: OPENAI_API_KEY_ENV,
3601
+ openrouter: OPENROUTER_API_KEY_ENV
3602
+ };
3603
+ function getRemoteProviderApiKey(provider) {
3604
+ const preferredKey = process.env[REMOTE_PROVIDER_ENV_VARS[provider]];
3605
+ if (preferredKey) return preferredKey;
3606
+ if (provider === "openai") {
3607
+ return process.env.OPENAI_API_KEY;
3608
+ }
3609
+ return void 0;
3610
+ }
3611
+ async function fetchRemoteProviderModels(provider, _baseUrl, timeoutMs = REMOTE_PROVIDER_TIMEOUT_MS) {
3612
+ const apiKey = getRemoteProviderApiKey(provider);
3613
+ if (!apiKey) return [];
3614
+ const baseUrl = REMOTE_PROVIDER_DEFAULTS[provider];
3615
+ const response = await fetch(`${baseUrl}${REMOTE_MODELS_ENDPOINT}`, {
3616
+ headers: {
3617
+ Authorization: `Bearer ${apiKey}`
3618
+ },
3619
+ signal: AbortSignal.timeout(timeoutMs)
3620
+ });
3621
+ if (!response.ok) {
3622
+ throw new Error(`${provider} models request failed with ${response.status}`);
3623
+ }
3624
+ const data = await response.json();
3625
+ const modelIds = (data.data ?? []).map((entry) => entry.id).filter((id) => typeof id === "string" && id.length > 0);
3626
+ return filterLlmModels(modelIds);
3627
+ }
3628
+ async function handleGetModels(req, logger) {
3480
3629
  const provider = req.query.provider;
3481
3630
  const type = req.query.type;
3631
+ const localBackend = req.query.local_backend;
3482
3632
  if (!provider) {
3483
3633
  return { status: 400, body: { error: "provider query parameter required" } };
3484
3634
  }
3485
3635
  let models = [];
3636
+ let fetchError;
3486
3637
  try {
3487
- if (provider === "ollama") {
3488
- const backend = new OllamaBackend({ base_url: req.query.base_url });
3489
- models = await backend.listModels(MODEL_LIST_TIMEOUT_MS);
3490
- } else if (provider === "lm-studio" || provider === "openai-compatible") {
3491
- const backend = new LmStudioBackend({ base_url: req.query.base_url });
3638
+ const localBackendKind = inferLocalOpenAIBackendKind({
3639
+ type: provider === "lm-studio" ? "lmstudio" : provider,
3640
+ localBackend,
3641
+ baseUrl: req.query.base_url
3642
+ });
3643
+ if (localBackendKind) {
3644
+ const backend = createLocalOpenAIBackend(localBackendKind, req.query.base_url);
3492
3645
  models = await backend.listModels(MODEL_LIST_TIMEOUT_MS);
3493
3646
  } else if (provider === "anthropic") {
3494
3647
  models = ANTHROPIC_MODELS;
3648
+ } else if (provider === "openai" || provider === "openrouter") {
3649
+ models = await fetchRemoteProviderModels(provider, void 0, MODEL_LIST_TIMEOUT_MS);
3495
3650
  }
3496
- } catch {
3651
+ } catch (err) {
3652
+ fetchError = errorMessage(err);
3653
+ logger?.warn(`models.${provider}.list-unavailable`, `${provider} model list unavailable`, { error: fetchError });
3497
3654
  }
3498
3655
  if (type === "embedding") {
3499
3656
  models = filterEmbeddingModels(models);
3500
3657
  } else if (type === "llm") {
3501
3658
  models = filterLlmModels(models);
3502
3659
  }
3503
- return { body: { provider, models } };
3660
+ return {
3661
+ body: {
3662
+ provider,
3663
+ models,
3664
+ ...fetchError ? { error: fetchError } : {}
3665
+ }
3666
+ };
3504
3667
  }
3505
3668
 
3506
3669
  // src/daemon/api/stats.ts
@@ -3709,138 +3872,6 @@ function getAttachmentByFilePath(filePath) {
3709
3872
  return row ? toAttachmentRow(row) : null;
3710
3873
  }
3711
3874
 
3712
- // src/db/queries/plans.ts
3713
- var DEFAULT_LIST_LIMIT2 = 100;
3714
- var DEFAULT_STATUS2 = "active";
3715
- var DEFAULT_PROCESSED2 = 0;
3716
- var PLAN_COLUMNS = [
3717
- "id",
3718
- "status",
3719
- "author",
3720
- "title",
3721
- "content",
3722
- "source_path",
3723
- "tags",
3724
- "session_id",
3725
- "prompt_batch_id",
3726
- "content_hash",
3727
- "processed",
3728
- "embedded",
3729
- "created_at",
3730
- "updated_at",
3731
- "machine_id",
3732
- "synced_at"
3733
- ];
3734
- var SELECT_COLUMNS4 = PLAN_COLUMNS.join(", ");
3735
- function toPlanRow(row) {
3736
- return {
3737
- id: row.id,
3738
- status: row.status,
3739
- author: row.author ?? null,
3740
- title: row.title ?? null,
3741
- content: row.content ?? null,
3742
- source_path: row.source_path ?? null,
3743
- tags: row.tags ?? null,
3744
- session_id: row.session_id ?? null,
3745
- prompt_batch_id: row.prompt_batch_id ?? null,
3746
- content_hash: row.content_hash ?? null,
3747
- processed: row.processed,
3748
- embedded: row.embedded ?? 0,
3749
- created_at: row.created_at,
3750
- updated_at: row.updated_at ?? null,
3751
- machine_id: row.machine_id ?? "local",
3752
- synced_at: row.synced_at ?? null
3753
- };
3754
- }
3755
- function upsertPlan(data) {
3756
- const db = getDatabase();
3757
- db.prepare(
3758
- `INSERT INTO plans (
3759
- id, status, author, title, content,
3760
- source_path, tags, session_id, prompt_batch_id, content_hash,
3761
- processed, created_at, updated_at, machine_id
3762
- ) VALUES (
3763
- ?, ?, ?, ?, ?,
3764
- ?, ?, ?, ?, ?,
3765
- ?, ?, ?, ?
3766
- )
3767
- ON CONFLICT (id) DO UPDATE SET
3768
- status = EXCLUDED.status,
3769
- author = EXCLUDED.author,
3770
- title = EXCLUDED.title,
3771
- content = EXCLUDED.content,
3772
- source_path = EXCLUDED.source_path,
3773
- tags = EXCLUDED.tags,
3774
- session_id = EXCLUDED.session_id,
3775
- prompt_batch_id = EXCLUDED.prompt_batch_id,
3776
- content_hash = EXCLUDED.content_hash,
3777
- processed = EXCLUDED.processed,
3778
- updated_at = EXCLUDED.updated_at,
3779
- embedded = CASE
3780
- WHEN EXCLUDED.content_hash != plans.content_hash THEN 0
3781
- ELSE plans.embedded
3782
- END`
3783
- ).run(
3784
- data.id,
3785
- data.status ?? DEFAULT_STATUS2,
3786
- data.author ?? null,
3787
- data.title ?? null,
3788
- data.content ?? null,
3789
- data.source_path ?? null,
3790
- data.tags ?? null,
3791
- data.session_id ?? null,
3792
- data.prompt_batch_id ?? null,
3793
- data.content_hash ?? null,
3794
- data.processed ?? DEFAULT_PROCESSED2,
3795
- data.created_at,
3796
- data.updated_at ?? null,
3797
- data.machine_id ?? getTeamMachineId()
3798
- );
3799
- const row = toPlanRow(
3800
- db.prepare(`SELECT ${SELECT_COLUMNS4} FROM plans WHERE id = ?`).get(data.id)
3801
- );
3802
- syncRow("plans", row);
3803
- return row;
3804
- }
3805
- function getPlan(id) {
3806
- const db = getDatabase();
3807
- const row = db.prepare(
3808
- `SELECT ${SELECT_COLUMNS4} FROM plans WHERE id = ?`
3809
- ).get(id);
3810
- if (!row) return null;
3811
- return toPlanRow(row);
3812
- }
3813
- function listPlans(options = {}) {
3814
- const db = getDatabase();
3815
- const conditions = [];
3816
- const params = [];
3817
- if (options.status !== void 0) {
3818
- conditions.push(`status = ?`);
3819
- params.push(options.status);
3820
- }
3821
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3822
- const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
3823
- params.push(limit);
3824
- const rows = db.prepare(
3825
- `SELECT ${SELECT_COLUMNS4}
3826
- FROM plans
3827
- ${where}
3828
- ORDER BY created_at DESC
3829
- LIMIT ?`
3830
- ).all(...params);
3831
- return rows.map(toPlanRow);
3832
- }
3833
- function listPlansBySession(sessionId) {
3834
- const db = getDatabase();
3835
- const rows = db.prepare(
3836
- `SELECT ${SELECT_COLUMNS4}
3837
- FROM plans
3838
- WHERE session_id = ?
3839
- ORDER BY created_at DESC`
3840
- ).all(sessionId);
3841
- return rows.map(toPlanRow);
3842
- }
3843
-
3844
3875
  // src/daemon/jobs/session-cleanup.ts
3845
3876
  import { unlink, glob } from "fs/promises";
3846
3877
  async function cleanupAfterSessionCascade(sessionId, result, embeddingManager, vaultDir) {
@@ -3885,11 +3916,12 @@ async function triggerTitleSummary(sessionId, deps) {
3885
3916
  if (config.agent.summary_batch_interval <= 0) return;
3886
3917
  if (config.agent.event_tasks_enabled === false) return;
3887
3918
  try {
3888
- const { runAgent } = await import("./executor-HWW2QNZQ.js");
3889
- runAgent(vaultDir, {
3919
+ const { runAgent: runAgent2 } = await import("./executor-DO6QFC6G.js");
3920
+ runAgent2(vaultDir, {
3890
3921
  task: "title-summary",
3891
3922
  instruction: `Process session ${sessionId} only`,
3892
- embeddingManager
3923
+ embeddingManager,
3924
+ logger
3893
3925
  }).catch((err) => {
3894
3926
  logger.warn(LOG_KINDS.AGENT_ERROR, "Title-summary task failed", {
3895
3927
  session_id: sessionId,
@@ -3900,11 +3932,16 @@ async function triggerTitleSummary(sessionId, deps) {
3900
3932
  }
3901
3933
  }
3902
3934
 
3935
+ // src/daemon/api/error-envelope.ts
3936
+ function errorBody(code, message) {
3937
+ return { error: { code, message } };
3938
+ }
3939
+
3903
3940
  // src/daemon/api/sessions.ts
3904
- var DEFAULT_LIST_LIMIT3 = 50;
3941
+ var DEFAULT_LIST_LIMIT2 = 50;
3905
3942
  var DEFAULT_LIST_OFFSET2 = 0;
3906
3943
  async function handleListSessions(req) {
3907
- const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT3;
3944
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT2;
3908
3945
  const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET2;
3909
3946
  const status = req.query.status || void 0;
3910
3947
  const agent = req.query.agent || void 0;
@@ -3947,7 +3984,7 @@ async function handleGetSessionAttachments(req) {
3947
3984
  }
3948
3985
  async function handleGetSessionPlans(req) {
3949
3986
  const plans = listPlansBySession(req.params.id);
3950
- return { body: plans };
3987
+ return { body: { plans } };
3951
3988
  }
3952
3989
  function createSessionMutationHandlers(deps) {
3953
3990
  const { embeddingManager, vaultDir, logger, liveConfig } = deps;
@@ -3988,7 +4025,125 @@ function createSessionMutationHandlers(deps) {
3988
4025
  const impact = getSessionImpact(sessionId);
3989
4026
  return { body: impact };
3990
4027
  }
3991
- return { handleDeleteSession, handleCompleteSession, handleGetSessionImpact };
4028
+ async function handleDeletePlan(req) {
4029
+ const existing = getPlan(req.params.id);
4030
+ if (!existing) return { status: 404, body: errorBody("plan-not-found", "Plan not found") };
4031
+ const localMachineId = getTeamMachineId();
4032
+ const body = req.body;
4033
+ const forceRemote = body?.force_remote === true;
4034
+ if (existing.machine_id !== localMachineId && !forceRemote) {
4035
+ logger.warn(LOG_KINDS.API_SESSION_DELETE, "Cross-machine plan delete rejected", {
4036
+ plan_id: existing.id,
4037
+ plan_machine_id: existing.machine_id,
4038
+ local_machine_id: localMachineId
4039
+ });
4040
+ return {
4041
+ status: 403,
4042
+ body: errorBody(
4043
+ "cross-machine-delete",
4044
+ 'Plan belongs to another machine; pass {"force_remote": true} to delete.'
4045
+ )
4046
+ };
4047
+ }
4048
+ if (existing.machine_id !== localMachineId && forceRemote) {
4049
+ logger.warn(LOG_KINDS.API_SESSION_DELETE, "Cross-machine plan delete allowed by force_remote", {
4050
+ plan_id: existing.id,
4051
+ plan_machine_id: existing.machine_id,
4052
+ local_machine_id: localMachineId
4053
+ });
4054
+ }
4055
+ const deleted = deletePlan(req.params.id);
4056
+ if (!deleted) return { status: 404, body: errorBody("plan-not-found", "Plan not found") };
4057
+ embeddingManager.onRemoved("plans", deleted.id);
4058
+ logger.info(LOG_KINDS.API_SESSION_DELETE, "Plan deleted", {
4059
+ plan_id: deleted.id,
4060
+ session_id: deleted.session_id,
4061
+ logical_key: deleted.logical_key
4062
+ });
4063
+ return {
4064
+ body: {
4065
+ ok: true,
4066
+ id: deleted.id,
4067
+ session_id: deleted.session_id
4068
+ }
4069
+ };
4070
+ }
4071
+ return { handleDeleteSession, handleCompleteSession, handleGetSessionImpact, handleDeletePlan };
4072
+ }
4073
+
4074
+ // src/db/queries/entities.ts
4075
+ var DEFAULT_LIST_LIMIT3 = 100;
4076
+ var ENTITY_COLUMNS = [
4077
+ "id",
4078
+ "agent_id",
4079
+ "type",
4080
+ "name",
4081
+ "properties",
4082
+ "first_seen",
4083
+ "last_seen",
4084
+ "status",
4085
+ "machine_id",
4086
+ "synced_at"
4087
+ ];
4088
+ var SELECT_COLUMNS4 = ENTITY_COLUMNS.join(", ");
4089
+ function toEntityRow(row) {
4090
+ return {
4091
+ id: row.id,
4092
+ agent_id: row.agent_id,
4093
+ type: row.type,
4094
+ name: row.name,
4095
+ properties: row.properties ?? null,
4096
+ first_seen: row.first_seen,
4097
+ last_seen: row.last_seen,
4098
+ status: row.status ?? "active",
4099
+ machine_id: row.machine_id ?? "local",
4100
+ synced_at: row.synced_at ?? null
4101
+ };
4102
+ }
4103
+ function listEntities(options = {}) {
4104
+ const db = getDatabase();
4105
+ const conditions = [];
4106
+ const params = [];
4107
+ if (options.agent_id !== void 0) {
4108
+ conditions.push(`agent_id = ?`);
4109
+ params.push(options.agent_id);
4110
+ }
4111
+ if (options.type !== void 0) {
4112
+ conditions.push(`type = ?`);
4113
+ params.push(options.type);
4114
+ }
4115
+ if (options.name !== void 0) {
4116
+ conditions.push(`name = ?`);
4117
+ params.push(options.name);
4118
+ }
4119
+ if (options.status !== void 0) {
4120
+ conditions.push(`status = ?`);
4121
+ params.push(options.status);
4122
+ } else {
4123
+ conditions.push(`status = ?`);
4124
+ params.push("active");
4125
+ }
4126
+ if (options.mentioned_in !== void 0 && options.note_type !== void 0) {
4127
+ conditions.push(
4128
+ `id IN (SELECT entity_id FROM entity_mentions WHERE note_id = ? AND note_type = ?)`
4129
+ );
4130
+ params.push(options.mentioned_in);
4131
+ params.push(options.note_type);
4132
+ }
4133
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4134
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT3;
4135
+ const offset = options.offset ?? 0;
4136
+ params.push(limit);
4137
+ params.push(offset);
4138
+ const rows = db.prepare(
4139
+ `SELECT ${SELECT_COLUMNS4}
4140
+ FROM entities
4141
+ ${where}
4142
+ ORDER BY last_seen DESC
4143
+ LIMIT ?
4144
+ OFFSET ?`
4145
+ ).all(...params);
4146
+ return rows.map(toEntityRow);
3992
4147
  }
3993
4148
 
3994
4149
  // src/daemon/api/mycelium.ts
@@ -3997,7 +4152,6 @@ var DEFAULT_LIST_OFFSET3 = 0;
3997
4152
  var DEFAULT_GRAPH_DEPTH = 1;
3998
4153
  var MAX_GRAPH_DEPTH = 3;
3999
4154
  var SPORE_NAME_PREVIEW_CHARS = 60;
4000
- var GRAPH_SEED_ENTITY_LIMIT = 4;
4001
4155
  var GRAPH_SEED_SPORE_LIMIT = 4;
4002
4156
  var GRAPH_SEED_SESSION_LIMIT = 4;
4003
4157
  var EXCLUDED_GRAPH_EDGE_TYPES = /* @__PURE__ */ new Set(["HAS_BATCH", "EXTRACTED_FROM"]);
@@ -4056,15 +4210,6 @@ async function handleGetGraphSeeds(_req) {
4056
4210
  ORDER BY started_at DESC
4057
4211
  LIMIT ?`
4058
4212
  ).all(GRAPH_SEED_SESSION_LIMIT);
4059
- const entityRows = db.prepare(
4060
- `SELECT e.id, e.type, e.name, e.status, e.first_seen as created_at, COUNT(em.entity_id) as mention_count
4061
- FROM entities e
4062
- LEFT JOIN entity_mentions em ON em.entity_id = e.id
4063
- WHERE e.agent_id = ? AND e.status = 'active'
4064
- GROUP BY e.id
4065
- ORDER BY mention_count DESC, e.last_seen DESC
4066
- LIMIT ?`
4067
- ).all(DEFAULT_AGENT_ID, GRAPH_SEED_ENTITY_LIMIT);
4068
4213
  const sporeSeeds = sporeRows.map((row) => ({
4069
4214
  id: row.id,
4070
4215
  name: (row.content ?? "").slice(0, SPORE_NAME_PREVIEW_CHARS),
@@ -4082,20 +4227,66 @@ async function handleGetGraphSeeds(_req) {
4082
4227
  created_at: row.created_at,
4083
4228
  content: row.summary ?? void 0
4084
4229
  }));
4085
- const entitySeeds = entityRows.map((row) => ({
4086
- id: row.id,
4087
- name: row.name,
4088
- type: row.type,
4089
- status: row.status ?? void 0,
4090
- created_at: row.created_at,
4091
- mention_count: Number(row.mention_count) || 0
4092
- }));
4230
+ const topConnectedRow = db.prepare(
4231
+ `SELECT node_id FROM (
4232
+ SELECT source_id AS node_id, COUNT(*) AS cnt
4233
+ FROM graph_edges
4234
+ WHERE agent_id = ?
4235
+ AND type NOT IN ('HAS_BATCH', 'EXTRACTED_FROM')
4236
+ AND source_type IN ('spore', 'session')
4237
+ GROUP BY source_id
4238
+ UNION ALL
4239
+ SELECT target_id, COUNT(*)
4240
+ FROM graph_edges
4241
+ WHERE agent_id = ?
4242
+ AND type NOT IN ('HAS_BATCH', 'EXTRACTED_FROM')
4243
+ AND target_type IN ('spore', 'session')
4244
+ GROUP BY target_id
4245
+ )
4246
+ GROUP BY node_id
4247
+ ORDER BY SUM(cnt) DESC
4248
+ LIMIT 1`
4249
+ ).get(DEFAULT_AGENT_ID, DEFAULT_AGENT_ID);
4250
+ let topSeed = null;
4251
+ if (topConnectedRow?.node_id) {
4252
+ const topId = topConnectedRow.node_id;
4253
+ const sporeHit = db.prepare(
4254
+ `SELECT id, observation_type, status, content, created_at
4255
+ FROM spores WHERE id = ?`
4256
+ ).get(topId);
4257
+ if (sporeHit) {
4258
+ topSeed = {
4259
+ id: sporeHit.id,
4260
+ name: (sporeHit.content ?? "").slice(0, SPORE_NAME_PREVIEW_CHARS),
4261
+ type: "spore",
4262
+ status: sporeHit.status ?? void 0,
4263
+ created_at: sporeHit.created_at,
4264
+ content: sporeHit.content,
4265
+ observation_type: sporeHit.observation_type
4266
+ };
4267
+ } else {
4268
+ const sessionHit = db.prepare(
4269
+ `SELECT id, title, summary, status, started_at as created_at
4270
+ FROM sessions WHERE id = ?`
4271
+ ).get(topId);
4272
+ if (sessionHit) {
4273
+ topSeed = {
4274
+ id: sessionHit.id,
4275
+ name: sessionHit.title ?? `Session ${sessionHit.id.slice(-6)}`,
4276
+ type: "session",
4277
+ status: sessionHit.status ?? void 0,
4278
+ created_at: sessionHit.created_at,
4279
+ content: sessionHit.summary ?? void 0
4280
+ };
4281
+ }
4282
+ }
4283
+ }
4093
4284
  const seeds = [
4094
- ...entitySeeds,
4095
- ...sessionSeeds,
4096
- ...sporeSeeds
4285
+ ...topSeed ? [topSeed] : [],
4286
+ ...sessionSeeds.filter((s) => s.id !== topSeed?.id),
4287
+ ...sporeSeeds.filter((s) => s.id !== topSeed?.id)
4097
4288
  ];
4098
- const recommendedId = entitySeeds[0]?.id ?? sessionSeeds[0]?.id ?? sporeSeeds[0]?.id ?? null;
4289
+ const recommendedId = topSeed?.id ?? sessionSeeds[0]?.id ?? sporeSeeds[0]?.id ?? null;
4099
4290
  return {
4100
4291
  body: {
4101
4292
  seeds,
@@ -4106,32 +4297,19 @@ async function handleGetGraphSeeds(_req) {
4106
4297
  async function handleGetGraph(req) {
4107
4298
  const depth = Math.min(Number(req.query.depth) || DEFAULT_GRAPH_DEPTH, MAX_GRAPH_DEPTH);
4108
4299
  const id = req.params.id;
4109
- let centerNode = null;
4110
- let centerType = "entity";
4111
- const entity = getEntity(id);
4112
- if (entity) {
4113
- centerNode = entity;
4114
- centerType = "entity";
4300
+ let centerType;
4301
+ if (getSpore(id)) {
4302
+ centerType = "spore";
4303
+ } else if (getSession(id)) {
4304
+ centerType = "session";
4115
4305
  } else {
4116
- const spore = getSpore(id);
4117
- if (spore) {
4118
- centerNode = spore;
4119
- centerType = "spore";
4120
- } else {
4121
- const session = getSession(id);
4122
- if (session) {
4123
- centerNode = session;
4124
- centerType = "session";
4125
- }
4126
- }
4306
+ return { status: 404, body: { error: "not_found" } };
4127
4307
  }
4128
- if (!centerNode) return { status: 404, body: { error: "not_found" } };
4129
4308
  const graph = getGraphForNode(id, centerType, { depth });
4130
4309
  const filteredEdges = graph.edges.filter(
4131
4310
  (e) => !EXCLUDED_GRAPH_EDGE_TYPES.has(e.type)
4132
4311
  );
4133
4312
  const graphDb = getDatabase();
4134
- const entityIds = /* @__PURE__ */ new Set();
4135
4313
  const sporeIds = /* @__PURE__ */ new Set();
4136
4314
  const sessionIds = /* @__PURE__ */ new Set();
4137
4315
  for (const edge of filteredEdges) {
@@ -4139,70 +4317,23 @@ async function handleGetGraph(req) {
4139
4317
  [edge.source_id, edge.source_type],
4140
4318
  [edge.target_id, edge.target_type]
4141
4319
  ]) {
4142
- switch (type) {
4143
- case "entity":
4144
- entityIds.add(nodeId);
4145
- break;
4146
- case "spore":
4147
- sporeIds.add(nodeId);
4148
- break;
4149
- case "session":
4150
- sessionIds.add(nodeId);
4151
- break;
4152
- }
4320
+ if (type === "spore") sporeIds.add(nodeId);
4321
+ else if (type === "session") sessionIds.add(nodeId);
4153
4322
  }
4154
4323
  }
4155
- if (centerType === "entity") entityIds.add(id);
4156
4324
  if (centerType === "spore") sporeIds.add(id);
4157
- if (centerType === "session") sessionIds.add(id);
4158
- const entityIdArray = Array.from(entityIds);
4159
- let entityNodes = [];
4160
- if (entityIdArray.length > 0) {
4161
- const placeholders = entityIdArray.map(() => "?").join(", ");
4162
- entityNodes = graphDb.prepare(
4163
- `SELECT id, type, name, properties, status, first_seen as created_at
4164
- FROM entities WHERE id IN (${placeholders})`
4165
- ).all(...entityIdArray);
4166
- }
4325
+ else sessionIds.add(id);
4167
4326
  const sporeIdArray = Array.from(sporeIds);
4168
- let sporeNodes = [];
4169
- if (sporeIdArray.length > 0) {
4170
- const placeholders = sporeIdArray.map(() => "?").join(", ");
4171
- sporeNodes = graphDb.prepare(
4172
- `SELECT id, observation_type, status, content, properties, created_at
4173
- FROM spores WHERE id IN (${placeholders})`
4174
- ).all(...sporeIdArray);
4175
- }
4327
+ const sporeNodes = sporeIdArray.length > 0 ? graphDb.prepare(
4328
+ `SELECT id, observation_type, status, content, properties, created_at
4329
+ FROM spores WHERE id IN (${sporeIdArray.map(() => "?").join(", ")})`
4330
+ ).all(...sporeIdArray) : [];
4176
4331
  const sessionIdArray = Array.from(sessionIds);
4177
- let sessionNodes = [];
4178
- if (sessionIdArray.length > 0) {
4179
- const placeholders = sessionIdArray.map(() => "?").join(", ");
4180
- sessionNodes = graphDb.prepare(
4181
- `SELECT id, title, summary, status, started_at as created_at
4182
- FROM sessions WHERE id IN (${placeholders})`
4183
- ).all(...sessionIdArray);
4184
- }
4185
- const mentionCounts = /* @__PURE__ */ new Map();
4186
- if (entityIdArray.length > 0) {
4187
- const placeholders = entityIdArray.map(() => "?").join(", ");
4188
- const mentionRows = graphDb.prepare(
4189
- `SELECT entity_id, COUNT(*) as count FROM entity_mentions
4190
- WHERE entity_id IN (${placeholders}) GROUP BY entity_id`
4191
- ).all(...entityIdArray);
4192
- for (const row of mentionRows) {
4193
- mentionCounts.set(row.entity_id, Number(row.count));
4194
- }
4195
- }
4332
+ const sessionNodes = sessionIdArray.length > 0 ? graphDb.prepare(
4333
+ `SELECT id, title, summary, status, started_at as created_at
4334
+ FROM sessions WHERE id IN (${sessionIdArray.map(() => "?").join(", ")})`
4335
+ ).all(...sessionIdArray) : [];
4196
4336
  const allNodes = [
4197
- ...entityNodes.map((n) => ({
4198
- id: n.id,
4199
- name: n.name,
4200
- type: n.type,
4201
- status: n.status ?? void 0,
4202
- created_at: n.created_at,
4203
- properties: n.properties ?? void 0,
4204
- mention_count: mentionCounts.get(n.id) ?? 0
4205
- })),
4206
4337
  ...sporeNodes.map((n) => ({
4207
4338
  id: n.id,
4208
4339
  name: (n.content ?? "").slice(0, SPORE_NAME_PREVIEW_CHARS),
@@ -4241,10 +4372,6 @@ async function handleGetGraph(req) {
4241
4372
  var FULL_GRAPH_NODE_LIMIT = 500;
4242
4373
  async function handleGetFullGraph(_req) {
4243
4374
  const db = getDatabase();
4244
- const entityRows = db.prepare(
4245
- `SELECT id, type, name, properties, status, first_seen as created_at
4246
- FROM entities WHERE agent_id = ? LIMIT ?`
4247
- ).all(DEFAULT_AGENT_ID, FULL_GRAPH_NODE_LIMIT);
4248
4375
  const sporeRows = db.prepare(
4249
4376
  `SELECT id, observation_type, status, content, properties, created_at
4250
4377
  FROM spores WHERE agent_id = ? AND status = 'active' LIMIT ?`
@@ -4254,43 +4381,21 @@ async function handleGetFullGraph(_req) {
4254
4381
  FROM sessions ORDER BY created_at DESC LIMIT ?`
4255
4382
  ).all(FULL_GRAPH_NODE_LIMIT);
4256
4383
  const allIds = /* @__PURE__ */ new Set();
4257
- for (const r of [...entityRows, ...sporeRows, ...sessionRows]) {
4384
+ for (const r of [...sporeRows, ...sessionRows]) {
4258
4385
  allIds.add(r.id);
4259
4386
  }
4260
4387
  const excludedTypes = Array.from(EXCLUDED_GRAPH_EDGE_TYPES).map(() => "?").join(", ");
4261
4388
  const allIdsList = Array.from(allIds);
4262
4389
  const idPlaceholders = allIdsList.map(() => "?").join(", ");
4263
- const edgeRows = db.prepare(
4390
+ const edgeRows = allIdsList.length > 0 ? db.prepare(
4264
4391
  `SELECT source_id, source_type, target_id, target_type, type, confidence
4265
- FROM graph_edges
4266
- WHERE agent_id = ?
4267
- AND type NOT IN (${excludedTypes})
4268
- AND source_id IN (${idPlaceholders})
4269
- AND target_id IN (${idPlaceholders})`
4270
- ).all(DEFAULT_AGENT_ID, ...Array.from(EXCLUDED_GRAPH_EDGE_TYPES), ...allIdsList, ...allIdsList);
4271
- const filteredEdges = edgeRows;
4272
- const mentionCounts = /* @__PURE__ */ new Map();
4273
- const entityIdArray = entityRows.map((r) => r.id);
4274
- if (entityIdArray.length > 0) {
4275
- const placeholders = entityIdArray.map(() => "?").join(", ");
4276
- const mentionRows = db.prepare(
4277
- `SELECT entity_id, COUNT(*) as count FROM entity_mentions
4278
- WHERE entity_id IN (${placeholders}) GROUP BY entity_id`
4279
- ).all(...entityIdArray);
4280
- for (const row of mentionRows) {
4281
- mentionCounts.set(row.entity_id, Number(row.count));
4282
- }
4283
- }
4392
+ FROM graph_edges
4393
+ WHERE agent_id = ?
4394
+ AND type NOT IN (${excludedTypes})
4395
+ AND source_id IN (${idPlaceholders})
4396
+ AND target_id IN (${idPlaceholders})`
4397
+ ).all(DEFAULT_AGENT_ID, ...Array.from(EXCLUDED_GRAPH_EDGE_TYPES), ...allIdsList, ...allIdsList) : [];
4284
4398
  const nodes = [
4285
- ...entityRows.map((n) => ({
4286
- id: n.id,
4287
- name: n.name,
4288
- type: n.type,
4289
- status: n.status ?? void 0,
4290
- created_at: n.created_at,
4291
- properties: n.properties ?? void 0,
4292
- mention_count: mentionCounts.get(n.id) ?? 0
4293
- })),
4294
4399
  ...sporeRows.map((n) => ({
4295
4400
  id: n.id,
4296
4401
  name: (n.content ?? "").slice(0, SPORE_NAME_PREVIEW_CHARS),
@@ -4310,7 +4415,7 @@ async function handleGetFullGraph(_req) {
4310
4415
  content: n.summary ?? void 0
4311
4416
  }))
4312
4417
  ];
4313
- const edges = filteredEdges.map((e) => ({
4418
+ const edges = edgeRows.map((e) => ({
4314
4419
  source_id: e.source_id,
4315
4420
  target_id: e.target_id,
4316
4421
  label: e.type,
@@ -4353,15 +4458,40 @@ function createSearchHandler(deps) {
4353
4458
  const type = req.query.type;
4354
4459
  const limit = Number(req.query.limit) || SEARCH_RESULTS_DEFAULT_LIMIT;
4355
4460
  const namespace = req.query.namespace;
4461
+ const sanitized = sanitizeFtsQuery(query);
4356
4462
  if (mode === "fts") {
4357
- const results = fullTextSearch(query, { type, limit });
4358
- return { body: { mode: "fts", results } };
4463
+ try {
4464
+ const results = fullTextSearch(sanitized, { type, limit });
4465
+ return { body: { mode: "fts", results } };
4466
+ } catch (err) {
4467
+ return {
4468
+ status: 500,
4469
+ body: {
4470
+ error: "fts_query_failed",
4471
+ message: errorMessage(err),
4472
+ query,
4473
+ sanitized_query: sanitized !== query ? sanitized : void 0
4474
+ }
4475
+ };
4476
+ }
4359
4477
  }
4360
4478
  const queryVector = await deps.embeddingManager.embedQuery(query);
4361
4479
  if (queryVector === null) {
4362
4480
  if (mode === "auto") {
4363
- const results = fullTextSearch(query, { type, limit });
4364
- return { body: { mode: "fts", results, fallback: true } };
4481
+ try {
4482
+ const results = fullTextSearch(sanitized, { type, limit });
4483
+ return { body: { mode: "fts", results, fallback: true } };
4484
+ } catch (err) {
4485
+ return {
4486
+ status: 500,
4487
+ body: {
4488
+ error: "fts_fallback_failed",
4489
+ message: errorMessage(err),
4490
+ query,
4491
+ sanitized_query: sanitized !== query ? sanitized : void 0
4492
+ }
4493
+ };
4494
+ }
4365
4495
  }
4366
4496
  return { body: { mode: "semantic", results: [], provider_unavailable: true } };
4367
4497
  }
@@ -4393,66 +4523,297 @@ function createSearchHandler(deps) {
4393
4523
  };
4394
4524
  }
4395
4525
 
4396
- // src/daemon/api/context.ts
4397
- var SessionContextBody = external_exports.object({
4398
- session_id: external_exports.string().optional(),
4399
- branch: external_exports.string().optional()
4400
- });
4401
- var ResumeContextBody = external_exports.object({
4402
- session_id: external_exports.string(),
4403
- parent_session_id: external_exports.string().optional(),
4404
- branch: external_exports.string().optional()
4405
- });
4406
- var PromptContextBody = external_exports.object({
4407
- prompt: external_exports.string(),
4408
- session_id: external_exports.string().optional()
4409
- });
4410
- function createSessionContextHandler(deps) {
4411
- return async function handleSessionContext(req) {
4412
- const { session_id, branch } = SessionContextBody.parse(req.body);
4413
- const { logger, liveConfig } = deps;
4414
- const config = liveConfig.current;
4415
- logger.debug(LOG_KINDS.CONTEXT_QUERY, "Session context query", { session_id });
4416
- try {
4417
- const parts = [];
4418
- const tier = config.context.digest_tier;
4419
- const extract = getDigestExtract(DEFAULT_AGENT_ID, tier);
4420
- if (extract) {
4421
- parts.push(extract.content);
4422
- logger.info(LOG_KINDS.CONTEXT_DIGEST, "Digest extract found", {
4423
- session_id,
4424
- tier,
4425
- content_length: extract.content.length,
4426
- generated_at: extract.generated_at
4427
- });
4428
- } else {
4429
- logger.debug(LOG_KINDS.CONTEXT_DIGEST, "No digest extract available", { session_id, tier });
4430
- }
4431
- if (branch) {
4432
- parts.push(`Branch:: \`${branch}\``);
4433
- }
4434
- parts.push(`Session:: \`${session_id}\``);
4435
- const source = extract ? "digest" : "basic";
4436
- const contextText = parts.join("\n\n");
4437
- const estimatedTokens = estimateTokens(contextText);
4438
- logger.info(
4439
- LOG_KINDS.CONTEXT_SESSION,
4440
- `Session context: ${estimatedTokens} est. tokens, source=${source}${extract ? `, tier=${tier}` : ""}`,
4441
- {
4442
- session_id,
4443
- source,
4444
- tier: extract ? tier : void 0,
4526
+ // src/symbionts/injection-support.ts
4527
+ import fs16 from "fs";
4528
+ import path17 from "path";
4529
+ var SESSION_START_SIGNALS = ["hook session-start", '"/context"', '"/context/resume"'];
4530
+ var PROMPT_SUBMIT_SIGNALS = ["hook user-prompt-submit", '"/context/prompt"'];
4531
+ function resolveTemplateCandidates(manifest) {
4532
+ const packageRoot = resolvePackageRoot();
4533
+ const templateFile = manifest.registration?.hooksFormat === "plugin-file" ? "plugin.ts" : "hooks.json";
4534
+ return [
4535
+ path17.join(packageRoot, "src", "symbionts", "templates", manifest.name, templateFile),
4536
+ path17.join(packageRoot, "dist", "src", "symbionts", "templates", manifest.name, templateFile)
4537
+ ];
4538
+ }
4539
+ function readHooksTemplate(manifest) {
4540
+ for (const candidate of resolveTemplateCandidates(manifest)) {
4541
+ if (fs16.existsSync(candidate)) {
4542
+ return fs16.readFileSync(candidate, "utf-8");
4543
+ }
4544
+ }
4545
+ return "";
4546
+ }
4547
+ function hasAnySignal(template, signals) {
4548
+ return signals.some((signal) => template.includes(signal));
4549
+ }
4550
+ function detectSymbiontInjectionSupport(manifest) {
4551
+ const template = readHooksTemplate(manifest);
4552
+ return {
4553
+ supportsSessionStartInjection: hasAnySignal(template, SESSION_START_SIGNALS),
4554
+ supportsPromptSubmitInjection: hasAnySignal(template, PROMPT_SUBMIT_SIGNALS)
4555
+ };
4556
+ }
4557
+
4558
+ // src/daemon/api/symbionts.ts
4559
+ function listSymbiontInfos(vaultDir) {
4560
+ const manifests = loadManifests();
4561
+ let enabledNames = null;
4562
+ try {
4563
+ enabledNames = getEnabledSymbiontNames(loadMergedConfig(vaultDir));
4564
+ } catch {
4565
+ }
4566
+ return manifests.map((manifest) => ({
4567
+ name: manifest.name,
4568
+ displayName: manifest.displayName,
4569
+ binary: manifest.binary,
4570
+ enabled: enabledNames ? enabledNames.has(manifest.name) : true,
4571
+ ...manifest.resumeCommand ? { resumeCommand: manifest.resumeCommand } : {},
4572
+ ...detectSymbiontInjectionSupport(manifest)
4573
+ }));
4574
+ }
4575
+ async function handleListSymbionts(vaultDir) {
4576
+ return { body: { symbionts: listSymbiontInfos(vaultDir) } };
4577
+ }
4578
+
4579
+ // src/daemon/cortex.ts
4580
+ var CORTEX_PROMPT_BUILDER_TASK = "cortex-prompt-builder";
4581
+ var CORTEX_INSTRUCTIONS_TASK = "cortex-instructions";
4582
+ var JSON_INDENT = 2;
4583
+ function isCortexPromptBuilderDetails(value) {
4584
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4585
+ }
4586
+ function getLatestReportForAction(runId, action) {
4587
+ const reports = listReports(runId);
4588
+ for (let i = reports.length - 1; i >= 0; i -= 1) {
4589
+ if (reports[i]?.action === action) return reports[i];
4590
+ }
4591
+ return void 0;
4592
+ }
4593
+ function getCortexInstructionsSnapshot(config) {
4594
+ const row = getCortexInstructions(DEFAULT_AGENT_ID);
4595
+ return {
4596
+ content: row?.content ?? "",
4597
+ generatedAt: row?.generated_at ?? null,
4598
+ sourceRunId: row?.source_run_id ?? null,
4599
+ enabled: config.context.cortex_enabled,
4600
+ stored: Boolean(row)
4601
+ };
4602
+ }
4603
+ function resolvePromptBuilderSymbiont(vaultDir, requestedName) {
4604
+ const enabledSymbionts = listSymbiontInfos(vaultDir).filter((symbiont) => symbiont.enabled);
4605
+ if (enabledSymbionts.length === 0) return null;
4606
+ if (!requestedName) return enabledSymbionts[0] ?? null;
4607
+ return enabledSymbionts.find((symbiont) => symbiont.name === requestedName) ?? null;
4608
+ }
4609
+ async function buildCortexPrompt(vaultDir, deps, goal, requestedSymbiont) {
4610
+ const targetSymbiont = resolvePromptBuilderSymbiont(vaultDir, requestedSymbiont);
4611
+ const delivery = resolveInstructionDelivery(deps.config.context, targetSymbiont);
4612
+ const instructions = delivery.inlineInstructions ? getCortexInstructions(DEFAULT_AGENT_ID) : null;
4613
+ const builderInstruction = [
4614
+ `Goal:
4615
+ ${goal.trim()}`,
4616
+ "",
4617
+ "## Target symbiont",
4618
+ JSON.stringify(
4619
+ targetSymbiont ? {
4620
+ name: targetSymbiont.name,
4621
+ displayName: targetSymbiont.displayName,
4622
+ supportsSessionStartInjection: targetSymbiont.supportsSessionStartInjection,
4623
+ supportsPromptSubmitInjection: targetSymbiont.supportsPromptSubmitInjection
4624
+ } : null,
4625
+ null,
4626
+ JSON_INDENT
4627
+ ),
4628
+ "",
4629
+ "## Delivery contract",
4630
+ JSON.stringify(
4631
+ {
4632
+ inline_instructions: delivery.inlineInstructions,
4633
+ reason: delivery.reason
4634
+ },
4635
+ null,
4636
+ JSON_INDENT
4637
+ ),
4638
+ "",
4639
+ delivery.inlineInstructions ? [
4640
+ "## Current Cortex session-start instructions",
4641
+ instructions?.content || "No current Cortex instructions are available.",
4642
+ ""
4643
+ ].join("\n") : "## Current Cortex session-start instructions\nOmit them from the prompt because this symbiont receives session-start injection.\n"
4644
+ ].join("\n");
4645
+ const resultPromise = runAgent(vaultDir, {
4646
+ task: CORTEX_PROMPT_BUILDER_TASK,
4647
+ agentId: DEFAULT_AGENT_ID,
4648
+ instruction: builderInstruction,
4649
+ embeddingManager: deps.embeddingManager,
4650
+ logger: deps.logger
4651
+ });
4652
+ const runId = getLatestRunId(DEFAULT_AGENT_ID, CORTEX_PROMPT_BUILDER_TASK);
4653
+ const tracked = resultPromise.catch((err) => {
4654
+ deps.logger.warn(LOG_KINDS.AGENT_ERROR, "cortex-prompt-builder task failed", {
4655
+ run_id: runId ?? void 0,
4656
+ error: String(err)
4657
+ });
4658
+ });
4659
+ deps.registerInflightRun?.(tracked);
4660
+ return {
4661
+ started: true,
4662
+ runId,
4663
+ inlineInstructions: delivery.inlineInstructions,
4664
+ targetSymbiont
4665
+ };
4666
+ }
4667
+ function getCortexPromptResult(runId) {
4668
+ const run = getRun(runId);
4669
+ if (!run) return null;
4670
+ const reports = listReports(runId);
4671
+ const promptReport = getLatestReportForAction(runId, "cortex_prompt_builder");
4672
+ const details = tryParseJson(promptReport?.details, isCortexPromptBuilderDetails);
4673
+ return {
4674
+ runId,
4675
+ status: run.status,
4676
+ prompt: details?.prompt ?? "",
4677
+ reports: reports.map((report) => ({
4678
+ id: report.id,
4679
+ action: report.action,
4680
+ summary: report.summary,
4681
+ created_at: report.created_at
4682
+ })),
4683
+ error: run.error
4684
+ };
4685
+ }
4686
+ async function triggerCortexInstructions(deps) {
4687
+ const { vaultDir, embeddingManager, liveConfig, logger, getTeamClient } = deps;
4688
+ const loadExecutor = deps.loadExecutor ?? (() => import("./executor-DO6QFC6G.js"));
4689
+ const config = liveConfig.current;
4690
+ if (config.agent.event_tasks_enabled === false) {
4691
+ return { started: false, reason: "event-tasks-disabled" };
4692
+ }
4693
+ if (!hasConfiguredProvider(config, CORTEX_INSTRUCTIONS_TASK)) {
4694
+ return { started: false, reason: "provider-not-configured" };
4695
+ }
4696
+ let runAgentFn;
4697
+ try {
4698
+ ({ runAgent: runAgentFn } = await loadExecutor());
4699
+ } catch (err) {
4700
+ logger.warn(LOG_KINDS.AGENT_ERROR, "cortex-instructions: agent module unavailable", {
4701
+ error: String(err)
4702
+ });
4703
+ return {
4704
+ started: false,
4705
+ reason: "agent-module-unavailable",
4706
+ error: String(err)
4707
+ };
4708
+ }
4709
+ try {
4710
+ const built = await buildCortexInstructionsInput(config, getTeamClient);
4711
+ const resultPromise = runAgentFn(vaultDir, {
4712
+ task: CORTEX_INSTRUCTIONS_TASK,
4713
+ agentId: DEFAULT_AGENT_ID,
4714
+ instruction: built.instruction,
4715
+ runContext: { cortex_instruction_input_hash: built.inputHash },
4716
+ embeddingManager
4717
+ });
4718
+ const runId = getLatestRunId(DEFAULT_AGENT_ID, CORTEX_INSTRUCTIONS_TASK);
4719
+ const tracked = resultPromise.catch((err) => {
4720
+ logger.warn(LOG_KINDS.AGENT_ERROR, "Cortex instructions task failed", {
4721
+ error: String(err),
4722
+ run_id: runId ?? void 0
4723
+ });
4724
+ });
4725
+ deps.registerInflightRun?.(tracked);
4726
+ return { started: true, runId };
4727
+ } catch (err) {
4728
+ logger.warn(LOG_KINDS.AGENT_ERROR, "Failed to start cortex-instructions task", {
4729
+ error: String(err)
4730
+ });
4731
+ return {
4732
+ started: false,
4733
+ reason: "startup-failed",
4734
+ error: String(err)
4735
+ };
4736
+ }
4737
+ }
4738
+
4739
+ // src/daemon/api/context.ts
4740
+ var SessionContextBody = external_exports.object({
4741
+ session_id: external_exports.string().optional(),
4742
+ branch: external_exports.string().optional()
4743
+ });
4744
+ var ResumeContextBody = external_exports.object({
4745
+ session_id: external_exports.string(),
4746
+ parent_session_id: external_exports.string().optional(),
4747
+ branch: external_exports.string().optional()
4748
+ });
4749
+ var PromptContextBody = external_exports.object({
4750
+ prompt: external_exports.string(),
4751
+ session_id: external_exports.string().optional()
4752
+ });
4753
+ function createSessionContextHandler(deps) {
4754
+ return async function handleSessionContext(req) {
4755
+ const { session_id, branch } = SessionContextBody.parse(req.body);
4756
+ const { logger, liveConfig } = deps;
4757
+ const config = liveConfig.current;
4758
+ logger.debug(LOG_KINDS.CONTEXT_QUERY, "Session context query", { session_id });
4759
+ try {
4760
+ const cortexEnabled = shouldInjectCortex(config.context);
4761
+ const digestEnabled = shouldInjectSessionStartDigest(config.context);
4762
+ if (!cortexEnabled && !digestEnabled) {
4763
+ logger.debug(LOG_KINDS.CONTEXT_SESSION, "Session-start context disabled", { session_id });
4764
+ return { body: { text: "" } };
4765
+ }
4766
+ let sourceRunId = null;
4767
+ let cortexContent = "";
4768
+ if (cortexEnabled) {
4769
+ const snapshot = getCortexInstructionsSnapshot(config);
4770
+ if (snapshot.content) {
4771
+ cortexContent = snapshot.content;
4772
+ sourceRunId = snapshot.sourceRunId;
4773
+ } else {
4774
+ logger.debug(LOG_KINDS.CONTEXT_SESSION, "No stored Cortex instructions available for session start", {
4775
+ session_id
4776
+ });
4777
+ }
4778
+ }
4779
+ const composed = composeSessionStartContext(config, cortexContent);
4780
+ const textParts = composed.parts.map((p) => p.text);
4781
+ const sourceParts = composed.parts.map(
4782
+ (p) => p.kind === "cortex" ? "cortex" : `digest:${p.tier ?? config.context.digest_tier}`
4783
+ );
4784
+ if (digestEnabled && !composed.parts.some((p) => p.kind === "digest")) {
4785
+ logger.debug(LOG_KINDS.CONTEXT_SESSION, "No preferred digest extract available for session start", {
4786
+ session_id,
4787
+ preferred_tier: config.context.digest_tier
4788
+ });
4789
+ }
4790
+ if (textParts.length === 0) {
4791
+ return { body: { text: "" } };
4792
+ }
4793
+ if (branch) {
4794
+ textParts.push(`Branch:: \`${branch}\``);
4795
+ }
4796
+ textParts.push(`Session:: \`${session_id}\``);
4797
+ const source = sourceParts.join("+");
4798
+ const contextText = textParts.join("\n\n");
4799
+ const estimatedTokens = estimateTokens(contextText);
4800
+ logger.info(
4801
+ LOG_KINDS.CONTEXT_SESSION,
4802
+ `Session context: ${estimatedTokens} est. tokens, source=${source}`,
4803
+ {
4804
+ session_id,
4805
+ source,
4806
+ branch,
4807
+ source_run_id: sourceRunId,
4445
4808
  text_length: contextText.length,
4446
4809
  estimated_tokens: estimatedTokens,
4447
- generated_at: extract?.generated_at,
4448
4810
  injected_text: contextText
4449
4811
  }
4450
4812
  );
4451
4813
  return {
4452
4814
  body: {
4453
4815
  text: contextText,
4454
- source,
4455
- ...extract ? { tier } : {}
4816
+ source
4456
4817
  }
4457
4818
  };
4458
4819
  } catch (error) {
@@ -4557,9 +4918,7 @@ function createPromptContextHandler(deps) {
4557
4918
  raw_results: vectorResults.length,
4558
4919
  top_similarity: vectorResults[0]?.similarity
4559
4920
  });
4560
- if (vectorResults.length === 0) {
4561
- return { body: { text: "" } };
4562
- }
4921
+ if (vectorResults.length === 0) return { body: { text: "" } };
4563
4922
  const eligible = vectorResults.filter(
4564
4923
  (r) => !EXCLUDED_SPORE_STATUSES.has(r.metadata.status)
4565
4924
  );
@@ -4570,20 +4929,22 @@ function createPromptContextHandler(deps) {
4570
4929
  const topResults = eligible.slice(0, maxSpores);
4571
4930
  const hydrated = hydrateSearchResults(topResults);
4572
4931
  const spores = hydrated.filter((r) => r.type === "spore");
4573
- if (spores.length === 0) {
4574
- return { body: { text: "" } };
4575
- }
4932
+ if (spores.length === 0) return { body: { text: "" } };
4576
4933
  const text = formatSporeContext(spores);
4577
4934
  const promptTokens = estimateTokens(text);
4578
4935
  const titles = spores.map((s) => s.title);
4579
- logger.info(LOG_KINDS.CONTEXT_PROMPT, `Prompt context: ${spores.length} spores [${titles.join(", ")}] (~${promptTokens} tokens)`, {
4580
- session_id,
4581
- spore_count: spores.length,
4582
- spore_titles: titles,
4583
- scores: spores.map((s) => s.score.toFixed(3)),
4584
- estimated_tokens: promptTokens,
4585
- injected_text: text
4586
- });
4936
+ logger.info(
4937
+ LOG_KINDS.CONTEXT_PROMPT,
4938
+ `Prompt context: ${spores.length} spores [${titles.join(", ")}] (~${promptTokens} tokens)`,
4939
+ {
4940
+ session_id,
4941
+ spore_count: spores.length,
4942
+ spore_titles: titles,
4943
+ scores: spores.map((s) => s.score.toFixed(3)),
4944
+ estimated_tokens: promptTokens,
4945
+ injected_text: text
4946
+ }
4947
+ );
4587
4948
  return { body: { text } };
4588
4949
  };
4589
4950
  }
@@ -4602,6 +4963,75 @@ function formatSporeContext(spores) {
4602
4963
  return text === header ? "" : text;
4603
4964
  }
4604
4965
 
4966
+ // src/daemon/api/cortex.ts
4967
+ var PromptBuilderBody = external_exports.object({
4968
+ goal: external_exports.string().trim().min(1),
4969
+ symbiont: external_exports.string().trim().optional()
4970
+ });
4971
+ var PromptBuilderStatusParams = external_exports.object({
4972
+ runId: external_exports.string().trim().min(1)
4973
+ });
4974
+ function createCortexHandlers(vaultDir, deps) {
4975
+ async function handleGetInstructions() {
4976
+ const snapshot = getCortexInstructionsSnapshot(deps.liveConfig.current);
4977
+ return { body: snapshot };
4978
+ }
4979
+ async function handleRefreshInstructions() {
4980
+ const result = await triggerCortexInstructions({
4981
+ vaultDir,
4982
+ embeddingManager: deps.embeddingManager,
4983
+ liveConfig: deps.liveConfig,
4984
+ logger: deps.logger,
4985
+ getTeamClient: deps.getTeamClient,
4986
+ registerInflightRun: deps.registerInflightRun
4987
+ });
4988
+ if (!result.started && (result.reason === "provider-not-configured" || result.reason === "event-tasks-disabled")) {
4989
+ return {
4990
+ status: 400,
4991
+ body: {
4992
+ ...errorBody(
4993
+ result.reason,
4994
+ result.reason === "provider-not-configured" ? "No agent provider configured. Configure one in Settings." : "Event-driven tasks are disabled. Enable them in Settings."
4995
+ ),
4996
+ started: false,
4997
+ reason: result.reason
4998
+ }
4999
+ };
5000
+ }
5001
+ return { body: result };
5002
+ }
5003
+ async function handleBuildPrompt(req) {
5004
+ const { goal, symbiont } = PromptBuilderBody.parse(req.body);
5005
+ const result = await buildCortexPrompt(
5006
+ vaultDir,
5007
+ {
5008
+ config: deps.liveConfig.current,
5009
+ embeddingManager: deps.embeddingManager,
5010
+ getTeamClient: deps.getTeamClient,
5011
+ logger: deps.logger,
5012
+ registerInflightRun: deps.registerInflightRun
5013
+ },
5014
+ goal,
5015
+ symbiont
5016
+ );
5017
+ return { body: result };
5018
+ }
5019
+ async function handleGetPromptResult(req) {
5020
+ const { runId } = PromptBuilderStatusParams.parse(req.params);
5021
+ const result = getCortexPromptResult(runId);
5022
+ if (!result) {
5023
+ return { status: 404, body: errorBody("run-not-found", "Run not found") };
5024
+ }
5025
+ return { body: result };
5026
+ }
5027
+ return {
5028
+ handleGetInstructions,
5029
+ handleRefreshInstructions,
5030
+ handleBuildPrompt,
5031
+ handleGetPromptResult
5032
+ };
5033
+ }
5034
+
4605
5035
  // src/db/queries/feed.ts
4606
5036
  function getActivityFeed(limit = FEED_DEFAULT_LIMIT) {
4607
5037
  const db = getDatabase();
@@ -4640,24 +5070,6 @@ async function handleGetFeed(req) {
4640
5070
  return { body: feed };
4641
5071
  }
4642
5072
 
4643
- // src/daemon/api/symbionts.ts
4644
- async function handleListSymbionts(vaultDir) {
4645
- const manifests = loadManifests();
4646
- let enabledNames = null;
4647
- try {
4648
- enabledNames = getEnabledSymbiontNames(loadMergedConfig(vaultDir));
4649
- } catch {
4650
- }
4651
- const symbionts = manifests.map((m) => ({
4652
- name: m.name,
4653
- displayName: m.displayName,
4654
- binary: m.binary,
4655
- enabled: enabledNames ? enabledNames.has(m.name) : true,
4656
- ...m.resumeCommand ? { resumeCommand: m.resumeCommand } : {}
4657
- }));
4658
- return { body: { symbionts } };
4659
- }
4660
-
4661
5073
  // src/daemon/api/embedding.ts
4662
5074
  var EMBEDDING_STATUS_IDLE = "idle";
4663
5075
  var EMBEDDING_STATUS_PENDING = "pending";
@@ -5661,17 +6073,17 @@ var SqliteRecordSource = class {
5661
6073
  };
5662
6074
 
5663
6075
  // src/daemon/database/manager.ts
5664
- import fs17 from "fs";
6076
+ import fs18 from "fs";
5665
6077
 
5666
6078
  // src/db/queries/database.ts
5667
- import fs16 from "fs";
6079
+ import fs17 from "fs";
5668
6080
  function pragmaScalar(name) {
5669
6081
  const db = getDatabase();
5670
6082
  return db.pragma(name, { simple: true });
5671
6083
  }
5672
6084
  function safeFileSize(filePath) {
5673
6085
  try {
5674
- return fs16.statSync(filePath).size;
6086
+ return fs17.statSync(filePath).size;
5675
6087
  } catch (err) {
5676
6088
  if (err.code === "ENOENT") return 0;
5677
6089
  throw err;
@@ -5943,7 +6355,7 @@ var DatabaseMaintenanceManager = class {
5943
6355
  }
5944
6356
  async vacuum() {
5945
6357
  const size_before = this.fileSize();
5946
- const stats = await fs17.promises.statfs(this.vaultDir);
6358
+ const stats = await fs18.promises.statfs(this.vaultDir);
5947
6359
  const free_bytes = Number(stats.bavail) * Number(stats.bsize);
5948
6360
  const required_bytes = size_before * VACUUM_FREE_SPACE_MULTIPLIER;
5949
6361
  if (free_bytes < required_bytes) {
@@ -5991,7 +6403,7 @@ var DatabaseMaintenanceManager = class {
5991
6403
  }
5992
6404
  fileSize() {
5993
6405
  try {
5994
- return fs17.statSync(this.dbPath).size;
6406
+ return fs18.statSync(this.dbPath).size;
5995
6407
  } catch {
5996
6408
  return 0;
5997
6409
  }
@@ -6308,45 +6720,104 @@ async function handleUpdateTaskConfig(req, vaultDir) {
6308
6720
  };
6309
6721
  }
6310
6722
 
6723
+ // src/agent/types.ts
6724
+ var PROVIDER_TYPES = [
6725
+ "anthropic",
6726
+ "ollama",
6727
+ "lmstudio",
6728
+ "openai",
6729
+ "openrouter",
6730
+ "openai-compatible"
6731
+ ];
6732
+
6311
6733
  // src/daemon/api/providers.ts
6312
6734
  var ANTHROPIC_MODELS_TIMEOUT_MS = 5e3;
6313
6735
  var ANTHROPIC_MODELS_CACHE_TTL_MS = 10 * 60 * 1e3;
6314
6736
  var anthropicModelsCache = null;
6315
6737
  var HTTP_OK2 = 200;
6316
6738
  var HTTP_BAD_REQUEST2 = 400;
6317
- async function handleGetProviders() {
6318
- const results = await Promise.allSettled([
6319
- detectAnthropic(),
6320
- detectLocalProviderInfo("ollama", OllamaBackend.DEFAULT_BASE_URL),
6321
- detectLocalProviderInfo("lmstudio", LmStudioBackend.DEFAULT_BASE_URL)
6322
- ]);
6739
+ async function handleGetProviders(logger) {
6740
+ const detectionPlan = [
6741
+ {
6742
+ detect: () => detectAnthropic(logger),
6743
+ fallback: { type: "anthropic", runtime: "claude-sdk", available: false, models: [] }
6744
+ },
6745
+ {
6746
+ detect: () => detectLocalProviderInfo("ollama", OllamaBackend.DEFAULT_BASE_URL),
6747
+ fallback: { type: "ollama", runtime: "claude-sdk", available: false, baseUrl: OllamaBackend.DEFAULT_BASE_URL, models: [] }
6748
+ },
6749
+ {
6750
+ detect: () => detectLocalProviderInfo("lmstudio", LmStudioBackend.DEFAULT_BASE_URL),
6751
+ fallback: { type: "lmstudio", runtime: "claude-sdk", available: false, baseUrl: LmStudioBackend.DEFAULT_BASE_URL, models: [] }
6752
+ },
6753
+ {
6754
+ detect: () => detectRemoteProviderInfo("openai", DEFAULT_OPENAI_URL, logger),
6755
+ fallback: { type: "openai", runtime: "openai-agents", available: false, authConfigured: false, baseUrl: DEFAULT_OPENAI_URL, models: [] }
6756
+ },
6757
+ {
6758
+ detect: () => detectRemoteProviderInfo("openrouter", DEFAULT_OPENROUTER_URL, logger),
6759
+ fallback: { type: "openrouter", runtime: "openai-agents", available: false, authConfigured: false, baseUrl: DEFAULT_OPENROUTER_URL, models: [] }
6760
+ },
6761
+ {
6762
+ detect: () => detectLocalProviderInfo("openai-compatible", LmStudioBackend.DEFAULT_BASE_URL),
6763
+ fallback: { type: "openai-compatible", runtime: "openai-agents", available: false, baseUrl: LmStudioBackend.DEFAULT_BASE_URL, models: [] }
6764
+ }
6765
+ ];
6766
+ const results = await Promise.allSettled(detectionPlan.map((entry) => entry.detect()));
6323
6767
  const providers = results.map(
6324
- (r) => r.status === "fulfilled" ? r.value : { type: "unknown", available: false, models: [] }
6768
+ (result, index) => result.status === "fulfilled" ? result.value : detectionPlan[index].fallback
6325
6769
  );
6326
6770
  return { status: HTTP_OK2, body: { providers } };
6327
6771
  }
6772
+ var ProviderTestBody = external_exports.object({
6773
+ type: external_exports.enum(PROVIDER_TYPES),
6774
+ baseUrl: external_exports.string().optional(),
6775
+ /**
6776
+ * Deprecated: `base_url` is the legacy snake_case key. Accepted for one
6777
+ * release and then removed. Use `baseUrl` going forward.
6778
+ */
6779
+ base_url: external_exports.string().optional(),
6780
+ local_backend: external_exports.enum(["ollama", "lmstudio"]).optional(),
6781
+ model: external_exports.string().optional()
6782
+ });
6328
6783
  async function handleTestProvider(req) {
6329
- const body = req.body;
6330
- const type = body?.type;
6331
- if (!type || !["anthropic", "ollama", "lmstudio"].includes(type)) {
6784
+ const parse = ProviderTestBody.safeParse(req.body);
6785
+ if (!parse.success) {
6332
6786
  return {
6333
6787
  status: HTTP_BAD_REQUEST2,
6334
- body: { error: "type is required and must be one of: anthropic, ollama, lmstudio" }
6788
+ body: { error: `type is required and must be one of: ${PROVIDER_TYPES.join(", ")}` }
6335
6789
  };
6336
6790
  }
6337
- const baseUrl = body?.baseUrl;
6791
+ const parsed = parse.data;
6792
+ if (parsed.base_url !== void 0 && parsed.baseUrl === void 0) {
6793
+ process.stderr.write(
6794
+ "[myco providers] POST /api/providers/test: base_url is deprecated; use baseUrl\n"
6795
+ );
6796
+ }
6797
+ const type = parsed.type;
6798
+ const baseUrl = parsed.baseUrl ?? parsed.base_url;
6799
+ const localBackend = parsed.local_backend;
6338
6800
  const start = performance.now();
6339
6801
  let result;
6340
6802
  try {
6341
6803
  if (type === "ollama") {
6342
- result = await testLocalProvider(new OllamaBackend({ base_url: baseUrl }), "Ollama", OllamaBackend.DEFAULT_BASE_URL, baseUrl);
6804
+ result = await testResolvedLocalProvider("ollama", baseUrl);
6343
6805
  } else if (type === "lmstudio") {
6344
- result = await testLocalProvider(new LmStudioBackend({ base_url: baseUrl }), "LM Studio", LmStudioBackend.DEFAULT_BASE_URL, baseUrl);
6806
+ result = await testResolvedLocalProvider("lmstudio", baseUrl);
6807
+ } else if (type === "openai-compatible") {
6808
+ result = await testResolvedLocalProvider("openai-compatible", baseUrl, localBackend);
6809
+ } else if (type === "openai") {
6810
+ result = await testRemoteProvider("openai", "OpenAI");
6811
+ } else if (type === "openrouter") {
6812
+ result = await testRemoteProvider("openrouter", "OpenRouter");
6345
6813
  } else {
6346
6814
  result = testAnthropic();
6347
6815
  }
6348
6816
  } catch (err) {
6349
- result = { ok: false, error: String(err) };
6817
+ result = {
6818
+ ok: false,
6819
+ error: errorMessage(err)
6820
+ };
6350
6821
  }
6351
6822
  if (result.ok) {
6352
6823
  result.latency_ms = Math.round(performance.now() - start);
@@ -6354,15 +6825,21 @@ async function handleTestProvider(req) {
6354
6825
  return { status: HTTP_OK2, body: result };
6355
6826
  }
6356
6827
  async function detectLocalProviderInfo(type, defaultBaseUrl) {
6357
- const status = await checkLocalProvider(type);
6828
+ const status = await checkLocalProvider(type === "openai-compatible" ? "lmstudio" : type);
6358
6829
  const variantFiltered = status.models.filter((m) => !/-ctx\d+/.test(m));
6359
6830
  const models = filterLlmModels(variantFiltered);
6360
- return { type, available: status.available, baseUrl: defaultBaseUrl, models };
6831
+ return {
6832
+ type,
6833
+ runtime: type === "openai-compatible" ? "openai-agents" : "claude-sdk",
6834
+ available: status.available,
6835
+ baseUrl: defaultBaseUrl,
6836
+ models
6837
+ };
6361
6838
  }
6362
- async function detectAnthropic() {
6839
+ async function detectAnthropic(logger) {
6363
6840
  const now = Date.now();
6364
6841
  if (anthropicModelsCache && now - anthropicModelsCache.ts < ANTHROPIC_MODELS_CACHE_TTL_MS) {
6365
- return { type: "anthropic", available: true, models: anthropicModelsCache.models };
6842
+ return { type: "anthropic", runtime: "claude-sdk", available: true, models: anthropicModelsCache.models };
6366
6843
  }
6367
6844
  let models = ANTHROPIC_MODELS;
6368
6845
  try {
@@ -6375,10 +6852,35 @@ async function detectAnthropic() {
6375
6852
  if (liveModels.length > 0) {
6376
6853
  models = liveModels;
6377
6854
  }
6378
- } catch {
6855
+ } catch (err) {
6856
+ const detail = errorMessage(err);
6857
+ logger?.warn("providers.anthropic.models-unavailable", "Anthropic model list unavailable", { error: detail });
6379
6858
  }
6380
6859
  anthropicModelsCache = { ts: now, models };
6381
- return { type: "anthropic", available: true, models };
6860
+ return { type: "anthropic", runtime: "claude-sdk", available: true, models };
6861
+ }
6862
+ async function detectRemoteProviderInfo(type, baseUrl, logger) {
6863
+ const authConfigured = Boolean(getRemoteProviderApiKey(type));
6864
+ let models = [];
6865
+ let available = false;
6866
+ if (authConfigured) {
6867
+ try {
6868
+ models = await fetchRemoteProviderModels(type, void 0, ANTHROPIC_MODELS_TIMEOUT_MS);
6869
+ available = true;
6870
+ } catch (err) {
6871
+ available = false;
6872
+ const detail = errorMessage(err);
6873
+ logger?.warn(`providers.${type}.models-unavailable`, `${type} model list unavailable`, { error: detail });
6874
+ }
6875
+ }
6876
+ return {
6877
+ type,
6878
+ runtime: "openai-agents",
6879
+ available,
6880
+ authConfigured,
6881
+ baseUrl,
6882
+ models
6883
+ };
6382
6884
  }
6383
6885
  async function testLocalProvider(backend, label, defaultBaseUrl, baseUrl) {
6384
6886
  const available = await backend.isAvailable();
@@ -6387,9 +6889,114 @@ async function testLocalProvider(backend, label, defaultBaseUrl, baseUrl) {
6387
6889
  }
6388
6890
  return { ok: true };
6389
6891
  }
6892
+ async function testResolvedLocalProvider(type, baseUrl, localBackend) {
6893
+ const kind = inferLocalOpenAIBackendKind({ type, localBackend, baseUrl }) ?? "lmstudio";
6894
+ const label = type === "openai-compatible" ? `OpenAI-compatible ${getLocalOpenAIBackendLabel(kind)} provider` : getLocalOpenAIBackendLabel(kind);
6895
+ return testLocalProvider(
6896
+ createLocalOpenAIBackend(kind, baseUrl),
6897
+ label,
6898
+ getLocalOpenAIBackendDefaultBaseUrl(kind),
6899
+ baseUrl
6900
+ );
6901
+ }
6390
6902
  function testAnthropic() {
6391
6903
  return { ok: true };
6392
6904
  }
6905
+ async function testRemoteProvider(provider, label) {
6906
+ if (!getRemoteProviderApiKey(provider)) {
6907
+ return { ok: false, error: `${label} API key not configured in daemon secrets or environment` };
6908
+ }
6909
+ try {
6910
+ await fetchRemoteProviderModels(provider);
6911
+ return { ok: true };
6912
+ } catch (error) {
6913
+ return { ok: false, error: error instanceof Error ? error.message : `${label} connection failed` };
6914
+ }
6915
+ }
6916
+
6917
+ // src/daemon/api/provider-secrets.ts
6918
+ var SECRET_PREVIEW_PREFIX_CHARS = 8;
6919
+ var SECRET_PREVIEW_SUFFIX_CHARS = 4;
6920
+ var SECRET_ENV_BY_PROVIDER = {
6921
+ openai: OPENAI_API_KEY_ENV,
6922
+ openrouter: OPENROUTER_API_KEY_ENV
6923
+ };
6924
+ function isSecretProvider(value) {
6925
+ return value === "openai" || value === "openrouter";
6926
+ }
6927
+ function maskSecret(secret) {
6928
+ if (secret.length <= SECRET_PREVIEW_PREFIX_CHARS + SECRET_PREVIEW_SUFFIX_CHARS) {
6929
+ return "*".repeat(secret.length);
6930
+ }
6931
+ return `${secret.slice(0, SECRET_PREVIEW_PREFIX_CHARS)}${"*".repeat(secret.length - SECRET_PREVIEW_PREFIX_CHARS - SECRET_PREVIEW_SUFFIX_CHARS)}${secret.slice(-SECRET_PREVIEW_SUFFIX_CHARS)}`;
6932
+ }
6933
+ function buildSecretInfo(provider, storedSecrets) {
6934
+ const envKey = SECRET_ENV_BY_PROVIDER[provider];
6935
+ const storedValue = storedSecrets[envKey];
6936
+ const envValue = process.env[envKey];
6937
+ const effectiveValue = storedValue ?? envValue;
6938
+ return {
6939
+ configured: Boolean(effectiveValue),
6940
+ envKey,
6941
+ maskedValue: effectiveValue ? maskSecret(effectiveValue) : null,
6942
+ source: storedValue ? "vault" : envValue ? "env" : "none"
6943
+ };
6944
+ }
6945
+ function getSecretInfo(vaultDir, provider) {
6946
+ return buildSecretInfo(provider, readSecrets(vaultDir));
6947
+ }
6948
+ async function handleGetProviderSecrets(vaultDir) {
6949
+ const storedSecrets = readSecrets(vaultDir);
6950
+ return {
6951
+ body: {
6952
+ secrets: {
6953
+ openai: buildSecretInfo("openai", storedSecrets),
6954
+ openrouter: buildSecretInfo("openrouter", storedSecrets)
6955
+ }
6956
+ }
6957
+ };
6958
+ }
6959
+ async function handlePutProviderSecret(vaultDir, req) {
6960
+ const provider = req.params.provider;
6961
+ const body = req.body;
6962
+ if (!provider || !isSecretProvider(provider)) {
6963
+ return { status: 400, body: { error: "provider must be one of: openai, openrouter" } };
6964
+ }
6965
+ if (!body?.api_key?.trim()) {
6966
+ return { status: 400, body: { error: "api_key is required" } };
6967
+ }
6968
+ const envKey = SECRET_ENV_BY_PROVIDER[provider];
6969
+ const apiKey = body.api_key.trim();
6970
+ writeSecret(vaultDir, envKey, apiKey);
6971
+ process.env[envKey] = apiKey;
6972
+ if (provider === "openai") {
6973
+ process.env.OPENAI_API_KEY = apiKey;
6974
+ }
6975
+ return {
6976
+ body: {
6977
+ provider,
6978
+ secret: getSecretInfo(vaultDir, provider)
6979
+ }
6980
+ };
6981
+ }
6982
+ async function handleDeleteProviderSecret(vaultDir, req) {
6983
+ const provider = req.params.provider;
6984
+ if (!provider || !isSecretProvider(provider)) {
6985
+ return { status: 400, body: { error: "provider must be one of: openai, openrouter" } };
6986
+ }
6987
+ const envKey = SECRET_ENV_BY_PROVIDER[provider];
6988
+ deleteSecrets(vaultDir, [envKey]);
6989
+ delete process.env[envKey];
6990
+ if (provider === "openai") {
6991
+ delete process.env.OPENAI_API_KEY;
6992
+ }
6993
+ return {
6994
+ body: {
6995
+ provider,
6996
+ secret: getSecretInfo(vaultDir, provider)
6997
+ }
6998
+ };
6999
+ }
6393
7000
 
6394
7001
  // src/daemon/task-scheduling.ts
6395
7002
  import { resolve } from "path";
@@ -6442,7 +7049,7 @@ function buildScheduledJobs(tasks, configOverrides, context, initialLastRuns) {
6442
7049
  // src/daemon/task-scheduling.ts
6443
7050
  var SCHEDULED_JOB_PREFIX = "scheduled:";
6444
7051
  async function registerScheduledTasks(powerManager, deps) {
6445
- const { definitionsDir, vaultDir, embeddingManager, logger, liveConfig } = deps;
7052
+ const { definitionsDir, vaultDir, embeddingManager, logger, liveConfig, getTeamClient } = deps;
6446
7053
  const runningTasks = /* @__PURE__ */ new Set();
6447
7054
  if (!definitionsDir) {
6448
7055
  logger.warn(LOG_KINDS.AGENT_ERROR, "Skipping dynamic task scheduling \u2014 definitions directory unavailable");
@@ -6452,7 +7059,7 @@ async function registerScheduledTasks(powerManager, deps) {
6452
7059
  if (!lastEnabled) {
6453
7060
  logger.info(LOG_KINDS.AGENT_RUN, "Scheduled agent tasks disabled (agent.scheduled_tasks_enabled: false) \u2014 jobs registered but will no-op until enabled");
6454
7061
  }
6455
- const { loadAllTasks: loadAllTasks2 } = await import("./registry-U4CHXK6R.js");
7062
+ const { loadAllTasks: loadAllTasks2 } = await import("./registry-F3THYC5M.js");
6456
7063
  const allTasks = Array.from(loadAllTasks2(definitionsDir, vaultDir).values());
6457
7064
  const taskAgentMap = /* @__PURE__ */ new Map();
6458
7065
  for (const task of allTasks) {
@@ -6488,10 +7095,34 @@ async function registerScheduledTasks(powerManager, deps) {
6488
7095
  lastEnabled = enabled;
6489
7096
  }
6490
7097
  if (!enabled) return;
6491
- const { runAgent } = await import("./executor-HWW2QNZQ.js");
7098
+ const { runAgent: runAgent2 } = await import("./executor-DO6QFC6G.js");
7099
+ const resumableRun = getLatestResumableRunForTask(DEFAULT_AGENT_ID, taskName);
7100
+ if (resumableRun) {
7101
+ const resumed = await runAgent2(vaultDir, {
7102
+ agentId: DEFAULT_AGENT_ID,
7103
+ task: taskName,
7104
+ resumeRunId: resumableRun.id,
7105
+ resumeMode: "scheduled",
7106
+ embeddingManager,
7107
+ logger
7108
+ });
7109
+ logger.info(LOG_KINDS.AGENT_RUN, `Scheduled task ${taskName} resumed`, {
7110
+ status: resumed.status,
7111
+ runId: resumed.runId
7112
+ });
7113
+ return;
7114
+ }
6492
7115
  const taskConfig = config.agent.tasks?.[taskName];
6493
7116
  const projectRoot = resolve(vaultDir, "..");
6494
- const built = buildTaskInstruction(taskName, taskConfig?.params, taskAgentMap.get(taskName), projectRoot, embeddingManager);
7117
+ const built = await buildTaskInstruction(
7118
+ taskName,
7119
+ taskConfig?.params,
7120
+ taskAgentMap.get(taskName),
7121
+ projectRoot,
7122
+ embeddingManager,
7123
+ config,
7124
+ getTeamClient
7125
+ );
6495
7126
  if (isInstructionRequiredTask(taskName) && !built) {
6496
7127
  logger.info(
6497
7128
  LOG_KINDS.AGENT_RUN,
@@ -6500,11 +7131,12 @@ async function registerScheduledTasks(powerManager, deps) {
6500
7131
  );
6501
7132
  return;
6502
7133
  }
6503
- const result = await runAgent(vaultDir, {
7134
+ const result = await runAgent2(vaultDir, {
6504
7135
  task: taskName,
6505
7136
  instruction: built?.instruction,
6506
7137
  runContext: built?.context,
6507
- embeddingManager
7138
+ embeddingManager,
7139
+ logger
6508
7140
  });
6509
7141
  logger.info(LOG_KINDS.AGENT_RUN, `Scheduled task ${taskName} completed`, {
6510
7142
  status: result.status,
@@ -6527,7 +7159,7 @@ async function registerScheduledTasks(powerManager, deps) {
6527
7159
  link: `/agent?run=${result.runId}`,
6528
7160
  metadata: { taskName, runId: result.runId }
6529
7161
  }, config);
6530
- const { countToolCallsByRun } = await import("./turns-YFNI5CQC.js");
7162
+ const { countToolCallsByRun } = await import("./turns-HU2CTZAP.js");
6531
7163
  const counts = countToolCallsByRun(result.runId, ["vault_create_spore", "vault_write_digest"]);
6532
7164
  const sporeCount = counts["vault_create_spore"] ?? 0;
6533
7165
  const digestCount = counts["vault_write_digest"] ?? 0;
@@ -6602,32 +7234,171 @@ function listTeamMembers() {
6602
7234
  ).all();
6603
7235
  }
6604
7236
 
6605
- // src/daemon/api/mcp-proxy.ts
6606
- var SPORE_ID_RANDOM_BYTES = 4;
6607
- var RESOLUTION_ID_RANDOM_BYTES = 8;
6608
- var MIN_CONSOLIDATE_SOURCES = 2;
6609
- var RememberBody = external_exports.object({
6610
- content: external_exports.string(),
6611
- type: external_exports.string().optional(),
6612
- tags: external_exports.array(external_exports.string()).optional()
6613
- });
6614
- var SupersedeBody = external_exports.object({
6615
- old_spore_id: external_exports.string(),
6616
- new_spore_id: external_exports.string(),
6617
- reason: external_exports.string().optional()
6618
- });
6619
- function isoToEpochSeconds(iso) {
6620
- const ms = Date.parse(iso);
6621
- return Number.isNaN(ms) ? void 0 : Math.floor(ms / 1e3);
6622
- }
6623
- function registerMcpUserAgent(createdAt) {
6624
- registerAgent({
6625
- id: USER_AGENT_ID,
6626
- name: USER_AGENT_NAME,
6627
- created_at: createdAt
6628
- });
6629
- }
6630
- function toPlanProgress(content) {
7237
+ // src/daemon/plan-capture.ts
7238
+ import { createHash as createHash4 } from "crypto";
7239
+ import os8 from "os";
7240
+ import path18 from "path";
7241
+ function extractTaggedPlans(text, tags) {
7242
+ const results = [];
7243
+ for (const tag of tags) {
7244
+ const regex = new RegExp(`<${tag}>\\n?([\\s\\S]*?)\\n?</${tag}>`, "g");
7245
+ let match;
7246
+ while ((match = regex.exec(text)) !== null) {
7247
+ const content = match[1].trim();
7248
+ if (content) results.push({ tag, content });
7249
+ }
7250
+ }
7251
+ return results;
7252
+ }
7253
+ var FILE_WRITE_TOOLS = /* @__PURE__ */ new Set([
7254
+ "Write",
7255
+ "Edit",
7256
+ "Create",
7257
+ "write",
7258
+ "edit",
7259
+ "patch",
7260
+ "create"
7261
+ ]);
7262
+ var HEADING_REGEX = /^#\s+(.+)$/m;
7263
+ var TRANSCRIPT_SOURCE_TITLE_PREFIX = `${TRANSCRIPT_SOURCE_PREFIX}`;
7264
+ function resolvePlanWatchDir(watchDir, projectRoot) {
7265
+ const expanded = watchDir.startsWith("~/") ? path18.join(os8.homedir(), watchDir.slice(2)) : watchDir;
7266
+ return path18.isAbsolute(expanded) ? expanded : path18.resolve(projectRoot, expanded);
7267
+ }
7268
+ function isInPlanDirectory(filePath, watchDirs, projectRoot) {
7269
+ const abs = path18.isAbsolute(filePath) ? filePath : path18.resolve(projectRoot, filePath);
7270
+ return watchDirs.some((dir) => {
7271
+ const absDir = resolvePlanWatchDir(dir, projectRoot);
7272
+ const prefix = absDir.endsWith(path18.sep) ? absDir : absDir + path18.sep;
7273
+ return abs === absDir || abs.startsWith(prefix);
7274
+ });
7275
+ }
7276
+ function isPlanWriteEvent(toolName, toolInput, config) {
7277
+ if (!FILE_WRITE_TOOLS.has(toolName)) return null;
7278
+ const filePath = toolInput?.file_path ?? toolInput?.path ?? toolInput?.filePath;
7279
+ if (typeof filePath !== "string") return null;
7280
+ if (!isInPlanDirectory(filePath, config.watchDirs, config.projectRoot)) return null;
7281
+ if (config.extensions?.length) {
7282
+ const ext = path18.extname(filePath).toLowerCase();
7283
+ if (!config.extensions.includes(ext)) return null;
7284
+ }
7285
+ return filePath;
7286
+ }
7287
+ function parsePlanTitle(content, filename) {
7288
+ const match = HEADING_REGEX.exec(content);
7289
+ if (match) return match[1].trim();
7290
+ return filename ?? null;
7291
+ }
7292
+ function normalizePlanTags(tags) {
7293
+ if (tags === void 0 || tags === null) return null;
7294
+ return Array.isArray(tags) ? tags.join(", ") : tags;
7295
+ }
7296
+ function fileTitleFromSourcePath(sourcePath) {
7297
+ if (!sourcePath || sourcePath.startsWith(TRANSCRIPT_SOURCE_TITLE_PREFIX)) return null;
7298
+ return path18.basename(sourcePath);
7299
+ }
7300
+ function resolvePlanTitle(input) {
7301
+ const explicitTitle = input.title?.trim();
7302
+ if (explicitTitle) return explicitTitle;
7303
+ const headingTitle = parsePlanTitle(input.content);
7304
+ if (headingTitle) return headingTitle;
7305
+ const sourcePathTitle = fileTitleFromSourcePath(input.sourcePath);
7306
+ if (sourcePathTitle) return sourcePathTitle;
7307
+ return input.planKey ? humanizePlanToken(input.planKey) : null;
7308
+ }
7309
+ function persistPlan(input) {
7310
+ const createdAt = input.createdAt ?? Math.floor(Date.now() / 1e3);
7311
+ const updatedAt = input.updatedAt ?? createdAt;
7312
+ const contentHash = createHash4(CONTENT_HASH_ALGORITHM).update(input.content).digest("hex");
7313
+ const existingPlan = getPlanByLogicalKey(input.logicalKey);
7314
+ const status = input.status ?? existingPlan?.status ?? "active";
7315
+ const promptBatchId = input.promptBatchId === void 0 ? existingPlan?.prompt_batch_id ?? null : input.promptBatchId;
7316
+ const resolvedTitle = resolvePlanTitle({
7317
+ content: input.content,
7318
+ title: input.title,
7319
+ sourcePath: input.sourcePath,
7320
+ planKey: input.planKey
7321
+ });
7322
+ if (existingPlan && existingPlan.content_hash === contentHash && existingPlan.title === resolvedTitle && existingPlan.status === status) {
7323
+ return existingPlan;
7324
+ }
7325
+ if (existingPlan && existingPlan.content_hash !== contentHash) {
7326
+ const priorSource = existingPlan.source_path ?? null;
7327
+ const newSource = input.sourcePath ?? null;
7328
+ if (priorSource !== newSource) {
7329
+ input.logger?.warn(LOG_KINDS.CAPTURE_PLAN, "Plan overwritten mid-session", {
7330
+ logical_key: input.logicalKey,
7331
+ session_id: input.sessionId,
7332
+ prior_source: priorSource,
7333
+ new_source: newSource,
7334
+ prior_updated_at: existingPlan.updated_at
7335
+ });
7336
+ }
7337
+ }
7338
+ return upsertPlan({
7339
+ id: buildPlanId(input.logicalKey),
7340
+ logical_key: input.logicalKey,
7341
+ title: resolvedTitle,
7342
+ content: input.content,
7343
+ source_path: input.sourcePath ?? null,
7344
+ tags: normalizePlanTags(input.tags),
7345
+ session_id: input.sessionId,
7346
+ prompt_batch_id: promptBatchId,
7347
+ content_hash: contentHash,
7348
+ status,
7349
+ created_at: createdAt,
7350
+ updated_at: updatedAt
7351
+ });
7352
+ }
7353
+ function capturePlan(input) {
7354
+ const normalizedSourcePath = normalizePlanSourcePath(input.sourcePath, input.projectRoot);
7355
+ return persistPlan({
7356
+ sessionId: input.sessionId,
7357
+ content: input.content,
7358
+ logicalKey: buildPathPlanLogicalKey(normalizedSourcePath),
7359
+ sourcePath: normalizedSourcePath,
7360
+ promptBatchId: input.promptBatchId,
7361
+ logger: input.logger
7362
+ });
7363
+ }
7364
+ function captureTaggedPlan(input) {
7365
+ return persistPlan({
7366
+ sessionId: input.sessionId,
7367
+ content: input.content,
7368
+ logicalKey: buildSessionTagPlanLogicalKey(input.sessionId, input.tag),
7369
+ sourcePath: `${TRANSCRIPT_SOURCE_PREFIX}${input.tag}`,
7370
+ promptBatchId: input.promptBatchId,
7371
+ planKey: input.tag,
7372
+ logger: input.logger
7373
+ });
7374
+ }
7375
+
7376
+ // src/daemon/api/mcp-proxy.ts
7377
+ var SPORE_ID_RANDOM_BYTES = 4;
7378
+ var RESOLUTION_ID_RANDOM_BYTES = 8;
7379
+ var MIN_CONSOLIDATE_SOURCES = 2;
7380
+ var RememberBody = external_exports.object({
7381
+ content: external_exports.string(),
7382
+ type: external_exports.string().optional(),
7383
+ tags: external_exports.array(external_exports.string()).optional()
7384
+ });
7385
+ var SupersedeBody = external_exports.object({
7386
+ old_spore_id: external_exports.string(),
7387
+ new_spore_id: external_exports.string(),
7388
+ reason: external_exports.string().optional()
7389
+ });
7390
+ function isoToEpochSeconds(iso) {
7391
+ const ms = Date.parse(iso);
7392
+ return Number.isNaN(ms) ? void 0 : Math.floor(ms / 1e3);
7393
+ }
7394
+ function registerMcpUserAgent(createdAt) {
7395
+ registerAgent({
7396
+ id: USER_AGENT_ID,
7397
+ name: USER_AGENT_NAME,
7398
+ created_at: createdAt
7399
+ });
7400
+ }
7401
+ function toPlanProgress(content) {
6631
7402
  const planContent = content ?? "";
6632
7403
  const checked = (planContent.match(/- \[x\]/gi) ?? []).length;
6633
7404
  const unchecked = (planContent.match(/- \[ \]/g) ?? []).length;
@@ -6644,8 +7415,20 @@ var ConsolidateBody = external_exports.object({
6644
7415
  tags: external_exports.array(external_exports.string()).optional(),
6645
7416
  reason: external_exports.string().optional()
6646
7417
  });
7418
+ var SavePlanBody = external_exports.object({
7419
+ session_id: external_exports.string(),
7420
+ content: external_exports.string().min(1),
7421
+ source_path: external_exports.string().min(1).optional(),
7422
+ plan_key: external_exports.string().min(1).optional(),
7423
+ title: external_exports.string().min(1).optional(),
7424
+ status: external_exports.enum(PLAN_STATUSES).optional(),
7425
+ tags: external_exports.array(external_exports.string()).optional()
7426
+ }).refine(
7427
+ (value) => Boolean(value.source_path) !== Boolean(value.plan_key),
7428
+ { message: "Provide exactly one of source_path or plan_key" }
7429
+ );
6647
7430
  function createMcpProxyHandlers(deps) {
6648
- const { machineId, embeddingManager } = deps;
7431
+ const { machineId, embeddingManager, projectRoot, logger } = deps;
6649
7432
  function toPlanSummary(row) {
6650
7433
  return {
6651
7434
  id: row.id,
@@ -6769,8 +7552,46 @@ function createMcpProxyHandlers(deps) {
6769
7552
  }
6770
7553
  };
6771
7554
  }
7555
+ async function handleSavePlan(req) {
7556
+ const { session_id, content, source_path, plan_key, title, status, tags } = SavePlanBody.parse(req.body);
7557
+ const session = getSession(session_id);
7558
+ if (!session) return { status: 404, body: errorBody("session-not-found", "Session not found") };
7559
+ const openBatch = getLatestOpenBatch(session_id);
7560
+ const normalizedSourcePath = source_path ? normalizePlanSourcePath(source_path, projectRoot) : null;
7561
+ const logicalKey = normalizedSourcePath ? buildPathPlanLogicalKey(normalizedSourcePath) : buildSessionPlanLogicalKey(session_id, plan_key);
7562
+ const row = persistPlan({
7563
+ sessionId: session_id,
7564
+ content,
7565
+ logicalKey,
7566
+ sourcePath: normalizedSourcePath,
7567
+ promptBatchId: openBatch?.id,
7568
+ title,
7569
+ status,
7570
+ tags,
7571
+ planKey: plan_key ?? null,
7572
+ logger
7573
+ });
7574
+ return {
7575
+ body: {
7576
+ id: row.id,
7577
+ logical_key: row.logical_key,
7578
+ title: row.title,
7579
+ status: row.status,
7580
+ source_path: row.source_path,
7581
+ session_id: row.session_id,
7582
+ prompt_batch_id: row.prompt_batch_id,
7583
+ tags: toPlanTags(row.tags),
7584
+ created_at: row.created_at,
7585
+ updated_at: row.updated_at
7586
+ }
7587
+ };
7588
+ }
6772
7589
  async function handlePlans(req) {
6773
7590
  const id = typeof req.query.id === "string" ? req.query.id : void 0;
7591
+ const session = typeof req.query.session === "string" ? req.query.session : void 0;
7592
+ if (id && session) {
7593
+ return { status: 400, body: errorBody("mutually-exclusive-query", "Pass either id or session, not both") };
7594
+ }
6774
7595
  if (id) {
6775
7596
  const row = getPlan(id);
6776
7597
  if (!row) return { body: { plans: [] } };
@@ -6783,6 +7604,11 @@ function createMcpProxyHandlers(deps) {
6783
7604
  }
6784
7605
  };
6785
7606
  }
7607
+ if (session) {
7608
+ const rows2 = listPlansBySession(session);
7609
+ const plans2 = rows2.map(toPlanSummary);
7610
+ return { body: { plans: plans2 } };
7611
+ }
6786
7612
  const statusFilter = req.query.status === "all" ? void 0 : req.query.status;
6787
7613
  const limit = req.query.limit ? Number(req.query.limit) : void 0;
6788
7614
  const rows = listPlans({ status: statusFilter, limit });
@@ -6836,6 +7662,7 @@ function createMcpProxyHandlers(deps) {
6836
7662
  handleSupersede,
6837
7663
  handleConsolidate,
6838
7664
  handlePlans,
7665
+ handleSavePlan,
6839
7666
  handleSessions,
6840
7667
  handleTeam
6841
7668
  };
@@ -6843,16 +7670,389 @@ function createMcpProxyHandlers(deps) {
6843
7670
 
6844
7671
  // src/daemon/api/agent-runs.ts
6845
7672
  import { resolve as resolve2 } from "path";
7673
+
7674
+ // src/services/phase-audit.ts
7675
+ function isStoredUsageData(value) {
7676
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7677
+ }
7678
+ function parseUsageData(raw) {
7679
+ return tryParseJson(raw, isStoredUsageData) ?? {};
7680
+ }
7681
+ function normalizeStatus(raw) {
7682
+ if (raw === "completed" || raw === "failed" || raw === "skipped") return raw;
7683
+ return "pending";
7684
+ }
7685
+ function aggregateTurnCounts(turns) {
7686
+ const toolCalls = {};
7687
+ for (const turn of turns) {
7688
+ toolCalls[turn.tool_name] = (toolCalls[turn.tool_name] ?? 0) + 1;
7689
+ }
7690
+ const toolErrors = {};
7691
+ return [toolCalls, toolErrors];
7692
+ }
7693
+ function aggregateWriteIntents(intents) {
7694
+ const byPhase = {};
7695
+ const unattributedByTool = {};
7696
+ const totalByTool = {};
7697
+ for (const intent of intents) {
7698
+ const tool = intent.tool_name;
7699
+ totalByTool[tool] = (totalByTool[tool] ?? 0) + 1;
7700
+ if (intent.phase_id) {
7701
+ if (!byPhase[intent.phase_id]) byPhase[intent.phase_id] = {};
7702
+ byPhase[intent.phase_id][tool] = (byPhase[intent.phase_id][tool] ?? 0) + 1;
7703
+ } else {
7704
+ unattributedByTool[tool] = (unattributedByTool[tool] ?? 0) + 1;
7705
+ }
7706
+ }
7707
+ return {
7708
+ total: intents.length,
7709
+ byPhase,
7710
+ unattributedByTool,
7711
+ totalByTool
7712
+ };
7713
+ }
7714
+ function serializeReports(reports) {
7715
+ return reports.map((r) => ({
7716
+ action: r.action,
7717
+ summary: r.summary ?? null,
7718
+ details: r.details ?? null,
7719
+ createdAt: r.created_at
7720
+ }));
7721
+ }
7722
+ function buildPhaseAudit(runId) {
7723
+ const run = getRun(runId);
7724
+ if (!run) return null;
7725
+ const dryRun = run.dry_run;
7726
+ const usageData = parseUsageData(run.usage_data);
7727
+ const checkpointState = parseCheckpointState(run.checkpoints);
7728
+ const usageByName = new Map(
7729
+ (usageData.phases ?? []).map((p) => [p.name, p])
7730
+ );
7731
+ const checkpointByName = new Map(
7732
+ Object.entries(checkpointState.phases).map(([key, cp]) => [
7733
+ cp.name ?? key,
7734
+ cp
7735
+ ])
7736
+ );
7737
+ const phaseNames = [];
7738
+ const seen = /* @__PURE__ */ new Set();
7739
+ for (const p of usageData.phases ?? []) {
7740
+ if (!seen.has(p.name)) {
7741
+ phaseNames.push(p.name);
7742
+ seen.add(p.name);
7743
+ }
7744
+ }
7745
+ for (const name of checkpointByName.keys()) {
7746
+ if (!seen.has(name)) {
7747
+ phaseNames.push(name);
7748
+ seen.add(name);
7749
+ }
7750
+ }
7751
+ const reports = listReports(runId);
7752
+ const turns = listTurnsByRun(runId);
7753
+ const [toolCalls, toolErrors] = aggregateTurnCounts(turns);
7754
+ const serializedReports = serializeReports(reports);
7755
+ let intentSummary = null;
7756
+ if (dryRun) {
7757
+ const intents = listWriteIntentTools(runId);
7758
+ intentSummary = aggregateWriteIntents(intents);
7759
+ }
7760
+ if (phaseNames.length === 0 && turns.length > 0) {
7761
+ const runStatus = normalizeStatus(run.status);
7762
+ const durationMs = runDurationMs(run);
7763
+ let writeIntents = null;
7764
+ if (intentSummary) {
7765
+ writeIntents = {
7766
+ total: intentSummary.total,
7767
+ byTool: intentSummary.totalByTool
7768
+ };
7769
+ }
7770
+ const summary = run.status === "failed" ? run.error ?? null : null;
7771
+ return {
7772
+ runId,
7773
+ taskName: run.task ?? null,
7774
+ dryRun,
7775
+ phases: [
7776
+ {
7777
+ phaseName: "run",
7778
+ status: runStatus,
7779
+ summary,
7780
+ turnsUsed: turns.length,
7781
+ maxTurns: null,
7782
+ tokensUsed: run.tokens_used ?? 0,
7783
+ costUsd: run.cost_usd ?? null,
7784
+ costSource: run.cost_source ?? null,
7785
+ durationMs,
7786
+ startedAt: run.started_at,
7787
+ completedAt: run.completed_at,
7788
+ skipReason: null,
7789
+ toolCalls,
7790
+ toolErrors,
7791
+ writeIntents,
7792
+ reports: serializedReports
7793
+ }
7794
+ ]
7795
+ };
7796
+ }
7797
+ const phases = phaseNames.map((phaseName) => {
7798
+ const usage = usageByName.get(phaseName);
7799
+ const cp = checkpointByName.get(phaseName);
7800
+ const status = normalizeStatus(cp?.status ?? (usage ? "pending" : "pending"));
7801
+ const tokensUsed = cp?.tokensUsed ?? usage?.tokensUsed ?? usage?.usage?.totalTokens ?? 0;
7802
+ const costUsd = cp?.costUsd !== void 0 ? cp.costUsd : usage?.costUsd !== void 0 ? usage.costUsd : null;
7803
+ const costSource = cp?.costSource ?? usage?.costSource ?? null;
7804
+ const durationMs = usage?.usage?.durationMs ?? null;
7805
+ const completedAt = status === "completed" || status === "failed" ? cp?.updatedAt ?? null : null;
7806
+ const startedAt = null;
7807
+ const skipReason = null;
7808
+ let writeIntents = null;
7809
+ if (intentSummary) {
7810
+ const phaseByTool = intentSummary.byPhase[phaseName] ?? {};
7811
+ writeIntents = {
7812
+ // Phase total = sum of tool calls attributed to this phase
7813
+ total: Object.values(phaseByTool).reduce((sum, n) => sum + n, 0),
7814
+ byTool: phaseByTool
7815
+ };
7816
+ }
7817
+ return {
7818
+ phaseName,
7819
+ status,
7820
+ summary: cp?.summary ?? null,
7821
+ turnsUsed: cp?.turnsUsed ?? 0,
7822
+ maxTurns: null,
7823
+ // Not persisted — see module JSDoc
7824
+ tokensUsed,
7825
+ costUsd,
7826
+ costSource,
7827
+ durationMs,
7828
+ startedAt,
7829
+ completedAt,
7830
+ skipReason,
7831
+ toolCalls,
7832
+ // run-level aggregate (no per-phase turn attribution)
7833
+ toolErrors,
7834
+ // always empty — no error flag on turn rows
7835
+ writeIntents,
7836
+ reports: serializedReports
7837
+ // all reports; no phase column exists
7838
+ };
7839
+ });
7840
+ return {
7841
+ runId,
7842
+ taskName: run.task ?? null,
7843
+ dryRun,
7844
+ phases
7845
+ };
7846
+ }
7847
+
7848
+ // src/daemon/api/schemas/execution-overrides.ts
7849
+ var ReasoningLevelEnum = external_exports.enum(["low", "default", "high"]);
7850
+ var RuntimeIdEnum = external_exports.enum(["claude-sdk", "openai-agents"]);
7851
+ var ProviderTypeEnum = external_exports.enum([
7852
+ "anthropic",
7853
+ "ollama",
7854
+ "lmstudio",
7855
+ "openai",
7856
+ "openrouter",
7857
+ "openai-compatible"
7858
+ ]);
7859
+ var ProviderOverrideWireSchema = external_exports.object({
7860
+ runtime: RuntimeIdEnum.optional(),
7861
+ type: ProviderTypeEnum,
7862
+ localBackend: external_exports.enum(["ollama", "lmstudio"]).optional(),
7863
+ baseUrl: external_exports.string().optional(),
7864
+ model: external_exports.string().optional(),
7865
+ reasoningMap: external_exports.object({
7866
+ low: external_exports.string().optional(),
7867
+ default: external_exports.string().optional(),
7868
+ high: external_exports.string().optional()
7869
+ }).optional(),
7870
+ contextLength: external_exports.number().int().positive().optional()
7871
+ });
7872
+ var PhaseExecutionOverrideBody = external_exports.object({
7873
+ reasoningLevel: ReasoningLevelEnum.optional(),
7874
+ model: external_exports.string().optional(),
7875
+ provider: ProviderOverrideWireSchema.optional(),
7876
+ maxTurns: external_exports.number().int().positive().optional()
7877
+ });
7878
+ var ExecutionOverrideBody = external_exports.object({
7879
+ runtime: RuntimeIdEnum.optional(),
7880
+ reasoningLevel: ReasoningLevelEnum.optional(),
7881
+ model: external_exports.string().optional(),
7882
+ provider: ProviderOverrideWireSchema.optional(),
7883
+ phases: external_exports.record(external_exports.string(), PhaseExecutionOverrideBody).optional()
7884
+ }).optional();
7885
+
7886
+ // src/daemon/api/schemas/execution-overrides-traversal.ts
7887
+ function transformProviderOverrides(overrides, transform) {
7888
+ if (overrides === null || overrides === void 0) {
7889
+ return overrides ?? null;
7890
+ }
7891
+ if (typeof overrides !== "object") return overrides;
7892
+ const cloned = { ...overrides };
7893
+ if (isPlainObject(cloned.provider)) {
7894
+ const next = transform({ ...cloned.provider });
7895
+ if (next === null) {
7896
+ delete cloned.provider;
7897
+ } else {
7898
+ cloned.provider = next;
7899
+ }
7900
+ }
7901
+ if (isPlainObject(cloned.phases)) {
7902
+ const nextPhases = {};
7903
+ for (const [name, phase] of Object.entries(cloned.phases)) {
7904
+ if (!isPlainObject(phase)) {
7905
+ nextPhases[name] = phase;
7906
+ continue;
7907
+ }
7908
+ const clonedPhase = { ...phase };
7909
+ if (isPlainObject(clonedPhase.provider)) {
7910
+ const nextProvider = transform({ ...clonedPhase.provider });
7911
+ if (nextProvider === null) {
7912
+ delete clonedPhase.provider;
7913
+ } else {
7914
+ clonedPhase.provider = nextProvider;
7915
+ }
7916
+ }
7917
+ nextPhases[name] = clonedPhase;
7918
+ }
7919
+ cloned.phases = nextPhases;
7920
+ }
7921
+ return cloned;
7922
+ }
7923
+ function isPlainObject(value) {
7924
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7925
+ }
7926
+
7927
+ // src/daemon/api/run-serializer.ts
7928
+ function buildPhaseCheckpointSummary(checkpointsRaw, logger) {
7929
+ if (!checkpointsRaw) return [];
7930
+ try {
7931
+ const parsed = JSON.parse(checkpointsRaw);
7932
+ return Object.entries(parsed.phases ?? {}).map(([name, phase]) => ({
7933
+ name: phase.name ?? name,
7934
+ status: phase.status ?? "pending",
7935
+ updatedAt: phase.updatedAt ?? 0,
7936
+ ...phase.tokensUsed !== void 0 ? { tokensUsed: phase.tokensUsed } : {},
7937
+ ...phase.costUsd !== void 0 ? { costUsd: phase.costUsd } : {},
7938
+ ...phase.costSource !== void 0 ? { costSource: phase.costSource } : {}
7939
+ }));
7940
+ } catch (err) {
7941
+ const detail = errorMessage(err);
7942
+ logger?.warn("run-serializer.checkpoints-parse-failed", "checkpoints JSON parse failed", { error: detail });
7943
+ return [];
7944
+ }
7945
+ }
7946
+ function scrubExecutionOverrides(overrides) {
7947
+ if (!overrides || typeof overrides !== "object") return overrides;
7948
+ const topLevelStripped = { ...overrides };
7949
+ if ("apiKey" in topLevelStripped) delete topLevelStripped.apiKey;
7950
+ return transformProviderOverrides(topLevelStripped, stripApiKey);
7951
+ }
7952
+ function stripApiKey(provider) {
7953
+ const cloned = { ...provider };
7954
+ if ("apiKey" in cloned) delete cloned.apiKey;
7955
+ return cloned;
7956
+ }
7957
+ function serializeRun(run, opts = {}) {
7958
+ const {
7959
+ includeResumeFields = true,
7960
+ includePhaseCheckpoints = true,
7961
+ writeIntents,
7962
+ duration_ms,
7963
+ logger
7964
+ } = opts;
7965
+ const base = {
7966
+ id: run.id,
7967
+ agent_id: run.agent_id,
7968
+ task: run.task,
7969
+ instruction: run.instruction,
7970
+ status: run.status,
7971
+ runtime: run.runtime,
7972
+ provider: run.provider,
7973
+ model: run.model,
7974
+ session_ref: run.session_ref,
7975
+ started_at: run.started_at,
7976
+ completed_at: run.completed_at,
7977
+ tokens_used: run.tokens_used,
7978
+ cost_usd: run.cost_usd,
7979
+ actual_cost_usd: run.actual_cost_usd,
7980
+ estimated_cost_usd: run.estimated_cost_usd,
7981
+ cost_source: run.cost_source,
7982
+ cost_data: run.cost_data,
7983
+ actions_taken: run.actions_taken,
7984
+ usage_data: run.usage_data,
7985
+ error: run.error,
7986
+ dry_run: run.dry_run,
7987
+ evaluation_id: run.evaluation_id,
7988
+ reasoning_level: run.reasoning_level,
7989
+ // Strip `apiKey` from historical rows defensively — before this PR the
7990
+ // API accepted apiKey in executionOverrides and stored it unmasked.
7991
+ execution_overrides: scrubExecutionOverrides(run.execution_overrides)
7992
+ };
7993
+ return {
7994
+ ...base,
7995
+ ...includeResumeFields ? {
7996
+ resumable: run.resumable === 1,
7997
+ resume_status: run.resume_status,
7998
+ resume_mode: run.resume_mode,
7999
+ resumed_at: run.resumed_at,
8000
+ checkpoints: run.checkpoints
8001
+ } : {},
8002
+ ...includePhaseCheckpoints ? { phase_checkpoints: buildPhaseCheckpointSummary(run.checkpoints, logger) } : {},
8003
+ ...writeIntents !== void 0 && writeIntents !== null ? { write_intents: writeIntents } : {},
8004
+ ...duration_ms !== void 0 ? { duration_ms } : {}
8005
+ };
8006
+ }
8007
+
8008
+ // src/daemon/api/agent-runs.ts
6846
8009
  var AGENT_RUNS_DEFAULT_LIMIT = 50;
8010
+ var REMOTE_PROVIDER_TYPES = /* @__PURE__ */ new Set(["openai", "openrouter"]);
8011
+ function stripBaseUrlForRemoteProviders(provider) {
8012
+ const type = provider.type;
8013
+ if (typeof type === "string" && REMOTE_PROVIDER_TYPES.has(type)) {
8014
+ const { baseUrl: _dropped, ...rest } = provider;
8015
+ return rest;
8016
+ }
8017
+ return provider;
8018
+ }
8019
+ function sanitizeExecutionOverrides(overrides) {
8020
+ if (!overrides) return overrides;
8021
+ const result = transformProviderOverrides(
8022
+ overrides,
8023
+ stripBaseUrlForRemoteProviders
8024
+ );
8025
+ return result;
8026
+ }
6847
8027
  var AgentRunBody = external_exports.object({
6848
8028
  task: external_exports.string().optional(),
6849
8029
  instruction: external_exports.string().optional(),
6850
- agentId: external_exports.string().optional()
8030
+ agentId: external_exports.string().optional(),
8031
+ /**
8032
+ * Run in dry-run mode — writes intercepted by the tool surface and
8033
+ * recorded to `agent_run_write_intents` instead of mutating the vault.
8034
+ */
8035
+ dryRun: external_exports.boolean().optional(),
8036
+ /** Evaluation matrix this run belongs to, if any. */
8037
+ evaluationId: external_exports.string().nullable().optional(),
8038
+ /** Per-run runtime/reasoning/model overrides; also per-phase overrides. */
8039
+ executionOverrides: ExecutionOverrideBody
8040
+ });
8041
+ var ResumeRunBody = external_exports.object({
8042
+ mode: external_exports.enum(["manual", "scheduled"]).optional()
6851
8043
  });
6852
8044
  function createAgentRunHandlers(deps) {
6853
- const { vaultDir, embeddingManager, logger } = deps;
8045
+ const { vaultDir, embeddingManager, logger, getTeamClient } = deps;
6854
8046
  async function handleRun(req) {
6855
- const { task, instruction: rawInstruction, agentId } = AgentRunBody.parse(req.body);
8047
+ const {
8048
+ task,
8049
+ instruction: rawInstruction,
8050
+ agentId,
8051
+ dryRun,
8052
+ evaluationId,
8053
+ executionOverrides: rawExecutionOverrides
8054
+ } = AgentRunBody.parse(req.body);
8055
+ const executionOverrides = sanitizeExecutionOverrides(rawExecutionOverrides);
6856
8056
  const mycoConfig = loadMergedConfig(vaultDir);
6857
8057
  if (!hasConfiguredProvider(mycoConfig, task)) {
6858
8058
  return {
@@ -6870,10 +8070,10 @@ function createAgentRunHandlers(deps) {
6870
8070
  try {
6871
8071
  const taskParams = mycoConfig.agent.tasks?.[task]?.params;
6872
8072
  const projectRoot = resolve2(vaultDir, "..");
6873
- built = buildTaskInstruction(task, taskParams, agentId, projectRoot, embeddingManager);
8073
+ built = await buildTaskInstruction(task, taskParams, agentId, projectRoot, embeddingManager, mycoConfig, getTeamClient);
6874
8074
  } catch {
6875
8075
  const projectRoot = resolve2(vaultDir, "..");
6876
- built = buildTaskInstruction(task, void 0, agentId, projectRoot, embeddingManager);
8076
+ built = await buildTaskInstruction(task, void 0, agentId, projectRoot, embeddingManager, mycoConfig, getTeamClient);
6877
8077
  }
6878
8078
  instruction = built?.instruction;
6879
8079
  runContext = built?.context;
@@ -6888,13 +8088,17 @@ function createAgentRunHandlers(deps) {
6888
8088
  };
6889
8089
  }
6890
8090
  }
6891
- const { runAgent } = await import("./executor-HWW2QNZQ.js");
6892
- const resultPromise = runAgent(vaultDir, {
8091
+ const { runAgent: runAgent2 } = await import("./executor-DO6QFC6G.js");
8092
+ const resultPromise = runAgent2(vaultDir, {
6893
8093
  task,
6894
8094
  instruction,
6895
8095
  agentId,
6896
8096
  embeddingManager,
6897
- runContext
8097
+ runContext,
8098
+ dryRun,
8099
+ evaluationId,
8100
+ executionOverrides,
8101
+ logger
6898
8102
  });
6899
8103
  const effectiveAgentId = agentId ?? "myco-agent";
6900
8104
  const runId = getLatestRunId(effectiveAgentId, task);
@@ -6946,14 +8150,59 @@ function createAgentRunHandlers(deps) {
6946
8150
  const filterOpts = { agent_id: agentId, status, task, search };
6947
8151
  const runs = listRuns({ ...filterOpts, limit, offset });
6948
8152
  const total = countRuns(filterOpts);
6949
- return { body: { runs, total, offset, limit } };
8153
+ return { body: { runs: runs.map((run) => serializeRun(run, { logger })), total, offset, limit } };
6950
8154
  }
6951
8155
  async function handleGetRun(req) {
6952
8156
  const run = getRun(req.params.id);
6953
8157
  if (!run) {
6954
8158
  return { status: 404, body: { error: "Run not found" } };
6955
8159
  }
6956
- return { body: { run } };
8160
+ const byTool = countWriteIntentsByTool(run.id);
8161
+ const total = Object.values(byTool).reduce((acc, n) => acc + n, 0);
8162
+ return {
8163
+ body: {
8164
+ run: serializeRun(run, {
8165
+ writeIntents: { total, by_tool: byTool },
8166
+ duration_ms: runDurationMs(run),
8167
+ logger
8168
+ })
8169
+ }
8170
+ };
8171
+ }
8172
+ async function handleResumeRun(req) {
8173
+ const run = getRun(req.params.id);
8174
+ if (!run) {
8175
+ return { status: 404, body: { error: "Run not found" } };
8176
+ }
8177
+ if (run.resumable !== 1 || run.status !== "failed") {
8178
+ return { status: 400, body: { error: "Run is not resumable" } };
8179
+ }
8180
+ const { mode } = ResumeRunBody.parse(req.body ?? {});
8181
+ const { runAgent: runAgent2 } = await import("./executor-DO6QFC6G.js");
8182
+ const resultPromise = runAgent2(vaultDir, {
8183
+ agentId: run.agent_id,
8184
+ task: run.task ?? void 0,
8185
+ instruction: run.instruction ?? void 0,
8186
+ resumeRunId: run.id,
8187
+ resumeMode: mode ?? "manual",
8188
+ embeddingManager,
8189
+ logger
8190
+ });
8191
+ resultPromise.then((result) => {
8192
+ logger.info(LOG_KINDS.AGENT_RUN, "Agent run resumed", {
8193
+ runId: result.runId,
8194
+ status: result.status,
8195
+ runtime: result.runtime,
8196
+ provider: result.provider,
8197
+ model: result.model
8198
+ });
8199
+ }).catch((err) => {
8200
+ logger.error(LOG_KINDS.AGENT_ERROR, "Agent run resume threw unhandled error", {
8201
+ runId: run.id,
8202
+ error: err instanceof Error ? err.message : String(err)
8203
+ });
8204
+ });
8205
+ return { body: { ok: true, message: "Agent resume started", runId: run.id } };
6957
8206
  }
6958
8207
  async function handleGetRunReports(req) {
6959
8208
  const reports = listReports(req.params.id);
@@ -6963,18 +8212,377 @@ function createAgentRunHandlers(deps) {
6963
8212
  const turns = listTurnsByRun(req.params.id);
6964
8213
  return { body: turns };
6965
8214
  }
8215
+ async function handleGetRunWriteIntents(req) {
8216
+ const rawLimit = req.query.limit ? Number(req.query.limit) : void 0;
8217
+ const rawOffset = req.query.offset ? Number(req.query.offset) : void 0;
8218
+ const limit = Number.isFinite(rawLimit) && rawLimit !== void 0 && rawLimit > 0 ? Math.min(rawLimit, 5e3) : 500;
8219
+ const offset = Number.isFinite(rawOffset) && rawOffset !== void 0 && rawOffset >= 0 ? rawOffset : 0;
8220
+ const intents = listWriteIntents(req.params.id, { limit, offset });
8221
+ const total = countWriteIntents(req.params.id);
8222
+ return { body: { intents, count: intents.length, total } };
8223
+ }
8224
+ async function handleGetRunAudit(req) {
8225
+ const audit = buildPhaseAudit(req.params.id);
8226
+ if (!audit) {
8227
+ return { status: 404, body: { error: "Run not found" } };
8228
+ }
8229
+ return { body: { audit } };
8230
+ }
6966
8231
  return {
6967
8232
  handleRun,
6968
8233
  handleListRuns,
6969
8234
  handleGetRun,
8235
+ handleResumeRun,
6970
8236
  handleGetRunReports,
6971
- handleGetRunTurns
8237
+ handleGetRunTurns,
8238
+ handleGetRunWriteIntents,
8239
+ handleGetRunAudit
6972
8240
  };
6973
8241
  }
6974
8242
 
8243
+ // src/daemon/api/agent-evaluations.ts
8244
+ import crypto from "crypto";
8245
+
8246
+ // src/db/queries/evaluations.ts
8247
+ var DEFAULT_LIMIT = 50;
8248
+ var EVAL_STATUS_PENDING = "pending";
8249
+ var EVAL_STATUS_RUNNING = "running";
8250
+ var EVAL_STATUS_COMPLETED = "completed";
8251
+ var EVAL_STATUS_FAILED = "failed";
8252
+ var EVAL_COLUMNS = [
8253
+ "id",
8254
+ "task_id",
8255
+ "matrix_json",
8256
+ "notes",
8257
+ "status",
8258
+ "created_at",
8259
+ "completed_at"
8260
+ ];
8261
+ var SELECT_COLUMNS5 = EVAL_COLUMNS.join(", ");
8262
+ function toEvaluationRow(row) {
8263
+ const matrixJson = row.matrix_json;
8264
+ let matrix = null;
8265
+ if (matrixJson) {
8266
+ try {
8267
+ matrix = JSON.parse(matrixJson);
8268
+ } catch {
8269
+ matrix = null;
8270
+ }
8271
+ }
8272
+ return {
8273
+ id: row.id,
8274
+ task_id: row.task_id,
8275
+ matrix,
8276
+ notes: row.notes ?? null,
8277
+ status: row.status,
8278
+ created_at: row.created_at,
8279
+ completed_at: row.completed_at ?? null
8280
+ };
8281
+ }
8282
+ function insertEvaluation(data) {
8283
+ const db = getDatabase();
8284
+ const createdAt = data.createdAt ?? epochSeconds();
8285
+ db.prepare(
8286
+ `INSERT INTO agent_run_evaluations
8287
+ (id, task_id, matrix_json, notes, status, created_at, completed_at)
8288
+ VALUES (?, ?, ?, ?, ?, ?, NULL)`
8289
+ ).run(
8290
+ data.id,
8291
+ data.taskId,
8292
+ JSON.stringify(data.matrix ?? null),
8293
+ data.notes ?? null,
8294
+ EVAL_STATUS_PENDING,
8295
+ createdAt
8296
+ );
8297
+ return getEvaluation(data.id);
8298
+ }
8299
+ function getEvaluation(id) {
8300
+ const db = getDatabase();
8301
+ const row = db.prepare(
8302
+ `SELECT ${SELECT_COLUMNS5} FROM agent_run_evaluations WHERE id = ?`
8303
+ ).get(id);
8304
+ return row ? toEvaluationRow(row) : null;
8305
+ }
8306
+ function listEvaluations(options = {}) {
8307
+ const db = getDatabase();
8308
+ const limit = options.limit ?? DEFAULT_LIMIT;
8309
+ const offset = options.offset ?? 0;
8310
+ const rows = db.prepare(
8311
+ `SELECT ${SELECT_COLUMNS5}
8312
+ FROM agent_run_evaluations
8313
+ ORDER BY created_at DESC, id DESC
8314
+ LIMIT ? OFFSET ?`
8315
+ ).all(limit, offset);
8316
+ return rows.map(toEvaluationRow);
8317
+ }
8318
+ function updateEvaluationStatus(id, status, completedAt) {
8319
+ const db = getDatabase();
8320
+ if (completedAt === void 0) {
8321
+ db.prepare(
8322
+ `UPDATE agent_run_evaluations SET status = ? WHERE id = ?`
8323
+ ).run(status, id);
8324
+ } else {
8325
+ db.prepare(
8326
+ `UPDATE agent_run_evaluations SET status = ?, completed_at = ? WHERE id = ?`
8327
+ ).run(status, completedAt, id);
8328
+ }
8329
+ return getEvaluation(id);
8330
+ }
8331
+
8332
+ // src/daemon/api/agent-evaluations.ts
8333
+ var DEFAULT_LIMIT2 = 50;
8334
+ var CreateEvaluationBody = external_exports.object({
8335
+ taskId: external_exports.string(),
8336
+ matrix: external_exports.object({
8337
+ runtimes: external_exports.array(RuntimeIdEnum).optional(),
8338
+ reasoningLevels: external_exports.array(ReasoningLevelEnum).optional(),
8339
+ models: external_exports.array(external_exports.string()).optional(),
8340
+ dryRun: external_exports.boolean().optional(),
8341
+ notes: external_exports.string().optional(),
8342
+ /**
8343
+ * Phase-level overrides applied to EVERY cell in the matrix. The matrix
8344
+ * dimensions (runtimes / reasoningLevels / models) vary the top-level
8345
+ * execution per cell; phase overrides here let you pin specific phases
8346
+ * to fixed reasoning/model/provider/maxTurns regardless of what the cell
8347
+ * is varying at the top level.
8348
+ *
8349
+ * Merged into each cell's `executionOverrides.phases` before runAgent
8350
+ * is invoked. Unknown phase names are ignored by the executor (a
8351
+ * one-shot warning is logged at run startup).
8352
+ */
8353
+ phases: external_exports.record(external_exports.string(), PhaseExecutionOverrideBody).optional()
8354
+ }),
8355
+ notes: external_exports.string().optional()
8356
+ });
8357
+ function aggregateRuns(runs) {
8358
+ let completed = 0;
8359
+ let failed = 0;
8360
+ let skipped = 0;
8361
+ let totalTokens = 0;
8362
+ let totalCostUsd = 0;
8363
+ for (const run of runs) {
8364
+ if (run.status === "completed") completed++;
8365
+ else if (run.status === "failed") failed++;
8366
+ else if (run.status === "skipped") skipped++;
8367
+ totalTokens += run.tokens_used ?? 0;
8368
+ totalCostUsd += run.cost_usd ?? 0;
8369
+ }
8370
+ return {
8371
+ total: runs.length,
8372
+ completed,
8373
+ failed,
8374
+ skipped,
8375
+ totalTokens,
8376
+ totalCostUsd
8377
+ };
8378
+ }
8379
+ function summarizeFromByTool(byTool) {
8380
+ const tools = byTool ?? {};
8381
+ let total = 0;
8382
+ for (const count of Object.values(tools)) total += count;
8383
+ return { total, by_tool: tools };
8384
+ }
8385
+ function createAgentEvaluationHandlers(deps) {
8386
+ const { vaultDir, embeddingManager, logger } = deps;
8387
+ async function handleCreate(req) {
8388
+ const parsed = CreateEvaluationBody.safeParse(req.body);
8389
+ if (!parsed.success) {
8390
+ return {
8391
+ status: 400,
8392
+ body: { error: "Invalid request body", details: parsed.error.flatten() }
8393
+ };
8394
+ }
8395
+ const body = parsed.data;
8396
+ const cells = enumerateMatrixCells(body.matrix);
8397
+ if (cells.length === 0) {
8398
+ return {
8399
+ status: 400,
8400
+ body: { error: "Matrix produced zero cells" }
8401
+ };
8402
+ }
8403
+ const evalId = crypto.randomUUID();
8404
+ insertEvaluation({
8405
+ id: evalId,
8406
+ taskId: body.taskId,
8407
+ matrix: body.matrix,
8408
+ notes: body.notes ?? null
8409
+ });
8410
+ const dryRun = body.matrix.dryRun ?? false;
8411
+ void (async () => {
8412
+ const { runAgent: runAgent2 } = await import("./executor-DO6QFC6G.js");
8413
+ let anyCompleted = false;
8414
+ let transitionedToRunning = false;
8415
+ const sharedPhases = body.matrix.phases;
8416
+ for (const cell of cells) {
8417
+ const cellOptions = {
8418
+ task: body.taskId,
8419
+ evaluationId: evalId,
8420
+ dryRun,
8421
+ embeddingManager,
8422
+ logger,
8423
+ executionOverrides: {
8424
+ ...cell.runtime ? { runtime: cell.runtime } : {},
8425
+ ...cell.reasoningLevel ? { reasoningLevel: cell.reasoningLevel } : {},
8426
+ ...cell.model ? { model: cell.model } : {},
8427
+ ...sharedPhases && Object.keys(sharedPhases).length > 0 ? { phases: sharedPhases } : {}
8428
+ }
8429
+ };
8430
+ if (!transitionedToRunning) {
8431
+ updateEvaluationStatus(evalId, EVAL_STATUS_RUNNING);
8432
+ transitionedToRunning = true;
8433
+ }
8434
+ try {
8435
+ const result = await runAgent2(vaultDir, cellOptions);
8436
+ if (result.status === "completed") {
8437
+ anyCompleted = true;
8438
+ }
8439
+ logger.info(LOG_KINDS.AGENT_RUN, "Evaluation cell finished", {
8440
+ evaluationId: evalId,
8441
+ runId: result.runId,
8442
+ status: result.status,
8443
+ runtime: cell.runtime,
8444
+ reasoningLevel: cell.reasoningLevel,
8445
+ model: cell.model
8446
+ });
8447
+ } catch (err) {
8448
+ logger.error(LOG_KINDS.AGENT_ERROR, "Evaluation cell threw", {
8449
+ evaluationId: evalId,
8450
+ runtime: cell.runtime,
8451
+ reasoningLevel: cell.reasoningLevel,
8452
+ model: cell.model,
8453
+ error: err instanceof Error ? err.message : String(err)
8454
+ });
8455
+ }
8456
+ }
8457
+ const status = anyCompleted ? EVAL_STATUS_COMPLETED : EVAL_STATUS_FAILED;
8458
+ updateEvaluationStatus(evalId, status, epochSeconds());
8459
+ logger.info(LOG_KINDS.AGENT_RUN, "Evaluation finished", {
8460
+ evaluationId: evalId,
8461
+ status,
8462
+ cellCount: cells.length
8463
+ });
8464
+ })().catch((err) => {
8465
+ logger.error(LOG_KINDS.AGENT_ERROR, "Evaluation fan-out failed", {
8466
+ evaluationId: evalId,
8467
+ error: err instanceof Error ? err.message : String(err)
8468
+ });
8469
+ });
8470
+ return { body: { evaluationId: evalId, cellCount: cells.length } };
8471
+ }
8472
+ async function handleGet(req) {
8473
+ const evaluation = getEvaluation(req.params.id);
8474
+ if (!evaluation) {
8475
+ return { status: 404, body: { error: "Evaluation not found" } };
8476
+ }
8477
+ const runs = listRunsForEvaluation(evaluation.id);
8478
+ const writeIntentsByRun = countWriteIntentsByToolForEvaluation(evaluation.id);
8479
+ return {
8480
+ body: {
8481
+ evaluation: {
8482
+ id: evaluation.id,
8483
+ taskId: evaluation.task_id,
8484
+ matrix: evaluation.matrix,
8485
+ notes: evaluation.notes,
8486
+ status: evaluation.status,
8487
+ createdAt: evaluation.created_at,
8488
+ completedAt: evaluation.completed_at
8489
+ },
8490
+ runs: runs.map(
8491
+ (run) => serializeRun(run, {
8492
+ includeResumeFields: false,
8493
+ includePhaseCheckpoints: false,
8494
+ writeIntents: summarizeFromByTool(writeIntentsByRun[run.id]),
8495
+ duration_ms: runDurationMs(run)
8496
+ })
8497
+ ),
8498
+ aggregate: aggregateRuns(runs)
8499
+ }
8500
+ };
8501
+ }
8502
+ async function handleList(req) {
8503
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIMIT2;
8504
+ const offset = req.query.offset ? Number(req.query.offset) : 0;
8505
+ const evaluations = listEvaluations({ limit, offset });
8506
+ return {
8507
+ body: {
8508
+ evaluations: evaluations.map((e) => ({
8509
+ id: e.id,
8510
+ taskId: e.task_id,
8511
+ matrix: e.matrix,
8512
+ notes: e.notes,
8513
+ status: e.status,
8514
+ createdAt: e.created_at,
8515
+ completedAt: e.completed_at
8516
+ })),
8517
+ total: evaluations.length
8518
+ }
8519
+ };
8520
+ }
8521
+ return { handleCreate, handleGet, handleList };
8522
+ }
8523
+
8524
+ // src/daemon/api/digest-revisions.ts
8525
+ var DEFAULT_LIMIT3 = 50;
8526
+ var RestoreBody = external_exports.object({
8527
+ /** If supplied, recorded on the new revision row as the run that triggered the restore. */
8528
+ runId: external_exports.string().optional()
8529
+ });
8530
+ function createDigestRevisionHandlers(deps) {
8531
+ const { logger } = deps;
8532
+ async function handleList(req) {
8533
+ const agentId = req.query.agentId || DEFAULT_AGENT_ID;
8534
+ const tierRaw = req.query.tier;
8535
+ if (!tierRaw) {
8536
+ return { status: 400, body: { error: "tier is required" } };
8537
+ }
8538
+ const tier = Number(tierRaw);
8539
+ if (!Number.isFinite(tier)) {
8540
+ return { status: 400, body: { error: `tier must be numeric, got ${tierRaw}` } };
8541
+ }
8542
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIMIT3;
8543
+ const revisions = listDigestRevisions({ agentId, tier, limit });
8544
+ return { body: { revisions, count: revisions.length } };
8545
+ }
8546
+ async function handleRestore(req) {
8547
+ const revisionId = Number(req.params.id);
8548
+ if (!Number.isFinite(revisionId)) {
8549
+ return { status: 400, body: { error: "Invalid revision id" } };
8550
+ }
8551
+ const parsed = RestoreBody.safeParse(req.body ?? {});
8552
+ if (!parsed.success) {
8553
+ return {
8554
+ status: 400,
8555
+ body: { error: "Invalid request body", details: parsed.error.flatten() }
8556
+ };
8557
+ }
8558
+ const result = rollbackDigestExtract({
8559
+ revisionId,
8560
+ runId: parsed.data.runId ?? null
8561
+ });
8562
+ if (!result) {
8563
+ return { status: 404, body: { error: "Revision not found" } };
8564
+ }
8565
+ logger.info(LOG_KINDS.AGENT_RUN, "Digest revision restored", {
8566
+ revisionId,
8567
+ newRevisionId: result.newRevisionId,
8568
+ agentId: result.row.agent_id,
8569
+ tier: result.row.tier,
8570
+ triggeredBy: parsed.data.runId ?? "operator"
8571
+ });
8572
+ return {
8573
+ body: {
8574
+ ok: true,
8575
+ restored: revisionId,
8576
+ newRevisionId: result.newRevisionId
8577
+ }
8578
+ };
8579
+ }
8580
+ return { handleList, handleRestore };
8581
+ }
8582
+
6975
8583
  // src/daemon/api/attachments.ts
6976
- import fs18 from "fs";
6977
- import path17 from "path";
8584
+ import fs19 from "fs";
8585
+ import path19 from "path";
6978
8586
  var ATTACHMENT_MEDIA_TYPES = {
6979
8587
  png: "image/png",
6980
8588
  jpg: "image/jpeg",
@@ -6994,14 +8602,14 @@ function createAttachmentHandler(deps) {
6994
8602
  const contentType2 = att.media_type ?? "application/octet-stream";
6995
8603
  return { status: 200, headers: { "Content-Type": contentType2 }, body: att.data };
6996
8604
  }
6997
- const filePath = path17.join(vaultDir, "attachments", filename);
8605
+ const filePath = path19.join(vaultDir, "attachments", filename);
6998
8606
  let diskData;
6999
8607
  try {
7000
- diskData = fs18.readFileSync(filePath);
8608
+ diskData = fs19.readFileSync(filePath);
7001
8609
  } catch {
7002
8610
  return { status: 404, body: { error: "not_found" } };
7003
8611
  }
7004
- const ext = path17.extname(filename).slice(1).toLowerCase();
8612
+ const ext = path19.extname(filename).slice(1).toLowerCase();
7005
8613
  const contentType = ATTACHMENT_MEDIA_TYPES[ext] ?? "application/octet-stream";
7006
8614
  return { status: 200, headers: { "Content-Type": contentType }, body: diskData };
7007
8615
  }
@@ -7009,19 +8617,19 @@ function createAttachmentHandler(deps) {
7009
8617
  }
7010
8618
 
7011
8619
  // src/daemon/log-reconcile.ts
7012
- import fs19 from "fs";
7013
- import path18 from "path";
8620
+ import fs20 from "fs";
8621
+ import path20 from "path";
7014
8622
  function reconcileLogBuffer(logDir, sinceTimestamp) {
7015
8623
  let replayed = 0;
7016
8624
  const files = [];
7017
8625
  for (let i = 3; i >= 1; i--) {
7018
- const rotated = path18.join(logDir, `daemon.${i}.log`);
7019
- if (fs19.existsSync(rotated)) files.push(rotated);
8626
+ const rotated = path20.join(logDir, `daemon.${i}.log`);
8627
+ if (fs20.existsSync(rotated)) files.push(rotated);
7020
8628
  }
7021
- const current = path18.join(logDir, "daemon.log");
7022
- if (fs19.existsSync(current)) files.push(current);
8629
+ const current = path20.join(logDir, "daemon.log");
8630
+ if (fs20.existsSync(current)) files.push(current);
7023
8631
  for (const file of files) {
7024
- const content = fs19.readFileSync(file, "utf-8");
8632
+ const content = fs20.readFileSync(file, "utf-8");
7025
8633
  for (const line of content.split("\n")) {
7026
8634
  if (!line.trim()) continue;
7027
8635
  try {
@@ -7049,11 +8657,14 @@ function reconcileLogBuffer(logDir, sinceTimestamp) {
7049
8657
  // src/config/focus.ts
7050
8658
  var CONFIG_FOCUS_SECTION_PARAM = "configSection";
7051
8659
  var CONFIG_FOCUS_FIELD_PARAM = "configField";
8660
+ var CONFIG_FOCUS_TAB_PARAM = "tab";
7052
8661
  var CONFIG_SECTION_IDS = {
7053
8662
  appearance: "config-section-appearance",
8663
+ cortexInstructions: "config-section-cortex-instructions",
8664
+ cortexBuilder: "config-section-cortex-builder",
8665
+ cortexDigest: "config-section-cortex-digest",
7054
8666
  settingsAgent: "config-section-settings-agent",
7055
8667
  settingsEmbedding: "config-section-settings-embedding",
7056
- settingsContextInjection: "config-section-settings-context-injection",
7057
8668
  settingsNotifications: "config-section-settings-notifications",
7058
8669
  settingsPlanCapture: "config-section-settings-plan-capture",
7059
8670
  settingsProject: "config-section-settings-project",
@@ -7080,11 +8691,30 @@ var SECTION_RULES = [
7080
8691
  sectionId: CONFIG_SECTION_IDS.settingsEmbedding,
7081
8692
  sectionLabel: "Embedding"
7082
8693
  },
8694
+ {
8695
+ prefix: "context.digest_tier",
8696
+ page: "/cortex",
8697
+ sectionId: CONFIG_SECTION_IDS.cortexDigest,
8698
+ sectionLabel: "Digest",
8699
+ searchParams: { [CONFIG_FOCUS_TAB_PARAM]: "digest" }
8700
+ },
8701
+ {
8702
+ prefix: "context.cortex",
8703
+ page: "/cortex",
8704
+ sectionId: CONFIG_SECTION_IDS.cortexInstructions,
8705
+ sectionLabel: "Instructions"
8706
+ },
8707
+ {
8708
+ prefix: "context.prompt",
8709
+ page: "/cortex",
8710
+ sectionId: CONFIG_SECTION_IDS.cortexInstructions,
8711
+ sectionLabel: "Instructions"
8712
+ },
7083
8713
  {
7084
8714
  prefix: "context",
7085
- page: "/settings",
7086
- sectionId: CONFIG_SECTION_IDS.settingsContextInjection,
7087
- sectionLabel: "Context Injection"
8715
+ page: "/cortex",
8716
+ sectionId: CONFIG_SECTION_IDS.cortexInstructions,
8717
+ sectionLabel: "Instructions"
7088
8718
  },
7089
8719
  {
7090
8720
  prefix: "notifications",
@@ -7151,7 +8781,9 @@ var EXACT_FIELD_LABELS = {
7151
8781
  "embedding.provider": "Provider",
7152
8782
  "embedding.model": "Model",
7153
8783
  "embedding.base_url": "Base URL",
7154
- "context.digest_tier": "Digest Tier",
8784
+ "context.digest_tier": "Preferred Digest Tier",
8785
+ "context.session_start_digest_enabled": "Session-Start Digest",
8786
+ "context.cortex_enabled": "Session-Start Instructions",
7155
8787
  "context.prompt_search": "Prompt Search",
7156
8788
  "context.prompt_max_spores": "Max Spores per Prompt",
7157
8789
  "notifications.enabled": "Notifications",
@@ -7172,8 +8804,8 @@ var EXACT_FIELD_LABELS = {
7172
8804
  var DYNAMIC_FIELD_LABEL_RULES = [
7173
8805
  {
7174
8806
  prefix: "notifications.domains.",
7175
- format: (path23) => {
7176
- const match = /^notifications\.domains\.([^.]+)\.(enabled|mode)$/.exec(path23);
8807
+ format: (path25) => {
8808
+ const match = /^notifications\.domains\.([^.]+)\.(enabled|mode)$/.exec(path25);
7177
8809
  if (!match) return null;
7178
8810
  const [, domain, leaf] = match;
7179
8811
  const domainLabel = humanizeToken(domain);
@@ -7182,13 +8814,13 @@ var DYNAMIC_FIELD_LABEL_RULES = [
7182
8814
  }
7183
8815
  ];
7184
8816
  var SAVE_MESSAGE_LABEL_LIMIT = 3;
7185
- function resolveConfigFocusTarget(path23) {
7186
- const section = findSectionRule(path23);
8817
+ function resolveConfigFocusTarget(path25) {
8818
+ const section = findSectionRule(path25);
7187
8819
  if (!section) return null;
7188
8820
  return {
7189
8821
  ...section,
7190
- fieldPath: path23,
7191
- fieldLabel: resolveFieldLabel(path23)
8822
+ fieldPath: path25,
8823
+ fieldLabel: resolveFieldLabel(path25)
7192
8824
  };
7193
8825
  }
7194
8826
  function buildConfigFocusLink(target) {
@@ -7223,25 +8855,25 @@ function buildScopedConfigSaveNotification(scope, touchedPaths) {
7223
8855
  }
7224
8856
  };
7225
8857
  }
7226
- function findSectionRule(path23) {
8858
+ function findSectionRule(path25) {
7227
8859
  for (const rule of SECTION_RULES) {
7228
- if (path23 === rule.prefix || path23.startsWith(`${rule.prefix}.`)) {
8860
+ if (path25 === rule.prefix || path25.startsWith(`${rule.prefix}.`)) {
7229
8861
  const { page, sectionId, sectionLabel, searchParams } = rule;
7230
8862
  return { page, sectionId, sectionLabel, searchParams };
7231
8863
  }
7232
8864
  }
7233
8865
  return null;
7234
8866
  }
7235
- function resolveFieldLabel(path23) {
7236
- const exact = EXACT_FIELD_LABELS[path23];
8867
+ function resolveFieldLabel(path25) {
8868
+ const exact = EXACT_FIELD_LABELS[path25];
7237
8869
  if (exact) return exact;
7238
8870
  for (const rule of DYNAMIC_FIELD_LABEL_RULES) {
7239
- if (path23 === rule.prefix || path23.startsWith(rule.prefix)) {
7240
- const label = rule.format(path23);
8871
+ if (path25 === rule.prefix || path25.startsWith(rule.prefix)) {
8872
+ const label = rule.format(path25);
7241
8873
  if (label) return label;
7242
8874
  }
7243
8875
  }
7244
- return humanizeToken(path23.split(".").pop() ?? "setting");
8876
+ return humanizeToken(path25.split(".").pop() ?? "setting");
7245
8877
  }
7246
8878
  function humanizeToken(value) {
7247
8879
  return value.split(/[-_]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
@@ -7361,6 +8993,48 @@ var PowerManager = class {
7361
8993
  }
7362
8994
  };
7363
8995
 
8996
+ // src/daemon/inflight-runs.ts
8997
+ var DEFAULT_DRAIN_TIMEOUT_MS = 3e4;
8998
+ var InflightRunRegistry = class {
8999
+ runs = /* @__PURE__ */ new Set();
9000
+ /**
9001
+ * Track a fire-and-forget agent run. The promise is removed from the
9002
+ * registry in a finally handler so a resolved/rejected run does not hold
9003
+ * memory. Resolves immediately — callers must not await this directly.
9004
+ */
9005
+ register(promise) {
9006
+ const tracked = Promise.resolve(promise).finally(() => {
9007
+ this.runs.delete(tracked);
9008
+ });
9009
+ this.runs.add(tracked);
9010
+ }
9011
+ /** Number of runs currently being tracked. */
9012
+ get size() {
9013
+ return this.runs.size;
9014
+ }
9015
+ /**
9016
+ * Wait for every tracked run to settle, up to `timeoutMs`. Returns when
9017
+ * either every run completes or the deadline elapses — whichever happens
9018
+ * first. The 30-second default mirrors a typical daemon shutdown budget;
9019
+ * callers are free to override for tests or tighter environments.
9020
+ */
9021
+ async drain(timeoutMs = DEFAULT_DRAIN_TIMEOUT_MS) {
9022
+ if (this.runs.size === 0) return { settled: true, remaining: 0 };
9023
+ const snapshot = Array.from(this.runs);
9024
+ let timer;
9025
+ const timeoutPromise = new Promise((resolve3) => {
9026
+ timer = setTimeout(() => resolve3("timeout"), timeoutMs);
9027
+ });
9028
+ const allSettled = Promise.allSettled(snapshot).then(() => "settled");
9029
+ try {
9030
+ const outcome = await Promise.race([allSettled, timeoutPromise]);
9031
+ return { settled: outcome === "settled", remaining: this.runs.size };
9032
+ } finally {
9033
+ if (timer !== void 0) clearTimeout(timer);
9034
+ }
9035
+ }
9036
+ };
9037
+
7364
9038
  // src/daemon/jobs/session-maintenance.ts
7365
9039
  function completeStaleActiveSessions(thresholdSeconds = STALE_SESSION_THRESHOLD_MS / MS_PER_SECOND) {
7366
9040
  const db = getDatabase();
@@ -7505,8 +9179,8 @@ function registerPowerJobs(powerManager, deps) {
7505
9179
  }
7506
9180
 
7507
9181
  // src/daemon/reconciliation.ts
7508
- import fs20 from "fs";
7509
- import path19 from "path";
9182
+ import fs21 from "fs";
9183
+ import path21 from "path";
7510
9184
 
7511
9185
  // src/daemon/event-handlers.ts
7512
9186
  var TOOL_INPUT_STORE_LIMIT = 4e3;
@@ -7675,10 +9349,10 @@ function createReconciler({ bufferDir, logger }) {
7675
9349
  function reconcileSession(sessionId) {
7676
9350
  if (reconciledSessions.has(sessionId)) return;
7677
9351
  reconciledSessions.add(sessionId);
7678
- const bufferPath = path19.join(bufferDir, `${sessionId}.jsonl`);
9352
+ const bufferPath = path21.join(bufferDir, `${sessionId}.jsonl`);
7679
9353
  let content;
7680
9354
  try {
7681
- content = fs20.readFileSync(bufferPath, "utf-8").trim();
9355
+ content = fs21.readFileSync(bufferPath, "utf-8").trim();
7682
9356
  } catch {
7683
9357
  return;
7684
9358
  }
@@ -7747,7 +9421,19 @@ function createReconciler({ bufferDir, logger }) {
7747
9421
  }
7748
9422
 
7749
9423
  // src/daemon/stop-processing.ts
7750
- import fs21 from "fs";
9424
+ import fs22 from "fs";
9425
+ import path22 from "path";
9426
+
9427
+ // src/daemon/capture-gating.ts
9428
+ function gateEventByCaptureRules(event, options) {
9429
+ const transcriptMeta = event.transcriptPath ? readTranscriptMeta(event.transcriptPath) ?? void 0 : void 0;
9430
+ const manifests = options?.manifests ?? loadManifests();
9431
+ const decision = evaluateSessionCaptureRules(manifests, event.agent, {
9432
+ transcriptPath: event.transcriptPath,
9433
+ transcriptMeta
9434
+ });
9435
+ return { decision, hadTranscriptMeta: transcriptMeta !== void 0 };
9436
+ }
7751
9437
 
7752
9438
  // src/daemon/capture-images.ts
7753
9439
  var SESSION_SHORT_LEN = 6;
@@ -7784,80 +9470,8 @@ function captureBatchImages(input) {
7784
9470
  }
7785
9471
  }
7786
9472
 
7787
- // src/daemon/plan-capture.ts
7788
- import { createHash as createHash4 } from "crypto";
7789
- import os8 from "os";
7790
- import path20 from "path";
7791
- function extractTaggedPlans(text, tags) {
7792
- const results = [];
7793
- for (const tag of tags) {
7794
- const regex = new RegExp(`<${tag}>\\n?([\\s\\S]*?)\\n?</${tag}>`, "g");
7795
- let match;
7796
- while ((match = regex.exec(text)) !== null) {
7797
- const content = match[1].trim();
7798
- if (content) results.push({ tag, content });
7799
- }
7800
- }
7801
- return results;
7802
- }
7803
- var TRANSCRIPT_SOURCE_PREFIX = "transcript:";
7804
- var FILE_WRITE_TOOLS = /* @__PURE__ */ new Set([
7805
- "Write",
7806
- "Edit",
7807
- "Create",
7808
- "write",
7809
- "edit",
7810
- "patch",
7811
- "create"
7812
- ]);
7813
- var HEADING_REGEX = /^#\s+(.+)$/m;
7814
- var PLAN_ID_HASH_LENGTH = 16;
7815
- function isInPlanDirectory(filePath, watchDirs, projectRoot) {
7816
- const abs = path20.isAbsolute(filePath) ? filePath : path20.resolve(projectRoot, filePath);
7817
- return watchDirs.some((dir) => {
7818
- const expanded = dir.startsWith("~/") ? path20.join(os8.homedir(), dir.slice(2)) : dir;
7819
- const absDir = path20.isAbsolute(expanded) ? expanded : path20.resolve(projectRoot, expanded);
7820
- const prefix = absDir.endsWith(path20.sep) ? absDir : absDir + path20.sep;
7821
- return abs === absDir || abs.startsWith(prefix);
7822
- });
7823
- }
7824
- function isPlanWriteEvent(toolName, toolInput, config) {
7825
- if (!FILE_WRITE_TOOLS.has(toolName)) return null;
7826
- const filePath = toolInput?.file_path ?? toolInput?.path ?? toolInput?.filePath;
7827
- if (typeof filePath !== "string") return null;
7828
- if (!isInPlanDirectory(filePath, config.watchDirs, config.projectRoot)) return null;
7829
- if (config.extensions?.length) {
7830
- const ext = path20.extname(filePath).toLowerCase();
7831
- if (!config.extensions.includes(ext)) return null;
7832
- }
7833
- return filePath;
7834
- }
7835
- function parsePlanTitle(content, filename) {
7836
- const match = HEADING_REGEX.exec(content);
7837
- if (match) return match[1].trim();
7838
- return filename ?? null;
7839
- }
7840
- function capturePlan(input) {
7841
- const now = Math.floor(Date.now() / 1e3);
7842
- const contentHash = createHash4(CONTENT_HASH_ALGORITHM).update(input.content).digest("hex");
7843
- const id = createHash4("md5").update(input.sourcePath).digest("hex").slice(0, PLAN_ID_HASH_LENGTH);
7844
- const title = parsePlanTitle(input.content, path20.basename(input.sourcePath));
7845
- return upsertPlan({
7846
- id,
7847
- title,
7848
- content: input.content,
7849
- source_path: input.sourcePath,
7850
- session_id: input.sessionId,
7851
- prompt_batch_id: input.promptBatchId ?? null,
7852
- content_hash: contentHash,
7853
- status: "active",
7854
- created_at: now,
7855
- updated_at: now
7856
- });
7857
- }
7858
-
7859
9473
  // src/daemon/skill-usage.ts
7860
- import crypto from "crypto";
9474
+ import crypto2 from "crypto";
7861
9475
  var SKILL_USAGE_DETECTION_ENABLED = false;
7862
9476
  var MAX_ACTIVE_SKILLS_CHECK = 1e3;
7863
9477
  function detectSkillUsage(sessionId, transcriptContent) {
@@ -7877,7 +9491,7 @@ function detectSkillUsage(sessionId, transcriptContent) {
7877
9491
  if (!pattern.test(transcriptContent)) continue;
7878
9492
  if (hasUsageForSkillAndSession(skill.id, sessionId)) continue;
7879
9493
  insertSkillUsage({
7880
- id: crypto.randomUUID(),
9494
+ id: crypto2.randomUUID(),
7881
9495
  skill_id: skill.id,
7882
9496
  session_id: sessionId,
7883
9497
  detected_at: now
@@ -7892,6 +9506,46 @@ function escapeRegex(s) {
7892
9506
  }
7893
9507
 
7894
9508
  // src/daemon/stop-processing.ts
9509
+ var MILLISECONDS_PER_SECOND = 1e3;
9510
+ function isEligiblePlanExtension(filePath, extensions) {
9511
+ if (!extensions || extensions.length === 0) return true;
9512
+ const extension = path22.extname(filePath).toLowerCase();
9513
+ return extensions.includes(extension);
9514
+ }
9515
+ function collectPlanFiles(rootDir, extensions) {
9516
+ const files = [];
9517
+ const stack = [rootDir];
9518
+ while (stack.length > 0) {
9519
+ const currentDir = stack.pop();
9520
+ if (!currentDir) continue;
9521
+ let entries;
9522
+ try {
9523
+ entries = fs22.readdirSync(currentDir, { withFileTypes: true });
9524
+ } catch {
9525
+ continue;
9526
+ }
9527
+ for (const entry of entries) {
9528
+ const absolutePath = path22.join(currentDir, entry.name);
9529
+ if (entry.isDirectory()) {
9530
+ stack.push(absolutePath);
9531
+ continue;
9532
+ }
9533
+ if (entry.isFile() && isEligiblePlanExtension(absolutePath, extensions)) {
9534
+ files.push(absolutePath);
9535
+ }
9536
+ }
9537
+ }
9538
+ return files;
9539
+ }
9540
+ function resolvePromptBatchIdForPlanWrite(batches, modifiedAtSeconds) {
9541
+ for (let index = batches.length - 1; index >= 0; index--) {
9542
+ const startedAt = batches[index].started_at;
9543
+ if (startedAt !== null && startedAt <= modifiedAtSeconds) {
9544
+ return batches[index].id;
9545
+ }
9546
+ }
9547
+ return void 0;
9548
+ }
7895
9549
  function enrichTurnsWithToolMetadata(turns, events) {
7896
9550
  if (events.length === 0 || turns.length === 0) return;
7897
9551
  const toolEvents = events.filter((e) => e.type === "tool_use");
@@ -7919,7 +9573,7 @@ function enrichTurnsWithToolMetadata(turns, events) {
7919
9573
  }
7920
9574
  }
7921
9575
  function createStopProcessor(deps) {
7922
- const { registry, sessionBuffers, transcriptMiner, embeddingManager, logger, liveConfig, vaultDir } = deps;
9576
+ const { registry, sessionBuffers, transcriptMiner, embeddingManager, logger, liveConfig, vaultDir, planWatchConfig } = deps;
7923
9577
  let activeStopProcessing = null;
7924
9578
  const sessionTitleCache = /* @__PURE__ */ new Map();
7925
9579
  const StopBody = external_exports.object({
@@ -8021,7 +9675,7 @@ function createStopProcessor(deps) {
8021
9675
  let transcriptText = null;
8022
9676
  if (hookTranscriptPath) {
8023
9677
  try {
8024
- transcriptText = fs21.readFileSync(hookTranscriptPath, "utf-8");
9678
+ transcriptText = fs22.readFileSync(hookTranscriptPath, "utf-8");
8025
9679
  } catch {
8026
9680
  }
8027
9681
  }
@@ -8053,11 +9707,12 @@ function createStopProcessor(deps) {
8053
9707
  const taggedPlans = extractTaggedPlans(turn.aiResponse, deps.planTags);
8054
9708
  for (const { tag, content } of taggedPlans) {
8055
9709
  try {
8056
- capturePlan({
8057
- sourcePath: `${TRANSCRIPT_SOURCE_PREFIX}${tag}`,
9710
+ captureTaggedPlan({
9711
+ tag,
8058
9712
  content,
8059
9713
  sessionId,
8060
- promptBatchId: latestBatch?.id ?? null
9714
+ promptBatchId: latestBatch?.id ?? null,
9715
+ logger
8061
9716
  });
8062
9717
  logger.info(LOG_KINDS.CAPTURE_PLAN, "Plan captured from transcript tag", {
8063
9718
  session_id: sessionId,
@@ -8074,6 +9729,50 @@ function createStopProcessor(deps) {
8074
9729
  }
8075
9730
  }
8076
9731
  }
9732
+ const sessionStartedAt = currentSession?.started_at ?? (sessionMeta?.started_at ? Math.floor(new Date(sessionMeta.started_at).getTime() / MILLISECONDS_PER_SECOND) : epochSeconds());
9733
+ const sessionStopMs = Date.now();
9734
+ const sessionBatches = listBatchesBySession(sessionId);
9735
+ for (const watchDir of planWatchConfig.watchDirs) {
9736
+ const absoluteWatchDir = resolvePlanWatchDir(watchDir, planWatchConfig.projectRoot);
9737
+ if (!fs22.existsSync(absoluteWatchDir)) continue;
9738
+ const planFiles = collectPlanFiles(absoluteWatchDir, planWatchConfig.extensions);
9739
+ for (const planFile of planFiles) {
9740
+ let stats;
9741
+ try {
9742
+ stats = fs22.statSync(planFile);
9743
+ } catch {
9744
+ continue;
9745
+ }
9746
+ if (stats.mtimeMs < sessionStartedAt * MILLISECONDS_PER_SECOND || stats.mtimeMs > sessionStopMs) {
9747
+ continue;
9748
+ }
9749
+ try {
9750
+ const content = fs22.readFileSync(planFile, "utf-8");
9751
+ const promptBatchId = resolvePromptBatchIdForPlanWrite(
9752
+ sessionBatches,
9753
+ Math.floor(stats.mtimeMs / MILLISECONDS_PER_SECOND)
9754
+ );
9755
+ capturePlan({
9756
+ sourcePath: planFile,
9757
+ projectRoot: planWatchConfig.projectRoot,
9758
+ content,
9759
+ sessionId,
9760
+ promptBatchId,
9761
+ logger
9762
+ });
9763
+ logger.info(LOG_KINDS.CAPTURE_PLAN, "Plan reconciled from configured plan dir", {
9764
+ session_id: sessionId,
9765
+ source_path: planFile
9766
+ });
9767
+ } catch (err) {
9768
+ logger.warn(LOG_KINDS.CAPTURE_PLAN, "Failed to reconcile plan from configured plan dir", {
9769
+ session_id: sessionId,
9770
+ source_path: planFile,
9771
+ error: err.message
9772
+ });
9773
+ }
9774
+ }
9775
+ }
8077
9776
  if (!hasTitle) {
8078
9777
  triggerTitleSummary2(sessionId);
8079
9778
  }
@@ -8120,12 +9819,11 @@ function createStopProcessor(deps) {
8120
9819
  last_assistant_message: lastAssistantMessage
8121
9820
  } = StopBody.parse(req.body);
8122
9821
  if (hookTranscriptPath) {
8123
- const transcriptMeta = readTranscriptMeta(hookTranscriptPath) ?? void 0;
8124
9822
  const detectedAgent = agent ?? getSession(sessionId)?.agent ?? "claude-code";
8125
- const decision = evaluateSessionCaptureRules(loadManifests(), detectedAgent, {
8126
- transcriptPath: hookTranscriptPath,
8127
- transcriptMeta
8128
- });
9823
+ const { decision } = gateEventByCaptureRules(
9824
+ { agent: detectedAgent, transcriptPath: hookTranscriptPath },
9825
+ { manifests: loadManifests() }
9826
+ );
8129
9827
  if (decision.action === "drop") {
8130
9828
  const deleted = cleanupInvalidCapturedSession(sessionId);
8131
9829
  logger.info(LOG_KINDS.HOOKS_STOP, "Stop ignored \u2014 invalid captured session", {
@@ -8180,9 +9878,10 @@ function createStopProcessor(deps) {
8180
9878
  }
8181
9879
 
8182
9880
  // src/daemon/event-dispatch.ts
8183
- import fs22 from "fs";
8184
- import path21 from "path";
9881
+ import fs23 from "fs";
9882
+ import path23 from "path";
8185
9883
  var EventBody = external_exports.object({ type: external_exports.string(), session_id: external_exports.string() }).passthrough();
9884
+ var DROP_DECISION_CACHE_MAX = 1024;
8186
9885
  function createEventDispatcher(deps) {
8187
9886
  const {
8188
9887
  registry,
@@ -8197,6 +9896,39 @@ function createEventDispatcher(deps) {
8197
9896
  triggerTitleSummary: triggerTitleSummary2
8198
9897
  } = deps;
8199
9898
  const projectRoot = process.cwd();
9899
+ const manifests = loadManifests();
9900
+ const planTagsByAgent = new Map(
9901
+ manifests.map((manifest) => [manifest.name, manifest.capture?.planTags ?? []])
9902
+ );
9903
+ const droppedSessions = /* @__PURE__ */ new Map();
9904
+ function rememberDropped(sessionId, reason, hadTranscriptMeta) {
9905
+ if (droppedSessions.size >= DROP_DECISION_CACHE_MAX) {
9906
+ const oldest = droppedSessions.keys().next().value;
9907
+ if (oldest !== void 0) droppedSessions.delete(oldest);
9908
+ }
9909
+ droppedSessions.set(sessionId, { reason, hadTranscriptMeta });
9910
+ }
9911
+ function evaluateAutoRegistration(event) {
9912
+ const transcriptPath = typeof event.transcript_path === "string" && event.transcript_path.length > 0 ? event.transcript_path : void 0;
9913
+ const detectedAgent = typeof event.agent === "string" ? event.agent : DEFAULT_SYMBIONT_NAME;
9914
+ try {
9915
+ return gateEventByCaptureRules(
9916
+ { agent: detectedAgent, transcriptPath },
9917
+ { manifests }
9918
+ );
9919
+ } catch (err) {
9920
+ logger.error(LOG_KINDS.LIFECYCLE_AUTO_REGISTER, "Capture-rules evaluator threw", {
9921
+ error: String(err),
9922
+ session_id: typeof event.session_id === "string" ? event.session_id : void 0,
9923
+ agent: detectedAgent
9924
+ });
9925
+ return { decision: { action: "pass" }, hadTranscriptMeta: false };
9926
+ }
9927
+ }
9928
+ function getPlanTagsForAgent(agent) {
9929
+ const agentName = typeof agent === "string" && agent.length > 0 ? agent : DEFAULT_SYMBIONT_NAME;
9930
+ return planTagsByAgent.get(agentName) ?? [];
9931
+ }
8200
9932
  return async (req) => {
8201
9933
  const validated = EventBody.parse(req.body);
8202
9934
  const event = {
@@ -8205,13 +9937,38 @@ function createEventDispatcher(deps) {
8205
9937
  };
8206
9938
  logger.debug(LOG_KINDS.HOOKS_EVENT, "Event received", { type: event.type, session_id: event.session_id });
8207
9939
  if (!registry.getSession(event.session_id)) {
9940
+ const cached = droppedSessions.get(event.session_id);
9941
+ const hasTranscriptNow = typeof event.transcript_path === "string" && event.transcript_path.length > 0;
9942
+ const shouldReevaluate = cached && !cached.hadTranscriptMeta && hasTranscriptNow;
9943
+ if (cached && !shouldReevaluate) {
9944
+ const reason = cached.reason ?? "rule";
9945
+ logger.debug(LOG_KINDS.LIFECYCLE_AUTO_REGISTER, "Ignored event for previously-dropped session", {
9946
+ session_id: event.session_id,
9947
+ type: event.type,
9948
+ reason
9949
+ });
9950
+ return { body: { ok: true, ignored: reason } };
9951
+ }
9952
+ if (shouldReevaluate) {
9953
+ droppedSessions.delete(event.session_id);
9954
+ }
9955
+ const { decision, hadTranscriptMeta } = evaluateAutoRegistration(event);
9956
+ if (decision.action === "drop") {
9957
+ rememberDropped(event.session_id, decision.reason, hadTranscriptMeta);
9958
+ logger.info(LOG_KINDS.LIFECYCLE_AUTO_REGISTER, "Ignored event that failed session capture rules", {
9959
+ session_id: event.session_id,
9960
+ type: event.type,
9961
+ reason: decision.reason ?? "rule"
9962
+ });
9963
+ return { body: { ok: true, ignored: decision.reason ?? "rule" } };
9964
+ }
8208
9965
  registry.register(event.session_id, { started_at: event.timestamp });
8209
9966
  logger.debug(LOG_KINDS.LIFECYCLE_AUTO_REGISTER, "Auto-registered session from event", { session_id: event.session_id });
8210
9967
  const now = epochSeconds();
8211
9968
  const startedEpoch = Math.floor(new Date(event.timestamp).getTime() / 1e3);
8212
9969
  upsertSession({
8213
9970
  id: event.session_id,
8214
- agent: event.agent ?? "claude-code",
9971
+ agent: event.agent ?? DEFAULT_SYMBIONT_NAME,
8215
9972
  status: "active",
8216
9973
  started_at: startedEpoch,
8217
9974
  created_at: now,
@@ -8220,7 +9977,7 @@ function createEventDispatcher(deps) {
8220
9977
  reconcileSession(event.session_id);
8221
9978
  }
8222
9979
  if (!sessionBuffers.has(event.session_id)) {
8223
- const bufferDir = path21.join(vaultDir, "buffer");
9980
+ const bufferDir = path23.join(vaultDir, "buffer");
8224
9981
  sessionBuffers.set(event.session_id, new EventBuffer(bufferDir, event.session_id));
8225
9982
  }
8226
9983
  sessionBuffers.get(event.session_id).append(event);
@@ -8246,6 +10003,29 @@ function createEventDispatcher(deps) {
8246
10003
  try {
8247
10004
  const { batchId, promptNumber } = handleUserPrompt(event.session_id, promptText || void 0);
8248
10005
  logger.debug(LOG_KINDS.CAPTURE_BATCH, "Batch opened", { session_id: event.session_id, batch_id: batchId, prompt_number: promptNumber });
10006
+ const taggedPlans = extractTaggedPlans(promptText, getPlanTagsForAgent(event.agent));
10007
+ for (const { tag, content } of taggedPlans) {
10008
+ try {
10009
+ captureTaggedPlan({
10010
+ tag,
10011
+ content,
10012
+ sessionId: event.session_id,
10013
+ promptBatchId: batchId,
10014
+ logger
10015
+ });
10016
+ logger.info(LOG_KINDS.CAPTURE_PLAN, "Plan captured from prompt tag", {
10017
+ session_id: event.session_id,
10018
+ tag,
10019
+ content_length: content.length
10020
+ });
10021
+ } catch (err) {
10022
+ logger.warn(LOG_KINDS.CAPTURE_PLAN, "Failed to capture plan from prompt tag", {
10023
+ session_id: event.session_id,
10024
+ tag,
10025
+ error: err.message
10026
+ });
10027
+ }
10028
+ }
8249
10029
  const eventImages = event.images;
8250
10030
  if (Array.isArray(eventImages) && eventImages.length > 0) {
8251
10031
  captureBatchImages({
@@ -8279,13 +10059,15 @@ function createEventDispatcher(deps) {
8279
10059
  );
8280
10060
  if (planFilePath) {
8281
10061
  const captureSessionId = event.session_id;
8282
- fs22.promises.readFile(planFilePath, "utf-8").then((planContent) => {
10062
+ fs23.promises.readFile(planFilePath, "utf-8").then((planContent) => {
8283
10063
  const latestBatch = getLatestBatch(captureSessionId);
8284
10064
  capturePlan({
8285
- sourcePath: path21.relative(projectRoot, planFilePath),
10065
+ sourcePath: planFilePath,
10066
+ projectRoot,
8286
10067
  content: planContent,
8287
10068
  sessionId: captureSessionId,
8288
- promptBatchId: latestBatch?.id ?? null
10069
+ promptBatchId: latestBatch?.id ?? null,
10070
+ logger
8289
10071
  });
8290
10072
  logger.info(LOG_KINDS.CAPTURE_PLAN, "Plan captured", {
8291
10073
  session_id: captureSessionId,
@@ -8445,8 +10227,8 @@ function createConfigReactionRegistry(logger) {
8445
10227
  function shouldFire(registeredPaths, touched) {
8446
10228
  if (registeredPaths.length === 0) return true;
8447
10229
  for (const prefix of registeredPaths) {
8448
- for (const path23 of touched) {
8449
- if (path23 === prefix || path23.startsWith(`${prefix}.`)) return true;
10230
+ for (const path25 of touched) {
10231
+ if (path25 === prefix || path25.startsWith(`${prefix}.`)) return true;
8450
10232
  }
8451
10233
  }
8452
10234
  return false;
@@ -8480,13 +10262,13 @@ function createPlanWatchReaction(deps) {
8480
10262
  }
8481
10263
 
8482
10264
  // src/daemon/main.ts
8483
- import fs23 from "fs";
8484
- import path22 from "path";
10265
+ import fs24 from "fs";
10266
+ import path24 from "path";
8485
10267
  function killStaleDaemon(vaultDir, logger) {
8486
- const daemonJsonPath = path22.join(vaultDir, "daemon.json");
10268
+ const daemonJsonPath = path24.join(vaultDir, "daemon.json");
8487
10269
  try {
8488
- if (!fs23.existsSync(daemonJsonPath)) return;
8489
- const info = JSON.parse(fs23.readFileSync(daemonJsonPath, "utf-8"));
10270
+ if (!fs24.existsSync(daemonJsonPath)) return;
10271
+ const info = JSON.parse(fs24.readFileSync(daemonJsonPath, "utf-8"));
8490
10272
  if (!info.pid) return;
8491
10273
  if (info.pid === process.pid) return;
8492
10274
  try {
@@ -8495,7 +10277,7 @@ function killStaleDaemon(vaultDir, logger) {
8495
10277
  logger.info(LOG_KINDS.DAEMON_START, "Killed stale daemon", { pid: info.pid });
8496
10278
  } catch {
8497
10279
  }
8498
- fs23.unlinkSync(daemonJsonPath);
10280
+ fs24.unlinkSync(daemonJsonPath);
8499
10281
  } catch {
8500
10282
  }
8501
10283
  }
@@ -8505,7 +10287,7 @@ async function main() {
8505
10287
  process.stderr.write("Usage: mycod --vault <path>\n");
8506
10288
  process.exit(1);
8507
10289
  }
8508
- const vaultDir = path22.resolve(vaultArg);
10290
+ const vaultDir = path24.resolve(vaultArg);
8509
10291
  loadSecrets(vaultDir);
8510
10292
  const config = loadMergedConfig(vaultDir);
8511
10293
  const liveConfig = { current: config };
@@ -8518,7 +10300,7 @@ async function main() {
8518
10300
  projectRoot,
8519
10301
  extensions: config.capture.artifact_extensions
8520
10302
  };
8521
- const logger = new DaemonLogger(path22.join(vaultDir, "logs"), {
10303
+ const logger = new DaemonLogger(path24.join(vaultDir, "logs"), {
8522
10304
  level: config.daemon.log_level
8523
10305
  });
8524
10306
  if (config.daemon.log_level === "debug") {
@@ -8547,7 +10329,7 @@ async function main() {
8547
10329
  const devCliEntry = detectDevBuild(
8548
10330
  globalPrefix,
8549
10331
  process.argv[1],
8550
- fs23.realpathSync
10332
+ fs24.realpathSync
8551
10333
  );
8552
10334
  if (devCliEntry) {
8553
10335
  setDevBuildCliEntry(devCliEntry);
@@ -8559,13 +10341,19 @@ async function main() {
8559
10341
  const db = initDatabase(vaultDbPath(vaultDir));
8560
10342
  createSchema(db, machineId);
8561
10343
  registerBuiltinDomains();
10344
+ const interruptedRuns = markRunningRunsInterrupted("Daemon restarted before the run completed");
10345
+ if (interruptedRuns > 0) {
10346
+ logger.warn(LOG_KINDS.AGENT_RUN, "Marked stale running runs as resumable after daemon restart", {
10347
+ count: interruptedRuns
10348
+ });
10349
+ }
8562
10350
  logger.info(LOG_KINDS.DAEMON_START, "SQLite initialized", { vault: vaultDir });
8563
10351
  {
8564
- const reasonPath = path22.join(vaultDir, RESTART_REASON_FILENAME);
10352
+ const reasonPath = path24.join(vaultDir, RESTART_REASON_FILENAME);
8565
10353
  try {
8566
- if (fs23.existsSync(reasonPath)) {
8567
- const raw = JSON.parse(fs23.readFileSync(reasonPath, "utf-8"));
8568
- fs23.unlinkSync(reasonPath);
10354
+ if (fs24.existsSync(reasonPath)) {
10355
+ const raw = JSON.parse(fs24.readFileSync(reasonPath, "utf-8"));
10356
+ fs24.unlinkSync(reasonPath);
8569
10357
  if (raw.reason === "version_sync" && raw.to_version) {
8570
10358
  const message = raw.local_update_ran ? "Restarted and updated local project hooks." : "Restarted to pick up the latest version.";
8571
10359
  notify(vaultDir, {
@@ -8607,13 +10395,13 @@ async function main() {
8607
10395
  });
8608
10396
  const lastLogTimestamp = getMaxTimestamp();
8609
10397
  if (lastLogTimestamp) {
8610
- const logDir = path22.join(vaultDir, "logs");
10398
+ const logDir = path24.join(vaultDir, "logs");
8611
10399
  const replayedCount = reconcileLogBuffer(logDir, lastLogTimestamp);
8612
10400
  if (replayedCount > 0) {
8613
10401
  logger.info(LOG_KINDS.DAEMON_RECONCILE, `Replayed ${replayedCount} log entries from buffer`, { replayed: replayedCount });
8614
10402
  }
8615
10403
  }
8616
- const vectorsDbPath = path22.join(vaultDir, "vectors.db");
10404
+ const vectorsDbPath = path24.join(vaultDir, "vectors.db");
8617
10405
  const vectorStore = new SqliteVecVectorStore(vectorsDbPath);
8618
10406
  const llmProvider = createEmbeddingProvider(config.embedding);
8619
10407
  const embeddingProvider = new EmbeddingProviderAdapter(llmProvider, config.embedding);
@@ -8623,7 +10411,7 @@ async function main() {
8623
10411
  const databaseManager = new DatabaseMaintenanceManager(vaultDbPath(vaultDir), vaultDir, logger);
8624
10412
  let definitionsDir;
8625
10413
  try {
8626
- const { registerBuiltInAgentsAndTasks, resolveDefinitionsDir: resolveDefinitionsDir2 } = await import("./loader-NOMBJUPW.js");
10414
+ const { registerBuiltInAgentsAndTasks, resolveDefinitionsDir: resolveDefinitionsDir2 } = await import("./loader-UDNUMEDA.js");
8627
10415
  definitionsDir = resolveDefinitionsDir2();
8628
10416
  await registerBuiltInAgentsAndTasks(definitionsDir, vaultDir);
8629
10417
  logger.info(LOG_KINDS.AGENT_TASK, "Built-in agents and tasks registered");
@@ -8660,10 +10448,10 @@ async function main() {
8660
10448
  }
8661
10449
  let uiDir = null;
8662
10450
  {
8663
- const root = findPackageRoot(path22.dirname(new URL(import.meta.url).pathname));
10451
+ const root = findPackageRoot(path24.dirname(new URL(import.meta.url).pathname));
8664
10452
  if (root) {
8665
- const candidate = path22.join(root, "dist", "ui");
8666
- if (fs23.existsSync(candidate)) uiDir = candidate;
10453
+ const candidate = path24.join(root, "dist", "ui");
10454
+ if (fs24.existsSync(candidate)) uiDir = candidate;
8667
10455
  }
8668
10456
  }
8669
10457
  if (uiDir) {
@@ -8677,6 +10465,7 @@ async function main() {
8677
10465
  sleepIntervalMs: POWER_SLEEP_INTERVAL_MS,
8678
10466
  logger
8679
10467
  });
10468
+ const inflightRuns = new InflightRunRegistry();
8680
10469
  const server = new DaemonServer({
8681
10470
  vaultDir,
8682
10471
  logger,
@@ -8696,7 +10485,7 @@ async function main() {
8696
10485
  (p) => createPerProjectAdapter(p, claudeCodeAdapter.parseTurns)
8697
10486
  )
8698
10487
  });
8699
- const bufferDir = path22.join(vaultDir, "buffer");
10488
+ const bufferDir = path24.join(vaultDir, "buffer");
8700
10489
  const sessionBuffers = /* @__PURE__ */ new Map();
8701
10490
  const reconciler = createReconciler({ bufferDir, logger });
8702
10491
  reconciler.runStartupReconciliation();
@@ -8708,7 +10497,8 @@ async function main() {
8708
10497
  logger,
8709
10498
  liveConfig,
8710
10499
  vaultDir,
8711
- planTags: symbiontPlanTags
10500
+ planTags: symbiontPlanTags,
10501
+ planWatchConfig
8712
10502
  });
8713
10503
  const sessionLifecycle = createSessionLifecycleHandlers({
8714
10504
  registry,
@@ -8738,14 +10528,32 @@ async function main() {
8738
10528
  });
8739
10529
  server.registerRoute("POST", "/events", eventDispatcher);
8740
10530
  server.registerRoute("POST", "/events/stop", stopProcessor.handleStopRoute);
8741
- const contextDeps = { embeddingManager, liveConfig, logger };
10531
+ let teamSync;
10532
+ const contextDeps = {
10533
+ vaultDir,
10534
+ embeddingManager,
10535
+ liveConfig,
10536
+ logger,
10537
+ getTeamClient: () => teamSync.getTeamClient()
10538
+ };
8742
10539
  server.registerRoute("POST", "/context", createSessionContextHandler(contextDeps));
8743
10540
  server.registerRoute("POST", "/context/resume", createResumeContextHandler(contextDeps));
8744
10541
  server.registerRoute("POST", "/context/prompt", createPromptContextHandler(contextDeps));
8745
10542
  const progressTracker = new ProgressTracker();
8746
10543
  let configHash = computeConfigHash(vaultDir);
10544
+ const cortexHandlers = createCortexHandlers(vaultDir, {
10545
+ liveConfig,
10546
+ embeddingManager,
10547
+ logger,
10548
+ getTeamClient: () => teamSync.getTeamClient(),
10549
+ registerInflightRun: (p) => inflightRuns.register(p)
10550
+ });
8747
10551
  server.registerRoute("GET", "/api/config", async () => handleGetConfig(vaultDir));
8748
10552
  server.registerRoute("GET", "/api/symbionts", async () => handleListSymbionts(vaultDir));
10553
+ server.registerRoute("GET", "/api/cortex/instructions", cortexHandlers.handleGetInstructions);
10554
+ server.registerRoute("POST", "/api/cortex/instructions/refresh", cortexHandlers.handleRefreshInstructions);
10555
+ server.registerRoute("POST", "/api/cortex/prompt-builder", cortexHandlers.handleBuildPrompt);
10556
+ server.registerRoute("GET", "/api/cortex/prompt-builder/:runId", cortexHandlers.handleGetPromptResult);
8749
10557
  server.registerRoute("GET", "/api/config/merged", async () => handleGetMergedConfig(vaultDir));
8750
10558
  server.registerRoute("GET", "/api/config/local", async () => handleGetLocalConfig(vaultDir));
8751
10559
  const symbiontPlanDirsByAgent = {};
@@ -8761,7 +10569,7 @@ async function main() {
8761
10569
  liveConfig.current = ctx;
8762
10570
  });
8763
10571
  reactions.on(["capture", "symbionts"], (ctx) => {
8764
- reconcileConfiguredSymbionts(path22.dirname(vaultDir), vaultDir, ctx);
10572
+ reconcileConfiguredSymbionts(path24.dirname(vaultDir), vaultDir, ctx);
8765
10573
  });
8766
10574
  reactions.on(["capture"], createPlanWatchReaction({
8767
10575
  symbiontPlanDirs,
@@ -8776,7 +10584,14 @@ async function main() {
8776
10584
  }
8777
10585
  });
8778
10586
  async function syncScheduledTasks() {
8779
- await registerScheduledTasks(powerManager, { definitionsDir, vaultDir, embeddingManager, logger, liveConfig });
10587
+ await registerScheduledTasks(powerManager, {
10588
+ definitionsDir,
10589
+ vaultDir,
10590
+ embeddingManager,
10591
+ logger,
10592
+ liveConfig,
10593
+ getTeamClient: () => teamSync.getTeamClient()
10594
+ });
8780
10595
  }
8781
10596
  reactions.on(["agent.tasks"], async () => {
8782
10597
  await syncScheduledTasks();
@@ -8828,9 +10643,9 @@ async function main() {
8828
10643
  server.registerRoute("GET", "/api/logs/stream", handleLogStream);
8829
10644
  server.registerRoute("GET", "/api/logs/:id", handleLogDetail);
8830
10645
  server.registerRoute("POST", "/api/log", createLogIngestionHandler(logger));
8831
- server.registerRoute("GET", "/api/models", async (req) => handleGetModels(req));
10646
+ server.registerRoute("GET", "/api/models", async (req) => handleGetModels(req, logger));
8832
10647
  server.registerRoute("POST", "/api/restart", async (req) => handleRestart({ vaultDir, progressTracker }, req.body));
8833
- const updateProjectRoot = path22.dirname(vaultDir);
10648
+ const updateProjectRoot = path24.dirname(vaultDir);
8834
10649
  const updateHandlers = createUpdateHandlers({
8835
10650
  vaultDir,
8836
10651
  projectRoot: updateProjectRoot,
@@ -8853,6 +10668,7 @@ async function main() {
8853
10668
  server.registerRoute("GET", "/api/sessions/:id/impact", sessionMutations.handleGetSessionImpact);
8854
10669
  server.registerRoute("POST", "/api/sessions/:id/complete", sessionMutations.handleCompleteSession);
8855
10670
  server.registerRoute("DELETE", "/api/sessions/:id", sessionMutations.handleDeleteSession);
10671
+ server.registerRoute("DELETE", "/api/plans/:id", sessionMutations.handleDeletePlan);
8856
10672
  server.registerRoute("GET", "/api/sessions/:id/batches", handleGetSessionBatches);
8857
10673
  server.registerRoute("GET", "/api/batches/:id/activities", handleGetBatchActivities);
8858
10674
  server.registerRoute("GET", "/api/sessions/:id/attachments", handleGetSessionAttachments);
@@ -8873,12 +10689,27 @@ async function main() {
8873
10689
  server.registerRoute("GET", "/api/digest", handleGetDigest);
8874
10690
  const attachments = createAttachmentHandler({ vaultDir });
8875
10691
  server.registerRoute("GET", "/api/attachments/:filename", attachments.handleGetAttachment);
8876
- const agentRunHandlers = createAgentRunHandlers({ vaultDir, embeddingManager, logger });
10692
+ const agentRunHandlers = createAgentRunHandlers({
10693
+ vaultDir,
10694
+ embeddingManager,
10695
+ logger,
10696
+ getTeamClient: () => teamSync.getTeamClient()
10697
+ });
8877
10698
  server.registerRoute("POST", "/api/agent/run", agentRunHandlers.handleRun);
8878
10699
  server.registerRoute("GET", "/api/agent/runs", agentRunHandlers.handleListRuns);
8879
10700
  server.registerRoute("GET", "/api/agent/runs/:id", agentRunHandlers.handleGetRun);
10701
+ server.registerRoute("POST", "/api/agent/runs/:id/resume", agentRunHandlers.handleResumeRun);
8880
10702
  server.registerRoute("GET", "/api/agent/runs/:id/reports", agentRunHandlers.handleGetRunReports);
8881
10703
  server.registerRoute("GET", "/api/agent/runs/:id/turns", agentRunHandlers.handleGetRunTurns);
10704
+ server.registerRoute("GET", "/api/agent/runs/:id/write-intents", agentRunHandlers.handleGetRunWriteIntents);
10705
+ server.registerRoute("GET", "/api/agent/runs/:id/audit", agentRunHandlers.handleGetRunAudit);
10706
+ const agentEvalHandlers = createAgentEvaluationHandlers({ vaultDir, embeddingManager, logger });
10707
+ server.registerRoute("POST", "/api/agent/evaluations", agentEvalHandlers.handleCreate);
10708
+ server.registerRoute("GET", "/api/agent/evaluations", agentEvalHandlers.handleList);
10709
+ server.registerRoute("GET", "/api/agent/evaluations/:id", agentEvalHandlers.handleGet);
10710
+ const digestRevisionHandlers = createDigestRevisionHandlers({ vaultDir, logger });
10711
+ server.registerRoute("GET", "/api/digest/revisions", digestRevisionHandlers.handleList);
10712
+ server.registerRoute("POST", "/api/digest/revisions/:id/restore", digestRevisionHandlers.handleRestore);
8882
10713
  server.registerRoute("GET", "/api/agent/tasks", async (req) => handleListTasks(req, vaultDir));
8883
10714
  server.registerRoute("GET", "/api/agent/tasks/:id", async (req) => handleGetTask(req, vaultDir));
8884
10715
  server.registerRoute("GET", "/api/agent/tasks/:id/yaml", async (req) => handleGetTaskYaml(req, vaultDir));
@@ -8918,13 +10749,17 @@ async function main() {
8918
10749
  }
8919
10750
  return result;
8920
10751
  });
8921
- server.registerRoute("GET", "/api/providers", async () => handleGetProviders());
10752
+ server.registerRoute("GET", "/api/providers", async () => handleGetProviders(logger));
8922
10753
  server.registerRoute("POST", "/api/providers/test", async (req) => handleTestProvider(req));
8923
- const mcpProxy = createMcpProxyHandlers({ machineId, embeddingManager });
10754
+ server.registerRoute("GET", "/api/providers/secrets", async () => handleGetProviderSecrets(vaultDir));
10755
+ server.registerRoute("PUT", "/api/providers/secrets/:provider", async (req) => handlePutProviderSecret(vaultDir, req));
10756
+ server.registerRoute("DELETE", "/api/providers/secrets/:provider", async (req) => handleDeleteProviderSecret(vaultDir, req));
10757
+ const mcpProxy = createMcpProxyHandlers({ machineId, embeddingManager, projectRoot, logger });
8924
10758
  server.registerRoute("POST", "/api/mcp/remember", mcpProxy.handleRemember);
8925
10759
  server.registerRoute("POST", "/api/mcp/supersede", mcpProxy.handleSupersede);
8926
10760
  server.registerRoute("POST", "/api/mcp/consolidate", mcpProxy.handleConsolidate);
8927
10761
  server.registerRoute("GET", "/api/mcp/plans", mcpProxy.handlePlans);
10762
+ server.registerRoute("POST", "/api/mcp/plans", mcpProxy.handleSavePlan);
8928
10763
  server.registerRoute("GET", "/api/mcp/sessions", mcpProxy.handleSessions);
8929
10764
  server.registerRoute("GET", "/api/mcp/team", mcpProxy.handleTeam);
8930
10765
  const backupHandlers = createBackupHandlers({ db, machineId, vaultDir, liveConfig });
@@ -8941,7 +10776,7 @@ async function main() {
8941
10776
  }
8942
10777
  return result;
8943
10778
  });
8944
- const teamSync = initTeamSync({ liveConfig, machineId, logger, vaultDir, serverVersion: server.version });
10779
+ teamSync = initTeamSync({ liveConfig, machineId, logger, vaultDir, serverVersion: server.version });
8945
10780
  reactions.on(["team"], async () => {
8946
10781
  await teamSync.reconcileClient();
8947
10782
  });
@@ -8950,7 +10785,7 @@ async function main() {
8950
10785
  vaultDir,
8951
10786
  machineId,
8952
10787
  logger,
8953
- getTeamClient: teamSync.getTeamClient,
10788
+ getTeamClient: () => teamSync.getTeamClient(),
8954
10789
  setTeamClient: teamSync.setTeamClient
8955
10790
  });
8956
10791
  server.registerRoute("POST", "/api/team/connect", async (req) => {
@@ -8973,14 +10808,14 @@ async function main() {
8973
10808
  server.registerRoute("POST", "/api/team/upgrade-worker", teamHandlers.handleUpgradeWorker);
8974
10809
  server.registerRoute("POST", "/api/team/rotate-mcp-token", teamHandlers.handleRotateMcpToken);
8975
10810
  const collectiveHandlers = createCollectiveHandlers({
8976
- getTeamClient: teamSync.getTeamClient
10811
+ getTeamClient: () => teamSync.getTeamClient()
8977
10812
  });
8978
10813
  server.registerRoute("GET", "/api/collective/status", collectiveHandlers.handleStatus);
8979
10814
  server.registerRoute("GET", "/api/collective/search", collectiveHandlers.handleSearch);
8980
10815
  server.registerRoute("GET", "/api/collective/projects", collectiveHandlers.handleProjects);
8981
10816
  server.registerRoute("GET", "/api/collective/project", collectiveHandlers.handleProject);
8982
10817
  server.registerRoute("GET", "/api/collective/settings", collectiveHandlers.handleSettings);
8983
- server.registerRoute("GET", "/api/search", createSearchHandler({ embeddingManager, getTeamClient: teamSync.getTeamClient, machineId }));
10818
+ server.registerRoute("GET", "/api/search", createSearchHandler({ embeddingManager, getTeamClient: () => teamSync.getTeamClient(), machineId }));
8984
10819
  server.registerRoute("GET", "/api/activity", handleGetFeed);
8985
10820
  server.registerRoute("GET", "/api/embedding/status", async () => handleGetEmbeddingStatus(vaultDir));
8986
10821
  server.registerRoute("GET", "/api/embedding/details", async () => handleEmbeddingDetails(embeddingManager));
@@ -9018,9 +10853,25 @@ async function main() {
9018
10853
  logger.warn(LOG_KINDS.DAEMON_CONFIG, "Failed to persist auto-derived port", { error: err.message });
9019
10854
  }
9020
10855
  }
9021
- registerPowerJobs(powerManager, { embeddingManager, registry, logger, liveConfig, db, machineId, vaultDir, databaseManager });
10856
+ registerPowerJobs(powerManager, {
10857
+ embeddingManager,
10858
+ registry,
10859
+ logger,
10860
+ liveConfig,
10861
+ db,
10862
+ machineId,
10863
+ vaultDir,
10864
+ databaseManager
10865
+ });
9022
10866
  teamSync.registerFlushJob(powerManager);
9023
- await registerScheduledTasks(powerManager, { definitionsDir, vaultDir, embeddingManager, logger, liveConfig });
10867
+ await registerScheduledTasks(powerManager, {
10868
+ definitionsDir,
10869
+ vaultDir,
10870
+ embeddingManager,
10871
+ logger,
10872
+ liveConfig,
10873
+ getTeamClient: () => teamSync.getTeamClient()
10874
+ });
9024
10875
  powerManager.start();
9025
10876
  const shutdown = async (signal) => {
9026
10877
  logger.info(LOG_KINDS.DAEMON_START, `${signal} received`);
@@ -9030,6 +10881,17 @@ async function main() {
9030
10881
  logger.info(LOG_KINDS.DAEMON_START, "Waiting for active stop processing to complete...");
9031
10882
  await activeStopProcessing;
9032
10883
  }
10884
+ if (inflightRuns.size > 0) {
10885
+ logger.info(LOG_KINDS.DAEMON_START, "Draining in-flight agent runs before shutdown...", {
10886
+ inflight_count: inflightRuns.size
10887
+ });
10888
+ const outcome = await inflightRuns.drain();
10889
+ if (!outcome.settled) {
10890
+ logger.warn(LOG_KINDS.DAEMON_START, "Some in-flight runs did not settle before shutdown timeout", {
10891
+ remaining: outcome.remaining
10892
+ });
10893
+ }
10894
+ }
9033
10895
  registry.destroy();
9034
10896
  await server.stop();
9035
10897
  vectorStore.close();
@@ -9052,4 +10914,4 @@ export {
9052
10914
  handleUserPrompt,
9053
10915
  main
9054
10916
  };
9055
- //# sourceMappingURL=main-YTBVRTBI.js.map
10917
+ //# sourceMappingURL=main-4J4QZZTZ.js.map