@goondocks/myco 0.2.7 → 0.2.9
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/plugin.json +1 -1
- package/CONTRIBUTING.md +1 -1
- package/commands/init.md +10 -26
- package/dist/chunk-4JML636J.js +52 -0
- package/dist/chunk-4JML636J.js.map +1 -0
- package/dist/chunk-AOMX45LH.js +8974 -0
- package/dist/chunk-AOMX45LH.js.map +1 -0
- package/dist/chunk-I7PMGO6S.js +58 -0
- package/dist/chunk-I7PMGO6S.js.map +1 -0
- package/dist/chunk-N33KUCFP.js +33 -0
- package/dist/chunk-N33KUCFP.js.map +1 -0
- package/dist/chunk-NYNEJ5QY.js +71 -0
- package/dist/chunk-NYNEJ5QY.js.map +1 -0
- package/dist/chunk-PA3VMINE.js +111 -0
- package/dist/chunk-PA3VMINE.js.map +1 -0
- package/dist/chunk-PZUWP5VK.js +44 -0
- package/dist/chunk-PZUWP5VK.js.map +1 -0
- package/dist/chunk-SVUINMDD.js +104 -0
- package/dist/chunk-SVUINMDD.js.map +1 -0
- package/dist/chunk-TH6GIBXG.js +91 -0
- package/dist/chunk-TH6GIBXG.js.map +1 -0
- package/dist/chunk-TWDS6MSU.js +354 -0
- package/dist/chunk-TWDS6MSU.js.map +1 -0
- package/dist/chunk-UIIZRTJU.js +21172 -0
- package/dist/chunk-UIIZRTJU.js.map +1 -0
- package/dist/chunk-YMYJ7FNH.js +19 -0
- package/dist/chunk-YMYJ7FNH.js.map +1 -0
- package/dist/chunk-ZJQ5G637.js +21 -0
- package/dist/chunk-ZJQ5G637.js.map +1 -0
- package/dist/chunk-ZTZVX5E6.js +421 -0
- package/dist/chunk-ZTZVX5E6.js.map +1 -0
- package/dist/cli-K5FSKLQC.js +625 -0
- package/dist/cli-K5FSKLQC.js.map +1 -0
- package/dist/client-4JMOYNKK.js +11 -0
- package/dist/client-4JMOYNKK.js.map +1 -0
- package/dist/main-5W4ADOBG.js +3224 -0
- package/dist/main-5W4ADOBG.js.map +1 -0
- package/dist/server-PIEPVUUH.js +14725 -0
- package/dist/server-PIEPVUUH.js.map +1 -0
- package/dist/session-start-2NNQHT5S.js +189 -0
- package/dist/session-start-2NNQHT5S.js.map +1 -0
- package/dist/src/cli.js +9 -582
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon/main.js +9 -737
- package/dist/src/daemon/main.js.map +1 -1
- package/dist/src/hooks/post-tool-use.js +47 -35
- package/dist/src/hooks/post-tool-use.js.map +1 -1
- package/dist/src/hooks/session-end.js +29 -18
- package/dist/src/hooks/session-end.js.map +1 -1
- package/dist/src/hooks/session-start.js +9 -48
- package/dist/src/hooks/session-start.js.map +1 -1
- package/dist/src/hooks/stop.js +39 -30
- package/dist/src/hooks/stop.js.map +1 -1
- package/dist/src/hooks/user-prompt-submit.js +48 -40
- package/dist/src/hooks/user-prompt-submit.js.map +1 -1
- package/dist/src/mcp/server.js +9 -304
- package/dist/src/mcp/server.js.map +1 -1
- package/package.json +3 -2
- package/dist/src/agents/adapter.d.ts +0 -76
- package/dist/src/agents/adapter.d.ts.map +0 -1
- package/dist/src/agents/adapter.js +0 -124
- package/dist/src/agents/adapter.js.map +0 -1
- package/dist/src/agents/claude-code.d.ts +0 -3
- package/dist/src/agents/claude-code.d.ts.map +0 -1
- package/dist/src/agents/claude-code.js +0 -22
- package/dist/src/agents/claude-code.js.map +0 -1
- package/dist/src/agents/cursor.d.ts +0 -3
- package/dist/src/agents/cursor.d.ts.map +0 -1
- package/dist/src/agents/cursor.js +0 -154
- package/dist/src/agents/cursor.js.map +0 -1
- package/dist/src/agents/index.d.ts +0 -6
- package/dist/src/agents/index.d.ts.map +0 -1
- package/dist/src/agents/index.js +0 -5
- package/dist/src/agents/index.js.map +0 -1
- package/dist/src/agents/registry.d.ts +0 -34
- package/dist/src/agents/registry.d.ts.map +0 -1
- package/dist/src/agents/registry.js +0 -95
- package/dist/src/agents/registry.js.map +0 -1
- package/dist/src/artifacts/candidates.d.ts +0 -20
- package/dist/src/artifacts/candidates.d.ts.map +0 -1
- package/dist/src/artifacts/candidates.js +0 -84
- package/dist/src/artifacts/candidates.js.map +0 -1
- package/dist/src/artifacts/slugify.d.ts +0 -2
- package/dist/src/artifacts/slugify.d.ts.map +0 -1
- package/dist/src/artifacts/slugify.js +0 -22
- package/dist/src/artifacts/slugify.js.map +0 -1
- package/dist/src/capture/buffer.d.ts +0 -20
- package/dist/src/capture/buffer.d.ts.map +0 -1
- package/dist/src/capture/buffer.js +0 -55
- package/dist/src/capture/buffer.js.map +0 -1
- package/dist/src/capture/transcript-miner.d.ts +0 -31
- package/dist/src/capture/transcript-miner.d.ts.map +0 -1
- package/dist/src/capture/transcript-miner.js +0 -61
- package/dist/src/capture/transcript-miner.js.map +0 -1
- package/dist/src/cli.d.ts +0 -3
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/config/loader.d.ts +0 -4
- package/dist/src/config/loader.d.ts.map +0 -1
- package/dist/src/config/loader.js +0 -32
- package/dist/src/config/loader.js.map +0 -1
- package/dist/src/config/schema.d.ts +0 -83
- package/dist/src/config/schema.d.ts.map +0 -1
- package/dist/src/config/schema.js +0 -55
- package/dist/src/config/schema.js.map +0 -1
- package/dist/src/constants.d.ts +0 -73
- package/dist/src/constants.d.ts.map +0 -1
- package/dist/src/constants.js +0 -86
- package/dist/src/constants.js.map +0 -1
- package/dist/src/context/injector.d.ts +0 -18
- package/dist/src/context/injector.d.ts.map +0 -1
- package/dist/src/context/injector.js +0 -71
- package/dist/src/context/injector.js.map +0 -1
- package/dist/src/context/relevance.d.ts +0 -13
- package/dist/src/context/relevance.d.ts.map +0 -1
- package/dist/src/context/relevance.js +0 -44
- package/dist/src/context/relevance.js.map +0 -1
- package/dist/src/daemon/batch.d.ts +0 -22
- package/dist/src/daemon/batch.d.ts.map +0 -1
- package/dist/src/daemon/batch.js +0 -38
- package/dist/src/daemon/batch.js.map +0 -1
- package/dist/src/daemon/lifecycle.d.ts +0 -27
- package/dist/src/daemon/lifecycle.d.ts.map +0 -1
- package/dist/src/daemon/lifecycle.js +0 -50
- package/dist/src/daemon/lifecycle.js.map +0 -1
- package/dist/src/daemon/lineage.d.ts +0 -42
- package/dist/src/daemon/lineage.d.ts.map +0 -1
- package/dist/src/daemon/lineage.js +0 -116
- package/dist/src/daemon/lineage.js.map +0 -1
- package/dist/src/daemon/logger.d.ts +0 -33
- package/dist/src/daemon/logger.d.ts.map +0 -1
- package/dist/src/daemon/logger.js +0 -88
- package/dist/src/daemon/logger.js.map +0 -1
- package/dist/src/daemon/main.d.ts +0 -2
- package/dist/src/daemon/main.d.ts.map +0 -1
- package/dist/src/daemon/processor.d.ts +0 -44
- package/dist/src/daemon/processor.d.ts.map +0 -1
- package/dist/src/daemon/processor.js +0 -142
- package/dist/src/daemon/processor.js.map +0 -1
- package/dist/src/daemon/server.d.ts +0 -24
- package/dist/src/daemon/server.d.ts.map +0 -1
- package/dist/src/daemon/server.js +0 -117
- package/dist/src/daemon/server.js.map +0 -1
- package/dist/src/daemon/watcher.d.ts +0 -29
- package/dist/src/daemon/watcher.d.ts.map +0 -1
- package/dist/src/daemon/watcher.js +0 -67
- package/dist/src/daemon/watcher.js.map +0 -1
- package/dist/src/hooks/client.d.ts +0 -20
- package/dist/src/hooks/client.d.ts.map +0 -1
- package/dist/src/hooks/client.js +0 -111
- package/dist/src/hooks/client.js.map +0 -1
- package/dist/src/hooks/post-tool-use.d.ts +0 -2
- package/dist/src/hooks/post-tool-use.d.ts.map +0 -1
- package/dist/src/hooks/read-stdin.d.ts +0 -2
- package/dist/src/hooks/read-stdin.d.ts.map +0 -1
- package/dist/src/hooks/read-stdin.js +0 -10
- package/dist/src/hooks/read-stdin.js.map +0 -1
- package/dist/src/hooks/session-end.d.ts +0 -2
- package/dist/src/hooks/session-end.d.ts.map +0 -1
- package/dist/src/hooks/session-start.d.ts +0 -2
- package/dist/src/hooks/session-start.d.ts.map +0 -1
- package/dist/src/hooks/stop.d.ts +0 -2
- package/dist/src/hooks/stop.d.ts.map +0 -1
- package/dist/src/hooks/user-prompt-submit.d.ts +0 -2
- package/dist/src/hooks/user-prompt-submit.d.ts.map +0 -1
- package/dist/src/index/fts.d.ts +0 -16
- package/dist/src/index/fts.d.ts.map +0 -1
- package/dist/src/index/fts.js +0 -53
- package/dist/src/index/fts.js.map +0 -1
- package/dist/src/index/rebuild.d.ts +0 -4
- package/dist/src/index/rebuild.d.ts.map +0 -1
- package/dist/src/index/rebuild.js +0 -40
- package/dist/src/index/rebuild.js.map +0 -1
- package/dist/src/index/sqlite.d.ts +0 -33
- package/dist/src/index/sqlite.d.ts.map +0 -1
- package/dist/src/index/sqlite.js +0 -99
- package/dist/src/index/sqlite.js.map +0 -1
- package/dist/src/index/vectors.d.ts +0 -24
- package/dist/src/index/vectors.d.ts.map +0 -1
- package/dist/src/index/vectors.js +0 -97
- package/dist/src/index/vectors.js.map +0 -1
- package/dist/src/intelligence/anthropic.d.ts +0 -17
- package/dist/src/intelligence/anthropic.d.ts.map +0 -1
- package/dist/src/intelligence/anthropic.js +0 -36
- package/dist/src/intelligence/anthropic.js.map +0 -1
- package/dist/src/intelligence/embeddings.d.ts +0 -3
- package/dist/src/intelligence/embeddings.d.ts.map +0 -1
- package/dist/src/intelligence/embeddings.js +0 -15
- package/dist/src/intelligence/embeddings.js.map +0 -1
- package/dist/src/intelligence/llm.d.ts +0 -33
- package/dist/src/intelligence/llm.d.ts.map +0 -1
- package/dist/src/intelligence/llm.js +0 -26
- package/dist/src/intelligence/llm.js.map +0 -1
- package/dist/src/intelligence/lm-studio.d.ts +0 -20
- package/dist/src/intelligence/lm-studio.d.ts.map +0 -1
- package/dist/src/intelligence/lm-studio.js +0 -59
- package/dist/src/intelligence/lm-studio.js.map +0 -1
- package/dist/src/intelligence/ollama.d.ts +0 -22
- package/dist/src/intelligence/ollama.d.ts.map +0 -1
- package/dist/src/intelligence/ollama.js +0 -64
- package/dist/src/intelligence/ollama.js.map +0 -1
- package/dist/src/intelligence/response.d.ts +0 -29
- package/dist/src/intelligence/response.d.ts.map +0 -1
- package/dist/src/intelligence/response.js +0 -71
- package/dist/src/intelligence/response.js.map +0 -1
- package/dist/src/logs/format.d.ts +0 -6
- package/dist/src/logs/format.d.ts.map +0 -1
- package/dist/src/logs/format.js +0 -46
- package/dist/src/logs/format.js.map +0 -1
- package/dist/src/logs/reader.d.ts +0 -28
- package/dist/src/logs/reader.d.ts.map +0 -1
- package/dist/src/logs/reader.js +0 -106
- package/dist/src/logs/reader.js.map +0 -1
- package/dist/src/mcp/server.d.ts +0 -16
- package/dist/src/mcp/server.d.ts.map +0 -1
- package/dist/src/mcp/tools/consolidate.d.ts +0 -15
- package/dist/src/mcp/tools/consolidate.d.ts.map +0 -1
- package/dist/src/mcp/tools/consolidate.js +0 -49
- package/dist/src/mcp/tools/consolidate.js.map +0 -1
- package/dist/src/mcp/tools/graph.d.ts +0 -30
- package/dist/src/mcp/tools/graph.d.ts.map +0 -1
- package/dist/src/mcp/tools/graph.js +0 -106
- package/dist/src/mcp/tools/graph.js.map +0 -1
- package/dist/src/mcp/tools/logs.d.ts +0 -3
- package/dist/src/mcp/tools/logs.d.ts.map +0 -1
- package/dist/src/mcp/tools/logs.js +0 -7
- package/dist/src/mcp/tools/logs.js.map +0 -1
- package/dist/src/mcp/tools/plans.d.ts +0 -23
- package/dist/src/mcp/tools/plans.d.ts.map +0 -1
- package/dist/src/mcp/tools/plans.js +0 -63
- package/dist/src/mcp/tools/plans.js.map +0 -1
- package/dist/src/mcp/tools/recall.d.ts +0 -30
- package/dist/src/mcp/tools/recall.d.ts.map +0 -1
- package/dist/src/mcp/tools/recall.js +0 -34
- package/dist/src/mcp/tools/recall.js.map +0 -1
- package/dist/src/mcp/tools/remember.d.ts +0 -15
- package/dist/src/mcp/tools/remember.d.ts.map +0 -1
- package/dist/src/mcp/tools/remember.js +0 -18
- package/dist/src/mcp/tools/remember.js.map +0 -1
- package/dist/src/mcp/tools/search.d.ts +0 -19
- package/dist/src/mcp/tools/search.d.ts.map +0 -1
- package/dist/src/mcp/tools/search.js +0 -59
- package/dist/src/mcp/tools/search.js.map +0 -1
- package/dist/src/mcp/tools/sessions.d.ts +0 -21
- package/dist/src/mcp/tools/sessions.d.ts.map +0 -1
- package/dist/src/mcp/tools/sessions.js +0 -36
- package/dist/src/mcp/tools/sessions.js.map +0 -1
- package/dist/src/mcp/tools/supersede.d.ts +0 -14
- package/dist/src/mcp/tools/supersede.d.ts.map +0 -1
- package/dist/src/mcp/tools/supersede.js +0 -30
- package/dist/src/mcp/tools/supersede.js.map +0 -1
- package/dist/src/mcp/tools/team.d.ts +0 -16
- package/dist/src/mcp/tools/team.d.ts.map +0 -1
- package/dist/src/mcp/tools/team.js +0 -32
- package/dist/src/mcp/tools/team.js.map +0 -1
- package/dist/src/obsidian/formatter.d.ts +0 -80
- package/dist/src/obsidian/formatter.d.ts.map +0 -1
- package/dist/src/obsidian/formatter.js +0 -227
- package/dist/src/obsidian/formatter.js.map +0 -1
- package/dist/src/prompts/index.d.ts +0 -13
- package/dist/src/prompts/index.d.ts.map +0 -1
- package/dist/src/prompts/index.js +0 -75
- package/dist/src/prompts/index.js.map +0 -1
- package/dist/src/vault/frontmatter.d.ts +0 -6
- package/dist/src/vault/frontmatter.d.ts.map +0 -1
- package/dist/src/vault/frontmatter.js +0 -10
- package/dist/src/vault/frontmatter.js.map +0 -1
- package/dist/src/vault/observations.d.ts +0 -10
- package/dist/src/vault/observations.d.ts.map +0 -1
- package/dist/src/vault/observations.js +0 -33
- package/dist/src/vault/observations.js.map +0 -1
- package/dist/src/vault/reader.d.ts +0 -10
- package/dist/src/vault/reader.d.ts.map +0 -1
- package/dist/src/vault/reader.js +0 -48
- package/dist/src/vault/reader.js.map +0 -1
- package/dist/src/vault/resolve.d.ts +0 -18
- package/dist/src/vault/resolve.d.ts.map +0 -1
- package/dist/src/vault/resolve.js +0 -51
- package/dist/src/vault/resolve.js.map +0 -1
- package/dist/src/vault/session-id.d.ts +0 -16
- package/dist/src/vault/session-id.d.ts.map +0 -1
- package/dist/src/vault/session-id.js +0 -29
- package/dist/src/vault/session-id.js.map +0 -1
- package/dist/src/vault/types.d.ts +0 -88
- package/dist/src/vault/types.d.ts.map +0 -1
- package/dist/src/vault/types.js +0 -94
- package/dist/src/vault/types.js.map +0 -1
- package/dist/src/vault/writer.d.ts +0 -66
- package/dist/src/vault/writer.d.ts.map +0 -1
- package/dist/src/vault/writer.js +0 -217
- package/dist/src/vault/writer.js.map +0 -1
package/dist/src/daemon/main.js
CHANGED
|
@@ -1,738 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import { initFts } from '../index/fts.js';
|
|
11
|
-
import { createLlmProvider, createEmbeddingProvider } from '../intelligence/llm.js';
|
|
12
|
-
import { VectorIndex } from '../index/vectors.js';
|
|
13
|
-
import { generateEmbedding } from '../intelligence/embeddings.js';
|
|
14
|
-
import { LineageGraph, LINEAGE_SIMILARITY_THRESHOLD, LINEAGE_SIMILARITY_HIGH_CONFIDENCE, LINEAGE_SIMILARITY_CANDIDATES, LINEAGE_SIMILARITY_MAX_TOKENS } from './lineage.js';
|
|
15
|
-
import { PlanWatcher } from './watcher.js';
|
|
16
|
-
import { buildSimilarityPrompt } from '../prompts/index.js';
|
|
17
|
-
import { extractNumber } from '../intelligence/response.js';
|
|
18
|
-
import { EMBEDDING_INPUT_LIMIT, CONTENT_SNIPPET_CHARS, STALE_BUFFER_MAX_AGE_MS, LINEAGE_RECENT_SESSIONS_LIMIT, RELATED_MEMORIES_LIMIT, CANDIDATE_CONTENT_PREVIEW, SESSION_CONTEXT_MAX_PLANS, PROMPT_CONTEXT_MAX_MEMORIES, PROMPT_CONTEXT_MIN_SIMILARITY, PROMPT_CONTEXT_MIN_LENGTH, CONTEXT_SESSION_PREVIEW_CHARS } from '../constants.js';
|
|
19
|
-
import { TranscriptMiner, extractTurnsFromBuffer } from '../capture/transcript-miner.js';
|
|
20
|
-
import { createPerProjectAdapter, extensionForMimeType } from '../agents/adapter.js';
|
|
21
|
-
import { claudeCodeAdapter } from '../agents/claude-code.js';
|
|
22
|
-
import { EventBuffer } from '../capture/buffer.js';
|
|
23
|
-
import { formatSessionBody } from '../obsidian/formatter.js';
|
|
24
|
-
import { writeObservationNotes } from '../vault/observations.js';
|
|
25
|
-
import { collectArtifactCandidates } from '../artifacts/candidates.js';
|
|
26
|
-
import { slugifyPath } from '../artifacts/slugify.js';
|
|
27
|
-
import { sessionNoteId, bareSessionId, sessionWikilink, sessionRelativePath } from '../vault/session-id.js';
|
|
28
|
-
import { z } from 'zod';
|
|
29
|
-
import YAML from 'yaml';
|
|
30
|
-
import fs from 'node:fs';
|
|
31
|
-
import path from 'node:path';
|
|
32
|
-
import { fileURLToPath } from 'node:url';
|
|
33
|
-
function indexAndEmbed(relativePath, noteId, embeddingText, metadata, deps) {
|
|
34
|
-
indexNote(deps.index, deps.vaultDir, relativePath);
|
|
35
|
-
if (deps.vectorIndex && embeddingText) {
|
|
36
|
-
generateEmbedding(deps.embeddingProvider, embeddingText.slice(0, EMBEDDING_INPUT_LIMIT))
|
|
37
|
-
.then((emb) => deps.vectorIndex.upsert(noteId, emb.embedding, metadata))
|
|
38
|
-
.catch((err) => deps.logger.debug('embeddings', 'Embedding failed', { id: noteId, error: err.message }));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function writeObservations(observations, sessionId, deps) {
|
|
42
|
-
const written = writeObservationNotes(observations, sessionId, deps.vault, deps.index, deps.vaultDir);
|
|
43
|
-
for (const note of written) {
|
|
44
|
-
indexAndEmbed(note.path, note.id, `${note.observation.title}\n${note.observation.content}`, { type: 'memory', importance: 'high', session_id: sessionId }, deps);
|
|
45
|
-
deps.logger.info('processor', 'Observation written', { type: note.observation.type, title: note.observation.title, session_id: sessionId });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function captureArtifacts(candidates, classified, sessionId, deps, lineage) {
|
|
49
|
-
const candidateMap = new Map(candidates.map((c) => [c.path, c]));
|
|
50
|
-
for (const artifact of classified) {
|
|
51
|
-
const candidate = candidateMap.get(artifact.source_path);
|
|
52
|
-
if (!candidate)
|
|
53
|
-
continue;
|
|
54
|
-
const artifactId = slugifyPath(artifact.source_path);
|
|
55
|
-
const artifactPath = deps.vault.writeArtifact({
|
|
56
|
-
id: artifactId,
|
|
57
|
-
artifact_type: artifact.artifact_type,
|
|
58
|
-
source_path: artifact.source_path,
|
|
59
|
-
title: artifact.title,
|
|
60
|
-
session: sessionId,
|
|
61
|
-
tags: artifact.tags,
|
|
62
|
-
content: candidate.content,
|
|
63
|
-
});
|
|
64
|
-
indexAndEmbed(artifactPath, artifactId, `${artifact.title}\n${candidate.content}`, { type: 'artifact', artifact_type: artifact.artifact_type, session_id: sessionId }, deps);
|
|
65
|
-
deps.logger.info('processor', 'Artifact captured', {
|
|
66
|
-
id: artifactId,
|
|
67
|
-
type: artifact.artifact_type,
|
|
68
|
-
source: artifact.source_path,
|
|
69
|
-
});
|
|
70
|
-
// Register artifact-session association for lineage detection.
|
|
71
|
-
// Future sessions referencing this artifact ID link as children.
|
|
72
|
-
lineage?.registerArtifactForSession(sessionId, artifactId);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export function migrateMemoryFiles(vaultDir) {
|
|
76
|
-
const memoriesDir = path.join(vaultDir, 'memories');
|
|
77
|
-
if (!fs.existsSync(memoriesDir))
|
|
78
|
-
return 0;
|
|
79
|
-
let moved = 0;
|
|
80
|
-
const entries = fs.readdirSync(memoriesDir);
|
|
81
|
-
for (const entry of entries) {
|
|
82
|
-
const fullPath = path.join(memoriesDir, entry);
|
|
83
|
-
if (!entry.endsWith('.md'))
|
|
84
|
-
continue;
|
|
85
|
-
if (fs.statSync(fullPath).isDirectory())
|
|
86
|
-
continue;
|
|
87
|
-
try {
|
|
88
|
-
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
89
|
-
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
90
|
-
if (!fmMatch)
|
|
91
|
-
continue;
|
|
92
|
-
const parsed = YAML.parse(fmMatch[1]);
|
|
93
|
-
const obsType = parsed.observation_type;
|
|
94
|
-
if (!obsType)
|
|
95
|
-
continue;
|
|
96
|
-
const normalizedType = obsType.replace(/_/g, '-');
|
|
97
|
-
const targetDir = path.join(memoriesDir, normalizedType);
|
|
98
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
99
|
-
const targetPath = path.join(targetDir, entry);
|
|
100
|
-
fs.renameSync(fullPath, targetPath);
|
|
101
|
-
// Touch the file so Obsidian detects the move and re-indexes backlinks
|
|
102
|
-
const now = new Date();
|
|
103
|
-
fs.utimesSync(targetPath, now, now);
|
|
104
|
-
moved++;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Skip files that can't be read or parsed
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return moved;
|
|
111
|
-
}
|
|
112
|
-
async function main() {
|
|
113
|
-
const vaultArg = process.argv.find((_, i) => process.argv[i - 1] === '--vault');
|
|
114
|
-
if (!vaultArg) {
|
|
115
|
-
process.stderr.write('Usage: mycod --vault <path>\n');
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
const vaultDir = path.resolve(vaultArg);
|
|
119
|
-
const config = loadConfig(vaultDir);
|
|
120
|
-
const logger = new DaemonLogger(path.join(vaultDir, 'logs'), {
|
|
121
|
-
level: config.daemon.log_level,
|
|
122
|
-
maxSize: config.daemon.max_log_size,
|
|
123
|
-
});
|
|
124
|
-
const server = new DaemonServer({ vaultDir, logger });
|
|
125
|
-
const registry = new SessionRegistry({
|
|
126
|
-
gracePeriod: config.daemon.grace_period,
|
|
127
|
-
onEmpty: async () => {
|
|
128
|
-
logger.info('daemon', 'Grace period expired, shutting down');
|
|
129
|
-
planWatcher.stopFileWatcher();
|
|
130
|
-
await server.stop();
|
|
131
|
-
vectorIndex?.close();
|
|
132
|
-
index.close();
|
|
133
|
-
logger.close();
|
|
134
|
-
process.exit(0);
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
// Batch processing setup
|
|
138
|
-
const llmProvider = createLlmProvider(config.intelligence.llm);
|
|
139
|
-
const embeddingProvider = createEmbeddingProvider(config.intelligence.embedding);
|
|
140
|
-
let vectorIndex = null;
|
|
141
|
-
try {
|
|
142
|
-
const testEmbed = await embeddingProvider.embed('test');
|
|
143
|
-
vectorIndex = new VectorIndex(path.join(vaultDir, 'vectors.db'), testEmbed.dimensions);
|
|
144
|
-
logger.info('embeddings', 'Vector index initialized', { dimensions: testEmbed.dimensions });
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
logger.warn('embeddings', 'Vector index unavailable', { error: error.message });
|
|
148
|
-
}
|
|
149
|
-
const processor = new BufferProcessor(llmProvider, config.intelligence.llm.context_window);
|
|
150
|
-
const vault = new VaultWriter(vaultDir);
|
|
151
|
-
const index = new MycoIndex(path.join(vaultDir, 'index.db'));
|
|
152
|
-
const lineageGraph = new LineageGraph(vaultDir);
|
|
153
|
-
const transcriptMiner = new TranscriptMiner({
|
|
154
|
-
additionalAdapters: config.capture.transcript_paths.map((p) => createPerProjectAdapter(p, claudeCodeAdapter.parseTurns)),
|
|
155
|
-
});
|
|
156
|
-
let activeStopProcessing = null;
|
|
157
|
-
const indexDeps = { index, vaultDir, vectorIndex, embeddingProvider, logger };
|
|
158
|
-
const bufferDir = path.join(vaultDir, 'buffer');
|
|
159
|
-
const sessionBuffers = new Map();
|
|
160
|
-
const sessionFilePaths = new Map();
|
|
161
|
-
// Clean up stale buffer files (>24h) on startup
|
|
162
|
-
if (fs.existsSync(bufferDir)) {
|
|
163
|
-
const cutoff = Date.now() - STALE_BUFFER_MAX_AGE_MS;
|
|
164
|
-
for (const file of fs.readdirSync(bufferDir)) {
|
|
165
|
-
const filePath = path.join(bufferDir, file);
|
|
166
|
-
const stat = fs.statSync(filePath);
|
|
167
|
-
if (stat.mtimeMs < cutoff) {
|
|
168
|
-
fs.unlinkSync(filePath);
|
|
169
|
-
logger.debug('daemon', 'Cleaned stale buffer', { file });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Migrate flat memory files into type subdirectories
|
|
174
|
-
const migrated = migrateMemoryFiles(vaultDir);
|
|
175
|
-
if (migrated > 0) {
|
|
176
|
-
logger.info('daemon', 'Migrated memory files to type subdirectories', { count: migrated });
|
|
177
|
-
// Rebuild FTS index to update stored paths (vectors are keyed by ID, unaffected)
|
|
178
|
-
initFts(index);
|
|
179
|
-
rebuildIndex(index, vaultDir);
|
|
180
|
-
}
|
|
181
|
-
// Route body schemas
|
|
182
|
-
const RegisterBody = z.object({
|
|
183
|
-
session_id: z.string(),
|
|
184
|
-
branch: z.string().optional(),
|
|
185
|
-
started_at: z.string().optional(),
|
|
186
|
-
});
|
|
187
|
-
const UnregisterBody = z.object({ session_id: z.string() });
|
|
188
|
-
const EventBody = z.object({ type: z.string(), session_id: z.string() }).passthrough();
|
|
189
|
-
const StopBody = z.object({
|
|
190
|
-
session_id: z.string(),
|
|
191
|
-
user: z.string().optional(),
|
|
192
|
-
transcript_path: z.string().optional(),
|
|
193
|
-
last_assistant_message: z.string().optional(),
|
|
194
|
-
});
|
|
195
|
-
const ContextBody = z.object({
|
|
196
|
-
session_id: z.string().optional(),
|
|
197
|
-
branch: z.string().optional(),
|
|
198
|
-
files: z.array(z.string()).optional(),
|
|
199
|
-
});
|
|
200
|
-
const planWatcher = new PlanWatcher({
|
|
201
|
-
projectRoot: process.cwd(),
|
|
202
|
-
watchPaths: config.capture.artifact_watch,
|
|
203
|
-
onPlan: (event) => {
|
|
204
|
-
logger.info('watcher', 'Plan detected', { source: event.source, file: event.filePath });
|
|
205
|
-
if (event.filePath) {
|
|
206
|
-
try {
|
|
207
|
-
const content = fs.readFileSync(event.filePath, 'utf-8');
|
|
208
|
-
const relativePath = path.relative(vaultDir, event.filePath);
|
|
209
|
-
const title = content.match(/^#\s+(.+)$/m)?.[1] ?? path.basename(event.filePath);
|
|
210
|
-
const planId = `plan-${path.basename(event.filePath, '.md')}`;
|
|
211
|
-
indexAndEmbed(relativePath, planId, `${title}\n${content}`, { type: 'plan' }, indexDeps);
|
|
212
|
-
// Register plan-session association for lineage detection.
|
|
213
|
-
// When a future session's first prompt mentions this plan ID,
|
|
214
|
-
// detectHeuristicParent links it as a child of this session.
|
|
215
|
-
if (event.sessionId) {
|
|
216
|
-
lineageGraph.registerArtifactForSession(event.sessionId, planId);
|
|
217
|
-
logger.debug('lineage', 'Plan registered for session', { planId, session: event.sessionId });
|
|
218
|
-
}
|
|
219
|
-
logger.info('watcher', 'Plan indexed', { path: relativePath });
|
|
220
|
-
}
|
|
221
|
-
catch (err) {
|
|
222
|
-
logger.debug('watcher', 'Plan index failed', { error: err.message });
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
planWatcher.startFileWatcher();
|
|
228
|
-
const batchManager = new BatchManager(async (closedBatch) => {
|
|
229
|
-
if (closedBatch.length === 0)
|
|
230
|
-
return;
|
|
231
|
-
const sessionId = closedBatch[0].session_id;
|
|
232
|
-
// Extract observations from this batch
|
|
233
|
-
const asRecords = closedBatch;
|
|
234
|
-
const result = await processor.process(asRecords, sessionId);
|
|
235
|
-
if (!result.degraded) {
|
|
236
|
-
writeObservations(result.observations, sessionId, { vault, ...indexDeps });
|
|
237
|
-
}
|
|
238
|
-
logger.debug('processor', 'Batch processed', {
|
|
239
|
-
session_id: sessionId,
|
|
240
|
-
events: closedBatch.length,
|
|
241
|
-
observations: result.observations.length,
|
|
242
|
-
degraded: result.degraded,
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
// Session routes
|
|
246
|
-
server.registerRoute('POST', '/sessions/register', async (body) => {
|
|
247
|
-
const { session_id, branch, started_at } = RegisterBody.parse(body);
|
|
248
|
-
const resolvedStartedAt = started_at ?? new Date().toISOString();
|
|
249
|
-
registry.register(session_id, { started_at: resolvedStartedAt, branch });
|
|
250
|
-
server.updateDaemonJsonSessions(registry.sessions);
|
|
251
|
-
// Heuristic lineage detection
|
|
252
|
-
try {
|
|
253
|
-
const recentSessions = index.query({ type: 'session', limit: LINEAGE_RECENT_SESSIONS_LIMIT })
|
|
254
|
-
.map((n) => {
|
|
255
|
-
const fm = n.frontmatter;
|
|
256
|
-
return { id: bareSessionId(n.id), ended: fm.ended, branch: fm.branch };
|
|
257
|
-
});
|
|
258
|
-
const activeSessions = registry.sessions
|
|
259
|
-
.filter((s) => s !== session_id)
|
|
260
|
-
.map((s) => registry.getSession(s))
|
|
261
|
-
.filter((s) => s !== undefined);
|
|
262
|
-
const link = lineageGraph.detectHeuristicParent(session_id, {
|
|
263
|
-
started_at: resolvedStartedAt,
|
|
264
|
-
branch,
|
|
265
|
-
}, recentSessions, activeSessions);
|
|
266
|
-
if (link) {
|
|
267
|
-
logger.info('lineage', 'Heuristic parent detected', { child: session_id, parent: link.parent, signal: link.signal });
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
catch (err) {
|
|
271
|
-
logger.debug('lineage', 'Heuristic detection failed', { error: err.message });
|
|
272
|
-
}
|
|
273
|
-
logger.info('lifecycle', 'Session registered', { session_id, branch });
|
|
274
|
-
return { ok: true, sessions: registry.sessions };
|
|
275
|
-
});
|
|
276
|
-
server.registerRoute('POST', '/sessions/unregister', async (body) => {
|
|
277
|
-
const { session_id } = UnregisterBody.parse(body);
|
|
278
|
-
registry.unregister(session_id);
|
|
279
|
-
// Note: we do NOT delete the buffer FILE for THIS session. Session reload
|
|
280
|
-
// (SessionEnd → SessionStart) reuses the same session_id, and deleting
|
|
281
|
-
// would wipe all prior events.
|
|
282
|
-
// We DO opportunistically clean stale buffers for OTHER sessions (>24h).
|
|
283
|
-
try {
|
|
284
|
-
const cutoff = Date.now() - STALE_BUFFER_MAX_AGE_MS;
|
|
285
|
-
for (const file of fs.readdirSync(bufferDir)) {
|
|
286
|
-
if (!file.endsWith('.jsonl'))
|
|
287
|
-
continue;
|
|
288
|
-
const bufferSessionId = file.replace('.jsonl', '');
|
|
289
|
-
if (bufferSessionId === session_id)
|
|
290
|
-
continue; // skip current session
|
|
291
|
-
const filePath = path.join(bufferDir, file);
|
|
292
|
-
const stat = fs.statSync(filePath);
|
|
293
|
-
if (stat.mtimeMs < cutoff) {
|
|
294
|
-
fs.unlinkSync(filePath);
|
|
295
|
-
logger.debug('daemon', 'Cleaned stale buffer', { file });
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch { /* buffer dir may not exist */ }
|
|
300
|
-
// We DO prune the in-memory Map entries to avoid unbounded growth.
|
|
301
|
-
sessionBuffers.delete(session_id);
|
|
302
|
-
sessionFilePaths.delete(session_id);
|
|
303
|
-
server.updateDaemonJsonSessions(registry.sessions);
|
|
304
|
-
logger.info('lifecycle', 'Session unregistered', { session_id });
|
|
305
|
-
return { ok: true, sessions: registry.sessions };
|
|
306
|
-
});
|
|
307
|
-
// Event routes
|
|
308
|
-
server.registerRoute('POST', '/events', async (body) => {
|
|
309
|
-
const validated = EventBody.parse(body);
|
|
310
|
-
const event = { ...validated, timestamp: validated.timestamp ?? new Date().toISOString() };
|
|
311
|
-
logger.debug('hooks', 'Event received', { type: event.type, session_id: event.session_id });
|
|
312
|
-
// Ensure session is registered (idempotent — handles daemon restarts mid-session)
|
|
313
|
-
if (!registry.getSession(event.session_id)) {
|
|
314
|
-
registry.register(event.session_id, { started_at: event.timestamp });
|
|
315
|
-
logger.debug('lifecycle', 'Auto-registered session from event', { session_id: event.session_id });
|
|
316
|
-
}
|
|
317
|
-
// Persist to disk so events survive daemon restarts
|
|
318
|
-
if (!sessionBuffers.has(event.session_id)) {
|
|
319
|
-
sessionBuffers.set(event.session_id, new EventBuffer(bufferDir, event.session_id));
|
|
320
|
-
}
|
|
321
|
-
sessionBuffers.get(event.session_id).append(event);
|
|
322
|
-
batchManager.addEvent(event);
|
|
323
|
-
if (validated.type === 'tool_use') {
|
|
324
|
-
const v = validated;
|
|
325
|
-
planWatcher.checkToolEvent({ tool_name: String(v.tool_name ?? ''), tool_input: v.tool_input, session_id: validated.session_id });
|
|
326
|
-
const toolName = String(v.tool_name ?? '');
|
|
327
|
-
if (toolName === 'Write' || toolName === 'Edit') {
|
|
328
|
-
const filePath = v.tool_input?.file_path;
|
|
329
|
-
if (filePath) {
|
|
330
|
-
if (!sessionFilePaths.has(event.session_id)) {
|
|
331
|
-
sessionFilePaths.set(event.session_id, new Set());
|
|
332
|
-
}
|
|
333
|
-
sessionFilePaths.get(event.session_id).add(filePath);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return { ok: true };
|
|
338
|
-
});
|
|
339
|
-
server.registerRoute('POST', '/events/stop', async (body) => {
|
|
340
|
-
const { session_id: sessionId, user, transcript_path: hookTranscriptPath, last_assistant_message: lastAssistantMessage } = StopBody.parse(body);
|
|
341
|
-
// Ensure session is registered (handles daemon restarts mid-session)
|
|
342
|
-
if (!registry.getSession(sessionId)) {
|
|
343
|
-
registry.register(sessionId, { started_at: new Date().toISOString() });
|
|
344
|
-
logger.debug('lifecycle', 'Auto-registered session from stop event', { session_id: sessionId });
|
|
345
|
-
}
|
|
346
|
-
const sessionMeta = registry.getSession(sessionId);
|
|
347
|
-
logger.info('hooks', 'Stop received', { session_id: sessionId, has_transcript_path: !!hookTranscriptPath, has_response: !!lastAssistantMessage });
|
|
348
|
-
// Respond immediately — the hook should not block on LLM processing.
|
|
349
|
-
// Serialize stop processing: if a previous stop is still running, chain
|
|
350
|
-
// the new one to run after it completes. This prevents concurrent
|
|
351
|
-
// processStopEvent calls from racing on the same session file.
|
|
352
|
-
const run = () => processStopEvent(sessionId, user, sessionMeta, hookTranscriptPath, lastAssistantMessage).catch((err) => {
|
|
353
|
-
logger.error('processor', 'Stop processing failed', { session_id: sessionId, error: err.message });
|
|
354
|
-
});
|
|
355
|
-
// Chain onto any in-flight processing. Only the tail of the chain clears the variable.
|
|
356
|
-
const prev = activeStopProcessing ?? Promise.resolve();
|
|
357
|
-
activeStopProcessing = prev.then(run).finally(() => { activeStopProcessing = null; });
|
|
358
|
-
return { ok: true };
|
|
359
|
-
});
|
|
360
|
-
async function processStopEvent(sessionId, user, sessionMeta, hookTranscriptPath, lastAssistantMessage) {
|
|
361
|
-
// --- Phase 1: Gather data (I/O only, no LLM) ---
|
|
362
|
-
const lastBatch = batchManager.finalize(sessionId);
|
|
363
|
-
// Tiered turn extraction:
|
|
364
|
-
// 1. Read transcript for complete turns (with AI responses)
|
|
365
|
-
// 2. Check buffer for prompts newer than the transcript's last turn
|
|
366
|
-
// (captures turns the transcript missed, e.g., after context compaction)
|
|
367
|
-
// 3. Fall back to buffer entirely if no transcript found
|
|
368
|
-
const transcriptResult = transcriptMiner.getAllTurnsWithSource(sessionId, hookTranscriptPath);
|
|
369
|
-
let allTurns = transcriptResult.turns;
|
|
370
|
-
let turnSource = transcriptResult.source;
|
|
371
|
-
const bufferEvents = sessionBuffers.get(sessionId)?.readAll() ?? [];
|
|
372
|
-
if (allTurns.length === 0) {
|
|
373
|
-
// No transcript — use buffer as primary source
|
|
374
|
-
allTurns = extractTurnsFromBuffer(bufferEvents);
|
|
375
|
-
turnSource = 'buffer';
|
|
376
|
-
}
|
|
377
|
-
else if (bufferEvents.length > 0) {
|
|
378
|
-
// Transcript exists — check for buffer events newer than the last transcript turn.
|
|
379
|
-
// These are prompts the transcript missed (e.g., after context compaction).
|
|
380
|
-
const lastTranscriptTs = allTurns[allTurns.length - 1].timestamp;
|
|
381
|
-
if (lastTranscriptTs) {
|
|
382
|
-
const newerEvents = bufferEvents.filter((e) => String(e.timestamp ?? '') > lastTranscriptTs);
|
|
383
|
-
if (newerEvents.length > 0) {
|
|
384
|
-
const bufferTurns = extractTurnsFromBuffer(newerEvents);
|
|
385
|
-
allTurns = [...allTurns, ...bufferTurns];
|
|
386
|
-
turnSource = `${transcriptResult.source}+buffer`;
|
|
387
|
-
logger.info('processor', 'Appended buffer turns missing from transcript', {
|
|
388
|
-
session_id: sessionId, transcriptTurns: transcriptResult.turns.length, bufferTurns: bufferTurns.length,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
// Attach the last assistant message from the hook to the most recent turn
|
|
394
|
-
// that doesn't already have an AI response. This captures the response
|
|
395
|
-
// for turns the transcript missed (post-compaction) or buffer-sourced turns.
|
|
396
|
-
if (lastAssistantMessage && allTurns.length > 0) {
|
|
397
|
-
const lastTurn = allTurns[allTurns.length - 1];
|
|
398
|
-
if (!lastTurn.aiResponse) {
|
|
399
|
-
lastTurn.aiResponse = lastAssistantMessage;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const ended = new Date().toISOString();
|
|
403
|
-
let started = (allTurns.length > 0 && allTurns[0].timestamp) ? allTurns[0].timestamp : ended;
|
|
404
|
-
// Find existing session file and clean up cross-date duplicates in one pass.
|
|
405
|
-
const sessionsDir = path.join(vaultDir, 'sessions');
|
|
406
|
-
const sessionFileName = `${sessionNoteId(sessionId)}.md`;
|
|
407
|
-
let existingContent;
|
|
408
|
-
const duplicatePaths = [];
|
|
409
|
-
try {
|
|
410
|
-
for (const dateDir of fs.readdirSync(sessionsDir)) {
|
|
411
|
-
const candidate = path.join(sessionsDir, dateDir, sessionFileName);
|
|
412
|
-
try {
|
|
413
|
-
const content = fs.readFileSync(candidate, 'utf-8');
|
|
414
|
-
if (!existingContent || content.length > existingContent.length) {
|
|
415
|
-
existingContent = content;
|
|
416
|
-
}
|
|
417
|
-
duplicatePaths.push(candidate);
|
|
418
|
-
}
|
|
419
|
-
catch { /* file doesn't exist in this date dir */ }
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
catch { /* sessions dir may not exist yet */ }
|
|
423
|
-
let existingTurnCount = 0;
|
|
424
|
-
if (existingContent) {
|
|
425
|
-
const startedMatch = existingContent.match(/^started:\s*"?(.+?)"?\s*$/m);
|
|
426
|
-
if (startedMatch)
|
|
427
|
-
started = startedMatch[1];
|
|
428
|
-
const turnMatches = existingContent.match(/^### Turn \d+/gm);
|
|
429
|
-
existingTurnCount = turnMatches?.length ?? 0;
|
|
430
|
-
}
|
|
431
|
-
// Collect artifact candidates (no LLM yet)
|
|
432
|
-
const writtenFiles = sessionFilePaths.get(sessionId) ?? new Set();
|
|
433
|
-
const artifactCandidates = collectArtifactCandidates(writtenFiles, { artifact_extensions: config.capture.artifact_extensions }, process.cwd());
|
|
434
|
-
// Guard: never overwrite an existing session with zero turns — the transcript
|
|
435
|
-
// is temporarily unreadable (daemon restart, file locked, etc.)
|
|
436
|
-
if (allTurns.length === 0 && existingTurnCount > 0) {
|
|
437
|
-
logger.warn('processor', 'Transcript unreadable, skipping rewrite to preserve existing data', { session_id: sessionId, existingTurns: existingTurnCount });
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
// Skip if no new turns AND no batch to process AND no artifacts to classify
|
|
441
|
-
if (allTurns.length > 0 && allTurns.length === existingTurnCount && lastBatch.length === 0 && artifactCandidates.length === 0) {
|
|
442
|
-
logger.debug('processor', 'No new turns, skipping session rewrite', { session_id: sessionId, turns: allTurns.length });
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
// --- Phase 2: LLM calls in parallel ---
|
|
446
|
-
// Build conversation text for summarization (pure string, no LLM)
|
|
447
|
-
const conversationText = allTurns.map((t, i) => {
|
|
448
|
-
const parts = [`### Turn ${i + 1}`];
|
|
449
|
-
if (t.prompt)
|
|
450
|
-
parts.push(`Prompt: ${t.prompt}`);
|
|
451
|
-
if (t.toolCount > 0)
|
|
452
|
-
parts.push(`Tools: ${t.toolCount} calls`);
|
|
453
|
-
if (t.aiResponse)
|
|
454
|
-
parts.push(`Response: ${t.aiResponse}`);
|
|
455
|
-
return parts.join('\n');
|
|
456
|
-
}).join('\n\n');
|
|
457
|
-
const conversationSection = `## Conversation\n\n${conversationText}`;
|
|
458
|
-
// Launch all LLM calls concurrently
|
|
459
|
-
const observationPromise = lastBatch.length > 0
|
|
460
|
-
? processor.process(lastBatch, sessionId)
|
|
461
|
-
.catch((err) => { logger.warn('processor', 'Observation extraction failed', { session_id: sessionId, error: err.message }); return null; })
|
|
462
|
-
: Promise.resolve(null);
|
|
463
|
-
const artifactPromise = artifactCandidates.length > 0
|
|
464
|
-
? processor.classifyArtifacts(artifactCandidates, sessionId)
|
|
465
|
-
.then((classified) => captureArtifacts(artifactCandidates, classified, sessionId, { vault, ...indexDeps }, lineageGraph))
|
|
466
|
-
.catch((err) => { logger.warn('processor', 'Artifact capture failed', { session_id: sessionId, error: err.message }); })
|
|
467
|
-
: Promise.resolve();
|
|
468
|
-
const summaryPromise = processor.summarizeSession(conversationSection, sessionId, user)
|
|
469
|
-
.catch((err) => { logger.warn('processor', 'Session summarization failed', { session_id: sessionId, error: err.message }); return null; });
|
|
470
|
-
// Wait for all LLM calls to complete
|
|
471
|
-
const [observationResult, , summaryResult] = await Promise.all([observationPromise, artifactPromise, summaryPromise]);
|
|
472
|
-
// --- Phase 3: Write results to vault ---
|
|
473
|
-
// Write observations
|
|
474
|
-
if (observationResult && !observationResult.degraded) {
|
|
475
|
-
writeObservations(observationResult.observations, sessionId, { vault, ...indexDeps });
|
|
476
|
-
}
|
|
477
|
-
// Compute canonical path
|
|
478
|
-
const date = started.slice(0, 10);
|
|
479
|
-
const relativePath = sessionRelativePath(sessionId, date);
|
|
480
|
-
const targetFullPath = path.join(vaultDir, relativePath);
|
|
481
|
-
// Remove cross-date duplicates
|
|
482
|
-
for (const dup of duplicatePaths) {
|
|
483
|
-
if (dup !== targetFullPath) {
|
|
484
|
-
try {
|
|
485
|
-
fs.unlinkSync(dup);
|
|
486
|
-
logger.debug('lifecycle', 'Removed duplicate session file', { path: dup });
|
|
487
|
-
}
|
|
488
|
-
catch { /* already gone */ }
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
// Write images to attachments
|
|
492
|
-
const attachmentsDir = path.join(vaultDir, 'attachments');
|
|
493
|
-
const hasImages = allTurns.some((t) => t.images?.length);
|
|
494
|
-
if (hasImages) {
|
|
495
|
-
fs.mkdirSync(attachmentsDir, { recursive: true });
|
|
496
|
-
}
|
|
497
|
-
const turnImageNames = new Map();
|
|
498
|
-
for (let i = 0; i < allTurns.length; i++) {
|
|
499
|
-
const turn = allTurns[i];
|
|
500
|
-
if (!turn.images?.length)
|
|
501
|
-
continue;
|
|
502
|
-
const names = [];
|
|
503
|
-
for (let j = 0; j < turn.images.length; j++) {
|
|
504
|
-
const img = turn.images[j];
|
|
505
|
-
const ext = extensionForMimeType(img.mediaType);
|
|
506
|
-
const filename = `${bareSessionId(sessionId)}-t${i + 1}-${j + 1}.${ext}`;
|
|
507
|
-
const filePath = path.join(attachmentsDir, filename);
|
|
508
|
-
if (!fs.existsSync(filePath)) {
|
|
509
|
-
fs.writeFileSync(filePath, Buffer.from(img.data, 'base64'));
|
|
510
|
-
logger.debug('processor', 'Image saved', { filename, turn: i + 1 });
|
|
511
|
-
}
|
|
512
|
-
names.push(filename);
|
|
513
|
-
}
|
|
514
|
-
turnImageNames.set(i, names);
|
|
515
|
-
}
|
|
516
|
-
// Build and write session note
|
|
517
|
-
let title = `Session ${sessionId}`;
|
|
518
|
-
let narrative = '';
|
|
519
|
-
if (summaryResult) {
|
|
520
|
-
title = summaryResult.title;
|
|
521
|
-
narrative = summaryResult.summary;
|
|
522
|
-
}
|
|
523
|
-
// Query related memories for this session
|
|
524
|
-
const relatedMemories = index.query({ type: 'memory', limit: RELATED_MEMORIES_LIMIT })
|
|
525
|
-
.filter((n) => {
|
|
526
|
-
const fm = n.frontmatter;
|
|
527
|
-
return fm.session === sessionNoteId(sessionId) || fm.session === sessionId;
|
|
528
|
-
})
|
|
529
|
-
.map((n) => ({ id: n.id, title: n.title }));
|
|
530
|
-
// The formatter always gets the full turn list — no more partial appending.
|
|
531
|
-
// existingConversation is no longer needed; we rebuild from the transcript each time.
|
|
532
|
-
const summary = formatSessionBody({
|
|
533
|
-
title,
|
|
534
|
-
narrative,
|
|
535
|
-
sessionId,
|
|
536
|
-
user,
|
|
537
|
-
started,
|
|
538
|
-
ended,
|
|
539
|
-
branch: sessionMeta?.branch,
|
|
540
|
-
relatedMemories,
|
|
541
|
-
turns: allTurns.map((t, i) => ({
|
|
542
|
-
prompt: t.prompt,
|
|
543
|
-
toolCount: t.toolCount,
|
|
544
|
-
aiResponse: t.aiResponse,
|
|
545
|
-
images: turnImageNames.get(i),
|
|
546
|
-
})),
|
|
547
|
-
});
|
|
548
|
-
const parentId = lineageGraph.getParent(sessionId);
|
|
549
|
-
const parentLink = parentId ? lineageGraph.getLinks().find((l) => l.child === sessionId) : undefined;
|
|
550
|
-
vault.writeSession({
|
|
551
|
-
id: sessionId,
|
|
552
|
-
user,
|
|
553
|
-
started,
|
|
554
|
-
ended,
|
|
555
|
-
branch: sessionMeta?.branch,
|
|
556
|
-
parent: parentId ? sessionWikilink(parentId) : undefined,
|
|
557
|
-
parent_reason: parentLink?.signal,
|
|
558
|
-
tools_used: allTurns.reduce((sum, t) => sum + t.toolCount, 0),
|
|
559
|
-
summary,
|
|
560
|
-
});
|
|
561
|
-
indexAndEmbed(relativePath, sessionNoteId(sessionId), narrative, { type: 'session', session_id: sessionId }, indexDeps);
|
|
562
|
-
logger.debug('processor', 'Session turns', { source: turnSource, total: allTurns.length });
|
|
563
|
-
// Wait for artifact capture (started concurrently with session building)
|
|
564
|
-
await artifactPromise;
|
|
565
|
-
// Phase 2: LLM similarity detection (fire-and-forget, only if no heuristic parent)
|
|
566
|
-
if (!parentId && vectorIndex && narrative) {
|
|
567
|
-
generateEmbedding(embeddingProvider, narrative)
|
|
568
|
-
.then(async (emb) => {
|
|
569
|
-
const candidates = vectorIndex.search(emb.embedding, { limit: LINEAGE_SIMILARITY_CANDIDATES })
|
|
570
|
-
.filter((r) => r.metadata.type === 'session' && r.id !== sessionNoteId(sessionId));
|
|
571
|
-
if (candidates.length === 0)
|
|
572
|
-
return;
|
|
573
|
-
// Score all candidates in parallel
|
|
574
|
-
const candidateNotes = index.queryByIds(candidates.map((c) => c.id));
|
|
575
|
-
const noteMap = new Map(candidateNotes.map((n) => [n.id, n]));
|
|
576
|
-
const scores = await Promise.all(candidates.map(async (candidate) => {
|
|
577
|
-
const note = noteMap.get(candidate.id);
|
|
578
|
-
if (!note)
|
|
579
|
-
return { id: candidate.id, score: 0 };
|
|
580
|
-
try {
|
|
581
|
-
const prompt = buildSimilarityPrompt(narrative, note.content.slice(0, CANDIDATE_CONTENT_PREVIEW));
|
|
582
|
-
const response = await llmProvider.summarize(prompt, { maxTokens: LINEAGE_SIMILARITY_MAX_TOKENS });
|
|
583
|
-
const score = extractNumber(response.text);
|
|
584
|
-
return { id: candidate.id, score: isNaN(score) ? 0 : score };
|
|
585
|
-
}
|
|
586
|
-
catch {
|
|
587
|
-
return { id: candidate.id, score: 0 };
|
|
588
|
-
}
|
|
589
|
-
}));
|
|
590
|
-
const best = scores.reduce((a, b) => (b.score > a.score ? b : a));
|
|
591
|
-
if (best.score >= LINEAGE_SIMILARITY_THRESHOLD) {
|
|
592
|
-
const bestParentId = bareSessionId(best.id);
|
|
593
|
-
const confidence = best.score >= LINEAGE_SIMILARITY_HIGH_CONFIDENCE ? 'high' : 'medium';
|
|
594
|
-
lineageGraph.addLink({
|
|
595
|
-
parent: bestParentId,
|
|
596
|
-
child: sessionId,
|
|
597
|
-
signal: 'semantic_similarity',
|
|
598
|
-
confidence: confidence,
|
|
599
|
-
});
|
|
600
|
-
// Retroactively update session frontmatter with parent + re-index
|
|
601
|
-
try {
|
|
602
|
-
vault.updateNoteFrontmatter(relativePath, {
|
|
603
|
-
parent: sessionWikilink(bestParentId),
|
|
604
|
-
parent_reason: 'semantic_similarity',
|
|
605
|
-
});
|
|
606
|
-
indexNote(index, vaultDir, relativePath);
|
|
607
|
-
}
|
|
608
|
-
catch { /* frontmatter update failed — link still in lineage.json */ }
|
|
609
|
-
logger.info('lineage', 'LLM similarity parent detected', {
|
|
610
|
-
child: sessionId, parent: bestParentId, score: best.score,
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
})
|
|
614
|
-
.catch((err) => logger.debug('lineage', 'Similarity detection failed', { error: err.message }));
|
|
615
|
-
}
|
|
616
|
-
logger.info('processor', 'Session note written', { session_id: sessionId, path: relativePath });
|
|
617
|
-
}
|
|
618
|
-
// Session-start context: structural facts only — active plans + parent session.
|
|
619
|
-
// Memories are injected per-prompt (more targeted, less noise).
|
|
620
|
-
server.registerRoute('POST', '/context', async (body) => {
|
|
621
|
-
const { session_id, branch } = ContextBody.parse(body);
|
|
622
|
-
logger.debug('hooks', 'Session context query', { session_id });
|
|
623
|
-
try {
|
|
624
|
-
const parts = [];
|
|
625
|
-
// Active plans — the agent needs to know what's in flight
|
|
626
|
-
const plans = index.query({ type: 'plan' });
|
|
627
|
-
const activePlans = plans.filter((p) => {
|
|
628
|
-
const status = p.frontmatter.status;
|
|
629
|
-
return status === 'active' || status === 'in_progress';
|
|
630
|
-
});
|
|
631
|
-
if (activePlans.length > 0) {
|
|
632
|
-
const planLines = activePlans.slice(0, SESSION_CONTEXT_MAX_PLANS).map((p) => {
|
|
633
|
-
const status = p.frontmatter.status;
|
|
634
|
-
return `- **${p.title}** (${status}) \`[${p.id}]\``;
|
|
635
|
-
});
|
|
636
|
-
parts.push(`### Active Plans\n${planLines.join('\n')}`);
|
|
637
|
-
}
|
|
638
|
-
// Parent session summary — lineage continuity
|
|
639
|
-
if (session_id) {
|
|
640
|
-
const parentId = lineageGraph.getParent(session_id);
|
|
641
|
-
if (parentId) {
|
|
642
|
-
const parentNotes = index.queryByIds([sessionNoteId(parentId)]);
|
|
643
|
-
if (parentNotes.length > 0) {
|
|
644
|
-
const parent = parentNotes[0];
|
|
645
|
-
parts.push(`### Previous Session\n- **${parent.title}**: ${parent.content.slice(0, CONTEXT_SESSION_PREVIEW_CHARS)} \`[${parent.id}]\``);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
// Branch info for awareness
|
|
650
|
-
if (branch) {
|
|
651
|
-
parts.push(`Branch:: \`${branch}\``);
|
|
652
|
-
}
|
|
653
|
-
if (parts.length > 0) {
|
|
654
|
-
return { text: parts.join('\n\n') };
|
|
655
|
-
}
|
|
656
|
-
return { text: '' };
|
|
657
|
-
}
|
|
658
|
-
catch (error) {
|
|
659
|
-
logger.error('daemon', 'Session context failed', { error: error.message });
|
|
660
|
-
return { text: '' };
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
// Per-prompt context: semantic search for memories relevant to THIS specific prompt.
|
|
664
|
-
// This is the primary intelligence delivery — targeted, high-confidence, token-efficient.
|
|
665
|
-
const PromptContextBody = z.object({
|
|
666
|
-
prompt: z.string(),
|
|
667
|
-
session_id: z.string().optional(),
|
|
668
|
-
});
|
|
669
|
-
server.registerRoute('POST', '/context/prompt', async (body) => {
|
|
670
|
-
const { prompt, session_id } = PromptContextBody.parse(body);
|
|
671
|
-
if (!prompt || prompt.length < PROMPT_CONTEXT_MIN_LENGTH || !vectorIndex) {
|
|
672
|
-
return { text: '' };
|
|
673
|
-
}
|
|
674
|
-
try {
|
|
675
|
-
const emb = await generateEmbedding(embeddingProvider, prompt.slice(0, EMBEDDING_INPUT_LIMIT));
|
|
676
|
-
const results = vectorIndex.search(emb.embedding, {
|
|
677
|
-
limit: PROMPT_CONTEXT_MAX_MEMORIES,
|
|
678
|
-
type: 'memory',
|
|
679
|
-
relativeThreshold: PROMPT_CONTEXT_MIN_SIMILARITY,
|
|
680
|
-
});
|
|
681
|
-
if (results.length === 0)
|
|
682
|
-
return { text: '' };
|
|
683
|
-
const noteMap = new Map(index.queryByIds(results.map((r) => r.id)).map((n) => [n.id, n]));
|
|
684
|
-
const lines = [];
|
|
685
|
-
for (const r of results) {
|
|
686
|
-
const note = noteMap.get(r.id);
|
|
687
|
-
if (!note)
|
|
688
|
-
continue;
|
|
689
|
-
const fm = note.frontmatter;
|
|
690
|
-
if (fm.status === 'superseded' || fm.status === 'archived')
|
|
691
|
-
continue;
|
|
692
|
-
const obsType = fm.observation_type ?? 'note';
|
|
693
|
-
lines.push(`- [${obsType}] ${note.title}: ${note.content.slice(0, CONTENT_SNIPPET_CHARS)} \`[${note.id}]\``);
|
|
694
|
-
}
|
|
695
|
-
if (lines.length === 0)
|
|
696
|
-
return { text: '' };
|
|
697
|
-
const injected = `**Relevant memories for this task:**\n${lines.join('\n')}`;
|
|
698
|
-
logger.debug('context', 'Prompt context injected', {
|
|
699
|
-
session_id,
|
|
700
|
-
memories: lines.length,
|
|
701
|
-
prompt_preview: prompt.slice(0, 50),
|
|
702
|
-
});
|
|
703
|
-
return { text: injected };
|
|
704
|
-
}
|
|
705
|
-
catch (err) {
|
|
706
|
-
logger.debug('context', 'Prompt context failed', { error: err.message });
|
|
707
|
-
return { text: '' };
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
await server.start();
|
|
711
|
-
logger.info('daemon', 'Daemon ready', { vault: vaultDir, port: server.port });
|
|
712
|
-
const shutdown = async (signal) => {
|
|
713
|
-
logger.info('daemon', `${signal} received`);
|
|
714
|
-
// Wait for any active stop processing to finish before shutting down
|
|
715
|
-
if (activeStopProcessing) {
|
|
716
|
-
logger.info('daemon', 'Waiting for active stop processing to complete...');
|
|
717
|
-
await activeStopProcessing;
|
|
718
|
-
}
|
|
719
|
-
planWatcher.stopFileWatcher();
|
|
720
|
-
registry.destroy();
|
|
721
|
-
await server.stop();
|
|
722
|
-
vectorIndex?.close();
|
|
723
|
-
index.close();
|
|
724
|
-
logger.close();
|
|
725
|
-
process.exit(0);
|
|
726
|
-
};
|
|
727
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
728
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
729
|
-
}
|
|
730
|
-
// Entry point guard — only run when executed directly, not when imported in tests
|
|
731
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
732
|
-
if (process.argv[1] === __filename) {
|
|
733
|
-
main().catch((err) => {
|
|
734
|
-
process.stderr.write(`[mycod] Fatal: ${err.message}\n`);
|
|
735
|
-
process.exit(1);
|
|
736
|
-
});
|
|
737
|
-
}
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ensureNativeDeps
|
|
4
|
+
} from "../../chunk-4JML636J.js";
|
|
5
|
+
import "../../chunk-PZUWP5VK.js";
|
|
6
|
+
|
|
7
|
+
// src/entries/daemon.ts
|
|
8
|
+
ensureNativeDeps();
|
|
9
|
+
await import("../../main-5W4ADOBG.js");
|
|
738
10
|
//# sourceMappingURL=main.js.map
|