@goondocks/myco 0.17.2 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -22
- package/bin/myco-run +15 -2
- package/dist/{agent-run-7AYHXIEF.js → agent-run-I4O2K2CK.js} +7 -7
- package/dist/{agent-tasks-UUIFKBD4.js → agent-tasks-UOW5BQIB.js} +7 -7
- package/dist/{chunk-XD3NEN3Q.js → chunk-2V7HR7HB.js} +2 -2
- package/dist/chunk-44PZCAYS.js +107 -0
- package/dist/chunk-44PZCAYS.js.map +1 -0
- package/dist/{chunk-DT42247G.js → chunk-75AZFBFW.js} +3 -3
- package/dist/{chunk-RMJPQZGF.js → chunk-C3EGL5JX.js} +755 -266
- package/dist/chunk-C3EGL5JX.js.map +1 -0
- package/dist/{chunk-7DAH5GLC.js → chunk-CKJAWZQE.js} +5 -1
- package/dist/chunk-CKJAWZQE.js.map +1 -0
- package/dist/{chunk-ML6GTPZU.js → chunk-CML4MCYF.js} +2 -2
- package/dist/{chunk-UTLWSKDV.js → chunk-CURS2TNP.js} +45 -4
- package/dist/chunk-CURS2TNP.js.map +1 -0
- package/dist/{chunk-EBIYONNZ.js → chunk-DPSLJ242.js} +34 -2
- package/dist/chunk-DPSLJ242.js.map +1 -0
- package/dist/{chunk-BZDZORVP.js → chunk-LSP5HYOO.js} +19 -16
- package/dist/chunk-LSP5HYOO.js.map +1 -0
- package/dist/{chunk-NUSTG3BH.js → chunk-N75GMQGA.js} +3 -3
- package/dist/{chunk-F6C4IC6R.js → chunk-NI23QCHB.js} +3 -3
- package/dist/{chunk-C3C5QVLK.js → chunk-O3TRN3RC.js} +2 -2
- package/dist/{chunk-3NCVCGUZ.js → chunk-RAV5YMRU.js} +3 -3
- package/dist/{chunk-25WHTV4N.js → chunk-RIDSOQDR.js} +21 -7
- package/dist/chunk-RIDSOQDR.js.map +1 -0
- package/dist/{chunk-HPZ7YAMA.js → chunk-TCSVDQF5.js} +1130 -195
- package/dist/chunk-TCSVDQF5.js.map +1 -0
- package/dist/{chunk-CTF7TQMJ.js → chunk-TLK46KKD.js} +14 -4
- package/dist/chunk-TLK46KKD.js.map +1 -0
- package/dist/{chunk-IGBHLFV5.js → chunk-TOER6RNC.js} +22 -2
- package/dist/chunk-TOER6RNC.js.map +1 -0
- package/dist/{chunk-ZSJPI5MS.js → chunk-TZAXQKO6.js} +6 -2
- package/dist/chunk-TZAXQKO6.js.map +1 -0
- package/dist/{chunk-RKPTMHED.js → chunk-U3J2DDSR.js} +2 -2
- package/dist/{chunk-SI5BBQAT.js → chunk-W7WENJ6F.js} +2 -2
- package/dist/{chunk-VVGZL2HX.js → chunk-WYOE4IAX.js} +153 -15
- package/dist/{chunk-VVGZL2HX.js.map → chunk-WYOE4IAX.js.map} +1 -1
- package/dist/{chunk-XZWFMMJR.js → chunk-XWOQL4XN.js} +3 -3
- package/dist/{chunk-5ZISXCDC.js → chunk-YZPI2Y3E.js} +39 -5
- package/dist/chunk-YZPI2Y3E.js.map +1 -0
- package/dist/{cli-WJVYP2QT.js → cli-D3TJYJ2U.js} +40 -40
- package/dist/{client-LZ3ZR4HC.js → client-4LLEXLVK.js} +4 -4
- package/dist/{config-ZQIMG3FB.js → config-DA4IUVFL.js} +3 -3
- package/dist/{detect-NJ2OREDP.js → detect-SZ2KDUF4.js} +2 -2
- package/dist/{detect-providers-C64L3QET.js → detect-providers-PSVKXTWE.js} +4 -4
- package/dist/{doctor-XEPBNHM3.js → doctor-KCTXPX5D.js} +12 -12
- package/dist/{executor-NXKJU5KW.js → executor-UYIZC3L5.js} +93 -285
- package/dist/executor-UYIZC3L5.js.map +1 -0
- package/dist/{init-BHVQAQ27.js → init-QFNBKKDC.js} +13 -13
- package/dist/{installer-45ZLP2RP.js → installer-BWJED3ED.js} +2 -2
- package/dist/{llm-KTD6SR55.js → llm-SMA5ZEAW.js} +4 -4
- package/dist/{loader-SHRKUKOS.js → loader-Q3P3R4UP.js} +3 -3
- package/dist/{loader-NEX3UF6U.js → loader-SKKUMT5C.js} +3 -3
- package/dist/{main-YFVBIRRK.js → main-5THODR77.js} +751 -257
- package/dist/main-5THODR77.js.map +1 -0
- package/dist/{open-2U7ZRGA3.js → open-7737CSPN.js} +7 -7
- package/dist/{post-compact-QIBMEWL3.js → post-compact-2TJ5FPZH.js} +7 -7
- package/dist/{post-tool-use-ICGFXDVY.js → post-tool-use-FRTSICC3.js} +6 -6
- package/dist/{post-tool-use-failure-C7TLH3XQ.js → post-tool-use-failure-KYO2NCNB.js} +7 -7
- package/dist/{pre-compact-IF7K4TQK.js → pre-compact-J6GCJEJR.js} +7 -7
- package/dist/{provider-check-LTLQ6BUZ.js → provider-check-AE3L5Z6R.js} +4 -4
- package/dist/{registry-TFQ22Z7N.js → registry-O2NZLO3V.js} +4 -4
- package/dist/{remove-FBGM2QVJ.js → remove-3WZZC7AX.js} +9 -9
- package/dist/{resolution-events-HGKIJOTA.js → resolution-events-XWYLLDRK.js} +4 -4
- package/dist/{restart-TQEECRNW.js → restart-HUHEFOXU.js} +8 -8
- package/dist/{search-NN5FC4Z6.js → search-ZGN3LDXG.js} +8 -8
- package/dist/{server-XMWJ4GF7.js → server-PTXLVVEE.js} +4 -4
- package/dist/{session-GLPAFYPO.js → session-7VV3IQMO.js} +9 -9
- package/dist/{session-end-TI3ILRBC.js → session-end-SMU55UCM.js} +6 -6
- package/dist/{session-start-PJLJDVJJ.js → session-start-NIMWEOIZ.js} +29 -13
- package/dist/session-start-NIMWEOIZ.js.map +1 -0
- package/dist/{setup-llm-AQSWLXCZ.js → setup-llm-7S3VPAPN.js} +8 -8
- package/dist/src/agent/definitions/tasks/extract-only.yaml +1 -1
- package/dist/src/agent/definitions/tasks/full-intelligence.yaml +10 -0
- package/dist/src/agent/definitions/tasks/skill-evolve.yaml +163 -49
- package/dist/src/agent/definitions/tasks/skill-generate.yaml +44 -27
- package/dist/src/agent/definitions/tasks/skill-survey.yaml +132 -138
- package/dist/src/agent/definitions/tasks/supersession-sweep.yaml +1 -1
- 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/codex.yaml +85 -0
- package/dist/src/symbionts/templates/claude-code/hooks.json +12 -12
- package/dist/src/symbionts/templates/claude-code/settings.json +3 -3
- package/dist/src/symbionts/templates/codex/hooks.json +4 -4
- package/dist/src/symbionts/templates/cursor/hooks.json +9 -9
- package/dist/src/symbionts/templates/cursor/settings.json +2 -2
- package/dist/src/symbionts/templates/gemini/hooks.json +6 -6
- package/dist/src/symbionts/templates/gemini/settings.json +2 -2
- package/dist/src/symbionts/templates/myco-run.cjs +44 -0
- package/dist/src/symbionts/templates/opencode/settings.json +2 -2
- package/dist/src/symbionts/templates/vscode-copilot/hooks.json +7 -7
- package/dist/src/symbionts/templates/vscode-copilot/settings.json +2 -2
- package/dist/src/symbionts/templates/windsurf/hooks.json +4 -4
- package/dist/src/symbionts/templates/windsurf/settings.json +2 -2
- package/dist/src/worker/package-lock.json +4338 -0
- package/dist/src/worker/package.json +5 -0
- package/dist/src/worker/src/index.ts +58 -65
- package/dist/src/worker/src/mcp/auth.ts +65 -0
- package/dist/src/worker/src/mcp/server.ts +53 -0
- package/dist/src/worker/src/mcp/tools/context.ts +13 -0
- package/dist/src/worker/src/mcp/tools/get.ts +15 -0
- package/dist/src/worker/src/mcp/tools/graph.ts +35 -0
- package/dist/src/worker/src/mcp/tools/search.ts +32 -0
- package/dist/src/worker/src/mcp/tools/sessions.ts +24 -0
- package/dist/src/worker/src/mcp/tools/skills.ts +16 -0
- package/dist/src/worker/src/mcp/tools/team.ts +9 -0
- package/dist/src/worker/src/schema.ts +5 -1
- package/dist/src/worker/src/search-helpers.ts +70 -0
- package/dist/src/worker/wrangler.toml +9 -0
- package/dist/{stats-BISBIBXZ.js → stats-GEOQ2DFF.js} +9 -9
- package/dist/{stop-47BJ42EO.js → stop-7AKYBJJ2.js} +6 -6
- package/dist/{stop-failure-VU5BTLWX.js → stop-failure-NLE2EURG.js} +7 -7
- package/dist/{subagent-start-SPTKQRHU.js → subagent-start-LBNZF2TG.js} +7 -7
- package/dist/{subagent-stop-UU75BYLC.js → subagent-stop-B2Z5GYAB.js} +7 -7
- package/dist/{task-completed-MVDO7TZF.js → task-completed-PO5TETJ7.js} +7 -7
- package/dist/{team-7X64J4Y6.js → team-DPNP2RN7.js} +97 -14
- package/dist/team-DPNP2RN7.js.map +1 -0
- package/dist/ui/assets/{index-rpmSpJpm.js → index-CiI1fwas.js} +120 -120
- package/dist/ui/index.html +1 -1
- package/dist/{update-DA7VEXOS.js → update-WBWB5URU.js} +18 -9
- package/dist/update-WBWB5URU.js.map +1 -0
- package/dist/{user-prompt-submit-ADZ4NTVO.js → user-prompt-submit-IZJC3NV7.js} +30 -7
- package/dist/user-prompt-submit-IZJC3NV7.js.map +1 -0
- package/dist/{verify-QYSERHF7.js → verify-FNSP62I3.js} +5 -5
- package/dist/{version-A72TAL2J.js → version-QEVU66NT.js} +2 -2
- package/package.json +7 -7
- package/dist/chunk-25WHTV4N.js.map +0 -1
- package/dist/chunk-5ZISXCDC.js.map +0 -1
- package/dist/chunk-7DAH5GLC.js.map +0 -1
- package/dist/chunk-BZDZORVP.js.map +0 -1
- package/dist/chunk-CTF7TQMJ.js.map +0 -1
- package/dist/chunk-EBIYONNZ.js.map +0 -1
- package/dist/chunk-HPZ7YAMA.js.map +0 -1
- package/dist/chunk-IGBHLFV5.js.map +0 -1
- package/dist/chunk-RMJPQZGF.js.map +0 -1
- package/dist/chunk-UTLWSKDV.js.map +0 -1
- package/dist/chunk-ZSJPI5MS.js.map +0 -1
- package/dist/executor-NXKJU5KW.js.map +0 -1
- package/dist/main-YFVBIRRK.js.map +0 -1
- package/dist/session-start-PJLJDVJJ.js.map +0 -1
- package/dist/src/symbionts/templates/hook-guard.cjs +0 -19
- package/dist/team-7X64J4Y6.js.map +0 -1
- package/dist/update-DA7VEXOS.js.map +0 -1
- package/dist/user-prompt-submit-ADZ4NTVO.js.map +0 -1
- /package/dist/{agent-run-7AYHXIEF.js.map → agent-run-I4O2K2CK.js.map} +0 -0
- /package/dist/{agent-tasks-UUIFKBD4.js.map → agent-tasks-UOW5BQIB.js.map} +0 -0
- /package/dist/{chunk-XD3NEN3Q.js.map → chunk-2V7HR7HB.js.map} +0 -0
- /package/dist/{chunk-DT42247G.js.map → chunk-75AZFBFW.js.map} +0 -0
- /package/dist/{chunk-ML6GTPZU.js.map → chunk-CML4MCYF.js.map} +0 -0
- /package/dist/{chunk-NUSTG3BH.js.map → chunk-N75GMQGA.js.map} +0 -0
- /package/dist/{chunk-F6C4IC6R.js.map → chunk-NI23QCHB.js.map} +0 -0
- /package/dist/{chunk-C3C5QVLK.js.map → chunk-O3TRN3RC.js.map} +0 -0
- /package/dist/{chunk-3NCVCGUZ.js.map → chunk-RAV5YMRU.js.map} +0 -0
- /package/dist/{chunk-RKPTMHED.js.map → chunk-U3J2DDSR.js.map} +0 -0
- /package/dist/{chunk-SI5BBQAT.js.map → chunk-W7WENJ6F.js.map} +0 -0
- /package/dist/{chunk-XZWFMMJR.js.map → chunk-XWOQL4XN.js.map} +0 -0
- /package/dist/{cli-WJVYP2QT.js.map → cli-D3TJYJ2U.js.map} +0 -0
- /package/dist/{client-LZ3ZR4HC.js.map → client-4LLEXLVK.js.map} +0 -0
- /package/dist/{config-ZQIMG3FB.js.map → config-DA4IUVFL.js.map} +0 -0
- /package/dist/{detect-NJ2OREDP.js.map → detect-SZ2KDUF4.js.map} +0 -0
- /package/dist/{detect-providers-C64L3QET.js.map → detect-providers-PSVKXTWE.js.map} +0 -0
- /package/dist/{doctor-XEPBNHM3.js.map → doctor-KCTXPX5D.js.map} +0 -0
- /package/dist/{init-BHVQAQ27.js.map → init-QFNBKKDC.js.map} +0 -0
- /package/dist/{installer-45ZLP2RP.js.map → installer-BWJED3ED.js.map} +0 -0
- /package/dist/{llm-KTD6SR55.js.map → llm-SMA5ZEAW.js.map} +0 -0
- /package/dist/{loader-NEX3UF6U.js.map → loader-Q3P3R4UP.js.map} +0 -0
- /package/dist/{loader-SHRKUKOS.js.map → loader-SKKUMT5C.js.map} +0 -0
- /package/dist/{open-2U7ZRGA3.js.map → open-7737CSPN.js.map} +0 -0
- /package/dist/{post-compact-QIBMEWL3.js.map → post-compact-2TJ5FPZH.js.map} +0 -0
- /package/dist/{post-tool-use-ICGFXDVY.js.map → post-tool-use-FRTSICC3.js.map} +0 -0
- /package/dist/{post-tool-use-failure-C7TLH3XQ.js.map → post-tool-use-failure-KYO2NCNB.js.map} +0 -0
- /package/dist/{pre-compact-IF7K4TQK.js.map → pre-compact-J6GCJEJR.js.map} +0 -0
- /package/dist/{provider-check-LTLQ6BUZ.js.map → provider-check-AE3L5Z6R.js.map} +0 -0
- /package/dist/{registry-TFQ22Z7N.js.map → registry-O2NZLO3V.js.map} +0 -0
- /package/dist/{remove-FBGM2QVJ.js.map → remove-3WZZC7AX.js.map} +0 -0
- /package/dist/{resolution-events-HGKIJOTA.js.map → resolution-events-XWYLLDRK.js.map} +0 -0
- /package/dist/{restart-TQEECRNW.js.map → restart-HUHEFOXU.js.map} +0 -0
- /package/dist/{search-NN5FC4Z6.js.map → search-ZGN3LDXG.js.map} +0 -0
- /package/dist/{server-XMWJ4GF7.js.map → server-PTXLVVEE.js.map} +0 -0
- /package/dist/{session-GLPAFYPO.js.map → session-7VV3IQMO.js.map} +0 -0
- /package/dist/{session-end-TI3ILRBC.js.map → session-end-SMU55UCM.js.map} +0 -0
- /package/dist/{setup-llm-AQSWLXCZ.js.map → setup-llm-7S3VPAPN.js.map} +0 -0
- /package/dist/{stats-BISBIBXZ.js.map → stats-GEOQ2DFF.js.map} +0 -0
- /package/dist/{stop-47BJ42EO.js.map → stop-7AKYBJJ2.js.map} +0 -0
- /package/dist/{stop-failure-VU5BTLWX.js.map → stop-failure-NLE2EURG.js.map} +0 -0
- /package/dist/{subagent-start-SPTKQRHU.js.map → subagent-start-LBNZF2TG.js.map} +0 -0
- /package/dist/{subagent-stop-UU75BYLC.js.map → subagent-stop-B2Z5GYAB.js.map} +0 -0
- /package/dist/{task-completed-MVDO7TZF.js.map → task-completed-PO5TETJ7.js.map} +0 -0
- /package/dist/{verify-QYSERHF7.js.map → verify-FNSP62I3.js.map} +0 -0
- /package/dist/{version-A72TAL2J.js.map → version-QEVU66NT.js.map} +0 -0
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
import { initD1Schema } from './schema';
|
|
10
10
|
import { validateAuth } from './auth';
|
|
11
|
+
import { createMcpHandler } from 'agents/mcp';
|
|
12
|
+
import { createMcpServerInstance } from './mcp/server';
|
|
13
|
+
import { authenticateMcpRequest, ensureMcpToken, rotateMcpToken, getMcpTokenHash, MCP_TOKEN_KEY } from './mcp/auth';
|
|
14
|
+
import { embedText, hydrateVectorMatches } from './search-helpers';
|
|
11
15
|
|
|
12
16
|
// ---------------------------------------------------------------------------
|
|
13
17
|
// Types
|
|
@@ -19,6 +23,7 @@ export interface Env {
|
|
|
19
23
|
AI: Ai;
|
|
20
24
|
MYCO_TEAM_API_KEY: string;
|
|
21
25
|
SYNC_PROTOCOL_VERSION: string;
|
|
26
|
+
MYCO_SECRETS: KVNamespace;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
/** Tables that support embedding in Vectorize. */
|
|
@@ -27,6 +32,7 @@ const EMBEDDABLE_TABLES: Record<string, string> = {
|
|
|
27
32
|
sessions: 'summary',
|
|
28
33
|
plans: 'content',
|
|
29
34
|
artifacts: 'content',
|
|
35
|
+
skill_records: 'description',
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
/** All tables the sync endpoint accepts records for. */
|
|
@@ -112,13 +118,6 @@ function vectorId(table: string, id: string, machineId: string): string {
|
|
|
112
118
|
return `${table}:${id}:${machineId}`;
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
/**
|
|
116
|
-
* Embed text via Workers AI (bge-m3) and return the vector.
|
|
117
|
-
*/
|
|
118
|
-
async function embedText(ai: Ai, text: string): Promise<number[]> {
|
|
119
|
-
const result = await ai.run('@cf/baai/bge-m3', { text: [text] });
|
|
120
|
-
return result.data[0];
|
|
121
|
-
}
|
|
122
121
|
|
|
123
122
|
/**
|
|
124
123
|
* Build column names and placeholders for an INSERT OR REPLACE from a data object.
|
|
@@ -150,10 +149,15 @@ function buildInsertParts(
|
|
|
150
149
|
// ---------------------------------------------------------------------------
|
|
151
150
|
|
|
152
151
|
async function handleHealth(env: Env): Promise<Response> {
|
|
153
|
-
const
|
|
154
|
-
count: number
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
const [countResult, storedToken] = await Promise.all([
|
|
153
|
+
env.MYCO_TEAM_DB.prepare('SELECT COUNT(*) as count FROM nodes').first<{ count: number }>(),
|
|
154
|
+
env.MYCO_SECRETS.get(MCP_TOKEN_KEY),
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
const count = countResult?.count ?? 0;
|
|
158
|
+
const mcpTokenHash = storedToken ? getMcpTokenHash(storedToken) : null;
|
|
159
|
+
|
|
160
|
+
return jsonResponse({ status: 'ok', nodes: count, mcp_token_hash: mcpTokenHash });
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
async function handleConnect(request: Request, env: Env): Promise<Response> {
|
|
@@ -191,10 +195,15 @@ async function handleConnect(request: Request, env: Env): Promise<Response> {
|
|
|
191
195
|
config[row.key] = row.value;
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
// MCP token is stored in KV (encrypted at rest), not in team_config
|
|
199
|
+
const mcpToken = await ensureMcpToken(env.MYCO_SECRETS);
|
|
200
|
+
|
|
194
201
|
return jsonResponse({
|
|
195
202
|
status: 'connected',
|
|
196
203
|
sync_protocol_version: parseInt(env.SYNC_PROTOCOL_VERSION, 10),
|
|
197
204
|
config,
|
|
205
|
+
mcp_token: mcpToken,
|
|
206
|
+
mcp_endpoint: '/mcp',
|
|
198
207
|
});
|
|
199
208
|
}
|
|
200
209
|
|
|
@@ -261,7 +270,11 @@ async function handleSync(request: Request, env: Env): Promise<Response> {
|
|
|
261
270
|
if (table === 'spores' && record.data.status === 'superseded') {
|
|
262
271
|
embeddingTasks.push(() => deleteVector(env, table, id, machine_id));
|
|
263
272
|
} else {
|
|
264
|
-
|
|
273
|
+
// Include domain-specific metadata for richer search results
|
|
274
|
+
const extra: Record<string, string> = {};
|
|
275
|
+
if (table === 'skill_records' && record.data.name) extra.name = record.data.name as string;
|
|
276
|
+
if (table === 'spores' && record.data.observation_type) extra.observation_type = record.data.observation_type as string;
|
|
277
|
+
embeddingTasks.push(() => embedAndUpsert(env, table, id, machine_id, textContent, extra));
|
|
265
278
|
}
|
|
266
279
|
}
|
|
267
280
|
}
|
|
@@ -303,6 +316,7 @@ async function embedAndUpsert(
|
|
|
303
316
|
id: string,
|
|
304
317
|
machineId: string,
|
|
305
318
|
text: string,
|
|
319
|
+
extra?: Record<string, string>,
|
|
306
320
|
): Promise<void> {
|
|
307
321
|
const vector = await embedText(env.AI, text);
|
|
308
322
|
const vid = vectorId(table, id, machineId);
|
|
@@ -310,7 +324,7 @@ async function embedAndUpsert(
|
|
|
310
324
|
{
|
|
311
325
|
id: vid,
|
|
312
326
|
values: vector,
|
|
313
|
-
metadata: { table, id, machine_id: machineId },
|
|
327
|
+
metadata: { table, id, machine_id: machineId, ...extra },
|
|
314
328
|
},
|
|
315
329
|
]);
|
|
316
330
|
}
|
|
@@ -333,61 +347,16 @@ async function handleSearch(request: Request, env: Env): Promise<Response> {
|
|
|
333
347
|
|
|
334
348
|
const topK = Math.min(parseInt(url.searchParams.get('top_k') ?? String(DEFAULT_TOP_K), 10), MAX_TOP_K);
|
|
335
349
|
|
|
336
|
-
// Embed query
|
|
337
350
|
const queryVector = await embedText(env.AI, query);
|
|
351
|
+
const matches = await env.MYCO_TEAM_VECTORS.query(queryVector, { topK, returnMetadata: 'all' });
|
|
338
352
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
returnMetadata: 'all',
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// Group matches by table for batch hydration
|
|
346
|
-
const byTable = new Map<string, { id: string; machine_id: string; score: number }[]>();
|
|
347
|
-
for (const match of matches.matches) {
|
|
348
|
-
const meta = match.metadata as { table: string; id: string; machine_id: string } | undefined;
|
|
349
|
-
if (!meta) continue;
|
|
350
|
-
let group = byTable.get(meta.table);
|
|
351
|
-
if (!group) {
|
|
352
|
-
group = [];
|
|
353
|
-
byTable.set(meta.table, group);
|
|
354
|
-
}
|
|
355
|
-
group.push({ id: meta.id, machine_id: meta.machine_id, score: match.score });
|
|
356
|
-
}
|
|
353
|
+
const validMatches = matches.matches
|
|
354
|
+
.filter((m) => m.metadata)
|
|
355
|
+
.map((m) => ({ metadata: m.metadata as { table: string; id: string; machine_id: string }, score: m.score }));
|
|
357
356
|
|
|
358
|
-
|
|
359
|
-
const results: SearchResult[] = [];
|
|
360
|
-
for (const [table, items] of byTable) {
|
|
361
|
-
const placeholders = items.map(() => '(?, ?)').join(', ');
|
|
362
|
-
const binds = items.flatMap((i) => [i.id, i.machine_id]);
|
|
363
|
-
const { results: rows } = await env.MYCO_TEAM_DB.prepare(
|
|
364
|
-
`SELECT * FROM ${table} WHERE (id, machine_id) IN (VALUES ${placeholders})`,
|
|
365
|
-
).bind(...binds).all();
|
|
366
|
-
|
|
367
|
-
const rowMap = new Map<string, Record<string, unknown>>();
|
|
368
|
-
for (const row of rows) {
|
|
369
|
-
const r = row as Record<string, unknown>;
|
|
370
|
-
rowMap.set(`${r.id}:${r.machine_id}`, r);
|
|
371
|
-
}
|
|
357
|
+
const results = await hydrateVectorMatches(env.MYCO_TEAM_DB, validMatches);
|
|
372
358
|
|
|
373
|
-
|
|
374
|
-
const row = rowMap.get(`${item.id}:${item.machine_id}`);
|
|
375
|
-
if (row) {
|
|
376
|
-
results.push({
|
|
377
|
-
table,
|
|
378
|
-
id: item.id,
|
|
379
|
-
machine_id: item.machine_id,
|
|
380
|
-
score: item.score,
|
|
381
|
-
data: row,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Sort by score to preserve ranking after batch hydration
|
|
388
|
-
results.sort((a, b) => b.score - a.score);
|
|
389
|
-
|
|
390
|
-
return jsonResponse({ results });
|
|
359
|
+
return jsonResponse({ results: results.map((r) => ({ table: r.type, ...r })) });
|
|
391
360
|
}
|
|
392
361
|
|
|
393
362
|
async function handleGetConfig(env: Env): Promise<Response> {
|
|
@@ -429,7 +398,7 @@ async function handlePutConfig(request: Request, env: Env): Promise<Response> {
|
|
|
429
398
|
// ---------------------------------------------------------------------------
|
|
430
399
|
|
|
431
400
|
export default {
|
|
432
|
-
async fetch(request: Request, env: Env): Promise<Response> {
|
|
401
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
433
402
|
const url = new URL(request.url);
|
|
434
403
|
const path = url.pathname;
|
|
435
404
|
const method = request.method;
|
|
@@ -448,6 +417,30 @@ export default {
|
|
|
448
417
|
}
|
|
449
418
|
}
|
|
450
419
|
|
|
420
|
+
// MCP routes — separate auth from sync routes
|
|
421
|
+
if (path.startsWith('/mcp')) {
|
|
422
|
+
if (!schemaInitialized) {
|
|
423
|
+
await initD1Schema(env.MYCO_TEAM_DB);
|
|
424
|
+
schemaInitialized = true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Token rotation — authenticated with team API key
|
|
428
|
+
if (path === '/mcp/rotate' && method === 'POST') {
|
|
429
|
+
const rotateAuthError = validateAuth(request, env);
|
|
430
|
+
if (rotateAuthError) return rotateAuthError;
|
|
431
|
+
const newToken = await rotateMcpToken(env.MYCO_SECRETS);
|
|
432
|
+
return jsonResponse({ token: newToken });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// MCP protocol — authenticated with MCP access token
|
|
436
|
+
const mcpAuthError = await authenticateMcpRequest(request, env.MYCO_SECRETS);
|
|
437
|
+
if (mcpAuthError) return mcpAuthError;
|
|
438
|
+
|
|
439
|
+
const server = createMcpServerInstance(env);
|
|
440
|
+
const handler = createMcpHandler(server);
|
|
441
|
+
return handler(request, env, ctx);
|
|
442
|
+
}
|
|
443
|
+
|
|
451
444
|
// All other routes require auth
|
|
452
445
|
const authError = validateAuth(request, env);
|
|
453
446
|
if (authError) return authError;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP access token auth for the Myco cloud MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Tokens are stored in Workers KV (MYCO_SECRETS namespace) under the key 'mcp_access_token'.
|
|
5
|
+
* KV provides AES-256-GCM encryption at rest — the Cloudflare-native approach for
|
|
6
|
+
* runtime-managed secrets. This is separate from the worker's MYCO_TEAM_API_KEY auth
|
|
7
|
+
* used for sync routes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const MCP_TOKEN_KEY = 'mcp_access_token';
|
|
11
|
+
|
|
12
|
+
export function generateMcpToken(): string {
|
|
13
|
+
return crypto.randomUUID();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getMcpTokenHash(token: string): string {
|
|
17
|
+
// Non-cryptographic hash for change detection, returns 8 hex chars
|
|
18
|
+
let hash = 0;
|
|
19
|
+
for (let i = 0; i < token.length; i++) {
|
|
20
|
+
hash = ((hash << 5) - hash + token.charCodeAt(i)) | 0;
|
|
21
|
+
}
|
|
22
|
+
return Math.abs(hash).toString(16).padStart(8, '0').slice(0, 8);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function validateMcpToken(kv: KVNamespace, token: string): Promise<boolean> {
|
|
26
|
+
const stored = await kv.get(MCP_TOKEN_KEY);
|
|
27
|
+
if (!stored) return false;
|
|
28
|
+
return stored === token;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function ensureMcpToken(kv: KVNamespace): Promise<string> {
|
|
32
|
+
const existing = await kv.get(MCP_TOKEN_KEY);
|
|
33
|
+
if (existing) return existing;
|
|
34
|
+
const token = generateMcpToken();
|
|
35
|
+
await kv.put(MCP_TOKEN_KEY, token);
|
|
36
|
+
return token;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function rotateMcpToken(kv: KVNamespace): Promise<string> {
|
|
40
|
+
const token = generateMcpToken();
|
|
41
|
+
await kv.put(MCP_TOKEN_KEY, token);
|
|
42
|
+
return token;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function authenticateMcpRequest(
|
|
46
|
+
request: Request,
|
|
47
|
+
kv: KVNamespace,
|
|
48
|
+
): Promise<Response | null> {
|
|
49
|
+
const header = request.headers.get('Authorization');
|
|
50
|
+
if (!header) {
|
|
51
|
+
return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
|
|
52
|
+
status: 401,
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const token = header.startsWith('Bearer ') ? header.slice(7) : '';
|
|
57
|
+
const valid = await validateMcpToken(kv, token);
|
|
58
|
+
if (!valid) {
|
|
59
|
+
return new Response(JSON.stringify({ error: 'Invalid MCP access token' }), {
|
|
60
|
+
status: 401,
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { Env } from '../index';
|
|
4
|
+
import { handleSearch } from './tools/search';
|
|
5
|
+
import { handleContext } from './tools/context';
|
|
6
|
+
import { handleGet } from './tools/get';
|
|
7
|
+
import { handleSessions } from './tools/sessions';
|
|
8
|
+
import { handleGraph } from './tools/graph';
|
|
9
|
+
import { handleSkills } from './tools/skills';
|
|
10
|
+
import { handleTeam } from './tools/team';
|
|
11
|
+
|
|
12
|
+
export function createMcpServerInstance(env: Env): McpServer {
|
|
13
|
+
const server = new McpServer({ name: 'myco', version: '1.0.0' });
|
|
14
|
+
|
|
15
|
+
server.tool('myco_search', 'Semantic and keyword search across all project knowledge — spores, sessions, plans, artifacts. Returns ranked results with content previews.', {
|
|
16
|
+
query: z.string().describe('The search query'),
|
|
17
|
+
types: z.array(z.string()).optional().describe('Filter to content types: spores, sessions, plans, artifacts'),
|
|
18
|
+
limit: z.number().min(1).max(50).default(10).describe('Maximum results'),
|
|
19
|
+
}, async (args) => handleSearch(args, env));
|
|
20
|
+
|
|
21
|
+
server.tool('myco_context', 'Pre-synthesized project digest. Start here to understand the project. Three tiers: 1500 (executive), 5000 (deep onboarding), 10000 (comprehensive).', {
|
|
22
|
+
tier: z.number().optional().describe('Digest depth: 1500, 5000 (default), or 10000'),
|
|
23
|
+
}, async (args) => handleContext(args, env));
|
|
24
|
+
|
|
25
|
+
server.tool('myco_get', 'Retrieve a specific item by ID and type. Use after search to get full details.', {
|
|
26
|
+
id: z.string().describe('The item ID'),
|
|
27
|
+
type: z.enum(['session', 'spore', 'plan', 'artifact', 'skill']).describe('The item type'),
|
|
28
|
+
}, async (args) => handleGet(args, env));
|
|
29
|
+
|
|
30
|
+
server.tool('myco_sessions', 'List and filter coding sessions. Useful for recent activity, work by branch or agent.', {
|
|
31
|
+
limit: z.number().min(1).max(100).default(20).optional().describe('Maximum sessions'),
|
|
32
|
+
status: z.string().optional().describe('Filter: active, completed'),
|
|
33
|
+
agent: z.string().optional().describe('Filter: claude, codex, cursor, etc.'),
|
|
34
|
+
branch: z.string().optional().describe('Filter by git branch'),
|
|
35
|
+
since: z.string().optional().describe('ISO date — sessions after this date'),
|
|
36
|
+
}, async (args) => handleSessions(args, env));
|
|
37
|
+
|
|
38
|
+
server.tool('myco_graph', 'Traverse the knowledge graph from an entity or note. Returns edges and connected entities.', {
|
|
39
|
+
node_id: z.string().describe('Entity or note ID to traverse from'),
|
|
40
|
+
direction: z.enum(['incoming', 'outgoing', 'both']).default('both').optional().describe('Edge direction'),
|
|
41
|
+
}, async (args) => handleGraph(args, env));
|
|
42
|
+
|
|
43
|
+
server.tool('myco_skills', 'List project skills — reusable patterns extracted from project knowledge.', {
|
|
44
|
+
status: z.string().optional().describe('Filter: active, draft, retired'),
|
|
45
|
+
limit: z.number().min(1).max(100).default(50).optional().describe('Maximum skills'),
|
|
46
|
+
}, async (args) => handleSkills(args, env));
|
|
47
|
+
|
|
48
|
+
// handleTeam takes only env (no args) since it has no parameters
|
|
49
|
+
server.tool('myco_team', 'List team nodes — machines/developers connected to this project with sync status.', {},
|
|
50
|
+
async () => handleTeam(env));
|
|
51
|
+
|
|
52
|
+
return server;
|
|
53
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
|
|
3
|
+
export async function handleContext(args: { tier?: number }, env: Pick<Env, 'MYCO_TEAM_DB'>) {
|
|
4
|
+
const tier = args.tier ?? 5000;
|
|
5
|
+
const row = await env.MYCO_TEAM_DB.prepare(
|
|
6
|
+
`SELECT id, tier, content, generated_at FROM digest_extracts WHERE tier = ? ORDER BY generated_at DESC LIMIT 1`,
|
|
7
|
+
).bind(tier).first<{ id: string; tier: number; content: string; generated_at: number }>();
|
|
8
|
+
|
|
9
|
+
if (!row) {
|
|
10
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ content: null, tier, message: `No digest available at tier ${tier}` }) }] };
|
|
11
|
+
}
|
|
12
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ content: row.content, tier: row.tier, generated_at: row.generated_at }) }] };
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
|
|
3
|
+
const TYPE_TO_TABLE: Record<string, string> = {
|
|
4
|
+
session: 'sessions', spore: 'spores', plan: 'plans', artifact: 'artifacts', skill: 'skill_records',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export async function handleGet(args: { id: string; type: string }, env: Pick<Env, 'MYCO_TEAM_DB'>) {
|
|
8
|
+
const table = TYPE_TO_TABLE[args.type];
|
|
9
|
+
if (!table) return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown type: ${args.type}` }) }] };
|
|
10
|
+
|
|
11
|
+
const row = await env.MYCO_TEAM_DB.prepare(`SELECT * FROM ${table} WHERE id = ? LIMIT 1`).bind(args.id).first<Record<string, unknown>>();
|
|
12
|
+
if (!row) return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `${args.type} '${args.id}' not found` }) }] };
|
|
13
|
+
|
|
14
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(row) }] };
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
|
|
3
|
+
export async function handleGraph(
|
|
4
|
+
args: { node_id: string; direction?: string },
|
|
5
|
+
env: Pick<Env, 'MYCO_TEAM_DB'>,
|
|
6
|
+
) {
|
|
7
|
+
const { node_id, direction = 'both' } = args;
|
|
8
|
+
let edgeCondition: string;
|
|
9
|
+
const edgeBinds: string[] = [];
|
|
10
|
+
|
|
11
|
+
if (direction === 'outgoing') { edgeCondition = 'source_id = ?'; edgeBinds.push(node_id); }
|
|
12
|
+
else if (direction === 'incoming') { edgeCondition = 'target_id = ?'; edgeBinds.push(node_id); }
|
|
13
|
+
else { edgeCondition = '(source_id = ? OR target_id = ?)'; edgeBinds.push(node_id, node_id); }
|
|
14
|
+
|
|
15
|
+
const { results: edges } = await env.MYCO_TEAM_DB.prepare(
|
|
16
|
+
`SELECT id, source_id, source_type, target_id, target_type, type, confidence, properties FROM graph_edges WHERE ${edgeCondition} LIMIT 100`,
|
|
17
|
+
).bind(...edgeBinds).all();
|
|
18
|
+
|
|
19
|
+
const entityIds = new Set<string>();
|
|
20
|
+
for (const edge of edges as Array<Record<string, unknown>>) {
|
|
21
|
+
if (edge.source_type === 'entity') entityIds.add(edge.source_id as string);
|
|
22
|
+
if (edge.target_type === 'entity') entityIds.add(edge.target_id as string);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let entities: Record<string, unknown>[] = [];
|
|
26
|
+
if (entityIds.size > 0) {
|
|
27
|
+
const placeholders = [...entityIds].map(() => '?').join(', ');
|
|
28
|
+
const { results } = await env.MYCO_TEAM_DB.prepare(
|
|
29
|
+
`SELECT id, type, name, properties, first_seen, last_seen FROM entities WHERE id IN (${placeholders})`,
|
|
30
|
+
).bind(...entityIds).all();
|
|
31
|
+
entities = results as Record<string, unknown>[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ node_id, edges, entities }) }] };
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
import { embedText, hydrateVectorMatches } from '../../search-helpers';
|
|
3
|
+
|
|
4
|
+
interface SearchArgs {
|
|
5
|
+
query: string;
|
|
6
|
+
types?: string[];
|
|
7
|
+
limit?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function handleSearch(args: SearchArgs, env: Pick<Env, 'MYCO_TEAM_DB' | 'MYCO_TEAM_VECTORS' | 'AI'>) {
|
|
11
|
+
const { query, types, limit = 10 } = args;
|
|
12
|
+
const topK = Math.min(Math.max(limit, 1), 50);
|
|
13
|
+
|
|
14
|
+
const queryVector = await embedText(env.AI, query);
|
|
15
|
+
const matches = await env.MYCO_TEAM_VECTORS.query(queryVector, { topK, returnMetadata: 'all' });
|
|
16
|
+
|
|
17
|
+
// Filter by types if specified, then cast metadata
|
|
18
|
+
let filtered = matches.matches.filter((m) => m.metadata);
|
|
19
|
+
if (types && types.length > 0) {
|
|
20
|
+
const typeSet = new Set(types);
|
|
21
|
+
filtered = filtered.filter((m) => typeSet.has((m.metadata as { table: string }).table));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const validMatches = filtered.map((m) => ({
|
|
25
|
+
metadata: m.metadata as { table: string; id: string; machine_id: string },
|
|
26
|
+
score: m.score,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const results = await hydrateVectorMatches(env.MYCO_TEAM_DB, validMatches);
|
|
30
|
+
|
|
31
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ results: results.slice(0, topK) }) }] };
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
|
|
3
|
+
export async function handleSessions(
|
|
4
|
+
args: { limit?: number; status?: string; agent?: string; branch?: string; since?: string },
|
|
5
|
+
env: Pick<Env, 'MYCO_TEAM_DB'>,
|
|
6
|
+
) {
|
|
7
|
+
const { limit = 20, status, agent, branch, since } = args;
|
|
8
|
+
const conditions: string[] = [];
|
|
9
|
+
const binds: unknown[] = [];
|
|
10
|
+
|
|
11
|
+
if (status) { conditions.push('status = ?'); binds.push(status); }
|
|
12
|
+
if (agent) { conditions.push('agent = ?'); binds.push(agent); }
|
|
13
|
+
if (branch) { conditions.push('branch = ?'); binds.push(branch); }
|
|
14
|
+
if (since) { conditions.push('started_at >= ?'); binds.push(Math.floor(new Date(since).getTime() / 1000)); }
|
|
15
|
+
|
|
16
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
17
|
+
binds.push(Math.min(Math.max(limit, 1), 100));
|
|
18
|
+
|
|
19
|
+
const { results } = await env.MYCO_TEAM_DB.prepare(
|
|
20
|
+
`SELECT id, machine_id, agent, "user", branch, status, title, SUBSTR(summary, 1, 300) as summary, prompt_count, tool_count, started_at, ended_at FROM sessions ${where} ORDER BY started_at DESC LIMIT ?`,
|
|
21
|
+
).bind(...binds).all();
|
|
22
|
+
|
|
23
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ sessions: results }) }] };
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
|
|
3
|
+
export async function handleSkills(args: { status?: string; limit?: number }, env: Pick<Env, 'MYCO_TEAM_DB'>) {
|
|
4
|
+
const { status, limit = 50 } = args;
|
|
5
|
+
const conditions: string[] = [];
|
|
6
|
+
const binds: unknown[] = [];
|
|
7
|
+
if (status) { conditions.push('status = ?'); binds.push(status); }
|
|
8
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
9
|
+
binds.push(Math.min(Math.max(limit, 1), 100));
|
|
10
|
+
|
|
11
|
+
const { results } = await env.MYCO_TEAM_DB.prepare(
|
|
12
|
+
`SELECT id, machine_id, name, display_name, description, status, generation, usage_count, last_used_at, created_at, updated_at FROM skill_records ${where} ORDER BY usage_count DESC, created_at DESC LIMIT ?`,
|
|
13
|
+
).bind(...binds).all();
|
|
14
|
+
|
|
15
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ skills: results }) }] };
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Env } from '../../index';
|
|
2
|
+
|
|
3
|
+
export async function handleTeam(env: Pick<Env, 'MYCO_TEAM_DB'>) {
|
|
4
|
+
const { results } = await env.MYCO_TEAM_DB.prepare(
|
|
5
|
+
`SELECT machine_id, package_version, schema_version, sync_protocol_version, last_seen, registered_at FROM nodes ORDER BY last_seen DESC`,
|
|
6
|
+
).all();
|
|
7
|
+
|
|
8
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ nodes: results }) }] };
|
|
9
|
+
}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* Fully idempotent — safe to call on every request.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// Inline constants — the worker is deployed independently and cannot
|
|
12
|
+
// import from the main @myco package at runtime.
|
|
13
|
+
const CANDIDATE_STATUS = { APPROVED: 'approved', GENERATED: 'generated' } as const;
|
|
12
14
|
|
|
13
15
|
const SESSIONS_TABLE = `
|
|
14
16
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -196,6 +198,7 @@ const SKILL_CANDIDATES_TABLE = `
|
|
|
196
198
|
status TEXT NOT NULL DEFAULT 'identified',
|
|
197
199
|
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
198
200
|
skill_id TEXT,
|
|
201
|
+
supersedes TEXT,
|
|
199
202
|
approved_at INTEGER,
|
|
200
203
|
created_at INTEGER NOT NULL,
|
|
201
204
|
updated_at INTEGER NOT NULL,
|
|
@@ -299,6 +302,7 @@ export async function initD1Schema(db: D1Database): Promise<void> {
|
|
|
299
302
|
const migrations = [
|
|
300
303
|
'ALTER TABLE skill_usage ADD COLUMN synced_at INTEGER',
|
|
301
304
|
'ALTER TABLE skill_candidates ADD COLUMN approved_at INTEGER',
|
|
305
|
+
'ALTER TABLE skill_candidates ADD COLUMN supersedes TEXT',
|
|
302
306
|
];
|
|
303
307
|
for (const sql of migrations) {
|
|
304
308
|
try {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared search helpers used by both the REST /search endpoint and MCP myco_search tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Embed text via Workers AI (bge-m3) and return the vector.
|
|
7
|
+
*/
|
|
8
|
+
export async function embedText(ai: Ai, text: string): Promise<number[]> {
|
|
9
|
+
const result = await ai.run('@cf/baai/bge-m3', { text: [text] }) as { data: number[][] };
|
|
10
|
+
return result.data[0];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HydratedResult {
|
|
14
|
+
id: string;
|
|
15
|
+
machine_id: string;
|
|
16
|
+
type: string;
|
|
17
|
+
score: number;
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface VectorMatch {
|
|
22
|
+
metadata: { table: string; id: string; machine_id: string };
|
|
23
|
+
score: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Group Vectorize matches by table and batch-hydrate each group from D1.
|
|
28
|
+
* Queries run in parallel across tables.
|
|
29
|
+
*/
|
|
30
|
+
export async function hydrateVectorMatches(
|
|
31
|
+
db: D1Database,
|
|
32
|
+
matches: VectorMatch[],
|
|
33
|
+
): Promise<HydratedResult[]> {
|
|
34
|
+
// Group by table
|
|
35
|
+
const groups = new Map<string, Array<{ id: string; machine_id: string; score: number }>>();
|
|
36
|
+
for (const match of matches) {
|
|
37
|
+
const { table, id, machine_id } = match.metadata;
|
|
38
|
+
const group = groups.get(table) ?? [];
|
|
39
|
+
group.push({ id, machine_id, score: match.score });
|
|
40
|
+
groups.set(table, group);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Hydrate all tables in parallel
|
|
44
|
+
const hydrationPromises = [...groups.entries()].map(async ([table, items]) => {
|
|
45
|
+
const placeholders = items.map(() => '(?, ?)').join(', ');
|
|
46
|
+
const binds = items.flatMap((item) => [item.id, item.machine_id]);
|
|
47
|
+
const { results: rows } = await db.prepare(
|
|
48
|
+
`SELECT * FROM ${table} WHERE (id, machine_id) IN (VALUES ${placeholders})`,
|
|
49
|
+
).bind(...binds).all();
|
|
50
|
+
|
|
51
|
+
const rowMap = new Map<string, Record<string, unknown>>();
|
|
52
|
+
for (const row of rows as Record<string, unknown>[]) {
|
|
53
|
+
rowMap.set(`${row.id}:${row.machine_id}`, row);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const results: HydratedResult[] = [];
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
const row = rowMap.get(`${item.id}:${item.machine_id}`);
|
|
59
|
+
if (row) {
|
|
60
|
+
results.push({ id: item.id, machine_id: item.machine_id, type: table, score: item.score, data: row });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const resultArrays = await Promise.all(hydrationPromises);
|
|
67
|
+
const results = resultArrays.flat();
|
|
68
|
+
results.sort((a, b) => b.score - a.score);
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
name = "myco-team-sync"
|
|
2
2
|
main = "src/index.ts"
|
|
3
3
|
compatibility_date = "2025-03-27"
|
|
4
|
+
# nodejs_compat is required by the agents SDK and @modelcontextprotocol/sdk,
|
|
5
|
+
# which depend on packages that import Node.js built-ins (node:async_hooks,
|
|
6
|
+
# node:diagnostics_channel, node:os, etc.).
|
|
7
|
+
compatibility_flags = [ "nodejs_compat" ]
|
|
4
8
|
|
|
5
9
|
[vars]
|
|
6
10
|
SYNC_PROTOCOL_VERSION = "1"
|
|
@@ -16,6 +20,11 @@ database_id = "<YOUR_D1_DATABASE_ID>"
|
|
|
16
20
|
binding = "MYCO_TEAM_VECTORS"
|
|
17
21
|
index_name = "myco-team-vectors"
|
|
18
22
|
|
|
23
|
+
# KV namespace for runtime secrets (MCP tokens, etc.)
|
|
24
|
+
[[kv_namespaces]]
|
|
25
|
+
binding = "MYCO_SECRETS"
|
|
26
|
+
id = "<YOUR_KV_NAMESPACE_ID>"
|
|
27
|
+
|
|
19
28
|
# Workers AI binding
|
|
20
29
|
[ai]
|
|
21
30
|
binding = "AI"
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
gatherStats
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LSP5HYOO.js";
|
|
5
5
|
import {
|
|
6
6
|
initVaultDb
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TLK46KKD.js";
|
|
8
8
|
import "./chunk-SAKJMNSR.js";
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-WYOE4IAX.js";
|
|
10
|
+
import "./chunk-CML4MCYF.js";
|
|
11
|
+
import "./chunk-2V7HR7HB.js";
|
|
12
12
|
import "./chunk-MYX5NCRH.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-XWOQL4XN.js";
|
|
14
|
+
import "./chunk-W7WENJ6F.js";
|
|
15
15
|
import "./chunk-LPUQPDC2.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-CKJAWZQE.js";
|
|
17
17
|
import "./chunk-E7NUADTQ.js";
|
|
18
18
|
import "./chunk-D7TYRPRM.js";
|
|
19
19
|
import "./chunk-E4VLWIJC.js";
|
|
@@ -92,4 +92,4 @@ function formatUptime(seconds) {
|
|
|
92
92
|
export {
|
|
93
93
|
run
|
|
94
94
|
};
|
|
95
|
-
//# sourceMappingURL=stats-
|
|
95
|
+
//# sourceMappingURL=stats-GEOQ2DFF.js.map
|