@goondocks/myco 0.4.3 → 0.5.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/dist/chunk-2AMAOSRF.js +105 -0
- package/dist/chunk-2AMAOSRF.js.map +1 -0
- package/dist/{chunk-I7PNZEBO.js → chunk-6LTNFMXO.js} +12 -1
- package/dist/{chunk-I7PNZEBO.js.map → chunk-6LTNFMXO.js.map} +1 -1
- package/dist/{chunk-2GJFTIWX.js → chunk-7KQB22DP.js} +2 -2
- package/dist/{chunk-JBD5KP5G.js → chunk-B6WVNDA5.js} +14 -2
- package/dist/chunk-B6WVNDA5.js.map +1 -0
- package/dist/chunk-FIA5NTRH.js +159 -0
- package/dist/chunk-FIA5NTRH.js.map +1 -0
- package/dist/{chunk-GFBG73P4.js → chunk-FIRMTYFH.js} +3 -3
- package/dist/{chunk-XCPQHC4X.js → chunk-HJG7Z6SJ.js} +2 -2
- package/dist/chunk-HL2S5QZG.js +385 -0
- package/dist/chunk-HL2S5QZG.js.map +1 -0
- package/dist/{chunk-WBT5DWGC.js → chunk-IURC35BF.js} +2 -2
- package/dist/{chunk-67R6EMYD.js → chunk-JI6M2L2W.js} +31 -52
- package/dist/chunk-JI6M2L2W.js.map +1 -0
- package/dist/{chunk-FPEDTLQ6.js → chunk-JJL6AMDA.js} +3 -101
- package/dist/chunk-JJL6AMDA.js.map +1 -0
- package/dist/chunk-KYL67SKZ.js +150 -0
- package/dist/chunk-KYL67SKZ.js.map +1 -0
- package/dist/{chunk-ZCBL5HER.js → chunk-ND4VK6C7.js} +2 -2
- package/dist/{chunk-V2OWD2VV.js → chunk-R6LQT3U7.js} +24 -146
- package/dist/chunk-R6LQT3U7.js.map +1 -0
- package/dist/{chunk-IYFKPSRP.js → chunk-RCV2I4AI.js} +3 -3
- package/dist/{chunk-BNIYWCST.js → chunk-X6TKHO22.js} +2 -2
- package/dist/{chunk-OUFSLZTX.js → chunk-ZWUFTOG3.js} +21 -9
- package/dist/chunk-ZWUFTOG3.js.map +1 -0
- package/dist/{cli-PMOFCZQL.js → cli-BLYNNKGJ.js} +24 -18
- package/dist/cli-BLYNNKGJ.js.map +1 -0
- package/dist/{client-5SUO2UYH.js → client-5GB4WVXE.js} +5 -5
- package/dist/curate-S4HOYWXA.js +231 -0
- package/dist/curate-S4HOYWXA.js.map +1 -0
- package/dist/{detect-providers-IRL2TTLK.js → detect-providers-BIHYFK5M.js} +3 -3
- package/dist/digest-7NKYXM6G.js +96 -0
- package/dist/digest-7NKYXM6G.js.map +1 -0
- package/dist/{init-NUF5UBUJ.js → init-HPQ77WWF.js} +5 -5
- package/dist/{main-2XEBVUR6.js → main-NFQ4II75.js} +253 -576
- package/dist/main-NFQ4II75.js.map +1 -0
- package/dist/{rebuild-E6YFIRYZ.js → rebuild-KQ6G2GZM.js} +8 -7
- package/dist/{rebuild-E6YFIRYZ.js.map → rebuild-KQ6G2GZM.js.map} +1 -1
- package/dist/{reprocess-7G7KQWCN.js → reprocess-ZL4HKTSC.js} +95 -24
- package/dist/reprocess-ZL4HKTSC.js.map +1 -0
- package/dist/{restart-ABW4ZK3P.js → restart-FYW662DR.js} +6 -6
- package/dist/{search-MPD7SFK6.js → search-E5JQMTXV.js} +6 -6
- package/dist/{server-NZLZRITH.js → server-TV3D35HZ.js} +38 -15
- package/dist/{server-NZLZRITH.js.map → server-TV3D35HZ.js.map} +1 -1
- package/dist/{session-start-YB4A4PZB.js → session-start-5MFEOVQ5.js} +6 -6
- package/dist/{setup-digest-K732MGOJ.js → setup-digest-DZAFIBEF.js} +5 -5
- package/dist/{setup-llm-XCCH5LYD.js → setup-llm-4BZM33YT.js} +5 -5
- package/dist/src/cli.js +4 -4
- package/dist/src/daemon/main.js +4 -4
- package/dist/src/hooks/post-tool-use.js +5 -5
- package/dist/src/hooks/session-end.js +5 -5
- package/dist/src/hooks/session-start.js +4 -4
- package/dist/src/hooks/stop.js +6 -6
- package/dist/src/hooks/stop.js.map +1 -1
- package/dist/src/hooks/user-prompt-submit.js +5 -5
- package/dist/src/mcp/server.js +4 -4
- package/dist/src/prompts/extraction.md +1 -1
- package/dist/src/prompts/summary.md +1 -11
- package/dist/src/prompts/supersession.md +32 -0
- package/dist/{stats-6G7SN5YZ.js → stats-ZIIJ2GB3.js} +5 -5
- package/dist/{verify-JFHQH55Z.js → verify-RACBFT2P.js} +4 -4
- package/dist/{version-5B2TWXQJ.js → version-HJTVNPOO.js} +4 -4
- package/package.json +1 -1
- package/skills/setup/SKILL.md +56 -28
- package/skills/setup/references/model-recommendations.md +49 -43
- package/dist/chunk-67R6EMYD.js.map +0 -1
- package/dist/chunk-FPEDTLQ6.js.map +0 -1
- package/dist/chunk-JBD5KP5G.js.map +0 -1
- package/dist/chunk-OUFSLZTX.js.map +0 -1
- package/dist/chunk-V2OWD2VV.js.map +0 -1
- package/dist/cli-PMOFCZQL.js.map +0 -1
- package/dist/main-2XEBVUR6.js.map +0 -1
- package/dist/reprocess-7G7KQWCN.js.map +0 -1
- /package/dist/{chunk-2GJFTIWX.js.map → chunk-7KQB22DP.js.map} +0 -0
- /package/dist/{chunk-GFBG73P4.js.map → chunk-FIRMTYFH.js.map} +0 -0
- /package/dist/{chunk-XCPQHC4X.js.map → chunk-HJG7Z6SJ.js.map} +0 -0
- /package/dist/{chunk-WBT5DWGC.js.map → chunk-IURC35BF.js.map} +0 -0
- /package/dist/{chunk-ZCBL5HER.js.map → chunk-ND4VK6C7.js.map} +0 -0
- /package/dist/{chunk-IYFKPSRP.js.map → chunk-RCV2I4AI.js.map} +0 -0
- /package/dist/{chunk-BNIYWCST.js.map → chunk-X6TKHO22.js.map} +0 -0
- /package/dist/{client-5SUO2UYH.js.map → client-5GB4WVXE.js.map} +0 -0
- /package/dist/{detect-providers-IRL2TTLK.js.map → detect-providers-BIHYFK5M.js.map} +0 -0
- /package/dist/{init-NUF5UBUJ.js.map → init-HPQ77WWF.js.map} +0 -0
- /package/dist/{restart-ABW4ZK3P.js.map → restart-FYW662DR.js.map} +0 -0
- /package/dist/{search-MPD7SFK6.js.map → search-E5JQMTXV.js.map} +0 -0
- /package/dist/{session-start-YB4A4PZB.js.map → session-start-5MFEOVQ5.js.map} +0 -0
- /package/dist/{setup-digest-K732MGOJ.js.map → setup-digest-DZAFIBEF.js.map} +0 -0
- /package/dist/{setup-llm-XCCH5LYD.js.map → setup-llm-4BZM33YT.js.map} +0 -0
- /package/dist/{stats-6G7SN5YZ.js.map → stats-ZIIJ2GB3.js.map} +0 -0
- /package/dist/{verify-JFHQH55Z.js.map → verify-RACBFT2P.js.map} +0 -0
- /package/dist/{version-5B2TWXQJ.js.map → version-HJTVNPOO.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
AgentRegistry
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-X6TKHO22.js";
|
|
5
5
|
|
|
6
6
|
// src/native-deps.ts
|
|
7
7
|
import { execFileSync } from "child_process";
|
|
@@ -53,4 +53,4 @@ function ensureNativeDeps() {
|
|
|
53
53
|
export {
|
|
54
54
|
ensureNativeDeps
|
|
55
55
|
};
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
56
|
+
//# sourceMappingURL=chunk-HJG7Z6SJ.js.map
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
loadPrompt,
|
|
4
|
+
stripReasoningTokens
|
|
5
|
+
} from "./chunk-KYL67SKZ.js";
|
|
6
|
+
import {
|
|
7
|
+
stripFrontmatter
|
|
8
|
+
} from "./chunk-MIU3DKLN.js";
|
|
9
|
+
import {
|
|
10
|
+
require_dist
|
|
11
|
+
} from "./chunk-6UJWI4IW.js";
|
|
12
|
+
import {
|
|
13
|
+
CHARS_PER_TOKEN,
|
|
14
|
+
DIGEST_LLM_REQUEST_TIMEOUT_MS,
|
|
15
|
+
DIGEST_SUBSTRATE_TYPE_WEIGHTS,
|
|
16
|
+
DIGEST_TIER_MIN_CONTEXT,
|
|
17
|
+
LLM_REASONING_MODE,
|
|
18
|
+
estimateTokens
|
|
19
|
+
} from "./chunk-B6WVNDA5.js";
|
|
20
|
+
import {
|
|
21
|
+
__toESM
|
|
22
|
+
} from "./chunk-PZUWP5VK.js";
|
|
23
|
+
|
|
24
|
+
// src/daemon/digest.ts
|
|
25
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
26
|
+
import fs from "fs";
|
|
27
|
+
import path from "path";
|
|
28
|
+
import crypto from "crypto";
|
|
29
|
+
var PREVIOUS_EXTRACT_OVERHEAD_TOKENS = 50;
|
|
30
|
+
var CONTEXT_SAFETY_MARGIN = 0.7;
|
|
31
|
+
var EXTRACT_TYPE = "extract";
|
|
32
|
+
var DigestEngine = class {
|
|
33
|
+
vaultDir;
|
|
34
|
+
index;
|
|
35
|
+
llm;
|
|
36
|
+
config;
|
|
37
|
+
log;
|
|
38
|
+
lastCycleTimestampCache = void 0;
|
|
39
|
+
cycleInProgress = false;
|
|
40
|
+
constructor(engineConfig) {
|
|
41
|
+
this.vaultDir = engineConfig.vaultDir;
|
|
42
|
+
this.index = engineConfig.index;
|
|
43
|
+
this.llm = engineConfig.llmProvider;
|
|
44
|
+
this.config = engineConfig.config;
|
|
45
|
+
this.log = engineConfig.log ?? (() => {
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Query index for recent vault notes to feed into the digest.
|
|
50
|
+
* Filters out extract notes (our own output) and caps at max_notes_per_cycle.
|
|
51
|
+
*/
|
|
52
|
+
discoverSubstrate(lastCycleTimestamp) {
|
|
53
|
+
const maxNotes = this.config.digest.substrate.max_notes_per_cycle;
|
|
54
|
+
const notes = lastCycleTimestamp ? this.index.query({ updatedSince: lastCycleTimestamp, limit: maxNotes }) : this.index.query({ limit: maxNotes });
|
|
55
|
+
const filtered = notes.filter((n) => n.type !== EXTRACT_TYPE).filter((n) => {
|
|
56
|
+
if (n.type !== "spore") return true;
|
|
57
|
+
const status = n.frontmatter.status;
|
|
58
|
+
return !status || status === "active";
|
|
59
|
+
});
|
|
60
|
+
filtered.sort((a, b) => {
|
|
61
|
+
const weightA = DIGEST_SUBSTRATE_TYPE_WEIGHTS[a.type] ?? 0;
|
|
62
|
+
const weightB = DIGEST_SUBSTRATE_TYPE_WEIGHTS[b.type] ?? 0;
|
|
63
|
+
if (weightB !== weightA) return weightB - weightA;
|
|
64
|
+
return b.created.localeCompare(a.created);
|
|
65
|
+
});
|
|
66
|
+
return filtered.slice(0, maxNotes);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Filter configured tiers by the context window available.
|
|
70
|
+
* Only tiers whose minimum context requirement is met are eligible.
|
|
71
|
+
*/
|
|
72
|
+
getEligibleTiers() {
|
|
73
|
+
const contextWindow = this.config.digest.intelligence.context_window;
|
|
74
|
+
return this.config.digest.tiers.filter((tier) => {
|
|
75
|
+
const minContext = DIGEST_TIER_MIN_CONTEXT[tier];
|
|
76
|
+
return minContext !== void 0 && minContext <= contextWindow;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format notes compactly for inclusion in the digest prompt.
|
|
81
|
+
* Stops adding notes once the token budget is exceeded.
|
|
82
|
+
*/
|
|
83
|
+
formatSubstrate(notes, tokenBudget) {
|
|
84
|
+
const charBudget = tokenBudget * CHARS_PER_TOKEN;
|
|
85
|
+
const parts = [];
|
|
86
|
+
let usedChars = 0;
|
|
87
|
+
for (const note of notes) {
|
|
88
|
+
const entry = `### [${note.type}] ${note.id} \u2014 "${note.title}"
|
|
89
|
+
${note.content}`;
|
|
90
|
+
if (usedChars + entry.length > charBudget && parts.length > 0) break;
|
|
91
|
+
parts.push(entry);
|
|
92
|
+
usedChars += entry.length;
|
|
93
|
+
}
|
|
94
|
+
return parts.join("\n\n");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Read a previously generated extract for a given tier.
|
|
98
|
+
* Returns the body (stripped of YAML frontmatter), or null if not found.
|
|
99
|
+
*/
|
|
100
|
+
readPreviousExtract(tier) {
|
|
101
|
+
const extractPath = path.join(this.vaultDir, "digest", `extract-${tier}.md`);
|
|
102
|
+
let content;
|
|
103
|
+
try {
|
|
104
|
+
content = fs.readFileSync(extractPath, "utf-8");
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return stripFrontmatter(content).body;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Write a digest extract to the vault with YAML frontmatter.
|
|
112
|
+
* Uses atomic write pattern (temp file + rename).
|
|
113
|
+
*/
|
|
114
|
+
writeExtract(tier, body, cycleId, model, substrateCount) {
|
|
115
|
+
const digestDir = path.join(this.vaultDir, "digest");
|
|
116
|
+
fs.mkdirSync(digestDir, { recursive: true });
|
|
117
|
+
const frontmatter = {
|
|
118
|
+
type: EXTRACT_TYPE,
|
|
119
|
+
tier,
|
|
120
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121
|
+
cycle_id: cycleId,
|
|
122
|
+
substrate_count: substrateCount,
|
|
123
|
+
model
|
|
124
|
+
};
|
|
125
|
+
const fmYaml = import_yaml.default.stringify(frontmatter, {
|
|
126
|
+
defaultStringType: "QUOTE_DOUBLE",
|
|
127
|
+
defaultKeyType: "PLAIN"
|
|
128
|
+
}).trim();
|
|
129
|
+
const file = `---
|
|
130
|
+
${fmYaml}
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
${body}
|
|
134
|
+
`;
|
|
135
|
+
const fullPath = path.join(digestDir, `extract-${tier}.md`);
|
|
136
|
+
const tmpPath = `${fullPath}.tmp`;
|
|
137
|
+
fs.writeFileSync(tmpPath, file, "utf-8");
|
|
138
|
+
fs.renameSync(tmpPath, fullPath);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Append a digest cycle result as a JSON line to trace.jsonl.
|
|
142
|
+
*/
|
|
143
|
+
appendTrace(record) {
|
|
144
|
+
const digestDir = path.join(this.vaultDir, "digest");
|
|
145
|
+
fs.mkdirSync(digestDir, { recursive: true });
|
|
146
|
+
const tracePath = path.join(digestDir, "trace.jsonl");
|
|
147
|
+
fs.appendFileSync(tracePath, JSON.stringify(record) + "\n", "utf-8");
|
|
148
|
+
this.lastCycleTimestampCache = record.timestamp;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Read the last cycle timestamp from trace.jsonl.
|
|
152
|
+
* Cached in memory after first read — subsequent calls are O(1).
|
|
153
|
+
*/
|
|
154
|
+
getLastCycleTimestamp() {
|
|
155
|
+
if (this.lastCycleTimestampCache !== void 0) return this.lastCycleTimestampCache;
|
|
156
|
+
const tracePath = path.join(this.vaultDir, "digest", "trace.jsonl");
|
|
157
|
+
let content;
|
|
158
|
+
try {
|
|
159
|
+
content = fs.readFileSync(tracePath, "utf-8").trim();
|
|
160
|
+
} catch {
|
|
161
|
+
this.lastCycleTimestampCache = null;
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (!content) {
|
|
165
|
+
this.lastCycleTimestampCache = null;
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const lines = content.split("\n");
|
|
169
|
+
const lastLine = lines[lines.length - 1];
|
|
170
|
+
try {
|
|
171
|
+
const record = JSON.parse(lastLine);
|
|
172
|
+
this.lastCycleTimestampCache = record.timestamp;
|
|
173
|
+
return record.timestamp;
|
|
174
|
+
} catch {
|
|
175
|
+
this.lastCycleTimestampCache = null;
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Run a full digest cycle: discover substrate, generate extracts for each tier.
|
|
181
|
+
* Returns the cycle result, or null if no substrate was found.
|
|
182
|
+
*/
|
|
183
|
+
async runCycle(opts) {
|
|
184
|
+
if (this.cycleInProgress) {
|
|
185
|
+
this.log("debug", "Cycle already in progress \u2014 skipping");
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
this.cycleInProgress = true;
|
|
189
|
+
try {
|
|
190
|
+
return await this.runCycleInternal(opts);
|
|
191
|
+
} finally {
|
|
192
|
+
this.cycleInProgress = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async runCycleInternal(opts) {
|
|
196
|
+
if (this.llm.ensureLoaded) {
|
|
197
|
+
const { context_window: contextWindow, gpu_kv_cache: gpuKvCache } = this.config.digest.intelligence;
|
|
198
|
+
this.log("debug", "Verifying digest model", { contextWindow, gpuKvCache });
|
|
199
|
+
await this.llm.ensureLoaded(contextWindow, gpuKvCache);
|
|
200
|
+
}
|
|
201
|
+
const startTime = Date.now();
|
|
202
|
+
const fullReprocess = opts?.fullReprocess ?? false;
|
|
203
|
+
const lastTimestamp = fullReprocess ? null : this.getLastCycleTimestamp();
|
|
204
|
+
const substrate = this.discoverSubstrate(lastTimestamp);
|
|
205
|
+
this.log("debug", "Discovering substrate", { lastTimestamp: lastTimestamp ?? "full reprocess", substrateCount: substrate.length });
|
|
206
|
+
if (substrate.length === 0) {
|
|
207
|
+
this.log("debug", "No substrate found \u2014 skipping cycle");
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
this.log("info", `Starting digest cycle`, { substrateCount: substrate.length, fullReprocess });
|
|
211
|
+
const cycleId = crypto.randomUUID();
|
|
212
|
+
const allEligible = this.getEligibleTiers();
|
|
213
|
+
const eligibleTiers = opts?.tiers ? allEligible.filter((t) => opts.tiers.includes(t)) : allEligible;
|
|
214
|
+
this.log("debug", `Eligible tiers: [${eligibleTiers.join(", ")}]`);
|
|
215
|
+
const tiersGenerated = [];
|
|
216
|
+
let totalTokensUsed = 0;
|
|
217
|
+
let model = "";
|
|
218
|
+
const typeToKey = {
|
|
219
|
+
session: "sessions",
|
|
220
|
+
spore: "spores",
|
|
221
|
+
plan: "plans",
|
|
222
|
+
artifact: "artifacts",
|
|
223
|
+
"team-member": "team"
|
|
224
|
+
};
|
|
225
|
+
const substrateIndex = {
|
|
226
|
+
sessions: [],
|
|
227
|
+
spores: [],
|
|
228
|
+
plans: [],
|
|
229
|
+
artifacts: [],
|
|
230
|
+
team: []
|
|
231
|
+
};
|
|
232
|
+
for (const note of substrate) {
|
|
233
|
+
const key = typeToKey[note.type];
|
|
234
|
+
if (key) {
|
|
235
|
+
substrateIndex[key].push(note.id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const cycleTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
239
|
+
const systemPrompt = loadPrompt("digest-system");
|
|
240
|
+
for (const tier of eligibleTiers) {
|
|
241
|
+
const tierPrompt = loadPrompt(`digest-${tier}`);
|
|
242
|
+
const previousExtract = opts?.cleanSlate ? null : this.readPreviousExtract(tier);
|
|
243
|
+
const contextWindow = this.config.digest.intelligence.context_window;
|
|
244
|
+
const systemPromptTokens = estimateTokens(systemPrompt);
|
|
245
|
+
const tierPromptTokens = estimateTokens(tierPrompt);
|
|
246
|
+
const previousExtractTokens = previousExtract ? estimateTokens(previousExtract) + PREVIOUS_EXTRACT_OVERHEAD_TOKENS : 0;
|
|
247
|
+
const availableTokens = Math.floor(contextWindow * CONTEXT_SAFETY_MARGIN);
|
|
248
|
+
const substrateBudget = availableTokens - tier - systemPromptTokens - tierPromptTokens - previousExtractTokens;
|
|
249
|
+
if (substrateBudget <= 0) continue;
|
|
250
|
+
const formattedSubstrate = this.formatSubstrate(substrate, substrateBudget);
|
|
251
|
+
const promptParts = [tierPrompt];
|
|
252
|
+
if (previousExtract) {
|
|
253
|
+
promptParts.push("", "## Previous Synthesis", "", previousExtract);
|
|
254
|
+
}
|
|
255
|
+
promptParts.push("", "## New Substrate", "", formattedSubstrate);
|
|
256
|
+
promptParts.push(
|
|
257
|
+
"",
|
|
258
|
+
"---",
|
|
259
|
+
"Produce your updated synthesis now. Stay within the token budget specified above."
|
|
260
|
+
);
|
|
261
|
+
const userPrompt = promptParts.join("\n");
|
|
262
|
+
const promptTokens = estimateTokens(systemPrompt + userPrompt);
|
|
263
|
+
this.log("debug", `Tier ${tier}: sending LLM request`, { promptTokens, maxTokens: tier, substrateBudget });
|
|
264
|
+
try {
|
|
265
|
+
const tierStart = Date.now();
|
|
266
|
+
const digestConfig = this.config.digest.intelligence;
|
|
267
|
+
const opts2 = {
|
|
268
|
+
maxTokens: tier,
|
|
269
|
+
timeoutMs: DIGEST_LLM_REQUEST_TIMEOUT_MS,
|
|
270
|
+
contextLength: contextWindow,
|
|
271
|
+
reasoning: LLM_REASONING_MODE,
|
|
272
|
+
systemPrompt,
|
|
273
|
+
keepAlive: digestConfig.keep_alive ?? void 0
|
|
274
|
+
};
|
|
275
|
+
const response = await this.llm.summarize(userPrompt, opts2);
|
|
276
|
+
const tierDuration = Date.now() - tierStart;
|
|
277
|
+
const extractText = stripReasoningTokens(response.text);
|
|
278
|
+
model = response.model;
|
|
279
|
+
const responseTokens = estimateTokens(extractText);
|
|
280
|
+
totalTokensUsed += promptTokens + responseTokens;
|
|
281
|
+
this.log("info", `Tier ${tier}: completed`, { durationMs: tierDuration, responseTokens, model: response.model });
|
|
282
|
+
this.writeExtract(tier, extractText, cycleId, response.model, substrate.length);
|
|
283
|
+
tiersGenerated.push(tier);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
this.log("warn", `Tier ${tier}: failed`, { error: err.message });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const result = {
|
|
289
|
+
cycleId,
|
|
290
|
+
timestamp: cycleTimestamp,
|
|
291
|
+
substrate: substrateIndex,
|
|
292
|
+
tiersGenerated,
|
|
293
|
+
model,
|
|
294
|
+
durationMs: Date.now() - startTime,
|
|
295
|
+
tokensUsed: totalTokensUsed
|
|
296
|
+
};
|
|
297
|
+
this.appendTrace(result);
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var MS_PER_SECOND = 1e3;
|
|
302
|
+
var Metabolism = class {
|
|
303
|
+
state = "active";
|
|
304
|
+
currentIntervalMs;
|
|
305
|
+
cooldownStep = 0;
|
|
306
|
+
lastSubstrateTime;
|
|
307
|
+
timer = null;
|
|
308
|
+
activeIntervalMs;
|
|
309
|
+
cooldownIntervalsMs;
|
|
310
|
+
dormancyThresholdMs;
|
|
311
|
+
constructor(config) {
|
|
312
|
+
this.activeIntervalMs = config.active_interval * MS_PER_SECOND;
|
|
313
|
+
this.cooldownIntervalsMs = config.cooldown_intervals.map((s) => s * MS_PER_SECOND);
|
|
314
|
+
this.dormancyThresholdMs = config.dormancy_threshold * MS_PER_SECOND;
|
|
315
|
+
this.currentIntervalMs = this.activeIntervalMs;
|
|
316
|
+
this.lastSubstrateTime = Date.now();
|
|
317
|
+
}
|
|
318
|
+
/** Reset to active state when new substrate is found. */
|
|
319
|
+
onSubstrateFound() {
|
|
320
|
+
this.state = "active";
|
|
321
|
+
this.cooldownStep = 0;
|
|
322
|
+
this.currentIntervalMs = this.activeIntervalMs;
|
|
323
|
+
this.lastSubstrateTime = Date.now();
|
|
324
|
+
}
|
|
325
|
+
/** Advance cooldown when a cycle finds no new substrate. */
|
|
326
|
+
onEmptyCycle() {
|
|
327
|
+
if (this.state === "dormant") return;
|
|
328
|
+
this.state = "cooling";
|
|
329
|
+
if (this.cooldownStep < this.cooldownIntervalsMs.length) {
|
|
330
|
+
this.currentIntervalMs = this.cooldownIntervalsMs[this.cooldownStep];
|
|
331
|
+
this.cooldownStep++;
|
|
332
|
+
}
|
|
333
|
+
this.checkDormancy();
|
|
334
|
+
}
|
|
335
|
+
/** Enter dormant state if enough time has elapsed since last substrate. */
|
|
336
|
+
checkDormancy() {
|
|
337
|
+
const elapsed = Date.now() - this.lastSubstrateTime;
|
|
338
|
+
if (elapsed >= this.dormancyThresholdMs) {
|
|
339
|
+
this.state = "dormant";
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** Return to active from any state, resetting timers and rescheduling immediately. */
|
|
343
|
+
activate() {
|
|
344
|
+
this.onSubstrateFound();
|
|
345
|
+
if (this.callback) {
|
|
346
|
+
this.reschedule();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/** Set lastSubstrateTime explicitly (for testing). */
|
|
350
|
+
markLastSubstrate(time) {
|
|
351
|
+
this.lastSubstrateTime = time;
|
|
352
|
+
}
|
|
353
|
+
/** Begin scheduling digest cycles with adaptive intervals. */
|
|
354
|
+
start(callback) {
|
|
355
|
+
this.callback = callback;
|
|
356
|
+
this.reschedule();
|
|
357
|
+
}
|
|
358
|
+
/** Stop the timer. */
|
|
359
|
+
stop() {
|
|
360
|
+
if (this.timer) {
|
|
361
|
+
clearTimeout(this.timer);
|
|
362
|
+
this.timer = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
callback = null;
|
|
366
|
+
reschedule() {
|
|
367
|
+
this.stop();
|
|
368
|
+
if (!this.callback) return;
|
|
369
|
+
const cb = this.callback;
|
|
370
|
+
const schedule = () => {
|
|
371
|
+
this.timer = setTimeout(async () => {
|
|
372
|
+
await cb();
|
|
373
|
+
schedule();
|
|
374
|
+
}, this.currentIntervalMs);
|
|
375
|
+
this.timer.unref();
|
|
376
|
+
};
|
|
377
|
+
schedule();
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export {
|
|
382
|
+
DigestEngine,
|
|
383
|
+
Metabolism
|
|
384
|
+
};
|
|
385
|
+
//# sourceMappingURL=chunk-HL2S5QZG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/digest.ts"],"sourcesContent":["/**\n * DigestEngine — synthesizes vault knowledge into tiered context extracts.\n * Metabolism — adaptive timer that throttles digest cycles based on activity.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport YAML from 'yaml';\n\nimport type { MycoIndex, IndexedNote } from '@myco/index/sqlite.js';\nimport type { LlmProvider, LlmRequestOptions } from '@myco/intelligence/llm.js';\nimport type { MycoConfig } from '@myco/config/schema.js';\nimport { loadPrompt } from '@myco/prompts/index.js';\nimport { stripReasoningTokens } from '@myco/intelligence/response.js';\nimport { stripFrontmatter } from '@myco/vault/frontmatter.js';\nimport {\n estimateTokens,\n CHARS_PER_TOKEN,\n DIGEST_TIER_MIN_CONTEXT,\n DIGEST_SUBSTRATE_TYPE_WEIGHTS,\n DIGEST_LLM_REQUEST_TIMEOUT_MS,\n LLM_REASONING_MODE,\n} from '@myco/constants.js';\n\n// --- Interfaces ---\n\nexport interface DigestCycleResult {\n cycleId: string;\n timestamp: string;\n substrate: {\n sessions: string[];\n spores: string[];\n plans: string[];\n artifacts: string[];\n team: string[];\n };\n tiersGenerated: number[];\n model: string;\n durationMs: number;\n tokensUsed: number;\n}\n\n/** Simple log function signature for digest progress reporting. */\nexport type DigestLogFn = (level: 'debug' | 'info' | 'warn', message: string, data?: Record<string, unknown>) => void;\n\nexport interface DigestCycleOptions {\n /** Process all substrate regardless of last cycle timestamp. */\n fullReprocess?: boolean;\n /** Only generate these tiers (default: all eligible). */\n tiers?: number[];\n /** Skip previous extract — start from clean slate. */\n cleanSlate?: boolean;\n}\n\nexport interface DigestEngineConfig {\n vaultDir: string;\n index: MycoIndex;\n llmProvider: LlmProvider;\n config: MycoConfig;\n log?: DigestLogFn;\n}\n\n// --- Constants ---\n\n/** Token overhead estimate for previous extract section wrapper. */\nconst PREVIOUS_EXTRACT_OVERHEAD_TOKENS = 50;\n\n/** Safety margin for context window — our CHARS_PER_TOKEN=4 heuristic significantly\n * underestimates real token counts (observed ~3.2 chars/token for mixed content).\n * 0.70 provides a safe buffer: 32K * 0.70 = 22.4K usable tokens. */\nconst CONTEXT_SAFETY_MARGIN = 0.70;\n\n/** Types that are digest output — excluded from substrate to avoid self-digestion. */\nconst EXTRACT_TYPE = 'extract';\n\n// --- DigestEngine ---\n\nexport class DigestEngine {\n private vaultDir: string;\n private index: MycoIndex;\n private llm: LlmProvider;\n private config: MycoConfig;\n private log: DigestLogFn;\n private lastCycleTimestampCache: string | null | undefined = undefined;\n private cycleInProgress = false;\n\n constructor(engineConfig: DigestEngineConfig) {\n this.vaultDir = engineConfig.vaultDir;\n this.index = engineConfig.index;\n this.llm = engineConfig.llmProvider;\n this.config = engineConfig.config;\n this.log = engineConfig.log ?? (() => {});\n }\n\n /**\n * Query index for recent vault notes to feed into the digest.\n * Filters out extract notes (our own output) and caps at max_notes_per_cycle.\n */\n discoverSubstrate(lastCycleTimestamp: string | null): IndexedNote[] {\n const maxNotes = this.config.digest.substrate.max_notes_per_cycle;\n\n const notes = lastCycleTimestamp\n ? this.index.query({ updatedSince: lastCycleTimestamp, limit: maxNotes })\n : this.index.query({ limit: maxNotes });\n\n // Guard against self-digestion: extract files are not currently indexed,\n // but this filter prevents feedback loops if they ever are (e.g., via rebuild)\n const filtered = notes\n .filter((n) => n.type !== EXTRACT_TYPE)\n .filter((n) => {\n if (n.type !== 'spore') return true;\n const status = n.frontmatter.status as string | undefined;\n return !status || status === 'active';\n });\n\n // Sort by type weight (descending) then by recency (descending)\n filtered.sort((a, b) => {\n const weightA = DIGEST_SUBSTRATE_TYPE_WEIGHTS[a.type] ?? 0;\n const weightB = DIGEST_SUBSTRATE_TYPE_WEIGHTS[b.type] ?? 0;\n if (weightB !== weightA) return weightB - weightA;\n // More recent first — created is ISO string, lexicographic sort works\n return b.created.localeCompare(a.created);\n });\n\n return filtered.slice(0, maxNotes);\n }\n\n /**\n * Filter configured tiers by the context window available.\n * Only tiers whose minimum context requirement is met are eligible.\n */\n getEligibleTiers(): number[] {\n const contextWindow = this.config.digest.intelligence.context_window;\n return this.config.digest.tiers.filter((tier) => {\n const minContext = DIGEST_TIER_MIN_CONTEXT[tier];\n return minContext !== undefined && minContext <= contextWindow;\n });\n }\n\n /**\n * Format notes compactly for inclusion in the digest prompt.\n * Stops adding notes once the token budget is exceeded.\n */\n formatSubstrate(notes: IndexedNote[], tokenBudget: number): string {\n const charBudget = tokenBudget * CHARS_PER_TOKEN;\n const parts: string[] = [];\n let usedChars = 0;\n\n for (const note of notes) {\n const entry = `### [${note.type}] ${note.id} — \"${note.title}\"\\n${note.content}`;\n if (usedChars + entry.length > charBudget && parts.length > 0) break;\n parts.push(entry);\n usedChars += entry.length;\n }\n\n return parts.join('\\n\\n');\n }\n\n /**\n * Read a previously generated extract for a given tier.\n * Returns the body (stripped of YAML frontmatter), or null if not found.\n */\n readPreviousExtract(tier: number): string | null {\n const extractPath = path.join(this.vaultDir, 'digest', `extract-${tier}.md`);\n let content: string;\n try {\n content = fs.readFileSync(extractPath, 'utf-8');\n } catch {\n return null;\n }\n\n return stripFrontmatter(content).body;\n }\n\n /**\n * Write a digest extract to the vault with YAML frontmatter.\n * Uses atomic write pattern (temp file + rename).\n */\n writeExtract(\n tier: number,\n body: string,\n cycleId: string,\n model: string,\n substrateCount: number,\n ): void {\n const digestDir = path.join(this.vaultDir, 'digest');\n fs.mkdirSync(digestDir, { recursive: true });\n\n const frontmatter: Record<string, unknown> = {\n type: EXTRACT_TYPE,\n tier,\n generated: new Date().toISOString(),\n cycle_id: cycleId,\n substrate_count: substrateCount,\n model,\n };\n\n const fmYaml = YAML.stringify(frontmatter, {\n defaultStringType: 'QUOTE_DOUBLE',\n defaultKeyType: 'PLAIN',\n }).trim();\n const file = `---\\n${fmYaml}\\n---\\n\\n${body}\\n`;\n\n const fullPath = path.join(digestDir, `extract-${tier}.md`);\n const tmpPath = `${fullPath}.tmp`;\n fs.writeFileSync(tmpPath, file, 'utf-8');\n fs.renameSync(tmpPath, fullPath);\n }\n\n /**\n * Append a digest cycle result as a JSON line to trace.jsonl.\n */\n appendTrace(record: DigestCycleResult): void {\n const digestDir = path.join(this.vaultDir, 'digest');\n fs.mkdirSync(digestDir, { recursive: true });\n const tracePath = path.join(digestDir, 'trace.jsonl');\n fs.appendFileSync(tracePath, JSON.stringify(record) + '\\n', 'utf-8');\n this.lastCycleTimestampCache = record.timestamp;\n }\n\n /**\n * Read the last cycle timestamp from trace.jsonl.\n * Cached in memory after first read — subsequent calls are O(1).\n */\n getLastCycleTimestamp(): string | null {\n if (this.lastCycleTimestampCache !== undefined) return this.lastCycleTimestampCache;\n\n const tracePath = path.join(this.vaultDir, 'digest', 'trace.jsonl');\n let content: string;\n try {\n content = fs.readFileSync(tracePath, 'utf-8').trim();\n } catch {\n this.lastCycleTimestampCache = null;\n return null;\n }\n\n if (!content) {\n this.lastCycleTimestampCache = null;\n return null;\n }\n\n const lines = content.split('\\n');\n const lastLine = lines[lines.length - 1];\n try {\n const record = JSON.parse(lastLine) as DigestCycleResult;\n this.lastCycleTimestampCache = record.timestamp;\n return record.timestamp;\n } catch {\n this.lastCycleTimestampCache = null;\n return null;\n }\n }\n\n /**\n * Run a full digest cycle: discover substrate, generate extracts for each tier.\n * Returns the cycle result, or null if no substrate was found.\n */\n async runCycle(opts?: DigestCycleOptions): Promise<DigestCycleResult | null> {\n if (this.cycleInProgress) {\n this.log('debug', 'Cycle already in progress — skipping');\n return null;\n }\n this.cycleInProgress = true;\n\n try {\n return await this.runCycleInternal(opts);\n } finally {\n this.cycleInProgress = false;\n }\n }\n\n private async runCycleInternal(opts?: DigestCycleOptions): Promise<DigestCycleResult | null> {\n // Ensure model is loaded with correct settings every cycle.\n // LM Studio's idle TTL can evict our instance between cycles — without\n // re-running ensureLoaded, the auto-reloaded instance would use LM Studio's\n // UI defaults (wrong KV cache setting). This is fast (~26ms) when the\n // instance is still alive (just a getLoadedInstances check).\n if (this.llm.ensureLoaded) {\n const { context_window: contextWindow, gpu_kv_cache: gpuKvCache } = this.config.digest.intelligence;\n this.log('debug', 'Verifying digest model', { contextWindow, gpuKvCache });\n await this.llm.ensureLoaded(contextWindow, gpuKvCache);\n }\n\n const startTime = Date.now();\n const fullReprocess = opts?.fullReprocess ?? false;\n const lastTimestamp = fullReprocess ? null : this.getLastCycleTimestamp();\n const substrate = this.discoverSubstrate(lastTimestamp);\n\n this.log('debug', 'Discovering substrate', { lastTimestamp: lastTimestamp ?? 'full reprocess', substrateCount: substrate.length });\n if (substrate.length === 0) {\n this.log('debug', 'No substrate found — skipping cycle');\n return null;\n }\n\n this.log('info', `Starting digest cycle`, { substrateCount: substrate.length, fullReprocess });\n const cycleId = crypto.randomUUID();\n const allEligible = this.getEligibleTiers();\n const eligibleTiers = opts?.tiers\n ? allEligible.filter((t) => opts.tiers!.includes(t))\n : allEligible;\n this.log('debug', `Eligible tiers: [${eligibleTiers.join(', ')}]`);\n const tiersGenerated: number[] = [];\n let totalTokensUsed = 0;\n let model = '';\n\n // Categorize substrate by type for the result\n const typeToKey: Record<string, keyof DigestCycleResult['substrate']> = {\n session: 'sessions',\n spore: 'spores',\n plan: 'plans',\n artifact: 'artifacts',\n 'team-member': 'team',\n };\n const substrateIndex: DigestCycleResult['substrate'] = {\n sessions: [],\n spores: [],\n plans: [],\n artifacts: [],\n team: [],\n };\n for (const note of substrate) {\n const key = typeToKey[note.type];\n if (key) {\n substrateIndex[key].push(note.id);\n }\n }\n\n // Record the cycle timestamp NOW, before tier processing. This ensures the\n // timestamp advances even if LLM calls fail, preventing the same substrate\n // from being rediscovered on every subsequent timer fire.\n const cycleTimestamp = new Date().toISOString();\n\n const systemPrompt = loadPrompt('digest-system');\n\n for (const tier of eligibleTiers) {\n const tierPrompt = loadPrompt(`digest-${tier}`);\n const previousExtract = opts?.cleanSlate ? null : this.readPreviousExtract(tier);\n\n // Calculate token budget for substrate:\n // (context_window * safety_margin) - output - system_prompt - tier_prompt - previous_extract\n const contextWindow = this.config.digest.intelligence.context_window;\n const systemPromptTokens = estimateTokens(systemPrompt);\n const tierPromptTokens = estimateTokens(tierPrompt);\n const previousExtractTokens = previousExtract\n ? estimateTokens(previousExtract) + PREVIOUS_EXTRACT_OVERHEAD_TOKENS\n : 0;\n const availableTokens = Math.floor(contextWindow * CONTEXT_SAFETY_MARGIN);\n const substrateBudget = availableTokens - tier - systemPromptTokens - tierPromptTokens - previousExtractTokens;\n\n if (substrateBudget <= 0) continue;\n\n const formattedSubstrate = this.formatSubstrate(substrate, substrateBudget);\n\n // Build user prompt (system prompt sent separately via LlmRequestOptions)\n const promptParts = [tierPrompt];\n\n if (previousExtract) {\n promptParts.push('', '## Previous Synthesis', '', previousExtract);\n }\n\n promptParts.push('', '## New Substrate', '', formattedSubstrate);\n promptParts.push(\n '',\n '---',\n 'Produce your updated synthesis now. Stay within the token budget specified above.',\n );\n\n const userPrompt = promptParts.join('\\n');\n const promptTokens = estimateTokens(systemPrompt + userPrompt);\n this.log('debug', `Tier ${tier}: sending LLM request`, { promptTokens, maxTokens: tier, substrateBudget });\n\n try {\n const tierStart = Date.now();\n const digestConfig = this.config.digest.intelligence;\n const opts: LlmRequestOptions = {\n maxTokens: tier,\n timeoutMs: DIGEST_LLM_REQUEST_TIMEOUT_MS,\n contextLength: contextWindow,\n reasoning: LLM_REASONING_MODE,\n systemPrompt,\n keepAlive: digestConfig.keep_alive ?? undefined,\n };\n const response = await this.llm.summarize(userPrompt, opts);\n const tierDuration = Date.now() - tierStart;\n\n // Strip reasoning tokens if present (some models output chain-of-thought)\n const extractText = stripReasoningTokens(response.text);\n model = response.model;\n const responseTokens = estimateTokens(extractText);\n totalTokensUsed += promptTokens + responseTokens;\n\n this.log('info', `Tier ${tier}: completed`, { durationMs: tierDuration, responseTokens, model: response.model });\n this.writeExtract(tier, extractText, cycleId, response.model, substrate.length);\n tiersGenerated.push(tier);\n } catch (err) {\n this.log('warn', `Tier ${tier}: failed`, { error: (err as Error).message });\n }\n }\n\n const result: DigestCycleResult = {\n cycleId,\n timestamp: cycleTimestamp,\n substrate: substrateIndex,\n tiersGenerated,\n model,\n durationMs: Date.now() - startTime,\n tokensUsed: totalTokensUsed,\n };\n\n this.appendTrace(result);\n return result;\n }\n}\n\n// --- Metabolism (Adaptive Timer) ---\n\nexport type MetabolismState = 'active' | 'cooling' | 'dormant';\n\n/** Milliseconds per second for config conversion. */\nconst MS_PER_SECOND = 1000;\n\nexport class Metabolism {\n state: MetabolismState = 'active';\n currentIntervalMs: number;\n\n private cooldownStep = 0;\n private lastSubstrateTime: number;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private activeIntervalMs: number;\n private cooldownIntervalsMs: number[];\n private dormancyThresholdMs: number;\n\n constructor(config: MycoConfig['digest']['metabolism']) {\n this.activeIntervalMs = config.active_interval * MS_PER_SECOND;\n this.cooldownIntervalsMs = config.cooldown_intervals.map((s) => s * MS_PER_SECOND);\n this.dormancyThresholdMs = config.dormancy_threshold * MS_PER_SECOND;\n this.currentIntervalMs = this.activeIntervalMs;\n this.lastSubstrateTime = Date.now();\n }\n\n /** Reset to active state when new substrate is found. */\n onSubstrateFound(): void {\n this.state = 'active';\n this.cooldownStep = 0;\n this.currentIntervalMs = this.activeIntervalMs;\n this.lastSubstrateTime = Date.now();\n }\n\n /** Advance cooldown when a cycle finds no new substrate. */\n onEmptyCycle(): void {\n if (this.state === 'dormant') return;\n\n this.state = 'cooling';\n if (this.cooldownStep < this.cooldownIntervalsMs.length) {\n this.currentIntervalMs = this.cooldownIntervalsMs[this.cooldownStep];\n this.cooldownStep++;\n }\n\n this.checkDormancy();\n }\n\n /** Enter dormant state if enough time has elapsed since last substrate. */\n checkDormancy(): void {\n const elapsed = Date.now() - this.lastSubstrateTime;\n if (elapsed >= this.dormancyThresholdMs) {\n this.state = 'dormant';\n // Keep the last cooldown interval as the dormant polling rate\n }\n }\n\n /** Return to active from any state, resetting timers and rescheduling immediately. */\n activate(): void {\n this.onSubstrateFound();\n // Reschedule with the new active interval — without this, the old\n // (possibly dormant) timer continues ticking at the wrong rate\n if (this.callback) {\n this.reschedule();\n }\n }\n\n /** Set lastSubstrateTime explicitly (for testing). */\n markLastSubstrate(time: number): void {\n this.lastSubstrateTime = time;\n }\n\n /** Begin scheduling digest cycles with adaptive intervals. */\n start(callback: () => Promise<void>): void {\n this.callback = callback;\n this.reschedule();\n }\n\n /** Stop the timer. */\n stop(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n private callback: (() => Promise<void>) | null = null;\n\n private reschedule(): void {\n this.stop();\n if (!this.callback) return;\n const cb = this.callback;\n const schedule = (): void => {\n this.timer = setTimeout(async () => {\n await cb();\n schedule();\n }, this.currentIntervalMs);\n this.timer.unref();\n };\n schedule();\n }\n}\n"],"mappings":"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","names":["YAML","opts"]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-MIU3DKLN.js";
|
|
5
5
|
import {
|
|
6
6
|
DIGEST_TIERS
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B6WVNDA5.js";
|
|
8
8
|
|
|
9
9
|
// src/mcp/tools/context.ts
|
|
10
10
|
import fs from "fs";
|
|
@@ -46,4 +46,4 @@ function handleMycoContext(vaultDir, input) {
|
|
|
46
46
|
export {
|
|
47
47
|
handleMycoContext
|
|
48
48
|
};
|
|
49
|
-
//# sourceMappingURL=chunk-
|
|
49
|
+
//# sourceMappingURL=chunk-IURC35BF.js.map
|
|
@@ -2,9 +2,8 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
2
2
|
import {
|
|
3
3
|
DAEMON_CLIENT_TIMEOUT_MS,
|
|
4
4
|
EMBEDDING_REQUEST_TIMEOUT_MS,
|
|
5
|
-
LLM_REQUEST_TIMEOUT_MS
|
|
6
|
-
|
|
7
|
-
} from "./chunk-JBD5KP5G.js";
|
|
5
|
+
LLM_REQUEST_TIMEOUT_MS
|
|
6
|
+
} from "./chunk-B6WVNDA5.js";
|
|
8
7
|
|
|
9
8
|
// src/intelligence/ollama.ts
|
|
10
9
|
var ENDPOINT_GENERATE = "/api/generate";
|
|
@@ -15,27 +14,23 @@ var OllamaBackend = class _OllamaBackend {
|
|
|
15
14
|
name = "ollama";
|
|
16
15
|
baseUrl;
|
|
17
16
|
model;
|
|
18
|
-
contextWindow;
|
|
19
17
|
defaultMaxTokens;
|
|
20
18
|
constructor(config) {
|
|
21
19
|
this.baseUrl = config?.base_url ?? _OllamaBackend.DEFAULT_BASE_URL;
|
|
22
20
|
this.model = config?.model ?? config?.summary_model ?? "llama3.2";
|
|
23
|
-
this.contextWindow = config?.context_window ?? 8192;
|
|
24
21
|
this.defaultMaxTokens = config?.max_tokens ?? 1024;
|
|
25
22
|
}
|
|
26
23
|
async summarize(prompt, opts) {
|
|
27
24
|
const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const options = { num_predict: maxTokens };
|
|
26
|
+
if (opts?.contextLength) {
|
|
27
|
+
options.num_ctx = opts.contextLength;
|
|
28
|
+
}
|
|
31
29
|
const body = {
|
|
32
30
|
model: this.model,
|
|
33
31
|
prompt,
|
|
34
32
|
stream: false,
|
|
35
|
-
options
|
|
36
|
-
num_ctx: numCtx,
|
|
37
|
-
num_predict: maxTokens
|
|
38
|
-
}
|
|
33
|
+
options
|
|
39
34
|
};
|
|
40
35
|
if (opts?.systemPrompt) {
|
|
41
36
|
body.system = opts.systemPrompt;
|
|
@@ -103,7 +98,6 @@ var OllamaBackend = class _OllamaBackend {
|
|
|
103
98
|
// src/intelligence/lm-studio.ts
|
|
104
99
|
var ENDPOINT_CHAT = "/api/v1/chat";
|
|
105
100
|
var ENDPOINT_MODELS_LOAD = "/api/v1/models/load";
|
|
106
|
-
var ENDPOINT_MODELS_UNLOAD = "/api/v1/models/unload";
|
|
107
101
|
var ENDPOINT_MODELS_LIST = "/v1/models";
|
|
108
102
|
var ENDPOINT_MODELS_NATIVE = "/api/v1/models";
|
|
109
103
|
var ENDPOINT_EMBEDDINGS = "/v1/embeddings";
|
|
@@ -112,7 +106,7 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
112
106
|
name = "lm-studio";
|
|
113
107
|
baseUrl;
|
|
114
108
|
model;
|
|
115
|
-
|
|
109
|
+
instanceId = null;
|
|
116
110
|
contextWindow;
|
|
117
111
|
defaultMaxTokens;
|
|
118
112
|
constructor(config) {
|
|
@@ -123,21 +117,22 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
123
117
|
}
|
|
124
118
|
/**
|
|
125
119
|
* Generate text using LM Studio's native REST API (/api/v1/chat).
|
|
126
|
-
*
|
|
120
|
+
* Routes to our specific instance by ID when available, with model name +
|
|
121
|
+
* context_length as fallback. This ensures correct routing when multiple
|
|
122
|
+
* daemons share the same LM Studio, and graceful degradation when our
|
|
123
|
+
* instance is evicted by idle TTL.
|
|
127
124
|
*/
|
|
128
125
|
async summarize(prompt, opts) {
|
|
129
126
|
const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
|
|
127
|
+
const contextLength = opts?.contextLength ?? this.contextWindow;
|
|
130
128
|
const body = {
|
|
131
|
-
model: this.
|
|
129
|
+
model: this.instanceId ?? this.model,
|
|
132
130
|
input: prompt,
|
|
133
131
|
max_output_tokens: maxTokens,
|
|
134
132
|
store: false
|
|
135
133
|
};
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
if (contextLength) {
|
|
139
|
-
body.context_length = contextLength;
|
|
140
|
-
}
|
|
134
|
+
if (contextLength) {
|
|
135
|
+
body.context_length = contextLength;
|
|
141
136
|
}
|
|
142
137
|
if (opts?.systemPrompt) {
|
|
143
138
|
body.system_prompt = opts.systemPrompt;
|
|
@@ -153,6 +148,9 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
153
148
|
});
|
|
154
149
|
if (!response.ok) {
|
|
155
150
|
const errorBody = await response.text().catch(() => "");
|
|
151
|
+
if (response.status === 404 && this.instanceId) {
|
|
152
|
+
this.instanceId = null;
|
|
153
|
+
}
|
|
156
154
|
throw new Error(`LM Studio summarize failed: ${response.status} ${errorBody.slice(0, 500)}`);
|
|
157
155
|
}
|
|
158
156
|
const data = await response.json();
|
|
@@ -183,9 +181,13 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
183
181
|
}
|
|
184
182
|
/**
|
|
185
183
|
* Ensure a model instance is loaded with the desired settings.
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
184
|
+
* Called every digest cycle (not cached) so it recovers from idle TTL eviction.
|
|
185
|
+
*
|
|
186
|
+
* The load API is necessary to control offload_kv_cache_to_gpu — a load-time
|
|
187
|
+
* setting that cannot be set per-request via the chat API.
|
|
188
|
+
*
|
|
189
|
+
* Multi-daemon safe: finds or loads our own compatible instance without
|
|
190
|
+
* touching instances from other daemons/projects. Routes by instance ID.
|
|
189
191
|
*/
|
|
190
192
|
async ensureLoaded(contextLength, gpuKvCache) {
|
|
191
193
|
const ctx = contextLength ?? this.contextWindow;
|
|
@@ -195,12 +197,10 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
195
197
|
const matchesContext = !ctx || instance.config.context_length === ctx;
|
|
196
198
|
const matchesKvCache = instance.config.offload_kv_cache_to_gpu === kvCache;
|
|
197
199
|
if (matchesContext && matchesKvCache) {
|
|
198
|
-
this.
|
|
199
|
-
await this.unloadIncompatibleInstances(instances, ctx, kvCache);
|
|
200
|
+
this.instanceId = instance.id;
|
|
200
201
|
return;
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
|
-
await this.unloadIncompatibleInstances(instances, ctx, kvCache);
|
|
204
204
|
const body = {
|
|
205
205
|
model: this.model,
|
|
206
206
|
flash_attention: true,
|
|
@@ -220,9 +220,9 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
220
220
|
throw new Error(`LM Studio model load failed: ${response.status} ${errorBody.slice(0, 200)}`);
|
|
221
221
|
}
|
|
222
222
|
const loadResult = await response.json();
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
this.
|
|
223
|
+
const id = loadResult.instance_id ?? loadResult.id ?? loadResult.model_instance_id;
|
|
224
|
+
if (id) {
|
|
225
|
+
this.instanceId = id;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
/**
|
|
@@ -242,27 +242,6 @@ var LmStudioBackend = class _LmStudioBackend {
|
|
|
242
242
|
return [];
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
/**
|
|
246
|
-
* Unload instances of this model that don't match the desired settings.
|
|
247
|
-
* Best-effort — failures are silently ignored to avoid blocking the load path.
|
|
248
|
-
*/
|
|
249
|
-
async unloadIncompatibleInstances(instances, contextLength, gpuKvCache) {
|
|
250
|
-
for (const instance of instances) {
|
|
251
|
-
const matchesContext = !contextLength || instance.config.context_length === contextLength;
|
|
252
|
-
const matchesKvCache = instance.config.offload_kv_cache_to_gpu === gpuKvCache;
|
|
253
|
-
if (!matchesContext || !matchesKvCache) {
|
|
254
|
-
try {
|
|
255
|
-
await fetch(`${this.baseUrl}${ENDPOINT_MODELS_UNLOAD}`, {
|
|
256
|
-
method: "POST",
|
|
257
|
-
headers: { "Content-Type": "application/json" },
|
|
258
|
-
body: JSON.stringify({ model: instance.id }),
|
|
259
|
-
signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS)
|
|
260
|
-
});
|
|
261
|
-
} catch {
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
245
|
async isAvailable() {
|
|
267
246
|
try {
|
|
268
247
|
const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LIST}`, {
|
|
@@ -291,4 +270,4 @@ export {
|
|
|
291
270
|
OllamaBackend,
|
|
292
271
|
LmStudioBackend
|
|
293
272
|
};
|
|
294
|
-
//# sourceMappingURL=chunk-
|
|
273
|
+
//# sourceMappingURL=chunk-JI6M2L2W.js.map
|