@goondocks/myco 0.20.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-eval-RJSQI5S2.js +355 -0
- package/dist/agent-eval-RJSQI5S2.js.map +1 -0
- package/dist/{agent-run-X25Q2A6T.js → agent-run-2JSYFOKU.js} +10 -8
- package/dist/{agent-run-X25Q2A6T.js.map → agent-run-2JSYFOKU.js.map} +1 -1
- package/dist/{agent-tasks-7B6OFERB.js → agent-tasks-APFJIM2T.js} +10 -8
- package/dist/{agent-tasks-7B6OFERB.js.map → agent-tasks-APFJIM2T.js.map} +1 -1
- package/dist/{chunk-OD4AA7PV.js → chunk-53RPGOEN.js} +56 -8
- package/dist/chunk-53RPGOEN.js.map +1 -0
- package/dist/chunk-54SXG5HF.js +26 -0
- package/dist/chunk-54SXG5HF.js.map +1 -0
- package/dist/{chunk-DCSGJ7W4.js → chunk-5ZG4RMUH.js} +2 -2
- package/dist/{chunk-FLLBJLHM.js → chunk-6C6QZ4PM.js} +9 -5
- package/dist/chunk-6C6QZ4PM.js.map +1 -0
- package/dist/chunk-6LB7XELY.js +406 -0
- package/dist/chunk-6LB7XELY.js.map +1 -0
- package/dist/{chunk-JZGN33AY.js → chunk-75Z7UKDY.js} +4 -4
- package/dist/{chunk-XG5RRUYF.js → chunk-BUTL6IFS.js} +2 -2
- package/dist/chunk-CESKJD44.js +586 -0
- package/dist/chunk-CESKJD44.js.map +1 -0
- package/dist/chunk-CISWUP5W.js +101 -0
- package/dist/chunk-CISWUP5W.js.map +1 -0
- package/dist/chunk-DJ3IHNYO.js +50 -0
- package/dist/chunk-DJ3IHNYO.js.map +1 -0
- package/dist/chunk-F3OEQYLS.js +847 -0
- package/dist/chunk-F3OEQYLS.js.map +1 -0
- package/dist/{chunk-6RFZWV4R.js → chunk-FCJ5JV54.js} +1 -1
- package/dist/{chunk-6RFZWV4R.js.map → chunk-FCJ5JV54.js.map} +1 -1
- package/dist/{chunk-2PDWCDKY.js → chunk-G6QIBNZM.js} +9 -6
- package/dist/{chunk-2PDWCDKY.js.map → chunk-G6QIBNZM.js.map} +1 -1
- package/dist/{chunk-6X2ERTQV.js → chunk-ILJPRYES.js} +6 -4
- package/dist/{chunk-6X2ERTQV.js.map → chunk-ILJPRYES.js.map} +1 -1
- package/dist/{chunk-US4LNCAT.js → chunk-IPPMYQ2Y.js} +5 -1
- package/dist/chunk-IPPMYQ2Y.js.map +1 -0
- package/dist/{chunk-KESLPBKV.js → chunk-JR54LTPP.js} +4 -4
- package/dist/{chunk-CCRGY3QW.js → chunk-JZS6GZ6T.js} +16 -94
- package/dist/chunk-JZS6GZ6T.js.map +1 -0
- package/dist/{chunk-5XIVBO25.js → chunk-LVIY7P35.js} +2 -2
- package/dist/chunk-NGH7U6A3.js +13844 -0
- package/dist/chunk-NGH7U6A3.js.map +1 -0
- package/dist/chunk-OUJSQSKE.js +113 -0
- package/dist/chunk-OUJSQSKE.js.map +1 -0
- package/dist/{chunk-VVNL26WX.js → chunk-P66DLD6G.js} +22 -10
- package/dist/chunk-P66DLD6G.js.map +1 -0
- package/dist/{chunk-XATDZX7U.js → chunk-R2JIJBCL.js} +18 -4
- package/dist/{chunk-XATDZX7U.js.map → chunk-R2JIJBCL.js.map} +1 -1
- package/dist/{chunk-MYOZLMB2.js → chunk-RL5R4CQU.js} +538 -19
- package/dist/chunk-RL5R4CQU.js.map +1 -0
- package/dist/{chunk-EVDQKYCG.js → chunk-RQSJLWP4.js} +13 -2
- package/dist/chunk-RQSJLWP4.js.map +1 -0
- package/dist/{chunk-BPRIYNLE.js → chunk-TKAJ3JVF.js} +3 -3
- package/dist/{chunk-Q36VMZST.js → chunk-VHNRMM4O.js} +3 -2
- package/dist/{chunk-FMRZ26U5.js → chunk-X3IGT5RV.js} +5 -2
- package/dist/{chunk-FMRZ26U5.js.map → chunk-X3IGT5RV.js.map} +1 -1
- package/dist/{chunk-KHT24OWC.js → chunk-YDUOSRGD.js} +8 -94
- package/dist/{chunk-KHT24OWC.js.map → chunk-YDUOSRGD.js.map} +1 -1
- package/dist/{cli-GGPWH4UO.js → cli-LNYSTDQM.js} +49 -42
- package/dist/cli-LNYSTDQM.js.map +1 -0
- package/dist/{client-YXQUTXVZ.js → client-NWE4TCNO.js} +4 -4
- package/dist/{config-OMCYHG2S.js → config-VC4ACP42.js} +6 -4
- package/dist/{config-OMCYHG2S.js.map → config-VC4ACP42.js.map} +1 -1
- package/dist/{detect-providers-5KOPZ7J2.js → detect-providers-ILLQZROY.js} +4 -4
- package/dist/{doctor-5JXJ36KA.js → doctor-TI7EZ3RW.js} +48 -15
- package/dist/doctor-TI7EZ3RW.js.map +1 -0
- package/dist/executor-F2YU7HXJ.js +44 -0
- package/dist/{init-LMYOVZAV.js → init-KG3TYVGE.js} +14 -12
- package/dist/{init-LMYOVZAV.js.map → init-KG3TYVGE.js.map} +1 -1
- package/dist/{installer-FS257JRZ.js → installer-UMH7OJ5A.js} +6 -4
- package/dist/{llm-TH4NLIRM.js → llm-AGVEF5XD.js} +5 -4
- package/dist/{loader-CQYTFHEW.js → loader-LX7TFRM6.js} +5 -3
- package/dist/{loader-NOMBJUPW.js → loader-NAVVZK63.js} +4 -3
- package/dist/{main-YTBVRTBI.js → main-5PRQNEEE.js} +2453 -650
- package/dist/main-5PRQNEEE.js.map +1 -0
- package/dist/{open-HG2DX6RN.js → open-5A27BCSB.js} +10 -8
- package/dist/{open-HG2DX6RN.js.map → open-5A27BCSB.js.map} +1 -1
- package/dist/{post-compact-JSECI44W.js → post-compact-USAODKPQ.js} +6 -6
- package/dist/{post-tool-use-POGPTJBA.js → post-tool-use-GMMSYBII.js} +9 -7
- package/dist/post-tool-use-GMMSYBII.js.map +1 -0
- package/dist/{post-tool-use-failure-OT7BFWQW.js → post-tool-use-failure-NZVSL2PO.js} +6 -6
- package/dist/{pre-compact-OXVODKH4.js → pre-compact-LZ57DLUS.js} +6 -6
- package/dist/{provider-check-43LAMSMH.js → provider-check-ZEV5P4KM.js} +4 -4
- package/dist/{registry-U4CHXK6R.js → registry-M2Z5QBWH.js} +5 -4
- package/dist/{remove-N7ZPELFU.js → remove-T3KE6C5N.js} +10 -8
- package/dist/{remove-N7ZPELFU.js.map → remove-T3KE6C5N.js.map} +1 -1
- package/dist/{restart-ADG5GBTB.js → restart-YWDEVZUJ.js} +11 -9
- package/dist/{restart-ADG5GBTB.js.map → restart-YWDEVZUJ.js.map} +1 -1
- package/dist/{search-AHZEUNRR.js → search-GKFDGELR.js} +11 -9
- package/dist/{search-AHZEUNRR.js.map → search-GKFDGELR.js.map} +1 -1
- package/dist/{server-AGVYZVP5.js → server-AHUR6CWF.js} +368 -269
- package/dist/server-AHUR6CWF.js.map +1 -0
- package/dist/{session-6IU4AXYP.js → session-2ZEPLWW6.js} +11 -9
- package/dist/{session-6IU4AXYP.js.map → session-2ZEPLWW6.js.map} +1 -1
- package/dist/{session-end-FT27DWYZ.js → session-end-LWJYQAXX.js} +5 -5
- package/dist/session-start-WTA6GCOQ.js +134 -0
- package/dist/session-start-WTA6GCOQ.js.map +1 -0
- package/dist/{setup-llm-77MP4I2G.js → setup-llm-E7UU5IO7.js} +11 -9
- package/dist/{setup-llm-77MP4I2G.js.map → setup-llm-E7UU5IO7.js.map} +1 -1
- package/dist/src/agent/definitions/agent.yaml +9 -5
- package/dist/src/agent/definitions/tasks/cortex-instructions.yaml +93 -0
- package/dist/src/agent/definitions/tasks/cortex-prompt-builder.yaml +67 -0
- package/dist/src/agent/definitions/tasks/digest-only.yaml +1 -1
- package/dist/src/agent/definitions/tasks/extract-only.yaml +1 -1
- package/dist/src/agent/definitions/tasks/review-session.yaml +10 -39
- package/dist/src/agent/definitions/tasks/skill-evolve.yaml +4 -4
- package/dist/src/agent/definitions/tasks/skill-generate.yaml +1 -1
- package/dist/src/agent/definitions/tasks/skill-survey.yaml +2 -6
- package/dist/src/agent/definitions/tasks/supersession-sweep.yaml +1 -1
- package/dist/src/agent/definitions/tasks/title-summary.yaml +12 -19
- package/dist/src/agent/definitions/tasks/{full-intelligence.yaml → vault-evolve.yaml} +17 -82
- package/dist/src/agent/definitions/tasks/vault-seed.yaml +370 -0
- package/dist/src/agent/prompts/agent.md +12 -38
- 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/symbionts/manifests/claude-code.yaml +4 -0
- package/dist/src/symbionts/manifests/pi.yaml +22 -0
- package/dist/src/symbionts/templates/pi/package.json +6 -0
- package/dist/src/symbionts/templates/pi/plugin.ts +559 -0
- package/dist/{stats-NVPWOYTE.js → stats-DFG6S23S.js} +11 -9
- package/dist/{stats-NVPWOYTE.js.map → stats-DFG6S23S.js.map} +1 -1
- package/dist/{stop-ZPIKVLH4.js → stop-WRBTXEVT.js} +5 -5
- package/dist/{stop-failure-2PX67YJC.js → stop-failure-32MGIG2Q.js} +6 -6
- package/dist/{subagent-start-UUE6EHQD.js → subagent-start-VFGHQFVL.js} +6 -6
- package/dist/{subagent-stop-KQWWWPE6.js → subagent-stop-663FXG3P.js} +6 -6
- package/dist/{task-completed-WMHOFQ7B.js → task-completed-ZCQYEFMZ.js} +6 -6
- package/dist/{team-LRZ6GTQK.js → team-JTI5CDUO.js} +7 -5
- package/dist/{turns-YFNI5CQC.js → turns-HU2CTZAP.js} +2 -2
- package/dist/ui/assets/index-DGf1h-Ha.js +842 -0
- package/dist/ui/assets/index-_OP4ifzH.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/{update-O6V4RC4W.js → update-3NBQTG32.js} +10 -8
- package/dist/{update-O6V4RC4W.js.map → update-3NBQTG32.js.map} +1 -1
- package/dist/{user-prompt-submit-N36KUPHI.js → user-prompt-submit-ME2TBKOS.js} +8 -7
- package/dist/{user-prompt-submit-N36KUPHI.js.map → user-prompt-submit-ME2TBKOS.js.map} +1 -1
- package/dist/{verify-LXPV7NYG.js → verify-R76ZFJSZ.js} +8 -5
- package/dist/{verify-LXPV7NYG.js.map → verify-R76ZFJSZ.js.map} +1 -1
- package/dist/{version-XMPPJQHR.js → version-GQAFBBPX.js} +2 -2
- package/dist/version-GQAFBBPX.js.map +1 -0
- package/package.json +3 -1
- package/skills/myco/SKILL.md +16 -1
- package/skills/myco/references/cli-usage.md +1 -1
- package/skills/myco-curate/SKILL.md +1 -1
- package/dist/chunk-4YFKBL3F.js +0 -195
- package/dist/chunk-4YFKBL3F.js.map +0 -1
- package/dist/chunk-CCRGY3QW.js.map +0 -1
- package/dist/chunk-EVDQKYCG.js.map +0 -1
- package/dist/chunk-FLLBJLHM.js.map +0 -1
- package/dist/chunk-MYOZLMB2.js.map +0 -1
- package/dist/chunk-OD4AA7PV.js.map +0 -1
- package/dist/chunk-US4LNCAT.js.map +0 -1
- package/dist/chunk-UYMFCYBF.js +0 -2326
- package/dist/chunk-UYMFCYBF.js.map +0 -1
- package/dist/chunk-VVNL26WX.js.map +0 -1
- package/dist/cli-GGPWH4UO.js.map +0 -1
- package/dist/doctor-5JXJ36KA.js.map +0 -1
- package/dist/executor-HWW2QNZQ.js +0 -2472
- package/dist/executor-HWW2QNZQ.js.map +0 -1
- package/dist/main-YTBVRTBI.js.map +0 -1
- package/dist/post-tool-use-POGPTJBA.js.map +0 -1
- package/dist/server-AGVYZVP5.js.map +0 -1
- package/dist/session-start-LAFICHII.js +0 -189
- package/dist/session-start-LAFICHII.js.map +0 -1
- package/dist/src/agent/definitions/tasks/graph-maintenance.yaml +0 -93
- package/dist/ui/assets/index-C2JuNtRB.css +0 -1
- package/dist/ui/assets/index-JLVaQKV2.js +0 -832
- /package/dist/{chunk-DCSGJ7W4.js.map → chunk-5ZG4RMUH.js.map} +0 -0
- /package/dist/{chunk-JZGN33AY.js.map → chunk-75Z7UKDY.js.map} +0 -0
- /package/dist/{chunk-XG5RRUYF.js.map → chunk-BUTL6IFS.js.map} +0 -0
- /package/dist/{chunk-KESLPBKV.js.map → chunk-JR54LTPP.js.map} +0 -0
- /package/dist/{chunk-5XIVBO25.js.map → chunk-LVIY7P35.js.map} +0 -0
- /package/dist/{chunk-BPRIYNLE.js.map → chunk-TKAJ3JVF.js.map} +0 -0
- /package/dist/{chunk-Q36VMZST.js.map → chunk-VHNRMM4O.js.map} +0 -0
- /package/dist/{client-YXQUTXVZ.js.map → client-NWE4TCNO.js.map} +0 -0
- /package/dist/{detect-providers-5KOPZ7J2.js.map → detect-providers-ILLQZROY.js.map} +0 -0
- /package/dist/{installer-FS257JRZ.js.map → executor-F2YU7HXJ.js.map} +0 -0
- /package/dist/{llm-TH4NLIRM.js.map → installer-UMH7OJ5A.js.map} +0 -0
- /package/dist/{loader-CQYTFHEW.js.map → llm-AGVEF5XD.js.map} +0 -0
- /package/dist/{loader-NOMBJUPW.js.map → loader-LX7TFRM6.js.map} +0 -0
- /package/dist/{provider-check-43LAMSMH.js.map → loader-NAVVZK63.js.map} +0 -0
- /package/dist/{post-compact-JSECI44W.js.map → post-compact-USAODKPQ.js.map} +0 -0
- /package/dist/{post-tool-use-failure-OT7BFWQW.js.map → post-tool-use-failure-NZVSL2PO.js.map} +0 -0
- /package/dist/{pre-compact-OXVODKH4.js.map → pre-compact-LZ57DLUS.js.map} +0 -0
- /package/dist/{registry-U4CHXK6R.js.map → provider-check-ZEV5P4KM.js.map} +0 -0
- /package/dist/{team-LRZ6GTQK.js.map → registry-M2Z5QBWH.js.map} +0 -0
- /package/dist/{session-end-FT27DWYZ.js.map → session-end-LWJYQAXX.js.map} +0 -0
- /package/dist/{stop-ZPIKVLH4.js.map → stop-WRBTXEVT.js.map} +0 -0
- /package/dist/{stop-failure-2PX67YJC.js.map → stop-failure-32MGIG2Q.js.map} +0 -0
- /package/dist/{subagent-start-UUE6EHQD.js.map → subagent-start-VFGHQFVL.js.map} +0 -0
- /package/dist/{subagent-stop-KQWWWPE6.js.map → subagent-stop-663FXG3P.js.map} +0 -0
- /package/dist/{task-completed-WMHOFQ7B.js.map → task-completed-ZCQYEFMZ.js.map} +0 -0
- /package/dist/{turns-YFNI5CQC.js.map → team-JTI5CDUO.js.map} +0 -0
- /package/dist/{version-XMPPJQHR.js.map → turns-HU2CTZAP.js.map} +0 -0
|
@@ -1,2472 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
DESCRIPTION_DUPLICATE_THRESHOLD,
|
|
4
|
-
SKILL_GENERATE_TASK,
|
|
5
|
-
STATUS_COMPLETED,
|
|
6
|
-
STATUS_FAILED,
|
|
7
|
-
STATUS_RUNNING,
|
|
8
|
-
TOPIC_OVERLAP_THRESHOLD,
|
|
9
|
-
checkFrontmatterPreservation,
|
|
10
|
-
createSporeLineage,
|
|
11
|
-
deleteCandidate,
|
|
12
|
-
deleteSkillRecordCascade,
|
|
13
|
-
descriptionSimilarity,
|
|
14
|
-
errorMessage,
|
|
15
|
-
getCandidate,
|
|
16
|
-
getRunningRunForTask,
|
|
17
|
-
getSkillRecord,
|
|
18
|
-
getSkillRecordByName,
|
|
19
|
-
getStatesForAgent,
|
|
20
|
-
getUnprocessedBatches,
|
|
21
|
-
insertCandidate,
|
|
22
|
-
insertEntity,
|
|
23
|
-
insertGraphEdge,
|
|
24
|
-
insertLineage,
|
|
25
|
-
insertReport,
|
|
26
|
-
insertResolutionEvent,
|
|
27
|
-
insertRun,
|
|
28
|
-
insertSkillRecord,
|
|
29
|
-
listCandidates,
|
|
30
|
-
listDigestExtracts,
|
|
31
|
-
listEntities,
|
|
32
|
-
listGraphEdges,
|
|
33
|
-
listSkillRecords,
|
|
34
|
-
markBatchProcessed,
|
|
35
|
-
notify,
|
|
36
|
-
resolveRunConfig,
|
|
37
|
-
setState,
|
|
38
|
-
topicOverlapSimilarity,
|
|
39
|
-
updateCandidate,
|
|
40
|
-
updateRunStatus,
|
|
41
|
-
updateSkillRecord,
|
|
42
|
-
upsertDigestExtract,
|
|
43
|
-
validateSkillContent
|
|
44
|
-
} from "./chunk-UYMFCYBF.js";
|
|
45
|
-
import {
|
|
46
|
-
fullTextSearch,
|
|
47
|
-
hydrateSearchResults
|
|
48
|
-
} from "./chunk-5XIVBO25.js";
|
|
49
|
-
import "./chunk-6X2ERTQV.js";
|
|
50
|
-
import {
|
|
51
|
-
getDefaultTask,
|
|
52
|
-
loadSystemPrompt
|
|
53
|
-
} from "./chunk-CCRGY3QW.js";
|
|
54
|
-
import {
|
|
55
|
-
insertTurn,
|
|
56
|
-
updateTurn
|
|
57
|
-
} from "./chunk-6RFZWV4R.js";
|
|
58
|
-
import {
|
|
59
|
-
cleanupStagedSkill,
|
|
60
|
-
readStagedManifest,
|
|
61
|
-
readStagedSkill,
|
|
62
|
-
writeStagedManifest,
|
|
63
|
-
writeStagedSkill
|
|
64
|
-
} from "./chunk-U5EW2VIQ.js";
|
|
65
|
-
import "./chunk-POEPHBQK.js";
|
|
66
|
-
import {
|
|
67
|
-
DEFAULT_IMPORTANCE,
|
|
68
|
-
getSpore,
|
|
69
|
-
insertSpore,
|
|
70
|
-
listSpores,
|
|
71
|
-
updateSporeStatus
|
|
72
|
-
} from "./chunk-4YFKBL3F.js";
|
|
73
|
-
import {
|
|
74
|
-
getActiveSessionIds,
|
|
75
|
-
listSessions,
|
|
76
|
-
updateSession
|
|
77
|
-
} from "./chunk-EVDQKYCG.js";
|
|
78
|
-
import {
|
|
79
|
-
AGENT_SETTABLE_STATUSES,
|
|
80
|
-
CANDIDATE_STATUS,
|
|
81
|
-
createSchema
|
|
82
|
-
} from "./chunk-MYOZLMB2.js";
|
|
83
|
-
import "./chunk-OD4AA7PV.js";
|
|
84
|
-
import {
|
|
85
|
-
getDatabase,
|
|
86
|
-
initDatabase,
|
|
87
|
-
vaultDbPath
|
|
88
|
-
} from "./chunk-MYX5NCRH.js";
|
|
89
|
-
import {
|
|
90
|
-
getPluginVersion
|
|
91
|
-
} from "./chunk-XG5RRUYF.js";
|
|
92
|
-
import {
|
|
93
|
-
findPackageRoot
|
|
94
|
-
} from "./chunk-LPUQPDC2.js";
|
|
95
|
-
import {
|
|
96
|
-
DEFAULT_AGENT_ID,
|
|
97
|
-
DEFAULT_LIST_LIMIT,
|
|
98
|
-
MS_PER_SECOND,
|
|
99
|
-
PHASE_SUMMARY_MAX_CHARS,
|
|
100
|
-
SEARCH_SIMILARITY_THRESHOLD,
|
|
101
|
-
TEAM_SOURCE_PREFIX,
|
|
102
|
-
epochSeconds
|
|
103
|
-
} from "./chunk-FLLBJLHM.js";
|
|
104
|
-
import "./chunk-UUHLLQXO.js";
|
|
105
|
-
import "./chunk-6LQIMRTC.js";
|
|
106
|
-
import "./chunk-ODXLRR4U.js";
|
|
107
|
-
import {
|
|
108
|
-
external_exports
|
|
109
|
-
} from "./chunk-U6PF3YII.js";
|
|
110
|
-
import "./chunk-PZUWP5VK.js";
|
|
111
|
-
|
|
112
|
-
// src/agent/executor.ts
|
|
113
|
-
import crypto4 from "crypto";
|
|
114
|
-
import { resolve as resolve2 } from "path";
|
|
115
|
-
|
|
116
|
-
// src/agent/tools.ts
|
|
117
|
-
import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
118
|
-
|
|
119
|
-
// src/agent/tools/read-tools.ts
|
|
120
|
-
import { tool } from "@anthropic-ai/claude-agent-sdk";
|
|
121
|
-
|
|
122
|
-
// src/agent/tools/types.ts
|
|
123
|
-
function textResult(data) {
|
|
124
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// src/agent/tools/read-tools.ts
|
|
128
|
-
var DEFAULT_UNPROCESSED_LIMIT = 50;
|
|
129
|
-
var DEFAULT_SPORES_LIMIT = 50;
|
|
130
|
-
var DEFAULT_SESSIONS_LIMIT = 20;
|
|
131
|
-
var DEFAULT_SEARCH_LIMIT = 10;
|
|
132
|
-
var DEFAULT_ENTITIES_LIMIT = 50;
|
|
133
|
-
var DEFAULT_EDGES_LIMIT = 50;
|
|
134
|
-
function createReadTools(deps) {
|
|
135
|
-
const { agentId, embeddingManager, teamClient, machineId } = deps;
|
|
136
|
-
const vaultUnprocessed = tool(
|
|
137
|
-
"vault_unprocessed",
|
|
138
|
-
"Get unprocessed prompt batches, ordered by id ASC. Supports cursor-based pagination. Batches from in-flight sessions are excluded by default so intelligence tasks only process settled work; pass include_active=true only if you specifically need live data (e.g., title-summary).",
|
|
139
|
-
{
|
|
140
|
-
after_id: external_exports.number().optional().describe("Return batches with id greater than this"),
|
|
141
|
-
limit: external_exports.number().optional().describe("Maximum number of batches to return"),
|
|
142
|
-
include_active: external_exports.boolean().optional().describe("Include batches from sessions still in active status (default: false)")
|
|
143
|
-
},
|
|
144
|
-
async (args) => {
|
|
145
|
-
const batches = getUnprocessedBatches({
|
|
146
|
-
after_id: args.after_id,
|
|
147
|
-
limit: args.limit ?? DEFAULT_UNPROCESSED_LIMIT,
|
|
148
|
-
includeActive: args.include_active === true
|
|
149
|
-
});
|
|
150
|
-
return textResult(batches);
|
|
151
|
-
},
|
|
152
|
-
{ annotations: { readOnlyHint: true } }
|
|
153
|
-
);
|
|
154
|
-
const vaultSpores = tool(
|
|
155
|
-
"vault_spores",
|
|
156
|
-
"List spores with optional filters (agent, observation type, status, session), or fetch exact spores by id for full-content inspection after a semantic shortlist. Spores from in-flight sessions are excluded by default; passing a specific session_id or ids bypasses this filter. Pass include_active=true to bulk-read live work.",
|
|
157
|
-
{
|
|
158
|
-
ids: external_exports.array(external_exports.string()).optional().describe("Fetch exact spores by id in the given order; bypasses active-session gating"),
|
|
159
|
-
agent_id: external_exports.string().optional().describe("Filter by agent ID"),
|
|
160
|
-
observation_type: external_exports.string().optional().describe("Filter by observation type (e.g., gotcha, decision)"),
|
|
161
|
-
status: external_exports.enum(["active", "superseded", "archived"]).optional().describe("Filter by status"),
|
|
162
|
-
session_id: external_exports.string().optional().describe("Filter by session ID (bypasses active-session gating)"),
|
|
163
|
-
limit: external_exports.number().optional().describe("Maximum number of spores to return"),
|
|
164
|
-
include_active: external_exports.boolean().optional().describe("Include spores from sessions still in active status (default: false)")
|
|
165
|
-
},
|
|
166
|
-
async (args) => {
|
|
167
|
-
if (args.ids && args.ids.length > 0) {
|
|
168
|
-
const spores2 = args.ids.map((id) => getSpore(id)).filter((spore) => spore !== null);
|
|
169
|
-
return textResult(spores2);
|
|
170
|
-
}
|
|
171
|
-
const spores = listSpores({
|
|
172
|
-
agent_id: args.agent_id,
|
|
173
|
-
observation_type: args.observation_type,
|
|
174
|
-
status: args.status,
|
|
175
|
-
session_id: args.session_id,
|
|
176
|
-
limit: args.limit ?? DEFAULT_SPORES_LIMIT,
|
|
177
|
-
includeActive: args.include_active === true
|
|
178
|
-
});
|
|
179
|
-
return textResult(spores);
|
|
180
|
-
},
|
|
181
|
-
{ annotations: { readOnlyHint: true } }
|
|
182
|
-
);
|
|
183
|
-
const vaultSessions = tool(
|
|
184
|
-
"vault_sessions",
|
|
185
|
-
"List sessions with optional status filter, ordered by created_at DESC. In-flight sessions are excluded by default; pass include_active=true or an explicit status to see them.",
|
|
186
|
-
{
|
|
187
|
-
limit: external_exports.number().optional().describe("Maximum number of sessions to return"),
|
|
188
|
-
status: external_exports.string().optional().describe("Filter by status (active, completed)"),
|
|
189
|
-
include_active: external_exports.boolean().optional().describe("Include sessions still in active status (default: false)")
|
|
190
|
-
},
|
|
191
|
-
async (args) => {
|
|
192
|
-
const sessions = listSessions({
|
|
193
|
-
limit: args.limit ?? DEFAULT_SESSIONS_LIMIT,
|
|
194
|
-
status: args.status,
|
|
195
|
-
includeActive: args.include_active === true
|
|
196
|
-
});
|
|
197
|
-
return textResult(sessions);
|
|
198
|
-
},
|
|
199
|
-
{ annotations: { readOnlyHint: true } }
|
|
200
|
-
);
|
|
201
|
-
const vaultSearchFts = tool(
|
|
202
|
-
"vault_search_fts",
|
|
203
|
-
"Full-text search across sessions, spores, prompt batches, and activities using FTS5. Best for finding exact keywords, file paths, function names, and specific text. Results from in-flight sessions are hidden by default so intelligence tasks only see settled work; pass include_active=true to bypass.",
|
|
204
|
-
{
|
|
205
|
-
query: external_exports.string().describe("Search query text"),
|
|
206
|
-
type: external_exports.string().optional().describe("Restrict to a result type (session, spore, prompt_batch, activity)"),
|
|
207
|
-
limit: external_exports.number().optional().describe("Maximum number of results to return"),
|
|
208
|
-
include_active: external_exports.boolean().optional().describe("Include results from sessions still in active status (default: false)")
|
|
209
|
-
},
|
|
210
|
-
async (args) => {
|
|
211
|
-
try {
|
|
212
|
-
const results = fullTextSearch(args.query, {
|
|
213
|
-
type: args.type,
|
|
214
|
-
limit: args.limit ?? DEFAULT_SEARCH_LIMIT,
|
|
215
|
-
includeActive: args.include_active === true
|
|
216
|
-
});
|
|
217
|
-
return textResult({ results });
|
|
218
|
-
} catch {
|
|
219
|
-
return textResult({ results: [], message: "Search unavailable" });
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
{ annotations: { readOnlyHint: true } }
|
|
223
|
-
);
|
|
224
|
-
const vaultSearchSemantic = tool(
|
|
225
|
-
"vault_search_semantic",
|
|
226
|
-
"Semantic similarity search across embedded vault content (spores, sessions, plans, artifacts, skill_records). Best for finding conceptually related content. Returns results ranked by similarity score. Results from in-flight sessions are filtered out by default; pass include_active=true to bypass.",
|
|
227
|
-
{
|
|
228
|
-
query: external_exports.string().describe("Search query text"),
|
|
229
|
-
namespace: external_exports.string().optional().describe("Restrict to a content type: spores, sessions, plans, artifacts, skill_records. Omit to search all."),
|
|
230
|
-
limit: external_exports.number().optional().describe("Maximum results to return"),
|
|
231
|
-
include_active: external_exports.boolean().optional().describe("Include results from sessions still in active status (default: false)")
|
|
232
|
-
},
|
|
233
|
-
async (args) => {
|
|
234
|
-
if (!embeddingManager) {
|
|
235
|
-
return textResult({ results: [], message: "Embedding provider unavailable" });
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
const queryVector = await embeddingManager.embedQuery(args.query);
|
|
239
|
-
if (!queryVector) {
|
|
240
|
-
return textResult({ results: [], message: "Embedding provider unavailable" });
|
|
241
|
-
}
|
|
242
|
-
const searchLimit = args.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
243
|
-
const excludeActive = args.include_active !== true;
|
|
244
|
-
const activeIds = excludeActive ? getActiveSessionIds() : /* @__PURE__ */ new Set();
|
|
245
|
-
const [rawLocalResults, teamResults] = await Promise.all([
|
|
246
|
-
Promise.resolve(
|
|
247
|
-
embeddingManager.searchVectors(queryVector, {
|
|
248
|
-
namespace: args.namespace,
|
|
249
|
-
limit: searchLimit,
|
|
250
|
-
threshold: SEARCH_SIMILARITY_THRESHOLD
|
|
251
|
-
}).map((r) => ({ ...r, source: "local" }))
|
|
252
|
-
),
|
|
253
|
-
teamClient ? teamClient.search(args.query, { limit: searchLimit }).then((res) => res.results.map((r) => ({ ...r, source: `${TEAM_SOURCE_PREFIX}${r.machine_id}` }))).catch(() => []) : Promise.resolve([])
|
|
254
|
-
]);
|
|
255
|
-
const localResults = activeIds.size > 0 ? rawLocalResults.filter((r) => {
|
|
256
|
-
const sid = r.metadata?.session_id;
|
|
257
|
-
return typeof sid !== "string" || !activeIds.has(sid);
|
|
258
|
-
}) : rawLocalResults;
|
|
259
|
-
const hydratedLocalResults = hydrateSearchResults(localResults).map((r) => ({
|
|
260
|
-
...r,
|
|
261
|
-
source: "local"
|
|
262
|
-
}));
|
|
263
|
-
let dedupedTeam = machineId ? teamResults.filter((r) => r.machine_id !== machineId) : teamResults;
|
|
264
|
-
if (activeIds.size > 0) {
|
|
265
|
-
dedupedTeam = dedupedTeam.filter((r) => {
|
|
266
|
-
const sid = r.metadata?.session_id;
|
|
267
|
-
return typeof sid !== "string" || !activeIds.has(sid);
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
const merged = [
|
|
271
|
-
...hydratedLocalResults,
|
|
272
|
-
...dedupedTeam
|
|
273
|
-
].sort((a, b) => (b.score ?? 0) - (a.score ?? 0)).slice(0, searchLimit);
|
|
274
|
-
return textResult({ results: merged });
|
|
275
|
-
} catch {
|
|
276
|
-
return textResult({ results: [], message: "Semantic search unavailable" });
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
{ annotations: { readOnlyHint: true, openWorldHint: true } }
|
|
280
|
-
);
|
|
281
|
-
const vaultState = tool(
|
|
282
|
-
"vault_state",
|
|
283
|
-
"Get all state key-value pairs for the current agent.",
|
|
284
|
-
{},
|
|
285
|
-
async () => {
|
|
286
|
-
const states = getStatesForAgent(agentId);
|
|
287
|
-
return textResult(states);
|
|
288
|
-
},
|
|
289
|
-
{ annotations: { readOnlyHint: true } }
|
|
290
|
-
);
|
|
291
|
-
const vaultEntities = tool(
|
|
292
|
-
"vault_entities",
|
|
293
|
-
"List knowledge graph entities with optional filters.",
|
|
294
|
-
{
|
|
295
|
-
type: external_exports.enum(["component", "concept", "person"]).optional().describe("Filter by entity type"),
|
|
296
|
-
name: external_exports.string().optional().describe("Filter by entity name (exact match)"),
|
|
297
|
-
limit: external_exports.number().optional().describe("Maximum entities to return")
|
|
298
|
-
},
|
|
299
|
-
async (args) => {
|
|
300
|
-
const entities = listEntities({
|
|
301
|
-
agent_id: agentId,
|
|
302
|
-
type: args.type,
|
|
303
|
-
name: args.name,
|
|
304
|
-
limit: args.limit ?? DEFAULT_ENTITIES_LIMIT
|
|
305
|
-
});
|
|
306
|
-
return textResult(entities);
|
|
307
|
-
},
|
|
308
|
-
{ annotations: { readOnlyHint: true } }
|
|
309
|
-
);
|
|
310
|
-
const vaultEdges = tool(
|
|
311
|
-
"vault_edges",
|
|
312
|
-
"List knowledge graph edges with optional filters. Use to check existing relationships before creating new ones.",
|
|
313
|
-
{
|
|
314
|
-
source_id: external_exports.string().optional().describe("Filter by source node ID"),
|
|
315
|
-
target_id: external_exports.string().optional().describe("Filter by target node ID"),
|
|
316
|
-
type: external_exports.string().optional().describe("Filter by edge type (REFERENCES, DEPENDS_ON, AFFECTS, etc.)"),
|
|
317
|
-
limit: external_exports.number().optional().describe("Maximum edges to return")
|
|
318
|
-
},
|
|
319
|
-
async (args) => {
|
|
320
|
-
const edges = listGraphEdges({
|
|
321
|
-
sourceId: args.source_id,
|
|
322
|
-
targetId: args.target_id,
|
|
323
|
-
type: args.type,
|
|
324
|
-
agentId,
|
|
325
|
-
limit: args.limit ?? DEFAULT_EDGES_LIMIT
|
|
326
|
-
});
|
|
327
|
-
return textResult(edges);
|
|
328
|
-
},
|
|
329
|
-
{ annotations: { readOnlyHint: true } }
|
|
330
|
-
);
|
|
331
|
-
return [
|
|
332
|
-
vaultUnprocessed,
|
|
333
|
-
vaultSpores,
|
|
334
|
-
vaultSessions,
|
|
335
|
-
vaultSearchFts,
|
|
336
|
-
vaultSearchSemantic,
|
|
337
|
-
vaultState,
|
|
338
|
-
vaultEntities,
|
|
339
|
-
vaultEdges
|
|
340
|
-
];
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// src/agent/tools/write-tools.ts
|
|
344
|
-
import crypto from "crypto";
|
|
345
|
-
import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
346
|
-
function createWriteTools(deps) {
|
|
347
|
-
const { agentId, embeddingManager, machineId } = deps;
|
|
348
|
-
const vaultCreateSpore = tool2(
|
|
349
|
-
"vault_create_spore",
|
|
350
|
-
"Create a new spore (observation) in the vault. The agent_id is set automatically.",
|
|
351
|
-
{
|
|
352
|
-
observation_type: external_exports.string().describe("Type of observation (gotcha, decision, discovery, trade-off, bug_fix, etc.)"),
|
|
353
|
-
content: external_exports.string().describe("The observation content in markdown"),
|
|
354
|
-
session_id: external_exports.string().optional().describe("Associated session ID"),
|
|
355
|
-
prompt_batch_id: external_exports.number().optional().describe("Associated prompt batch ID"),
|
|
356
|
-
importance: external_exports.number().optional().describe("Importance score 1-10 (default 5)"),
|
|
357
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
358
|
-
context: external_exports.string().optional().describe("Additional context about the observation"),
|
|
359
|
-
file_path: external_exports.string().optional().describe("Related file path"),
|
|
360
|
-
properties: external_exports.string().optional().describe('JSON metadata (e.g., {"consolidated_from": [...]} for wisdom spores)')
|
|
361
|
-
},
|
|
362
|
-
async (args) => {
|
|
363
|
-
const id = crypto.randomUUID();
|
|
364
|
-
const now = epochSeconds();
|
|
365
|
-
const spore = insertSpore({
|
|
366
|
-
id,
|
|
367
|
-
agent_id: agentId,
|
|
368
|
-
machine_id: machineId,
|
|
369
|
-
observation_type: args.observation_type,
|
|
370
|
-
content: args.content,
|
|
371
|
-
session_id: args.session_id ?? null,
|
|
372
|
-
prompt_batch_id: args.prompt_batch_id ?? null,
|
|
373
|
-
importance: args.importance ?? DEFAULT_IMPORTANCE,
|
|
374
|
-
tags: args.tags ? JSON.stringify(args.tags) : null,
|
|
375
|
-
context: args.context ?? null,
|
|
376
|
-
file_path: args.file_path ?? null,
|
|
377
|
-
properties: args.properties ?? null,
|
|
378
|
-
created_at: now
|
|
379
|
-
});
|
|
380
|
-
try {
|
|
381
|
-
createSporeLineage(spore);
|
|
382
|
-
} catch {
|
|
383
|
-
}
|
|
384
|
-
embeddingManager?.onContentWritten("spores", spore.id, args.content, {
|
|
385
|
-
status: "active",
|
|
386
|
-
observation_type: args.observation_type,
|
|
387
|
-
session_id: args.session_id
|
|
388
|
-
}).catch(() => {
|
|
389
|
-
});
|
|
390
|
-
return textResult(spore);
|
|
391
|
-
},
|
|
392
|
-
{ annotations: { openWorldHint: true } }
|
|
393
|
-
);
|
|
394
|
-
const vaultCreateEntity = tool2(
|
|
395
|
-
"vault_create_entity",
|
|
396
|
-
"Create or update an entity in the knowledge graph. Uses UPSERT on (agent_id, type, name).",
|
|
397
|
-
{
|
|
398
|
-
type: external_exports.enum(["component", "concept", "person"]).describe("Entity type"),
|
|
399
|
-
name: external_exports.string().describe("Entity name (unique within agent + type)"),
|
|
400
|
-
properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Additional properties as key-value pairs")
|
|
401
|
-
},
|
|
402
|
-
async (args) => {
|
|
403
|
-
const id = crypto.randomUUID();
|
|
404
|
-
const now = epochSeconds();
|
|
405
|
-
const props = args.properties ? JSON.stringify(args.properties) : null;
|
|
406
|
-
const entity = insertEntity({
|
|
407
|
-
id,
|
|
408
|
-
agent_id: agentId,
|
|
409
|
-
machine_id: machineId,
|
|
410
|
-
type: args.type,
|
|
411
|
-
name: args.name,
|
|
412
|
-
properties: props,
|
|
413
|
-
first_seen: now,
|
|
414
|
-
last_seen: now
|
|
415
|
-
});
|
|
416
|
-
return textResult(entity);
|
|
417
|
-
},
|
|
418
|
-
{ annotations: { idempotentHint: true } }
|
|
419
|
-
);
|
|
420
|
-
const vaultCreateEdge = tool2(
|
|
421
|
-
"vault_create_edge",
|
|
422
|
-
"Create a semantic edge in the knowledge graph. Lineage edges (FROM_SESSION, EXTRACTED_FROM, HAS_BATCH, DERIVED_FROM) are created automatically \u2014 do NOT create those.",
|
|
423
|
-
{
|
|
424
|
-
source_id: external_exports.string().describe("Source node ID"),
|
|
425
|
-
source_type: external_exports.enum(["session", "batch", "spore", "entity"]).describe("Source node type"),
|
|
426
|
-
target_id: external_exports.string().describe("Target node ID"),
|
|
427
|
-
target_type: external_exports.enum(["session", "batch", "spore", "entity"]).describe("Target node type"),
|
|
428
|
-
type: external_exports.enum(["RELATES_TO", "SUPERSEDED_BY", "REFERENCES", "DEPENDS_ON", "AFFECTS"]).describe("Semantic edge type"),
|
|
429
|
-
session_id: external_exports.string().optional().describe("Session where this relationship was observed"),
|
|
430
|
-
confidence: external_exports.number().optional().describe("Confidence score 0-1 (default 1.0)"),
|
|
431
|
-
properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Additional properties as key-value pairs")
|
|
432
|
-
},
|
|
433
|
-
async (args) => {
|
|
434
|
-
const now = epochSeconds();
|
|
435
|
-
const props = args.properties ? JSON.stringify(args.properties) : void 0;
|
|
436
|
-
const edge = insertGraphEdge({
|
|
437
|
-
agent_id: agentId,
|
|
438
|
-
machine_id: machineId,
|
|
439
|
-
source_id: args.source_id,
|
|
440
|
-
source_type: args.source_type,
|
|
441
|
-
target_id: args.target_id,
|
|
442
|
-
target_type: args.target_type,
|
|
443
|
-
type: args.type,
|
|
444
|
-
session_id: args.session_id,
|
|
445
|
-
confidence: args.confidence,
|
|
446
|
-
properties: props,
|
|
447
|
-
created_at: now
|
|
448
|
-
});
|
|
449
|
-
return textResult(edge);
|
|
450
|
-
},
|
|
451
|
-
{ annotations: { idempotentHint: true } }
|
|
452
|
-
);
|
|
453
|
-
const vaultResolveSpore = tool2(
|
|
454
|
-
"vault_resolve_spore",
|
|
455
|
-
"Resolve a spore by updating its status and recording a resolution event.",
|
|
456
|
-
{
|
|
457
|
-
spore_id: external_exports.string().describe("ID of the spore to resolve"),
|
|
458
|
-
action: external_exports.enum(["supersede", "archive", "merge", "split", "consolidate"]).describe("Resolution action"),
|
|
459
|
-
new_spore_id: external_exports.string().optional().describe("ID of the replacement spore (for supersede/merge)"),
|
|
460
|
-
reason: external_exports.string().optional().describe("Explanation for the resolution"),
|
|
461
|
-
session_id: external_exports.string().optional().describe("Session where this resolution occurred")
|
|
462
|
-
},
|
|
463
|
-
async (args) => {
|
|
464
|
-
const now = epochSeconds();
|
|
465
|
-
const statusMap = {
|
|
466
|
-
supersede: "superseded",
|
|
467
|
-
archive: "archived",
|
|
468
|
-
merge: "merged",
|
|
469
|
-
split: "split",
|
|
470
|
-
consolidate: "consolidated"
|
|
471
|
-
};
|
|
472
|
-
const newStatus = statusMap[args.action] ?? args.action;
|
|
473
|
-
const updatedSpore = updateSporeStatus(args.spore_id, newStatus, now);
|
|
474
|
-
const eventId = crypto.randomUUID();
|
|
475
|
-
insertResolutionEvent({
|
|
476
|
-
id: eventId,
|
|
477
|
-
agent_id: agentId,
|
|
478
|
-
machine_id: machineId,
|
|
479
|
-
spore_id: args.spore_id,
|
|
480
|
-
action: args.action,
|
|
481
|
-
new_spore_id: args.new_spore_id ?? null,
|
|
482
|
-
reason: args.reason ?? null,
|
|
483
|
-
session_id: args.session_id ?? null,
|
|
484
|
-
created_at: now
|
|
485
|
-
});
|
|
486
|
-
if (newStatus !== "active") {
|
|
487
|
-
try {
|
|
488
|
-
embeddingManager?.onStatusChanged("spores", args.spore_id, newStatus);
|
|
489
|
-
} catch {
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
return textResult({ spore: updatedSpore, resolution_event_id: eventId });
|
|
493
|
-
},
|
|
494
|
-
{ annotations: { destructiveHint: true } }
|
|
495
|
-
);
|
|
496
|
-
const vaultUpdateSession = tool2(
|
|
497
|
-
"vault_update_session",
|
|
498
|
-
"Update a session title and/or summary. When generating for the first time, provide BOTH title and summary. Title should be under 80 characters and reflect the full session scope.",
|
|
499
|
-
{
|
|
500
|
-
session_id: external_exports.string().describe("Session ID to update"),
|
|
501
|
-
title: external_exports.string().optional().describe("New session title"),
|
|
502
|
-
summary: external_exports.string().optional().describe("New session summary")
|
|
503
|
-
},
|
|
504
|
-
async (args) => {
|
|
505
|
-
const updates = {};
|
|
506
|
-
if (args.title !== void 0) updates.title = args.title;
|
|
507
|
-
if (args.summary !== void 0) updates.summary = args.summary;
|
|
508
|
-
const session = updateSession(args.session_id, updates);
|
|
509
|
-
if (args.summary) {
|
|
510
|
-
embeddingManager?.onContentWritten("sessions", args.session_id, args.summary, {}).catch(() => {
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
return textResult(session);
|
|
514
|
-
},
|
|
515
|
-
{ annotations: { idempotentHint: true } }
|
|
516
|
-
);
|
|
517
|
-
const vaultSetState = tool2(
|
|
518
|
-
"vault_set_state",
|
|
519
|
-
"Set a key-value state pair for the current agent. Used for bookmarks, cursors, and preferences.",
|
|
520
|
-
{
|
|
521
|
-
key: external_exports.string().describe("State key (e.g., last_processed_batch_id, cursor)"),
|
|
522
|
-
value: external_exports.string().describe("State value (stored as text)")
|
|
523
|
-
},
|
|
524
|
-
async (args) => {
|
|
525
|
-
const now = epochSeconds();
|
|
526
|
-
const state = setState(agentId, args.key, args.value, now);
|
|
527
|
-
return textResult(state);
|
|
528
|
-
},
|
|
529
|
-
{ annotations: { idempotentHint: true } }
|
|
530
|
-
);
|
|
531
|
-
const vaultReadDigest = tool2(
|
|
532
|
-
"vault_read_digest",
|
|
533
|
-
"Read current digest extracts. Without a tier parameter, returns a summary of all tiers (content length, generation time). With a tier parameter, returns the full content for that specific tier.",
|
|
534
|
-
{
|
|
535
|
-
tier: external_exports.number().optional().describe("Specific tier to read in full (e.g., 1500, 5000, 10000). Omit to get summary of all tiers.")
|
|
536
|
-
},
|
|
537
|
-
async (args) => {
|
|
538
|
-
const extracts = listDigestExtracts(agentId);
|
|
539
|
-
if (args.tier !== void 0) {
|
|
540
|
-
const extract = extracts.find((e) => e.tier === args.tier);
|
|
541
|
-
if (!extract) return textResult({ tier: args.tier, content: null, message: "No digest at this tier" });
|
|
542
|
-
return textResult({ tier: extract.tier, content: extract.content, generated_at: extract.generated_at });
|
|
543
|
-
}
|
|
544
|
-
return textResult(extracts.map((e) => ({
|
|
545
|
-
tier: e.tier,
|
|
546
|
-
content_length: e.content.length,
|
|
547
|
-
generated_at: e.generated_at
|
|
548
|
-
})));
|
|
549
|
-
},
|
|
550
|
-
{ annotations: { readOnlyHint: true } }
|
|
551
|
-
);
|
|
552
|
-
const vaultWriteDigest = tool2(
|
|
553
|
-
"vault_write_digest",
|
|
554
|
-
"Write or update a digest extract at a specific token tier. Uses UPSERT on (agent_id, tier).",
|
|
555
|
-
{
|
|
556
|
-
tier: external_exports.number().describe("Token budget tier (e.g., 1500, 5000, 10000)"),
|
|
557
|
-
content: external_exports.string().describe("The digest extract content in markdown")
|
|
558
|
-
},
|
|
559
|
-
async (args) => {
|
|
560
|
-
const now = epochSeconds();
|
|
561
|
-
const extract = upsertDigestExtract({
|
|
562
|
-
agent_id: agentId,
|
|
563
|
-
tier: args.tier,
|
|
564
|
-
content: args.content,
|
|
565
|
-
generated_at: now
|
|
566
|
-
});
|
|
567
|
-
return textResult(extract);
|
|
568
|
-
},
|
|
569
|
-
{ annotations: { idempotentHint: true } }
|
|
570
|
-
);
|
|
571
|
-
const vaultMarkProcessed = tool2(
|
|
572
|
-
"vault_mark_processed",
|
|
573
|
-
"Mark a prompt batch as processed so it is not returned by vault_unprocessed.",
|
|
574
|
-
{
|
|
575
|
-
batch_id: external_exports.number().describe("ID of the prompt batch to mark as processed")
|
|
576
|
-
},
|
|
577
|
-
async (args) => {
|
|
578
|
-
const batch = markBatchProcessed(args.batch_id);
|
|
579
|
-
return textResult(batch);
|
|
580
|
-
},
|
|
581
|
-
{ annotations: { destructiveHint: true } }
|
|
582
|
-
);
|
|
583
|
-
return [
|
|
584
|
-
vaultCreateSpore,
|
|
585
|
-
vaultCreateEntity,
|
|
586
|
-
vaultCreateEdge,
|
|
587
|
-
vaultResolveSpore,
|
|
588
|
-
vaultUpdateSession,
|
|
589
|
-
vaultSetState,
|
|
590
|
-
vaultReadDigest,
|
|
591
|
-
vaultWriteDigest,
|
|
592
|
-
vaultMarkProcessed
|
|
593
|
-
];
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// src/agent/tools/observability-tools.ts
|
|
597
|
-
import { tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
|
|
598
|
-
function createObservabilityTools(deps) {
|
|
599
|
-
const { runId, agentId } = deps;
|
|
600
|
-
const vaultReport = tool3(
|
|
601
|
-
"vault_report",
|
|
602
|
-
'Record an observability report for the current run. Use action "skip" when skipping expected operations (e.g., not updating a session summary) with reasoning in the summary field.',
|
|
603
|
-
{
|
|
604
|
-
action: external_exports.string().describe("Action name (e.g., extract, consolidate, digest, skip)"),
|
|
605
|
-
summary: external_exports.string().describe("Human-readable summary of what was done"),
|
|
606
|
-
details: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Structured details as key-value pairs")
|
|
607
|
-
},
|
|
608
|
-
async (args) => {
|
|
609
|
-
const now = epochSeconds();
|
|
610
|
-
const report = insertReport({
|
|
611
|
-
run_id: runId,
|
|
612
|
-
agent_id: agentId,
|
|
613
|
-
action: args.action,
|
|
614
|
-
summary: args.summary,
|
|
615
|
-
details: args.details ? JSON.stringify(args.details) : null,
|
|
616
|
-
created_at: now
|
|
617
|
-
});
|
|
618
|
-
return textResult(report);
|
|
619
|
-
},
|
|
620
|
-
{ annotations: {} }
|
|
621
|
-
);
|
|
622
|
-
return [vaultReport];
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// src/agent/tools/skill-tools.ts
|
|
626
|
-
import crypto2 from "crypto";
|
|
627
|
-
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from "fs";
|
|
628
|
-
import { resolve } from "path";
|
|
629
|
-
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
630
|
-
function createSkillTools(deps) {
|
|
631
|
-
const { agentId, machineId, projectRoot, vaultDir, embeddingManager } = deps;
|
|
632
|
-
function findOverlappingCandidate(newTopic, existing) {
|
|
633
|
-
let best = null;
|
|
634
|
-
for (const candidate of existing) {
|
|
635
|
-
const jaccard = descriptionSimilarity(newTopic, candidate.topic);
|
|
636
|
-
const overlap = topicOverlapSimilarity(newTopic, candidate.topic);
|
|
637
|
-
const hitsJaccard = jaccard >= DESCRIPTION_DUPLICATE_THRESHOLD;
|
|
638
|
-
const hitsOverlap = overlap >= TOPIC_OVERLAP_THRESHOLD;
|
|
639
|
-
if (!hitsJaccard && !hitsOverlap) continue;
|
|
640
|
-
const score = Math.max(jaccard, overlap);
|
|
641
|
-
if (!best || score > best.score) {
|
|
642
|
-
best = { candidate, score };
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return best;
|
|
646
|
-
}
|
|
647
|
-
function candidateOverlapError(match) {
|
|
648
|
-
const common = `already has an existing candidate with a similar topic: "${match.topic}"`;
|
|
649
|
-
switch (match.status) {
|
|
650
|
-
case CANDIDATE_STATUS.DISMISSED:
|
|
651
|
-
return `Note: similar to dismissed candidate "${match.topic}". If this is a broader domain that subsumes the dismissed topic, creation is allowed.`;
|
|
652
|
-
case CANDIDATE_STATUS.GENERATED:
|
|
653
|
-
return `Candidate rejected: the vault ${common} that was already fulfilled by a generated skill. Do not re-identify.`;
|
|
654
|
-
case CANDIDATE_STATUS.APPROVED:
|
|
655
|
-
return `Candidate rejected: the vault ${common} that is already queued in approved state. Wait for the generate task to process it.`;
|
|
656
|
-
case CANDIDATE_STATUS.IDENTIFIED:
|
|
657
|
-
return `Candidate rejected: the vault ${common} already in the review queue. Update the existing candidate with new evidence (action: update) instead of creating a duplicate.`;
|
|
658
|
-
default:
|
|
659
|
-
return `Candidate rejected: the vault ${common} in status '${match.status}'.`;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
function requireApprovedCandidate(candidateId) {
|
|
663
|
-
const candidate = getCandidate(candidateId);
|
|
664
|
-
if (!candidate) {
|
|
665
|
-
return {
|
|
666
|
-
error: `Candidate ${candidateId} not found. Skill writes require a candidate in the approved state.`
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
if (candidate.status !== CANDIDATE_STATUS.APPROVED) {
|
|
670
|
-
return {
|
|
671
|
-
error: `Candidate ${candidateId} is in '${candidate.status}' state. Skills can only be generated from candidates in 'approved' state \u2014 the human review step. If a candidate in an earlier state needs to become a skill, route it through the normal approval flow first.`,
|
|
672
|
-
candidate_status: candidate.status
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
function checkDedupGates(args) {
|
|
678
|
-
const existingSameName = getSkillRecordByName(args.name);
|
|
679
|
-
if (existingSameName) {
|
|
680
|
-
if (args.rejectSameName) {
|
|
681
|
-
return {
|
|
682
|
-
error: `Skill "${args.name}" already exists. This path is create-only. Use vault_write_skill to evolve the existing skill (it bumps the generation), or mark the current record stale via vault_skill_records first.`,
|
|
683
|
-
existing_skill: {
|
|
684
|
-
id: existingSameName.id,
|
|
685
|
-
name: existingSameName.name,
|
|
686
|
-
path: existingSameName.path
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
if (args.candidate_id) {
|
|
693
|
-
const candidate = getCandidate(args.candidate_id);
|
|
694
|
-
if (candidate?.skill_id) {
|
|
695
|
-
const linkedSkill = getSkillRecord(candidate.skill_id);
|
|
696
|
-
if (linkedSkill && linkedSkill.name !== args.name) {
|
|
697
|
-
return {
|
|
698
|
-
error: `Candidate ${args.candidate_id} is already fulfilled by skill "${linkedSkill.name}". Do not create a sibling skill. If the existing skill needs changes, write to the same name to evolve it (this bumps its generation), or mark it stale via vault_skill_records before replacing.`,
|
|
699
|
-
existing_skill: {
|
|
700
|
-
id: linkedSkill.id,
|
|
701
|
-
name: linkedSkill.name,
|
|
702
|
-
description: linkedSkill.description,
|
|
703
|
-
path: linkedSkill.path
|
|
704
|
-
}
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const activeSkills = listSkillRecords({ agent_id: agentId, status: "active", limit: 200 });
|
|
710
|
-
let bestMatch = null;
|
|
711
|
-
for (const skill of activeSkills) {
|
|
712
|
-
const score = descriptionSimilarity(args.description, skill.description);
|
|
713
|
-
if (score >= DESCRIPTION_DUPLICATE_THRESHOLD && (!bestMatch || score > bestMatch.score)) {
|
|
714
|
-
bestMatch = { skill, score };
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
if (bestMatch) {
|
|
718
|
-
return {
|
|
719
|
-
error: `Description overlaps with existing active skill "${bestMatch.skill.name}" (Jaccard ${bestMatch.score.toFixed(2)}, threshold ${DESCRIPTION_DUPLICATE_THRESHOLD}). Do not create a duplicate. Either evolve the existing skill by writing to its name ("${bestMatch.skill.name}"), or reframe this skill so its description describes a distinct procedure.`,
|
|
720
|
-
overlapping_skill: {
|
|
721
|
-
id: bestMatch.skill.id,
|
|
722
|
-
name: bestMatch.skill.name,
|
|
723
|
-
description: bestMatch.skill.description,
|
|
724
|
-
path: bestMatch.skill.path
|
|
725
|
-
},
|
|
726
|
-
similarity: bestMatch.score
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
async function promoteNewSkill(params) {
|
|
732
|
-
const root = projectRoot ?? process.cwd();
|
|
733
|
-
const skillDir = resolve(root, ".agents", "skills", params.name);
|
|
734
|
-
const skillPath = resolve(skillDir, "SKILL.md");
|
|
735
|
-
const skillDirPreexisted = existsSync(skillDir);
|
|
736
|
-
async function cleanupCreatedSkillArtifactsOnRollback() {
|
|
737
|
-
try {
|
|
738
|
-
if (!skillDirPreexisted) {
|
|
739
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
740
|
-
} else {
|
|
741
|
-
rmSync(skillPath, { force: true });
|
|
742
|
-
}
|
|
743
|
-
} catch (rollbackErr) {
|
|
744
|
-
console.warn(
|
|
745
|
-
`[${params.label}] file rollback after DB failure also failed:`,
|
|
746
|
-
rollbackErr instanceof Error ? rollbackErr.message : rollbackErr
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
try {
|
|
750
|
-
const { syncSkillSymlinks } = await import("./installer-FS257JRZ.js");
|
|
751
|
-
syncSkillSymlinks(root, params.name, { remove: true });
|
|
752
|
-
} catch (rollbackErr) {
|
|
753
|
-
console.warn(
|
|
754
|
-
`[${params.label}] symlink rollback after DB failure also failed:`,
|
|
755
|
-
rollbackErr instanceof Error ? rollbackErr.message : rollbackErr
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
mkdirSync(skillDir, { recursive: true });
|
|
761
|
-
writeFileSync(skillPath, params.content, "utf-8");
|
|
762
|
-
} catch (err) {
|
|
763
|
-
return {
|
|
764
|
-
error: `Failed to write skill file: ${err instanceof Error ? err.message : String(err)}`
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
try {
|
|
768
|
-
const { syncSkillSymlinks } = await import("./installer-FS257JRZ.js");
|
|
769
|
-
syncSkillSymlinks(root, params.name);
|
|
770
|
-
} catch (err) {
|
|
771
|
-
console.warn(
|
|
772
|
-
`[${params.label}] syncSkillSymlinks failed:`,
|
|
773
|
-
err instanceof Error ? err.message : err
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
const now = epochSeconds();
|
|
777
|
-
const relativePath = `.agents/skills/${params.name}/SKILL.md`;
|
|
778
|
-
const recordId = crypto2.randomUUID();
|
|
779
|
-
const generation = 1;
|
|
780
|
-
const txDb = getDatabase();
|
|
781
|
-
try {
|
|
782
|
-
txDb.transaction(() => {
|
|
783
|
-
insertSkillRecord({
|
|
784
|
-
id: recordId,
|
|
785
|
-
agent_id: agentId,
|
|
786
|
-
machine_id: machineId,
|
|
787
|
-
name: params.name,
|
|
788
|
-
display_name: params.display_name,
|
|
789
|
-
description: params.description,
|
|
790
|
-
candidate_id: params.candidate_id ?? null,
|
|
791
|
-
source_ids: params.source_ids,
|
|
792
|
-
path: relativePath,
|
|
793
|
-
created_at: now,
|
|
794
|
-
updated_at: now
|
|
795
|
-
});
|
|
796
|
-
insertLineage({
|
|
797
|
-
id: crypto2.randomUUID(),
|
|
798
|
-
skill_id: recordId,
|
|
799
|
-
generation,
|
|
800
|
-
action: "created",
|
|
801
|
-
rationale: params.rationale ?? "Initial skill creation",
|
|
802
|
-
source_ids_added: params.source_ids,
|
|
803
|
-
content_snapshot: params.content,
|
|
804
|
-
created_at: now
|
|
805
|
-
});
|
|
806
|
-
params.linkCandidate?.(recordId, now);
|
|
807
|
-
})();
|
|
808
|
-
} catch (err) {
|
|
809
|
-
await cleanupCreatedSkillArtifactsOnRollback();
|
|
810
|
-
return {
|
|
811
|
-
error: `Skill write aborted: database transaction failed and on-disk state was rolled back. ${err instanceof Error ? err.message : String(err)}`
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
return {
|
|
815
|
-
id: recordId,
|
|
816
|
-
name: params.name,
|
|
817
|
-
path: relativePath,
|
|
818
|
-
generation
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
function emitSkillNotification(kind, opts) {
|
|
822
|
-
notify(vaultDir, {
|
|
823
|
-
domain: "skills",
|
|
824
|
-
type: kind === "created" ? "skill.created" : "skill.evolved",
|
|
825
|
-
title: `Skill ${kind}: ${opts.display_name}`,
|
|
826
|
-
message: opts.description.slice(0, 120),
|
|
827
|
-
link: `/skills?skill=${encodeURIComponent(opts.name)}`,
|
|
828
|
-
metadata: { skillId: opts.recordId, name: opts.name, generation: opts.generation }
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
const vaultSkillCandidates = tool4(
|
|
832
|
-
"vault_skill_candidates",
|
|
833
|
-
"Manage skill candidates (identified topics that may become skills). Supports list, get, create, and update actions.",
|
|
834
|
-
{
|
|
835
|
-
action: external_exports.enum(["list", "get", "create", "update", "delete"]).describe("Action to perform"),
|
|
836
|
-
id: external_exports.string().optional().describe("Candidate ID (required for get/update)"),
|
|
837
|
-
topic: external_exports.string().optional().describe("Skill topic (required for create)"),
|
|
838
|
-
rationale: external_exports.string().optional().describe("Why this should be a skill (required for create)"),
|
|
839
|
-
confidence: external_exports.number().optional().describe("Confidence score 0-1"),
|
|
840
|
-
status: external_exports.enum(AGENT_SETTABLE_STATUSES).optional().describe(
|
|
841
|
-
"Candidate status \u2014 agent-settable values only. 'identified' is the initial state; 'dismissed' retires a candidate. 'approved' and 'generated' are lifecycle transitions owned by the human UI and vault_finalize_skill respectively."
|
|
842
|
-
),
|
|
843
|
-
source_ids: external_exports.string().optional().describe("JSON array of source spore/entity IDs"),
|
|
844
|
-
skill_id: external_exports.string().optional().describe("Associated skill record ID (after materialization)"),
|
|
845
|
-
supersedes: external_exports.string().optional().describe("JSON array of skill record names this candidate would replace (for domain-level candidates that subsume existing narrow skills)"),
|
|
846
|
-
limit: external_exports.number().optional().describe("Maximum candidates to return (for list)")
|
|
847
|
-
},
|
|
848
|
-
async (args) => {
|
|
849
|
-
switch (args.action) {
|
|
850
|
-
case "list": {
|
|
851
|
-
const candidates = listCandidates({
|
|
852
|
-
agent_id: agentId,
|
|
853
|
-
status: args.status,
|
|
854
|
-
limit: args.limit ?? DEFAULT_LIST_LIMIT
|
|
855
|
-
});
|
|
856
|
-
return textResult(candidates);
|
|
857
|
-
}
|
|
858
|
-
case "get": {
|
|
859
|
-
if (!args.id) return textResult({ error: "id is required for get action" });
|
|
860
|
-
const candidate = getCandidate(args.id);
|
|
861
|
-
if (!candidate) return textResult({ error: `Candidate not found: ${args.id}` });
|
|
862
|
-
return textResult(candidate);
|
|
863
|
-
}
|
|
864
|
-
case "create": {
|
|
865
|
-
if (!args.topic || !args.rationale) {
|
|
866
|
-
return textResult({ error: "topic and rationale are required for create action" });
|
|
867
|
-
}
|
|
868
|
-
let supersedesNames = [];
|
|
869
|
-
if (args.supersedes) {
|
|
870
|
-
try {
|
|
871
|
-
supersedesNames = JSON.parse(args.supersedes);
|
|
872
|
-
} catch {
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
const supersedesSet = new Set(supersedesNames);
|
|
876
|
-
const activeSkills = listSkillRecords({ agent_id: agentId, status: "active", limit: 100 });
|
|
877
|
-
const topicLower = args.topic.toLowerCase();
|
|
878
|
-
const overlapping = activeSkills.filter((s) => {
|
|
879
|
-
if (supersedesSet.has(s.name)) return false;
|
|
880
|
-
const nameWords = s.name.split("-").filter((w) => w.length > 2);
|
|
881
|
-
if (nameWords.length < 2) return false;
|
|
882
|
-
return nameWords.every((w) => topicLower.includes(w));
|
|
883
|
-
});
|
|
884
|
-
if (overlapping.length > 0) {
|
|
885
|
-
return textResult({
|
|
886
|
-
error: "Candidate rejected: active skill(s) already cover this topic. Update the existing skill via vault_skill_records instead.",
|
|
887
|
-
overlapping_skills: overlapping.map((s) => ({ name: s.name, display_name: s.display_name, description: s.description }))
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
const allExisting = listCandidates({ agent_id: agentId, limit: 500 });
|
|
891
|
-
const match = findOverlappingCandidate(args.topic, allExisting);
|
|
892
|
-
let dismissedMatch;
|
|
893
|
-
if (match) {
|
|
894
|
-
if (match.candidate.status === CANDIDATE_STATUS.DISMISSED) {
|
|
895
|
-
dismissedMatch = match;
|
|
896
|
-
} else {
|
|
897
|
-
return textResult({
|
|
898
|
-
error: candidateOverlapError(match.candidate),
|
|
899
|
-
existing_candidate: {
|
|
900
|
-
id: match.candidate.id,
|
|
901
|
-
status: match.candidate.status,
|
|
902
|
-
topic: match.candidate.topic
|
|
903
|
-
},
|
|
904
|
-
similarity: match.score
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
const now = epochSeconds();
|
|
909
|
-
const candidate = insertCandidate({
|
|
910
|
-
id: crypto2.randomUUID(),
|
|
911
|
-
agent_id: agentId,
|
|
912
|
-
machine_id: machineId,
|
|
913
|
-
topic: args.topic,
|
|
914
|
-
rationale: args.rationale,
|
|
915
|
-
confidence: args.confidence,
|
|
916
|
-
status: args.status,
|
|
917
|
-
source_ids: args.source_ids,
|
|
918
|
-
supersedes: args.supersedes,
|
|
919
|
-
created_at: now,
|
|
920
|
-
updated_at: now
|
|
921
|
-
});
|
|
922
|
-
const result = { ...candidate };
|
|
923
|
-
if (dismissedMatch) {
|
|
924
|
-
result.warning = candidateOverlapError(dismissedMatch.candidate);
|
|
925
|
-
result.similar_dismissed_candidate = {
|
|
926
|
-
id: dismissedMatch.candidate.id,
|
|
927
|
-
topic: dismissedMatch.candidate.topic
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
notify(vaultDir, {
|
|
931
|
-
domain: "skills",
|
|
932
|
-
type: "skill.surveyed",
|
|
933
|
-
title: `Skill candidate: ${args.topic}`,
|
|
934
|
-
message: args.rationale.slice(0, 120),
|
|
935
|
-
link: "/skills?tab=candidates",
|
|
936
|
-
metadata: { candidateId: candidate.id, topic: args.topic }
|
|
937
|
-
});
|
|
938
|
-
return textResult(result);
|
|
939
|
-
}
|
|
940
|
-
case "update": {
|
|
941
|
-
if (!args.id) return textResult({ error: "id is required for update action" });
|
|
942
|
-
const now = epochSeconds();
|
|
943
|
-
const updated = updateCandidate(args.id, {
|
|
944
|
-
...args.topic !== void 0 ? { topic: args.topic } : {},
|
|
945
|
-
...args.rationale !== void 0 ? { rationale: args.rationale } : {},
|
|
946
|
-
...args.confidence !== void 0 ? { confidence: args.confidence } : {},
|
|
947
|
-
...args.status !== void 0 ? { status: args.status } : {},
|
|
948
|
-
...args.source_ids !== void 0 ? { source_ids: args.source_ids } : {},
|
|
949
|
-
...args.skill_id !== void 0 ? { skill_id: args.skill_id } : {},
|
|
950
|
-
...args.supersedes !== void 0 ? { supersedes: args.supersedes } : {},
|
|
951
|
-
updated_at: now
|
|
952
|
-
});
|
|
953
|
-
if (!updated) return textResult({ error: `Candidate not found: ${args.id}` });
|
|
954
|
-
return textResult(updated);
|
|
955
|
-
}
|
|
956
|
-
case "delete": {
|
|
957
|
-
if (!args.id) return textResult({ error: "id is required for delete action" });
|
|
958
|
-
const deleted = deleteCandidate(args.id);
|
|
959
|
-
if (!deleted) return textResult({ error: `Candidate not found: ${args.id}` });
|
|
960
|
-
return textResult({ deleted: true, id: args.id });
|
|
961
|
-
}
|
|
962
|
-
default:
|
|
963
|
-
return textResult({ error: `Unknown action: ${args.action}` });
|
|
964
|
-
}
|
|
965
|
-
},
|
|
966
|
-
{ annotations: {} }
|
|
967
|
-
);
|
|
968
|
-
const vaultSkillRecords = tool4(
|
|
969
|
-
"vault_skill_records",
|
|
970
|
-
"Read, update, and delete skill records (materialized skills on disk). Supports list, get, update, and delete actions. The get action includes the full SKILL.md file content.",
|
|
971
|
-
{
|
|
972
|
-
action: external_exports.enum(["list", "get", "update", "delete"]).describe("Action to perform"),
|
|
973
|
-
id: external_exports.string().optional().describe("Skill record ID or name (required for get/update/delete)"),
|
|
974
|
-
status: external_exports.enum(["active", "stale", "retired"]).optional().describe("Filter by status"),
|
|
975
|
-
generation: external_exports.number().optional().describe("New generation number (for update)"),
|
|
976
|
-
source_ids: external_exports.string().optional().describe("JSON array of source IDs (for update)"),
|
|
977
|
-
description: external_exports.string().optional().describe("Updated description (for update)"),
|
|
978
|
-
limit: external_exports.number().optional().describe("Maximum records to return (for list)")
|
|
979
|
-
},
|
|
980
|
-
async (args) => {
|
|
981
|
-
switch (args.action) {
|
|
982
|
-
case "list": {
|
|
983
|
-
const records = listSkillRecords({
|
|
984
|
-
agent_id: agentId,
|
|
985
|
-
status: args.status,
|
|
986
|
-
limit: args.limit ?? DEFAULT_LIST_LIMIT
|
|
987
|
-
});
|
|
988
|
-
return textResult(records);
|
|
989
|
-
}
|
|
990
|
-
case "get": {
|
|
991
|
-
if (!args.id) return textResult({ error: "id is required for get action" });
|
|
992
|
-
const record = getSkillRecord(args.id) ?? getSkillRecordByName(args.id);
|
|
993
|
-
if (!record) return textResult({ error: `Skill record not found: ${args.id}` });
|
|
994
|
-
const result = { ...record };
|
|
995
|
-
if (record.path && projectRoot) {
|
|
996
|
-
try {
|
|
997
|
-
result.content = readFileSync(resolve(projectRoot, record.path), "utf-8");
|
|
998
|
-
} catch {
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
return textResult(result);
|
|
1002
|
-
}
|
|
1003
|
-
case "update": {
|
|
1004
|
-
if (!args.id) return textResult({ error: "id is required for update action" });
|
|
1005
|
-
const existing = getSkillRecord(args.id) ?? getSkillRecordByName(args.id);
|
|
1006
|
-
if (!existing) return textResult({ error: `Skill record not found: ${args.id}` });
|
|
1007
|
-
const now = epochSeconds();
|
|
1008
|
-
const updated = updateSkillRecord(existing.id, {
|
|
1009
|
-
...args.status !== void 0 ? { status: args.status } : {},
|
|
1010
|
-
...args.generation !== void 0 ? { generation: args.generation } : {},
|
|
1011
|
-
...args.source_ids !== void 0 ? { source_ids: args.source_ids } : {},
|
|
1012
|
-
...args.description !== void 0 ? { description: args.description } : {},
|
|
1013
|
-
updated_at: now
|
|
1014
|
-
});
|
|
1015
|
-
if (!updated) return textResult({ error: `Failed to update skill record: ${existing.id}` });
|
|
1016
|
-
return textResult(updated);
|
|
1017
|
-
}
|
|
1018
|
-
case "delete": {
|
|
1019
|
-
if (!args.id) return textResult({ error: "id is required for delete action" });
|
|
1020
|
-
const result = deleteSkillRecordCascade(args.id);
|
|
1021
|
-
if (!result) return textResult({ error: `Skill record not found: ${args.id}` });
|
|
1022
|
-
try {
|
|
1023
|
-
embeddingManager?.onRemoved("skill_records", result.id);
|
|
1024
|
-
} catch {
|
|
1025
|
-
}
|
|
1026
|
-
const root = projectRoot ?? process.cwd();
|
|
1027
|
-
if (!/[/\\]|\.\./.test(result.name)) {
|
|
1028
|
-
const skillDir = resolve(root, ".agents", "skills", result.name);
|
|
1029
|
-
try {
|
|
1030
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
1031
|
-
} catch (err) {
|
|
1032
|
-
console.warn("[vault_skill_records] Failed to remove skill directory:", err instanceof Error ? err.message : err);
|
|
1033
|
-
}
|
|
1034
|
-
try {
|
|
1035
|
-
const { syncSkillSymlinks } = await import("./installer-FS257JRZ.js");
|
|
1036
|
-
syncSkillSymlinks(root, result.name, { remove: true });
|
|
1037
|
-
} catch (err) {
|
|
1038
|
-
console.warn("[vault_skill_records] Failed to remove symlinks:", err instanceof Error ? err.message : err);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
return textResult({ deleted: true, id: result.id, name: result.name });
|
|
1042
|
-
}
|
|
1043
|
-
default:
|
|
1044
|
-
return textResult({ error: `Unknown action: ${args.action}` });
|
|
1045
|
-
}
|
|
1046
|
-
},
|
|
1047
|
-
{ annotations: {} }
|
|
1048
|
-
);
|
|
1049
|
-
const vaultWriteSkill = tool4(
|
|
1050
|
-
"vault_write_skill",
|
|
1051
|
-
"Write a SKILL.md file to disk and create or update the corresponding skill record and lineage entry.",
|
|
1052
|
-
{
|
|
1053
|
-
name: external_exports.string().describe("Skill directory name (kebab-case, NO colon). The myco: prefix goes in the SKILL.md frontmatter name field, not here."),
|
|
1054
|
-
display_name: external_exports.string().describe("Human-readable display name"),
|
|
1055
|
-
description: external_exports.string().describe("Short description of what the skill does"),
|
|
1056
|
-
content: external_exports.string().describe("Full SKILL.md content in markdown"),
|
|
1057
|
-
source_ids: external_exports.string().optional().describe("JSON array of source spore/entity IDs"),
|
|
1058
|
-
candidate_id: external_exports.string().optional().describe("Candidate ID that prompted this skill creation"),
|
|
1059
|
-
rationale: external_exports.string().optional().describe("Why this skill was created or updated")
|
|
1060
|
-
},
|
|
1061
|
-
async (args) => {
|
|
1062
|
-
const validationErrors = validateSkillContent(args.content, args.name);
|
|
1063
|
-
if (validationErrors.length > 0) {
|
|
1064
|
-
return textResult({
|
|
1065
|
-
error: "Skill validation failed. Fix these issues and try again.",
|
|
1066
|
-
issues: validationErrors
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
if (!args.name || /[/\\]|\.\./.test(args.name)) {
|
|
1070
|
-
return textResult({
|
|
1071
|
-
error: 'Invalid skill name: must be a simple directory name without path separators or ".."'
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
const dedupError = checkDedupGates({
|
|
1075
|
-
candidate_id: args.candidate_id,
|
|
1076
|
-
name: args.name,
|
|
1077
|
-
description: args.description
|
|
1078
|
-
});
|
|
1079
|
-
if (dedupError) {
|
|
1080
|
-
return textResult(dedupError);
|
|
1081
|
-
}
|
|
1082
|
-
const existing = getSkillRecordByName(args.name);
|
|
1083
|
-
const root = projectRoot ?? process.cwd();
|
|
1084
|
-
const skillPath = resolve(root, ".agents", "skills", args.name, "SKILL.md");
|
|
1085
|
-
if (existsSync(skillPath)) {
|
|
1086
|
-
const existingContent = readFileSync(skillPath, "utf-8");
|
|
1087
|
-
const violations = checkFrontmatterPreservation(existingContent, args.content);
|
|
1088
|
-
if (violations.length > 0) {
|
|
1089
|
-
return textResult({
|
|
1090
|
-
error: "Skill update rejected: protected frontmatter fields were changed. Read the existing skill and preserve these values exactly.",
|
|
1091
|
-
violations
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
if (!existing) {
|
|
1096
|
-
if (args.candidate_id) {
|
|
1097
|
-
const candidateError = requireApprovedCandidate(args.candidate_id);
|
|
1098
|
-
if (candidateError) {
|
|
1099
|
-
return textResult(candidateError);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
const linkCandidate = (recordId2, now2) => {
|
|
1103
|
-
if (!args.candidate_id) return;
|
|
1104
|
-
const exact = updateCandidate(args.candidate_id, {
|
|
1105
|
-
status: CANDIDATE_STATUS.GENERATED,
|
|
1106
|
-
skill_id: recordId2,
|
|
1107
|
-
updated_at: now2
|
|
1108
|
-
});
|
|
1109
|
-
if (exact) return;
|
|
1110
|
-
const approvedCandidates = listCandidates({ status: CANDIDATE_STATUS.APPROVED, limit: 10 });
|
|
1111
|
-
const prefixMatch = approvedCandidates.find((c) => c.id.startsWith(args.candidate_id));
|
|
1112
|
-
if (prefixMatch) {
|
|
1113
|
-
updateCandidate(prefixMatch.id, {
|
|
1114
|
-
status: CANDIDATE_STATUS.GENERATED,
|
|
1115
|
-
skill_id: recordId2,
|
|
1116
|
-
updated_at: now2
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
const result = await promoteNewSkill({
|
|
1121
|
-
name: args.name,
|
|
1122
|
-
display_name: args.display_name,
|
|
1123
|
-
description: args.description,
|
|
1124
|
-
content: args.content,
|
|
1125
|
-
source_ids: args.source_ids,
|
|
1126
|
-
candidate_id: args.candidate_id,
|
|
1127
|
-
rationale: args.rationale,
|
|
1128
|
-
linkCandidate,
|
|
1129
|
-
label: "vault_write_skill"
|
|
1130
|
-
});
|
|
1131
|
-
if ("error" in result) return textResult(result);
|
|
1132
|
-
emitSkillNotification("created", {
|
|
1133
|
-
name: result.name,
|
|
1134
|
-
display_name: args.display_name,
|
|
1135
|
-
description: args.description,
|
|
1136
|
-
recordId: result.id,
|
|
1137
|
-
generation: result.generation
|
|
1138
|
-
});
|
|
1139
|
-
embeddingManager?.onContentWritten("skill_records", result.id, args.description, {
|
|
1140
|
-
status: "active",
|
|
1141
|
-
name: args.name
|
|
1142
|
-
}).catch(() => {
|
|
1143
|
-
});
|
|
1144
|
-
return textResult(result);
|
|
1145
|
-
}
|
|
1146
|
-
const priorSkillContent = readFileSync(skillPath, "utf-8");
|
|
1147
|
-
try {
|
|
1148
|
-
writeFileSync(skillPath, args.content, "utf-8");
|
|
1149
|
-
} catch (err) {
|
|
1150
|
-
return textResult({ error: `Failed to write skill file: ${err instanceof Error ? err.message : String(err)}` });
|
|
1151
|
-
}
|
|
1152
|
-
try {
|
|
1153
|
-
const { syncSkillSymlinks } = await import("./installer-FS257JRZ.js");
|
|
1154
|
-
syncSkillSymlinks(root, args.name);
|
|
1155
|
-
} catch (err) {
|
|
1156
|
-
console.warn("[vault_write_skill] syncSkillSymlinks failed:", err instanceof Error ? err.message : err);
|
|
1157
|
-
}
|
|
1158
|
-
const now = epochSeconds();
|
|
1159
|
-
const relativePath = `.agents/skills/${args.name}/SKILL.md`;
|
|
1160
|
-
const generation = existing.generation + 1;
|
|
1161
|
-
const recordId = existing.id;
|
|
1162
|
-
const txDb = getDatabase();
|
|
1163
|
-
try {
|
|
1164
|
-
txDb.transaction(() => {
|
|
1165
|
-
updateSkillRecord(existing.id, {
|
|
1166
|
-
display_name: args.display_name,
|
|
1167
|
-
description: args.description,
|
|
1168
|
-
generation,
|
|
1169
|
-
...args.source_ids !== void 0 ? { source_ids: args.source_ids } : {},
|
|
1170
|
-
path: relativePath,
|
|
1171
|
-
updated_at: now
|
|
1172
|
-
});
|
|
1173
|
-
insertLineage({
|
|
1174
|
-
id: crypto2.randomUUID(),
|
|
1175
|
-
skill_id: existing.id,
|
|
1176
|
-
generation,
|
|
1177
|
-
action: "updated",
|
|
1178
|
-
rationale: args.rationale ?? "Skill content updated",
|
|
1179
|
-
source_ids_added: args.source_ids,
|
|
1180
|
-
content_snapshot: args.content,
|
|
1181
|
-
created_at: now
|
|
1182
|
-
});
|
|
1183
|
-
})();
|
|
1184
|
-
} catch (err) {
|
|
1185
|
-
try {
|
|
1186
|
-
writeFileSync(skillPath, priorSkillContent, "utf-8");
|
|
1187
|
-
} catch (rollbackErr) {
|
|
1188
|
-
console.warn(
|
|
1189
|
-
"[vault_write_skill] file rollback after DB failure also failed:",
|
|
1190
|
-
rollbackErr instanceof Error ? rollbackErr.message : rollbackErr
|
|
1191
|
-
);
|
|
1192
|
-
}
|
|
1193
|
-
return textResult({
|
|
1194
|
-
error: `Skill write aborted: database transaction failed and on-disk state was rolled back. ${err instanceof Error ? err.message : String(err)}`
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
emitSkillNotification("evolved", {
|
|
1198
|
-
name: args.name,
|
|
1199
|
-
display_name: args.display_name,
|
|
1200
|
-
description: args.description,
|
|
1201
|
-
recordId,
|
|
1202
|
-
generation
|
|
1203
|
-
});
|
|
1204
|
-
embeddingManager?.onContentWritten("skill_records", recordId, args.description, {
|
|
1205
|
-
status: "active",
|
|
1206
|
-
name: args.name
|
|
1207
|
-
}).catch(() => {
|
|
1208
|
-
});
|
|
1209
|
-
return textResult({
|
|
1210
|
-
id: recordId,
|
|
1211
|
-
name: args.name,
|
|
1212
|
-
path: relativePath,
|
|
1213
|
-
generation
|
|
1214
|
-
});
|
|
1215
|
-
},
|
|
1216
|
-
{ annotations: { openWorldHint: true } }
|
|
1217
|
-
);
|
|
1218
|
-
const vaultStageSkill = tool4(
|
|
1219
|
-
"vault_stage_skill",
|
|
1220
|
-
"Stage a provisional SKILL.md under .myco/staging/skills/<candidate_id>/ for later promotion by vault_finalize_skill. Use this from the skill-generate draft phase. The write is NOT live \u2014 the skill does not appear under .agents/skills/ and no DB rows are created until vault_finalize_skill is called with the same candidate_id.",
|
|
1221
|
-
{
|
|
1222
|
-
candidate_id: external_exports.string().describe(
|
|
1223
|
-
"Candidate ID from the instruction. Required \u2014 staging is keyed by candidate so the validate phase (and on-failure cleanup) can find the staged content."
|
|
1224
|
-
),
|
|
1225
|
-
name: external_exports.string().describe("Final skill directory name (kebab-case, no colon). Stored in the manifest for finalize."),
|
|
1226
|
-
display_name: external_exports.string().describe("Human-readable display name"),
|
|
1227
|
-
description: external_exports.string().describe("Short description \u2014 used for the dedup gate and the final skill record"),
|
|
1228
|
-
content: external_exports.string().describe("Full SKILL.md content in markdown including frontmatter"),
|
|
1229
|
-
source_ids: external_exports.string().optional().describe("JSON array of source spore/entity IDs"),
|
|
1230
|
-
rationale: external_exports.string().optional().describe("Why this skill is being created \u2014 stored in lineage after finalize")
|
|
1231
|
-
},
|
|
1232
|
-
async (args) => {
|
|
1233
|
-
if (!vaultDir) {
|
|
1234
|
-
return textResult({
|
|
1235
|
-
error: "vault_stage_skill requires vaultDir on the tool deps \u2014 staging has no location otherwise"
|
|
1236
|
-
});
|
|
1237
|
-
}
|
|
1238
|
-
const validationErrors = validateSkillContent(args.content, args.name);
|
|
1239
|
-
if (validationErrors.length > 0) {
|
|
1240
|
-
return textResult({
|
|
1241
|
-
error: "Skill validation failed. Fix these issues and re-stage.",
|
|
1242
|
-
issues: validationErrors
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
if (!args.name || /[/\\]|\.\./.test(args.name)) {
|
|
1246
|
-
return textResult({
|
|
1247
|
-
error: 'Invalid skill name: must be a simple directory name without path separators or ".."'
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
const candidateError = requireApprovedCandidate(args.candidate_id);
|
|
1251
|
-
if (candidateError) return textResult(candidateError);
|
|
1252
|
-
const dedupError = checkDedupGates({
|
|
1253
|
-
candidate_id: args.candidate_id,
|
|
1254
|
-
name: args.name,
|
|
1255
|
-
description: args.description,
|
|
1256
|
-
rejectSameName: true
|
|
1257
|
-
});
|
|
1258
|
-
if (dedupError) return textResult(dedupError);
|
|
1259
|
-
let stagingFilePath;
|
|
1260
|
-
try {
|
|
1261
|
-
stagingFilePath = writeStagedSkill(vaultDir, args.candidate_id, args.content);
|
|
1262
|
-
const manifest = {
|
|
1263
|
-
candidate_id: args.candidate_id,
|
|
1264
|
-
name: args.name,
|
|
1265
|
-
display_name: args.display_name,
|
|
1266
|
-
description: args.description,
|
|
1267
|
-
source_ids: args.source_ids ?? "[]",
|
|
1268
|
-
rationale: args.rationale ?? "Initial draft"
|
|
1269
|
-
};
|
|
1270
|
-
writeStagedManifest(vaultDir, args.candidate_id, manifest);
|
|
1271
|
-
} catch (err) {
|
|
1272
|
-
return textResult({
|
|
1273
|
-
error: `Failed to write staged skill: ${err instanceof Error ? err.message : String(err)}`
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
return textResult({
|
|
1277
|
-
candidate_id: args.candidate_id,
|
|
1278
|
-
staging_path: stagingFilePath,
|
|
1279
|
-
status: "staged"
|
|
1280
|
-
});
|
|
1281
|
-
},
|
|
1282
|
-
{ annotations: { openWorldHint: true } }
|
|
1283
|
-
);
|
|
1284
|
-
const vaultFinalizeSkill = tool4(
|
|
1285
|
-
"vault_finalize_skill",
|
|
1286
|
-
"Promote a staged skill to live at .agents/skills/<name>/ and insert the skill_records / lineage rows. Call this from skill-generate validate phase after your quality checks pass. Requires vault_stage_skill to have been called earlier with the same candidate_id; reads the staged SKILL.md + manifest rather than taking duplicate metadata.",
|
|
1287
|
-
{
|
|
1288
|
-
candidate_id: external_exports.string().describe("Candidate ID whose staged skill should be promoted. Must match a previous vault_stage_skill call.")
|
|
1289
|
-
},
|
|
1290
|
-
async (args) => {
|
|
1291
|
-
if (!vaultDir) {
|
|
1292
|
-
return textResult({
|
|
1293
|
-
error: "vault_finalize_skill requires vaultDir on the tool deps"
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
const stagedContent = readStagedSkill(vaultDir, args.candidate_id);
|
|
1297
|
-
const manifest = readStagedManifest(vaultDir, args.candidate_id);
|
|
1298
|
-
if (!stagedContent || !manifest) {
|
|
1299
|
-
return textResult({
|
|
1300
|
-
error: `No staged skill found for candidate ${args.candidate_id}. Call vault_stage_skill first.`
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
const candidateError = requireApprovedCandidate(args.candidate_id);
|
|
1304
|
-
if (candidateError) return textResult(candidateError);
|
|
1305
|
-
const validationErrors = validateSkillContent(stagedContent, manifest.name);
|
|
1306
|
-
if (validationErrors.length > 0) {
|
|
1307
|
-
return textResult({
|
|
1308
|
-
error: "Staged skill failed validation on finalize. Re-stage with valid content.",
|
|
1309
|
-
issues: validationErrors
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
const dedupError = checkDedupGates({
|
|
1313
|
-
candidate_id: args.candidate_id,
|
|
1314
|
-
name: manifest.name,
|
|
1315
|
-
description: manifest.description,
|
|
1316
|
-
rejectSameName: true
|
|
1317
|
-
});
|
|
1318
|
-
if (dedupError) return textResult(dedupError);
|
|
1319
|
-
const result = await promoteNewSkill({
|
|
1320
|
-
name: manifest.name,
|
|
1321
|
-
display_name: manifest.display_name,
|
|
1322
|
-
description: manifest.description,
|
|
1323
|
-
content: stagedContent,
|
|
1324
|
-
source_ids: manifest.source_ids,
|
|
1325
|
-
candidate_id: manifest.candidate_id,
|
|
1326
|
-
rationale: manifest.rationale,
|
|
1327
|
-
linkCandidate: (recordId, now) => {
|
|
1328
|
-
updateCandidate(manifest.candidate_id, {
|
|
1329
|
-
status: CANDIDATE_STATUS.GENERATED,
|
|
1330
|
-
skill_id: recordId,
|
|
1331
|
-
updated_at: now
|
|
1332
|
-
});
|
|
1333
|
-
},
|
|
1334
|
-
label: "vault_finalize_skill"
|
|
1335
|
-
});
|
|
1336
|
-
if ("error" in result) return textResult(result);
|
|
1337
|
-
cleanupStagedSkill(vaultDir, args.candidate_id);
|
|
1338
|
-
emitSkillNotification("created", {
|
|
1339
|
-
name: manifest.name,
|
|
1340
|
-
display_name: manifest.display_name,
|
|
1341
|
-
description: manifest.description,
|
|
1342
|
-
recordId: result.id,
|
|
1343
|
-
generation: result.generation
|
|
1344
|
-
});
|
|
1345
|
-
embeddingManager?.onContentWritten("skill_records", result.id, manifest.description, {
|
|
1346
|
-
status: "active",
|
|
1347
|
-
name: manifest.name
|
|
1348
|
-
}).catch(() => {
|
|
1349
|
-
});
|
|
1350
|
-
return textResult(result);
|
|
1351
|
-
},
|
|
1352
|
-
{ annotations: { openWorldHint: true } }
|
|
1353
|
-
);
|
|
1354
|
-
return [
|
|
1355
|
-
vaultSkillCandidates,
|
|
1356
|
-
vaultSkillRecords,
|
|
1357
|
-
vaultWriteSkill,
|
|
1358
|
-
vaultStageSkill,
|
|
1359
|
-
vaultFinalizeSkill
|
|
1360
|
-
];
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// src/agent/tools.ts
|
|
1364
|
-
var READ_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1365
|
-
"vault_unprocessed",
|
|
1366
|
-
"vault_spores",
|
|
1367
|
-
"vault_sessions",
|
|
1368
|
-
"vault_search_fts",
|
|
1369
|
-
"vault_search_semantic",
|
|
1370
|
-
"vault_state",
|
|
1371
|
-
"vault_entities",
|
|
1372
|
-
"vault_edges"
|
|
1373
|
-
]);
|
|
1374
|
-
var WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1375
|
-
"vault_create_spore",
|
|
1376
|
-
"vault_create_entity",
|
|
1377
|
-
"vault_create_edge",
|
|
1378
|
-
"vault_resolve_spore",
|
|
1379
|
-
"vault_update_session",
|
|
1380
|
-
"vault_set_state",
|
|
1381
|
-
"vault_read_digest",
|
|
1382
|
-
"vault_write_digest",
|
|
1383
|
-
"vault_mark_processed"
|
|
1384
|
-
]);
|
|
1385
|
-
var OBSERVABILITY_TOOL_NAMES = /* @__PURE__ */ new Set(["vault_report"]);
|
|
1386
|
-
var SKILL_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1387
|
-
"vault_skill_candidates",
|
|
1388
|
-
"vault_skill_records",
|
|
1389
|
-
"vault_write_skill",
|
|
1390
|
-
"vault_stage_skill",
|
|
1391
|
-
"vault_finalize_skill"
|
|
1392
|
-
]);
|
|
1393
|
-
var TOOL_OUTPUT_SUMMARY_LIMIT = 240;
|
|
1394
|
-
var VAULT_TOOL_COUNT = READ_TOOL_NAMES.size + WRITE_TOOL_NAMES.size + OBSERVABILITY_TOOL_NAMES.size + SKILL_TOOL_NAMES.size;
|
|
1395
|
-
function setsOverlap(a, b) {
|
|
1396
|
-
for (const item of a) {
|
|
1397
|
-
if (b.has(item)) return true;
|
|
1398
|
-
}
|
|
1399
|
-
return false;
|
|
1400
|
-
}
|
|
1401
|
-
function truncateSummary(text) {
|
|
1402
|
-
if (!text) return null;
|
|
1403
|
-
return text.length > TOOL_OUTPUT_SUMMARY_LIMIT ? `${text.slice(0, TOOL_OUTPUT_SUMMARY_LIMIT - 1)}\u2026` : text;
|
|
1404
|
-
}
|
|
1405
|
-
function summarizeToolResult(result) {
|
|
1406
|
-
if (!result || typeof result !== "object") return null;
|
|
1407
|
-
const content = result.content;
|
|
1408
|
-
if (!Array.isArray(content) || content.length === 0) return null;
|
|
1409
|
-
const first = content[0];
|
|
1410
|
-
if (!first || first.type !== "text" || typeof first.text !== "string") return null;
|
|
1411
|
-
return truncateSummary(first.text.replace(/\s+/g, " ").trim());
|
|
1412
|
-
}
|
|
1413
|
-
function summarizeToolError(error) {
|
|
1414
|
-
if (error instanceof Error && error.message) {
|
|
1415
|
-
return truncateSummary(error.message) ?? "Tool failed";
|
|
1416
|
-
}
|
|
1417
|
-
return truncateSummary(String(error)) ?? "Tool failed";
|
|
1418
|
-
}
|
|
1419
|
-
function createVaultTools(agentId, runId, options) {
|
|
1420
|
-
const { turnOffset = 0, embeddingManager, teamClient, machineId, projectRoot, vaultDir, onlyNames } = options ?? {};
|
|
1421
|
-
let turnCounter = turnOffset;
|
|
1422
|
-
function recordTurn(toolName, toolInput) {
|
|
1423
|
-
turnCounter++;
|
|
1424
|
-
try {
|
|
1425
|
-
const turn = insertTurn({
|
|
1426
|
-
run_id: runId,
|
|
1427
|
-
agent_id: agentId,
|
|
1428
|
-
turn_number: turnCounter,
|
|
1429
|
-
tool_name: toolName,
|
|
1430
|
-
tool_input: JSON.stringify(toolInput),
|
|
1431
|
-
started_at: epochSeconds()
|
|
1432
|
-
});
|
|
1433
|
-
return turn.id;
|
|
1434
|
-
} catch {
|
|
1435
|
-
return null;
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
const deps = {
|
|
1439
|
-
agentId,
|
|
1440
|
-
runId,
|
|
1441
|
-
embeddingManager,
|
|
1442
|
-
teamClient,
|
|
1443
|
-
machineId,
|
|
1444
|
-
projectRoot,
|
|
1445
|
-
vaultDir,
|
|
1446
|
-
recordTurn
|
|
1447
|
-
};
|
|
1448
|
-
const needsAll = !onlyNames;
|
|
1449
|
-
const tools = [
|
|
1450
|
-
...needsAll || setsOverlap(onlyNames, READ_TOOL_NAMES) ? createReadTools(deps) : [],
|
|
1451
|
-
...needsAll || setsOverlap(onlyNames, WRITE_TOOL_NAMES) ? createWriteTools(deps) : [],
|
|
1452
|
-
...needsAll || setsOverlap(onlyNames, OBSERVABILITY_TOOL_NAMES) ? createObservabilityTools(deps) : [],
|
|
1453
|
-
...needsAll || setsOverlap(onlyNames, SKILL_TOOL_NAMES) ? createSkillTools(deps) : []
|
|
1454
|
-
];
|
|
1455
|
-
return tools.map((toolDef) => wrapToolWithAudit(toolDef));
|
|
1456
|
-
function wrapToolWithAudit(toolDef) {
|
|
1457
|
-
const originalHandler = toolDef.handler;
|
|
1458
|
-
return {
|
|
1459
|
-
...toolDef,
|
|
1460
|
-
handler: async (args, extra) => {
|
|
1461
|
-
const turnId = recordTurn(toolDef.name, args);
|
|
1462
|
-
try {
|
|
1463
|
-
const result = await originalHandler(args, extra);
|
|
1464
|
-
if (turnId !== null) {
|
|
1465
|
-
try {
|
|
1466
|
-
updateTurn(turnId, {
|
|
1467
|
-
tool_output_summary: summarizeToolResult(result),
|
|
1468
|
-
completed_at: epochSeconds()
|
|
1469
|
-
});
|
|
1470
|
-
} catch {
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
return result;
|
|
1474
|
-
} catch (error) {
|
|
1475
|
-
if (turnId !== null) {
|
|
1476
|
-
try {
|
|
1477
|
-
updateTurn(turnId, {
|
|
1478
|
-
tool_output_summary: summarizeToolError(error),
|
|
1479
|
-
completed_at: epochSeconds()
|
|
1480
|
-
});
|
|
1481
|
-
} catch {
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
throw error;
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
function createVaultToolServer(agentId, runId, options) {
|
|
1491
|
-
const tools = createVaultTools(agentId, runId, options);
|
|
1492
|
-
return createSdkMcpServer({
|
|
1493
|
-
name: "myco-vault",
|
|
1494
|
-
version: getPluginVersion(),
|
|
1495
|
-
tools
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
function createScopedVaultToolServer(agentId, runId, toolNames, options) {
|
|
1499
|
-
const nameSet = new Set(toolNames);
|
|
1500
|
-
const allTools = createVaultTools(agentId, runId, { ...options, onlyNames: nameSet });
|
|
1501
|
-
const eligible = options?.readOnly ? allTools.filter((t) => t.annotations?.readOnlyHint === true) : allTools;
|
|
1502
|
-
const scopedTools = eligible.filter((t) => nameSet.has(t.name));
|
|
1503
|
-
return createSdkMcpServer({
|
|
1504
|
-
name: "myco-vault",
|
|
1505
|
-
version: getPluginVersion(),
|
|
1506
|
-
tools: scopedTools
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
// src/agent/context.ts
|
|
1511
|
-
var STATE_UNSET = "(unset)";
|
|
1512
|
-
var STATE_KEY_LAST_PROCESSED_BATCH = "last_processed_batch_id";
|
|
1513
|
-
var SPORE_STATUS_ACTIVE = "active";
|
|
1514
|
-
function countRows(table, conditions = []) {
|
|
1515
|
-
const db = getDatabase();
|
|
1516
|
-
const whereParts = [];
|
|
1517
|
-
const params = [];
|
|
1518
|
-
for (const { clause, value } of conditions) {
|
|
1519
|
-
whereParts.push(clause);
|
|
1520
|
-
params.push(value);
|
|
1521
|
-
}
|
|
1522
|
-
const whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(" AND ")}` : "";
|
|
1523
|
-
const row = db.prepare(
|
|
1524
|
-
`SELECT count(*) AS count FROM ${table} ${whereClause}`
|
|
1525
|
-
).get(...params);
|
|
1526
|
-
return Number(row.count);
|
|
1527
|
-
}
|
|
1528
|
-
function buildVaultContext(agentId) {
|
|
1529
|
-
const states = getStatesForAgent(agentId);
|
|
1530
|
-
const totalSessions = countRows("sessions");
|
|
1531
|
-
const totalActiveSpores = countRows("spores", [{ clause: "status = ?", value: SPORE_STATUS_ACTIVE }]);
|
|
1532
|
-
const totalEntities = countRows("entities");
|
|
1533
|
-
const totalEdges = countRows("graph_edges");
|
|
1534
|
-
const unprocessedBatches = countRows("prompt_batches", [{ clause: "processed = ?", value: 0 }]);
|
|
1535
|
-
const lastDigestAt = getLastDigestTimestamp(agentId);
|
|
1536
|
-
const stateMap = new Map(states.map((s) => [s.key, s.value]));
|
|
1537
|
-
const lastProcessedBatchId = stateMap.get(STATE_KEY_LAST_PROCESSED_BATCH) ?? STATE_UNSET;
|
|
1538
|
-
const lines = [
|
|
1539
|
-
"## Current Vault State",
|
|
1540
|
-
`agent_id: ${agentId}`,
|
|
1541
|
-
`last_processed_batch_id: ${lastProcessedBatchId}`,
|
|
1542
|
-
`unprocessed_batches: ${unprocessedBatches}`,
|
|
1543
|
-
`total_sessions: ${totalSessions}`,
|
|
1544
|
-
`total_active_spores: ${totalActiveSpores}`,
|
|
1545
|
-
`total_entities: ${totalEntities}`,
|
|
1546
|
-
`total_edges: ${totalEdges}`,
|
|
1547
|
-
`last_digest_at: ${lastDigestAt}`
|
|
1548
|
-
];
|
|
1549
|
-
return lines.join("\n");
|
|
1550
|
-
}
|
|
1551
|
-
function getLastDigestTimestamp(agentId) {
|
|
1552
|
-
const db = getDatabase();
|
|
1553
|
-
const row = db.prepare(
|
|
1554
|
-
`SELECT MAX(generated_at) AS max_at
|
|
1555
|
-
FROM digest_extracts
|
|
1556
|
-
WHERE agent_id = ?`
|
|
1557
|
-
).get(agentId);
|
|
1558
|
-
return row?.max_at ?? 0;
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
// src/agent/orchestrator.ts
|
|
1562
|
-
import fs from "fs";
|
|
1563
|
-
import path from "path";
|
|
1564
|
-
import { fileURLToPath } from "url";
|
|
1565
|
-
|
|
1566
|
-
// src/intelligence/response.ts
|
|
1567
|
-
var REASONING_PATTERNS = [
|
|
1568
|
-
// <think>...</think>answer (DeepSeek, Qwen, GLM, many others)
|
|
1569
|
-
/<think>[\s\S]*?<\/think>\s*/gi,
|
|
1570
|
-
// Implicit opening: reasoning...</think>answer (GLM-4.7 observed)
|
|
1571
|
-
/^[\s\S]*?<\/think>\s*/i,
|
|
1572
|
-
// <reasoning>...</reasoning>answer
|
|
1573
|
-
/<reasoning>[\s\S]*?<\/reasoning>\s*/gi,
|
|
1574
|
-
// <|thinking|>...<|/thinking|>answer
|
|
1575
|
-
/<\|thinking\|>[\s\S]*?<\|\/thinking\|>\s*/gi,
|
|
1576
|
-
// Plain-text "Thinking Process:" block followed by actual content
|
|
1577
|
-
// (Qwen 3.5 via LM Studio without native thinking mode)
|
|
1578
|
-
// Matches from "Thinking Process:" up to the last numbered step, then the synthesis follows
|
|
1579
|
-
/^Thinking Process:[\s\S]*?(?=\n(?:## |# |\*\*[A-Z]))/i
|
|
1580
|
-
];
|
|
1581
|
-
function stripReasoningTokens(text) {
|
|
1582
|
-
if (!text) return text;
|
|
1583
|
-
for (const pattern of REASONING_PATTERNS) {
|
|
1584
|
-
const stripped = text.replace(pattern, "").trim();
|
|
1585
|
-
if (stripped && stripped !== text.trim()) {
|
|
1586
|
-
return stripped;
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
return text;
|
|
1590
|
-
}
|
|
1591
|
-
function extractJson(text) {
|
|
1592
|
-
const cleaned = stripReasoningTokens(text);
|
|
1593
|
-
const fenceMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
1594
|
-
if (fenceMatch) {
|
|
1595
|
-
return JSON.parse(fenceMatch[1].trim());
|
|
1596
|
-
}
|
|
1597
|
-
const objectMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
1598
|
-
if (objectMatch) {
|
|
1599
|
-
return JSON.parse(objectMatch[0]);
|
|
1600
|
-
}
|
|
1601
|
-
return JSON.parse(cleaned);
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
// src/agent/orchestrator.ts
|
|
1605
|
-
var DEFAULT_ORCHESTRATOR_MAX_TURNS = 3;
|
|
1606
|
-
var ORCHESTRATOR_PROMPT_FILE = "orchestrator.md";
|
|
1607
|
-
var PHASE_PROMPT_PREVIEW_CHARS = 100;
|
|
1608
|
-
var ORCHESTRATOR_GUIDANCE_HEADER = "## Orchestrator Guidance";
|
|
1609
|
-
var NO_CONTEXT_QUERIES_TEXT = "No context queries configured.";
|
|
1610
|
-
var FALLBACK_REASONING_PARSE_ERROR = "Orchestrator response could not be parsed \u2014 running all phases with defaults.";
|
|
1611
|
-
var FALLBACK_REASONING_MISSING_PHASES = "Orchestrator plan missing phases array \u2014 running all phases with defaults.";
|
|
1612
|
-
var PLACEHOLDER_VAULT_STATE = "{{vault_state}}";
|
|
1613
|
-
var PLACEHOLDER_PHASE_DEFINITIONS = "{{phase_definitions}}";
|
|
1614
|
-
var PLACEHOLDER_CONTEXT_RESULTS = "{{context_results}}";
|
|
1615
|
-
var cachedPromptTemplate;
|
|
1616
|
-
function loadPromptTemplate() {
|
|
1617
|
-
if (!cachedPromptTemplate) {
|
|
1618
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
1619
|
-
const adjacentPath = path.join(scriptDir, "prompts", ORCHESTRATOR_PROMPT_FILE);
|
|
1620
|
-
if (fs.existsSync(adjacentPath)) {
|
|
1621
|
-
cachedPromptTemplate = fs.readFileSync(adjacentPath, "utf-8");
|
|
1622
|
-
return cachedPromptTemplate;
|
|
1623
|
-
}
|
|
1624
|
-
const root = findPackageRoot(scriptDir);
|
|
1625
|
-
if (root) {
|
|
1626
|
-
const distPath = path.join(root, "dist", "src", "agent", "prompts", ORCHESTRATOR_PROMPT_FILE);
|
|
1627
|
-
if (fs.existsSync(distPath)) {
|
|
1628
|
-
cachedPromptTemplate = fs.readFileSync(distPath, "utf-8");
|
|
1629
|
-
return cachedPromptTemplate;
|
|
1630
|
-
}
|
|
1631
|
-
const srcPath = path.join(root, "src", "agent", "prompts", ORCHESTRATOR_PROMPT_FILE);
|
|
1632
|
-
cachedPromptTemplate = fs.readFileSync(srcPath, "utf-8");
|
|
1633
|
-
return cachedPromptTemplate;
|
|
1634
|
-
}
|
|
1635
|
-
cachedPromptTemplate = fs.readFileSync(adjacentPath, "utf-8");
|
|
1636
|
-
}
|
|
1637
|
-
return cachedPromptTemplate;
|
|
1638
|
-
}
|
|
1639
|
-
function composeOrchestratorPrompt(vaultState, phases, contextResults) {
|
|
1640
|
-
const template = loadPromptTemplate();
|
|
1641
|
-
const phaseList = formatPhaseList(phases);
|
|
1642
|
-
const contextSection = formatContextResults(contextResults);
|
|
1643
|
-
return template.replace(PLACEHOLDER_VAULT_STATE, vaultState).replace(PLACEHOLDER_PHASE_DEFINITIONS, phaseList).replace(PLACEHOLDER_CONTEXT_RESULTS, contextSection);
|
|
1644
|
-
}
|
|
1645
|
-
function parseOrchestratorPlan(response, phases) {
|
|
1646
|
-
const trimmed = response.trim();
|
|
1647
|
-
if (!trimmed) {
|
|
1648
|
-
return buildRunAllPlan(phases, FALLBACK_REASONING_PARSE_ERROR);
|
|
1649
|
-
}
|
|
1650
|
-
try {
|
|
1651
|
-
const parsed = extractJson(trimmed);
|
|
1652
|
-
if (!isOrchestratorPlanShape(parsed)) {
|
|
1653
|
-
return buildRunAllPlan(phases, FALLBACK_REASONING_MISSING_PHASES);
|
|
1654
|
-
}
|
|
1655
|
-
return parsed;
|
|
1656
|
-
} catch {
|
|
1657
|
-
return buildRunAllPlan(phases, FALLBACK_REASONING_PARSE_ERROR);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
function applyDirectives(phases, directives) {
|
|
1661
|
-
const directiveMap = new Map(
|
|
1662
|
-
directives.map((d) => [d.name, d])
|
|
1663
|
-
);
|
|
1664
|
-
const result = [];
|
|
1665
|
-
for (const phase of phases) {
|
|
1666
|
-
const directive = directiveMap.get(phase.name);
|
|
1667
|
-
if (!directive) {
|
|
1668
|
-
result.push(phase);
|
|
1669
|
-
continue;
|
|
1670
|
-
}
|
|
1671
|
-
if (directive.skip) {
|
|
1672
|
-
if (phase.required) {
|
|
1673
|
-
console.warn(
|
|
1674
|
-
`[orchestrator] Cannot skip required phase "${phase.name}" \u2014 keeping it. Reason: ${directive.skipReason ?? "none given"}`
|
|
1675
|
-
);
|
|
1676
|
-
result.push(applyNonSkipDirective(phase, directive));
|
|
1677
|
-
}
|
|
1678
|
-
continue;
|
|
1679
|
-
}
|
|
1680
|
-
result.push(applyNonSkipDirective(phase, directive));
|
|
1681
|
-
}
|
|
1682
|
-
return result;
|
|
1683
|
-
}
|
|
1684
|
-
function applyNonSkipDirective(phase, directive) {
|
|
1685
|
-
let updated = { ...phase };
|
|
1686
|
-
if (directive.maxTurns !== void 0) {
|
|
1687
|
-
updated = { ...updated, maxTurns: directive.maxTurns };
|
|
1688
|
-
}
|
|
1689
|
-
if (directive.contextNotes) {
|
|
1690
|
-
updated = {
|
|
1691
|
-
...updated,
|
|
1692
|
-
prompt: `${updated.prompt}
|
|
1693
|
-
|
|
1694
|
-
${ORCHESTRATOR_GUIDANCE_HEADER}
|
|
1695
|
-
|
|
1696
|
-
${directive.contextNotes}`
|
|
1697
|
-
};
|
|
1698
|
-
}
|
|
1699
|
-
return updated;
|
|
1700
|
-
}
|
|
1701
|
-
function formatPhaseList(phases) {
|
|
1702
|
-
if (phases.length === 0) {
|
|
1703
|
-
return "(no phases defined)";
|
|
1704
|
-
}
|
|
1705
|
-
return phases.map((p) => {
|
|
1706
|
-
const preview = p.prompt.slice(0, PHASE_PROMPT_PREVIEW_CHARS);
|
|
1707
|
-
const ellipsis = p.prompt.length > PHASE_PROMPT_PREVIEW_CHARS ? "..." : "";
|
|
1708
|
-
return `- **${p.name}** (maxTurns: ${p.maxTurns}, required: ${p.required}): ${preview}${ellipsis}`;
|
|
1709
|
-
}).join("\n");
|
|
1710
|
-
}
|
|
1711
|
-
function formatContextResults(results) {
|
|
1712
|
-
if (results.length === 0) {
|
|
1713
|
-
return NO_CONTEXT_QUERIES_TEXT;
|
|
1714
|
-
}
|
|
1715
|
-
return results.map((r) => {
|
|
1716
|
-
const dataSection = r.error ? `Error: ${r.error}` : JSON.stringify(r.data, null, 2);
|
|
1717
|
-
return `### ${r.tool}
|
|
1718
|
-
Purpose: ${r.purpose}
|
|
1719
|
-
|
|
1720
|
-
${dataSection}`;
|
|
1721
|
-
}).join("\n\n");
|
|
1722
|
-
}
|
|
1723
|
-
function isOrchestratorPlanShape(value) {
|
|
1724
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1725
|
-
const obj = value;
|
|
1726
|
-
return Array.isArray(obj["phases"]);
|
|
1727
|
-
}
|
|
1728
|
-
function buildRunAllPlan(phases, reasoning) {
|
|
1729
|
-
return {
|
|
1730
|
-
phases: phases.map((p) => ({ name: p.name, skip: false })),
|
|
1731
|
-
reasoning
|
|
1732
|
-
};
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
// src/agent/context-queries.ts
|
|
1736
|
-
var DEFAULT_CONTEXT_QUERY_LIMIT = 10;
|
|
1737
|
-
async function executeContextQueries(agentId, queries) {
|
|
1738
|
-
for (const query of queries) {
|
|
1739
|
-
validateTool(query.tool);
|
|
1740
|
-
}
|
|
1741
|
-
const settled = await Promise.allSettled(
|
|
1742
|
-
queries.map(async (query) => {
|
|
1743
|
-
const limit = query.limit ?? DEFAULT_CONTEXT_QUERY_LIMIT;
|
|
1744
|
-
const data = await executeQuery(agentId, query.tool, limit);
|
|
1745
|
-
return { tool: query.tool, purpose: query.purpose, data };
|
|
1746
|
-
})
|
|
1747
|
-
);
|
|
1748
|
-
const results = [];
|
|
1749
|
-
for (let i = 0; i < settled.length; i++) {
|
|
1750
|
-
const outcome = settled[i];
|
|
1751
|
-
const query = queries[i];
|
|
1752
|
-
if (outcome.status === "fulfilled") {
|
|
1753
|
-
results.push(outcome.value);
|
|
1754
|
-
} else {
|
|
1755
|
-
const message = errorMessage(outcome.reason);
|
|
1756
|
-
if (query.required) {
|
|
1757
|
-
throw new Error(
|
|
1758
|
-
`Required context query "${query.tool}" failed: ${message}`
|
|
1759
|
-
);
|
|
1760
|
-
}
|
|
1761
|
-
results.push({
|
|
1762
|
-
tool: query.tool,
|
|
1763
|
-
purpose: query.purpose,
|
|
1764
|
-
data: null,
|
|
1765
|
-
error: message
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
return results;
|
|
1770
|
-
}
|
|
1771
|
-
var KNOWN_CONTEXT_QUERY_TOOLS = /* @__PURE__ */ new Set([
|
|
1772
|
-
"vault_unprocessed",
|
|
1773
|
-
"vault_spores",
|
|
1774
|
-
"vault_sessions",
|
|
1775
|
-
"vault_state"
|
|
1776
|
-
]);
|
|
1777
|
-
function validateTool(tool5) {
|
|
1778
|
-
if (!KNOWN_CONTEXT_QUERY_TOOLS.has(tool5)) {
|
|
1779
|
-
throw new Error(`Unknown context query tool: "${tool5}"`);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
async function executeQuery(agentId, tool5, limit) {
|
|
1783
|
-
switch (tool5) {
|
|
1784
|
-
case "vault_unprocessed":
|
|
1785
|
-
return getUnprocessedBatches({ limit, includeActive: false });
|
|
1786
|
-
case "vault_spores":
|
|
1787
|
-
return listSpores({ agent_id: agentId, limit, includeActive: false });
|
|
1788
|
-
case "vault_sessions":
|
|
1789
|
-
return listSessions({ limit, includeActive: false });
|
|
1790
|
-
case "vault_state":
|
|
1791
|
-
return getStatesForAgent(agentId);
|
|
1792
|
-
default:
|
|
1793
|
-
throw new Error(`Unknown context query tool: "${tool5}"`);
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
// src/agent/provider.ts
|
|
1798
|
-
var ENV_ANTHROPIC_BASE_URL = "ANTHROPIC_BASE_URL";
|
|
1799
|
-
var ENV_ANTHROPIC_AUTH_TOKEN = "ANTHROPIC_AUTH_TOKEN";
|
|
1800
|
-
var ENV_ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY";
|
|
1801
|
-
var ENV_OLLAMA_NUM_CTX = "OLLAMA_NUM_CTX";
|
|
1802
|
-
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
1803
|
-
var DEFAULT_LMSTUDIO_URL = "http://localhost:1234";
|
|
1804
|
-
var OLLAMA_AUTH_TOKEN = "ollama";
|
|
1805
|
-
var LMSTUDIO_AUTH_TOKEN = "lmstudio";
|
|
1806
|
-
function buildPhaseEnv(provider) {
|
|
1807
|
-
if (!provider || provider.type === "anthropic") return void 0;
|
|
1808
|
-
return { ...process.env, ...getProviderEnvVars(provider) };
|
|
1809
|
-
}
|
|
1810
|
-
function getProviderEnvVars(provider) {
|
|
1811
|
-
switch (provider.type) {
|
|
1812
|
-
case "anthropic":
|
|
1813
|
-
return {};
|
|
1814
|
-
case "ollama":
|
|
1815
|
-
return {
|
|
1816
|
-
[ENV_ANTHROPIC_BASE_URL]: provider.baseUrl ?? DEFAULT_OLLAMA_URL,
|
|
1817
|
-
[ENV_ANTHROPIC_AUTH_TOKEN]: OLLAMA_AUTH_TOKEN,
|
|
1818
|
-
[ENV_ANTHROPIC_API_KEY]: "",
|
|
1819
|
-
...provider.contextLength ? { [ENV_OLLAMA_NUM_CTX]: String(provider.contextLength) } : {}
|
|
1820
|
-
};
|
|
1821
|
-
case "lmstudio":
|
|
1822
|
-
return {
|
|
1823
|
-
[ENV_ANTHROPIC_BASE_URL]: provider.baseUrl ?? DEFAULT_LMSTUDIO_URL,
|
|
1824
|
-
[ENV_ANTHROPIC_AUTH_TOKEN]: provider.apiKey ?? LMSTUDIO_AUTH_TOKEN,
|
|
1825
|
-
[ENV_ANTHROPIC_API_KEY]: ""
|
|
1826
|
-
};
|
|
1827
|
-
default:
|
|
1828
|
-
return {};
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
// src/agent/ollama-context.ts
|
|
1833
|
-
import { execFileSync } from "child_process";
|
|
1834
|
-
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
1835
|
-
import { tmpdir } from "os";
|
|
1836
|
-
import { join } from "path";
|
|
1837
|
-
var OLLAMA_PRELOAD_TIMEOUT_MS = 3e4;
|
|
1838
|
-
var DEFAULT_OLLAMA_CONTEXT_LENGTH = 32768;
|
|
1839
|
-
async function ensureOllamaContextVariant(model, contextLength) {
|
|
1840
|
-
const baseName = model.replace(/:latest$/, "");
|
|
1841
|
-
const variantName = `${baseName}-ctx${contextLength}`;
|
|
1842
|
-
try {
|
|
1843
|
-
execFileSync("ollama", ["show", variantName], { stdio: "ignore" });
|
|
1844
|
-
return variantName;
|
|
1845
|
-
} catch {
|
|
1846
|
-
}
|
|
1847
|
-
try {
|
|
1848
|
-
const modelfilePath = join(tmpdir(), `myco-modelfile-${Date.now()}`);
|
|
1849
|
-
writeFileSync2(modelfilePath, `FROM ${model}
|
|
1850
|
-
PARAMETER num_ctx ${contextLength}
|
|
1851
|
-
`);
|
|
1852
|
-
execFileSync("ollama", ["create", variantName, "-f", modelfilePath], {
|
|
1853
|
-
stdio: "ignore",
|
|
1854
|
-
timeout: OLLAMA_PRELOAD_TIMEOUT_MS
|
|
1855
|
-
});
|
|
1856
|
-
try {
|
|
1857
|
-
unlinkSync(modelfilePath);
|
|
1858
|
-
} catch {
|
|
1859
|
-
}
|
|
1860
|
-
return variantName;
|
|
1861
|
-
} catch {
|
|
1862
|
-
return model;
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
async function resolveOllamaContextVariants(taskProvider, phaseOverrides, createVariant = ensureOllamaContextVariant) {
|
|
1866
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1867
|
-
const recordOllama = (p) => {
|
|
1868
|
-
if (p?.type !== "ollama" || !p.model) return;
|
|
1869
|
-
const ctx = p.contextLength ?? DEFAULT_OLLAMA_CONTEXT_LENGTH;
|
|
1870
|
-
const set = seen.get(p.model) ?? /* @__PURE__ */ new Set();
|
|
1871
|
-
set.add(ctx);
|
|
1872
|
-
seen.set(p.model, set);
|
|
1873
|
-
};
|
|
1874
|
-
recordOllama(taskProvider);
|
|
1875
|
-
for (const override of Object.values(phaseOverrides)) {
|
|
1876
|
-
recordOllama(override.provider);
|
|
1877
|
-
}
|
|
1878
|
-
if (seen.size === 0) {
|
|
1879
|
-
return { taskProvider, phaseOverrides, conflicts: [] };
|
|
1880
|
-
}
|
|
1881
|
-
const resolvedContext = /* @__PURE__ */ new Map();
|
|
1882
|
-
const conflicts = [];
|
|
1883
|
-
for (const [model, values] of seen) {
|
|
1884
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
1885
|
-
const max = sorted[sorted.length - 1];
|
|
1886
|
-
resolvedContext.set(model, max);
|
|
1887
|
-
if (sorted.length > 1) {
|
|
1888
|
-
conflicts.push({ model, values: sorted, resolved: max });
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
const variantEntries = await Promise.all(
|
|
1892
|
-
[...resolvedContext.entries()].map(async ([model, ctx]) => {
|
|
1893
|
-
const variant = await createVariant(model, ctx);
|
|
1894
|
-
return [model, variant];
|
|
1895
|
-
})
|
|
1896
|
-
);
|
|
1897
|
-
const variantByModel = new Map(variantEntries);
|
|
1898
|
-
const rewriteProvider = (p) => {
|
|
1899
|
-
if (!p) return p;
|
|
1900
|
-
if (p.type !== "ollama" || !p.model) return p;
|
|
1901
|
-
const variant = variantByModel.get(p.model);
|
|
1902
|
-
const resolvedCtx = resolvedContext.get(p.model);
|
|
1903
|
-
if (!variant) return p;
|
|
1904
|
-
return { ...p, model: variant, contextLength: resolvedCtx };
|
|
1905
|
-
};
|
|
1906
|
-
const rewrittenPhaseOverrides = {};
|
|
1907
|
-
for (const [name, override] of Object.entries(phaseOverrides)) {
|
|
1908
|
-
rewrittenPhaseOverrides[name] = {
|
|
1909
|
-
...override,
|
|
1910
|
-
...override.provider ? { provider: rewriteProvider(override.provider) } : {}
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
return {
|
|
1914
|
-
taskProvider: rewriteProvider(taskProvider),
|
|
1915
|
-
phaseOverrides: rewrittenPhaseOverrides,
|
|
1916
|
-
conflicts
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
// src/agent/wave-computation.ts
|
|
1921
|
-
import crypto3 from "crypto";
|
|
1922
|
-
function computeWaves(phases) {
|
|
1923
|
-
const nameToPhase = new Map(phases.map((p) => [p.name, p]));
|
|
1924
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1925
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
1926
|
-
for (const phase of phases) {
|
|
1927
|
-
inDegree.set(phase.name, 0);
|
|
1928
|
-
dependents.set(phase.name, []);
|
|
1929
|
-
}
|
|
1930
|
-
for (const phase of phases) {
|
|
1931
|
-
const deps = phase.dependsOn ?? [];
|
|
1932
|
-
for (const dep of deps) {
|
|
1933
|
-
if (!nameToPhase.has(dep)) continue;
|
|
1934
|
-
inDegree.set(phase.name, (inDegree.get(phase.name) ?? 0) + 1);
|
|
1935
|
-
dependents.get(dep).push(phase.name);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
const waves = [];
|
|
1939
|
-
const completed = /* @__PURE__ */ new Set();
|
|
1940
|
-
while (completed.size < phases.length) {
|
|
1941
|
-
const wave = [];
|
|
1942
|
-
for (const phase of phases) {
|
|
1943
|
-
if (completed.has(phase.name)) continue;
|
|
1944
|
-
if ((inDegree.get(phase.name) ?? 0) === 0) {
|
|
1945
|
-
wave.push(phase);
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
if (wave.length === 0) {
|
|
1949
|
-
const remaining = phases.filter((p) => !completed.has(p.name)).map((p) => p.name);
|
|
1950
|
-
throw new Error(`Circular dependency detected among phases: ${remaining.join(", ")}`);
|
|
1951
|
-
}
|
|
1952
|
-
waves.push(wave);
|
|
1953
|
-
for (const phase of wave) {
|
|
1954
|
-
completed.add(phase.name);
|
|
1955
|
-
for (const dependent of dependents.get(phase.name) ?? []) {
|
|
1956
|
-
inDegree.set(dependent, (inDegree.get(dependent) ?? 0) - 1);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
return waves;
|
|
1961
|
-
}
|
|
1962
|
-
function phaseSessionId(runId, phaseName) {
|
|
1963
|
-
const hash = crypto3.createHash("sha256").update(`${runId}-${phaseName}`).digest("hex");
|
|
1964
|
-
return [
|
|
1965
|
-
hash.slice(0, 8),
|
|
1966
|
-
hash.slice(8, 12),
|
|
1967
|
-
hash.slice(12, 16),
|
|
1968
|
-
hash.slice(16, 20),
|
|
1969
|
-
hash.slice(20, 32)
|
|
1970
|
-
].join("-");
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
// src/agent/executor.ts
|
|
1974
|
-
var STATUS_SKIPPED = "skipped";
|
|
1975
|
-
var SKIP_REASON_ALREADY_RUNNING = "already_running";
|
|
1976
|
-
var PROMPT_SECTION_TASK = "## Task: ";
|
|
1977
|
-
var PROMPT_SECTION_INSTRUCTION = "## User Instruction";
|
|
1978
|
-
var PROMPT_SECTION_SEPARATOR = "\n\n";
|
|
1979
|
-
var MCP_SERVER_NAME = "myco-vault";
|
|
1980
|
-
var PERSIST_SESSION = true;
|
|
1981
|
-
var PROMPT_SECTION_PRIOR_PHASES = "## Prior Phase Results";
|
|
1982
|
-
var PROMPT_SECTION_CURRENT_PHASE = "## Current Phase: ";
|
|
1983
|
-
var TOOL_DEBUG_PREVIEW_CHARS = 240;
|
|
1984
|
-
function debugToolCallsEnabled() {
|
|
1985
|
-
return process.env.MYCO_AGENT_DEBUG === "1";
|
|
1986
|
-
}
|
|
1987
|
-
function previewPayload(value) {
|
|
1988
|
-
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
1989
|
-
if (str === void 0) return "";
|
|
1990
|
-
return str.length > TOOL_DEBUG_PREVIEW_CHARS ? `${str.slice(0, TOOL_DEBUG_PREVIEW_CHARS)}\u2026(${str.length - TOOL_DEBUG_PREVIEW_CHARS} more chars)` : str;
|
|
1991
|
-
}
|
|
1992
|
-
function logToolUseBlocks(phaseName, message) {
|
|
1993
|
-
if (!debugToolCallsEnabled()) return;
|
|
1994
|
-
const blocks = message.message?.content;
|
|
1995
|
-
if (!Array.isArray(blocks)) return;
|
|
1996
|
-
for (const block of blocks) {
|
|
1997
|
-
if (block.type === "tool_use") {
|
|
1998
|
-
console.log(
|
|
1999
|
-
`[agent:debug] ${phaseName} tool_use: ${block.name ?? "unknown"} input=${previewPayload(block.input)}`
|
|
2000
|
-
);
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
function logToolResultBlocks(phaseName, message) {
|
|
2005
|
-
if (!debugToolCallsEnabled()) return;
|
|
2006
|
-
const blocks = message.message?.content;
|
|
2007
|
-
if (!Array.isArray(blocks)) return;
|
|
2008
|
-
for (const block of blocks) {
|
|
2009
|
-
if (block.type === "tool_result") {
|
|
2010
|
-
const flag = block.is_error ? " [ERROR]" : "";
|
|
2011
|
-
console.log(
|
|
2012
|
-
`[agent:debug] ${phaseName} tool_result${flag}: ${previewPayload(block.content)}`
|
|
2013
|
-
);
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
function abortReasonMessage(abortController) {
|
|
2018
|
-
if (!abortController?.signal.aborted) return null;
|
|
2019
|
-
const reason = abortController.signal.reason;
|
|
2020
|
-
if (reason instanceof Error) return reason.message;
|
|
2021
|
-
if (typeof reason === "string" && reason.length > 0) return reason;
|
|
2022
|
-
return "Agent run aborted";
|
|
2023
|
-
}
|
|
2024
|
-
function composeTaskPrompt(vaultContext, taskDisplayName, taskPrompt, instruction) {
|
|
2025
|
-
const sessionIdMatch = instruction?.match(/\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/i);
|
|
2026
|
-
const sessionId = sessionIdMatch?.[1] ?? "";
|
|
2027
|
-
let resolvedPrompt = taskPrompt;
|
|
2028
|
-
resolvedPrompt = resolvedPrompt.replace(/\{\{session_id\}\}/g, sessionId);
|
|
2029
|
-
resolvedPrompt = resolvedPrompt.replace(/\{\{instruction\}\}/g, instruction ?? "");
|
|
2030
|
-
const parts = [
|
|
2031
|
-
vaultContext,
|
|
2032
|
-
`${PROMPT_SECTION_TASK}${taskDisplayName}
|
|
2033
|
-
${resolvedPrompt}`
|
|
2034
|
-
];
|
|
2035
|
-
if (instruction) {
|
|
2036
|
-
parts.push(`${PROMPT_SECTION_INSTRUCTION}
|
|
2037
|
-
${instruction}`);
|
|
2038
|
-
}
|
|
2039
|
-
return parts.join(PROMPT_SECTION_SEPARATOR);
|
|
2040
|
-
}
|
|
2041
|
-
function composePhasePrompt(vaultContext, taskDisplayName, taskOverview, phase, priorPhaseResults, instruction) {
|
|
2042
|
-
const parts = [
|
|
2043
|
-
vaultContext,
|
|
2044
|
-
`${PROMPT_SECTION_TASK}${taskDisplayName}
|
|
2045
|
-
${taskOverview}`
|
|
2046
|
-
];
|
|
2047
|
-
if (instruction) {
|
|
2048
|
-
parts.push(`${PROMPT_SECTION_INSTRUCTION}
|
|
2049
|
-
${instruction}`);
|
|
2050
|
-
}
|
|
2051
|
-
if (priorPhaseResults.length > 0 && !phase.skipPriorContext) {
|
|
2052
|
-
const summaries = priorPhaseResults.map((pr) => {
|
|
2053
|
-
const truncated = pr.summary.length > PHASE_SUMMARY_MAX_CHARS ? pr.summary.slice(0, PHASE_SUMMARY_MAX_CHARS) + "..." : pr.summary;
|
|
2054
|
-
return `### ${pr.name} (${pr.status})
|
|
2055
|
-
${truncated}`;
|
|
2056
|
-
});
|
|
2057
|
-
parts.push(`${PROMPT_SECTION_PRIOR_PHASES}
|
|
2058
|
-
${summaries.join("\n\n")}`);
|
|
2059
|
-
}
|
|
2060
|
-
parts.push(`${PROMPT_SECTION_CURRENT_PHASE}${phase.name}
|
|
2061
|
-
${phase.prompt}`);
|
|
2062
|
-
return parts.join(PROMPT_SECTION_SEPARATOR);
|
|
2063
|
-
}
|
|
2064
|
-
async function executePhase(query, phasePrompt, phaseModel, systemPrompt, toolServer, phase, env, sessionId, abortController) {
|
|
2065
|
-
let phaseCost = 0;
|
|
2066
|
-
let phaseTokens = 0;
|
|
2067
|
-
let phaseTurns = 0;
|
|
2068
|
-
let agenticTurns = 0;
|
|
2069
|
-
let phaseSummary = "";
|
|
2070
|
-
try {
|
|
2071
|
-
for await (const message of query({
|
|
2072
|
-
prompt: phasePrompt,
|
|
2073
|
-
options: {
|
|
2074
|
-
model: phaseModel,
|
|
2075
|
-
systemPrompt,
|
|
2076
|
-
mcpServers: { [MCP_SERVER_NAME]: toolServer },
|
|
2077
|
-
strictMcpConfig: true,
|
|
2078
|
-
maxTurns: phase.maxTurns,
|
|
2079
|
-
permissionMode: "bypassPermissions",
|
|
2080
|
-
allowDangerouslySkipPermissions: true,
|
|
2081
|
-
persistSession: PERSIST_SESSION,
|
|
2082
|
-
env,
|
|
2083
|
-
tools: [],
|
|
2084
|
-
...sessionId ? { sessionId } : {},
|
|
2085
|
-
...abortController ? { abortController } : {}
|
|
2086
|
-
}
|
|
2087
|
-
})) {
|
|
2088
|
-
if (message.type === "assistant") {
|
|
2089
|
-
agenticTurns++;
|
|
2090
|
-
logToolUseBlocks(phase.name, message);
|
|
2091
|
-
}
|
|
2092
|
-
if (message.type === "user") {
|
|
2093
|
-
logToolResultBlocks(phase.name, message);
|
|
2094
|
-
}
|
|
2095
|
-
if (message.type === "result") {
|
|
2096
|
-
phaseCost = message.total_cost_usd ?? 0;
|
|
2097
|
-
phaseTokens = (message.usage.input_tokens ?? 0) + (message.usage.output_tokens ?? 0);
|
|
2098
|
-
phaseTurns = message.num_turns ?? 0;
|
|
2099
|
-
if ("result" in message && typeof message.result === "string") {
|
|
2100
|
-
phaseSummary = message.result;
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
if (phase.maxTurns) {
|
|
2105
|
-
console.log(
|
|
2106
|
-
`[agent] Phase "${phase.name}": num_turns=${phaseTurns}, assistant_msgs=${agenticTurns}, budget=${phase.maxTurns}`
|
|
2107
|
-
);
|
|
2108
|
-
}
|
|
2109
|
-
if (phase.required && phaseTurns === 0) {
|
|
2110
|
-
console.warn(`[agent] Required phase "${phase.name}" produced 0 turns`);
|
|
2111
|
-
}
|
|
2112
|
-
return {
|
|
2113
|
-
name: phase.name,
|
|
2114
|
-
status: "completed",
|
|
2115
|
-
turnsUsed: phaseTurns,
|
|
2116
|
-
tokensUsed: phaseTokens,
|
|
2117
|
-
costUsd: phaseCost,
|
|
2118
|
-
summary: phaseSummary
|
|
2119
|
-
};
|
|
2120
|
-
} catch (err) {
|
|
2121
|
-
const abortReason = abortReasonMessage(abortController);
|
|
2122
|
-
return {
|
|
2123
|
-
name: phase.name,
|
|
2124
|
-
status: "failed",
|
|
2125
|
-
turnsUsed: phaseTurns,
|
|
2126
|
-
tokensUsed: phaseTokens,
|
|
2127
|
-
costUsd: phaseCost,
|
|
2128
|
-
summary: abortReason ? `Error: ${abortReason}` : `Error: ${errorMessage(err)}`
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
async function executeSingleQuery(config, systemPrompt, taskPrompt, agentId, runId, provider, embeddingManager, abortController, vaultDir) {
|
|
2133
|
-
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
2134
|
-
const toolServer = createVaultToolServer(agentId, runId, { embeddingManager, vaultDir });
|
|
2135
|
-
const baseEnv = buildPhaseEnv(provider);
|
|
2136
|
-
const env = { ...baseEnv ?? process.env, MYCO_AGENT_SESSION: "1" };
|
|
2137
|
-
const effectiveModel = provider?.model ?? config.model;
|
|
2138
|
-
let resultCostUsd = 0;
|
|
2139
|
-
let resultTokens = 0;
|
|
2140
|
-
for await (const message of query({
|
|
2141
|
-
prompt: taskPrompt,
|
|
2142
|
-
options: {
|
|
2143
|
-
model: effectiveModel,
|
|
2144
|
-
systemPrompt,
|
|
2145
|
-
mcpServers: { [MCP_SERVER_NAME]: toolServer },
|
|
2146
|
-
strictMcpConfig: true,
|
|
2147
|
-
maxTurns: config.maxTurns,
|
|
2148
|
-
permissionMode: "bypassPermissions",
|
|
2149
|
-
allowDangerouslySkipPermissions: true,
|
|
2150
|
-
persistSession: PERSIST_SESSION,
|
|
2151
|
-
env,
|
|
2152
|
-
tools: [],
|
|
2153
|
-
...abortController ? { abortController } : {}
|
|
2154
|
-
}
|
|
2155
|
-
})) {
|
|
2156
|
-
if (message.type === "result") {
|
|
2157
|
-
resultCostUsd = message.total_cost_usd ?? 0;
|
|
2158
|
-
resultTokens = (message.usage.input_tokens ?? 0) + (message.usage.output_tokens ?? 0);
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
return { tokensUsed: resultTokens, costUsd: resultCostUsd };
|
|
2162
|
-
}
|
|
2163
|
-
async function executePhasedQuery(config, systemPrompt, vaultContext, agentId, runId, taskProviderOverride, phaseProviderOverrides, instruction, embeddingManager, abortController, projectRoot, vaultDir) {
|
|
2164
|
-
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
2165
|
-
const phases = config.phases;
|
|
2166
|
-
const phaseResults = [];
|
|
2167
|
-
let totalTokens = 0;
|
|
2168
|
-
let totalCost = 0;
|
|
2169
|
-
let runningTurnCount = 0;
|
|
2170
|
-
let effectivePhases = [...phases];
|
|
2171
|
-
if (config.orchestrator?.enabled) {
|
|
2172
|
-
const contextQueries = config.contextQueries ? Object.values(config.contextQueries).flat() : [];
|
|
2173
|
-
const contextResults = contextQueries.length > 0 ? await executeContextQueries(agentId, contextQueries) : [];
|
|
2174
|
-
const orchestratorPrompt = composeOrchestratorPrompt(vaultContext, phases, contextResults);
|
|
2175
|
-
const orchestratorModel = config.orchestrator.model ?? config.model;
|
|
2176
|
-
const orchestratorMaxTurns = config.orchestrator.maxTurns ?? DEFAULT_ORCHESTRATOR_MAX_TURNS;
|
|
2177
|
-
const orchestratorEnv = { ...buildPhaseEnv(taskProviderOverride) ?? process.env, MYCO_AGENT_SESSION: "1" };
|
|
2178
|
-
let planResponse = "";
|
|
2179
|
-
for await (const message of query({
|
|
2180
|
-
prompt: orchestratorPrompt,
|
|
2181
|
-
options: {
|
|
2182
|
-
model: orchestratorModel,
|
|
2183
|
-
maxTurns: orchestratorMaxTurns,
|
|
2184
|
-
permissionMode: "bypassPermissions",
|
|
2185
|
-
allowDangerouslySkipPermissions: true,
|
|
2186
|
-
persistSession: PERSIST_SESSION,
|
|
2187
|
-
strictMcpConfig: true,
|
|
2188
|
-
env: orchestratorEnv,
|
|
2189
|
-
tools: []
|
|
2190
|
-
}
|
|
2191
|
-
})) {
|
|
2192
|
-
if (message.type === "result" && "result" in message && typeof message.result === "string") {
|
|
2193
|
-
planResponse = message.result;
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
const plan = parseOrchestratorPlan(planResponse, phases);
|
|
2197
|
-
effectivePhases = applyDirectives(phases, plan.phases);
|
|
2198
|
-
}
|
|
2199
|
-
const declarationOrder = new Map(phases.map((p, i) => [p.name, i]));
|
|
2200
|
-
const waves = computeWaves(effectivePhases);
|
|
2201
|
-
for (const wave of waves) {
|
|
2202
|
-
const executions = wave.map((phase, indexInWave) => {
|
|
2203
|
-
const phasePrompt = composePhasePrompt(
|
|
2204
|
-
vaultContext,
|
|
2205
|
-
config.taskDisplayName,
|
|
2206
|
-
config.taskPrompt,
|
|
2207
|
-
phase,
|
|
2208
|
-
phaseResults,
|
|
2209
|
-
instruction
|
|
2210
|
-
);
|
|
2211
|
-
const phaseOverride = phaseProviderOverrides?.[phase.name];
|
|
2212
|
-
const effectiveMaxTurns = phaseOverride?.maxTurns ?? phase.maxTurns;
|
|
2213
|
-
const phaseModel = phase.model ?? phaseOverride?.provider?.model ?? taskProviderOverride?.model ?? config.model;
|
|
2214
|
-
const toolServer = createScopedVaultToolServer(
|
|
2215
|
-
agentId,
|
|
2216
|
-
runId,
|
|
2217
|
-
phase.tools,
|
|
2218
|
-
{
|
|
2219
|
-
turnOffset: runningTurnCount + indexInWave * effectiveMaxTurns,
|
|
2220
|
-
embeddingManager,
|
|
2221
|
-
projectRoot,
|
|
2222
|
-
vaultDir,
|
|
2223
|
-
readOnly: phase.readOnly
|
|
2224
|
-
}
|
|
2225
|
-
);
|
|
2226
|
-
const phaseProvider = phase.provider ?? phaseOverride?.provider ?? taskProviderOverride ?? config.execution?.provider;
|
|
2227
|
-
const baseEnv = buildPhaseEnv(phaseProvider);
|
|
2228
|
-
const env = { ...baseEnv ?? process.env, MYCO_AGENT_SESSION: "1" };
|
|
2229
|
-
const sessionId = phaseSessionId(runId, phase.name);
|
|
2230
|
-
const effectivePhase = effectiveMaxTurns !== phase.maxTurns ? { ...phase, maxTurns: effectiveMaxTurns } : phase;
|
|
2231
|
-
return executePhase(query, phasePrompt, phaseModel, systemPrompt, toolServer, effectivePhase, env, sessionId, abortController);
|
|
2232
|
-
});
|
|
2233
|
-
const settled = await Promise.allSettled(executions);
|
|
2234
|
-
const waveResults = settled.map((outcome, i) => {
|
|
2235
|
-
if (outcome.status === "fulfilled") {
|
|
2236
|
-
return outcome.value;
|
|
2237
|
-
}
|
|
2238
|
-
return {
|
|
2239
|
-
name: wave[i].name,
|
|
2240
|
-
status: "failed",
|
|
2241
|
-
turnsUsed: 0,
|
|
2242
|
-
tokensUsed: 0,
|
|
2243
|
-
costUsd: 0,
|
|
2244
|
-
summary: `Error: ${errorMessage(outcome.reason)}`
|
|
2245
|
-
};
|
|
2246
|
-
});
|
|
2247
|
-
waveResults.sort(
|
|
2248
|
-
(a, b) => (declarationOrder.get(a.name) ?? 0) - (declarationOrder.get(b.name) ?? 0)
|
|
2249
|
-
);
|
|
2250
|
-
for (const result of waveResults) {
|
|
2251
|
-
phaseResults.push(result);
|
|
2252
|
-
totalTokens += result.tokensUsed;
|
|
2253
|
-
totalCost += result.costUsd;
|
|
2254
|
-
runningTurnCount += result.turnsUsed;
|
|
2255
|
-
}
|
|
2256
|
-
const shouldStop = wave.some((phase, i) => {
|
|
2257
|
-
if (!phase.required) return false;
|
|
2258
|
-
const outcome = settled[i];
|
|
2259
|
-
if (outcome.status === "rejected") return true;
|
|
2260
|
-
return outcome.value.status === "failed";
|
|
2261
|
-
});
|
|
2262
|
-
if (shouldStop) {
|
|
2263
|
-
break;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
return { tokensUsed: totalTokens, costUsd: totalCost, phases: phaseResults };
|
|
2267
|
-
}
|
|
2268
|
-
async function runAgent(vaultDir, options) {
|
|
2269
|
-
const db = initDatabase(vaultDbPath(vaultDir));
|
|
2270
|
-
createSchema(db);
|
|
2271
|
-
const agentId = options?.agentId ?? DEFAULT_AGENT_ID;
|
|
2272
|
-
const requestedTask = options?.task;
|
|
2273
|
-
{
|
|
2274
|
-
const effectiveTask = requestedTask ?? getDefaultTask(agentId)?.id;
|
|
2275
|
-
if (effectiveTask) {
|
|
2276
|
-
const runningId = getRunningRunForTask(agentId, effectiveTask);
|
|
2277
|
-
if (runningId) {
|
|
2278
|
-
return {
|
|
2279
|
-
runId: runningId,
|
|
2280
|
-
status: STATUS_SKIPPED,
|
|
2281
|
-
reason: SKIP_REASON_ALREADY_RUNNING
|
|
2282
|
-
};
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
const {
|
|
2287
|
-
config,
|
|
2288
|
-
definitionsDir,
|
|
2289
|
-
taskProviderOverride: resolvedTaskProvider,
|
|
2290
|
-
phaseProviderOverrides: resolvedPhaseOverrides
|
|
2291
|
-
} = resolveRunConfig(agentId, requestedTask, vaultDir);
|
|
2292
|
-
let taskProviderOverride = resolvedTaskProvider;
|
|
2293
|
-
let phaseProviderOverrides = resolvedPhaseOverrides;
|
|
2294
|
-
const runId = options?.resumeRunId ?? crypto4.randomUUID();
|
|
2295
|
-
const now = epochSeconds();
|
|
2296
|
-
if (!options?.resumeRunId) {
|
|
2297
|
-
insertRun({
|
|
2298
|
-
id: runId,
|
|
2299
|
-
agent_id: agentId,
|
|
2300
|
-
task: config.taskName,
|
|
2301
|
-
instruction: options?.instruction ?? null,
|
|
2302
|
-
status: STATUS_RUNNING,
|
|
2303
|
-
started_at: now
|
|
2304
|
-
});
|
|
2305
|
-
}
|
|
2306
|
-
const systemPrompt = loadSystemPrompt(definitionsDir, config.systemPromptPath);
|
|
2307
|
-
const vaultContext = buildVaultContext(agentId);
|
|
2308
|
-
const effectiveProvider = taskProviderOverride ?? config.execution?.provider;
|
|
2309
|
-
const effectiveModel = effectiveProvider?.model ?? config.model;
|
|
2310
|
-
const runMeta = {
|
|
2311
|
-
model: effectiveModel,
|
|
2312
|
-
provider: effectiveProvider?.type ?? "anthropic",
|
|
2313
|
-
...effectiveProvider?.baseUrl ? { baseUrl: effectiveProvider.baseUrl } : {}
|
|
2314
|
-
};
|
|
2315
|
-
{
|
|
2316
|
-
const resolved = await resolveOllamaContextVariants(
|
|
2317
|
-
taskProviderOverride,
|
|
2318
|
-
phaseProviderOverrides
|
|
2319
|
-
);
|
|
2320
|
-
taskProviderOverride = resolved.taskProvider;
|
|
2321
|
-
phaseProviderOverrides = resolved.phaseOverrides;
|
|
2322
|
-
for (const conflict of resolved.conflicts) {
|
|
2323
|
-
console.warn(
|
|
2324
|
-
`[agent] Ollama model "${conflict.model}" referenced with conflicting context_length values [${conflict.values.join(", ")}] \u2014 reconciled to ${conflict.resolved} to avoid loading multiple variants. Configure one value per model to silence this warning.`
|
|
2325
|
-
);
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
const taskAbortController = new AbortController();
|
|
2329
|
-
const timeoutMs = config.timeoutSeconds * MS_PER_SECOND;
|
|
2330
|
-
const timeoutId = setTimeout(() => {
|
|
2331
|
-
console.warn(`[agent] Run ${runId} exceeded timeout (${config.timeoutSeconds}s), aborting`);
|
|
2332
|
-
taskAbortController.abort(new Error(`Agent run timed out after ${config.timeoutSeconds} seconds`));
|
|
2333
|
-
}, timeoutMs);
|
|
2334
|
-
timeoutId.unref?.();
|
|
2335
|
-
let phaseResults;
|
|
2336
|
-
try {
|
|
2337
|
-
let tokensUsed;
|
|
2338
|
-
let costUsd;
|
|
2339
|
-
if (config.phases && config.phases.length > 0) {
|
|
2340
|
-
const projectRoot = resolve2(vaultDir, "..");
|
|
2341
|
-
const result = await executePhasedQuery(
|
|
2342
|
-
config,
|
|
2343
|
-
systemPrompt,
|
|
2344
|
-
vaultContext,
|
|
2345
|
-
agentId,
|
|
2346
|
-
runId,
|
|
2347
|
-
taskProviderOverride,
|
|
2348
|
-
phaseProviderOverrides,
|
|
2349
|
-
options?.instruction,
|
|
2350
|
-
options?.embeddingManager,
|
|
2351
|
-
taskAbortController,
|
|
2352
|
-
projectRoot,
|
|
2353
|
-
vaultDir
|
|
2354
|
-
);
|
|
2355
|
-
tokensUsed = result.tokensUsed;
|
|
2356
|
-
costUsd = result.costUsd;
|
|
2357
|
-
phaseResults = result.phases;
|
|
2358
|
-
const requiredPhaseNames = new Set(
|
|
2359
|
-
config.phases.filter((p) => p.required).map((p) => p.name)
|
|
2360
|
-
);
|
|
2361
|
-
const failedRequired = phaseResults.find(
|
|
2362
|
-
(p) => p.status === "failed" && requiredPhaseNames.has(p.name)
|
|
2363
|
-
);
|
|
2364
|
-
if (failedRequired) {
|
|
2365
|
-
throw new Error(
|
|
2366
|
-
`Required phase "${failedRequired.name}" failed: ${failedRequired.summary}`
|
|
2367
|
-
);
|
|
2368
|
-
}
|
|
2369
|
-
} else {
|
|
2370
|
-
const taskPrompt = composeTaskPrompt(
|
|
2371
|
-
vaultContext,
|
|
2372
|
-
config.taskDisplayName,
|
|
2373
|
-
config.taskPrompt,
|
|
2374
|
-
options?.instruction
|
|
2375
|
-
);
|
|
2376
|
-
const singleProvider = taskProviderOverride ?? config.execution?.provider;
|
|
2377
|
-
const result = await executeSingleQuery(
|
|
2378
|
-
config,
|
|
2379
|
-
systemPrompt,
|
|
2380
|
-
taskPrompt,
|
|
2381
|
-
agentId,
|
|
2382
|
-
runId,
|
|
2383
|
-
singleProvider,
|
|
2384
|
-
options?.embeddingManager,
|
|
2385
|
-
taskAbortController,
|
|
2386
|
-
vaultDir
|
|
2387
|
-
);
|
|
2388
|
-
tokensUsed = result.tokensUsed;
|
|
2389
|
-
costUsd = result.costUsd;
|
|
2390
|
-
}
|
|
2391
|
-
clearTimeout(timeoutId);
|
|
2392
|
-
const completedAt = epochSeconds();
|
|
2393
|
-
updateRunStatus(runId, STATUS_COMPLETED, {
|
|
2394
|
-
completed_at: completedAt,
|
|
2395
|
-
tokens_used: tokensUsed,
|
|
2396
|
-
cost_usd: costUsd,
|
|
2397
|
-
actions_taken: JSON.stringify({ ...runMeta, ...phaseResults ? { phases: phaseResults } : {} })
|
|
2398
|
-
});
|
|
2399
|
-
return {
|
|
2400
|
-
runId,
|
|
2401
|
-
status: STATUS_COMPLETED,
|
|
2402
|
-
tokensUsed,
|
|
2403
|
-
costUsd,
|
|
2404
|
-
...phaseResults ? { phases: phaseResults } : {}
|
|
2405
|
-
};
|
|
2406
|
-
} catch (err) {
|
|
2407
|
-
clearTimeout(timeoutId);
|
|
2408
|
-
let errorMessage2;
|
|
2409
|
-
if (err instanceof Error) {
|
|
2410
|
-
errorMessage2 = err.message || err.constructor.name || "Error (no message)";
|
|
2411
|
-
if (err.stack) errorMessage2 += `
|
|
2412
|
-
${err.stack.split("\n").slice(0, 3).join("\n")}`;
|
|
2413
|
-
} else if (typeof err === "string") {
|
|
2414
|
-
errorMessage2 = err || "Empty string error";
|
|
2415
|
-
} else {
|
|
2416
|
-
try {
|
|
2417
|
-
errorMessage2 = JSON.stringify(err);
|
|
2418
|
-
} catch {
|
|
2419
|
-
errorMessage2 = "Unserializable error";
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
const failedAt = epochSeconds();
|
|
2423
|
-
console.error(`[agent] Run ${runId} failed: ${errorMessage2}`);
|
|
2424
|
-
try {
|
|
2425
|
-
updateRunStatus(runId, STATUS_FAILED, {
|
|
2426
|
-
completed_at: failedAt,
|
|
2427
|
-
error: errorMessage2,
|
|
2428
|
-
// Preserve phase results collected before the failure
|
|
2429
|
-
actions_taken: JSON.stringify({ ...runMeta, ...phaseResults ? { phases: phaseResults } : {} })
|
|
2430
|
-
});
|
|
2431
|
-
} catch (dbErr) {
|
|
2432
|
-
console.error(`[agent] Failed to save error to DB:`, dbErr);
|
|
2433
|
-
}
|
|
2434
|
-
await cleanupOnTaskFailure({
|
|
2435
|
-
taskName: config.taskName,
|
|
2436
|
-
vaultDir,
|
|
2437
|
-
runContext: options?.runContext
|
|
2438
|
-
});
|
|
2439
|
-
return {
|
|
2440
|
-
runId,
|
|
2441
|
-
status: STATUS_FAILED,
|
|
2442
|
-
error: errorMessage2,
|
|
2443
|
-
...phaseResults ? { phases: phaseResults } : {}
|
|
2444
|
-
};
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
async function cleanupOnTaskFailure(args) {
|
|
2448
|
-
if (args.taskName !== SKILL_GENERATE_TASK) return;
|
|
2449
|
-
if (!args.vaultDir) return;
|
|
2450
|
-
const candidateId = args.runContext?.candidate_id;
|
|
2451
|
-
if (!candidateId) return;
|
|
2452
|
-
try {
|
|
2453
|
-
const { cleanupStagedSkill: cleanupStagedSkill2 } = await import("./skill-staging-SWM7UC5D.js");
|
|
2454
|
-
cleanupStagedSkill2(args.vaultDir, candidateId);
|
|
2455
|
-
console.warn(
|
|
2456
|
-
`[agent] skill-generate failed \u2014 cleaned up staging for candidate ${candidateId}`
|
|
2457
|
-
);
|
|
2458
|
-
} catch (cleanupErr) {
|
|
2459
|
-
console.warn(
|
|
2460
|
-
`[agent] Failed to clean staging for candidate ${candidateId}:`,
|
|
2461
|
-
cleanupErr instanceof Error ? cleanupErr.message : cleanupErr
|
|
2462
|
-
);
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
export {
|
|
2466
|
-
cleanupOnTaskFailure,
|
|
2467
|
-
composePhasePrompt,
|
|
2468
|
-
composeTaskPrompt,
|
|
2469
|
-
computeWaves,
|
|
2470
|
-
runAgent
|
|
2471
|
-
};
|
|
2472
|
-
//# sourceMappingURL=executor-HWW2QNZQ.js.map
|