@goondocks/myco 0.14.4 → 0.15.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 (173) hide show
  1. package/dist/{agent-run-GZ5UVLDV.js → agent-run-T433ENJS.js} +6 -6
  2. package/dist/{agent-tasks-KKQ2GBBB.js → agent-tasks-TAIU3V5I.js} +6 -6
  3. package/dist/{chunk-X34OFKYU.js → chunk-23FJUKCN.js} +2 -2
  4. package/dist/{chunk-LD6U3L6O.js → chunk-2QMDRZPJ.js} +21 -17
  5. package/dist/chunk-2QMDRZPJ.js.map +1 -0
  6. package/dist/{chunk-KNTJOMWY.js → chunk-3MEOYXOW.js} +2 -2
  7. package/dist/{chunk-PSYLKCWQ.js → chunk-4BQ5QE76.js} +24 -5
  8. package/dist/chunk-4BQ5QE76.js.map +1 -0
  9. package/dist/{chunk-UZ5Y6XMP.js → chunk-4O3QNM5I.js} +2 -2
  10. package/dist/{chunk-JTYZRPX5.js → chunk-5ZT2Q6P5.js} +1 -1
  11. package/dist/{chunk-BCKYVLUZ.js → chunk-6GG2IVNV.js} +3 -3
  12. package/dist/{chunk-JJXVDCEX.js → chunk-75J2BR4P.js} +486 -488
  13. package/dist/chunk-75J2BR4P.js.map +1 -0
  14. package/dist/{chunk-TFBAV3PV.js → chunk-BFM6AM6R.js} +2 -2
  15. package/dist/{chunk-S6I62FAH.js → chunk-CUADDHHU.js} +4 -2
  16. package/dist/{chunk-S6I62FAH.js.map → chunk-CUADDHHU.js.map} +1 -1
  17. package/dist/{chunk-4VF6KQ2Z.js → chunk-DJQOYEK3.js} +87 -84
  18. package/dist/chunk-DJQOYEK3.js.map +1 -0
  19. package/dist/{chunk-OQVKLTQY.js → chunk-EYMKBNRP.js} +2 -2
  20. package/dist/{chunk-STBNNKL5.js → chunk-GCCBXCHF.js} +6 -6
  21. package/dist/{chunk-KH64DHOY.js → chunk-GDY63YAW.js} +279 -277
  22. package/dist/chunk-GDY63YAW.js.map +1 -0
  23. package/dist/{chunk-ZESTWGJT.js → chunk-GYIA6XLB.js} +2 -2
  24. package/dist/{chunk-S66YG6QK.js → chunk-LF5Z62X6.js} +46 -7
  25. package/dist/chunk-LF5Z62X6.js.map +1 -0
  26. package/dist/{chunk-PX5KIOKY.js → chunk-SPJGJEFV.js} +10 -2
  27. package/dist/{chunk-PX5KIOKY.js.map → chunk-SPJGJEFV.js.map} +1 -1
  28. package/dist/{chunk-QFMBZ72S.js → chunk-SV6UCB2Z.js} +2 -2
  29. package/dist/{chunk-GMTWRMLP.js → chunk-TQO4PF5K.js} +3 -3
  30. package/dist/{chunk-NVCGF2DS.js → chunk-X4XFJG6I.js} +10 -6
  31. package/dist/chunk-X4XFJG6I.js.map +1 -0
  32. package/dist/{chunk-TNCBMGWB.js → chunk-X5IXK5KO.js} +262 -226
  33. package/dist/chunk-X5IXK5KO.js.map +1 -0
  34. package/dist/{chunk-TVV6PZOC.js → chunk-Z7TZJ2SP.js} +2 -2
  35. package/dist/{cli-JLDCZ77U.js → cli-W37MRZHD.js} +45 -44
  36. package/dist/cli-W37MRZHD.js.map +1 -0
  37. package/dist/{client-LRQMMKLP.js → client-YNTTC75R.js} +4 -4
  38. package/dist/{config-H657SF6B.js → config-MOWCOJJ4.js} +4 -4
  39. package/dist/{detect-27DN6UTL.js → detect-GFYKKHLJ.js} +3 -3
  40. package/dist/{detect-providers-PAVE2X6O.js → detect-providers-EU35RUL3.js} +2 -2
  41. package/dist/{doctor-IG3CXMI7.js → doctor-PAAQU5AS.js} +38 -19
  42. package/dist/doctor-PAAQU5AS.js.map +1 -0
  43. package/dist/{executor-HKNINUWO.js → executor-4OXDK4ZA.js} +790 -312
  44. package/dist/executor-4OXDK4ZA.js.map +1 -0
  45. package/dist/{init-RHQUINC2.js → init-PHQAQANR.js} +41 -23
  46. package/dist/init-PHQAQANR.js.map +1 -0
  47. package/dist/{init-wizard-ZB3JRDLE.js → init-wizard-RFD46XAJ.js} +7 -7
  48. package/dist/{installer-25TSX4SR.js → installer-BTUNKWOU.js} +2 -2
  49. package/dist/{llm-T3QVHC3Y.js → llm-D4VWYUK7.js} +4 -4
  50. package/dist/{loader-WQKVWL5D.js → loader-WC4U5NM5.js} +4 -4
  51. package/dist/{loader-JQLO6K44.js → loader-WGDVRGLM.js} +6 -4
  52. package/dist/{logs-LXHPDKUA.js → logs-WFBX2I7C.js} +3 -3
  53. package/dist/{main-MVXPBP5Z.js → main-ADLCOYKM.js} +2351 -1914
  54. package/dist/main-ADLCOYKM.js.map +1 -0
  55. package/dist/{open-CVEMRH3Z.js → open-3VPUP3HD.js} +6 -6
  56. package/dist/{openai-embeddings-5T5ZP7LO.js → openai-embeddings-SEIV7AM3.js} +2 -2
  57. package/dist/{openrouter-RD2COFC7.js → openrouter-ELODIZRP.js} +2 -2
  58. package/dist/{post-compact-ALQ2UGZ7.js → post-compact-5NYLOC46.js} +9 -9
  59. package/dist/{post-tool-use-SPL7HIYU.js → post-tool-use-SNNXSZ5Y.js} +10 -10
  60. package/dist/{post-tool-use-failure-B3CUYBTR.js → post-tool-use-failure-POKVXQHY.js} +9 -9
  61. package/dist/{pre-compact-KPWC4V64.js → pre-compact-ZUICBJEX.js} +9 -9
  62. package/dist/{provider-check-QN7OGXZA.js → provider-check-B66E5PWS.js} +2 -2
  63. package/dist/{registry-2XQMCPA6.js → registry-DHWVHXWY.js} +5 -5
  64. package/dist/{remove-O2WCN6RC.js → remove-SVU2V4Q7.js} +52 -20
  65. package/dist/remove-SVU2V4Q7.js.map +1 -0
  66. package/dist/{resolution-events-5EVUEWHS.js → resolution-events-DBCRVZGU.js} +4 -4
  67. package/dist/{restart-S52VV3SP.js → restart-NBB5CXJ4.js} +7 -7
  68. package/dist/{search-IOJ5O37S.js → search-YUQZFRZX.js} +6 -6
  69. package/dist/{server-T4VPK6FU.js → server-NBRX56VL.js} +8 -8
  70. package/dist/{session-ID6BX72K.js → session-2QP4HMZ5.js} +8 -8
  71. package/dist/{session-end-I7ZABXRI.js → session-end-NNFBW7CQ.js} +10 -10
  72. package/dist/{session-start-VPOUY42U.js → session-start-NPNP4IXX.js} +15 -15
  73. package/dist/{setup-llm-G5UG5N3T.js → setup-llm-C3IGFLRN.js} +8 -8
  74. package/dist/src/agent/definitions/tasks/full-intelligence.yaml +8 -7
  75. package/dist/src/agent/definitions/tasks/skill-evolve.yaml +71 -144
  76. package/dist/src/agent/definitions/tasks/skill-generate.yaml +10 -62
  77. package/dist/src/agent/definitions/tasks/skill-survey.yaml +87 -53
  78. package/dist/src/agent/prompts/agent.md +1 -0
  79. package/dist/src/cli.js +1 -1
  80. package/dist/src/daemon/main.js +1 -1
  81. package/dist/src/hooks/post-tool-use.js +1 -1
  82. package/dist/src/hooks/session-end.js +1 -1
  83. package/dist/src/hooks/session-start.js +1 -1
  84. package/dist/src/hooks/stop.js +1 -1
  85. package/dist/src/hooks/user-prompt-submit.js +1 -1
  86. package/dist/src/mcp/server.js +1 -1
  87. package/dist/src/worker/src/schema.ts +14 -0
  88. package/dist/{stats-GRI4MTS2.js → stats-FEEXIRMS.js} +9 -9
  89. package/dist/{stop-UTZ2CXI2.js → stop-FGDGWXTK.js} +10 -10
  90. package/dist/{stop-failure-CECM5NB7.js → stop-failure-5YAGH2TQ.js} +9 -9
  91. package/dist/{subagent-start-SYZGJYUN.js → subagent-start-UCKVJDR4.js} +9 -9
  92. package/dist/{subagent-stop-7WWW7TGQ.js → subagent-stop-H25B3QEC.js} +9 -9
  93. package/dist/{task-completed-N7SIY6T6.js → task-completed-2JGZN2CF.js} +9 -9
  94. package/dist/{team-SJPDXELY.js → team-TG5WZXWO.js} +34 -26
  95. package/dist/team-TG5WZXWO.js.map +1 -0
  96. package/dist/ui/assets/index-7Vimyg7g.js +837 -0
  97. package/dist/ui/assets/{index-BmsHIwjl.css → index-DlEQ8A8Y.css} +1 -1
  98. package/dist/ui/index.html +2 -2
  99. package/dist/{update-DZZYQ4NJ.js → update-EG3N2EXI.js} +30 -14
  100. package/dist/update-EG3N2EXI.js.map +1 -0
  101. package/dist/{user-prompt-submit-UUNRRS5P.js → user-prompt-submit-7FFQ3ORA.js} +10 -10
  102. package/dist/{verify-JHIMXTY5.js → verify-2M3DYHEY.js} +6 -6
  103. package/dist/{version-VKNCAPZW.js → version-JUQU5W22.js} +2 -2
  104. package/package.json +3 -3
  105. package/dist/chunk-4VF6KQ2Z.js.map +0 -1
  106. package/dist/chunk-JJXVDCEX.js.map +0 -1
  107. package/dist/chunk-KH64DHOY.js.map +0 -1
  108. package/dist/chunk-LD6U3L6O.js.map +0 -1
  109. package/dist/chunk-NVCGF2DS.js.map +0 -1
  110. package/dist/chunk-PSYLKCWQ.js.map +0 -1
  111. package/dist/chunk-S66YG6QK.js.map +0 -1
  112. package/dist/chunk-TNCBMGWB.js.map +0 -1
  113. package/dist/cli-JLDCZ77U.js.map +0 -1
  114. package/dist/doctor-IG3CXMI7.js.map +0 -1
  115. package/dist/executor-HKNINUWO.js.map +0 -1
  116. package/dist/init-RHQUINC2.js.map +0 -1
  117. package/dist/main-MVXPBP5Z.js.map +0 -1
  118. package/dist/remove-O2WCN6RC.js.map +0 -1
  119. package/dist/resolve-3FEUV462.js +0 -9
  120. package/dist/team-SJPDXELY.js.map +0 -1
  121. package/dist/ui/assets/index-Cn6cQwJy.js +0 -842
  122. package/dist/update-DZZYQ4NJ.js.map +0 -1
  123. package/dist/version-VKNCAPZW.js.map +0 -1
  124. /package/dist/{agent-run-GZ5UVLDV.js.map → agent-run-T433ENJS.js.map} +0 -0
  125. /package/dist/{agent-tasks-KKQ2GBBB.js.map → agent-tasks-TAIU3V5I.js.map} +0 -0
  126. /package/dist/{chunk-X34OFKYU.js.map → chunk-23FJUKCN.js.map} +0 -0
  127. /package/dist/{chunk-KNTJOMWY.js.map → chunk-3MEOYXOW.js.map} +0 -0
  128. /package/dist/{chunk-UZ5Y6XMP.js.map → chunk-4O3QNM5I.js.map} +0 -0
  129. /package/dist/{chunk-JTYZRPX5.js.map → chunk-5ZT2Q6P5.js.map} +0 -0
  130. /package/dist/{chunk-BCKYVLUZ.js.map → chunk-6GG2IVNV.js.map} +0 -0
  131. /package/dist/{chunk-TFBAV3PV.js.map → chunk-BFM6AM6R.js.map} +0 -0
  132. /package/dist/{chunk-OQVKLTQY.js.map → chunk-EYMKBNRP.js.map} +0 -0
  133. /package/dist/{chunk-STBNNKL5.js.map → chunk-GCCBXCHF.js.map} +0 -0
  134. /package/dist/{chunk-ZESTWGJT.js.map → chunk-GYIA6XLB.js.map} +0 -0
  135. /package/dist/{chunk-QFMBZ72S.js.map → chunk-SV6UCB2Z.js.map} +0 -0
  136. /package/dist/{chunk-GMTWRMLP.js.map → chunk-TQO4PF5K.js.map} +0 -0
  137. /package/dist/{chunk-TVV6PZOC.js.map → chunk-Z7TZJ2SP.js.map} +0 -0
  138. /package/dist/{client-LRQMMKLP.js.map → client-YNTTC75R.js.map} +0 -0
  139. /package/dist/{config-H657SF6B.js.map → config-MOWCOJJ4.js.map} +0 -0
  140. /package/dist/{detect-27DN6UTL.js.map → detect-GFYKKHLJ.js.map} +0 -0
  141. /package/dist/{detect-providers-PAVE2X6O.js.map → detect-providers-EU35RUL3.js.map} +0 -0
  142. /package/dist/{init-wizard-ZB3JRDLE.js.map → init-wizard-RFD46XAJ.js.map} +0 -0
  143. /package/dist/{installer-25TSX4SR.js.map → installer-BTUNKWOU.js.map} +0 -0
  144. /package/dist/{llm-T3QVHC3Y.js.map → llm-D4VWYUK7.js.map} +0 -0
  145. /package/dist/{loader-JQLO6K44.js.map → loader-WC4U5NM5.js.map} +0 -0
  146. /package/dist/{loader-WQKVWL5D.js.map → loader-WGDVRGLM.js.map} +0 -0
  147. /package/dist/{logs-LXHPDKUA.js.map → logs-WFBX2I7C.js.map} +0 -0
  148. /package/dist/{open-CVEMRH3Z.js.map → open-3VPUP3HD.js.map} +0 -0
  149. /package/dist/{openai-embeddings-5T5ZP7LO.js.map → openai-embeddings-SEIV7AM3.js.map} +0 -0
  150. /package/dist/{openrouter-RD2COFC7.js.map → openrouter-ELODIZRP.js.map} +0 -0
  151. /package/dist/{post-compact-ALQ2UGZ7.js.map → post-compact-5NYLOC46.js.map} +0 -0
  152. /package/dist/{post-tool-use-SPL7HIYU.js.map → post-tool-use-SNNXSZ5Y.js.map} +0 -0
  153. /package/dist/{post-tool-use-failure-B3CUYBTR.js.map → post-tool-use-failure-POKVXQHY.js.map} +0 -0
  154. /package/dist/{pre-compact-KPWC4V64.js.map → pre-compact-ZUICBJEX.js.map} +0 -0
  155. /package/dist/{provider-check-QN7OGXZA.js.map → provider-check-B66E5PWS.js.map} +0 -0
  156. /package/dist/{registry-2XQMCPA6.js.map → registry-DHWVHXWY.js.map} +0 -0
  157. /package/dist/{resolution-events-5EVUEWHS.js.map → resolution-events-DBCRVZGU.js.map} +0 -0
  158. /package/dist/{restart-S52VV3SP.js.map → restart-NBB5CXJ4.js.map} +0 -0
  159. /package/dist/{search-IOJ5O37S.js.map → search-YUQZFRZX.js.map} +0 -0
  160. /package/dist/{server-T4VPK6FU.js.map → server-NBRX56VL.js.map} +0 -0
  161. /package/dist/{session-ID6BX72K.js.map → session-2QP4HMZ5.js.map} +0 -0
  162. /package/dist/{session-end-I7ZABXRI.js.map → session-end-NNFBW7CQ.js.map} +0 -0
  163. /package/dist/{session-start-VPOUY42U.js.map → session-start-NPNP4IXX.js.map} +0 -0
  164. /package/dist/{setup-llm-G5UG5N3T.js.map → setup-llm-C3IGFLRN.js.map} +0 -0
  165. /package/dist/{stats-GRI4MTS2.js.map → stats-FEEXIRMS.js.map} +0 -0
  166. /package/dist/{stop-UTZ2CXI2.js.map → stop-FGDGWXTK.js.map} +0 -0
  167. /package/dist/{stop-failure-CECM5NB7.js.map → stop-failure-5YAGH2TQ.js.map} +0 -0
  168. /package/dist/{subagent-start-SYZGJYUN.js.map → subagent-start-UCKVJDR4.js.map} +0 -0
  169. /package/dist/{subagent-stop-7WWW7TGQ.js.map → subagent-stop-H25B3QEC.js.map} +0 -0
  170. /package/dist/{task-completed-N7SIY6T6.js.map → task-completed-2JGZN2CF.js.map} +0 -0
  171. /package/dist/{user-prompt-submit-UUNRRS5P.js.map → user-prompt-submit-7FFQ3ORA.js.map} +0 -0
  172. /package/dist/{verify-JHIMXTY5.js.map → verify-2M3DYHEY.js.map} +0 -0
  173. /package/dist/{resolve-3FEUV462.js.map → version-JUQU5W22.js.map} +0 -0
@@ -2,7 +2,7 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
2
2
  import {
3
3
  DaemonLogger,
4
4
  LEVEL_ORDER
5
- } from "./chunk-ZESTWGJT.js";
5
+ } from "./chunk-GYIA6XLB.js";
6
6
  import {
7
7
  closeOpenBatches,
8
8
  countBatchesBySession,
@@ -48,7 +48,7 @@ import {
48
48
  setResponseSummary,
49
49
  updateCandidate,
50
50
  updateNotificationStatus
51
- } from "./chunk-JJXVDCEX.js";
51
+ } from "./chunk-75J2BR4P.js";
52
52
  import {
53
53
  fullTextSearch,
54
54
  hydrateSearchResults
@@ -65,36 +65,29 @@ import {
65
65
  getEmbeddingQueueDepth,
66
66
  getUnembedded,
67
67
  markEmbedded
68
- } from "./chunk-GMTWRMLP.js";
68
+ } from "./chunk-TQO4PF5K.js";
69
69
  import {
70
70
  getMachineId
71
71
  } from "./chunk-ENWBFX7F.js";
72
72
  import {
73
73
  createEmbeddingProvider
74
- } from "./chunk-LD6U3L6O.js";
74
+ } from "./chunk-2QMDRZPJ.js";
75
75
  import {
76
76
  copyTaskToUser,
77
77
  deleteUserTask,
78
78
  loadAllTasks,
79
79
  validateTaskName,
80
80
  writeUserTask
81
- } from "./chunk-KNTJOMWY.js";
81
+ } from "./chunk-3MEOYXOW.js";
82
82
  import {
83
83
  AgentTaskSchema,
84
84
  registerAgent,
85
85
  resolveDefinitionsDir,
86
86
  taskFromParsed
87
- } from "./chunk-NVCGF2DS.js";
87
+ } from "./chunk-X4XFJG6I.js";
88
88
  import {
89
89
  listTurnsByRun
90
90
  } from "./chunk-QLCD77AN.js";
91
- import {
92
- loadSecrets,
93
- readSecrets,
94
- writeSecret
95
- } from "./chunk-RJMXDUMA.js";
96
- import "./chunk-BCKYVLUZ.js";
97
- import "./chunk-SAKJMNSR.js";
98
91
  import {
99
92
  checkLocalProvider
100
93
  } from "./chunk-GDCSPMH4.js";
@@ -104,21 +97,29 @@ import {
104
97
  listBufferSessionIds
105
98
  } from "./chunk-V7XG6V6C.js";
106
99
  import "./chunk-IB76KGBY.js";
107
- import "./chunk-TNCBMGWB.js";
100
+ import "./chunk-RBFECYNA.js";
101
+ import "./chunk-OKCSSDFC.js";
102
+ import "./chunk-5YQ6VOFZ.js";
103
+ import "./chunk-6GG2IVNV.js";
104
+ import "./chunk-SAKJMNSR.js";
105
+ import {
106
+ loadSecrets,
107
+ readSecrets,
108
+ writeSecret
109
+ } from "./chunk-RJMXDUMA.js";
110
+ import "./chunk-X5IXK5KO.js";
108
111
  import {
109
112
  LmStudioBackend,
110
113
  OllamaBackend
111
114
  } from "./chunk-HHZ3RTEI.js";
112
- import "./chunk-RBFECYNA.js";
113
- import "./chunk-OKCSSDFC.js";
114
- import "./chunk-5YQ6VOFZ.js";
115
115
  import {
116
116
  countSpores,
117
117
  getSpore,
118
118
  insertSpore,
119
+ listSporeIdsSince,
119
120
  listSpores,
120
121
  updateSporeStatus
121
- } from "./chunk-PX5KIOKY.js";
122
+ } from "./chunk-SPJGJEFV.js";
122
123
  import {
123
124
  closeSession,
124
125
  countSessions,
@@ -129,33 +130,37 @@ import {
129
130
  listSessions,
130
131
  updateSession,
131
132
  upsertSession
132
- } from "./chunk-X34OFKYU.js";
133
+ } from "./chunk-23FJUKCN.js";
133
134
  import {
134
135
  backfillUnsynced,
136
+ countDeadLettered,
135
137
  countPending,
136
138
  enqueueOutbox,
137
139
  getTeamMachineId,
140
+ incrementRetryCount,
138
141
  initTeamContext,
139
142
  isTeamSyncEnabled,
140
143
  listPending,
141
144
  markSent,
142
145
  markSourceRowsSynced,
143
146
  pruneOld,
147
+ retryDeadLettered,
144
148
  syncRow
145
- } from "./chunk-S66YG6QK.js";
149
+ } from "./chunk-LF5Z62X6.js";
146
150
  import {
147
151
  EMBEDDING_DIMENSIONS,
148
152
  SCHEMA_VERSION,
149
153
  createSchema
150
- } from "./chunk-4VF6KQ2Z.js";
154
+ } from "./chunk-DJQOYEK3.js";
151
155
  import {
152
156
  CONFIG_FILENAME,
153
157
  MycoConfigSchema,
158
+ getEnabledSymbiontNames,
154
159
  loadConfig,
155
160
  updateBackupConfig,
156
161
  updateConfig,
157
162
  updateTeamConfig
158
- } from "./chunk-PSYLKCWQ.js";
163
+ } from "./chunk-4BQ5QE76.js";
159
164
  import {
160
165
  closeDatabase,
161
166
  getDatabase,
@@ -164,13 +169,13 @@ import {
164
169
  } from "./chunk-MYX5NCRH.js";
165
170
  import {
166
171
  resolveCliEntryPath
167
- } from "./chunk-UZ5Y6XMP.js";
172
+ } from "./chunk-4O3QNM5I.js";
168
173
  import {
169
174
  getPluginVersion
170
- } from "./chunk-TFBAV3PV.js";
175
+ } from "./chunk-BFM6AM6R.js";
171
176
  import {
172
177
  loadManifests
173
- } from "./chunk-QFMBZ72S.js";
178
+ } from "./chunk-SV6UCB2Z.js";
174
179
  import {
175
180
  findPackageRoot
176
181
  } from "./chunk-LPUQPDC2.js";
@@ -229,14 +234,14 @@ import {
229
234
  import {
230
235
  LOG_KINDS,
231
236
  kindToComponent
232
- } from "./chunk-S6I62FAH.js";
237
+ } from "./chunk-CUADDHHU.js";
233
238
  import {
234
239
  require_dist
235
240
  } from "./chunk-D7TYRPRM.js";
236
241
  import "./chunk-E4VLWIJC.js";
237
242
  import {
238
243
  external_exports
239
- } from "./chunk-KH64DHOY.js";
244
+ } from "./chunk-GDY63YAW.js";
240
245
  import {
241
246
  __toESM
242
247
  } from "./chunk-PZUWP5VK.js";
@@ -1264,183 +1269,6 @@ function extractTurnsFromBuffer(events) {
1264
1269
  return turns;
1265
1270
  }
1266
1271
 
1267
- // src/daemon/plan-capture.ts
1268
- import { createHash as createHash2 } from "crypto";
1269
- import os6 from "os";
1270
- import path9 from "path";
1271
-
1272
- // src/db/queries/plans.ts
1273
- var DEFAULT_LIST_LIMIT2 = 100;
1274
- var DEFAULT_STATUS2 = "active";
1275
- var DEFAULT_PROCESSED = 0;
1276
- var PLAN_COLUMNS = [
1277
- "id",
1278
- "status",
1279
- "author",
1280
- "title",
1281
- "content",
1282
- "source_path",
1283
- "tags",
1284
- "session_id",
1285
- "prompt_batch_id",
1286
- "content_hash",
1287
- "processed",
1288
- "embedded",
1289
- "created_at",
1290
- "updated_at",
1291
- "machine_id",
1292
- "synced_at"
1293
- ];
1294
- var SELECT_COLUMNS = PLAN_COLUMNS.join(", ");
1295
- function toPlanRow(row) {
1296
- return {
1297
- id: row.id,
1298
- status: row.status,
1299
- author: row.author ?? null,
1300
- title: row.title ?? null,
1301
- content: row.content ?? null,
1302
- source_path: row.source_path ?? null,
1303
- tags: row.tags ?? null,
1304
- session_id: row.session_id ?? null,
1305
- prompt_batch_id: row.prompt_batch_id ?? null,
1306
- content_hash: row.content_hash ?? null,
1307
- processed: row.processed,
1308
- embedded: row.embedded ?? 0,
1309
- created_at: row.created_at,
1310
- updated_at: row.updated_at ?? null,
1311
- machine_id: row.machine_id ?? "local",
1312
- synced_at: row.synced_at ?? null
1313
- };
1314
- }
1315
- function upsertPlan(data) {
1316
- const db = getDatabase();
1317
- db.prepare(
1318
- `INSERT INTO plans (
1319
- id, status, author, title, content,
1320
- source_path, tags, session_id, prompt_batch_id, content_hash,
1321
- processed, created_at, updated_at, machine_id
1322
- ) VALUES (
1323
- ?, ?, ?, ?, ?,
1324
- ?, ?, ?, ?, ?,
1325
- ?, ?, ?, ?
1326
- )
1327
- ON CONFLICT (id) DO UPDATE SET
1328
- status = EXCLUDED.status,
1329
- author = EXCLUDED.author,
1330
- title = EXCLUDED.title,
1331
- content = EXCLUDED.content,
1332
- source_path = EXCLUDED.source_path,
1333
- tags = EXCLUDED.tags,
1334
- session_id = EXCLUDED.session_id,
1335
- prompt_batch_id = EXCLUDED.prompt_batch_id,
1336
- content_hash = EXCLUDED.content_hash,
1337
- processed = EXCLUDED.processed,
1338
- updated_at = EXCLUDED.updated_at,
1339
- embedded = CASE
1340
- WHEN EXCLUDED.content_hash != plans.content_hash THEN 0
1341
- ELSE plans.embedded
1342
- END`
1343
- ).run(
1344
- data.id,
1345
- data.status ?? DEFAULT_STATUS2,
1346
- data.author ?? null,
1347
- data.title ?? null,
1348
- data.content ?? null,
1349
- data.source_path ?? null,
1350
- data.tags ?? null,
1351
- data.session_id ?? null,
1352
- data.prompt_batch_id ?? null,
1353
- data.content_hash ?? null,
1354
- data.processed ?? DEFAULT_PROCESSED,
1355
- data.created_at,
1356
- data.updated_at ?? null,
1357
- data.machine_id ?? getTeamMachineId()
1358
- );
1359
- const row = toPlanRow(
1360
- db.prepare(`SELECT ${SELECT_COLUMNS} FROM plans WHERE id = ?`).get(data.id)
1361
- );
1362
- syncRow("plans", row);
1363
- return row;
1364
- }
1365
- function listPlans(options = {}) {
1366
- const db = getDatabase();
1367
- const conditions = [];
1368
- const params = [];
1369
- if (options.status !== void 0) {
1370
- conditions.push(`status = ?`);
1371
- params.push(options.status);
1372
- }
1373
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1374
- const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
1375
- params.push(limit);
1376
- const rows = db.prepare(
1377
- `SELECT ${SELECT_COLUMNS}
1378
- FROM plans
1379
- ${where}
1380
- ORDER BY created_at DESC
1381
- LIMIT ?`
1382
- ).all(...params);
1383
- return rows.map(toPlanRow);
1384
- }
1385
- function listPlansBySession(sessionId) {
1386
- const db = getDatabase();
1387
- const rows = db.prepare(
1388
- `SELECT ${SELECT_COLUMNS}
1389
- FROM plans
1390
- WHERE session_id = ?
1391
- ORDER BY created_at DESC`
1392
- ).all(sessionId);
1393
- return rows.map(toPlanRow);
1394
- }
1395
-
1396
- // src/daemon/plan-capture.ts
1397
- var FILE_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "Create"]);
1398
- var HEADING_REGEX = /^#\s+(.+)$/m;
1399
- var PLAN_ID_HASH_LENGTH = 16;
1400
- function isInPlanDirectory(filePath, watchDirs, projectRoot) {
1401
- const abs = path9.isAbsolute(filePath) ? filePath : path9.resolve(projectRoot, filePath);
1402
- return watchDirs.some((dir) => {
1403
- const expanded = dir.startsWith("~/") ? path9.join(os6.homedir(), dir.slice(2)) : dir;
1404
- const absDir = path9.isAbsolute(expanded) ? expanded : path9.resolve(projectRoot, expanded);
1405
- const prefix = absDir.endsWith(path9.sep) ? absDir : absDir + path9.sep;
1406
- return abs === absDir || abs.startsWith(prefix);
1407
- });
1408
- }
1409
- function isPlanWriteEvent(toolName, toolInput, config) {
1410
- if (!FILE_WRITE_TOOLS.has(toolName)) return null;
1411
- const filePath = toolInput?.file_path ?? toolInput?.path;
1412
- if (typeof filePath !== "string") return null;
1413
- if (!isInPlanDirectory(filePath, config.watchDirs, config.projectRoot)) return null;
1414
- if (config.extensions?.length) {
1415
- const ext = path9.extname(filePath).toLowerCase();
1416
- if (!config.extensions.includes(ext)) return null;
1417
- }
1418
- return filePath;
1419
- }
1420
- function parsePlanTitle(content, filename) {
1421
- const match = HEADING_REGEX.exec(content);
1422
- if (match) return match[1].trim();
1423
- return filename ?? null;
1424
- }
1425
- function capturePlan(input) {
1426
- const now = Math.floor(Date.now() / 1e3);
1427
- const contentHash = createHash2(CONTENT_HASH_ALGORITHM).update(input.content).digest("hex");
1428
- const id = createHash2("md5").update(input.sourcePath).digest("hex").slice(0, PLAN_ID_HASH_LENGTH);
1429
- const title = parsePlanTitle(input.content, path9.basename(input.sourcePath));
1430
- return upsertPlan({
1431
- id,
1432
- title,
1433
- content: input.content,
1434
- source_path: input.sourcePath,
1435
- session_id: input.sessionId,
1436
- prompt_batch_id: input.promptBatchId ?? null,
1437
- content_hash: contentHash,
1438
- status: "active",
1439
- created_at: now,
1440
- updated_at: now
1441
- });
1442
- }
1443
-
1444
1272
  // src/daemon/api/config.ts
1445
1273
  function mergeConfigSections(current, incoming) {
1446
1274
  return {
@@ -1470,6 +1298,33 @@ async function handlePutConfig(vaultDir, body) {
1470
1298
  const updated = updateConfig(vaultDir, (current) => mergeConfigSections(current, result.data));
1471
1299
  return { body: updated };
1472
1300
  }
1301
+ function createPlanDirHandlers(deps) {
1302
+ const { vaultDir, symbiontPlanDirsByAgent, symbiontPlanDirs } = deps;
1303
+ async function handleGetPlanDirs(_req) {
1304
+ return {
1305
+ body: {
1306
+ symbiont: symbiontPlanDirsByAgent,
1307
+ custom: deps.planWatchConfig.watchDirs.filter((d) => !symbiontPlanDirs.includes(d))
1308
+ }
1309
+ };
1310
+ }
1311
+ async function handleUpdatePlanDirs(req) {
1312
+ const body = req.body;
1313
+ if (!Array.isArray(body.plan_dirs)) {
1314
+ return { status: 400, body: { error: "plan_dirs must be an array" } };
1315
+ }
1316
+ const updated = updateConfig(vaultDir, (cfg) => ({
1317
+ ...cfg,
1318
+ capture: { ...cfg.capture, plan_dirs: body.plan_dirs }
1319
+ }));
1320
+ deps.setPlanWatchConfig({
1321
+ ...deps.planWatchConfig,
1322
+ watchDirs: [.../* @__PURE__ */ new Set([...symbiontPlanDirs, ...body.plan_dirs])]
1323
+ });
1324
+ return { body: { custom: updated.capture.plan_dirs } };
1325
+ }
1326
+ return { handleGetPlanDirs, handleUpdatePlanDirs };
1327
+ }
1473
1328
 
1474
1329
  // src/db/queries/logs.ts
1475
1330
  var DEFAULT_PAGE_SIZE = 100;
@@ -1667,6 +1522,19 @@ async function handleLogDetail(req) {
1667
1522
  }
1668
1523
  };
1669
1524
  }
1525
+ var ExternalLogBody = external_exports.object({
1526
+ level: external_exports.enum(["debug", "info", "warn", "error"]),
1527
+ component: external_exports.string(),
1528
+ message: external_exports.string(),
1529
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
1530
+ });
1531
+ function createLogIngestionHandler(logger) {
1532
+ return async (req) => {
1533
+ const { level, component, message, data } = ExternalLogBody.parse(req.body);
1534
+ logger.log(level, LOG_KINDS.MCP_EVENT, message, { ...data, mcp_component: component });
1535
+ return { body: { ok: true } };
1536
+ };
1537
+ }
1670
1538
  function formatEntry(entry) {
1671
1539
  return {
1672
1540
  ...entry,
@@ -1705,7 +1573,7 @@ async function handleRestart(deps, body) {
1705
1573
  // src/daemon/update-checker.ts
1706
1574
  var import_yaml = __toESM(require_dist(), 1);
1707
1575
  import fs8 from "fs";
1708
- import path10 from "path";
1576
+ import path9 from "path";
1709
1577
  import semver from "semver";
1710
1578
  var REGISTRY_FETCH_TIMEOUT_MS = 1e4;
1711
1579
  function isUpdateExempt() {
@@ -1822,7 +1690,7 @@ async function checkForUpdate(currentVersion) {
1822
1690
  channel: config.channel
1823
1691
  };
1824
1692
  try {
1825
- fs8.mkdirSync(path10.dirname(UPDATE_CHECK_CACHE_PATH), { recursive: true });
1693
+ fs8.mkdirSync(path9.dirname(UPDATE_CHECK_CACHE_PATH), { recursive: true });
1826
1694
  fs8.writeFileSync(UPDATE_CHECK_CACHE_PATH, JSON.stringify(freshCache, null, 2), "utf-8");
1827
1695
  } catch {
1828
1696
  }
@@ -1837,8 +1705,8 @@ function statusFromCache(currentVersion, cache, config) {
1837
1705
 
1838
1706
  // src/daemon/update-installer.ts
1839
1707
  import fs9 from "fs";
1840
- import os7 from "os";
1841
- import path11 from "path";
1708
+ import os6 from "os";
1709
+ import path10 from "path";
1842
1710
  import { spawn as spawn2 } from "child_process";
1843
1711
  function generateUpdateScript(params) {
1844
1712
  const { targetVersion, projectRoot, vaultDir } = params;
@@ -1877,7 +1745,7 @@ rm -f "$0"
1877
1745
  function spawnUpdateScript(params) {
1878
1746
  fs9.mkdirSync(MYCO_GLOBAL_DIR, { recursive: true });
1879
1747
  const scriptName = `myco-update-${Date.now()}.sh`;
1880
- const scriptPath = path11.join(os7.tmpdir(), scriptName);
1748
+ const scriptPath = path10.join(os6.tmpdir(), scriptName);
1881
1749
  const script = generateUpdateScript(params);
1882
1750
  fs9.writeFileSync(scriptPath, script, { encoding: "utf-8", mode: 493 });
1883
1751
  const child = spawn2("/bin/sh", [scriptPath], {
@@ -1983,7 +1851,7 @@ function createUpdateHandlers(deps) {
1983
1851
 
1984
1852
  // src/daemon/backup.ts
1985
1853
  import fs10 from "fs";
1986
- import path12 from "path";
1854
+ import path11 from "path";
1987
1855
  var BACKUP_TABLES = [
1988
1856
  "sessions",
1989
1857
  "prompt_batches",
@@ -2027,7 +1895,7 @@ function createBackup(db, backupDir, machineId) {
2027
1895
  }
2028
1896
  lines.push("");
2029
1897
  }
2030
- const filePath = path12.join(backupDir, `${machineId}${BACKUP_EXTENSION}`);
1898
+ const filePath = path11.join(backupDir, `${machineId}${BACKUP_EXTENSION}`);
2031
1899
  fs10.writeFileSync(filePath, lines.join("\n"), "utf-8");
2032
1900
  return filePath;
2033
1901
  }
@@ -2041,7 +1909,7 @@ function listBackups(backupDir) {
2041
1909
  const backups = [];
2042
1910
  for (const entry of entries) {
2043
1911
  if (!entry.endsWith(BACKUP_EXTENSION)) continue;
2044
- const filePath = path12.join(backupDir, entry);
1912
+ const filePath = path11.join(backupDir, entry);
2045
1913
  const stat = fs10.statSync(filePath);
2046
1914
  backups.push({
2047
1915
  machine_id: entry.slice(0, -BACKUP_EXTENSION.length),
@@ -2138,6 +2006,7 @@ function restoreBackup(db, backupPath) {
2138
2006
  }
2139
2007
 
2140
2008
  // src/daemon/api/backup.ts
2009
+ import path12 from "path";
2141
2010
  function createBackupHandlers(deps) {
2142
2011
  async function handleCreateBackup(_req) {
2143
2012
  const filePath = createBackup(deps.db, deps.backupDir, deps.machineId);
@@ -2192,6 +2061,19 @@ function createBackupHandlers(deps) {
2192
2061
  handleRestore
2193
2062
  };
2194
2063
  }
2064
+ function createBackupConfigHandlers(deps) {
2065
+ const { vaultDir } = deps;
2066
+ async function handleGetBackupConfig() {
2067
+ const cfg = loadConfig(vaultDir);
2068
+ return { body: { dir: cfg.backup.dir ?? null, default_dir: path12.resolve(vaultDir, "backups") } };
2069
+ }
2070
+ async function handlePutBackupConfig(req) {
2071
+ const { dir } = req.body;
2072
+ updateBackupConfig(vaultDir, { dir: dir || void 0 });
2073
+ return { body: { dir: dir || null } };
2074
+ }
2075
+ return { handleGetBackupConfig, handlePutBackupConfig };
2076
+ }
2195
2077
 
2196
2078
  // src/daemon/team-sync.ts
2197
2079
  var TeamSyncClient = class {
@@ -2303,15 +2185,15 @@ var TeamSyncClient = class {
2303
2185
  "Content-Type": "application/json"
2304
2186
  };
2305
2187
  }
2306
- async request(method, path16, body) {
2307
- const res = await this.fetchFn(`${this.workerUrl}${path16}`, {
2188
+ async request(method, path21, body) {
2189
+ const res = await this.fetchFn(`${this.workerUrl}${path21}`, {
2308
2190
  method,
2309
2191
  headers: this.headers(),
2310
2192
  body: body !== void 0 ? JSON.stringify(body) : void 0
2311
2193
  });
2312
2194
  if (!res.ok) {
2313
2195
  const text = await res.text().catch(() => "");
2314
- throw new Error(`Team sync request ${method} ${path16} failed: ${res.status} ${text}`);
2196
+ throw new Error(`Team sync request ${method} ${path21} failed: ${res.status} ${text}`);
2315
2197
  }
2316
2198
  return res.json();
2317
2199
  }
@@ -2383,8 +2265,10 @@ function createTeamHandlers(deps) {
2383
2265
  }
2384
2266
  }
2385
2267
  let pendingCount = 0;
2268
+ let deadLetterCount = 0;
2386
2269
  try {
2387
2270
  pendingCount = countPending();
2271
+ deadLetterCount = countDeadLettered();
2388
2272
  } catch {
2389
2273
  }
2390
2274
  return {
@@ -2396,437 +2280,1013 @@ function createTeamHandlers(deps) {
2396
2280
  healthy,
2397
2281
  health_error: healthError,
2398
2282
  pending_sync_count: pendingCount,
2283
+ dead_letter_count: deadLetterCount,
2399
2284
  machine_id: machineId,
2400
2285
  package_version: getPluginVersion(),
2286
+ deployed_worker_version: config.team.deployed_worker_version ?? null,
2287
+ worker_update_available: config.team.enabled ? config.team.deployed_worker_version !== getPluginVersion() : false,
2401
2288
  schema_version: SCHEMA_VERSION,
2402
2289
  sync_protocol_version: SYNC_PROTOCOL_VERSION
2403
2290
  }
2404
2291
  };
2405
2292
  }
2406
- return { handleConnect, handleDisconnect, handleStatus };
2407
- }
2408
-
2409
- // src/daemon/api/progress.ts
2410
- import { randomUUID } from "crypto";
2411
- var MAX_CONCURRENT_OPERATIONS = 10;
2412
- var PROGRESS_TTL_MS = 5 * 60 * 1e3;
2413
- var ProgressTracker = class {
2414
- entries = /* @__PURE__ */ new Map();
2415
- /**
2416
- * Create a new tracked operation. Returns the existing token if an
2417
- * operation of the same type is already running (duplicate prevention).
2418
- * Throws if the maximum concurrent operations limit is reached.
2419
- */
2420
- /**
2421
- * Create a new tracked operation or return existing one.
2422
- * Returns `{ token, isNew }` — if `isNew` is false, the operation
2423
- * was already running and the caller should NOT launch it again.
2424
- * Throws if the maximum concurrent operations limit is reached.
2425
- */
2426
- create(type) {
2427
- this.cleanup();
2428
- for (const entry of this.entries.values()) {
2429
- if (entry.type === type && entry.status === "running") {
2430
- return { token: entry.token, isNew: false };
2293
+ async function handleBackfill(_req) {
2294
+ const count = backfillUnsynced(machineId);
2295
+ return { body: { enqueued: count } };
2296
+ }
2297
+ async function handleRetryFailed(_req) {
2298
+ const count = retryDeadLettered();
2299
+ return { body: { retried: count } };
2300
+ }
2301
+ async function handleUpgradeWorker(_req) {
2302
+ const { upgradeWorker } = await import("./team-TG5WZXWO.js");
2303
+ const result = upgradeWorker(vaultDir);
2304
+ if (!result.success) {
2305
+ return { status: 500, body: { error: result.error } };
2306
+ }
2307
+ if (result.worker_url && deps.getTeamClient()) {
2308
+ const secrets = readSecrets(vaultDir);
2309
+ const apiKey = secrets[TEAM_API_KEY_SECRET];
2310
+ if (apiKey) {
2311
+ deps.setTeamClient(new TeamSyncClient({
2312
+ workerUrl: result.worker_url,
2313
+ apiKey,
2314
+ machineId,
2315
+ syncProtocolVersion: SYNC_PROTOCOL_VERSION
2316
+ }));
2431
2317
  }
2432
2318
  }
2433
- const runningCount = [...this.entries.values()].filter((e) => e.status === "running").length;
2434
- if (runningCount >= MAX_CONCURRENT_OPERATIONS) {
2435
- throw new Error(`Maximum concurrent operations reached (${MAX_CONCURRENT_OPERATIONS})`);
2436
- }
2437
- const token = randomUUID();
2438
- const now = Date.now();
2439
- this.entries.set(token, {
2440
- token,
2441
- type,
2442
- status: "running",
2443
- created: now,
2444
- updated: now
2445
- });
2446
- return { token, isNew: true };
2447
- }
2448
- /**
2449
- * Update progress for a tracked operation.
2450
- */
2451
- update(token, data) {
2452
- const entry = this.entries.get(token);
2453
- if (!entry) return;
2454
- if (data.percent !== void 0) entry.percent = data.percent;
2455
- if (data.message !== void 0) entry.message = data.message;
2456
- if (data.status !== void 0) entry.status = data.status;
2457
- entry.updated = Date.now();
2458
- }
2459
- /**
2460
- * Get the current state of a tracked operation.
2461
- */
2462
- get(token) {
2463
- return this.entries.get(token);
2464
- }
2465
- /**
2466
- * Check whether any operations are currently running.
2467
- */
2468
- hasActiveOperations() {
2469
- for (const entry of this.entries.values()) {
2470
- if (entry.status === "running") return true;
2471
- }
2472
- return false;
2473
- }
2474
- /**
2475
- * Remove completed/failed entries older than PROGRESS_TTL_MS.
2476
- */
2477
- cleanup() {
2478
- const cutoff = Date.now() - PROGRESS_TTL_MS;
2479
- for (const [token, entry] of this.entries) {
2480
- if (entry.status !== "running" && entry.updated < cutoff) {
2481
- this.entries.delete(token);
2482
- }
2483
- }
2484
- }
2485
- };
2486
- async function handleGetProgress(tracker, token) {
2487
- const entry = tracker.get(token);
2488
- if (!entry) {
2489
- return { status: 404, body: { error: "not_found", message: "Progress token not found" } };
2319
+ return { body: result };
2490
2320
  }
2491
- return { body: entry };
2321
+ return { handleConnect, handleDisconnect, handleStatus, handleBackfill, handleRetryFailed, handleUpgradeWorker };
2492
2322
  }
2493
2323
 
2494
- // src/daemon/api/models.ts
2495
- var MODEL_LIST_TIMEOUT_MS = 5e3;
2496
- var ANTHROPIC_MODELS = [
2497
- "claude-opus-4-6",
2498
- "claude-sonnet-4-6",
2499
- "claude-haiku-4-5-20251001"
2500
- ];
2501
- var EMBEDDING_PATTERNS = [
2502
- "embed",
2503
- "bge-",
2504
- "nomic-embed",
2505
- "e5-",
2506
- "gte-",
2507
- "granite-embedding"
2508
- ];
2509
- function filterEmbeddingModels(models) {
2510
- return models.filter((m) => {
2511
- const name = m.toLowerCase();
2512
- return EMBEDDING_PATTERNS.some((p) => name.includes(p));
2513
- });
2514
- }
2515
- function filterLlmModels(models) {
2516
- return models.filter((m) => {
2517
- const name = m.toLowerCase();
2518
- return !EMBEDDING_PATTERNS.some((p) => name.includes(p));
2519
- });
2520
- }
2521
- async function handleGetModels(req) {
2522
- const provider = req.query.provider;
2523
- const type = req.query.type;
2524
- if (!provider) {
2525
- return { status: 400, body: { error: "provider query parameter required" } };
2526
- }
2527
- let models = [];
2528
- try {
2529
- if (provider === "ollama") {
2530
- const backend = new OllamaBackend({ base_url: req.query.base_url });
2531
- models = await backend.listModels(MODEL_LIST_TIMEOUT_MS);
2532
- } else if (provider === "lm-studio" || provider === "openai-compatible") {
2533
- const backend = new LmStudioBackend({ base_url: req.query.base_url });
2534
- models = await backend.listModels(MODEL_LIST_TIMEOUT_MS);
2535
- } else if (provider === "anthropic") {
2536
- models = ANTHROPIC_MODELS;
2537
- }
2538
- } catch {
2324
+ // src/daemon/api/session-lifecycle.ts
2325
+ var RegisterBody = external_exports.object({
2326
+ session_id: external_exports.string(),
2327
+ agent: external_exports.string().optional(),
2328
+ branch: external_exports.string().optional(),
2329
+ started_at: external_exports.string().optional()
2330
+ });
2331
+ var UnregisterBody = external_exports.object({ session_id: external_exports.string() });
2332
+ function createSessionLifecycleHandlers(deps) {
2333
+ const {
2334
+ registry,
2335
+ sessionBuffers,
2336
+ reconciler,
2337
+ stopProcessor,
2338
+ server,
2339
+ powerManager,
2340
+ machineId,
2341
+ logger,
2342
+ config,
2343
+ vaultDir
2344
+ } = deps;
2345
+ async function handleRegister(req) {
2346
+ powerManager.recordActivity();
2347
+ const { session_id, agent, branch, started_at } = RegisterBody.parse(req.body);
2348
+ const resolvedStartedAt = started_at ?? (/* @__PURE__ */ new Date()).toISOString();
2349
+ registry.register(session_id, { started_at: resolvedStartedAt, branch });
2350
+ server.updateDaemonJsonSessions(registry.sessions);
2351
+ const now = epochSeconds();
2352
+ const startedEpoch = Math.floor(new Date(resolvedStartedAt).getTime() / 1e3);
2353
+ upsertSession({
2354
+ id: session_id,
2355
+ agent: agent ?? "claude-code",
2356
+ user: null,
2357
+ project_root: process.cwd(),
2358
+ branch: branch ?? null,
2359
+ started_at: startedEpoch,
2360
+ created_at: now,
2361
+ status: "active",
2362
+ machine_id: machineId
2363
+ });
2364
+ updateSession(session_id, { ended_at: null, status: "active" });
2365
+ reconciler.reconcileSession(session_id);
2366
+ logger.info(LOG_KINDS.LIFECYCLE_REGISTER, "Session registered", { session_id, branch, started_at: started_at ?? null });
2367
+ notify(vaultDir, {
2368
+ domain: "sessions",
2369
+ type: "session.started",
2370
+ title: "Session started",
2371
+ message: branch ? `Branch: ${branch}` : void 0,
2372
+ link: `/sessions/${session_id}`,
2373
+ metadata: { sessionId: session_id, agent: agent ?? "claude-code", branch }
2374
+ }, config);
2375
+ return { body: { ok: true, sessions: registry.sessions } };
2539
2376
  }
2540
- if (type === "embedding") {
2541
- models = filterEmbeddingModels(models);
2542
- } else if (type === "llm") {
2543
- models = filterLlmModels(models);
2377
+ async function handleUnregister(req) {
2378
+ const { session_id } = UnregisterBody.parse(req.body);
2379
+ registry.unregister(session_id);
2380
+ const bufferDir = `${vaultDir}/buffer`;
2381
+ cleanStaleBuffers(bufferDir, STALE_BUFFER_MAX_AGE_MS, session_id);
2382
+ closeSession(session_id, epochSeconds());
2383
+ sessionBuffers.delete(session_id);
2384
+ stopProcessor.clearSession(session_id);
2385
+ reconciler.clearSession(session_id);
2386
+ server.updateDaemonJsonSessions(registry.sessions);
2387
+ logger.info(LOG_KINDS.LIFECYCLE_UNREGISTER, "Session unregistered", { session_id });
2388
+ notify(vaultDir, {
2389
+ domain: "sessions",
2390
+ type: "session.ended",
2391
+ title: "Session ended",
2392
+ link: `/sessions/${session_id}`,
2393
+ metadata: { sessionId: session_id }
2394
+ }, config);
2395
+ return { body: { ok: true, sessions: registry.sessions } };
2544
2396
  }
2545
- return { body: { provider, models } };
2397
+ return { handleRegister, handleUnregister };
2546
2398
  }
2547
2399
 
2548
- // src/daemon/api/stats.ts
2549
- import { createHash as createHash3 } from "crypto";
2400
+ // src/daemon/api/skills.ts
2550
2401
  import fs11 from "fs";
2551
2402
  import path13 from "path";
2552
- function computeConfigHash(vaultDir) {
2553
- try {
2554
- const configPath = path13.join(vaultDir, CONFIG_FILENAME);
2555
- const raw = fs11.readFileSync(configPath, "utf-8");
2556
- return createHash3("md5").update(raw).digest("hex");
2557
- } catch {
2558
- return "";
2559
- }
2560
- }
2561
2403
 
2562
- // src/db/queries/activities.ts
2563
- var DEFAULT_SUCCESS = 1;
2564
- var DEFAULT_PROCESSED2 = 0;
2565
- var ACTIVITY_COLUMNS = [
2404
+ // src/db/queries/skill-usage.ts
2405
+ var USAGE_COLUMNS = [
2566
2406
  "id",
2407
+ "skill_id",
2567
2408
  "session_id",
2568
- "prompt_batch_id",
2569
- "tool_name",
2570
- "tool_input",
2571
- "tool_output_summary",
2572
- "file_path",
2573
- "files_affected",
2574
- "duration_ms",
2575
- "success",
2576
- "error_message",
2577
- "timestamp",
2578
- "processed",
2579
- "content_hash",
2580
- "created_at"
2409
+ "machine_id",
2410
+ "detected_at"
2581
2411
  ];
2582
- var SELECT_COLUMNS2 = ACTIVITY_COLUMNS.join(", ");
2583
- function toActivityRow(row) {
2412
+ var SELECT_COLUMNS = USAGE_COLUMNS.join(", ");
2413
+ function toUsageRow(row) {
2584
2414
  return {
2585
2415
  id: row.id,
2416
+ skill_id: row.skill_id,
2586
2417
  session_id: row.session_id,
2587
- prompt_batch_id: row.prompt_batch_id ?? null,
2588
- tool_name: row.tool_name,
2589
- tool_input: row.tool_input ?? null,
2590
- tool_output_summary: row.tool_output_summary ?? null,
2591
- file_path: row.file_path ?? null,
2592
- files_affected: row.files_affected ?? null,
2593
- duration_ms: row.duration_ms ?? null,
2594
- success: row.success,
2595
- error_message: row.error_message ?? null,
2596
- timestamp: row.timestamp,
2597
- processed: row.processed,
2598
- content_hash: row.content_hash ?? null,
2599
- created_at: row.created_at
2418
+ machine_id: row.machine_id ?? getTeamMachineId(),
2419
+ detected_at: row.detected_at
2600
2420
  };
2601
2421
  }
2602
- function insertActivityWithBatch(data) {
2422
+ function insertSkillUsage(data) {
2603
2423
  const db = getDatabase();
2604
- const info = db.prepare(
2605
- `INSERT INTO activities (
2606
- session_id, prompt_batch_id, tool_name, tool_input,
2607
- tool_output_summary, file_path, files_affected, duration_ms,
2608
- success, error_message, timestamp, processed,
2609
- content_hash, created_at
2424
+ db.prepare(
2425
+ `INSERT INTO skill_usage (
2426
+ id, skill_id, session_id, machine_id, detected_at
2610
2427
  ) VALUES (
2611
- ?,
2612
- (SELECT id FROM prompt_batches WHERE session_id = ? AND ended_at IS NULL ORDER BY id DESC LIMIT 1),
2613
- ?, ?,
2614
- ?, ?, ?, ?,
2615
- ?, ?, ?, ?,
2616
- ?, ?
2428
+ ?, ?, ?, ?, ?
2617
2429
  )`
2618
2430
  ).run(
2431
+ data.id,
2432
+ data.skill_id,
2619
2433
  data.session_id,
2620
- data.session_id,
2621
- data.tool_name,
2622
- data.tool_input ?? null,
2623
- data.tool_output_summary ?? null,
2624
- data.file_path ?? null,
2625
- data.files_affected ?? null,
2626
- data.duration_ms ?? null,
2627
- data.success ?? DEFAULT_SUCCESS,
2628
- data.error_message ?? null,
2629
- data.timestamp,
2630
- DEFAULT_PROCESSED2,
2631
- data.content_hash ?? null,
2632
- data.created_at
2434
+ data.machine_id ?? getTeamMachineId(),
2435
+ data.detected_at
2633
2436
  );
2634
- const activityId = Number(info.lastInsertRowid);
2635
- const toolName = data.tool_name;
2636
- const toolInput = data.tool_input ?? null;
2637
- const filePath = data.file_path ?? null;
2638
- if (toolName || toolInput || filePath) {
2639
- db.prepare(
2640
- "INSERT INTO activities_fts(rowid, tool_name, tool_input, file_path) VALUES (?, ?, ?, ?)"
2641
- ).run(activityId, toolName ?? "", toolInput ?? "", filePath ?? "");
2642
- }
2643
- return toActivityRow(
2644
- db.prepare(`SELECT ${SELECT_COLUMNS2} FROM activities WHERE id = ?`).get(activityId)
2437
+ const row = toUsageRow(
2438
+ db.prepare(`SELECT ${SELECT_COLUMNS} FROM skill_usage WHERE id = ?`).get(data.id)
2645
2439
  );
2440
+ return row;
2646
2441
  }
2647
- function listActivitiesByBatch(batchId) {
2648
- const db = getDatabase();
2649
- const rows = db.prepare(
2650
- `SELECT ${SELECT_COLUMNS2}
2651
- FROM activities
2652
- WHERE prompt_batch_id = ?
2653
- ORDER BY timestamp ASC`
2654
- ).all(batchId);
2655
- return rows.map(toActivityRow);
2656
- }
2657
- function countActivities(sessionId) {
2442
+ function hasUsageForSkillAndSession(skillId, sessionId) {
2658
2443
  const db = getDatabase();
2659
2444
  const row = db.prepare(
2660
- `SELECT COUNT(*) AS count FROM activities WHERE session_id = ?`
2661
- ).get(sessionId);
2662
- return row.count;
2663
- }
2664
-
2665
- // src/db/queries/attachments.ts
2666
- var ATTACHMENT_COLUMNS = [
2667
- "id",
2668
- "session_id",
2669
- "prompt_batch_id",
2670
- "file_path",
2671
- "media_type",
2672
- "description",
2673
- "data",
2674
- "content_hash",
2675
- "created_at"
2676
- ];
2677
- var ATTACHMENT_LIST_COLUMNS = [
2678
- "id",
2679
- "session_id",
2680
- "prompt_batch_id",
2681
- "file_path",
2682
- "media_type",
2683
- "description",
2684
- "content_hash",
2685
- "created_at"
2686
- ];
2687
- var SELECT_COLUMNS3 = ATTACHMENT_COLUMNS.join(", ");
2688
- var SELECT_LIST_COLUMNS = ATTACHMENT_LIST_COLUMNS.join(", ");
2689
- function toAttachmentBase(row) {
2690
- return {
2691
- id: row.id,
2692
- session_id: row.session_id,
2693
- prompt_batch_id: row.prompt_batch_id ?? null,
2694
- file_path: row.file_path,
2695
- media_type: row.media_type ?? null,
2696
- description: row.description ?? null,
2697
- content_hash: row.content_hash ?? null,
2698
- created_at: row.created_at
2699
- };
2700
- }
2701
- function toAttachmentRow(row) {
2702
- return { ...toAttachmentBase(row), data: row.data ?? null };
2703
- }
2704
- function toAttachmentListRow(row) {
2705
- return toAttachmentBase(row);
2706
- }
2707
- function insertAttachment(data) {
2708
- const db = getDatabase();
2709
- const info = db.prepare(
2710
- `INSERT INTO attachments (${SELECT_COLUMNS3})
2711
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2712
- ON CONFLICT (id) DO NOTHING`
2713
- ).run(
2714
- data.id,
2715
- data.session_id,
2716
- data.prompt_batch_id ?? null,
2717
- data.file_path,
2718
- data.media_type ?? null,
2719
- data.description ?? null,
2720
- data.data ?? null,
2721
- data.content_hash ?? null,
2722
- data.created_at
2723
- );
2724
- if (info.changes === 0) return void 0;
2725
- return toAttachmentRow(
2726
- db.prepare(`SELECT ${SELECT_COLUMNS3} FROM attachments WHERE id = ?`).get(data.id)
2727
- );
2728
- }
2729
- function listAttachmentsBySession(sessionId) {
2730
- const db = getDatabase();
2731
- const rows = db.prepare(
2732
- `SELECT ${SELECT_LIST_COLUMNS} FROM attachments WHERE session_id = ? ORDER BY created_at ASC`
2733
- ).all(sessionId);
2734
- return rows.map(toAttachmentListRow);
2445
+ `SELECT 1 FROM skill_usage WHERE skill_id = ? AND session_id = ? LIMIT 1`
2446
+ ).get(skillId, sessionId);
2447
+ return row !== void 0;
2735
2448
  }
2736
- function getAttachmentByFilePath(filePath) {
2449
+ function countUsageForSkill(skillId) {
2737
2450
  const db = getDatabase();
2738
2451
  const row = db.prepare(
2739
- `SELECT ${SELECT_COLUMNS3} FROM attachments WHERE file_path = ? LIMIT 1`
2740
- ).get(filePath);
2741
- return row ? toAttachmentRow(row) : null;
2452
+ `SELECT COUNT(*) as count FROM skill_usage WHERE skill_id = ?`
2453
+ ).get(skillId);
2454
+ return row.count;
2742
2455
  }
2743
2456
 
2744
- // src/daemon/api/sessions.ts
2745
- var DEFAULT_LIST_LIMIT3 = 50;
2457
+ // src/daemon/api/skills.ts
2746
2458
  var DEFAULT_LIST_OFFSET = 0;
2747
- async function handleListSessions(req) {
2748
- const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT3;
2749
- const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET;
2459
+ async function handleListCandidates(req) {
2750
2460
  const status = req.query.status || void 0;
2751
- const agent = req.query.agent || void 0;
2752
- const search = req.query.search || void 0;
2753
- const filterOpts = { status, agent, search };
2754
- const sessions = listSessions({ ...filterOpts, limit, offset }).map((s) => ({
2755
- id: s.id,
2756
- date: new Date(s.started_at * 1e3).toISOString().slice(0, 10),
2757
- title: s.title || s.id.slice(0, 8),
2758
- status: s.status,
2759
- agent: s.agent,
2760
- prompt_count: s.prompt_count,
2761
- tool_count: s.tool_count,
2762
- started_at: s.started_at,
2763
- ended_at: s.ended_at
2764
- }));
2765
- const total = countSessions(filterOpts);
2766
- return { body: { sessions, total, offset, limit } };
2461
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT;
2462
+ const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET;
2463
+ const { items: candidates, total } = listCandidatesWithCount({ status, limit, offset });
2464
+ return { status: 200, body: { candidates, total } };
2767
2465
  }
2768
- async function handleGetSession(req) {
2769
- const session = getSession(req.params.id);
2770
- if (!session) return { status: 404, body: { error: "not_found" } };
2771
- const promptCount = countBatchesBySession(session.id);
2772
- const toolCount = countActivities(session.id);
2773
- return { body: { ...session, prompt_count: promptCount, tool_count: toolCount } };
2466
+ async function handleGetCandidate(req) {
2467
+ const candidate = getCandidate(req.params.id);
2468
+ if (!candidate) {
2469
+ return { status: 404, body: { error: `Not found: ${req.params.id}` } };
2470
+ }
2471
+ return { status: 200, body: { candidate } };
2774
2472
  }
2775
- async function handleGetSessionBatches(req) {
2776
- const batches = listBatchesBySession(req.params.id);
2777
- return { body: batches };
2473
+ async function handleUpdateCandidate(req) {
2474
+ const id = req.params.id;
2475
+ const body = req.body;
2476
+ if (!body) return { status: 400, body: { error: "Request body required" } };
2477
+ const { status, topic, rationale, confidence, source_ids, skill_id } = body;
2478
+ const updated = updateCandidate(id, {
2479
+ ...status !== void 0 ? { status } : {},
2480
+ ...topic !== void 0 ? { topic } : {},
2481
+ ...rationale !== void 0 ? { rationale } : {},
2482
+ ...confidence !== void 0 ? { confidence } : {},
2483
+ ...source_ids !== void 0 ? { source_ids } : {},
2484
+ ...skill_id !== void 0 ? { skill_id } : {},
2485
+ updated_at: epochSeconds()
2486
+ });
2487
+ if (!updated) return { status: 404, body: { error: `Candidate not found: ${id}` } };
2488
+ return { status: 200, body: { candidate: updated } };
2778
2489
  }
2779
- async function handleGetBatchActivities(req) {
2780
- const batchId = Number(req.params.id);
2781
- if (isNaN(batchId)) return { status: 400, body: { error: "invalid_batch_id" } };
2782
- const activities = listActivitiesByBatch(batchId);
2783
- return { body: activities };
2490
+ async function handleListSkillRecords(req) {
2491
+ const status = req.query.status || void 0;
2492
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT;
2493
+ const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET;
2494
+ const { items: records, total } = listSkillRecordsWithCount({ status, limit, offset });
2495
+ return { status: 200, body: { records, total } };
2784
2496
  }
2785
- async function handleGetSessionAttachments(req) {
2786
- const attachments = listAttachmentsBySession(req.params.id);
2787
- return { body: attachments };
2497
+ async function handleGetSkillRecord(req) {
2498
+ const idOrName = req.params.id;
2499
+ const record = getSkillRecord(idOrName) ?? getSkillRecordByName(idOrName);
2500
+ if (!record) {
2501
+ return { status: 404, body: { error: `Not found: ${idOrName}` } };
2502
+ }
2503
+ const lineage = listLineageForSkill(record.id);
2504
+ const usage_total = countUsageForSkill(record.id);
2505
+ const latestSnapshot = lineage[0]?.content_snapshot;
2506
+ const frontmatterFields = {};
2507
+ if (latestSnapshot) {
2508
+ const fmMatch = latestSnapshot.match(/^---\n([\s\S]*?)\n---/);
2509
+ if (fmMatch) {
2510
+ for (const line of fmMatch[1].split("\n")) {
2511
+ const colonIdx = line.indexOf(":");
2512
+ if (colonIdx > 0) {
2513
+ const key = line.slice(0, colonIdx).trim();
2514
+ const val = line.slice(colonIdx + 1).trim();
2515
+ if (key && val) frontmatterFields[key] = val;
2516
+ }
2517
+ }
2518
+ }
2519
+ }
2520
+ return { status: 200, body: { ...record, lineage, usage_total, frontmatter: frontmatterFields } };
2788
2521
  }
2789
- async function handleGetSessionPlans(req) {
2790
- const plans = listPlansBySession(req.params.id);
2791
- return { body: plans };
2522
+ async function handleDeleteCandidate(req) {
2523
+ const id = req.params.id;
2524
+ const deleted = deleteCandidate(id);
2525
+ if (!deleted) return { status: 404, body: { error: `Not found: ${id}` } };
2526
+ return { status: 200, body: { deleted: true, id } };
2792
2527
  }
2793
-
2794
- // src/daemon/api/mycelium.ts
2795
- var DEFAULT_LIST_LIMIT4 = 50;
2796
- var DEFAULT_LIST_OFFSET2 = 0;
2797
- var DEFAULT_GRAPH_DEPTH = 1;
2798
- var MAX_GRAPH_DEPTH = 3;
2799
- var SPORE_NAME_PREVIEW_CHARS = 60;
2800
- var EXCLUDED_GRAPH_EDGE_TYPES = /* @__PURE__ */ new Set(["HAS_BATCH", "EXTRACTED_FROM"]);
2801
- async function handleListSpores(req) {
2802
- const agentId = req.query.agent_id;
2803
- const type = req.query.type;
2804
- const status = req.query.status;
2805
- const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT4;
2806
- const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET2;
2807
- const search = req.query.search || void 0;
2808
- const filterOpts = {
2809
- ...agentId ? { agent_id: agentId } : {},
2810
- observation_type: type,
2811
- status,
2812
- search
2813
- };
2814
- const spores = listSpores({ ...filterOpts, limit, offset });
2815
- const total = countSpores(filterOpts);
2816
- return { body: { spores, total, offset, limit } };
2528
+ async function handleDeleteSkillRecord(req) {
2529
+ const idOrName = req.params.id;
2530
+ const result = deleteSkillRecordCascade(idOrName);
2531
+ if (!result) return { status: 404, body: { error: `Not found: ${idOrName}` } };
2532
+ if (isTeamSyncEnabled()) {
2533
+ try {
2534
+ enqueueOutbox({
2535
+ table_name: "skill_records",
2536
+ row_id: result.id,
2537
+ operation: "delete",
2538
+ payload: JSON.stringify({ id: result.id, name: result.name }),
2539
+ machine_id: getTeamMachineId(),
2540
+ created_at: epochSeconds()
2541
+ });
2542
+ } catch (err) {
2543
+ console.warn("[team-sync] Failed to enqueue skill record deletion:", err instanceof Error ? err.message : err);
2544
+ }
2545
+ }
2546
+ return { status: 200, body: { deleted: true, id: result.id, name: result.name } };
2817
2547
  }
2818
- async function handleGetSpore(req) {
2819
- const spore = getSpore(req.params.id);
2820
- if (!spore) return { status: 404, body: { error: "not_found" } };
2821
- return { body: spore };
2548
+ function createSkillRecordDeleteHandler(deps) {
2549
+ const { vaultDir, logger } = deps;
2550
+ return async function handleDeleteSkillRecordWithCleanup(req) {
2551
+ const result = await handleDeleteSkillRecord(req);
2552
+ if (result.body?.deleted) {
2553
+ const record = result.body;
2554
+ if (record.name) {
2555
+ const projectRoot = path13.resolve(vaultDir, "..");
2556
+ const skillDir = path13.resolve(projectRoot, ".agents", "skills", record.name);
2557
+ try {
2558
+ fs11.rmSync(skillDir, { recursive: true, force: true });
2559
+ } catch (err) {
2560
+ logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to remove skill directory", { name: record.name, error: String(err) });
2561
+ }
2562
+ try {
2563
+ const { syncSkillSymlinks } = await import("./installer-BTUNKWOU.js");
2564
+ syncSkillSymlinks(projectRoot, record.name, { remove: true });
2565
+ } catch (err) {
2566
+ logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to remove skill symlinks", { name: record.name, error: String(err) });
2567
+ }
2568
+ }
2569
+ }
2570
+ return result;
2571
+ };
2822
2572
  }
2823
- async function handleListEntities(req) {
2824
- const agentId = req.query.agent_id ?? DEFAULT_AGENT_ID;
2573
+
2574
+ // src/daemon/team-sync-init.ts
2575
+ function initTeamSync(deps) {
2576
+ const { config, machineId, logger, vaultDir, serverVersion } = deps;
2577
+ let teamClient = null;
2578
+ if (config.team.enabled && config.team.worker_url) {
2579
+ const secrets = readSecrets(vaultDir);
2580
+ const teamApiKey = secrets[TEAM_API_KEY_SECRET];
2581
+ if (teamApiKey) {
2582
+ teamClient = new TeamSyncClient({
2583
+ workerUrl: config.team.worker_url,
2584
+ apiKey: teamApiKey,
2585
+ machineId,
2586
+ syncProtocolVersion: SYNC_PROTOCOL_VERSION
2587
+ });
2588
+ logger.info(LOG_KINDS.TEAM_SYNC_START, "Team sync client initialized", { worker_url: config.team.worker_url });
2589
+ teamClient.connect({
2590
+ machine_id: machineId,
2591
+ version: serverVersion
2592
+ }).then(() => {
2593
+ logger.info(LOG_KINDS.TEAM_SYNC_START, "Node registered with team worker");
2594
+ }).catch((err) => {
2595
+ logger.warn(LOG_KINDS.TEAM_SYNC_ERROR, "Node registration failed (will retry on next flush)", { error: err.message });
2596
+ });
2597
+ setTimeout(() => {
2598
+ try {
2599
+ const backfilled = backfillUnsynced(machineId);
2600
+ if (backfilled > 0) {
2601
+ logger.info(LOG_KINDS.TEAM_SYNC_START, `Backfilled ${backfilled} unsynced records into outbox`);
2602
+ }
2603
+ } catch (err) {
2604
+ logger.error(LOG_KINDS.TEAM_SYNC_ERROR, "Backfill failed", { error: err.message });
2605
+ }
2606
+ }, 0);
2607
+ }
2608
+ }
2609
+ return {
2610
+ getTeamClient: () => teamClient,
2611
+ setTeamClient: (client) => {
2612
+ teamClient = client;
2613
+ },
2614
+ registerFlushJob: (powerManager) => {
2615
+ if (!config.team.enabled) return;
2616
+ const logDeadLettered = (ids) => {
2617
+ if (ids.length > 0) {
2618
+ logger.error(LOG_KINDS.TEAM_SYNC_DEAD_LETTER, `Dead-lettered ${ids.length} records after max retries`, { ids });
2619
+ }
2620
+ };
2621
+ powerManager.register({
2622
+ name: "team-sync-flush",
2623
+ runIn: ["active", "idle", "sleep"],
2624
+ preventsDeepSleep: () => countPending() > 0,
2625
+ fn: async () => {
2626
+ const client = teamClient;
2627
+ if (!client) return;
2628
+ const pending = listPending();
2629
+ if (pending.length === 0) return;
2630
+ try {
2631
+ logger.info(LOG_KINDS.TEAM_SYNC_START, "Flushing outbox", { count: pending.length });
2632
+ const result = await client.pushBatch(pending);
2633
+ const now = epochSeconds();
2634
+ const failedIds = new Set(result.errors.map((e) => e.id));
2635
+ const sentRecords = pending.filter((r) => !failedIds.has(String(r.row_id)));
2636
+ const sentIds = sentRecords.map((r) => r.id);
2637
+ if (sentIds.length > 0) {
2638
+ markSent(sentIds, now);
2639
+ markSourceRowsSynced(sentRecords, now);
2640
+ }
2641
+ if (result.errors.length > 0) {
2642
+ const failedOutboxIds = pending.filter((r) => failedIds.has(String(r.row_id))).map((r) => r.id);
2643
+ const deadLettered = incrementRetryCount(failedOutboxIds, now);
2644
+ logger.warn(LOG_KINDS.TEAM_SYNC_RETRY, `Retrying ${failedOutboxIds.length} records`, {
2645
+ errors: result.errors.slice(0, 5)
2646
+ });
2647
+ logDeadLettered(deadLettered);
2648
+ }
2649
+ pruneOld();
2650
+ logger.info(LOG_KINDS.TEAM_SYNC_COMPLETE, "Outbox flush complete", {
2651
+ synced: result.synced,
2652
+ skipped: result.skipped,
2653
+ errors: result.errors.length,
2654
+ total: pending.length
2655
+ });
2656
+ } catch (err) {
2657
+ try {
2658
+ const now = epochSeconds();
2659
+ const allIds = pending.map((r) => r.id);
2660
+ const deadLettered = incrementRetryCount(allIds, now);
2661
+ logger.warn(LOG_KINDS.TEAM_SYNC_RETRY, `Batch failed, retrying ${allIds.length} records`, {
2662
+ error: err.message
2663
+ });
2664
+ logDeadLettered(deadLettered);
2665
+ } catch {
2666
+ }
2667
+ logger.error(LOG_KINDS.TEAM_SYNC_ERROR, "Outbox flush failed", { error: err.message });
2668
+ }
2669
+ }
2670
+ });
2671
+ }
2672
+ };
2673
+ }
2674
+
2675
+ // src/daemon/api/progress.ts
2676
+ import { randomUUID } from "crypto";
2677
+ var MAX_CONCURRENT_OPERATIONS = 10;
2678
+ var PROGRESS_TTL_MS = 5 * 60 * 1e3;
2679
+ var ProgressTracker = class {
2680
+ entries = /* @__PURE__ */ new Map();
2681
+ /**
2682
+ * Create a new tracked operation. Returns the existing token if an
2683
+ * operation of the same type is already running (duplicate prevention).
2684
+ * Throws if the maximum concurrent operations limit is reached.
2685
+ */
2686
+ /**
2687
+ * Create a new tracked operation or return existing one.
2688
+ * Returns `{ token, isNew }` — if `isNew` is false, the operation
2689
+ * was already running and the caller should NOT launch it again.
2690
+ * Throws if the maximum concurrent operations limit is reached.
2691
+ */
2692
+ create(type) {
2693
+ this.cleanup();
2694
+ for (const entry of this.entries.values()) {
2695
+ if (entry.type === type && entry.status === "running") {
2696
+ return { token: entry.token, isNew: false };
2697
+ }
2698
+ }
2699
+ const runningCount = [...this.entries.values()].filter((e) => e.status === "running").length;
2700
+ if (runningCount >= MAX_CONCURRENT_OPERATIONS) {
2701
+ throw new Error(`Maximum concurrent operations reached (${MAX_CONCURRENT_OPERATIONS})`);
2702
+ }
2703
+ const token = randomUUID();
2704
+ const now = Date.now();
2705
+ this.entries.set(token, {
2706
+ token,
2707
+ type,
2708
+ status: "running",
2709
+ created: now,
2710
+ updated: now
2711
+ });
2712
+ return { token, isNew: true };
2713
+ }
2714
+ /**
2715
+ * Update progress for a tracked operation.
2716
+ */
2717
+ update(token, data) {
2718
+ const entry = this.entries.get(token);
2719
+ if (!entry) return;
2720
+ if (data.percent !== void 0) entry.percent = data.percent;
2721
+ if (data.message !== void 0) entry.message = data.message;
2722
+ if (data.status !== void 0) entry.status = data.status;
2723
+ entry.updated = Date.now();
2724
+ }
2725
+ /**
2726
+ * Get the current state of a tracked operation.
2727
+ */
2728
+ get(token) {
2729
+ return this.entries.get(token);
2730
+ }
2731
+ /**
2732
+ * Check whether any operations are currently running.
2733
+ */
2734
+ hasActiveOperations() {
2735
+ for (const entry of this.entries.values()) {
2736
+ if (entry.status === "running") return true;
2737
+ }
2738
+ return false;
2739
+ }
2740
+ /**
2741
+ * Remove completed/failed entries older than PROGRESS_TTL_MS.
2742
+ */
2743
+ cleanup() {
2744
+ const cutoff = Date.now() - PROGRESS_TTL_MS;
2745
+ for (const [token, entry] of this.entries) {
2746
+ if (entry.status !== "running" && entry.updated < cutoff) {
2747
+ this.entries.delete(token);
2748
+ }
2749
+ }
2750
+ }
2751
+ };
2752
+ async function handleGetProgress(tracker, token) {
2753
+ const entry = tracker.get(token);
2754
+ if (!entry) {
2755
+ return { status: 404, body: { error: "not_found", message: "Progress token not found" } };
2756
+ }
2757
+ return { body: entry };
2758
+ }
2759
+
2760
+ // src/daemon/api/models.ts
2761
+ var MODEL_LIST_TIMEOUT_MS = 5e3;
2762
+ var ANTHROPIC_MODELS = [
2763
+ "claude-opus-4-6",
2764
+ "claude-sonnet-4-6",
2765
+ "claude-haiku-4-5-20251001"
2766
+ ];
2767
+ var EMBEDDING_PATTERNS = [
2768
+ "embed",
2769
+ "bge-",
2770
+ "nomic-embed",
2771
+ "e5-",
2772
+ "gte-",
2773
+ "granite-embedding"
2774
+ ];
2775
+ function filterEmbeddingModels(models) {
2776
+ return models.filter((m) => {
2777
+ const name = m.toLowerCase();
2778
+ return EMBEDDING_PATTERNS.some((p) => name.includes(p));
2779
+ });
2780
+ }
2781
+ function filterLlmModels(models) {
2782
+ return models.filter((m) => {
2783
+ const name = m.toLowerCase();
2784
+ return !EMBEDDING_PATTERNS.some((p) => name.includes(p));
2785
+ });
2786
+ }
2787
+ async function handleGetModels(req) {
2788
+ const provider = req.query.provider;
2789
+ const type = req.query.type;
2790
+ if (!provider) {
2791
+ return { status: 400, body: { error: "provider query parameter required" } };
2792
+ }
2793
+ let models = [];
2794
+ try {
2795
+ if (provider === "ollama") {
2796
+ const backend = new OllamaBackend({ base_url: req.query.base_url });
2797
+ models = await backend.listModels(MODEL_LIST_TIMEOUT_MS);
2798
+ } else if (provider === "lm-studio" || provider === "openai-compatible") {
2799
+ const backend = new LmStudioBackend({ base_url: req.query.base_url });
2800
+ models = await backend.listModels(MODEL_LIST_TIMEOUT_MS);
2801
+ } else if (provider === "anthropic") {
2802
+ models = ANTHROPIC_MODELS;
2803
+ }
2804
+ } catch {
2805
+ }
2806
+ if (type === "embedding") {
2807
+ models = filterEmbeddingModels(models);
2808
+ } else if (type === "llm") {
2809
+ models = filterLlmModels(models);
2810
+ }
2811
+ return { body: { provider, models } };
2812
+ }
2813
+
2814
+ // src/daemon/api/stats.ts
2815
+ import { createHash as createHash2 } from "crypto";
2816
+ import fs12 from "fs";
2817
+ import path14 from "path";
2818
+ function computeConfigHash(vaultDir) {
2819
+ try {
2820
+ const configPath = path14.join(vaultDir, CONFIG_FILENAME);
2821
+ const raw = fs12.readFileSync(configPath, "utf-8");
2822
+ return createHash2("md5").update(raw).digest("hex");
2823
+ } catch {
2824
+ return "";
2825
+ }
2826
+ }
2827
+ function createLiveStatsHandler(deps) {
2828
+ return async () => {
2829
+ const stats = gatherStats(deps.vaultDir, { active_sessions: deps.registry.sessions });
2830
+ stats.daemon.pid = process.pid;
2831
+ stats.daemon.port = deps.server.port;
2832
+ stats.daemon.version = deps.server.version;
2833
+ stats.daemon.uptime_seconds = Math.floor(process.uptime());
2834
+ return { body: { ...stats, config_hash: deps.configHash.get() } };
2835
+ };
2836
+ }
2837
+
2838
+ // src/db/queries/activities.ts
2839
+ var DEFAULT_SUCCESS = 1;
2840
+ var DEFAULT_PROCESSED = 0;
2841
+ var ACTIVITY_COLUMNS = [
2842
+ "id",
2843
+ "session_id",
2844
+ "prompt_batch_id",
2845
+ "tool_name",
2846
+ "tool_input",
2847
+ "tool_output_summary",
2848
+ "file_path",
2849
+ "files_affected",
2850
+ "duration_ms",
2851
+ "success",
2852
+ "error_message",
2853
+ "timestamp",
2854
+ "processed",
2855
+ "content_hash",
2856
+ "created_at"
2857
+ ];
2858
+ var SELECT_COLUMNS2 = ACTIVITY_COLUMNS.join(", ");
2859
+ function toActivityRow(row) {
2860
+ return {
2861
+ id: row.id,
2862
+ session_id: row.session_id,
2863
+ prompt_batch_id: row.prompt_batch_id ?? null,
2864
+ tool_name: row.tool_name,
2865
+ tool_input: row.tool_input ?? null,
2866
+ tool_output_summary: row.tool_output_summary ?? null,
2867
+ file_path: row.file_path ?? null,
2868
+ files_affected: row.files_affected ?? null,
2869
+ duration_ms: row.duration_ms ?? null,
2870
+ success: row.success,
2871
+ error_message: row.error_message ?? null,
2872
+ timestamp: row.timestamp,
2873
+ processed: row.processed,
2874
+ content_hash: row.content_hash ?? null,
2875
+ created_at: row.created_at
2876
+ };
2877
+ }
2878
+ function insertActivityWithBatch(data) {
2879
+ const db = getDatabase();
2880
+ const info = db.prepare(
2881
+ `INSERT INTO activities (
2882
+ session_id, prompt_batch_id, tool_name, tool_input,
2883
+ tool_output_summary, file_path, files_affected, duration_ms,
2884
+ success, error_message, timestamp, processed,
2885
+ content_hash, created_at
2886
+ ) VALUES (
2887
+ ?,
2888
+ (SELECT id FROM prompt_batches WHERE session_id = ? AND ended_at IS NULL ORDER BY id DESC LIMIT 1),
2889
+ ?, ?,
2890
+ ?, ?, ?, ?,
2891
+ ?, ?, ?, ?,
2892
+ ?, ?
2893
+ )`
2894
+ ).run(
2895
+ data.session_id,
2896
+ data.session_id,
2897
+ data.tool_name,
2898
+ data.tool_input ?? null,
2899
+ data.tool_output_summary ?? null,
2900
+ data.file_path ?? null,
2901
+ data.files_affected ?? null,
2902
+ data.duration_ms ?? null,
2903
+ data.success ?? DEFAULT_SUCCESS,
2904
+ data.error_message ?? null,
2905
+ data.timestamp,
2906
+ DEFAULT_PROCESSED,
2907
+ data.content_hash ?? null,
2908
+ data.created_at
2909
+ );
2910
+ const activityId = Number(info.lastInsertRowid);
2911
+ const toolName = data.tool_name;
2912
+ const toolInput = data.tool_input ?? null;
2913
+ const filePath = data.file_path ?? null;
2914
+ if (toolName || toolInput || filePath) {
2915
+ db.prepare(
2916
+ "INSERT INTO activities_fts(rowid, tool_name, tool_input, file_path) VALUES (?, ?, ?, ?)"
2917
+ ).run(activityId, toolName ?? "", toolInput ?? "", filePath ?? "");
2918
+ }
2919
+ return toActivityRow(
2920
+ db.prepare(`SELECT ${SELECT_COLUMNS2} FROM activities WHERE id = ?`).get(activityId)
2921
+ );
2922
+ }
2923
+ function listActivitiesByBatch(batchId) {
2924
+ const db = getDatabase();
2925
+ const rows = db.prepare(
2926
+ `SELECT ${SELECT_COLUMNS2}
2927
+ FROM activities
2928
+ WHERE prompt_batch_id = ?
2929
+ ORDER BY timestamp ASC`
2930
+ ).all(batchId);
2931
+ return rows.map(toActivityRow);
2932
+ }
2933
+ function countActivities(sessionId) {
2934
+ const db = getDatabase();
2935
+ const row = db.prepare(
2936
+ `SELECT COUNT(*) AS count FROM activities WHERE session_id = ?`
2937
+ ).get(sessionId);
2938
+ return row.count;
2939
+ }
2940
+
2941
+ // src/db/queries/attachments.ts
2942
+ var ATTACHMENT_COLUMNS = [
2943
+ "id",
2944
+ "session_id",
2945
+ "prompt_batch_id",
2946
+ "file_path",
2947
+ "media_type",
2948
+ "description",
2949
+ "data",
2950
+ "content_hash",
2951
+ "created_at"
2952
+ ];
2953
+ var ATTACHMENT_LIST_COLUMNS = [
2954
+ "id",
2955
+ "session_id",
2956
+ "prompt_batch_id",
2957
+ "file_path",
2958
+ "media_type",
2959
+ "description",
2960
+ "content_hash",
2961
+ "created_at"
2962
+ ];
2963
+ var SELECT_COLUMNS3 = ATTACHMENT_COLUMNS.join(", ");
2964
+ var SELECT_LIST_COLUMNS = ATTACHMENT_LIST_COLUMNS.join(", ");
2965
+ function toAttachmentBase(row) {
2966
+ return {
2967
+ id: row.id,
2968
+ session_id: row.session_id,
2969
+ prompt_batch_id: row.prompt_batch_id ?? null,
2970
+ file_path: row.file_path,
2971
+ media_type: row.media_type ?? null,
2972
+ description: row.description ?? null,
2973
+ content_hash: row.content_hash ?? null,
2974
+ created_at: row.created_at
2975
+ };
2976
+ }
2977
+ function toAttachmentRow(row) {
2978
+ return { ...toAttachmentBase(row), data: row.data ?? null };
2979
+ }
2980
+ function toAttachmentListRow(row) {
2981
+ return toAttachmentBase(row);
2982
+ }
2983
+ function insertAttachment(data) {
2984
+ const db = getDatabase();
2985
+ const info = db.prepare(
2986
+ `INSERT INTO attachments (${SELECT_COLUMNS3})
2987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2988
+ ON CONFLICT (id) DO NOTHING`
2989
+ ).run(
2990
+ data.id,
2991
+ data.session_id,
2992
+ data.prompt_batch_id ?? null,
2993
+ data.file_path,
2994
+ data.media_type ?? null,
2995
+ data.description ?? null,
2996
+ data.data ?? null,
2997
+ data.content_hash ?? null,
2998
+ data.created_at
2999
+ );
3000
+ if (info.changes === 0) return void 0;
3001
+ return toAttachmentRow(
3002
+ db.prepare(`SELECT ${SELECT_COLUMNS3} FROM attachments WHERE id = ?`).get(data.id)
3003
+ );
3004
+ }
3005
+ function listAttachmentsBySession(sessionId) {
3006
+ const db = getDatabase();
3007
+ const rows = db.prepare(
3008
+ `SELECT ${SELECT_LIST_COLUMNS} FROM attachments WHERE session_id = ? ORDER BY created_at ASC`
3009
+ ).all(sessionId);
3010
+ return rows.map(toAttachmentListRow);
3011
+ }
3012
+ function getAttachmentByFilePath(filePath) {
3013
+ const db = getDatabase();
3014
+ const row = db.prepare(
3015
+ `SELECT ${SELECT_COLUMNS3} FROM attachments WHERE file_path = ? LIMIT 1`
3016
+ ).get(filePath);
3017
+ return row ? toAttachmentRow(row) : null;
3018
+ }
3019
+
3020
+ // src/db/queries/plans.ts
3021
+ var DEFAULT_LIST_LIMIT2 = 100;
3022
+ var DEFAULT_STATUS2 = "active";
3023
+ var DEFAULT_PROCESSED2 = 0;
3024
+ var PLAN_COLUMNS = [
3025
+ "id",
3026
+ "status",
3027
+ "author",
3028
+ "title",
3029
+ "content",
3030
+ "source_path",
3031
+ "tags",
3032
+ "session_id",
3033
+ "prompt_batch_id",
3034
+ "content_hash",
3035
+ "processed",
3036
+ "embedded",
3037
+ "created_at",
3038
+ "updated_at",
3039
+ "machine_id",
3040
+ "synced_at"
3041
+ ];
3042
+ var SELECT_COLUMNS4 = PLAN_COLUMNS.join(", ");
3043
+ function toPlanRow(row) {
3044
+ return {
3045
+ id: row.id,
3046
+ status: row.status,
3047
+ author: row.author ?? null,
3048
+ title: row.title ?? null,
3049
+ content: row.content ?? null,
3050
+ source_path: row.source_path ?? null,
3051
+ tags: row.tags ?? null,
3052
+ session_id: row.session_id ?? null,
3053
+ prompt_batch_id: row.prompt_batch_id ?? null,
3054
+ content_hash: row.content_hash ?? null,
3055
+ processed: row.processed,
3056
+ embedded: row.embedded ?? 0,
3057
+ created_at: row.created_at,
3058
+ updated_at: row.updated_at ?? null,
3059
+ machine_id: row.machine_id ?? "local",
3060
+ synced_at: row.synced_at ?? null
3061
+ };
3062
+ }
3063
+ function upsertPlan(data) {
3064
+ const db = getDatabase();
3065
+ db.prepare(
3066
+ `INSERT INTO plans (
3067
+ id, status, author, title, content,
3068
+ source_path, tags, session_id, prompt_batch_id, content_hash,
3069
+ processed, created_at, updated_at, machine_id
3070
+ ) VALUES (
3071
+ ?, ?, ?, ?, ?,
3072
+ ?, ?, ?, ?, ?,
3073
+ ?, ?, ?, ?
3074
+ )
3075
+ ON CONFLICT (id) DO UPDATE SET
3076
+ status = EXCLUDED.status,
3077
+ author = EXCLUDED.author,
3078
+ title = EXCLUDED.title,
3079
+ content = EXCLUDED.content,
3080
+ source_path = EXCLUDED.source_path,
3081
+ tags = EXCLUDED.tags,
3082
+ session_id = EXCLUDED.session_id,
3083
+ prompt_batch_id = EXCLUDED.prompt_batch_id,
3084
+ content_hash = EXCLUDED.content_hash,
3085
+ processed = EXCLUDED.processed,
3086
+ updated_at = EXCLUDED.updated_at,
3087
+ embedded = CASE
3088
+ WHEN EXCLUDED.content_hash != plans.content_hash THEN 0
3089
+ ELSE plans.embedded
3090
+ END`
3091
+ ).run(
3092
+ data.id,
3093
+ data.status ?? DEFAULT_STATUS2,
3094
+ data.author ?? null,
3095
+ data.title ?? null,
3096
+ data.content ?? null,
3097
+ data.source_path ?? null,
3098
+ data.tags ?? null,
3099
+ data.session_id ?? null,
3100
+ data.prompt_batch_id ?? null,
3101
+ data.content_hash ?? null,
3102
+ data.processed ?? DEFAULT_PROCESSED2,
3103
+ data.created_at,
3104
+ data.updated_at ?? null,
3105
+ data.machine_id ?? getTeamMachineId()
3106
+ );
3107
+ const row = toPlanRow(
3108
+ db.prepare(`SELECT ${SELECT_COLUMNS4} FROM plans WHERE id = ?`).get(data.id)
3109
+ );
3110
+ syncRow("plans", row);
3111
+ return row;
3112
+ }
3113
+ function listPlans(options = {}) {
3114
+ const db = getDatabase();
3115
+ const conditions = [];
3116
+ const params = [];
3117
+ if (options.status !== void 0) {
3118
+ conditions.push(`status = ?`);
3119
+ params.push(options.status);
3120
+ }
3121
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3122
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
3123
+ params.push(limit);
3124
+ const rows = db.prepare(
3125
+ `SELECT ${SELECT_COLUMNS4}
3126
+ FROM plans
3127
+ ${where}
3128
+ ORDER BY created_at DESC
3129
+ LIMIT ?`
3130
+ ).all(...params);
3131
+ return rows.map(toPlanRow);
3132
+ }
3133
+ function listPlansBySession(sessionId) {
3134
+ const db = getDatabase();
3135
+ const rows = db.prepare(
3136
+ `SELECT ${SELECT_COLUMNS4}
3137
+ FROM plans
3138
+ WHERE session_id = ?
3139
+ ORDER BY created_at DESC`
3140
+ ).all(sessionId);
3141
+ return rows.map(toPlanRow);
3142
+ }
3143
+
3144
+ // src/daemon/jobs/session-cleanup.ts
3145
+ import { unlink, glob } from "fs/promises";
3146
+ async function cleanupAfterSessionCascade(sessionId, result, embeddingManager, vaultDir) {
3147
+ try {
3148
+ embeddingManager.onRemoved("sessions", sessionId);
3149
+ } catch {
3150
+ }
3151
+ for (const sporeId of result.deletedSporeIds) {
3152
+ try {
3153
+ embeddingManager.onRemoved("spores", sporeId);
3154
+ } catch {
3155
+ }
3156
+ }
3157
+ try {
3158
+ for await (const f of glob(`sessions/**/session-${sessionId}.md`, { cwd: vaultDir })) {
3159
+ await unlink(`${vaultDir}/${f}`).catch(() => {
3160
+ });
3161
+ }
3162
+ } catch {
3163
+ }
3164
+ for (const sporeId of result.deletedSporeIds) {
3165
+ try {
3166
+ for await (const f of glob(`spores/**/${sporeId}*.md`, { cwd: vaultDir })) {
3167
+ await unlink(`${vaultDir}/${f}`).catch(() => {
3168
+ });
3169
+ }
3170
+ } catch {
3171
+ }
3172
+ }
3173
+ for (const filePath of result.deletedAttachmentPaths) {
3174
+ try {
3175
+ await unlink(filePath);
3176
+ } catch {
3177
+ }
3178
+ }
3179
+ }
3180
+
3181
+ // src/daemon/api/sessions.ts
3182
+ var DEFAULT_LIST_LIMIT3 = 50;
3183
+ var DEFAULT_LIST_OFFSET2 = 0;
3184
+ async function handleListSessions(req) {
3185
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT3;
3186
+ const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET2;
3187
+ const status = req.query.status || void 0;
3188
+ const agent = req.query.agent || void 0;
3189
+ const search = req.query.search || void 0;
3190
+ const filterOpts = { status, agent, search };
3191
+ const sessions = listSessions({ ...filterOpts, limit, offset }).map((s) => ({
3192
+ id: s.id,
3193
+ date: new Date(s.started_at * 1e3).toISOString().slice(0, 10),
3194
+ title: s.title || s.id.slice(0, 8),
3195
+ status: s.status,
3196
+ agent: s.agent,
3197
+ prompt_count: s.prompt_count,
3198
+ tool_count: s.tool_count,
3199
+ started_at: s.started_at,
3200
+ ended_at: s.ended_at
3201
+ }));
3202
+ const total = countSessions(filterOpts);
3203
+ return { body: { sessions, total, offset, limit } };
3204
+ }
3205
+ async function handleGetSession(req) {
3206
+ const session = getSession(req.params.id);
3207
+ if (!session) return { status: 404, body: { error: "not_found" } };
3208
+ const promptCount = countBatchesBySession(session.id);
3209
+ const toolCount = countActivities(session.id);
3210
+ return { body: { ...session, prompt_count: promptCount, tool_count: toolCount } };
3211
+ }
3212
+ async function handleGetSessionBatches(req) {
3213
+ const batches = listBatchesBySession(req.params.id);
3214
+ return { body: batches };
3215
+ }
3216
+ async function handleGetBatchActivities(req) {
3217
+ const batchId = Number(req.params.id);
3218
+ if (isNaN(batchId)) return { status: 400, body: { error: "invalid_batch_id" } };
3219
+ const activities = listActivitiesByBatch(batchId);
3220
+ return { body: activities };
3221
+ }
3222
+ async function handleGetSessionAttachments(req) {
3223
+ const attachments = listAttachmentsBySession(req.params.id);
3224
+ return { body: attachments };
3225
+ }
3226
+ async function handleGetSessionPlans(req) {
3227
+ const plans = listPlansBySession(req.params.id);
3228
+ return { body: plans };
3229
+ }
3230
+ function createSessionMutationHandlers(deps) {
3231
+ const { embeddingManager, vaultDir, logger } = deps;
3232
+ async function handleDeleteSession(req) {
3233
+ const sessionId = req.params.id;
3234
+ const result = deleteSessionCascade(sessionId);
3235
+ if (!result.deleted) return { status: 404, body: { error: "Session not found" } };
3236
+ cleanupAfterSessionCascade(sessionId, result, embeddingManager, vaultDir).catch(() => {
3237
+ });
3238
+ logger.info(LOG_KINDS.API_SESSION_DELETE, "Session cascade deleted", {
3239
+ session_id: sessionId,
3240
+ counts: result.counts
3241
+ });
3242
+ return { body: { ok: true, counts: result.counts } };
3243
+ }
3244
+ async function handleGetSessionImpact(req) {
3245
+ const sessionId = req.params.id;
3246
+ const session = getSession(sessionId);
3247
+ if (!session) return { status: 404, body: { error: "Session not found" } };
3248
+ const impact = getSessionImpact(sessionId);
3249
+ return { body: impact };
3250
+ }
3251
+ return { handleDeleteSession, handleGetSessionImpact };
3252
+ }
3253
+
3254
+ // src/daemon/api/mycelium.ts
3255
+ var DEFAULT_LIST_LIMIT4 = 50;
3256
+ var DEFAULT_LIST_OFFSET3 = 0;
3257
+ var DEFAULT_GRAPH_DEPTH = 1;
3258
+ var MAX_GRAPH_DEPTH = 3;
3259
+ var SPORE_NAME_PREVIEW_CHARS = 60;
3260
+ var EXCLUDED_GRAPH_EDGE_TYPES = /* @__PURE__ */ new Set(["HAS_BATCH", "EXTRACTED_FROM"]);
3261
+ async function handleListSpores(req) {
3262
+ const agentId = req.query.agent_id;
3263
+ const type = req.query.type;
3264
+ const status = req.query.status;
3265
+ const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT4;
3266
+ const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET3;
3267
+ const search = req.query.search || void 0;
3268
+ const filterOpts = {
3269
+ ...agentId ? { agent_id: agentId } : {},
3270
+ observation_type: type,
3271
+ status,
3272
+ search
3273
+ };
3274
+ const spores = listSpores({ ...filterOpts, limit, offset });
3275
+ const total = countSpores(filterOpts);
3276
+ return { body: { spores, total, offset, limit } };
3277
+ }
3278
+ async function handleGetSpore(req) {
3279
+ const spore = getSpore(req.params.id);
3280
+ if (!spore) return { status: 404, body: { error: "not_found" } };
3281
+ return { body: spore };
3282
+ }
3283
+ async function handleListEntities(req) {
3284
+ const agentId = req.query.agent_id ?? DEFAULT_AGENT_ID;
2825
3285
  const type = req.query.type;
2826
3286
  const mentioned_in = req.query.mentioned_in;
2827
3287
  const note_type = req.query.note_type;
2828
3288
  const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT4;
2829
- const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET2;
3289
+ const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET3;
2830
3290
  const entities = listEntities({
2831
3291
  agent_id: agentId,
2832
3292
  type,
@@ -3272,12 +3732,18 @@ async function handleGetFeed(req) {
3272
3732
  }
3273
3733
 
3274
3734
  // src/daemon/api/symbionts.ts
3275
- async function handleListSymbionts() {
3735
+ async function handleListSymbionts(vaultDir) {
3276
3736
  const manifests = loadManifests();
3737
+ let enabledNames = null;
3738
+ try {
3739
+ enabledNames = getEnabledSymbiontNames(loadConfig(vaultDir));
3740
+ } catch {
3741
+ }
3277
3742
  const symbionts = manifests.map((m) => ({
3278
3743
  name: m.name,
3279
3744
  displayName: m.displayName,
3280
3745
  binary: m.binary,
3746
+ enabled: enabledNames ? enabledNames.has(m.name) : true,
3281
3747
  ...m.resumeCommand ? { resumeCommand: m.resumeCommand } : {}
3282
3748
  }));
3283
3749
  return { body: { symbionts } };
@@ -3322,7 +3788,7 @@ async function handleEmbeddingReembedStale(manager) {
3322
3788
  }
3323
3789
 
3324
3790
  // src/daemon/embedding/manager.ts
3325
- import { createHash as createHash4 } from "crypto";
3791
+ import { createHash as createHash3 } from "crypto";
3326
3792
  var ACTIVE_STATUS = "active";
3327
3793
  var EmbeddingManager = class {
3328
3794
  constructor(vectorStore, embeddingProvider, recordSource, logger) {
@@ -3335,7 +3801,7 @@ var EmbeddingManager = class {
3335
3801
  // Private helpers
3336
3802
  // -------------------------------------------------------------------------
3337
3803
  contentHash(text) {
3338
- return createHash4(CONTENT_HASH_ALGORITHM).update(text).digest("hex");
3804
+ return createHash3(CONTENT_HASH_ALGORITHM).update(text).digest("hex");
3339
3805
  }
3340
3806
  // -------------------------------------------------------------------------
3341
3807
  // Write-path event handlers
@@ -4513,200 +4979,559 @@ function testCloud() {
4513
4979
  return { ok: true };
4514
4980
  }
4515
4981
 
4516
- // src/db/queries/skill-usage.ts
4517
- var USAGE_COLUMNS = [
4518
- "id",
4519
- "skill_id",
4520
- "session_id",
4521
- "machine_id",
4522
- "detected_at"
4523
- ];
4524
- var SELECT_COLUMNS4 = USAGE_COLUMNS.join(", ");
4525
- function toUsageRow(row) {
4982
+ // src/daemon/task-scheduler.ts
4983
+ function resolveSchedule(yamlSchedule, configOverride) {
4984
+ if (!configOverride?.schedule) return yamlSchedule;
4526
4985
  return {
4527
- id: row.id,
4528
- skill_id: row.skill_id,
4529
- session_id: row.session_id,
4530
- machine_id: row.machine_id ?? getTeamMachineId(),
4531
- detected_at: row.detected_at
4986
+ enabled: configOverride.schedule.enabled ?? yamlSchedule.enabled,
4987
+ intervalSeconds: configOverride.schedule.intervalSeconds ?? yamlSchedule.intervalSeconds,
4988
+ runIn: configOverride.schedule.runIn ?? yamlSchedule.runIn,
4989
+ preCondition: configOverride.schedule.preCondition ?? yamlSchedule.preCondition
4532
4990
  };
4533
4991
  }
4534
- function insertSkillUsage(data) {
4535
- const db = getDatabase();
4536
- db.prepare(
4537
- `INSERT INTO skill_usage (
4538
- id, skill_id, session_id, machine_id, detected_at
4539
- ) VALUES (
4540
- ?, ?, ?, ?, ?
4541
- )`
4542
- ).run(
4543
- data.id,
4544
- data.skill_id,
4545
- data.session_id,
4546
- data.machine_id ?? getTeamMachineId(),
4547
- data.detected_at
4548
- );
4549
- const row = toUsageRow(
4550
- db.prepare(`SELECT ${SELECT_COLUMNS4} FROM skill_usage WHERE id = ?`).get(data.id)
4551
- );
4552
- return row;
4553
- }
4554
- function hasUsageForSkillAndSession(skillId, sessionId) {
4555
- const db = getDatabase();
4556
- const row = db.prepare(
4557
- `SELECT 1 FROM skill_usage WHERE skill_id = ? AND session_id = ? LIMIT 1`
4558
- ).get(skillId, sessionId);
4559
- return row !== void 0;
4560
- }
4561
- function countUsageForSkill(skillId) {
4562
- const db = getDatabase();
4563
- const row = db.prepare(
4564
- `SELECT COUNT(*) as count FROM skill_usage WHERE skill_id = ?`
4565
- ).get(skillId);
4566
- return row.count;
4992
+ function buildScheduledJobs(tasks, configOverrides, context, initialLastRuns) {
4993
+ const jobs = [];
4994
+ for (const task of tasks) {
4995
+ if (!task.schedule) continue;
4996
+ const override = configOverrides[task.name];
4997
+ const effective = resolveSchedule(task.schedule, override);
4998
+ if (!effective.enabled) continue;
4999
+ let lastRun = initialLastRuns?.[task.name] ?? 0;
5000
+ const intervalMs = effective.intervalSeconds * 1e3;
5001
+ jobs.push({
5002
+ name: `scheduled:${task.name}`,
5003
+ runIn: effective.runIn,
5004
+ fn: async () => {
5005
+ if (!context) return;
5006
+ if (context.isTaskRunning(task.name)) return;
5007
+ if (Date.now() - lastRun < intervalMs) return;
5008
+ if (effective.preCondition) {
5009
+ const check = context.preConditions[effective.preCondition];
5010
+ if (!check) return;
5011
+ if (!check()) return;
5012
+ }
5013
+ try {
5014
+ context.setTaskRunning(task.name, true);
5015
+ await context.runTask(task.name);
5016
+ } finally {
5017
+ lastRun = Date.now();
5018
+ context.setTaskRunning(task.name, false);
5019
+ }
5020
+ }
5021
+ });
5022
+ }
5023
+ return jobs;
4567
5024
  }
4568
5025
 
4569
- // src/daemon/api/skills.ts
4570
- var DEFAULT_LIST_OFFSET3 = 0;
4571
- async function handleListCandidates(req) {
4572
- const status = req.query.status || void 0;
4573
- const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT;
4574
- const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET3;
4575
- const { items: candidates, total } = listCandidatesWithCount({ status, limit, offset });
4576
- return { status: 200, body: { candidates, total } };
4577
- }
4578
- async function handleGetCandidate(req) {
4579
- const candidate = getCandidate(req.params.id);
4580
- if (!candidate) {
4581
- return { status: 404, body: { error: `Not found: ${req.params.id}` } };
5026
+ // src/agent/instruction-builders.ts
5027
+ var SKILL_GENERATE_TASK = "skill-generate";
5028
+ var SKILL_EVOLVE_TASK = "skill-evolve";
5029
+ function buildSkillGenerateInstruction() {
5030
+ const candidates = listCandidates({ status: "approved", limit: 1 });
5031
+ if (candidates.length === 0) return void 0;
5032
+ const c = candidates[0];
5033
+ const parts = [
5034
+ `candidate_id: ${c.id}`,
5035
+ `topic: ${c.topic}`,
5036
+ `confidence: ${c.confidence}`,
5037
+ `rationale: ${c.rationale}`,
5038
+ "",
5039
+ "## Source Material"
5040
+ ];
5041
+ let sourceIds = [];
5042
+ try {
5043
+ sourceIds = JSON.parse(c.source_ids || "[]");
5044
+ } catch {
5045
+ }
5046
+ for (const src of sourceIds) {
5047
+ if (src.type === "spore") {
5048
+ const spore = getSpore(src.id);
5049
+ if (spore) {
5050
+ parts.push(`
5051
+ ### Spore: ${src.id} (${spore.observation_type}, importance ${spore.importance})`);
5052
+ parts.push(spore.content);
5053
+ if (spore.context) parts.push(`Context: ${spore.context}`);
5054
+ if (spore.tags) parts.push(`Tags: ${spore.tags}`);
5055
+ }
5056
+ } else if (src.type === "session") {
5057
+ const session = getSession(src.id);
5058
+ if (session) {
5059
+ parts.push(`
5060
+ ### Session: ${src.id}`);
5061
+ if (session.title) parts.push(`Title: ${session.title}`);
5062
+ if (session.summary) parts.push(session.summary);
5063
+ }
5064
+ }
4582
5065
  }
4583
- return { status: 200, body: { candidate } };
4584
- }
4585
- async function handleUpdateCandidate(req) {
4586
- const id = req.params.id;
4587
- const body = req.body;
4588
- if (!body) return { status: 400, body: { error: "Request body required" } };
4589
- const { status, topic, rationale, confidence, source_ids, skill_id } = body;
4590
- const updated = updateCandidate(id, {
4591
- ...status !== void 0 ? { status } : {},
4592
- ...topic !== void 0 ? { topic } : {},
4593
- ...rationale !== void 0 ? { rationale } : {},
4594
- ...confidence !== void 0 ? { confidence } : {},
4595
- ...source_ids !== void 0 ? { source_ids } : {},
4596
- ...skill_id !== void 0 ? { skill_id } : {},
4597
- updated_at: epochSeconds()
4598
- });
4599
- if (!updated) return { status: 404, body: { error: `Candidate not found: ${id}` } };
4600
- return { status: 200, body: { candidate: updated } };
5066
+ return parts.join("\n");
4601
5067
  }
4602
- async function handleListSkillRecords(req) {
4603
- const status = req.query.status || void 0;
4604
- const limit = req.query.limit ? Number(req.query.limit) : DEFAULT_LIST_LIMIT;
4605
- const offset = req.query.offset ? Number(req.query.offset) : DEFAULT_LIST_OFFSET3;
4606
- const { items: records, total } = listSkillRecordsWithCount({ status, limit, offset });
4607
- return { status: 200, body: { records, total } };
5068
+ var SKILL_EVOLVE_DEFAULT_ASSESS_INTERVAL_HOURS = 24;
5069
+ var SKILL_EVOLVE_DEFAULT_MAX_SKILLS_PER_RUN = 5;
5070
+ function buildSkillEvolveInstruction(params) {
5071
+ const assessIntervalHours = Number(params?.assess_interval_hours ?? SKILL_EVOLVE_DEFAULT_ASSESS_INTERVAL_HOURS);
5072
+ const maxSkillsPerRun = Number(params?.max_skills_per_run ?? SKILL_EVOLVE_DEFAULT_MAX_SKILLS_PER_RUN);
5073
+ const now = epochSeconds();
5074
+ const intervalSeconds = assessIntervalHours * 3600;
5075
+ const allSkills = listSkillRecords({ status: "active", limit: 100 });
5076
+ const needsAssessment = [];
5077
+ for (const skill of allSkills) {
5078
+ let props = {};
5079
+ try {
5080
+ props = JSON.parse(skill.properties || "{}");
5081
+ } catch {
5082
+ props = {};
5083
+ }
5084
+ const lastAssessedAt = typeof props.last_assessed_at === "number" ? props.last_assessed_at : 0;
5085
+ const knowledgeWatermark = typeof props.knowledge_watermark === "number" ? props.knowledge_watermark : 0;
5086
+ if (lastAssessedAt > 0 && now - lastAssessedAt < intervalSeconds) continue;
5087
+ const newSporeIds = listSporeIdsSince(knowledgeWatermark, 10);
5088
+ if (newSporeIds.length === 0) continue;
5089
+ const lineage = listLineageForSkill(skill.id, 1);
5090
+ if (lineage.length === 0) continue;
5091
+ needsAssessment.push({
5092
+ id: skill.id,
5093
+ name: skill.name,
5094
+ generation: skill.generation,
5095
+ description: skill.description,
5096
+ contentSnapshot: lineage[0].content_snapshot,
5097
+ newSporeIds
5098
+ });
5099
+ if (needsAssessment.length >= maxSkillsPerRun) {
5100
+ break;
5101
+ }
5102
+ }
5103
+ if (needsAssessment.length === 0) {
5104
+ return "No skills need assessment. All active skills are current or were recently assessed. Report skip via vault_report and finish.";
5105
+ }
5106
+ const parts = [
5107
+ `${needsAssessment.length} skill(s) need assessment.`,
5108
+ `assess_interval_hours: ${assessIntervalHours}`,
5109
+ `max_skills_per_run: ${maxSkillsPerRun}`
5110
+ ];
5111
+ for (const skill of needsAssessment) {
5112
+ parts.push("");
5113
+ parts.push("---");
5114
+ parts.push(`## Skill: ${skill.name} (gen ${skill.generation})`);
5115
+ parts.push(`id: ${skill.id}`);
5116
+ parts.push(`description: ${skill.description}`);
5117
+ parts.push(`new_spore_ids: ${JSON.stringify(skill.newSporeIds)}`);
5118
+ parts.push("");
5119
+ parts.push("### Current Content");
5120
+ parts.push("");
5121
+ parts.push(skill.contentSnapshot);
5122
+ }
5123
+ return parts.join("\n");
5124
+ }
5125
+ function buildTaskInstruction(taskName, taskParams) {
5126
+ switch (taskName) {
5127
+ case SKILL_GENERATE_TASK:
5128
+ return buildSkillGenerateInstruction();
5129
+ case SKILL_EVOLVE_TASK:
5130
+ return buildSkillEvolveInstruction(taskParams);
5131
+ default:
5132
+ return void 0;
5133
+ }
4608
5134
  }
4609
- async function handleGetSkillRecord(req) {
4610
- const idOrName = req.params.id;
4611
- const record = getSkillRecord(idOrName) ?? getSkillRecordByName(idOrName);
4612
- if (!record) {
4613
- return { status: 404, body: { error: `Not found: ${idOrName}` } };
5135
+
5136
+ // src/daemon/task-scheduling.ts
5137
+ async function registerScheduledTasks(powerManager, deps) {
5138
+ const { definitionsDir, vaultDir, embeddingManager, logger, config } = deps;
5139
+ const runningTasks = /* @__PURE__ */ new Set();
5140
+ if (!definitionsDir) {
5141
+ logger.warn(LOG_KINDS.AGENT_ERROR, "Skipping dynamic task scheduling \u2014 definitions directory unavailable");
5142
+ return;
5143
+ }
5144
+ if (config.agent.scheduled_tasks_enabled === false) {
5145
+ logger.info(LOG_KINDS.AGENT_RUN, "Scheduled agent tasks disabled globally (agent.scheduled_tasks_enabled: false)");
5146
+ return;
5147
+ }
5148
+ const { loadAllTasks: loadAllTasks2 } = await import("./registry-DHWVHXWY.js");
5149
+ const allTasks = Array.from(loadAllTasks2(definitionsDir, vaultDir).values());
5150
+ const initialLastRuns = {};
5151
+ try {
5152
+ const recentRuns = getDatabase().prepare(
5153
+ `SELECT task, MAX(completed_at) as last_completed
5154
+ FROM agent_runs
5155
+ WHERE status IN ('completed', 'failed') AND completed_at IS NOT NULL
5156
+ GROUP BY task`
5157
+ ).all();
5158
+ for (const row of recentRuns) {
5159
+ initialLastRuns[row.task] = row.last_completed * 1e3;
5160
+ }
5161
+ } catch {
4614
5162
  }
4615
- const lineage = listLineageForSkill(record.id);
4616
- const usage_total = countUsageForSkill(record.id);
4617
- const latestSnapshot = lineage[0]?.content_snapshot;
4618
- const frontmatterFields = {};
4619
- if (latestSnapshot) {
4620
- const fmMatch = latestSnapshot.match(/^---\n([\s\S]*?)\n---/);
4621
- if (fmMatch) {
4622
- for (const line of fmMatch[1].split("\n")) {
4623
- const colonIdx = line.indexOf(":");
4624
- if (colonIdx > 0) {
4625
- const key = line.slice(0, colonIdx).trim();
4626
- const val = line.slice(colonIdx + 1).trim();
4627
- if (key && val) frontmatterFields[key] = val;
5163
+ const scheduledContext = {
5164
+ isTaskRunning: (name) => runningTasks.has(name),
5165
+ setTaskRunning: (name, running) => {
5166
+ if (running) runningTasks.add(name);
5167
+ else runningTasks.delete(name);
5168
+ },
5169
+ runTask: async (taskName) => {
5170
+ const { runAgent } = await import("./executor-4OXDK4ZA.js");
5171
+ const taskConfig = config.agent.tasks?.[taskName];
5172
+ const instruction = buildTaskInstruction(taskName, taskConfig?.params);
5173
+ const result = await runAgent(vaultDir, { task: taskName, instruction, embeddingManager });
5174
+ logger.info(LOG_KINDS.AGENT_RUN, `Scheduled task ${taskName} completed`, {
5175
+ status: result.status,
5176
+ runId: result.runId
5177
+ });
5178
+ if (result.status === "failed") {
5179
+ notify(vaultDir, {
5180
+ domain: "agents",
5181
+ type: "agent.task.failure",
5182
+ title: `Task failed: ${taskName}`,
5183
+ message: result.error ?? "Unknown error",
5184
+ link: `/agent?run=${result.runId}`,
5185
+ metadata: { taskName, runId: result.runId }
5186
+ }, config);
5187
+ } else if (result.status === "completed") {
5188
+ notify(vaultDir, {
5189
+ domain: "agents",
5190
+ type: "agent.task.success",
5191
+ title: `Task completed: ${taskName}`,
5192
+ link: `/agent?run=${result.runId}`,
5193
+ metadata: { taskName, runId: result.runId }
5194
+ }, config);
5195
+ const { countToolCallsByRun } = await import("./turns-3ZQAF6HF.js");
5196
+ const counts = countToolCallsByRun(result.runId, ["vault_create_spore", "vault_write_digest"]);
5197
+ const sporeCount = counts["vault_create_spore"] ?? 0;
5198
+ const digestCount = counts["vault_write_digest"] ?? 0;
5199
+ if (sporeCount > 0) {
5200
+ notify(vaultDir, {
5201
+ domain: "mycelium",
5202
+ type: "mycelium.spore.created",
5203
+ title: sporeCount === 1 ? "Extracted 1 observation" : `Extracted ${sporeCount} observations`,
5204
+ message: `From ${taskName} run`,
5205
+ link: "/mycelium?tab=spores",
5206
+ metadata: { count: sporeCount, taskName, runId: result.runId }
5207
+ }, config);
4628
5208
  }
5209
+ if (digestCount > 0) {
5210
+ notify(vaultDir, {
5211
+ domain: "mycelium",
5212
+ type: "mycelium.digest.completed",
5213
+ title: `Digest updated (${digestCount} ${digestCount === 1 ? "tier" : "tiers"})`,
5214
+ link: "/mycelium?tab=digest",
5215
+ metadata: { tierCount: digestCount, taskName, runId: result.runId }
5216
+ }, config);
5217
+ }
5218
+ }
5219
+ },
5220
+ preConditions: {
5221
+ "has-unprocessed-batches": () => {
5222
+ const row = getDatabase().prepare(
5223
+ "SELECT 1 FROM prompt_batches WHERE processed = 0 LIMIT 1"
5224
+ ).get();
5225
+ return row !== void 0;
5226
+ },
5227
+ "has-active-skills": () => {
5228
+ return countSkillRecords({ status: "active" }) > 0;
5229
+ },
5230
+ "has-approved-candidates": () => {
5231
+ return countCandidates({ status: "approved" }) > 0;
4629
5232
  }
4630
5233
  }
5234
+ };
5235
+ const scheduledJobs = buildScheduledJobs(
5236
+ allTasks,
5237
+ config.agent.tasks ?? {},
5238
+ scheduledContext,
5239
+ initialLastRuns
5240
+ );
5241
+ for (const job of scheduledJobs) {
5242
+ powerManager.register(job);
4631
5243
  }
4632
- return { status: 200, body: { ...record, lineage, usage_total, frontmatter: frontmatterFields } };
5244
+ logger.info(LOG_KINDS.DAEMON_START, `Registered ${scheduledJobs.length} scheduled task(s)`, {
5245
+ tasks: scheduledJobs.map((j) => j.name)
5246
+ });
4633
5247
  }
4634
- async function handleDeleteCandidate(req) {
4635
- const id = req.params.id;
4636
- const deleted = deleteCandidate(id);
4637
- if (!deleted) return { status: 404, body: { error: `Not found: ${id}` } };
4638
- return { status: 200, body: { deleted: true, id } };
5248
+
5249
+ // src/db/queries/team-members.ts
5250
+ function listTeamMembers() {
5251
+ return getDatabase().prepare(
5252
+ `SELECT id, "user", role, joined, tags
5253
+ FROM team_members
5254
+ ORDER BY id ASC`
5255
+ ).all();
4639
5256
  }
4640
- async function handleDeleteSkillRecord(req) {
4641
- const idOrName = req.params.id;
4642
- const result = deleteSkillRecordCascade(idOrName);
4643
- if (!result) return { status: 404, body: { error: `Not found: ${idOrName}` } };
4644
- if (isTeamSyncEnabled()) {
5257
+
5258
+ // src/daemon/api/mcp-proxy.ts
5259
+ var SPORE_ID_RANDOM_BYTES = 4;
5260
+ var RESOLUTION_ID_RANDOM_BYTES = 8;
5261
+ var RememberBody = external_exports.object({
5262
+ content: external_exports.string(),
5263
+ type: external_exports.string().optional(),
5264
+ tags: external_exports.array(external_exports.string()).optional()
5265
+ });
5266
+ var SupersedeBody = external_exports.object({
5267
+ old_spore_id: external_exports.string(),
5268
+ new_spore_id: external_exports.string(),
5269
+ reason: external_exports.string().optional()
5270
+ });
5271
+ function createMcpProxyHandlers(deps) {
5272
+ const { machineId, embeddingManager } = deps;
5273
+ async function handleRemember(req) {
5274
+ const { content, type, tags } = RememberBody.parse(req.body);
5275
+ const { randomBytes } = await import("crypto");
5276
+ const observationType = type ?? "discovery";
5277
+ const id = `${observationType}-${randomBytes(SPORE_ID_RANDOM_BYTES).toString("hex")}`;
5278
+ const now = epochSeconds();
5279
+ registerAgent({
5280
+ id: USER_AGENT_ID,
5281
+ name: USER_AGENT_NAME,
5282
+ created_at: now
5283
+ });
5284
+ const spore = insertSpore({
5285
+ id,
5286
+ agent_id: USER_AGENT_ID,
5287
+ machine_id: machineId,
5288
+ observation_type: observationType,
5289
+ content,
5290
+ tags: tags ? tags.join(", ") : null,
5291
+ created_at: now
5292
+ });
5293
+ embeddingManager.onContentWritten("spores", spore.id, content, {
5294
+ status: "active",
5295
+ observation_type: observationType
5296
+ }).catch(() => {
5297
+ });
5298
+ return {
5299
+ body: {
5300
+ id: spore.id,
5301
+ observation_type: spore.observation_type,
5302
+ status: spore.status,
5303
+ created_at: spore.created_at
5304
+ }
5305
+ };
5306
+ }
5307
+ async function handleSupersede(req) {
5308
+ const { old_spore_id, new_spore_id, reason } = SupersedeBody.parse(req.body);
5309
+ const { randomBytes } = await import("crypto");
5310
+ const now = epochSeconds();
5311
+ updateSporeStatus(old_spore_id, "superseded", now);
4645
5312
  try {
4646
- enqueueOutbox({
4647
- table_name: "skill_records",
4648
- row_id: result.id,
4649
- operation: "delete",
4650
- payload: JSON.stringify({ id: result.id, name: result.name }),
4651
- machine_id: getTeamMachineId(),
4652
- created_at: epochSeconds()
5313
+ embeddingManager.onStatusChanged("spores", old_spore_id, "superseded");
5314
+ } catch {
5315
+ }
5316
+ registerAgent({
5317
+ id: USER_AGENT_ID,
5318
+ name: USER_AGENT_NAME,
5319
+ created_at: now
5320
+ });
5321
+ const { insertResolutionEvent } = await import("./resolution-events-DBCRVZGU.js");
5322
+ const resolutionId = `res-${randomBytes(RESOLUTION_ID_RANDOM_BYTES).toString("hex")}`;
5323
+ insertResolutionEvent({
5324
+ id: resolutionId,
5325
+ agent_id: USER_AGENT_ID,
5326
+ machine_id: machineId,
5327
+ spore_id: old_spore_id,
5328
+ action: "supersede",
5329
+ new_spore_id,
5330
+ reason: reason ?? null,
5331
+ created_at: now
5332
+ });
5333
+ return {
5334
+ body: {
5335
+ old_spore: old_spore_id,
5336
+ new_spore: new_spore_id,
5337
+ status: "superseded"
5338
+ }
5339
+ };
5340
+ }
5341
+ async function handlePlans(req) {
5342
+ const statusFilter = req.query.status === "all" ? void 0 : req.query.status;
5343
+ const limit = req.query.limit ? Number(req.query.limit) : void 0;
5344
+ const rows = listPlans({ status: statusFilter, limit });
5345
+ const plans = rows.map((row) => {
5346
+ const content = row.content ?? "";
5347
+ const checked = (content.match(/- \[x\]/gi) ?? []).length;
5348
+ const unchecked = (content.match(/- \[ \]/g) ?? []).length;
5349
+ const total = checked + unchecked;
5350
+ const progress = total === 0 ? "N/A" : `${checked}/${total}`;
5351
+ return {
5352
+ id: row.id,
5353
+ title: row.title,
5354
+ status: row.status,
5355
+ progress,
5356
+ tags: row.tags ? row.tags.split(",").map((t) => t.trim()) : [],
5357
+ created_at: row.created_at
5358
+ };
5359
+ });
5360
+ return { body: { plans } };
5361
+ }
5362
+ async function handleSessions(req) {
5363
+ const limit = req.query.limit ? Number(req.query.limit) : 20;
5364
+ const status = req.query.status;
5365
+ const rows = listSessions({ limit, status });
5366
+ const sessions = rows.map((row) => ({
5367
+ id: row.id,
5368
+ agent: row.agent,
5369
+ user: row.user,
5370
+ branch: row.branch,
5371
+ started_at: row.started_at,
5372
+ ended_at: row.ended_at,
5373
+ status: row.status,
5374
+ title: row.title,
5375
+ summary: (row.summary ?? "").slice(0, 300),
5376
+ prompt_count: row.prompt_count,
5377
+ tool_count: row.tool_count,
5378
+ parent_session_id: row.parent_session_id
5379
+ }));
5380
+ return { body: { sessions } };
5381
+ }
5382
+ async function handleTeam(_req) {
5383
+ const rows = listTeamMembers();
5384
+ const members = rows.map((row) => ({
5385
+ id: row.id,
5386
+ user: row.user,
5387
+ role: row.role,
5388
+ joined: row.joined,
5389
+ tags: row.tags ? row.tags.split(",").map((t) => t.trim()) : []
5390
+ }));
5391
+ return { body: { members } };
5392
+ }
5393
+ return {
5394
+ handleRemember,
5395
+ handleSupersede,
5396
+ handlePlans,
5397
+ handleSessions,
5398
+ handleTeam
5399
+ };
5400
+ }
5401
+
5402
+ // src/daemon/api/agent-runs.ts
5403
+ var AGENT_RUNS_DEFAULT_LIMIT = 50;
5404
+ var AgentRunBody = external_exports.object({
5405
+ task: external_exports.string().optional(),
5406
+ instruction: external_exports.string().optional(),
5407
+ agentId: external_exports.string().optional()
5408
+ });
5409
+ function createAgentRunHandlers(deps) {
5410
+ const { vaultDir, embeddingManager, logger } = deps;
5411
+ async function handleRun(req) {
5412
+ const { task, instruction: rawInstruction, agentId } = AgentRunBody.parse(req.body);
5413
+ let instruction = rawInstruction;
5414
+ if (task && !instruction) {
5415
+ try {
5416
+ const mycoConfig = loadConfig(vaultDir);
5417
+ const taskParams = mycoConfig.agent.tasks?.[task]?.params;
5418
+ instruction = buildTaskInstruction(task, taskParams);
5419
+ } catch {
5420
+ instruction = buildTaskInstruction(task);
5421
+ }
5422
+ }
5423
+ const { runAgent } = await import("./executor-4OXDK4ZA.js");
5424
+ const resultPromise = runAgent(vaultDir, { task, instruction, agentId, embeddingManager });
5425
+ const effectiveAgentId = agentId ?? "myco-agent";
5426
+ const runId = getLatestRunId(effectiveAgentId, task);
5427
+ resultPromise.then((result) => {
5428
+ if (result.status === "failed") {
5429
+ logger.error(LOG_KINDS.AGENT_ERROR, "Agent run failed", {
5430
+ runId: result.runId,
5431
+ error: result.error ?? "No error message",
5432
+ phases: result.phases?.map((p) => `${p.name}:${p.status}`) ?? []
5433
+ });
5434
+ } else {
5435
+ logger.info(LOG_KINDS.AGENT_RUN, "Agent run completed", {
5436
+ runId: result.runId,
5437
+ status: result.status,
5438
+ phases: result.phases?.map((p) => `${p.name}:${p.status}`) ?? []
5439
+ });
5440
+ }
5441
+ }).catch((err) => {
5442
+ logger.error(LOG_KINDS.AGENT_ERROR, "Agent run threw unhandled error", {
5443
+ error: err.message ?? String(err),
5444
+ stack: err.stack?.split("\n").slice(0, 3).join(" | ")
4653
5445
  });
4654
- } catch (err) {
4655
- console.warn("[team-sync] Failed to enqueue skill record deletion:", err instanceof Error ? err.message : err);
5446
+ });
5447
+ return { body: { ok: true, message: "Agent started", runId } };
5448
+ }
5449
+ async function handleListRuns(req) {
5450
+ const limit = req.query.limit ? Number(req.query.limit) : AGENT_RUNS_DEFAULT_LIMIT;
5451
+ const offset = req.query.offset ? Number(req.query.offset) : 0;
5452
+ const agentId = req.query.agentId || void 0;
5453
+ const status = req.query.status || void 0;
5454
+ const task = req.query.task || void 0;
5455
+ const search = req.query.search || void 0;
5456
+ const filterOpts = { agent_id: agentId, status, task, search };
5457
+ const runs = listRuns({ ...filterOpts, limit, offset });
5458
+ const total = countRuns(filterOpts);
5459
+ return { body: { runs, total, offset, limit } };
5460
+ }
5461
+ async function handleGetRun(req) {
5462
+ const run = getRun(req.params.id);
5463
+ if (!run) {
5464
+ return { status: 404, body: { error: "Run not found" } };
4656
5465
  }
5466
+ return { body: { run } };
5467
+ }
5468
+ async function handleGetRunReports(req) {
5469
+ const reports = listReports(req.params.id);
5470
+ return { body: { reports } };
5471
+ }
5472
+ async function handleGetRunTurns(req) {
5473
+ const turns = listTurnsByRun(req.params.id);
5474
+ return { body: turns };
4657
5475
  }
4658
- return { status: 200, body: { deleted: true, id: result.id, name: result.name } };
5476
+ return {
5477
+ handleRun,
5478
+ handleListRuns,
5479
+ handleGetRun,
5480
+ handleGetRunReports,
5481
+ handleGetRunTurns
5482
+ };
4659
5483
  }
4660
5484
 
4661
- // src/daemon/skill-usage.ts
4662
- import crypto from "crypto";
4663
- var SKILL_USAGE_DETECTION_ENABLED = false;
4664
- var MAX_ACTIVE_SKILLS_CHECK = 1e3;
4665
- function detectSkillUsage(sessionId, transcriptContent) {
4666
- if (transcriptContent.includes("vault_write_skill")) return;
4667
- if (!SKILL_USAGE_DETECTION_ENABLED) return;
4668
- const activeSkills = listSkillRecords({ status: "active", limit: MAX_ACTIVE_SKILLS_CHECK });
4669
- if (activeSkills.length === 0) return;
4670
- const skillPatterns = activeSkills.map((skill) => ({
4671
- skill,
4672
- pattern: new RegExp(
4673
- `skills/${escapeRegex(skill.name)}/SKILL\\.md|<skill[^>]*name=["']${escapeRegex(skill.name)}["']`
4674
- )
4675
- }));
4676
- const now = epochSeconds();
4677
- for (const { skill, pattern } of skillPatterns) {
5485
+ // src/daemon/api/attachments.ts
5486
+ import fs13 from "fs";
5487
+ import path15 from "path";
5488
+ var ATTACHMENT_MEDIA_TYPES = {
5489
+ png: "image/png",
5490
+ jpg: "image/jpeg",
5491
+ jpeg: "image/jpeg",
5492
+ gif: "image/gif",
5493
+ webp: "image/webp"
5494
+ };
5495
+ function createAttachmentHandler(deps) {
5496
+ const { vaultDir } = deps;
5497
+ async function handleGetAttachment(req) {
5498
+ const filename = req.params.filename;
5499
+ if (filename.includes("..") || filename.includes("/")) {
5500
+ return { status: 400, body: { error: "invalid_filename" } };
5501
+ }
5502
+ const att = getAttachmentByFilePath(filename);
5503
+ if (att?.data) {
5504
+ const contentType2 = att.media_type ?? "application/octet-stream";
5505
+ return { status: 200, headers: { "Content-Type": contentType2 }, body: att.data };
5506
+ }
5507
+ const filePath = path15.join(vaultDir, "attachments", filename);
5508
+ let diskData;
4678
5509
  try {
4679
- if (!pattern.test(transcriptContent)) continue;
4680
- if (hasUsageForSkillAndSession(skill.id, sessionId)) continue;
4681
- insertSkillUsage({
4682
- id: crypto.randomUUID(),
4683
- skill_id: skill.id,
4684
- session_id: sessionId,
4685
- detected_at: now
4686
- });
4687
- incrementSkillUsageCount(skill.id, now);
5510
+ diskData = fs13.readFileSync(filePath);
4688
5511
  } catch {
5512
+ return { status: 404, body: { error: "not_found" } };
4689
5513
  }
5514
+ const ext = path15.extname(filename).slice(1).toLowerCase();
5515
+ const contentType = ATTACHMENT_MEDIA_TYPES[ext] ?? "application/octet-stream";
5516
+ return { status: 200, headers: { "Content-Type": contentType }, body: diskData };
4690
5517
  }
4691
- }
4692
- function escapeRegex(s) {
4693
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5518
+ return { handleGetAttachment };
4694
5519
  }
4695
5520
 
4696
5521
  // src/daemon/log-reconcile.ts
4697
- import fs12 from "fs";
4698
- import path14 from "path";
5522
+ import fs14 from "fs";
5523
+ import path16 from "path";
4699
5524
  function reconcileLogBuffer(logDir, sinceTimestamp) {
4700
5525
  let replayed = 0;
4701
5526
  const files = [];
4702
5527
  for (let i = 3; i >= 1; i--) {
4703
- const rotated = path14.join(logDir, `daemon.${i}.log`);
4704
- if (fs12.existsSync(rotated)) files.push(rotated);
5528
+ const rotated = path16.join(logDir, `daemon.${i}.log`);
5529
+ if (fs14.existsSync(rotated)) files.push(rotated);
4705
5530
  }
4706
- const current = path14.join(logDir, "daemon.log");
4707
- if (fs12.existsSync(current)) files.push(current);
5531
+ const current = path16.join(logDir, "daemon.log");
5532
+ if (fs14.existsSync(current)) files.push(current);
4708
5533
  for (const file of files) {
4709
- const content = fs12.readFileSync(file, "utf-8");
5534
+ const content = fs14.readFileSync(file, "utf-8");
4710
5535
  for (const line of content.split("\n")) {
4711
5536
  if (!line.trim()) continue;
4712
5537
  try {
@@ -4740,6 +5565,7 @@ var PowerManager = class {
4740
5565
  running = false;
4741
5566
  config;
4742
5567
  logger;
5568
+ deepSleepHeld = false;
4743
5569
  constructor(config) {
4744
5570
  this.config = config;
4745
5571
  this.logger = config.logger;
@@ -4749,6 +5575,7 @@ var PowerManager = class {
4749
5575
  }
4750
5576
  recordActivity() {
4751
5577
  this.lastActivity = Date.now();
5578
+ this.deepSleepHeld = false;
4752
5579
  if (this.state === "deep_sleep") {
4753
5580
  this.logger.info(LOG_KINDS.POWER_STATE, "Waking from deep sleep");
4754
5581
  this.state = "active";
@@ -4780,7 +5607,17 @@ var PowerManager = class {
4780
5607
  const idleMs = Date.now() - this.lastActivity;
4781
5608
  let target;
4782
5609
  if (idleMs >= this.config.deepSleepThresholdMs) {
4783
- target = "deep_sleep";
5610
+ const blocker = this.jobs.find((j) => j.preventsDeepSleep?.());
5611
+ if (blocker) {
5612
+ target = "sleep";
5613
+ if (!this.deepSleepHeld) {
5614
+ this.deepSleepHeld = true;
5615
+ this.logger.info(LOG_KINDS.POWER_STATE, "Deep sleep held", { by: blocker.name });
5616
+ }
5617
+ } else {
5618
+ target = "deep_sleep";
5619
+ this.deepSleepHeld = false;
5620
+ }
4784
5621
  } else if (idleMs >= this.config.sleepThresholdMs) {
4785
5622
  target = "sleep";
4786
5623
  } else if (idleMs >= this.config.idleThresholdMs) {
@@ -4829,87 +5666,6 @@ var PowerManager = class {
4829
5666
  }
4830
5667
  };
4831
5668
 
4832
- // src/daemon/task-scheduler.ts
4833
- function resolveSchedule(yamlSchedule, configOverride) {
4834
- if (!configOverride?.schedule) return yamlSchedule;
4835
- return {
4836
- enabled: configOverride.schedule.enabled ?? yamlSchedule.enabled,
4837
- intervalSeconds: configOverride.schedule.intervalSeconds ?? yamlSchedule.intervalSeconds,
4838
- runIn: configOverride.schedule.runIn ?? yamlSchedule.runIn,
4839
- preCondition: configOverride.schedule.preCondition ?? yamlSchedule.preCondition
4840
- };
4841
- }
4842
- function buildScheduledJobs(tasks, configOverrides, context, initialLastRuns) {
4843
- const jobs = [];
4844
- for (const task of tasks) {
4845
- if (!task.schedule) continue;
4846
- const override = configOverrides[task.name];
4847
- const effective = resolveSchedule(task.schedule, override);
4848
- if (!effective.enabled) continue;
4849
- let lastRun = initialLastRuns?.[task.name] ?? 0;
4850
- const intervalMs = effective.intervalSeconds * 1e3;
4851
- jobs.push({
4852
- name: `scheduled:${task.name}`,
4853
- runIn: effective.runIn,
4854
- fn: async () => {
4855
- if (!context) return;
4856
- if (context.isTaskRunning(task.name)) return;
4857
- if (Date.now() - lastRun < intervalMs) return;
4858
- if (effective.preCondition) {
4859
- const check = context.preConditions[effective.preCondition];
4860
- if (!check) return;
4861
- if (!check()) return;
4862
- }
4863
- try {
4864
- context.setTaskRunning(task.name, true);
4865
- await context.runTask(task.name);
4866
- } finally {
4867
- lastRun = Date.now();
4868
- context.setTaskRunning(task.name, false);
4869
- }
4870
- }
4871
- });
4872
- }
4873
- return jobs;
4874
- }
4875
-
4876
- // src/daemon/jobs/session-cleanup.ts
4877
- import { unlink, glob } from "fs/promises";
4878
- async function cleanupAfterSessionCascade(sessionId, result, embeddingManager, vaultDir) {
4879
- try {
4880
- embeddingManager.onRemoved("sessions", sessionId);
4881
- } catch {
4882
- }
4883
- for (const sporeId of result.deletedSporeIds) {
4884
- try {
4885
- embeddingManager.onRemoved("spores", sporeId);
4886
- } catch {
4887
- }
4888
- }
4889
- try {
4890
- for await (const f of glob(`sessions/**/session-${sessionId}.md`, { cwd: vaultDir })) {
4891
- await unlink(`${vaultDir}/${f}`).catch(() => {
4892
- });
4893
- }
4894
- } catch {
4895
- }
4896
- for (const sporeId of result.deletedSporeIds) {
4897
- try {
4898
- for await (const f of glob(`spores/**/${sporeId}*.md`, { cwd: vaultDir })) {
4899
- await unlink(`${vaultDir}/${f}`).catch(() => {
4900
- });
4901
- }
4902
- } catch {
4903
- }
4904
- }
4905
- for (const filePath of result.deletedAttachmentPaths) {
4906
- try {
4907
- await unlink(filePath);
4908
- } catch {
4909
- }
4910
- }
4911
- }
4912
-
4913
5669
  // src/daemon/jobs/session-maintenance.ts
4914
5670
  var STALE_SESSION_THRESHOLD_S = STALE_SESSION_THRESHOLD_MS / MS_PER_SECOND;
4915
5671
  function completeStaleActiveSessions(registeredSessionIds) {
@@ -4965,11 +5721,65 @@ async function runSessionMaintenance(deps) {
4965
5721
  }
4966
5722
  }
4967
5723
 
4968
- // src/daemon/main.ts
4969
- import fs13 from "fs";
4970
- import os8 from "os";
4971
- import path15 from "path";
4972
- var AGENT_RUNS_DEFAULT_LIMIT = 50;
5724
+ // src/daemon/power-jobs.ts
5725
+ function registerPowerJobs(powerManager, deps) {
5726
+ const { embeddingManager, registry, logger, config, db, backupDir, machineId, vaultDir } = deps;
5727
+ let reconcileRunning = false;
5728
+ powerManager.register({
5729
+ name: "embedding-reconcile",
5730
+ runIn: ["active", "idle"],
5731
+ fn: async () => {
5732
+ if (reconcileRunning) return;
5733
+ reconcileRunning = true;
5734
+ try {
5735
+ await embeddingManager.reconcile(EMBEDDING_BATCH_SIZE);
5736
+ } finally {
5737
+ reconcileRunning = false;
5738
+ }
5739
+ }
5740
+ });
5741
+ powerManager.register({
5742
+ name: "session-maintenance",
5743
+ runIn: ["active", "idle", "sleep"],
5744
+ fn: () => runSessionMaintenance({
5745
+ logger,
5746
+ registeredSessionIds: () => registry.sessions,
5747
+ embeddingManager,
5748
+ vaultDir
5749
+ })
5750
+ });
5751
+ powerManager.register({
5752
+ name: "log-retention",
5753
+ runIn: ["idle", "sleep"],
5754
+ fn: async () => {
5755
+ const retentionDays = config.daemon.log_retention_days;
5756
+ const cutoff = new Date(Date.now() - retentionDays * MS_PER_DAY).toISOString();
5757
+ const deleted = deleteOldLogs(cutoff);
5758
+ if (deleted > 0) {
5759
+ logger.info(LOG_KINDS.LOG_RETENTION, `Deleted ${deleted} log entries older than ${retentionDays} days`, { deleted, retention_days: retentionDays });
5760
+ }
5761
+ }
5762
+ });
5763
+ powerManager.register({
5764
+ name: "auto-backup",
5765
+ runIn: ["idle", "sleep"],
5766
+ fn: async () => {
5767
+ try {
5768
+ logger.info(LOG_KINDS.BACKUP_START, "Auto-backup starting");
5769
+ const filePath = createBackup(db, backupDir, machineId);
5770
+ logger.info(LOG_KINDS.BACKUP_COMPLETE, "Auto-backup complete", { file_path: filePath });
5771
+ } catch (err) {
5772
+ logger.error(LOG_KINDS.BACKUP_ERROR, "Auto-backup failed", { error: err.message });
5773
+ }
5774
+ }
5775
+ });
5776
+ }
5777
+
5778
+ // src/daemon/reconciliation.ts
5779
+ import fs15 from "fs";
5780
+ import path17 from "path";
5781
+
5782
+ // src/daemon/event-handlers.ts
4973
5783
  var TOOL_INPUT_STORE_LIMIT = 4e3;
4974
5784
  var TOOL_OUTPUT_STORE_LIMIT = 2e3;
4975
5785
  var TITLE_PREVIEW_CHARS = 80;
@@ -4981,7 +5791,6 @@ function isSystemMessage(prompt) {
4981
5791
  const trimmed = prompt.trimStart();
4982
5792
  return SYSTEM_MESSAGE_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
4983
5793
  }
4984
- var REPLAYABLE_EVENT_TYPES = /* @__PURE__ */ new Set(["user_prompt", "tool_use", "tool_failure"]);
4985
5794
  function handleUserPrompt(sessionId, prompt) {
4986
5795
  const now = epochSeconds();
4987
5796
  closeOpenBatches(sessionId, now);
@@ -5069,179 +5878,218 @@ function handleStopFailure(sessionId, error, errorDetails) {
5069
5878
  tool_output_summary: errorDetails?.slice(0, TOOL_OUTPUT_STORE_LIMIT) ?? null,
5070
5879
  success: 0,
5071
5880
  error_message: error?.slice(0, TOOL_OUTPUT_STORE_LIMIT) ?? null,
5072
- timestamp: now,
5073
- created_at: now
5074
- });
5075
- }
5076
- function handleTaskCompleted(sessionId, taskId, taskSubject, taskDescription) {
5077
- const now = epochSeconds();
5078
- insertActivityWithBatch({
5079
- session_id: sessionId,
5080
- tool_name: "task_completed",
5081
- tool_input: JSON.stringify({ task_id: taskId, task_subject: taskSubject, task_description: taskDescription }).slice(0, TOOL_INPUT_STORE_LIMIT),
5082
- tool_output_summary: taskSubject?.slice(0, TOOL_OUTPUT_STORE_LIMIT) ?? null,
5083
- timestamp: now,
5084
- created_at: now
5085
- });
5086
- }
5087
- function handleCompact(sessionId, phase, trigger, compactSummary) {
5088
- const now = epochSeconds();
5089
- insertActivityWithBatch({
5090
- session_id: sessionId,
5091
- tool_name: `${phase}_compact`,
5092
- tool_input: trigger ? JSON.stringify({ trigger }).slice(0, TOOL_INPUT_STORE_LIMIT) : null,
5093
- tool_output_summary: compactSummary?.slice(0, TOOL_OUTPUT_STORE_LIMIT) ?? null,
5094
- timestamp: now,
5095
- created_at: now
5096
- });
5097
- }
5098
- function killStaleDaemon(vaultDir, logger) {
5099
- const daemonJsonPath = path15.join(vaultDir, "daemon.json");
5100
- try {
5101
- if (!fs13.existsSync(daemonJsonPath)) return;
5102
- const info = JSON.parse(fs13.readFileSync(daemonJsonPath, "utf-8"));
5103
- if (!info.pid) return;
5104
- if (info.pid === process.pid) return;
5105
- try {
5106
- process.kill(info.pid, 0);
5107
- process.kill(info.pid, "SIGTERM");
5108
- logger.info(LOG_KINDS.DAEMON_START, "Killed stale daemon", { pid: info.pid });
5109
- } catch {
5110
- }
5111
- fs13.unlinkSync(daemonJsonPath);
5112
- } catch {
5113
- }
5114
- }
5115
- async function main() {
5116
- const vaultArg = process.argv.find((_, i) => process.argv[i - 1] === "--vault");
5117
- if (!vaultArg) {
5118
- process.stderr.write("Usage: mycod --vault <path>\n");
5119
- process.exit(1);
5120
- }
5121
- const vaultDir = path15.resolve(vaultArg);
5122
- loadSecrets(vaultDir);
5123
- const config = loadConfig(vaultDir);
5124
- const manifests = loadManifests();
5125
- const symbiontPlanDirs = manifests.flatMap((m) => m.capture?.planDirs ?? []);
5126
- const projectRoot = process.cwd();
5127
- let planWatchConfig = {
5128
- watchDirs: [.../* @__PURE__ */ new Set([...symbiontPlanDirs, ...config.capture.plan_dirs ?? []])],
5129
- projectRoot,
5130
- extensions: config.capture.artifact_extensions
5131
- };
5132
- const logger = new DaemonLogger(path15.join(vaultDir, "logs"), {
5133
- level: config.daemon.log_level
5134
- });
5135
- killStaleDaemon(vaultDir, logger);
5136
- logger.info(LOG_KINDS.DAEMON_CONFIG, "Config loaded", {
5137
- vault: vaultDir,
5138
- embedding_provider: config.embedding.provider
5881
+ timestamp: now,
5882
+ created_at: now
5139
5883
  });
5140
- logger.info(LOG_KINDS.CAPTURE_PLAN, "Plan watch directories", { dirs: planWatchConfig.watchDirs });
5141
- const machineId = getMachineId(vaultDir);
5142
- logger.info(LOG_KINDS.DAEMON_START, "Machine ID resolved", { machine_id: machineId });
5143
- const db = initDatabase(vaultDbPath(vaultDir));
5144
- createSchema(db, machineId);
5145
- registerBuiltinDomains();
5146
- logger.info(LOG_KINDS.DAEMON_START, "SQLite initialized", { vault: vaultDir });
5147
- initTeamContext(config.team.enabled, machineId);
5148
- logger.setPersistFn((entry) => {
5149
- const { timestamp, level, kind, component, message, ...rest } = entry;
5150
- insertLogEntry({
5151
- timestamp,
5152
- level,
5153
- kind,
5154
- component,
5155
- message,
5156
- data: Object.keys(rest).length > 0 ? JSON.stringify(rest) : null,
5157
- session_id: rest.session_id ?? null
5158
- });
5884
+ }
5885
+ function handleTaskCompleted(sessionId, taskId, taskSubject, taskDescription) {
5886
+ const now = epochSeconds();
5887
+ insertActivityWithBatch({
5888
+ session_id: sessionId,
5889
+ tool_name: "task_completed",
5890
+ tool_input: JSON.stringify({ task_id: taskId, task_subject: taskSubject, task_description: taskDescription }).slice(0, TOOL_INPUT_STORE_LIMIT),
5891
+ tool_output_summary: taskSubject?.slice(0, TOOL_OUTPUT_STORE_LIMIT) ?? null,
5892
+ timestamp: now,
5893
+ created_at: now
5159
5894
  });
5160
- const lastLogTimestamp = getMaxTimestamp();
5161
- if (lastLogTimestamp) {
5162
- const logDir = path15.join(vaultDir, "logs");
5163
- const replayedCount = reconcileLogBuffer(logDir, lastLogTimestamp);
5164
- if (replayedCount > 0) {
5165
- logger.info(LOG_KINDS.DAEMON_RECONCILE, `Replayed ${replayedCount} log entries from buffer`, { replayed: replayedCount });
5895
+ }
5896
+ function handleCompact(sessionId, phase, trigger, compactSummary) {
5897
+ const now = epochSeconds();
5898
+ insertActivityWithBatch({
5899
+ session_id: sessionId,
5900
+ tool_name: `${phase}_compact`,
5901
+ tool_input: trigger ? JSON.stringify({ trigger }).slice(0, TOOL_INPUT_STORE_LIMIT) : null,
5902
+ tool_output_summary: compactSummary?.slice(0, TOOL_OUTPUT_STORE_LIMIT) ?? null,
5903
+ timestamp: now,
5904
+ created_at: now
5905
+ });
5906
+ }
5907
+
5908
+ // src/daemon/reconciliation.ts
5909
+ var REPLAYABLE_EVENT_TYPES = /* @__PURE__ */ new Set(["user_prompt", "tool_use", "tool_failure"]);
5910
+ function createReconciler({ bufferDir, logger }) {
5911
+ const reconciledSessions = /* @__PURE__ */ new Set();
5912
+ function replayEvent(sessionId, event) {
5913
+ if (event.type === "user_prompt") {
5914
+ if (isSystemMessage(String(event.prompt ?? ""))) return null;
5915
+ handleUserPrompt(sessionId, String(event.prompt ?? ""));
5916
+ return "prompt";
5166
5917
  }
5918
+ if (event.type === "tool_use") {
5919
+ handleToolUse(
5920
+ sessionId,
5921
+ String(event.tool_name ?? ""),
5922
+ event.tool_input,
5923
+ typeof event.output_preview === "string" ? event.output_preview : void 0
5924
+ );
5925
+ return "activity";
5926
+ }
5927
+ if (event.type === "tool_failure") {
5928
+ handleToolFailure(
5929
+ sessionId,
5930
+ String(event.tool_name ?? ""),
5931
+ event.tool_input,
5932
+ typeof event.error === "string" ? event.error : void 0,
5933
+ !!event.is_interrupt
5934
+ );
5935
+ return "activity";
5936
+ }
5937
+ return null;
5167
5938
  }
5168
- const vectorsDbPath = path15.join(vaultDir, "vectors.db");
5169
- const vectorStore = new SqliteVecVectorStore(vectorsDbPath);
5170
- const llmProvider = createEmbeddingProvider(config.embedding);
5171
- const embeddingProvider = new EmbeddingProviderAdapter(llmProvider, config.embedding);
5172
- const recordSource = new SqliteRecordSource();
5173
- const embeddingManager = new EmbeddingManager(vectorStore, embeddingProvider, recordSource, logger);
5174
- logger.info(LOG_KINDS.EMBEDDING_EMBED, "EmbeddingManager initialized", { vectors_db: vectorsDbPath });
5175
- let definitionsDir;
5176
- try {
5177
- const { registerBuiltInAgentsAndTasks, resolveDefinitionsDir: resolveDefinitionsDir2 } = await import("./loader-WQKVWL5D.js");
5178
- definitionsDir = resolveDefinitionsDir2();
5179
- await registerBuiltInAgentsAndTasks(definitionsDir, vaultDir);
5180
- logger.info(LOG_KINDS.AGENT_TASK, "Built-in agents and tasks registered");
5181
- } catch (err) {
5182
- logger.warn(LOG_KINDS.AGENT_ERROR, "Failed to register built-in agents/tasks", { error: err.message });
5183
- }
5184
- try {
5185
- const staleDb = getDatabase();
5186
- const staleRows = staleDb.prepare(
5187
- `SELECT id FROM agent_runs WHERE status = 'running'`
5188
- ).all();
5189
- if (staleRows.length > 0) {
5190
- staleDb.prepare(
5191
- `UPDATE agent_runs SET status = 'failed', completed_at = ?, error = 'Daemon restarted while run was in progress' WHERE status = 'running'`
5192
- ).run(epochSeconds());
5193
- logger.info(LOG_KINDS.AGENT_RUN, "Cleaned stale running agent runs", {
5194
- count: staleRows.length,
5195
- ids: staleRows.map((r) => r.id)
5939
+ function reconcileSession(sessionId) {
5940
+ if (reconciledSessions.has(sessionId)) return;
5941
+ reconciledSessions.add(sessionId);
5942
+ const bufferPath = path17.join(bufferDir, `${sessionId}.jsonl`);
5943
+ let content;
5944
+ try {
5945
+ content = fs15.readFileSync(bufferPath, "utf-8").trim();
5946
+ } catch {
5947
+ return;
5948
+ }
5949
+ if (!content) return;
5950
+ if (!getSession(sessionId)) {
5951
+ logger.debug(LOG_KINDS.LIFECYCLE_RECONCILE, "Skipping reconciliation for deleted session", { session_id: sessionId });
5952
+ return;
5953
+ }
5954
+ const allEvents = content.split("\n").map((line) => JSON.parse(line));
5955
+ const existingBatchCount = listBatchesBySession(sessionId).length;
5956
+ let promptsSeen = 0;
5957
+ let replayStartIndex = -1;
5958
+ for (let i = 0; i < allEvents.length; i++) {
5959
+ const e = allEvents[i];
5960
+ if (e.type === "user_prompt" && !isSystemMessage(String(e.prompt ?? ""))) {
5961
+ promptsSeen++;
5962
+ if (promptsSeen === existingBatchCount + 1) {
5963
+ replayStartIndex = i;
5964
+ break;
5965
+ }
5966
+ }
5967
+ }
5968
+ if (replayStartIndex === -1) return;
5969
+ const eventsToReplay = allEvents.slice(replayStartIndex).filter(
5970
+ (e) => REPLAYABLE_EVENT_TYPES.has(String(e.type))
5971
+ );
5972
+ let promptsRecovered = 0;
5973
+ let activitiesRecovered = 0;
5974
+ for (const event of eventsToReplay) {
5975
+ try {
5976
+ const result = replayEvent(sessionId, event);
5977
+ if (result === "prompt") promptsRecovered++;
5978
+ else if (result === "activity") activitiesRecovered++;
5979
+ } catch (err) {
5980
+ logger.warn(LOG_KINDS.LIFECYCLE_RECONCILE, "Reconciliation: failed to replay event", {
5981
+ type: String(event.type),
5982
+ error: String(err)
5983
+ });
5984
+ }
5985
+ }
5986
+ if (promptsRecovered > 0 || activitiesRecovered > 0) {
5987
+ logger.info(LOG_KINDS.LIFECYCLE_RECONCILE, "Buffer reconciliation complete", {
5988
+ session_id: sessionId,
5989
+ prompts_recovered: promptsRecovered,
5990
+ activities_recovered: activitiesRecovered
5196
5991
  });
5197
5992
  }
5198
- } catch (err) {
5199
- logger.warn(LOG_KINDS.AGENT_ERROR, "Failed to clean stale runs", { error: err.message });
5200
5993
  }
5201
- let uiDir = null;
5202
- {
5203
- const root = findPackageRoot(path15.dirname(new URL(import.meta.url).pathname));
5204
- if (root) {
5205
- const candidate = path15.join(root, "dist", "ui");
5206
- if (fs13.existsSync(candidate)) uiDir = candidate;
5994
+ function runStartupReconciliation() {
5995
+ const startupCleanedCount = cleanStaleBuffers(bufferDir, STALE_BUFFER_MAX_AGE_MS);
5996
+ if (startupCleanedCount > 0) {
5997
+ logger.info(LOG_KINDS.CAPTURE_BUFFER, "Buffer cleanup complete", { stale_removed: startupCleanedCount });
5998
+ }
5999
+ for (const sessionId of listBufferSessionIds(bufferDir)) {
6000
+ try {
6001
+ reconcileSession(sessionId);
6002
+ } catch (err) {
6003
+ logger.warn(LOG_KINDS.LIFECYCLE_RECONCILE, "Startup reconciliation failed", { session_id: sessionId, error: String(err) });
6004
+ }
5207
6005
  }
5208
6006
  }
5209
- if (uiDir) {
5210
- logger.debug(LOG_KINDS.DAEMON_START, "Static UI directory found", { path: uiDir });
6007
+ function clearSession(sessionId) {
6008
+ reconciledSessions.delete(sessionId);
5211
6009
  }
5212
- const powerManager = new PowerManager({
5213
- idleThresholdMs: POWER_IDLE_THRESHOLD_MS,
5214
- sleepThresholdMs: POWER_SLEEP_THRESHOLD_MS,
5215
- deepSleepThresholdMs: POWER_DEEP_SLEEP_THRESHOLD_MS,
5216
- activeIntervalMs: POWER_ACTIVE_INTERVAL_MS,
5217
- sleepIntervalMs: POWER_SLEEP_INTERVAL_MS,
5218
- logger
5219
- });
5220
- const server = new DaemonServer({
5221
- vaultDir,
5222
- logger,
5223
- uiDir: uiDir ?? void 0
5224
- // Don't record activity on every HTTP request — UI polling (every 3-10s)
5225
- // would prevent the PowerManager from ever reaching 'idle' state, blocking
5226
- // all idle-only scheduled tasks (skill-survey, skill-generate, skill-evolve).
5227
- // Activity is recorded on meaningful events below (session register, prompt capture, etc.).
5228
- });
5229
- const registry = new SessionRegistry({
5230
- gracePeriod: 0,
5231
- onEmpty: () => {
5232
- }
5233
- });
5234
- const transcriptMiner = new TranscriptMiner({
5235
- additionalAdapters: config.capture.transcript_paths.map(
5236
- (p) => createPerProjectAdapter(p, claudeCodeAdapter.parseTurns)
6010
+ return { reconcileSession, replayEvent, runStartupReconciliation, clearSession };
6011
+ }
6012
+
6013
+ // src/daemon/stop-processing.ts
6014
+ import fs16 from "fs";
6015
+
6016
+ // src/daemon/skill-usage.ts
6017
+ import crypto from "crypto";
6018
+ var SKILL_USAGE_DETECTION_ENABLED = false;
6019
+ var MAX_ACTIVE_SKILLS_CHECK = 1e3;
6020
+ function detectSkillUsage(sessionId, transcriptContent) {
6021
+ if (transcriptContent.includes("vault_write_skill")) return;
6022
+ if (!SKILL_USAGE_DETECTION_ENABLED) return;
6023
+ const activeSkills = listSkillRecords({ status: "active", limit: MAX_ACTIVE_SKILLS_CHECK });
6024
+ if (activeSkills.length === 0) return;
6025
+ const skillPatterns = activeSkills.map((skill) => ({
6026
+ skill,
6027
+ pattern: new RegExp(
6028
+ `skills/${escapeRegex(skill.name)}/SKILL\\.md|<skill[^>]*name=["']${escapeRegex(skill.name)}["']`
5237
6029
  )
5238
- });
6030
+ }));
6031
+ const now = epochSeconds();
6032
+ for (const { skill, pattern } of skillPatterns) {
6033
+ try {
6034
+ if (!pattern.test(transcriptContent)) continue;
6035
+ if (hasUsageForSkillAndSession(skill.id, sessionId)) continue;
6036
+ insertSkillUsage({
6037
+ id: crypto.randomUUID(),
6038
+ skill_id: skill.id,
6039
+ session_id: sessionId,
6040
+ detected_at: now
6041
+ });
6042
+ incrementSkillUsageCount(skill.id, now);
6043
+ } catch {
6044
+ }
6045
+ }
6046
+ }
6047
+ function escapeRegex(s) {
6048
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6049
+ }
6050
+
6051
+ // src/daemon/stop-processing.ts
6052
+ function enrichTurnsWithToolMetadata(turns, events) {
6053
+ if (events.length === 0 || turns.length === 0) return;
6054
+ const toolEvents = events.filter((e) => e.type === "tool_use");
6055
+ if (toolEvents.length === 0) return;
6056
+ let cursor = 0;
6057
+ for (let i = 0; i < turns.length; i++) {
6058
+ const turnEnd = i + 1 < turns.length ? turns[i + 1].timestamp : null;
6059
+ const breakdown = {};
6060
+ const files = /* @__PURE__ */ new Set();
6061
+ while (cursor < toolEvents.length) {
6062
+ const ts = String(toolEvents[cursor].timestamp ?? "");
6063
+ if (turnEnd !== null && ts >= turnEnd) break;
6064
+ const evt = toolEvents[cursor];
6065
+ const toolName = String(evt.tool_name ?? evt.tool ?? "unknown");
6066
+ breakdown[toolName] = (breakdown[toolName] ?? 0) + 1;
6067
+ const input = evt.tool_input;
6068
+ const filePath = input?.file_path ?? input?.path;
6069
+ if (typeof filePath === "string") files.add(filePath);
6070
+ cursor++;
6071
+ }
6072
+ if (Object.keys(breakdown).length > 0) {
6073
+ turns[i].toolBreakdown = breakdown;
6074
+ if (files.size > 0) turns[i].files = [...files];
6075
+ }
6076
+ }
6077
+ }
6078
+ function createStopProcessor(deps) {
6079
+ const { registry, sessionBuffers, transcriptMiner, embeddingManager, logger, config, vaultDir } = deps;
5239
6080
  let activeStopProcessing = null;
5240
6081
  const sessionTitleCache = /* @__PURE__ */ new Map();
6082
+ const StopBody = external_exports.object({
6083
+ session_id: external_exports.string(),
6084
+ user: external_exports.string().optional(),
6085
+ transcript_path: external_exports.string().optional(),
6086
+ last_assistant_message: external_exports.string().optional()
6087
+ });
5241
6088
  async function triggerTitleSummary(sessionId) {
5242
6089
  if (config.agent.summary_batch_interval <= 0) return;
6090
+ if (config.agent.event_tasks_enabled === false) return;
5243
6091
  try {
5244
- const { runAgent } = await import("./executor-HKNINUWO.js");
6092
+ const { runAgent } = await import("./executor-4OXDK4ZA.js");
5245
6093
  runAgent(vaultDir, {
5246
6094
  task: "title-summary",
5247
6095
  instruction: `Process session ${sessionId} only`,
@@ -5250,166 +6098,277 @@ async function main() {
5250
6098
  } catch {
5251
6099
  }
5252
6100
  }
5253
- const bufferDir = path15.join(vaultDir, "buffer");
5254
- const sessionBuffers = /* @__PURE__ */ new Map();
5255
- const startupCleanedCount = cleanStaleBuffers(bufferDir, STALE_BUFFER_MAX_AGE_MS);
5256
- if (startupCleanedCount > 0) {
5257
- logger.info(LOG_KINDS.CAPTURE_BUFFER, "Buffer cleanup complete", { stale_removed: startupCleanedCount });
5258
- }
5259
- const reconciledSessions = /* @__PURE__ */ new Set();
5260
- for (const sessionId of listBufferSessionIds(bufferDir)) {
5261
- try {
5262
- reconcileSession(sessionId);
5263
- } catch (err) {
5264
- logger.warn(LOG_KINDS.LIFECYCLE_RECONCILE, "Startup reconciliation failed", { session_id: sessionId, error: String(err) });
6101
+ async function processStopEvent(sessionId, user, sessionMeta, hookTranscriptPath, lastAssistantMessage) {
6102
+ const transcriptResult = transcriptMiner.getAllTurnsWithSource(sessionId, hookTranscriptPath);
6103
+ let allTurns = transcriptResult.turns;
6104
+ let turnSource = transcriptResult.source;
6105
+ const bufferEvents = sessionBuffers.get(sessionId)?.readAll() ?? [];
6106
+ if (allTurns.length === 0) {
6107
+ allTurns = extractTurnsFromBuffer(bufferEvents);
6108
+ turnSource = "buffer";
6109
+ } else if (bufferEvents.length > 0) {
6110
+ const lastTranscriptTs = allTurns[allTurns.length - 1].timestamp;
6111
+ if (lastTranscriptTs) {
6112
+ const newerEvents = bufferEvents.filter(
6113
+ (e) => String(e.timestamp ?? "") > lastTranscriptTs
6114
+ );
6115
+ if (newerEvents.length > 0) {
6116
+ const bufferTurns = extractTurnsFromBuffer(newerEvents);
6117
+ allTurns = [...allTurns, ...bufferTurns];
6118
+ turnSource = `${transcriptResult.source}+buffer`;
6119
+ logger.info(LOG_KINDS.PROCESSOR_TRANSCRIPT, "Appended buffer turns missing from transcript", {
6120
+ session_id: sessionId,
6121
+ transcriptTurns: transcriptResult.turns.length,
6122
+ bufferTurns: bufferTurns.length
6123
+ });
6124
+ }
6125
+ }
5265
6126
  }
5266
- }
5267
- function replayEvent(sessionId, event) {
5268
- if (event.type === "user_prompt") {
5269
- if (isSystemMessage(String(event.prompt ?? ""))) return null;
5270
- handleUserPrompt(sessionId, String(event.prompt ?? ""));
5271
- return "prompt";
6127
+ if (lastAssistantMessage && allTurns.length > 0) {
6128
+ const lastTurn = allTurns[allTurns.length - 1];
6129
+ if (!lastTurn.aiResponse) {
6130
+ lastTurn.aiResponse = lastAssistantMessage;
6131
+ }
5272
6132
  }
5273
- if (event.type === "tool_use") {
5274
- handleToolUse(
5275
- sessionId,
5276
- String(event.tool_name ?? ""),
5277
- event.tool_input,
5278
- typeof event.output_preview === "string" ? event.output_preview : void 0
5279
- );
5280
- return "activity";
6133
+ enrichTurnsWithToolMetadata(allTurns, bufferEvents);
6134
+ const imageCount = allTurns.reduce((sum, t) => sum + (t.images?.length ?? 0), 0);
6135
+ logger.debug(LOG_KINDS.PROCESSOR_TRANSCRIPT, "Transcript parsed", {
6136
+ session_id: sessionId,
6137
+ turn_count: allTurns.length,
6138
+ image_count: imageCount
6139
+ });
6140
+ const latestBatch = getLatestBatch(sessionId);
6141
+ if (lastAssistantMessage && latestBatch && !latestBatch.response_summary) {
6142
+ try {
6143
+ setResponseSummary(latestBatch.id, lastAssistantMessage);
6144
+ } catch (err) {
6145
+ logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to set response_summary on latest batch", { error: String(err) });
6146
+ }
5281
6147
  }
5282
- if (event.type === "tool_failure") {
5283
- handleToolFailure(
5284
- sessionId,
5285
- String(event.tool_name ?? ""),
5286
- event.tool_input,
5287
- typeof event.error === "string" ? event.error : void 0,
5288
- !!event.is_interrupt
5289
- );
5290
- return "activity";
6148
+ closeOpenBatches(sessionId, epochSeconds());
6149
+ const existingSession = getSession(sessionId);
6150
+ const hasTitle = existingSession?.title !== null && existingSession?.title !== void 0;
6151
+ if (!hasTitle) {
6152
+ let title = sessionTitleCache.get(sessionId) ?? null;
6153
+ if (!title) {
6154
+ const firstBatch = listBatchesBySession(sessionId, { limit: 1 })[0];
6155
+ if (firstBatch?.user_prompt) {
6156
+ title = firstBatch.user_prompt.slice(0, TITLE_PREVIEW_CHARS);
6157
+ if (firstBatch.user_prompt.length > TITLE_PREVIEW_CHARS) {
6158
+ title += "...";
6159
+ }
6160
+ sessionTitleCache.set(sessionId, title);
6161
+ }
6162
+ }
5291
6163
  }
5292
- return null;
5293
- }
5294
- function reconcileSession(sessionId) {
5295
- if (reconciledSessions.has(sessionId)) return;
5296
- reconciledSessions.add(sessionId);
5297
- const bufferPath = path15.join(bufferDir, `${sessionId}.jsonl`);
5298
- if (!fs13.existsSync(bufferPath)) return;
5299
- const content = fs13.readFileSync(bufferPath, "utf-8").trim();
5300
- if (!content) return;
5301
- if (!getSession(sessionId)) {
5302
- logger.debug(LOG_KINDS.LIFECYCLE_RECONCILE, "Skipping reconciliation for deleted session", { session_id: sessionId });
5303
- return;
6164
+ const currentSession = getSession(sessionId);
6165
+ const transcriptPromptCount = allTurns.length;
6166
+ const transcriptToolCount = allTurns.reduce((sum, t) => sum + t.toolCount, 0);
6167
+ const updateFields = {
6168
+ transcript_path: hookTranscriptPath ?? null,
6169
+ prompt_count: Math.max(transcriptPromptCount, currentSession?.prompt_count ?? 0),
6170
+ tool_count: Math.max(transcriptToolCount, currentSession?.tool_count ?? 0)
6171
+ };
6172
+ if (user) updateFields.user = user;
6173
+ if (!hasTitle && sessionTitleCache.has(sessionId)) {
6174
+ updateFields.title = sessionTitleCache.get(sessionId);
5304
6175
  }
5305
- const allEvents = content.split("\n").map((line) => JSON.parse(line));
5306
- const existingBatchCount = listBatchesBySession(sessionId).length;
5307
- let promptsSeen = 0;
5308
- let replayStartIndex = -1;
5309
- for (let i = 0; i < allEvents.length; i++) {
5310
- const e = allEvents[i];
5311
- if (e.type === "user_prompt" && !isSystemMessage(String(e.prompt ?? ""))) {
5312
- promptsSeen++;
5313
- if (promptsSeen === existingBatchCount + 1) {
5314
- replayStartIndex = i;
5315
- break;
6176
+ updateSession(sessionId, updateFields);
6177
+ if (SKILL_USAGE_DETECTION_ENABLED) {
6178
+ try {
6179
+ let transcriptText = null;
6180
+ if (hookTranscriptPath) {
6181
+ try {
6182
+ transcriptText = fs16.readFileSync(hookTranscriptPath, "utf-8");
6183
+ } catch {
6184
+ }
6185
+ }
6186
+ if (!transcriptText && allTurns.length > 0) {
6187
+ transcriptText = allTurns.map((t) => [t.prompt ?? "", t.aiResponse ?? ""].join(" ")).join("\n");
5316
6188
  }
6189
+ if (transcriptText) {
6190
+ detectSkillUsage(sessionId, transcriptText);
6191
+ }
6192
+ } catch {
5317
6193
  }
5318
6194
  }
5319
- if (replayStartIndex === -1) return;
5320
- const eventsToReplay = allEvents.slice(replayStartIndex).filter(
5321
- (e) => REPLAYABLE_EVENT_TYPES.has(String(e.type))
5322
- );
5323
- let promptsRecovered = 0;
5324
- let activitiesRecovered = 0;
5325
- for (const event of eventsToReplay) {
6195
+ const responses = [];
6196
+ for (let i = 0; i < allTurns.length; i++) {
6197
+ if (allTurns[i].aiResponse) {
6198
+ responses.push({ turnIndex: i + 1, response: allTurns[i].aiResponse });
6199
+ }
6200
+ }
6201
+ if (responses.length > 0) {
5326
6202
  try {
5327
- const result = replayEvent(sessionId, event);
5328
- if (result === "prompt") promptsRecovered++;
5329
- else if (result === "activity") activitiesRecovered++;
6203
+ populateBatchResponses(sessionId, responses);
5330
6204
  } catch (err) {
5331
- logger.warn(LOG_KINDS.LIFECYCLE_RECONCILE, "Reconciliation: failed to replay event", {
5332
- type: String(event.type),
5333
- error: String(err)
5334
- });
6205
+ logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to populate batch responses", { error: String(err) });
5335
6206
  }
5336
6207
  }
5337
- if (promptsRecovered > 0 || activitiesRecovered > 0) {
5338
- logger.info(LOG_KINDS.LIFECYCLE_RECONCILE, "Buffer reconciliation complete", {
5339
- session_id: sessionId,
5340
- prompts_recovered: promptsRecovered,
5341
- activities_recovered: activitiesRecovered
5342
- });
6208
+ if (!hasTitle) {
6209
+ triggerTitleSummary(sessionId);
6210
+ }
6211
+ const sessionShort = sessionId.slice(-6);
6212
+ for (let i = 0; i < allTurns.length; i++) {
6213
+ const turn = allTurns[i];
6214
+ if (!turn.images?.length) continue;
6215
+ const isLastTurn = i === allTurns.length - 1;
6216
+ let resolvedBatchId = null;
6217
+ let resolvedPromptNumber = i + 1;
6218
+ if (isLastTurn && latestBatch) {
6219
+ resolvedBatchId = latestBatch.id;
6220
+ resolvedPromptNumber = latestBatch.prompt_number ?? resolvedPromptNumber;
6221
+ } else if (turn.prompt) {
6222
+ try {
6223
+ const match = findBatchByPromptPrefix(sessionId, turn.prompt);
6224
+ if (match) {
6225
+ resolvedBatchId = match.id;
6226
+ resolvedPromptNumber = match.prompt_number;
6227
+ }
6228
+ } catch {
6229
+ }
6230
+ }
6231
+ for (let j = 0; j < turn.images.length; j++) {
6232
+ const img = turn.images[j];
6233
+ const ext = extensionForMimeType(img.mediaType);
6234
+ const filename = `${sessionShort}-t${resolvedPromptNumber}-${j + 1}.${ext}`;
6235
+ const imageBuffer = Buffer.from(img.data, "base64");
6236
+ try {
6237
+ insertAttachment({
6238
+ id: `${sessionShort}-b${resolvedPromptNumber}-${j + 1}`,
6239
+ session_id: sessionId,
6240
+ prompt_batch_id: resolvedBatchId ?? void 0,
6241
+ file_path: filename,
6242
+ media_type: img.mediaType,
6243
+ data: imageBuffer,
6244
+ created_at: epochSeconds()
6245
+ });
6246
+ logger.debug(LOG_KINDS.CAPTURE_ATTACHMENT, "Image stored in DB", { filename, batch: resolvedPromptNumber });
6247
+ } catch (err) {
6248
+ logger.warn(LOG_KINDS.CAPTURE_ATTACHMENT, "Failed to record attachment", { error: String(err) });
6249
+ }
6250
+ }
5343
6251
  }
6252
+ logger.info(LOG_KINDS.PROCESSOR_SESSION, "Session captured", {
6253
+ session_id: sessionId,
6254
+ turns: allTurns.length,
6255
+ source: turnSource,
6256
+ title: existingSession?.title ?? sessionTitleCache.get(sessionId) ?? "(untitled)"
6257
+ });
5344
6258
  }
5345
- const RegisterBody = external_exports.object({
5346
- session_id: external_exports.string(),
5347
- agent: external_exports.string().optional(),
5348
- branch: external_exports.string().optional(),
5349
- started_at: external_exports.string().optional()
5350
- });
5351
- const UnregisterBody = external_exports.object({ session_id: external_exports.string() });
5352
- const EventBody = external_exports.object({ type: external_exports.string(), session_id: external_exports.string() }).passthrough();
5353
- const StopBody = external_exports.object({
5354
- session_id: external_exports.string(),
5355
- user: external_exports.string().optional(),
5356
- transcript_path: external_exports.string().optional(),
5357
- last_assistant_message: external_exports.string().optional()
5358
- });
5359
- server.registerRoute("POST", "/sessions/register", async (req) => {
5360
- powerManager.recordActivity();
5361
- const { session_id, agent, branch, started_at } = RegisterBody.parse(req.body);
5362
- const resolvedStartedAt = started_at ?? (/* @__PURE__ */ new Date()).toISOString();
5363
- registry.register(session_id, { started_at: resolvedStartedAt, branch });
5364
- server.updateDaemonJsonSessions(registry.sessions);
5365
- const now = epochSeconds();
5366
- const startedEpoch = Math.floor(new Date(resolvedStartedAt).getTime() / 1e3);
5367
- upsertSession({
5368
- id: session_id,
5369
- agent: agent ?? "claude-code",
5370
- user: null,
5371
- project_root: process.cwd(),
5372
- branch: branch ?? null,
5373
- started_at: startedEpoch,
5374
- created_at: now,
5375
- status: "active",
5376
- machine_id: machineId
6259
+ const handleStopRoute = async (req) => {
6260
+ const { session_id: sessionId, user, transcript_path: hookTranscriptPath, last_assistant_message: lastAssistantMessage } = StopBody.parse(req.body);
6261
+ if (!registry.getSession(sessionId)) {
6262
+ registry.register(sessionId, { started_at: (/* @__PURE__ */ new Date()).toISOString() });
6263
+ logger.debug(LOG_KINDS.LIFECYCLE_AUTO_REGISTER, "Auto-registered session from stop event", { session_id: sessionId });
6264
+ }
6265
+ const sessionMeta = registry.getSession(sessionId);
6266
+ logger.info(LOG_KINDS.HOOKS_STOP, "Stop received", {
6267
+ session_id: sessionId,
6268
+ has_transcript_path: !!hookTranscriptPath,
6269
+ has_response: !!lastAssistantMessage
5377
6270
  });
5378
- updateSession(session_id, { ended_at: null, status: "active" });
5379
- reconcileSession(session_id);
5380
- logger.info(LOG_KINDS.LIFECYCLE_REGISTER, "Session registered", { session_id, branch, started_at: started_at ?? null });
5381
- notify(vaultDir, {
5382
- domain: "sessions",
5383
- type: "session.started",
5384
- title: "Session started",
5385
- message: branch ? `Branch: ${branch}` : void 0,
5386
- link: `/sessions/${session_id}`,
5387
- metadata: { sessionId: session_id, agent: agent ?? "claude-code", branch }
5388
- }, config);
5389
- return { body: { ok: true, sessions: registry.sessions } };
6271
+ logger.debug(LOG_KINDS.HOOKS_STOP, "Stop event detail", {
6272
+ session_id: sessionId,
6273
+ transcript_path: hookTranscriptPath ?? null,
6274
+ last_message_preview: lastAssistantMessage?.slice(0, LOG_MESSAGE_PREVIEW_CHARS) ?? null
6275
+ });
6276
+ const run = () => processStopEvent(sessionId, user, sessionMeta, hookTranscriptPath, lastAssistantMessage).catch((err) => {
6277
+ logger.error(LOG_KINDS.PROCESSOR_SESSION, "Stop processing failed", { session_id: sessionId, error: err.message });
6278
+ });
6279
+ const prev = activeStopProcessing ?? Promise.resolve();
6280
+ activeStopProcessing = prev.then(run).finally(() => {
6281
+ activeStopProcessing = null;
6282
+ });
6283
+ return { body: { ok: true } };
6284
+ };
6285
+ return {
6286
+ handleStopRoute,
6287
+ clearSession: (sessionId) => {
6288
+ sessionTitleCache.delete(sessionId);
6289
+ },
6290
+ getActiveProcessing: () => activeStopProcessing,
6291
+ triggerTitleSummary
6292
+ };
6293
+ }
6294
+
6295
+ // src/daemon/event-dispatch.ts
6296
+ import fs17 from "fs";
6297
+ import path19 from "path";
6298
+
6299
+ // src/daemon/plan-capture.ts
6300
+ import { createHash as createHash4 } from "crypto";
6301
+ import os7 from "os";
6302
+ import path18 from "path";
6303
+ var FILE_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "Create"]);
6304
+ var HEADING_REGEX = /^#\s+(.+)$/m;
6305
+ var PLAN_ID_HASH_LENGTH = 16;
6306
+ function isInPlanDirectory(filePath, watchDirs, projectRoot) {
6307
+ const abs = path18.isAbsolute(filePath) ? filePath : path18.resolve(projectRoot, filePath);
6308
+ return watchDirs.some((dir) => {
6309
+ const expanded = dir.startsWith("~/") ? path18.join(os7.homedir(), dir.slice(2)) : dir;
6310
+ const absDir = path18.isAbsolute(expanded) ? expanded : path18.resolve(projectRoot, expanded);
6311
+ const prefix = absDir.endsWith(path18.sep) ? absDir : absDir + path18.sep;
6312
+ return abs === absDir || abs.startsWith(prefix);
5390
6313
  });
5391
- server.registerRoute("POST", "/sessions/unregister", async (req) => {
5392
- const { session_id } = UnregisterBody.parse(req.body);
5393
- registry.unregister(session_id);
5394
- cleanStaleBuffers(bufferDir, STALE_BUFFER_MAX_AGE_MS, session_id);
5395
- closeSession(session_id, epochSeconds());
5396
- sessionBuffers.delete(session_id);
5397
- sessionTitleCache.delete(session_id);
5398
- reconciledSessions.delete(session_id);
5399
- server.updateDaemonJsonSessions(registry.sessions);
5400
- logger.info(LOG_KINDS.LIFECYCLE_UNREGISTER, "Session unregistered", { session_id });
5401
- notify(vaultDir, {
5402
- domain: "sessions",
5403
- type: "session.ended",
5404
- title: "Session ended",
5405
- link: `/sessions/${session_id}`,
5406
- metadata: { sessionId: session_id }
5407
- }, config);
5408
- return { body: { ok: true, sessions: registry.sessions } };
6314
+ }
6315
+ function isPlanWriteEvent(toolName, toolInput, config) {
6316
+ if (!FILE_WRITE_TOOLS.has(toolName)) return null;
6317
+ const filePath = toolInput?.file_path ?? toolInput?.path;
6318
+ if (typeof filePath !== "string") return null;
6319
+ if (!isInPlanDirectory(filePath, config.watchDirs, config.projectRoot)) return null;
6320
+ if (config.extensions?.length) {
6321
+ const ext = path18.extname(filePath).toLowerCase();
6322
+ if (!config.extensions.includes(ext)) return null;
6323
+ }
6324
+ return filePath;
6325
+ }
6326
+ function parsePlanTitle(content, filename) {
6327
+ const match = HEADING_REGEX.exec(content);
6328
+ if (match) return match[1].trim();
6329
+ return filename ?? null;
6330
+ }
6331
+ function capturePlan(input) {
6332
+ const now = Math.floor(Date.now() / 1e3);
6333
+ const contentHash = createHash4(CONTENT_HASH_ALGORITHM).update(input.content).digest("hex");
6334
+ const id = createHash4("md5").update(input.sourcePath).digest("hex").slice(0, PLAN_ID_HASH_LENGTH);
6335
+ const title = parsePlanTitle(input.content, path18.basename(input.sourcePath));
6336
+ return upsertPlan({
6337
+ id,
6338
+ title,
6339
+ content: input.content,
6340
+ source_path: input.sourcePath,
6341
+ session_id: input.sessionId,
6342
+ prompt_batch_id: input.promptBatchId ?? null,
6343
+ content_hash: contentHash,
6344
+ status: "active",
6345
+ created_at: now,
6346
+ updated_at: now
5409
6347
  });
5410
- server.registerRoute("POST", "/events", async (req) => {
6348
+ }
6349
+
6350
+ // src/daemon/event-dispatch.ts
6351
+ var EventBody = external_exports.object({ type: external_exports.string(), session_id: external_exports.string() }).passthrough();
6352
+ function createEventDispatcher(deps) {
6353
+ const {
6354
+ registry,
6355
+ sessionBuffers,
6356
+ powerManager,
6357
+ logger,
6358
+ machineId,
6359
+ config,
6360
+ vaultDir,
6361
+ reconcileSession,
6362
+ planWatchConfig,
6363
+ triggerTitleSummary
6364
+ } = deps;
6365
+ const projectRoot = process.cwd();
6366
+ return async (req) => {
5411
6367
  const validated = EventBody.parse(req.body);
5412
- const event = { ...validated, timestamp: validated.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() };
6368
+ const event = {
6369
+ ...validated,
6370
+ timestamp: validated.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
6371
+ };
5413
6372
  logger.debug(LOG_KINDS.HOOKS_EVENT, "Event received", { type: event.type, session_id: event.session_id });
5414
6373
  if (!registry.getSession(event.session_id)) {
5415
6374
  registry.register(event.session_id, { started_at: event.timestamp });
@@ -5427,6 +6386,7 @@ async function main() {
5427
6386
  reconcileSession(event.session_id);
5428
6387
  }
5429
6388
  if (!sessionBuffers.has(event.session_id)) {
6389
+ const bufferDir = path19.join(vaultDir, "buffer");
5430
6390
  sessionBuffers.set(event.session_id, new EventBuffer(bufferDir, event.session_id));
5431
6391
  }
5432
6392
  sessionBuffers.get(event.session_id).append(event);
@@ -5470,10 +6430,10 @@ async function main() {
5470
6430
  );
5471
6431
  if (planFilePath) {
5472
6432
  const captureSessionId = event.session_id;
5473
- fs13.promises.readFile(planFilePath, "utf-8").then((planContent) => {
6433
+ fs17.promises.readFile(planFilePath, "utf-8").then((planContent) => {
5474
6434
  const latestBatch = getLatestBatch(captureSessionId);
5475
6435
  capturePlan({
5476
- sourcePath: path15.relative(projectRoot, planFilePath),
6436
+ sourcePath: path19.relative(projectRoot, planFilePath),
5477
6437
  content: planContent,
5478
6438
  sessionId: captureSessionId,
5479
6439
  promptBatchId: latestBatch?.id ?? null
@@ -5611,224 +6571,205 @@ async function main() {
5611
6571
  }
5612
6572
  }
5613
6573
  return { body: { ok: true } };
5614
- });
5615
- server.registerRoute("POST", "/events/stop", async (req) => {
5616
- const { session_id: sessionId, user, transcript_path: hookTranscriptPath, last_assistant_message: lastAssistantMessage } = StopBody.parse(req.body);
5617
- if (!registry.getSession(sessionId)) {
5618
- registry.register(sessionId, { started_at: (/* @__PURE__ */ new Date()).toISOString() });
5619
- logger.debug(LOG_KINDS.LIFECYCLE_AUTO_REGISTER, "Auto-registered session from stop event", { session_id: sessionId });
6574
+ };
6575
+ }
6576
+
6577
+ // src/daemon/main.ts
6578
+ import fs18 from "fs";
6579
+ import os8 from "os";
6580
+ import path20 from "path";
6581
+ function killStaleDaemon(vaultDir, logger) {
6582
+ const daemonJsonPath = path20.join(vaultDir, "daemon.json");
6583
+ try {
6584
+ if (!fs18.existsSync(daemonJsonPath)) return;
6585
+ const info = JSON.parse(fs18.readFileSync(daemonJsonPath, "utf-8"));
6586
+ if (!info.pid) return;
6587
+ if (info.pid === process.pid) return;
6588
+ try {
6589
+ process.kill(info.pid, 0);
6590
+ process.kill(info.pid, "SIGTERM");
6591
+ logger.info(LOG_KINDS.DAEMON_START, "Killed stale daemon", { pid: info.pid });
6592
+ } catch {
5620
6593
  }
5621
- const sessionMeta = registry.getSession(sessionId);
5622
- logger.info(LOG_KINDS.HOOKS_STOP, "Stop received", {
5623
- session_id: sessionId,
5624
- has_transcript_path: !!hookTranscriptPath,
5625
- has_response: !!lastAssistantMessage
5626
- });
5627
- logger.debug(LOG_KINDS.HOOKS_STOP, "Stop event detail", {
5628
- session_id: sessionId,
5629
- transcript_path: hookTranscriptPath ?? null,
5630
- last_message_preview: lastAssistantMessage?.slice(0, LOG_MESSAGE_PREVIEW_CHARS) ?? null
5631
- });
5632
- const run = () => processStopEvent(sessionId, user, sessionMeta, hookTranscriptPath, lastAssistantMessage).catch((err) => {
5633
- logger.error(LOG_KINDS.PROCESSOR_SESSION, "Stop processing failed", { session_id: sessionId, error: err.message });
5634
- });
5635
- const prev = activeStopProcessing ?? Promise.resolve();
5636
- activeStopProcessing = prev.then(run).finally(() => {
5637
- activeStopProcessing = null;
5638
- });
5639
- return { body: { ok: true } };
6594
+ fs18.unlinkSync(daemonJsonPath);
6595
+ } catch {
6596
+ }
6597
+ }
6598
+ async function main() {
6599
+ const vaultArg = process.argv.find((_, i) => process.argv[i - 1] === "--vault");
6600
+ if (!vaultArg) {
6601
+ process.stderr.write("Usage: mycod --vault <path>\n");
6602
+ process.exit(1);
6603
+ }
6604
+ const vaultDir = path20.resolve(vaultArg);
6605
+ loadSecrets(vaultDir);
6606
+ const config = loadConfig(vaultDir);
6607
+ const manifests = loadManifests();
6608
+ const symbiontPlanDirs = manifests.flatMap((m) => m.capture?.planDirs ?? []);
6609
+ const projectRoot = process.cwd();
6610
+ let planWatchConfig = {
6611
+ watchDirs: [.../* @__PURE__ */ new Set([...symbiontPlanDirs, ...config.capture.plan_dirs ?? []])],
6612
+ projectRoot,
6613
+ extensions: config.capture.artifact_extensions
6614
+ };
6615
+ const logger = new DaemonLogger(path20.join(vaultDir, "logs"), {
6616
+ level: config.daemon.log_level
5640
6617
  });
5641
- function enrichTurnsWithToolMetadata(turns, events) {
5642
- if (events.length === 0 || turns.length === 0) return;
5643
- const toolEvents = events.filter((e) => e.type === "tool_use");
5644
- if (toolEvents.length === 0) return;
5645
- let cursor = 0;
5646
- for (let i = 0; i < turns.length; i++) {
5647
- const turnEnd = i + 1 < turns.length ? turns[i + 1].timestamp : null;
5648
- const breakdown = {};
5649
- const files = /* @__PURE__ */ new Set();
5650
- while (cursor < toolEvents.length) {
5651
- const ts = String(toolEvents[cursor].timestamp ?? "");
5652
- if (turnEnd !== null && ts >= turnEnd) break;
5653
- const evt = toolEvents[cursor];
5654
- const toolName = String(evt.tool_name ?? evt.tool ?? "unknown");
5655
- breakdown[toolName] = (breakdown[toolName] ?? 0) + 1;
5656
- const input = evt.tool_input;
5657
- const filePath = input?.file_path ?? input?.path;
5658
- if (typeof filePath === "string") files.add(filePath);
5659
- cursor++;
5660
- }
5661
- if (Object.keys(breakdown).length > 0) {
5662
- turns[i].toolBreakdown = breakdown;
5663
- if (files.size > 0) turns[i].files = [...files];
5664
- }
5665
- }
6618
+ if (config.daemon.log_level === "debug") {
6619
+ process.env.MYCO_AGENT_DEBUG = "1";
5666
6620
  }
5667
- async function processStopEvent(sessionId, user, sessionMeta, hookTranscriptPath, lastAssistantMessage) {
5668
- const transcriptResult = transcriptMiner.getAllTurnsWithSource(sessionId, hookTranscriptPath);
5669
- let allTurns = transcriptResult.turns;
5670
- let turnSource = transcriptResult.source;
5671
- const bufferEvents = sessionBuffers.get(sessionId)?.readAll() ?? [];
5672
- if (allTurns.length === 0) {
5673
- allTurns = extractTurnsFromBuffer(bufferEvents);
5674
- turnSource = "buffer";
5675
- } else if (bufferEvents.length > 0) {
5676
- const lastTranscriptTs = allTurns[allTurns.length - 1].timestamp;
5677
- if (lastTranscriptTs) {
5678
- const newerEvents = bufferEvents.filter(
5679
- (e) => String(e.timestamp ?? "") > lastTranscriptTs
5680
- );
5681
- if (newerEvents.length > 0) {
5682
- const bufferTurns = extractTurnsFromBuffer(newerEvents);
5683
- allTurns = [...allTurns, ...bufferTurns];
5684
- turnSource = `${transcriptResult.source}+buffer`;
5685
- logger.info(LOG_KINDS.PROCESSOR_TRANSCRIPT, "Appended buffer turns missing from transcript", {
5686
- session_id: sessionId,
5687
- transcriptTurns: transcriptResult.turns.length,
5688
- bufferTurns: bufferTurns.length
5689
- });
5690
- }
5691
- }
5692
- }
5693
- if (lastAssistantMessage && allTurns.length > 0) {
5694
- const lastTurn = allTurns[allTurns.length - 1];
5695
- if (!lastTurn.aiResponse) {
5696
- lastTurn.aiResponse = lastAssistantMessage;
5697
- }
5698
- }
5699
- enrichTurnsWithToolMetadata(allTurns, bufferEvents);
5700
- const imageCount = allTurns.reduce((sum, t) => sum + (t.images?.length ?? 0), 0);
5701
- logger.debug(LOG_KINDS.PROCESSOR_TRANSCRIPT, "Transcript parsed", {
5702
- session_id: sessionId,
5703
- turn_count: allTurns.length,
5704
- image_count: imageCount
6621
+ killStaleDaemon(vaultDir, logger);
6622
+ logger.info(LOG_KINDS.DAEMON_CONFIG, "Config loaded", {
6623
+ vault: vaultDir,
6624
+ embedding_provider: config.embedding.provider
6625
+ });
6626
+ logger.info(LOG_KINDS.CAPTURE_PLAN, "Plan watch directories", { dirs: planWatchConfig.watchDirs });
6627
+ const machineId = getMachineId(vaultDir);
6628
+ logger.info(LOG_KINDS.DAEMON_START, "Machine ID resolved", { machine_id: machineId });
6629
+ const db = initDatabase(vaultDbPath(vaultDir));
6630
+ createSchema(db, machineId);
6631
+ registerBuiltinDomains();
6632
+ logger.info(LOG_KINDS.DAEMON_START, "SQLite initialized", { vault: vaultDir });
6633
+ initTeamContext(config.team.enabled, machineId);
6634
+ logger.setPersistFn((entry) => {
6635
+ const { timestamp, level, kind, component, message, ...rest } = entry;
6636
+ insertLogEntry({
6637
+ timestamp,
6638
+ level,
6639
+ kind,
6640
+ component,
6641
+ message,
6642
+ data: Object.keys(rest).length > 0 ? JSON.stringify(rest) : null,
6643
+ session_id: rest.session_id ?? null
5705
6644
  });
5706
- const latestBatch = getLatestBatch(sessionId);
5707
- if (lastAssistantMessage && latestBatch && !latestBatch.response_summary) {
5708
- try {
5709
- setResponseSummary(latestBatch.id, lastAssistantMessage);
5710
- } catch (err) {
5711
- logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to set response_summary on latest batch", { error: String(err) });
5712
- }
5713
- }
5714
- closeOpenBatches(sessionId, epochSeconds());
5715
- const existingSession = getSession(sessionId);
5716
- const hasTitle = existingSession?.title !== null && existingSession?.title !== void 0;
5717
- if (!hasTitle) {
5718
- let title = sessionTitleCache.get(sessionId) ?? null;
5719
- if (!title) {
5720
- const firstBatch = listBatchesBySession(sessionId, { limit: 1 })[0];
5721
- if (firstBatch?.user_prompt) {
5722
- title = firstBatch.user_prompt.slice(0, TITLE_PREVIEW_CHARS);
5723
- if (firstBatch.user_prompt.length > TITLE_PREVIEW_CHARS) {
5724
- title += "...";
5725
- }
5726
- sessionTitleCache.set(sessionId, title);
5727
- }
5728
- }
5729
- }
5730
- const currentSession = getSession(sessionId);
5731
- const transcriptPromptCount = allTurns.length;
5732
- const transcriptToolCount = allTurns.reduce((sum, t) => sum + t.toolCount, 0);
5733
- const updateFields = {
5734
- transcript_path: hookTranscriptPath ?? null,
5735
- prompt_count: Math.max(transcriptPromptCount, currentSession?.prompt_count ?? 0),
5736
- tool_count: Math.max(transcriptToolCount, currentSession?.tool_count ?? 0)
5737
- };
5738
- if (user) updateFields.user = user;
5739
- if (!hasTitle && sessionTitleCache.has(sessionId)) {
5740
- updateFields.title = sessionTitleCache.get(sessionId);
5741
- }
5742
- updateSession(sessionId, updateFields);
5743
- if (SKILL_USAGE_DETECTION_ENABLED) {
5744
- try {
5745
- let transcriptText = null;
5746
- if (hookTranscriptPath) {
5747
- try {
5748
- transcriptText = fs13.readFileSync(hookTranscriptPath, "utf-8");
5749
- } catch {
5750
- }
5751
- }
5752
- if (!transcriptText && allTurns.length > 0) {
5753
- transcriptText = allTurns.map((t) => [t.prompt ?? "", t.aiResponse ?? ""].join(" ")).join("\n");
5754
- }
5755
- if (transcriptText) {
5756
- detectSkillUsage(sessionId, transcriptText);
5757
- }
5758
- } catch {
5759
- }
5760
- }
5761
- const responses = [];
5762
- for (let i = 0; i < allTurns.length; i++) {
5763
- if (allTurns[i].aiResponse) {
5764
- responses.push({ turnIndex: i + 1, response: allTurns[i].aiResponse });
5765
- }
5766
- }
5767
- if (responses.length > 0) {
5768
- try {
5769
- populateBatchResponses(sessionId, responses);
5770
- } catch (err) {
5771
- logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to populate batch responses", { error: String(err) });
5772
- }
6645
+ });
6646
+ const lastLogTimestamp = getMaxTimestamp();
6647
+ if (lastLogTimestamp) {
6648
+ const logDir = path20.join(vaultDir, "logs");
6649
+ const replayedCount = reconcileLogBuffer(logDir, lastLogTimestamp);
6650
+ if (replayedCount > 0) {
6651
+ logger.info(LOG_KINDS.DAEMON_RECONCILE, `Replayed ${replayedCount} log entries from buffer`, { replayed: replayedCount });
5773
6652
  }
5774
- if (!hasTitle) {
5775
- triggerTitleSummary(sessionId);
6653
+ }
6654
+ const vectorsDbPath = path20.join(vaultDir, "vectors.db");
6655
+ const vectorStore = new SqliteVecVectorStore(vectorsDbPath);
6656
+ const llmProvider = createEmbeddingProvider(config.embedding);
6657
+ const embeddingProvider = new EmbeddingProviderAdapter(llmProvider, config.embedding);
6658
+ const recordSource = new SqliteRecordSource();
6659
+ const embeddingManager = new EmbeddingManager(vectorStore, embeddingProvider, recordSource, logger);
6660
+ logger.info(LOG_KINDS.EMBEDDING_EMBED, "EmbeddingManager initialized", { vectors_db: vectorsDbPath });
6661
+ let definitionsDir;
6662
+ try {
6663
+ const { registerBuiltInAgentsAndTasks, resolveDefinitionsDir: resolveDefinitionsDir2 } = await import("./loader-WC4U5NM5.js");
6664
+ definitionsDir = resolveDefinitionsDir2();
6665
+ await registerBuiltInAgentsAndTasks(definitionsDir, vaultDir);
6666
+ logger.info(LOG_KINDS.AGENT_TASK, "Built-in agents and tasks registered");
6667
+ } catch (err) {
6668
+ logger.warn(LOG_KINDS.AGENT_ERROR, "Failed to register built-in agents/tasks", { error: err.message });
6669
+ }
6670
+ try {
6671
+ const staleDb = getDatabase();
6672
+ const staleRows = staleDb.prepare(
6673
+ `SELECT id FROM agent_runs WHERE status = 'running'`
6674
+ ).all();
6675
+ if (staleRows.length > 0) {
6676
+ staleDb.prepare(
6677
+ `UPDATE agent_runs SET status = 'failed', completed_at = ?, error = 'Daemon restarted while run was in progress' WHERE status = 'running'`
6678
+ ).run(epochSeconds());
6679
+ logger.info(LOG_KINDS.AGENT_RUN, "Cleaned stale running agent runs", {
6680
+ count: staleRows.length,
6681
+ ids: staleRows.map((r) => r.id)
6682
+ });
5776
6683
  }
5777
- const sessionShort = sessionId.slice(-6);
5778
- for (let i = 0; i < allTurns.length; i++) {
5779
- const turn = allTurns[i];
5780
- if (!turn.images?.length) continue;
5781
- const isLastTurn = i === allTurns.length - 1;
5782
- let resolvedBatchId = null;
5783
- let resolvedPromptNumber = i + 1;
5784
- if (isLastTurn && latestBatch) {
5785
- resolvedBatchId = latestBatch.id;
5786
- resolvedPromptNumber = latestBatch.prompt_number ?? resolvedPromptNumber;
5787
- } else if (turn.prompt) {
5788
- try {
5789
- const match = findBatchByPromptPrefix(sessionId, turn.prompt);
5790
- if (match) {
5791
- resolvedBatchId = match.id;
5792
- resolvedPromptNumber = match.prompt_number;
5793
- }
5794
- } catch {
5795
- }
5796
- }
5797
- for (let j = 0; j < turn.images.length; j++) {
5798
- const img = turn.images[j];
5799
- const ext = extensionForMimeType(img.mediaType);
5800
- const filename = `${sessionShort}-t${resolvedPromptNumber}-${j + 1}.${ext}`;
5801
- const imageBuffer = Buffer.from(img.data, "base64");
5802
- try {
5803
- insertAttachment({
5804
- id: `${sessionShort}-b${resolvedPromptNumber}-${j + 1}`,
5805
- session_id: sessionId,
5806
- prompt_batch_id: resolvedBatchId ?? void 0,
5807
- file_path: filename,
5808
- media_type: img.mediaType,
5809
- data: imageBuffer,
5810
- created_at: epochSeconds()
5811
- });
5812
- logger.debug(LOG_KINDS.CAPTURE_ATTACHMENT, "Image stored in DB", { filename, batch: resolvedPromptNumber });
5813
- } catch (err) {
5814
- logger.warn(LOG_KINDS.CAPTURE_ATTACHMENT, "Failed to record attachment", { error: String(err) });
5815
- }
5816
- }
6684
+ } catch (err) {
6685
+ logger.warn(LOG_KINDS.AGENT_ERROR, "Failed to clean stale runs", { error: err.message });
6686
+ }
6687
+ let uiDir = null;
6688
+ {
6689
+ const root = findPackageRoot(path20.dirname(new URL(import.meta.url).pathname));
6690
+ if (root) {
6691
+ const candidate = path20.join(root, "dist", "ui");
6692
+ if (fs18.existsSync(candidate)) uiDir = candidate;
5817
6693
  }
5818
- logger.info(LOG_KINDS.PROCESSOR_SESSION, "Session captured", {
5819
- session_id: sessionId,
5820
- turns: allTurns.length,
5821
- source: turnSource,
5822
- title: existingSession?.title ?? sessionTitleCache.get(sessionId) ?? "(untitled)"
5823
- });
5824
6694
  }
6695
+ if (uiDir) {
6696
+ logger.debug(LOG_KINDS.DAEMON_START, "Static UI directory found", { path: uiDir });
6697
+ }
6698
+ const powerManager = new PowerManager({
6699
+ idleThresholdMs: POWER_IDLE_THRESHOLD_MS,
6700
+ sleepThresholdMs: POWER_SLEEP_THRESHOLD_MS,
6701
+ deepSleepThresholdMs: POWER_DEEP_SLEEP_THRESHOLD_MS,
6702
+ activeIntervalMs: POWER_ACTIVE_INTERVAL_MS,
6703
+ sleepIntervalMs: POWER_SLEEP_INTERVAL_MS,
6704
+ logger
6705
+ });
6706
+ const server = new DaemonServer({
6707
+ vaultDir,
6708
+ logger,
6709
+ uiDir: uiDir ?? void 0
6710
+ // Don't record activity on every HTTP request — UI polling (every 3-10s)
6711
+ // would prevent the PowerManager from ever reaching 'idle' state, blocking
6712
+ // all idle-only scheduled tasks (skill-survey, skill-generate, skill-evolve).
6713
+ // Activity is recorded on meaningful events below (session register, prompt capture, etc.).
6714
+ });
6715
+ const registry = new SessionRegistry({
6716
+ gracePeriod: 0,
6717
+ onEmpty: () => {
6718
+ }
6719
+ });
6720
+ const transcriptMiner = new TranscriptMiner({
6721
+ additionalAdapters: config.capture.transcript_paths.map(
6722
+ (p) => createPerProjectAdapter(p, claudeCodeAdapter.parseTurns)
6723
+ )
6724
+ });
6725
+ const bufferDir = path20.join(vaultDir, "buffer");
6726
+ const sessionBuffers = /* @__PURE__ */ new Map();
6727
+ const reconciler = createReconciler({ bufferDir, logger });
6728
+ reconciler.runStartupReconciliation();
6729
+ const stopProcessor = createStopProcessor({
6730
+ registry,
6731
+ sessionBuffers,
6732
+ transcriptMiner,
6733
+ embeddingManager,
6734
+ logger,
6735
+ config,
6736
+ vaultDir
6737
+ });
6738
+ const sessionLifecycle = createSessionLifecycleHandlers({
6739
+ registry,
6740
+ sessionBuffers,
6741
+ reconciler,
6742
+ stopProcessor,
6743
+ server,
6744
+ powerManager,
6745
+ machineId,
6746
+ logger,
6747
+ config,
6748
+ vaultDir
6749
+ });
6750
+ server.registerRoute("POST", "/sessions/register", sessionLifecycle.handleRegister);
6751
+ server.registerRoute("POST", "/sessions/unregister", sessionLifecycle.handleUnregister);
6752
+ const eventDispatcher = createEventDispatcher({
6753
+ registry,
6754
+ sessionBuffers,
6755
+ powerManager,
6756
+ logger,
6757
+ machineId,
6758
+ config,
6759
+ vaultDir,
6760
+ reconcileSession: reconciler.reconcileSession,
6761
+ planWatchConfig,
6762
+ triggerTitleSummary: stopProcessor.triggerTitleSummary
6763
+ });
6764
+ server.registerRoute("POST", "/events", eventDispatcher);
6765
+ server.registerRoute("POST", "/events/stop", stopProcessor.handleStopRoute);
5825
6766
  const contextDeps = { embeddingManager, config, logger };
5826
6767
  server.registerRoute("POST", "/context", createSessionContextHandler(contextDeps));
5827
6768
  server.registerRoute("POST", "/context/prompt", createPromptContextHandler(contextDeps));
5828
6769
  const progressTracker = new ProgressTracker();
5829
6770
  let configHash = computeConfigHash(vaultDir);
5830
6771
  server.registerRoute("GET", "/api/config", async () => handleGetConfig(vaultDir));
5831
- server.registerRoute("GET", "/api/symbionts", handleListSymbionts);
6772
+ server.registerRoute("GET", "/api/symbionts", async () => handleListSymbionts(vaultDir));
5832
6773
  server.registerRoute("PUT", "/api/config", async (req) => {
5833
6774
  const result = await handlePutConfig(vaultDir, req.body);
5834
6775
  if (!result.status || result.status < 400) {
@@ -5841,46 +6782,31 @@ async function main() {
5841
6782
  const dirs = m.capture?.planDirs ?? [];
5842
6783
  if (dirs.length > 0) symbiontPlanDirsByAgent[m.displayName] = dirs;
5843
6784
  }
5844
- server.registerRoute("GET", "/api/config/plan-dirs", async () => {
5845
- return { body: { symbiont: symbiontPlanDirsByAgent, custom: planWatchConfig.watchDirs.filter((d) => !symbiontPlanDirs.includes(d)) } };
5846
- });
5847
- server.registerRoute("POST", "/api/config/plan-dirs", async (req) => {
5848
- const body = req.body;
5849
- if (!Array.isArray(body.plan_dirs)) {
5850
- return { status: 400, body: { error: "plan_dirs must be an array" } };
6785
+ const planDirHandlers = createPlanDirHandlers({
6786
+ vaultDir,
6787
+ symbiontPlanDirsByAgent,
6788
+ symbiontPlanDirs,
6789
+ planWatchConfig,
6790
+ setPlanWatchConfig: (cfg) => {
6791
+ planWatchConfig = cfg;
5851
6792
  }
5852
- const updated = updateConfig(vaultDir, (cfg) => ({
5853
- ...cfg,
5854
- capture: { ...cfg.capture, plan_dirs: body.plan_dirs }
5855
- }));
5856
- planWatchConfig = { ...planWatchConfig, watchDirs: [.../* @__PURE__ */ new Set([...symbiontPlanDirs, ...body.plan_dirs])] };
5857
- return { body: { custom: updated.capture.plan_dirs } };
5858
- });
5859
- server.registerRoute("GET", "/api/stats", async () => {
5860
- const stats = gatherStats(vaultDir, { active_sessions: registry.sessions });
5861
- stats.daemon.pid = process.pid;
5862
- stats.daemon.port = server.port;
5863
- stats.daemon.version = server.version;
5864
- stats.daemon.uptime_seconds = Math.floor(process.uptime());
5865
- return { body: { ...stats, config_hash: configHash } };
5866
6793
  });
6794
+ server.registerRoute("GET", "/api/config/plan-dirs", planDirHandlers.handleGetPlanDirs);
6795
+ server.registerRoute("POST", "/api/config/plan-dirs", planDirHandlers.handleUpdatePlanDirs);
6796
+ const configHashRef = { get: () => configHash };
6797
+ server.registerRoute("GET", "/api/stats", createLiveStatsHandler({
6798
+ vaultDir,
6799
+ registry,
6800
+ server,
6801
+ configHash: configHashRef
6802
+ }));
5867
6803
  server.registerRoute("GET", "/api/logs/search", handleLogSearch);
5868
6804
  server.registerRoute("GET", "/api/logs/stream", handleLogStream);
5869
6805
  server.registerRoute("GET", "/api/logs/:id", handleLogDetail);
5870
- const ExternalLogBody = external_exports.object({
5871
- level: external_exports.enum(["debug", "info", "warn", "error"]),
5872
- component: external_exports.string(),
5873
- message: external_exports.string(),
5874
- data: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
5875
- });
5876
- server.registerRoute("POST", "/api/log", async (req) => {
5877
- const { level, component, message, data } = ExternalLogBody.parse(req.body);
5878
- logger.log(level, LOG_KINDS.MCP_EVENT, message, { ...data, mcp_component: component });
5879
- return { body: { ok: true } };
5880
- });
6806
+ server.registerRoute("POST", "/api/log", createLogIngestionHandler(logger));
5881
6807
  server.registerRoute("GET", "/api/models", async (req) => handleGetModels(req));
5882
6808
  server.registerRoute("POST", "/api/restart", async (req) => handleRestart({ vaultDir, progressTracker }, req.body));
5883
- const updateProjectRoot = path15.dirname(vaultDir);
6809
+ const updateProjectRoot = path20.dirname(vaultDir);
5884
6810
  const updateHandlers = createUpdateHandlers({
5885
6811
  vaultDir,
5886
6812
  projectRoot: updateProjectRoot,
@@ -5898,162 +6824,34 @@ async function main() {
5898
6824
  server.registerRoute("GET", "/api/progress/:token", async (req) => handleGetProgress(progressTracker, req.params.token));
5899
6825
  server.registerRoute("GET", "/api/sessions", handleListSessions);
5900
6826
  server.registerRoute("GET", "/api/sessions/:id", handleGetSession);
5901
- server.registerRoute("GET", "/api/sessions/:id/impact", async (req) => {
5902
- const sessionId = req.params.id;
5903
- const session = getSession(sessionId);
5904
- if (!session) return { status: 404, body: { error: "Session not found" } };
5905
- const impact = getSessionImpact(sessionId);
5906
- return { body: impact };
5907
- });
5908
- server.registerRoute("DELETE", "/api/sessions/:id", async (req) => {
5909
- const sessionId = req.params.id;
5910
- const result = deleteSessionCascade(sessionId);
5911
- if (!result.deleted) return { status: 404, body: { error: "Session not found" } };
5912
- cleanupAfterSessionCascade(sessionId, result, embeddingManager, vaultDir).catch(() => {
5913
- });
5914
- logger.info(LOG_KINDS.API_SESSION_DELETE, "Session cascade deleted", {
5915
- session_id: sessionId,
5916
- counts: result.counts
5917
- });
5918
- return { body: { ok: true, counts: result.counts } };
5919
- });
6827
+ const sessionMutations = createSessionMutationHandlers({ embeddingManager, vaultDir, logger });
6828
+ server.registerRoute("GET", "/api/sessions/:id/impact", sessionMutations.handleGetSessionImpact);
6829
+ server.registerRoute("DELETE", "/api/sessions/:id", sessionMutations.handleDeleteSession);
5920
6830
  server.registerRoute("GET", "/api/sessions/:id/batches", handleGetSessionBatches);
5921
6831
  server.registerRoute("GET", "/api/batches/:id/activities", handleGetBatchActivities);
5922
6832
  server.registerRoute("GET", "/api/sessions/:id/attachments", handleGetSessionAttachments);
5923
6833
  server.registerRoute("GET", "/api/sessions/:id/plans", handleGetSessionPlans);
5924
- server.registerRoute("GET", "/api/skill-candidates", handleListCandidates);
5925
- server.registerRoute("GET", "/api/skill-candidates/:id", handleGetCandidate);
5926
- server.registerRoute("PUT", "/api/skill-candidates/:id", handleUpdateCandidate);
5927
- server.registerRoute("GET", "/api/skill-records", handleListSkillRecords);
5928
- server.registerRoute("GET", "/api/skill-records/:id", handleGetSkillRecord);
5929
- server.registerRoute("DELETE", "/api/skill-candidates/:id", handleDeleteCandidate);
5930
- server.registerRoute("DELETE", "/api/skill-records/:id", async (req) => {
5931
- const result = await handleDeleteSkillRecord(req);
5932
- if (result.body?.deleted) {
5933
- const record = result.body;
5934
- if (record.name) {
5935
- const projectRoot2 = path15.resolve(vaultDir, "..");
5936
- const skillDir = path15.resolve(projectRoot2, ".agents", "skills", record.name);
5937
- try {
5938
- fs13.rmSync(skillDir, { recursive: true, force: true });
5939
- } catch (err) {
5940
- logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to remove skill directory", { name: record.name, error: String(err) });
5941
- }
5942
- try {
5943
- const { syncSkillSymlinks } = await import("./installer-25TSX4SR.js");
5944
- syncSkillSymlinks(projectRoot2, record.name, { remove: true });
5945
- } catch (err) {
5946
- logger.warn(LOG_KINDS.PROCESSOR_BATCH, "Failed to remove skill symlinks", { name: record.name, error: String(err) });
5947
- }
5948
- }
5949
- }
5950
- return result;
5951
- });
5952
- server.registerRoute("GET", "/api/spores", handleListSpores);
5953
- server.registerRoute("GET", "/api/spores/:id", handleGetSpore);
5954
- server.registerRoute("GET", "/api/entities", handleListEntities);
5955
- server.registerRoute("GET", "/api/graph", handleGetFullGraph);
5956
- server.registerRoute("GET", "/api/graph/:id", handleGetGraph);
5957
- server.registerRoute("GET", "/api/digest", handleGetDigest);
5958
- const ATTACHMENT_MEDIA_TYPES = {
5959
- png: "image/png",
5960
- jpg: "image/jpeg",
5961
- jpeg: "image/jpeg",
5962
- gif: "image/gif",
5963
- webp: "image/webp"
5964
- };
5965
- server.registerRoute("GET", "/api/attachments/:filename", async (req) => {
5966
- const filename = req.params.filename;
5967
- if (filename.includes("..") || filename.includes("/")) {
5968
- return { status: 400, body: { error: "invalid_filename" } };
5969
- }
5970
- const att = getAttachmentByFilePath(filename);
5971
- if (att?.data) {
5972
- const contentType2 = att.media_type ?? "application/octet-stream";
5973
- return { status: 200, headers: { "Content-Type": contentType2 }, body: att.data };
5974
- }
5975
- const filePath = path15.join(vaultDir, "attachments", filename);
5976
- let diskData;
5977
- try {
5978
- diskData = fs13.readFileSync(filePath);
5979
- } catch {
5980
- return { status: 404, body: { error: "not_found" } };
5981
- }
5982
- const ext = path15.extname(filename).slice(1).toLowerCase();
5983
- const contentType = ATTACHMENT_MEDIA_TYPES[ext] ?? "application/octet-stream";
5984
- return { status: 200, headers: { "Content-Type": contentType }, body: diskData };
5985
- });
5986
- function buildSkillGenerateInstruction() {
5987
- const candidates = listCandidates({ status: "approved", limit: 1 });
5988
- if (candidates.length > 0) {
5989
- return `Generate skill for candidate id=${candidates[0].id} topic="${candidates[0].topic}"`;
5990
- }
5991
- return void 0;
5992
- }
5993
- const AgentRunBody = external_exports.object({
5994
- task: external_exports.string().optional(),
5995
- instruction: external_exports.string().optional(),
5996
- agentId: external_exports.string().optional()
5997
- });
5998
- server.registerRoute("POST", "/api/agent/run", async (req) => {
5999
- const { task, instruction: rawInstruction, agentId } = AgentRunBody.parse(req.body);
6000
- let instruction = rawInstruction;
6001
- if (task === "skill-generate" && !instruction) {
6002
- instruction = buildSkillGenerateInstruction();
6003
- }
6004
- const { runAgent } = await import("./executor-HKNINUWO.js");
6005
- const resultPromise = runAgent(vaultDir, { task, instruction, agentId, embeddingManager });
6006
- const effectiveAgentId = agentId ?? "myco-agent";
6007
- const runId = getLatestRunId(effectiveAgentId, task);
6008
- resultPromise.then((result) => {
6009
- if (result.status === "failed") {
6010
- logger.error(LOG_KINDS.AGENT_ERROR, "Agent run failed", {
6011
- runId: result.runId,
6012
- error: result.error ?? "No error message",
6013
- phases: result.phases?.map((p) => `${p.name}:${p.status}`) ?? []
6014
- });
6015
- } else {
6016
- logger.info(LOG_KINDS.AGENT_RUN, "Agent run completed", {
6017
- runId: result.runId,
6018
- status: result.status,
6019
- phases: result.phases?.map((p) => `${p.name}:${p.status}`) ?? []
6020
- });
6021
- }
6022
- }).catch((err) => {
6023
- logger.error(LOG_KINDS.AGENT_ERROR, "Agent run threw unhandled error", {
6024
- error: err.message ?? String(err),
6025
- stack: err.stack?.split("\n").slice(0, 3).join(" | ")
6026
- });
6027
- });
6028
- return { body: { ok: true, message: "Agent started", runId } };
6029
- });
6030
- server.registerRoute("GET", "/api/agent/runs", async (req) => {
6031
- const limit = req.query.limit ? Number(req.query.limit) : AGENT_RUNS_DEFAULT_LIMIT;
6032
- const offset = req.query.offset ? Number(req.query.offset) : 0;
6033
- const agentId = req.query.agentId || void 0;
6034
- const status = req.query.status || void 0;
6035
- const task = req.query.task || void 0;
6036
- const search = req.query.search || void 0;
6037
- const filterOpts = { agent_id: agentId, status, task, search };
6038
- const runs = listRuns({ ...filterOpts, limit, offset });
6039
- const total = countRuns(filterOpts);
6040
- return { body: { runs, total, offset, limit } };
6041
- });
6042
- server.registerRoute("GET", "/api/agent/runs/:id", async (req) => {
6043
- const run = getRun(req.params.id);
6044
- if (!run) {
6045
- return { status: 404, body: { error: "Run not found" } };
6046
- }
6047
- return { body: { run } };
6048
- });
6049
- server.registerRoute("GET", "/api/agent/runs/:id/reports", async (req) => {
6050
- const reports = listReports(req.params.id);
6051
- return { body: { reports } };
6052
- });
6053
- server.registerRoute("GET", "/api/agent/runs/:id/turns", async (req) => {
6054
- const turns = listTurnsByRun(req.params.id);
6055
- return { body: turns };
6056
- });
6834
+ server.registerRoute("GET", "/api/skill-candidates", handleListCandidates);
6835
+ server.registerRoute("GET", "/api/skill-candidates/:id", handleGetCandidate);
6836
+ server.registerRoute("PUT", "/api/skill-candidates/:id", handleUpdateCandidate);
6837
+ server.registerRoute("GET", "/api/skill-records", handleListSkillRecords);
6838
+ server.registerRoute("GET", "/api/skill-records/:id", handleGetSkillRecord);
6839
+ server.registerRoute("DELETE", "/api/skill-candidates/:id", handleDeleteCandidate);
6840
+ server.registerRoute("DELETE", "/api/skill-records/:id", createSkillRecordDeleteHandler({ vaultDir, logger }));
6841
+ server.registerRoute("GET", "/api/spores", handleListSpores);
6842
+ server.registerRoute("GET", "/api/spores/:id", handleGetSpore);
6843
+ server.registerRoute("GET", "/api/entities", handleListEntities);
6844
+ server.registerRoute("GET", "/api/graph", handleGetFullGraph);
6845
+ server.registerRoute("GET", "/api/graph/:id", handleGetGraph);
6846
+ server.registerRoute("GET", "/api/digest", handleGetDigest);
6847
+ const attachments = createAttachmentHandler({ vaultDir });
6848
+ server.registerRoute("GET", "/api/attachments/:filename", attachments.handleGetAttachment);
6849
+ const agentRunHandlers = createAgentRunHandlers({ vaultDir, embeddingManager, logger });
6850
+ server.registerRoute("POST", "/api/agent/run", agentRunHandlers.handleRun);
6851
+ server.registerRoute("GET", "/api/agent/runs", agentRunHandlers.handleListRuns);
6852
+ server.registerRoute("GET", "/api/agent/runs/:id", agentRunHandlers.handleGetRun);
6853
+ server.registerRoute("GET", "/api/agent/runs/:id/reports", agentRunHandlers.handleGetRunReports);
6854
+ server.registerRoute("GET", "/api/agent/runs/:id/turns", agentRunHandlers.handleGetRunTurns);
6057
6855
  server.registerRoute("GET", "/api/agent/tasks", async (req) => handleListTasks(req, vaultDir));
6058
6856
  server.registerRoute("GET", "/api/agent/tasks/:id", async (req) => handleGetTask(req, vaultDir));
6059
6857
  server.registerRoute("GET", "/api/agent/tasks/:id/yaml", async (req) => handleGetTaskYaml(req, vaultDir));
@@ -6065,207 +6863,36 @@ async function main() {
6065
6863
  server.registerRoute("PUT", "/api/agent/tasks/:id/config", async (req) => handleUpdateTaskConfig(req, vaultDir));
6066
6864
  server.registerRoute("GET", "/api/providers", async () => handleGetProviders());
6067
6865
  server.registerRoute("POST", "/api/providers/test", async (req) => handleTestProvider(req));
6068
- const SPORE_ID_RANDOM_BYTES = 4;
6069
- const RESOLUTION_ID_RANDOM_BYTES = 8;
6070
- const RememberBody = external_exports.object({
6071
- content: external_exports.string(),
6072
- type: external_exports.string().optional(),
6073
- tags: external_exports.array(external_exports.string()).optional()
6074
- });
6075
- server.registerRoute("POST", "/api/mcp/remember", async (req) => {
6076
- const { content, type, tags } = RememberBody.parse(req.body);
6077
- const { randomBytes } = await import("crypto");
6078
- const observationType = type ?? "discovery";
6079
- const id = `${observationType}-${randomBytes(SPORE_ID_RANDOM_BYTES).toString("hex")}`;
6080
- const now = epochSeconds();
6081
- registerAgent({
6082
- id: USER_AGENT_ID,
6083
- name: USER_AGENT_NAME,
6084
- created_at: now
6085
- });
6086
- const spore = insertSpore({
6087
- id,
6088
- agent_id: USER_AGENT_ID,
6089
- machine_id: machineId,
6090
- observation_type: observationType,
6091
- content,
6092
- tags: tags ? tags.join(", ") : null,
6093
- created_at: now
6094
- });
6095
- embeddingManager.onContentWritten("spores", spore.id, content, {
6096
- status: "active",
6097
- observation_type: observationType
6098
- }).catch(() => {
6099
- });
6100
- return {
6101
- body: {
6102
- id: spore.id,
6103
- observation_type: spore.observation_type,
6104
- status: spore.status,
6105
- created_at: spore.created_at
6106
- }
6107
- };
6108
- });
6109
- server.registerRoute("GET", "/api/mcp/plans", async (req) => {
6110
- const statusFilter = req.query.status === "all" ? void 0 : req.query.status;
6111
- const limit = req.query.limit ? Number(req.query.limit) : void 0;
6112
- const rows = listPlans({ status: statusFilter, limit });
6113
- const plans = rows.map((row) => {
6114
- const content = row.content ?? "";
6115
- const checked = (content.match(/- \[x\]/gi) ?? []).length;
6116
- const unchecked = (content.match(/- \[ \]/g) ?? []).length;
6117
- const total = checked + unchecked;
6118
- const progress = total === 0 ? "N/A" : `${checked}/${total}`;
6119
- return {
6120
- id: row.id,
6121
- title: row.title,
6122
- status: row.status,
6123
- progress,
6124
- tags: row.tags ? row.tags.split(",").map((t) => t.trim()) : [],
6125
- created_at: row.created_at
6126
- };
6127
- });
6128
- return { body: { plans } };
6129
- });
6130
- server.registerRoute("GET", "/api/mcp/sessions", async (req) => {
6131
- const limit = req.query.limit ? Number(req.query.limit) : 20;
6132
- const status = req.query.status;
6133
- const rows = listSessions({ limit, status });
6134
- const sessions = rows.map((row) => ({
6135
- id: row.id,
6136
- agent: row.agent,
6137
- user: row.user,
6138
- branch: row.branch,
6139
- started_at: row.started_at,
6140
- ended_at: row.ended_at,
6141
- status: row.status,
6142
- title: row.title,
6143
- summary: (row.summary ?? "").slice(0, 300),
6144
- prompt_count: row.prompt_count,
6145
- tool_count: row.tool_count,
6146
- parent_session_id: row.parent_session_id
6147
- }));
6148
- return { body: { sessions } };
6149
- });
6150
- server.registerRoute("GET", "/api/mcp/team", async () => {
6151
- const teamDb = getDatabase();
6152
- const rows = teamDb.prepare(
6153
- `SELECT id, "user", role, joined, tags
6154
- FROM team_members
6155
- ORDER BY id ASC`
6156
- ).all();
6157
- const members = rows.map((row) => ({
6158
- id: row.id,
6159
- user: row.user,
6160
- role: row.role ?? null,
6161
- joined: row.joined ?? null,
6162
- tags: row.tags ? row.tags.split(",").map((t) => t.trim()) : []
6163
- }));
6164
- return { body: { members } };
6165
- });
6166
- const SupersedeBody = external_exports.object({
6167
- old_spore_id: external_exports.string(),
6168
- new_spore_id: external_exports.string(),
6169
- reason: external_exports.string().optional()
6170
- });
6171
- server.registerRoute("POST", "/api/mcp/supersede", async (req) => {
6172
- const { old_spore_id, new_spore_id, reason } = SupersedeBody.parse(req.body);
6173
- const { randomBytes } = await import("crypto");
6174
- const now = epochSeconds();
6175
- updateSporeStatus(old_spore_id, "superseded", now);
6176
- try {
6177
- embeddingManager.onStatusChanged("spores", old_spore_id, "superseded");
6178
- } catch {
6179
- }
6180
- registerAgent({
6181
- id: USER_AGENT_ID,
6182
- name: USER_AGENT_NAME,
6183
- created_at: now
6184
- });
6185
- const { insertResolutionEvent } = await import("./resolution-events-5EVUEWHS.js");
6186
- const resolutionId = `res-${randomBytes(RESOLUTION_ID_RANDOM_BYTES).toString("hex")}`;
6187
- insertResolutionEvent({
6188
- id: resolutionId,
6189
- agent_id: USER_AGENT_ID,
6190
- machine_id: machineId,
6191
- spore_id: old_spore_id,
6192
- action: "supersede",
6193
- new_spore_id,
6194
- reason: reason ?? null,
6195
- created_at: now
6196
- });
6197
- return {
6198
- body: {
6199
- old_spore: old_spore_id,
6200
- new_spore: new_spore_id,
6201
- status: "superseded"
6202
- }
6203
- };
6204
- });
6866
+ const mcpProxy = createMcpProxyHandlers({ machineId, embeddingManager });
6867
+ server.registerRoute("POST", "/api/mcp/remember", mcpProxy.handleRemember);
6868
+ server.registerRoute("POST", "/api/mcp/supersede", mcpProxy.handleSupersede);
6869
+ server.registerRoute("GET", "/api/mcp/plans", mcpProxy.handlePlans);
6870
+ server.registerRoute("GET", "/api/mcp/sessions", mcpProxy.handleSessions);
6871
+ server.registerRoute("GET", "/api/mcp/team", mcpProxy.handleTeam);
6205
6872
  const rawBackupDir = config.backup.dir;
6206
- const backupDir = rawBackupDir ? path15.resolve(rawBackupDir.startsWith("~/") ? path15.join(os8.homedir(), rawBackupDir.slice(2)) : rawBackupDir) : path15.resolve(vaultDir, "backups");
6873
+ const backupDir = rawBackupDir ? path20.resolve(rawBackupDir.startsWith("~/") ? path20.join(os8.homedir(), rawBackupDir.slice(2)) : rawBackupDir) : path20.resolve(vaultDir, "backups");
6207
6874
  const backupHandlers = createBackupHandlers({ db, backupDir, machineId });
6208
6875
  server.registerRoute("POST", "/api/backup", backupHandlers.handleCreateBackup);
6209
6876
  server.registerRoute("GET", "/api/backups", backupHandlers.handleListBackups);
6210
6877
  server.registerRoute("POST", "/api/restore/preview", backupHandlers.handleRestorePreview);
6211
6878
  server.registerRoute("POST", "/api/restore", backupHandlers.handleRestore);
6212
- server.registerRoute("GET", "/api/backup/config", async () => {
6213
- const cfg = loadConfig(vaultDir);
6214
- return { body: { dir: cfg.backup.dir ?? null, default_dir: path15.resolve(vaultDir, "backups") } };
6215
- });
6216
- server.registerRoute("PUT", "/api/backup/config", async (req) => {
6217
- const { dir } = req.body;
6218
- updateBackupConfig(vaultDir, { dir: dir || void 0 });
6219
- return { body: { dir: dir || null } };
6220
- });
6221
- let teamClient = null;
6222
- if (config.team.enabled && config.team.worker_url) {
6223
- const secrets = readSecrets(vaultDir);
6224
- const teamApiKey = secrets["MYCO_TEAM_API_KEY"];
6225
- if (teamApiKey) {
6226
- teamClient = new TeamSyncClient({
6227
- workerUrl: config.team.worker_url,
6228
- apiKey: teamApiKey,
6229
- machineId,
6230
- syncProtocolVersion: SYNC_PROTOCOL_VERSION
6231
- });
6232
- logger.info(LOG_KINDS.TEAM_SYNC_START, "Team sync client initialized", { worker_url: config.team.worker_url });
6233
- teamClient.connect({
6234
- machine_id: machineId,
6235
- version: server.version
6236
- }).then(() => {
6237
- logger.info(LOG_KINDS.TEAM_SYNC_START, "Node registered with team worker");
6238
- }).catch((err) => {
6239
- logger.warn(LOG_KINDS.TEAM_SYNC_ERROR, "Node registration failed (will retry on next flush)", { error: err.message });
6240
- });
6241
- setTimeout(() => {
6242
- try {
6243
- const backfilled = backfillUnsynced(machineId);
6244
- if (backfilled > 0) {
6245
- logger.info(LOG_KINDS.TEAM_SYNC_START, `Backfilled ${backfilled} unsynced records into outbox`);
6246
- }
6247
- } catch (err) {
6248
- logger.error(LOG_KINDS.TEAM_SYNC_ERROR, "Backfill failed", { error: err.message });
6249
- }
6250
- }, 0);
6251
- }
6252
- }
6879
+ const backupConfigHandlers = createBackupConfigHandlers({ vaultDir });
6880
+ server.registerRoute("GET", "/api/backup/config", backupConfigHandlers.handleGetBackupConfig);
6881
+ server.registerRoute("PUT", "/api/backup/config", backupConfigHandlers.handlePutBackupConfig);
6882
+ const teamSync = initTeamSync({ config, machineId, logger, vaultDir, serverVersion: server.version });
6253
6883
  const teamHandlers = createTeamHandlers({
6254
6884
  vaultDir,
6255
6885
  machineId,
6256
- getTeamClient: () => teamClient,
6257
- setTeamClient: (c) => {
6258
- teamClient = c;
6259
- }
6886
+ getTeamClient: teamSync.getTeamClient,
6887
+ setTeamClient: teamSync.setTeamClient
6260
6888
  });
6261
6889
  server.registerRoute("POST", "/api/team/connect", teamHandlers.handleConnect);
6262
6890
  server.registerRoute("POST", "/api/team/disconnect", teamHandlers.handleDisconnect);
6263
6891
  server.registerRoute("GET", "/api/team/status", teamHandlers.handleStatus);
6264
- server.registerRoute("POST", "/api/team/backfill", async () => {
6265
- const count = backfillUnsynced(machineId);
6266
- return { body: { enqueued: count } };
6267
- });
6268
- server.registerRoute("GET", "/api/search", createSearchHandler({ embeddingManager, getTeamClient: () => teamClient, machineId }));
6892
+ server.registerRoute("POST", "/api/team/backfill", teamHandlers.handleBackfill);
6893
+ server.registerRoute("POST", "/api/team/retry-failed", teamHandlers.handleRetryFailed);
6894
+ server.registerRoute("POST", "/api/team/upgrade-worker", teamHandlers.handleUpgradeWorker);
6895
+ server.registerRoute("GET", "/api/search", createSearchHandler({ embeddingManager, getTeamClient: teamSync.getTeamClient, machineId }));
6269
6896
  server.registerRoute("GET", "/api/activity", handleGetFeed);
6270
6897
  server.registerRoute("GET", "/api/embedding/status", async () => handleGetEmbeddingStatus(vaultDir));
6271
6898
  server.registerRoute("GET", "/api/embedding/details", async () => handleEmbeddingDetails(embeddingManager));
@@ -6298,204 +6925,14 @@ async function main() {
6298
6925
  logger.warn(LOG_KINDS.DAEMON_CONFIG, "Failed to persist auto-derived port", { error: err.message });
6299
6926
  }
6300
6927
  }
6301
- let reconcileRunning = false;
6302
- powerManager.register({
6303
- name: "embedding-reconcile",
6304
- runIn: ["active", "idle"],
6305
- fn: async () => {
6306
- if (reconcileRunning) return;
6307
- reconcileRunning = true;
6308
- try {
6309
- await embeddingManager.reconcile(EMBEDDING_BATCH_SIZE);
6310
- } finally {
6311
- reconcileRunning = false;
6312
- }
6313
- }
6314
- });
6315
- powerManager.register({
6316
- name: "session-maintenance",
6317
- runIn: ["active", "idle", "sleep"],
6318
- fn: () => runSessionMaintenance({
6319
- logger,
6320
- registeredSessionIds: () => registry.sessions,
6321
- embeddingManager,
6322
- vaultDir
6323
- })
6324
- });
6325
- powerManager.register({
6326
- name: "log-retention",
6327
- runIn: ["idle", "sleep"],
6328
- fn: async () => {
6329
- const retentionDays = config.daemon.log_retention_days;
6330
- const cutoff = new Date(Date.now() - retentionDays * MS_PER_DAY).toISOString();
6331
- const deleted = deleteOldLogs(cutoff);
6332
- if (deleted > 0) {
6333
- logger.info(LOG_KINDS.LOG_RETENTION, `Deleted ${deleted} log entries older than ${retentionDays} days`, { deleted, retention_days: retentionDays });
6334
- }
6335
- }
6336
- });
6337
- powerManager.register({
6338
- name: "auto-backup",
6339
- runIn: ["idle", "sleep"],
6340
- fn: async () => {
6341
- try {
6342
- logger.info(LOG_KINDS.BACKUP_START, "Auto-backup starting");
6343
- const filePath = createBackup(db, backupDir, machineId);
6344
- logger.info(LOG_KINDS.BACKUP_COMPLETE, "Auto-backup complete", { file_path: filePath });
6345
- } catch (err) {
6346
- logger.error(LOG_KINDS.BACKUP_ERROR, "Auto-backup failed", { error: err.message });
6347
- }
6348
- }
6349
- });
6350
- if (config.team.enabled) {
6351
- powerManager.register({
6352
- name: "team-sync-flush",
6353
- runIn: ["active", "idle"],
6354
- fn: async () => {
6355
- const client = teamClient;
6356
- if (!client) return;
6357
- try {
6358
- const pending = listPending();
6359
- if (pending.length === 0) return;
6360
- logger.info(LOG_KINDS.TEAM_SYNC_START, "Flushing outbox", { count: pending.length });
6361
- const result = await client.pushBatch(pending);
6362
- if (result.synced > 0 || result.skipped > 0) {
6363
- const failedIds = new Set(result.errors.map((e) => e.id));
6364
- const sentRecords = pending.filter((r) => !failedIds.has(String(r.row_id)));
6365
- const sentIds = sentRecords.map((r) => r.id);
6366
- if (sentIds.length > 0) {
6367
- const now = epochSeconds();
6368
- markSent(sentIds, now);
6369
- markSourceRowsSynced(sentRecords, now);
6370
- }
6371
- }
6372
- if (result.errors.length > 0) {
6373
- logger.warn(LOG_KINDS.TEAM_SYNC_ERROR, `Sync errors: ${result.errors.length}`, {
6374
- errors: result.errors.slice(0, 5)
6375
- });
6376
- }
6377
- pruneOld();
6378
- logger.info(LOG_KINDS.TEAM_SYNC_COMPLETE, "Outbox flush complete", {
6379
- synced: result.synced,
6380
- skipped: result.skipped,
6381
- errors: result.errors.length,
6382
- total: pending.length
6383
- });
6384
- } catch (err) {
6385
- logger.error(LOG_KINDS.TEAM_SYNC_ERROR, "Outbox flush failed", { error: err.message });
6386
- }
6387
- }
6388
- });
6389
- }
6390
- {
6391
- const runningTasks = /* @__PURE__ */ new Set();
6392
- if (definitionsDir) {
6393
- const { loadAllTasks: loadAllTasks2 } = await import("./registry-2XQMCPA6.js");
6394
- const allTasks = Array.from(loadAllTasks2(definitionsDir, vaultDir).values());
6395
- const initialLastRuns = {};
6396
- try {
6397
- const recentRuns = getDatabase().prepare(
6398
- `SELECT task, MAX(completed_at) as last_completed
6399
- FROM agent_runs
6400
- WHERE status IN ('completed', 'failed') AND completed_at IS NOT NULL
6401
- GROUP BY task`
6402
- ).all();
6403
- for (const row of recentRuns) {
6404
- initialLastRuns[row.task] = row.last_completed * 1e3;
6405
- }
6406
- } catch {
6407
- }
6408
- const scheduledContext = {
6409
- isTaskRunning: (name) => runningTasks.has(name),
6410
- setTaskRunning: (name, running) => {
6411
- if (running) runningTasks.add(name);
6412
- else runningTasks.delete(name);
6413
- },
6414
- runTask: async (taskName) => {
6415
- const { runAgent } = await import("./executor-HKNINUWO.js");
6416
- const instruction = taskName === "skill-generate" ? buildSkillGenerateInstruction() : void 0;
6417
- const result = await runAgent(vaultDir, { task: taskName, instruction, embeddingManager });
6418
- logger.info(LOG_KINDS.AGENT_RUN, `Scheduled task ${taskName} completed`, {
6419
- status: result.status,
6420
- runId: result.runId
6421
- });
6422
- if (result.status === "failed") {
6423
- notify(vaultDir, {
6424
- domain: "agents",
6425
- type: "agent.task.failure",
6426
- title: `Task failed: ${taskName}`,
6427
- message: result.error ?? "Unknown error",
6428
- link: `/agent?run=${result.runId}`,
6429
- metadata: { taskName, runId: result.runId }
6430
- }, config);
6431
- } else if (result.status === "completed") {
6432
- notify(vaultDir, {
6433
- domain: "agents",
6434
- type: "agent.task.success",
6435
- title: `Task completed: ${taskName}`,
6436
- link: `/agent?run=${result.runId}`,
6437
- metadata: { taskName, runId: result.runId }
6438
- }, config);
6439
- const { countToolCallsByRun } = await import("./turns-3ZQAF6HF.js");
6440
- const counts = countToolCallsByRun(result.runId, ["vault_create_spore", "vault_write_digest"]);
6441
- const sporeCount = counts["vault_create_spore"] ?? 0;
6442
- const digestCount = counts["vault_write_digest"] ?? 0;
6443
- if (sporeCount > 0) {
6444
- notify(vaultDir, {
6445
- domain: "mycelium",
6446
- type: "mycelium.spore.created",
6447
- title: sporeCount === 1 ? "Extracted 1 observation" : `Extracted ${sporeCount} observations`,
6448
- message: `From ${taskName} run`,
6449
- link: "/mycelium?tab=spores",
6450
- metadata: { count: sporeCount, taskName, runId: result.runId }
6451
- }, config);
6452
- }
6453
- if (digestCount > 0) {
6454
- notify(vaultDir, {
6455
- domain: "mycelium",
6456
- type: "mycelium.digest.completed",
6457
- title: `Digest updated (${digestCount} ${digestCount === 1 ? "tier" : "tiers"})`,
6458
- link: "/mycelium?tab=digest",
6459
- metadata: { tierCount: digestCount, taskName, runId: result.runId }
6460
- }, config);
6461
- }
6462
- }
6463
- },
6464
- preConditions: {
6465
- "has-unprocessed-batches": () => {
6466
- const row = getDatabase().prepare(
6467
- "SELECT 1 FROM prompt_batches WHERE processed = 0 LIMIT 1"
6468
- ).get();
6469
- return row !== void 0;
6470
- },
6471
- "has-active-skills": () => {
6472
- return countSkillRecords({ status: "active" }) > 0;
6473
- },
6474
- "has-approved-candidates": () => {
6475
- return countCandidates({ status: "approved" }) > 0;
6476
- }
6477
- }
6478
- };
6479
- const scheduledJobs = buildScheduledJobs(
6480
- allTasks,
6481
- config.agent.tasks ?? {},
6482
- scheduledContext,
6483
- initialLastRuns
6484
- );
6485
- for (const job of scheduledJobs) {
6486
- powerManager.register(job);
6487
- }
6488
- logger.info(LOG_KINDS.DAEMON_START, `Registered ${scheduledJobs.length} scheduled task(s)`, {
6489
- tasks: scheduledJobs.map((j) => j.name)
6490
- });
6491
- } else {
6492
- logger.warn(LOG_KINDS.AGENT_ERROR, "Skipping dynamic task scheduling \u2014 definitions directory unavailable");
6493
- }
6494
- }
6928
+ registerPowerJobs(powerManager, { embeddingManager, registry, logger, config, db, backupDir, machineId, vaultDir });
6929
+ teamSync.registerFlushJob(powerManager);
6930
+ await registerScheduledTasks(powerManager, { definitionsDir, vaultDir, embeddingManager, logger, config });
6495
6931
  powerManager.start();
6496
6932
  const shutdown = async (signal) => {
6497
6933
  logger.info(LOG_KINDS.DAEMON_START, `${signal} received`);
6498
6934
  powerManager.stop();
6935
+ const activeStopProcessing = stopProcessor.getActiveProcessing();
6499
6936
  if (activeStopProcessing) {
6500
6937
  logger.info(LOG_KINDS.DAEMON_START, "Waiting for active stop processing to complete...");
6501
6938
  await activeStopProcessing;
@@ -6522,4 +6959,4 @@ export {
6522
6959
  handleUserPrompt,
6523
6960
  main
6524
6961
  };
6525
- //# sourceMappingURL=main-MVXPBP5Z.js.map
6962
+ //# sourceMappingURL=main-ADLCOYKM.js.map