@jeffober/thatch 0.1.26 → 0.1.28
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 +7 -7
- package/artifacts/skills/common.md +1 -1
- package/artifacts/skills/thatch-change-walkthrough.md +2 -2
- package/artifacts/skills/thatch-code-archaeology.md +252 -0
- package/artifacts/skills/thatch-code-review.md +4 -4
- package/artifacts/skills/thatch-code-walkthrough.md +2 -2
- package/artifacts/skills/thatch-coding-workflow.md +79 -0
- package/artifacts/skills/thatch-fact-extractor.md +2 -2
- package/artifacts/skills/{pr-description.md → thatch-pr-description.md} +1 -1
- package/artifacts/skills/thatch-review-context.md +9 -0
- package/artifacts/skills/thatch-review-followup.md +29 -1
- package/artifacts/skills/thatch-review-pedantic.md +16 -1
- package/artifacts/skills/thatch-review-response.md +365 -0
- package/artifacts/skills/thatch-review-synthesizer.md +92 -5
- package/artifacts/skills/{split-overlarge-pr.md → thatch-split-overlarge-pr.md} +1 -1
- package/artifacts/skills/{ticket-description.md → thatch-ticket-description.md} +3 -3
- package/bin/release +3 -3
- package/bin/thatch +11 -10
- package/docs/dev/README.md +7 -6
- package/docs/dev/mcp-parity.md +1 -1
- package/docs/dev/skills.md +29 -9
- package/docs/plans/README.md +9 -6
- package/docs/plans/intuition-drives.md +147 -0
- package/docs/plans/nomenclater.md +89 -0
- package/docs/plans/prediction-consolidation.md +361 -0
- package/docs/qa/README.md +20 -3
- package/docs/user/README.md +19 -5
- package/package.json +1 -1
- package/src/db.ts +6 -6
- package/src/embeddings.ts +2 -2
- package/src/extract-queue.ts +4 -4
- package/src/extraction.ts +4 -4
- package/src/git.ts +1 -1
- package/src/hygiene.ts +1 -1
- package/src/index.ts +40 -34
- package/src/mcp.ts +34 -6
- package/src/prompts.ts +96 -85
- package/src/setup.ts +19 -19
- package/src/sideband.ts +3 -3
- package/src/skills.ts +104 -6
- package/src/tool-defs.ts +93 -14
- package/src/tools.ts +12 -2
- package/artifacts/skills/thatch-workflow-research.md +0 -91
- package/docs/in-progress/prediction-engine.md +0 -404
- package/docs/plans/001-memory-store.md +0 -106
- package/docs/plans/002-agent-driven-pipelines.md +0 -59
- package/docs/plans/003-memory-hygiene.md +0 -61
- package/docs/qa/use-cases/README.md +0 -51
- package/docs/qa/use-cases/UC-001-memory-roundtrip.md +0 -20
- package/docs/qa/use-cases/UC-002-dedup-cycle.md +0 -23
- package/docs/qa/use-cases/UC-003-extraction-nudge.md +0 -56
- package/docs/qa/use-cases/UC-004-cli-inspection.md +0 -22
- package/docs/qa/use-cases/UC-005-setup-install.md +0 -34
- package/docs/qa/use-cases/UC-006-cursor-hook-lifecycle.md +0 -32
- package/docs/qa/use-cases/UC-007-recall-nudge.md +0 -31
- package/docs/qa/use-cases/UC-008-hygiene-heartbeat.md +0 -26
- package/docs/qa/use-cases/UC-009-flush-tools-tiers.md +0 -33
- package/docs/qa/use-cases/UC-010-prime.md +0 -25
- package/docs/qa/use-cases/UC-011-write-time-similarity-warning.md +0 -24
- package/docs/qa/use-cases/UC-012-model-migration.md +0 -28
- package/docs/qa/use-cases/UC-013-compaction-context.md +0 -25
- package/docs/qa/use-cases/UC-014-skill-install-drift.md +0 -28
- package/docs/qa/use-cases/UC-015-env-override-matrix.md +0 -28
- package/docs/qa/use-cases/UC-016-concurrent-session-isolation.md +0 -37
- package/docs/qa/use-cases/UC-017-buffer-tool-vs-buffer-batch.md +0 -27
- package/docs/qa/use-cases/UC-018-mcp-startup-setup-detection.md +0 -38
- package/docs/qa/use-cases/UC-019-archived-memory-lifecycle.md +0 -36
- package/docs/qa/use-cases/UC-020-extraction-escalation.md +0 -52
- package/docs/qa/use-cases/UC-021-prediction-autofire.md +0 -21
- package/docs/qa/use-cases/UC-022-prediction-dedup.md +0 -19
- package/docs/qa/use-cases/UC-023-prediction-confidence-model.md +0 -23
- package/docs/qa/use-cases/UC-024-prediction-delete.md +0 -20
package/src/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { installSkills, SHARED_SKILLS, OPENCODE_ONLY_SKILLS } from "./skills";
|
|
|
19
19
|
import { hygieneReport } from "./hygiene";
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
|
-
// V1 server export
|
|
22
|
+
// V1 server export - tools, prompt injection, session hooks
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
|
|
25
25
|
// Minimum cosine score for the prompt-aware recall nudge. Lower than
|
|
@@ -27,7 +27,7 @@ import { hygieneReport } from "./hygiene";
|
|
|
27
27
|
// than "duplicate." Tunable via THATCH_RECALL_THRESHOLD.
|
|
28
28
|
const RECALL_THRESHOLD = parseFloat(process.env.THATCH_RECALL_THRESHOLD ?? "0.55");
|
|
29
29
|
|
|
30
|
-
// Prompts shorter than this skip the recall nudge
|
|
30
|
+
// Prompts shorter than this skip the recall nudge - trivially short prompts
|
|
31
31
|
// like "yes" or "ok" match too broadly to be useful.
|
|
32
32
|
const MIN_PROMPT_LEN = 10;
|
|
33
33
|
|
|
@@ -50,12 +50,12 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
50
50
|
const extraction = new ExtractionPipeline();
|
|
51
51
|
|
|
52
52
|
// Sessions currently being compacted. chat.message nudges are skipped while
|
|
53
|
-
// a session is in this set
|
|
53
|
+
// a session is in this set - the agent can't call tools during summary
|
|
54
54
|
// generation, so a recall or extraction nudge would cause a blocked-tool
|
|
55
55
|
// error. Cleared by experimental.compaction.autocontinue (success), the
|
|
56
56
|
// session.compacted event (redundant belt-and-suspenders), or chat.message
|
|
57
57
|
// itself when a non-compaction message arrives (compaction failure fallback
|
|
58
|
-
//
|
|
58
|
+
// - autocontinue never fired, so the next real user message clears the
|
|
59
59
|
// stale flag). If all three somehow miss, the flag leaks (graceful
|
|
60
60
|
// degradation: nudges stay off for that session, but no crash).
|
|
61
61
|
const compacting = new Set<string>();
|
|
@@ -78,7 +78,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
78
78
|
// When the child writes a memory, consumeSnapshot drains only these entries
|
|
79
79
|
// (by reference identity), preserving interleaved-turn entries that arrived
|
|
80
80
|
// while the sub-agent was running. Without this, the child's memory_remember
|
|
81
|
-
// would drain the parent's ENTIRE buffer
|
|
81
|
+
// would drain the parent's ENTIRE buffer - silently dropping facts from
|
|
82
82
|
// any tool calls the parent made concurrently with the sub-agent.
|
|
83
83
|
const parentSnapshots = new Map<string, ToolInteraction[]>();
|
|
84
84
|
|
|
@@ -108,7 +108,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
108
108
|
// Keyed by child session ID. Cleaned up on child idle, error, or deletion.
|
|
109
109
|
const childMetrics = new Map<string, { new: number; updated: number; deleted: number }>();
|
|
110
110
|
|
|
111
|
-
// Skills always install to the global opencode config
|
|
111
|
+
// Skills always install to the global opencode config - installing into the
|
|
112
112
|
// worktree would mutate the user's repo (untracked files in git status).
|
|
113
113
|
// A failed install degrades the nudge workflow but must not kill the plugin.
|
|
114
114
|
try {
|
|
@@ -129,7 +129,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
129
129
|
// drain machinery handles buffer cleanup. The nudge path is a fallback if
|
|
130
130
|
// this throws.
|
|
131
131
|
//
|
|
132
|
-
// Does NOT call extraction.accept
|
|
132
|
+
// Does NOT call extraction.accept - entries stay in pending so consumeSnapshot
|
|
133
133
|
// can drain them by reference identity when the child writes a memory. The
|
|
134
134
|
// extracting set (not accept) suppresses the nudge in chat.message.
|
|
135
135
|
//
|
|
@@ -141,8 +141,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
141
141
|
extracting.add(parentID);
|
|
142
142
|
|
|
143
143
|
const batch = extraction.peek(parentID);
|
|
144
|
-
const
|
|
145
|
-
const promptText = extractionDirectPrompt(batch.length, payload);
|
|
144
|
+
const promptText = extractionDirectPrompt(batch.length, parentID);
|
|
146
145
|
|
|
147
146
|
const result = await client.session.create({
|
|
148
147
|
body: { parentID, title: "thatch-extraction" },
|
|
@@ -183,7 +182,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
183
182
|
throw err;
|
|
184
183
|
}
|
|
185
184
|
} else {
|
|
186
|
-
// Fire and forget
|
|
185
|
+
// Fire and forget - the parent is already idle, so blocking the event
|
|
187
186
|
// handler would only delay other event processing. The child runs to
|
|
188
187
|
// completion and its idle event triggers cleanup.
|
|
189
188
|
client.session
|
|
@@ -201,14 +200,22 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
201
200
|
}
|
|
202
201
|
|
|
203
202
|
return {
|
|
204
|
-
tool: createTools(db, model, repo
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
tool: createTools(db, model, repo, {
|
|
204
|
+
extractionPayloadProvider: (sessionID: string): string | null => {
|
|
205
|
+
const interactions = extraction.peek(sessionID);
|
|
206
|
+
const accepted = extraction.peekAccepted(sessionID);
|
|
207
|
+
const all = [...accepted, ...interactions];
|
|
208
|
+
if (all.length === 0) return null;
|
|
209
|
+
return extraction.buildPayload(all, repo);
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
|
|
213
|
+
// 1. System prompt - always in context.
|
|
207
214
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
208
215
|
output.system.push(sys);
|
|
209
216
|
},
|
|
210
217
|
|
|
211
|
-
// 2. Compaction context
|
|
218
|
+
// 2. Compaction context - re-familiarizes after compaction. The flag
|
|
212
219
|
// suppresses chat.message nudges during summary generation (tool
|
|
213
220
|
// calls are blocked there).
|
|
214
221
|
"experimental.session.compacting": async (input, output) => {
|
|
@@ -222,15 +229,15 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
222
229
|
compacting.delete(input.sessionID);
|
|
223
230
|
},
|
|
224
231
|
|
|
225
|
-
// 3. Tool buffering
|
|
232
|
+
// 3. Tool buffering - feeds the extraction pipeline (direct extraction
|
|
226
233
|
// via SDK or nudge fallback). Excluded tools:
|
|
227
234
|
// - thatch_*: extracting facts from memory ops would echo the store
|
|
228
235
|
// - skill/task: meta-tools that orchestrate agent behavior (loading
|
|
229
236
|
// skills, dispatching sub-agents). Buffering them creates a feedback
|
|
230
|
-
// loop
|
|
237
|
+
// loop - extraction triggers a skill load, which gets buffered, which
|
|
231
238
|
// triggers another extraction on the next turn.
|
|
232
239
|
// Memory writes consume the buffer and reset the missed-nudge counter.
|
|
233
|
-
// The buffer is NOT drained on nudge delivery
|
|
240
|
+
// The buffer is NOT drained on nudge delivery - it persists until the
|
|
234
241
|
// agent writes a memory, so ignored nudges accumulate and the payload
|
|
235
242
|
// grows with each missed cycle.
|
|
236
243
|
//
|
|
@@ -247,7 +254,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
247
254
|
// session errors or is deleted before completing.
|
|
248
255
|
//
|
|
249
256
|
// A memory_remember call in a child session (sub-agent) also drains
|
|
250
|
-
// the parent's pending buffer
|
|
257
|
+
// the parent's pending buffer - but only the entries that existed at
|
|
251
258
|
// dispatch time (the snapshot). Entries from interleaved turns survive
|
|
252
259
|
// so their facts aren't silently dropped.
|
|
253
260
|
"tool.execute.after": async (input, output) => {
|
|
@@ -269,7 +276,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
269
276
|
// from the pending buffer. If no snapshot was recorded (unreachable
|
|
270
277
|
// when childToParent has the entry, since both are set together in
|
|
271
278
|
// session.created), skip the drain rather than dropping the entire
|
|
272
|
-
// buffer
|
|
279
|
+
// buffer - interleaved-turn entries that arrived while the child
|
|
273
280
|
// was running must survive for the next extraction cycle.
|
|
274
281
|
extraction.completeAccepted(parentID);
|
|
275
282
|
const snapshot = parentSnapshots.get(input.sessionID);
|
|
@@ -300,7 +307,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
300
307
|
return;
|
|
301
308
|
}
|
|
302
309
|
// Track memory deletions in child sessions for the toast metrics.
|
|
303
|
-
// Same scoping as the remember handler above
|
|
310
|
+
// Same scoping as the remember handler above - only child sessions,
|
|
304
311
|
// not the parent or manual memory writes from the user's session.
|
|
305
312
|
if (input.tool === "thatch_memory_forget" && childToParent.has(input.sessionID)) {
|
|
306
313
|
const metrics = childMetrics.get(input.sessionID) ?? { new: 0, updated: 0, deleted: 0 };
|
|
@@ -318,7 +325,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
318
325
|
});
|
|
319
326
|
},
|
|
320
327
|
|
|
321
|
-
// 4. Per-message nudge
|
|
328
|
+
// 4. Per-message nudge - two priority tiers:
|
|
322
329
|
// a. Extraction nudge: prior tool interactions are queued for fact
|
|
323
330
|
// extraction (carries the JSON payload for thatch-fact-extractor).
|
|
324
331
|
// b. Recall + prediction nudge: when no extraction is pending, embed
|
|
@@ -337,24 +344,23 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
337
344
|
if (compacting.has(input.sessionID)) {
|
|
338
345
|
// The session is marked as compacting. If this message is the
|
|
339
346
|
// compaction summary generation itself (has a compaction-type part),
|
|
340
|
-
// suppress nudges
|
|
347
|
+
// suppress nudges - tools are blocked during summary generation and a
|
|
341
348
|
// nudge would cause a blocked-tool error. If it is NOT a compaction
|
|
342
349
|
// message, compaction has already failed (autocontinue never fired)
|
|
343
350
|
// and the user is sending a new message. Clear the stale flag and
|
|
344
|
-
// proceed normally
|
|
351
|
+
// proceed normally - tools are available again.
|
|
345
352
|
const isCompactionMsg = (output.parts as any[]).some((p) => p.type === "compaction");
|
|
346
353
|
if (isCompactionMsg) return;
|
|
347
354
|
compacting.delete(input.sessionID);
|
|
348
355
|
}
|
|
349
356
|
// Extraction nudge (fallback path). Skipped when the extracting set
|
|
350
|
-
// is active
|
|
357
|
+
// is active - that means a direct-extraction child session is running
|
|
351
358
|
// and the plugin is handling extraction via the SDK. The nudge fires
|
|
352
359
|
// here only when direct extraction was never triggered or threw.
|
|
353
360
|
if (!extracting.has(input.sessionID) && extraction.pending(input.sessionID)) {
|
|
354
361
|
const batch = extraction.peek(input.sessionID);
|
|
355
|
-
const payload = extraction.buildPayload(batch, repo);
|
|
356
362
|
const missed = missedNudges.get(input.sessionID) ?? 0;
|
|
357
|
-
const text = extractionNudge(batch.length, missed, "thatch_memory_remember",
|
|
363
|
+
const text = extractionNudge(batch.length, missed, "thatch_memory_remember", input.sessionID);
|
|
358
364
|
missedNudges.set(input.sessionID, missed + 1);
|
|
359
365
|
|
|
360
366
|
output.parts.push({
|
|
@@ -368,7 +374,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
368
374
|
return;
|
|
369
375
|
}
|
|
370
376
|
|
|
371
|
-
// No extraction pending
|
|
377
|
+
// No extraction pending - try the prompt-aware recall nudge. Extract
|
|
372
378
|
// the user's prompt text from the message parts, embed it with the
|
|
373
379
|
// warm in-process model, and search for matches. Best-effort: any
|
|
374
380
|
// failure (no text, model not loaded, empty store) silently skips.
|
|
@@ -469,7 +475,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
469
475
|
if (info.parentID) {
|
|
470
476
|
childToParent.set(info.id, info.parentID);
|
|
471
477
|
parentSnapshots.set(info.id, [...extraction.peek(info.parentID)]);
|
|
472
|
-
// Child sessions don't need the session-start reminder
|
|
478
|
+
// Child sessions don't need the session-start reminder - only
|
|
473
479
|
// top-level sessions do. Returning here prevents extraction
|
|
474
480
|
// children from receiving the hygiene report and tool overview.
|
|
475
481
|
return;
|
|
@@ -499,23 +505,23 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
499
505
|
// - Task-dispatched sub-agent (code review specialist, nudge-path
|
|
500
506
|
// fact-extractor, any model-dispatched task): complete the
|
|
501
507
|
// parent's accepted entries and reset missedNudges. Do NOT drain
|
|
502
|
-
// the buffer or delete the session
|
|
508
|
+
// the buffer or delete the session - the task tool that
|
|
503
509
|
// dispatched the sub-agent needs to read its output.
|
|
504
510
|
if (extractionChildren.has(sessionID)) {
|
|
505
511
|
// Drain the parent's snapshot entries from the pending buffer.
|
|
506
512
|
// If the child wrote memories, consumeSnapshot already ran in
|
|
507
|
-
// tool.execute.after and the snapshot is gone
|
|
513
|
+
// tool.execute.after and the snapshot is gone - nothing to
|
|
508
514
|
// drain. If the child did a no-save run, the snapshot entries
|
|
509
515
|
// are still in the buffer and need to be drained here so they
|
|
510
516
|
// don't replay as a nudge on the next chat.message. Never drain
|
|
511
|
-
// the entire buffer
|
|
517
|
+
// the entire buffer - interleaved-turn entries must survive.
|
|
512
518
|
const snapshot = parentSnapshots.get(sessionID);
|
|
513
519
|
if (snapshot) {
|
|
514
520
|
extraction.consumeSnapshot(parentID, snapshot);
|
|
515
521
|
}
|
|
516
522
|
|
|
517
523
|
// Fire a toast with the extraction metrics. Only show a toast
|
|
518
|
-
// when memories were actually written
|
|
524
|
+
// when memories were actually written - no toast for no-save
|
|
519
525
|
// runs to avoid notification fatigue.
|
|
520
526
|
const metrics = childMetrics.get(sessionID);
|
|
521
527
|
const parts: string[] = [];
|
|
@@ -546,7 +552,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
546
552
|
try {
|
|
547
553
|
await client.session.delete({ path: { id: sessionID } });
|
|
548
554
|
} catch {
|
|
549
|
-
// Best-effort
|
|
555
|
+
// Best-effort - the child is idle and harmless if not deleted.
|
|
550
556
|
}
|
|
551
557
|
} else {
|
|
552
558
|
// Task-dispatched sub-agent went idle. Complete the parent's
|
|
@@ -557,7 +563,7 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
557
563
|
}
|
|
558
564
|
return;
|
|
559
565
|
}
|
|
560
|
-
// Parent went idle
|
|
566
|
+
// Parent went idle - trigger direct extraction if there are pending
|
|
561
567
|
// tool interactions and no extraction is already running. Falls
|
|
562
568
|
// back to the nudge path on the next chat.message if this throws.
|
|
563
569
|
if (
|
package/src/mcp.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { detectRepo } from "./git";
|
|
|
5
5
|
import { checkSetup } from "./setup";
|
|
6
6
|
import { TOOL_DEFS, type CoreContext, type ToolDef } from "./tool-defs";
|
|
7
7
|
import { SidebandServer, sidebandSocketPath } from "./sideband";
|
|
8
|
+
import { peekQueue, consumeQueue, resetMissedCount } from "./extract-queue";
|
|
9
|
+
import { buildExtractionPayload } from "./extraction";
|
|
8
10
|
import pkg from "../package.json";
|
|
9
11
|
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
@@ -39,14 +41,14 @@ const SERVER_VERSION = pkg.version;
|
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* MCP capabilities declared in the initialize response. Thatch is a tools-only
|
|
42
|
-
* server
|
|
44
|
+
* server - no resources, prompts, or subscriptions.
|
|
43
45
|
*/
|
|
44
46
|
const CAPABILITIES = {
|
|
45
47
|
tools: { listChanged: false },
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
// ---------------------------------------------------------------------------
|
|
49
|
-
// Tool index
|
|
51
|
+
// Tool index - builds validators and JSON Schema once at server startup
|
|
50
52
|
// ---------------------------------------------------------------------------
|
|
51
53
|
|
|
52
54
|
interface CompiledTool {
|
|
@@ -74,7 +76,7 @@ function compileTools(): Map<string, CompiledTool> {
|
|
|
74
76
|
|
|
75
77
|
/**
|
|
76
78
|
* Runs the thatch MCP server over stdio. Reads newline-delimited JSON-RPC from
|
|
77
|
-
* stdin, writes responses to stdout. All diagnostics go to stderr
|
|
79
|
+
* stdin, writes responses to stdout. All diagnostics go to stderr - any stray
|
|
78
80
|
* output on stdout corrupts the protocol.
|
|
79
81
|
*
|
|
80
82
|
* Repo identity is resolved from CLAUDE_PROJECT_DIR (set by Claude Code for
|
|
@@ -98,7 +100,33 @@ export async function runMcpServer(): Promise<void> {
|
|
|
98
100
|
|
|
99
101
|
const db = new ThatchDB(dbPath);
|
|
100
102
|
const model = new BgeEmbeddingModel(modelName);
|
|
101
|
-
|
|
103
|
+
|
|
104
|
+
// Extraction payload provider: peeks the file-backed queue and returns the
|
|
105
|
+
// serialized JSON payload. Called by the get_extraction_payload tool when a
|
|
106
|
+
// sub-agent needs to fetch the parent's queued interactions. Returns null
|
|
107
|
+
// when no interactions are queued.
|
|
108
|
+
const extractionPayloadProvider = (sessionID: string): string | null => {
|
|
109
|
+
const interactions = peekQueue(sessionID);
|
|
110
|
+
if (interactions.length === 0) return null;
|
|
111
|
+
return buildExtractionPayload(interactions, repo);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Drain a session's file-backed queue and reset its missed-nudge counter.
|
|
115
|
+
// Called by extraction_done when a sub-agent passes the parent's session_id.
|
|
116
|
+
// Without this, the MCP path's appendBatch self-detection would drain the
|
|
117
|
+
// sub-agent's (empty) queue instead of the parent's.
|
|
118
|
+
const drainExtractionQueue = (sessionID: string): void => {
|
|
119
|
+
resetMissedCount(sessionID);
|
|
120
|
+
consumeQueue(sessionID);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const ctx: CoreContext = {
|
|
124
|
+
db,
|
|
125
|
+
model,
|
|
126
|
+
defaultStore: repo,
|
|
127
|
+
extractionPayloadProvider,
|
|
128
|
+
drainExtractionQueue,
|
|
129
|
+
};
|
|
102
130
|
const tools = compileTools();
|
|
103
131
|
|
|
104
132
|
// Check whether `thatch setup` was run for the current host. If not, or if
|
|
@@ -113,7 +141,7 @@ export async function runMcpServer(): Promise<void> {
|
|
|
113
141
|
}
|
|
114
142
|
|
|
115
143
|
// The sideband socket lets one-shot hook processes (flush-tools) ask the
|
|
116
|
-
// warm MCP server to embed a prompt and search for matches
|
|
144
|
+
// warm MCP server to embed a prompt and search for matches - without
|
|
117
145
|
// loading the ~34 MB model themselves. If the socket can't be opened
|
|
118
146
|
// (permissions, tmpdir issues), the MCP server still works; only the
|
|
119
147
|
// prompt-aware recall nudge degrades.
|
|
@@ -146,7 +174,7 @@ export async function runMcpServer(): Promise<void> {
|
|
|
146
174
|
continue;
|
|
147
175
|
}
|
|
148
176
|
|
|
149
|
-
// Notifications (no id) are fire-and-forget
|
|
177
|
+
// Notifications (no id) are fire-and-forget - no response expected.
|
|
150
178
|
const isNotification = req.id === null || req.id === undefined;
|
|
151
179
|
const res = await dispatch(req, tools, ctx);
|
|
152
180
|
if (res === null || isNotification) continue;
|