@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.
- package/dist/{agent-run-GZ5UVLDV.js → agent-run-T433ENJS.js} +6 -6
- package/dist/{agent-tasks-KKQ2GBBB.js → agent-tasks-TAIU3V5I.js} +6 -6
- package/dist/{chunk-X34OFKYU.js → chunk-23FJUKCN.js} +2 -2
- package/dist/{chunk-LD6U3L6O.js → chunk-2QMDRZPJ.js} +21 -17
- package/dist/chunk-2QMDRZPJ.js.map +1 -0
- package/dist/{chunk-KNTJOMWY.js → chunk-3MEOYXOW.js} +2 -2
- package/dist/{chunk-PSYLKCWQ.js → chunk-4BQ5QE76.js} +24 -5
- package/dist/chunk-4BQ5QE76.js.map +1 -0
- package/dist/{chunk-UZ5Y6XMP.js → chunk-4O3QNM5I.js} +2 -2
- package/dist/{chunk-JTYZRPX5.js → chunk-5ZT2Q6P5.js} +1 -1
- package/dist/{chunk-BCKYVLUZ.js → chunk-6GG2IVNV.js} +3 -3
- package/dist/{chunk-JJXVDCEX.js → chunk-75J2BR4P.js} +486 -488
- package/dist/chunk-75J2BR4P.js.map +1 -0
- package/dist/{chunk-TFBAV3PV.js → chunk-BFM6AM6R.js} +2 -2
- package/dist/{chunk-S6I62FAH.js → chunk-CUADDHHU.js} +4 -2
- package/dist/{chunk-S6I62FAH.js.map → chunk-CUADDHHU.js.map} +1 -1
- package/dist/{chunk-4VF6KQ2Z.js → chunk-DJQOYEK3.js} +87 -84
- package/dist/chunk-DJQOYEK3.js.map +1 -0
- package/dist/{chunk-OQVKLTQY.js → chunk-EYMKBNRP.js} +2 -2
- package/dist/{chunk-STBNNKL5.js → chunk-GCCBXCHF.js} +6 -6
- package/dist/{chunk-KH64DHOY.js → chunk-GDY63YAW.js} +279 -277
- package/dist/chunk-GDY63YAW.js.map +1 -0
- package/dist/{chunk-ZESTWGJT.js → chunk-GYIA6XLB.js} +2 -2
- package/dist/{chunk-S66YG6QK.js → chunk-LF5Z62X6.js} +46 -7
- package/dist/chunk-LF5Z62X6.js.map +1 -0
- package/dist/{chunk-PX5KIOKY.js → chunk-SPJGJEFV.js} +10 -2
- package/dist/{chunk-PX5KIOKY.js.map → chunk-SPJGJEFV.js.map} +1 -1
- package/dist/{chunk-QFMBZ72S.js → chunk-SV6UCB2Z.js} +2 -2
- package/dist/{chunk-GMTWRMLP.js → chunk-TQO4PF5K.js} +3 -3
- package/dist/{chunk-NVCGF2DS.js → chunk-X4XFJG6I.js} +10 -6
- package/dist/chunk-X4XFJG6I.js.map +1 -0
- package/dist/{chunk-TNCBMGWB.js → chunk-X5IXK5KO.js} +262 -226
- package/dist/chunk-X5IXK5KO.js.map +1 -0
- package/dist/{chunk-TVV6PZOC.js → chunk-Z7TZJ2SP.js} +2 -2
- package/dist/{cli-JLDCZ77U.js → cli-W37MRZHD.js} +45 -44
- package/dist/cli-W37MRZHD.js.map +1 -0
- package/dist/{client-LRQMMKLP.js → client-YNTTC75R.js} +4 -4
- package/dist/{config-H657SF6B.js → config-MOWCOJJ4.js} +4 -4
- package/dist/{detect-27DN6UTL.js → detect-GFYKKHLJ.js} +3 -3
- package/dist/{detect-providers-PAVE2X6O.js → detect-providers-EU35RUL3.js} +2 -2
- package/dist/{doctor-IG3CXMI7.js → doctor-PAAQU5AS.js} +38 -19
- package/dist/doctor-PAAQU5AS.js.map +1 -0
- package/dist/{executor-HKNINUWO.js → executor-4OXDK4ZA.js} +790 -312
- package/dist/executor-4OXDK4ZA.js.map +1 -0
- package/dist/{init-RHQUINC2.js → init-PHQAQANR.js} +41 -23
- package/dist/init-PHQAQANR.js.map +1 -0
- package/dist/{init-wizard-ZB3JRDLE.js → init-wizard-RFD46XAJ.js} +7 -7
- package/dist/{installer-25TSX4SR.js → installer-BTUNKWOU.js} +2 -2
- package/dist/{llm-T3QVHC3Y.js → llm-D4VWYUK7.js} +4 -4
- package/dist/{loader-WQKVWL5D.js → loader-WC4U5NM5.js} +4 -4
- package/dist/{loader-JQLO6K44.js → loader-WGDVRGLM.js} +6 -4
- package/dist/{logs-LXHPDKUA.js → logs-WFBX2I7C.js} +3 -3
- package/dist/{main-MVXPBP5Z.js → main-ADLCOYKM.js} +2351 -1914
- package/dist/main-ADLCOYKM.js.map +1 -0
- package/dist/{open-CVEMRH3Z.js → open-3VPUP3HD.js} +6 -6
- package/dist/{openai-embeddings-5T5ZP7LO.js → openai-embeddings-SEIV7AM3.js} +2 -2
- package/dist/{openrouter-RD2COFC7.js → openrouter-ELODIZRP.js} +2 -2
- package/dist/{post-compact-ALQ2UGZ7.js → post-compact-5NYLOC46.js} +9 -9
- package/dist/{post-tool-use-SPL7HIYU.js → post-tool-use-SNNXSZ5Y.js} +10 -10
- package/dist/{post-tool-use-failure-B3CUYBTR.js → post-tool-use-failure-POKVXQHY.js} +9 -9
- package/dist/{pre-compact-KPWC4V64.js → pre-compact-ZUICBJEX.js} +9 -9
- package/dist/{provider-check-QN7OGXZA.js → provider-check-B66E5PWS.js} +2 -2
- package/dist/{registry-2XQMCPA6.js → registry-DHWVHXWY.js} +5 -5
- package/dist/{remove-O2WCN6RC.js → remove-SVU2V4Q7.js} +52 -20
- package/dist/remove-SVU2V4Q7.js.map +1 -0
- package/dist/{resolution-events-5EVUEWHS.js → resolution-events-DBCRVZGU.js} +4 -4
- package/dist/{restart-S52VV3SP.js → restart-NBB5CXJ4.js} +7 -7
- package/dist/{search-IOJ5O37S.js → search-YUQZFRZX.js} +6 -6
- package/dist/{server-T4VPK6FU.js → server-NBRX56VL.js} +8 -8
- package/dist/{session-ID6BX72K.js → session-2QP4HMZ5.js} +8 -8
- package/dist/{session-end-I7ZABXRI.js → session-end-NNFBW7CQ.js} +10 -10
- package/dist/{session-start-VPOUY42U.js → session-start-NPNP4IXX.js} +15 -15
- package/dist/{setup-llm-G5UG5N3T.js → setup-llm-C3IGFLRN.js} +8 -8
- package/dist/src/agent/definitions/tasks/full-intelligence.yaml +8 -7
- package/dist/src/agent/definitions/tasks/skill-evolve.yaml +71 -144
- package/dist/src/agent/definitions/tasks/skill-generate.yaml +10 -62
- package/dist/src/agent/definitions/tasks/skill-survey.yaml +87 -53
- package/dist/src/agent/prompts/agent.md +1 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/daemon/main.js +1 -1
- package/dist/src/hooks/post-tool-use.js +1 -1
- package/dist/src/hooks/session-end.js +1 -1
- package/dist/src/hooks/session-start.js +1 -1
- package/dist/src/hooks/stop.js +1 -1
- package/dist/src/hooks/user-prompt-submit.js +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/worker/src/schema.ts +14 -0
- package/dist/{stats-GRI4MTS2.js → stats-FEEXIRMS.js} +9 -9
- package/dist/{stop-UTZ2CXI2.js → stop-FGDGWXTK.js} +10 -10
- package/dist/{stop-failure-CECM5NB7.js → stop-failure-5YAGH2TQ.js} +9 -9
- package/dist/{subagent-start-SYZGJYUN.js → subagent-start-UCKVJDR4.js} +9 -9
- package/dist/{subagent-stop-7WWW7TGQ.js → subagent-stop-H25B3QEC.js} +9 -9
- package/dist/{task-completed-N7SIY6T6.js → task-completed-2JGZN2CF.js} +9 -9
- package/dist/{team-SJPDXELY.js → team-TG5WZXWO.js} +34 -26
- package/dist/team-TG5WZXWO.js.map +1 -0
- package/dist/ui/assets/index-7Vimyg7g.js +837 -0
- package/dist/ui/assets/{index-BmsHIwjl.css → index-DlEQ8A8Y.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/{update-DZZYQ4NJ.js → update-EG3N2EXI.js} +30 -14
- package/dist/update-EG3N2EXI.js.map +1 -0
- package/dist/{user-prompt-submit-UUNRRS5P.js → user-prompt-submit-7FFQ3ORA.js} +10 -10
- package/dist/{verify-JHIMXTY5.js → verify-2M3DYHEY.js} +6 -6
- package/dist/{version-VKNCAPZW.js → version-JUQU5W22.js} +2 -2
- package/package.json +3 -3
- package/dist/chunk-4VF6KQ2Z.js.map +0 -1
- package/dist/chunk-JJXVDCEX.js.map +0 -1
- package/dist/chunk-KH64DHOY.js.map +0 -1
- package/dist/chunk-LD6U3L6O.js.map +0 -1
- package/dist/chunk-NVCGF2DS.js.map +0 -1
- package/dist/chunk-PSYLKCWQ.js.map +0 -1
- package/dist/chunk-S66YG6QK.js.map +0 -1
- package/dist/chunk-TNCBMGWB.js.map +0 -1
- package/dist/cli-JLDCZ77U.js.map +0 -1
- package/dist/doctor-IG3CXMI7.js.map +0 -1
- package/dist/executor-HKNINUWO.js.map +0 -1
- package/dist/init-RHQUINC2.js.map +0 -1
- package/dist/main-MVXPBP5Z.js.map +0 -1
- package/dist/remove-O2WCN6RC.js.map +0 -1
- package/dist/resolve-3FEUV462.js +0 -9
- package/dist/team-SJPDXELY.js.map +0 -1
- package/dist/ui/assets/index-Cn6cQwJy.js +0 -842
- package/dist/update-DZZYQ4NJ.js.map +0 -1
- package/dist/version-VKNCAPZW.js.map +0 -1
- /package/dist/{agent-run-GZ5UVLDV.js.map → agent-run-T433ENJS.js.map} +0 -0
- /package/dist/{agent-tasks-KKQ2GBBB.js.map → agent-tasks-TAIU3V5I.js.map} +0 -0
- /package/dist/{chunk-X34OFKYU.js.map → chunk-23FJUKCN.js.map} +0 -0
- /package/dist/{chunk-KNTJOMWY.js.map → chunk-3MEOYXOW.js.map} +0 -0
- /package/dist/{chunk-UZ5Y6XMP.js.map → chunk-4O3QNM5I.js.map} +0 -0
- /package/dist/{chunk-JTYZRPX5.js.map → chunk-5ZT2Q6P5.js.map} +0 -0
- /package/dist/{chunk-BCKYVLUZ.js.map → chunk-6GG2IVNV.js.map} +0 -0
- /package/dist/{chunk-TFBAV3PV.js.map → chunk-BFM6AM6R.js.map} +0 -0
- /package/dist/{chunk-OQVKLTQY.js.map → chunk-EYMKBNRP.js.map} +0 -0
- /package/dist/{chunk-STBNNKL5.js.map → chunk-GCCBXCHF.js.map} +0 -0
- /package/dist/{chunk-ZESTWGJT.js.map → chunk-GYIA6XLB.js.map} +0 -0
- /package/dist/{chunk-QFMBZ72S.js.map → chunk-SV6UCB2Z.js.map} +0 -0
- /package/dist/{chunk-GMTWRMLP.js.map → chunk-TQO4PF5K.js.map} +0 -0
- /package/dist/{chunk-TVV6PZOC.js.map → chunk-Z7TZJ2SP.js.map} +0 -0
- /package/dist/{client-LRQMMKLP.js.map → client-YNTTC75R.js.map} +0 -0
- /package/dist/{config-H657SF6B.js.map → config-MOWCOJJ4.js.map} +0 -0
- /package/dist/{detect-27DN6UTL.js.map → detect-GFYKKHLJ.js.map} +0 -0
- /package/dist/{detect-providers-PAVE2X6O.js.map → detect-providers-EU35RUL3.js.map} +0 -0
- /package/dist/{init-wizard-ZB3JRDLE.js.map → init-wizard-RFD46XAJ.js.map} +0 -0
- /package/dist/{installer-25TSX4SR.js.map → installer-BTUNKWOU.js.map} +0 -0
- /package/dist/{llm-T3QVHC3Y.js.map → llm-D4VWYUK7.js.map} +0 -0
- /package/dist/{loader-JQLO6K44.js.map → loader-WC4U5NM5.js.map} +0 -0
- /package/dist/{loader-WQKVWL5D.js.map → loader-WGDVRGLM.js.map} +0 -0
- /package/dist/{logs-LXHPDKUA.js.map → logs-WFBX2I7C.js.map} +0 -0
- /package/dist/{open-CVEMRH3Z.js.map → open-3VPUP3HD.js.map} +0 -0
- /package/dist/{openai-embeddings-5T5ZP7LO.js.map → openai-embeddings-SEIV7AM3.js.map} +0 -0
- /package/dist/{openrouter-RD2COFC7.js.map → openrouter-ELODIZRP.js.map} +0 -0
- /package/dist/{post-compact-ALQ2UGZ7.js.map → post-compact-5NYLOC46.js.map} +0 -0
- /package/dist/{post-tool-use-SPL7HIYU.js.map → post-tool-use-SNNXSZ5Y.js.map} +0 -0
- /package/dist/{post-tool-use-failure-B3CUYBTR.js.map → post-tool-use-failure-POKVXQHY.js.map} +0 -0
- /package/dist/{pre-compact-KPWC4V64.js.map → pre-compact-ZUICBJEX.js.map} +0 -0
- /package/dist/{provider-check-QN7OGXZA.js.map → provider-check-B66E5PWS.js.map} +0 -0
- /package/dist/{registry-2XQMCPA6.js.map → registry-DHWVHXWY.js.map} +0 -0
- /package/dist/{resolution-events-5EVUEWHS.js.map → resolution-events-DBCRVZGU.js.map} +0 -0
- /package/dist/{restart-S52VV3SP.js.map → restart-NBB5CXJ4.js.map} +0 -0
- /package/dist/{search-IOJ5O37S.js.map → search-YUQZFRZX.js.map} +0 -0
- /package/dist/{server-T4VPK6FU.js.map → server-NBRX56VL.js.map} +0 -0
- /package/dist/{session-ID6BX72K.js.map → session-2QP4HMZ5.js.map} +0 -0
- /package/dist/{session-end-I7ZABXRI.js.map → session-end-NNFBW7CQ.js.map} +0 -0
- /package/dist/{session-start-VPOUY42U.js.map → session-start-NPNP4IXX.js.map} +0 -0
- /package/dist/{setup-llm-G5UG5N3T.js.map → setup-llm-C3IGFLRN.js.map} +0 -0
- /package/dist/{stats-GRI4MTS2.js.map → stats-FEEXIRMS.js.map} +0 -0
- /package/dist/{stop-UTZ2CXI2.js.map → stop-FGDGWXTK.js.map} +0 -0
- /package/dist/{stop-failure-CECM5NB7.js.map → stop-failure-5YAGH2TQ.js.map} +0 -0
- /package/dist/{subagent-start-SYZGJYUN.js.map → subagent-start-UCKVJDR4.js.map} +0 -0
- /package/dist/{subagent-stop-7WWW7TGQ.js.map → subagent-stop-H25B3QEC.js.map} +0 -0
- /package/dist/{task-completed-N7SIY6T6.js.map → task-completed-2JGZN2CF.js.map} +0 -0
- /package/dist/{user-prompt-submit-UUNRRS5P.js.map → user-prompt-submit-7FFQ3ORA.js.map} +0 -0
- /package/dist/{verify-JHIMXTY5.js.map → verify-2M3DYHEY.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
81
|
+
} from "./chunk-3MEOYXOW.js";
|
|
82
82
|
import {
|
|
83
83
|
AgentTaskSchema,
|
|
84
84
|
registerAgent,
|
|
85
85
|
resolveDefinitionsDir,
|
|
86
86
|
taskFromParsed
|
|
87
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
149
|
+
} from "./chunk-LF5Z62X6.js";
|
|
146
150
|
import {
|
|
147
151
|
EMBEDDING_DIMENSIONS,
|
|
148
152
|
SCHEMA_VERSION,
|
|
149
153
|
createSchema
|
|
150
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
172
|
+
} from "./chunk-4O3QNM5I.js";
|
|
168
173
|
import {
|
|
169
174
|
getPluginVersion
|
|
170
|
-
} from "./chunk-
|
|
175
|
+
} from "./chunk-BFM6AM6R.js";
|
|
171
176
|
import {
|
|
172
177
|
loadManifests
|
|
173
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
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(
|
|
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
|
|
1841
|
-
import
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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,
|
|
2307
|
-
const res = await this.fetchFn(`${this.workerUrl}${
|
|
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} ${
|
|
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
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
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
|
-
|
|
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 {
|
|
2321
|
+
return { handleConnect, handleDisconnect, handleStatus, handleBackfill, handleRetryFailed, handleUpgradeWorker };
|
|
2492
2322
|
}
|
|
2493
2323
|
|
|
2494
|
-
// src/daemon/api/
|
|
2495
|
-
var
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
var
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
}
|
|
2515
|
-
function
|
|
2516
|
-
|
|
2517
|
-
const
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
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
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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 {
|
|
2397
|
+
return { handleRegister, handleUnregister };
|
|
2546
2398
|
}
|
|
2547
2399
|
|
|
2548
|
-
// src/daemon/api/
|
|
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/
|
|
2563
|
-
var
|
|
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
|
-
"
|
|
2569
|
-
"
|
|
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
|
|
2583
|
-
function
|
|
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
|
-
|
|
2588
|
-
|
|
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
|
|
2422
|
+
function insertSkillUsage(data) {
|
|
2603
2423
|
const db = getDatabase();
|
|
2604
|
-
|
|
2605
|
-
`INSERT INTO
|
|
2606
|
-
|
|
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.
|
|
2621
|
-
data.
|
|
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
|
|
2635
|
-
|
|
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
|
|
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
|
|
2661
|
-
).get(sessionId);
|
|
2662
|
-
return row
|
|
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
|
|
2449
|
+
function countUsageForSkill(skillId) {
|
|
2737
2450
|
const db = getDatabase();
|
|
2738
2451
|
const row = db.prepare(
|
|
2739
|
-
`SELECT
|
|
2740
|
-
).get(
|
|
2741
|
-
return row
|
|
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/
|
|
2745
|
-
var DEFAULT_LIST_LIMIT3 = 50;
|
|
2457
|
+
// src/daemon/api/skills.ts
|
|
2746
2458
|
var DEFAULT_LIST_OFFSET = 0;
|
|
2747
|
-
async function
|
|
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
|
|
2752
|
-
const
|
|
2753
|
-
const
|
|
2754
|
-
|
|
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
|
|
2769
|
-
const
|
|
2770
|
-
if (!
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
return {
|
|
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
|
|
2776
|
-
const
|
|
2777
|
-
|
|
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
|
|
2780
|
-
const
|
|
2781
|
-
|
|
2782
|
-
const
|
|
2783
|
-
|
|
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
|
|
2786
|
-
const
|
|
2787
|
-
|
|
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
|
|
2790
|
-
const
|
|
2791
|
-
|
|
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
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
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
|
-
|
|
2819
|
-
const
|
|
2820
|
-
|
|
2821
|
-
|
|
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
|
-
|
|
2824
|
-
|
|
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) :
|
|
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
|
|
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
|
|
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/
|
|
4517
|
-
|
|
4518
|
-
|
|
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
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
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
|
|
4535
|
-
const
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
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/
|
|
4570
|
-
var
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
const
|
|
4574
|
-
|
|
4575
|
-
const
|
|
4576
|
-
|
|
4577
|
-
}
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
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
|
|
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
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
const
|
|
4606
|
-
const
|
|
4607
|
-
|
|
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
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
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
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
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
|
-
|
|
5244
|
+
logger.info(LOG_KINDS.DAEMON_START, `Registered ${scheduledJobs.length} scheduled task(s)`, {
|
|
5245
|
+
tasks: scheduledJobs.map((j) => j.name)
|
|
5246
|
+
});
|
|
4633
5247
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
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
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
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
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
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
|
-
}
|
|
4655
|
-
|
|
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 {
|
|
5476
|
+
return {
|
|
5477
|
+
handleRun,
|
|
5478
|
+
handleListRuns,
|
|
5479
|
+
handleGetRun,
|
|
5480
|
+
handleGetRunReports,
|
|
5481
|
+
handleGetRunTurns
|
|
5482
|
+
};
|
|
4659
5483
|
}
|
|
4660
5484
|
|
|
4661
|
-
// src/daemon/
|
|
4662
|
-
import
|
|
4663
|
-
|
|
4664
|
-
var
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
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
|
-
|
|
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
|
|
4698
|
-
import
|
|
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 =
|
|
4704
|
-
if (
|
|
5528
|
+
const rotated = path16.join(logDir, `daemon.${i}.log`);
|
|
5529
|
+
if (fs14.existsSync(rotated)) files.push(rotated);
|
|
4705
5530
|
}
|
|
4706
|
-
const current =
|
|
4707
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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/
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
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
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
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
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
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
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
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
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
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
|
-
|
|
5210
|
-
|
|
6007
|
+
function clearSession(sessionId) {
|
|
6008
|
+
reconciledSessions.delete(sessionId);
|
|
5211
6009
|
}
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
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-
|
|
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
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
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
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
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
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
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
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
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
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
if (
|
|
5301
|
-
if (!
|
|
5302
|
-
|
|
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
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
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
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
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
|
-
|
|
5328
|
-
if (result === "prompt") promptsRecovered++;
|
|
5329
|
-
else if (result === "activity") activitiesRecovered++;
|
|
6203
|
+
populateBatchResponses(sessionId, responses);
|
|
5330
6204
|
} catch (err) {
|
|
5331
|
-
logger.warn(LOG_KINDS.
|
|
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 (
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
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
|
|
5346
|
-
session_id:
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
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
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
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
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
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
|
-
|
|
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 = {
|
|
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
|
-
|
|
6433
|
+
fs17.promises.readFile(planFilePath, "utf-8").then((planContent) => {
|
|
5474
6434
|
const latestBatch = getLatestBatch(captureSessionId);
|
|
5475
6435
|
capturePlan({
|
|
5476
|
-
sourcePath:
|
|
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
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
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
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
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
|
-
|
|
5642
|
-
|
|
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
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
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
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
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
|
-
|
|
5775
|
-
|
|
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
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
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
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
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",
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
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
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
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 ?
|
|
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
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
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:
|
|
6257
|
-
setTeamClient:
|
|
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",
|
|
6265
|
-
|
|
6266
|
-
|
|
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
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
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-
|
|
6962
|
+
//# sourceMappingURL=main-ADLCOYKM.js.map
|