bikky 0.4.0 → 0.4.2
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/CHANGELOG.md +26 -0
- package/CODE_OF_CONDUCT.md +80 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +48 -14
- package/SECURITY.md +58 -0
- package/SUPPORT.md +23 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +72 -0
- package/dist/daemon/extraction.d.ts +12 -2
- package/dist/daemon/extraction.js +85 -133
- package/dist/daemon/transcript-sources.d.ts +26 -0
- package/dist/daemon/transcript-sources.js +193 -0
- package/dist/daemon/watcher.d.ts +3 -2
- package/dist/daemon/watcher.js +51 -2
- package/dist/install.d.ts +9 -1
- package/dist/install.js +62 -34
- package/dist/mcp/api.d.ts +4 -3
- package/dist/mcp/api.js +4 -3
- package/dist/mcp/tools.js +317 -93
- package/dist/search-scope.d.ts +24 -0
- package/dist/search-scope.js +174 -0
- package/docs/config/fully-hosted.md +1 -1
- package/docs/config/hosted-models.md +1 -1
- package/docs/configuration.md +34 -5
- package/docs/privacy-first.md +140 -0
- package/package.json +27 -5
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -9
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -576
- package/dist/config.test.js.map +0 -1
- package/dist/daemon/capture-policy.d.ts.map +0 -1
- package/dist/daemon/capture-policy.js.map +0 -1
- package/dist/daemon/capture-policy.test.d.ts +0 -2
- package/dist/daemon/capture-policy.test.d.ts.map +0 -1
- package/dist/daemon/capture-policy.test.js +0 -48
- package/dist/daemon/capture-policy.test.js.map +0 -1
- package/dist/daemon/consolidation.d.ts.map +0 -1
- package/dist/daemon/consolidation.js.map +0 -1
- package/dist/daemon/entity-typing.d.ts.map +0 -1
- package/dist/daemon/entity-typing.js.map +0 -1
- package/dist/daemon/entity-typing.test.d.ts +0 -2
- package/dist/daemon/entity-typing.test.d.ts.map +0 -1
- package/dist/daemon/entity-typing.test.js +0 -50
- package/dist/daemon/entity-typing.test.js.map +0 -1
- package/dist/daemon/episode-summary.d.ts.map +0 -1
- package/dist/daemon/episode-summary.js.map +0 -1
- package/dist/daemon/episode-summary.test.d.ts +0 -2
- package/dist/daemon/episode-summary.test.d.ts.map +0 -1
- package/dist/daemon/episode-summary.test.js +0 -104
- package/dist/daemon/episode-summary.test.js.map +0 -1
- package/dist/daemon/extraction-quality.test.d.ts +0 -2
- package/dist/daemon/extraction-quality.test.d.ts.map +0 -1
- package/dist/daemon/extraction-quality.test.js +0 -283
- package/dist/daemon/extraction-quality.test.js.map +0 -1
- package/dist/daemon/extraction-rules.d.ts.map +0 -1
- package/dist/daemon/extraction-rules.js.map +0 -1
- package/dist/daemon/extraction-rules.test.d.ts +0 -2
- package/dist/daemon/extraction-rules.test.d.ts.map +0 -1
- package/dist/daemon/extraction-rules.test.js +0 -203
- package/dist/daemon/extraction-rules.test.js.map +0 -1
- package/dist/daemon/extraction.d.ts.map +0 -1
- package/dist/daemon/extraction.js.map +0 -1
- package/dist/daemon/extraction.test.d.ts +0 -2
- package/dist/daemon/extraction.test.d.ts.map +0 -1
- package/dist/daemon/extraction.test.js +0 -225
- package/dist/daemon/extraction.test.js.map +0 -1
- package/dist/daemon/index.d.ts.map +0 -1
- package/dist/daemon/index.js.map +0 -1
- package/dist/daemon/loop.d.ts.map +0 -1
- package/dist/daemon/loop.js.map +0 -1
- package/dist/daemon/loop.test.d.ts +0 -2
- package/dist/daemon/loop.test.d.ts.map +0 -1
- package/dist/daemon/loop.test.js +0 -85
- package/dist/daemon/loop.test.js.map +0 -1
- package/dist/daemon/maintenance-state.d.ts.map +0 -1
- package/dist/daemon/maintenance-state.js.map +0 -1
- package/dist/daemon/maintenance-state.test.d.ts +0 -2
- package/dist/daemon/maintenance-state.test.d.ts.map +0 -1
- package/dist/daemon/maintenance-state.test.js +0 -56
- package/dist/daemon/maintenance-state.test.js.map +0 -1
- package/dist/daemon/qdrant.d.ts.map +0 -1
- package/dist/daemon/qdrant.js.map +0 -1
- package/dist/daemon/qdrant.test.d.ts +0 -8
- package/dist/daemon/qdrant.test.d.ts.map +0 -1
- package/dist/daemon/qdrant.test.js +0 -265
- package/dist/daemon/qdrant.test.js.map +0 -1
- package/dist/daemon/relations-vocab.d.ts.map +0 -1
- package/dist/daemon/relations-vocab.js.map +0 -1
- package/dist/daemon/relations-vocab.test.d.ts +0 -2
- package/dist/daemon/relations-vocab.test.d.ts.map +0 -1
- package/dist/daemon/relations-vocab.test.js +0 -69
- package/dist/daemon/relations-vocab.test.js.map +0 -1
- package/dist/daemon/relations.d.ts.map +0 -1
- package/dist/daemon/relations.js.map +0 -1
- package/dist/daemon/relations.test.d.ts +0 -2
- package/dist/daemon/relations.test.d.ts.map +0 -1
- package/dist/daemon/relations.test.js +0 -36
- package/dist/daemon/relations.test.js.map +0 -1
- package/dist/daemon/session-index.d.ts.map +0 -1
- package/dist/daemon/session-index.js.map +0 -1
- package/dist/daemon/session-index.test.d.ts +0 -2
- package/dist/daemon/session-index.test.d.ts.map +0 -1
- package/dist/daemon/session-index.test.js +0 -60
- package/dist/daemon/session-index.test.js.map +0 -1
- package/dist/daemon/session-summary.d.ts.map +0 -1
- package/dist/daemon/session-summary.js.map +0 -1
- package/dist/daemon/session-summary.test.d.ts +0 -2
- package/dist/daemon/session-summary.test.d.ts.map +0 -1
- package/dist/daemon/session-summary.test.js +0 -162
- package/dist/daemon/session-summary.test.js.map +0 -1
- package/dist/daemon/staleness.d.ts.map +0 -1
- package/dist/daemon/staleness.js.map +0 -1
- package/dist/daemon/staleness.test.d.ts +0 -7
- package/dist/daemon/staleness.test.d.ts.map +0 -1
- package/dist/daemon/staleness.test.js +0 -128
- package/dist/daemon/staleness.test.js.map +0 -1
- package/dist/daemon/watcher-health.d.ts.map +0 -1
- package/dist/daemon/watcher-health.js.map +0 -1
- package/dist/daemon/watcher-health.test.d.ts +0 -5
- package/dist/daemon/watcher-health.test.d.ts.map +0 -1
- package/dist/daemon/watcher-health.test.js +0 -119
- package/dist/daemon/watcher-health.test.js.map +0 -1
- package/dist/daemon/watcher.d.ts.map +0 -1
- package/dist/daemon/watcher.js.map +0 -1
- package/dist/daemon/watcher.test.d.ts +0 -9
- package/dist/daemon/watcher.test.d.ts.map +0 -1
- package/dist/daemon/watcher.test.js +0 -204
- package/dist/daemon/watcher.test.js.map +0 -1
- package/dist/daemon/workstream-resolver.d.ts.map +0 -1
- package/dist/daemon/workstream-resolver.js.map +0 -1
- package/dist/daemon/workstream-resolver.test.d.ts +0 -2
- package/dist/daemon/workstream-resolver.test.d.ts.map +0 -1
- package/dist/daemon/workstream-resolver.test.js +0 -128
- package/dist/daemon/workstream-resolver.test.js.map +0 -1
- package/dist/daemon/workstream-summary.d.ts.map +0 -1
- package/dist/daemon/workstream-summary.js.map +0 -1
- package/dist/daemon/workstream-summary.test.d.ts +0 -2
- package/dist/daemon/workstream-summary.test.d.ts.map +0 -1
- package/dist/daemon/workstream-summary.test.js +0 -89
- package/dist/daemon/workstream-summary.test.js.map +0 -1
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js.map +0 -1
- package/dist/install.test.d.ts +0 -9
- package/dist/install.test.d.ts.map +0 -1
- package/dist/install.test.js +0 -126
- package/dist/install.test.js.map +0 -1
- package/dist/lib/qdrant-client.d.ts.map +0 -1
- package/dist/lib/qdrant-client.js.map +0 -1
- package/dist/lib/qdrant-client.test.d.ts +0 -8
- package/dist/lib/qdrant-client.test.d.ts.map +0 -1
- package/dist/lib/qdrant-client.test.js +0 -274
- package/dist/lib/qdrant-client.test.js.map +0 -1
- package/dist/lib/qdrant-pool.d.ts.map +0 -1
- package/dist/lib/qdrant-pool.js.map +0 -1
- package/dist/lifecycle.d.ts.map +0 -1
- package/dist/lifecycle.js.map +0 -1
- package/dist/lifecycle.test.d.ts +0 -8
- package/dist/lifecycle.test.d.ts.map +0 -1
- package/dist/lifecycle.test.js +0 -74
- package/dist/lifecycle.test.js.map +0 -1
- package/dist/llm/embedding/index.d.ts.map +0 -1
- package/dist/llm/embedding/index.js.map +0 -1
- package/dist/llm/embedding/index.test.d.ts +0 -8
- package/dist/llm/embedding/index.test.d.ts.map +0 -1
- package/dist/llm/embedding/index.test.js +0 -100
- package/dist/llm/embedding/index.test.js.map +0 -1
- package/dist/llm/embedding/providers/bedrock.d.ts.map +0 -1
- package/dist/llm/embedding/providers/bedrock.js.map +0 -1
- package/dist/llm/embedding/providers/bedrock.test.d.ts +0 -2
- package/dist/llm/embedding/providers/bedrock.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/bedrock.test.js +0 -24
- package/dist/llm/embedding/providers/bedrock.test.js.map +0 -1
- package/dist/llm/embedding/providers/index.d.ts.map +0 -1
- package/dist/llm/embedding/providers/index.js.map +0 -1
- package/dist/llm/embedding/providers/ollama.d.ts.map +0 -1
- package/dist/llm/embedding/providers/ollama.js.map +0 -1
- package/dist/llm/embedding/providers/ollama.test.d.ts +0 -2
- package/dist/llm/embedding/providers/ollama.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/ollama.test.js +0 -54
- package/dist/llm/embedding/providers/ollama.test.js.map +0 -1
- package/dist/llm/embedding/providers/openai.d.ts.map +0 -1
- package/dist/llm/embedding/providers/openai.js.map +0 -1
- package/dist/llm/embedding/providers/openai.test.d.ts +0 -2
- package/dist/llm/embedding/providers/openai.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/openai.test.js +0 -48
- package/dist/llm/embedding/providers/openai.test.js.map +0 -1
- package/dist/llm/embedding/providers/portkey.d.ts.map +0 -1
- package/dist/llm/embedding/providers/portkey.js.map +0 -1
- package/dist/llm/embedding/providers/portkey.test.d.ts +0 -2
- package/dist/llm/embedding/providers/portkey.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/portkey.test.js +0 -56
- package/dist/llm/embedding/providers/portkey.test.js.map +0 -1
- package/dist/llm/embedding/registry.d.ts.map +0 -1
- package/dist/llm/embedding/registry.js.map +0 -1
- package/dist/llm/embedding/registry.test.d.ts +0 -7
- package/dist/llm/embedding/registry.test.d.ts.map +0 -1
- package/dist/llm/embedding/registry.test.js +0 -68
- package/dist/llm/embedding/registry.test.js.map +0 -1
- package/dist/llm/embedding/types.d.ts.map +0 -1
- package/dist/llm/embedding/types.js.map +0 -1
- package/dist/llm/errors.d.ts.map +0 -1
- package/dist/llm/errors.js.map +0 -1
- package/dist/llm/errors.test.d.ts +0 -2
- package/dist/llm/errors.test.d.ts.map +0 -1
- package/dist/llm/errors.test.js +0 -103
- package/dist/llm/errors.test.js.map +0 -1
- package/dist/llm/fetch.d.ts.map +0 -1
- package/dist/llm/fetch.js.map +0 -1
- package/dist/llm/index.d.ts.map +0 -1
- package/dist/llm/index.js.map +0 -1
- package/dist/llm/inference/index.d.ts.map +0 -1
- package/dist/llm/inference/index.js.map +0 -1
- package/dist/llm/inference/index.test.d.ts +0 -6
- package/dist/llm/inference/index.test.d.ts.map +0 -1
- package/dist/llm/inference/index.test.js +0 -150
- package/dist/llm/inference/index.test.js.map +0 -1
- package/dist/llm/inference/providers/bedrock.d.ts.map +0 -1
- package/dist/llm/inference/providers/bedrock.js.map +0 -1
- package/dist/llm/inference/providers/bedrock.test.d.ts +0 -2
- package/dist/llm/inference/providers/bedrock.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/bedrock.test.js +0 -68
- package/dist/llm/inference/providers/bedrock.test.js.map +0 -1
- package/dist/llm/inference/providers/index.d.ts.map +0 -1
- package/dist/llm/inference/providers/index.js.map +0 -1
- package/dist/llm/inference/providers/ollama.d.ts.map +0 -1
- package/dist/llm/inference/providers/ollama.js.map +0 -1
- package/dist/llm/inference/providers/ollama.test.d.ts +0 -2
- package/dist/llm/inference/providers/ollama.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/ollama.test.js +0 -57
- package/dist/llm/inference/providers/ollama.test.js.map +0 -1
- package/dist/llm/inference/providers/openai.d.ts.map +0 -1
- package/dist/llm/inference/providers/openai.js.map +0 -1
- package/dist/llm/inference/providers/openai.test.d.ts +0 -2
- package/dist/llm/inference/providers/openai.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/openai.test.js +0 -82
- package/dist/llm/inference/providers/openai.test.js.map +0 -1
- package/dist/llm/inference/providers/portkey.d.ts.map +0 -1
- package/dist/llm/inference/providers/portkey.js.map +0 -1
- package/dist/llm/inference/providers/portkey.test.d.ts +0 -2
- package/dist/llm/inference/providers/portkey.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/portkey.test.js +0 -48
- package/dist/llm/inference/providers/portkey.test.js.map +0 -1
- package/dist/llm/inference/registry.d.ts.map +0 -1
- package/dist/llm/inference/registry.js.map +0 -1
- package/dist/llm/inference/registry.test.d.ts +0 -6
- package/dist/llm/inference/registry.test.d.ts.map +0 -1
- package/dist/llm/inference/registry.test.js +0 -63
- package/dist/llm/inference/registry.test.js.map +0 -1
- package/dist/llm/inference/types.d.ts.map +0 -1
- package/dist/llm/inference/types.js.map +0 -1
- package/dist/llm/telemetry.d.ts.map +0 -1
- package/dist/llm/telemetry.js.map +0 -1
- package/dist/llm/telemetry.test.d.ts +0 -5
- package/dist/llm/telemetry.test.d.ts.map +0 -1
- package/dist/llm/telemetry.test.js +0 -89
- package/dist/llm/telemetry.test.js.map +0 -1
- package/dist/llm/types.d.ts.map +0 -1
- package/dist/llm/types.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/logger.test.d.ts +0 -5
- package/dist/logger.test.d.ts.map +0 -1
- package/dist/logger.test.js +0 -103
- package/dist/logger.test.js.map +0 -1
- package/dist/mcp/api.d.ts.map +0 -1
- package/dist/mcp/api.js.map +0 -1
- package/dist/mcp/helpers.d.ts.map +0 -1
- package/dist/mcp/helpers.js.map +0 -1
- package/dist/mcp/helpers.test.d.ts +0 -5
- package/dist/mcp/helpers.test.d.ts.map +0 -1
- package/dist/mcp/helpers.test.js +0 -530
- package/dist/mcp/helpers.test.js.map +0 -1
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/taxonomy.d.ts.map +0 -1
- package/dist/mcp/taxonomy.js.map +0 -1
- package/dist/mcp/taxonomy.test.d.ts +0 -5
- package/dist/mcp/taxonomy.test.d.ts.map +0 -1
- package/dist/mcp/taxonomy.test.js +0 -215
- package/dist/mcp/taxonomy.test.js.map +0 -1
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js.map +0 -1
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js.map +0 -1
- package/dist/postinstall.d.ts.map +0 -1
- package/dist/postinstall.js.map +0 -1
- package/dist/privacy/redaction.d.ts.map +0 -1
- package/dist/privacy/redaction.js.map +0 -1
- package/dist/privacy/redaction.test.d.ts +0 -2
- package/dist/privacy/redaction.test.d.ts.map +0 -1
- package/dist/privacy/redaction.test.js +0 -51
- package/dist/privacy/redaction.test.js.map +0 -1
- package/dist/prompts/brief.d.ts.map +0 -1
- package/dist/prompts/brief.js.map +0 -1
- package/dist/prompts/contradiction.d.ts.map +0 -1
- package/dist/prompts/contradiction.js.map +0 -1
- package/dist/prompts/distill.d.ts.map +0 -1
- package/dist/prompts/distill.js.map +0 -1
- package/dist/prompts/entity-typing.d.ts.map +0 -1
- package/dist/prompts/entity-typing.js.map +0 -1
- package/dist/prompts/episode-summary.d.ts.map +0 -1
- package/dist/prompts/episode-summary.js.map +0 -1
- package/dist/prompts/extraction.d.ts.map +0 -1
- package/dist/prompts/extraction.js.map +0 -1
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/prompts.test.d.ts +0 -8
- package/dist/prompts/prompts.test.d.ts.map +0 -1
- package/dist/prompts/prompts.test.js +0 -140
- package/dist/prompts/prompts.test.js.map +0 -1
- package/dist/prompts/relations.d.ts.map +0 -1
- package/dist/prompts/relations.js.map +0 -1
- package/dist/prompts/workstream-summary.d.ts.map +0 -1
- package/dist/prompts/workstream-summary.js.map +0 -1
- package/dist/provenance/actor.d.ts.map +0 -1
- package/dist/provenance/actor.js.map +0 -1
- package/dist/provenance/actor.test.d.ts +0 -2
- package/dist/provenance/actor.test.d.ts.map +0 -1
- package/dist/provenance/actor.test.js +0 -49
- package/dist/provenance/actor.test.js.map +0 -1
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js.map +0 -1
- package/dist/render.test.d.ts +0 -8
- package/dist/render.test.d.ts.map +0 -1
- package/dist/render.test.js +0 -244
- package/dist/render.test.js.map +0 -1
- package/dist/routing.d.ts.map +0 -1
- package/dist/routing.js.map +0 -1
- package/dist/routing.test.d.ts +0 -2
- package/dist/routing.test.d.ts.map +0 -1
- package/dist/routing.test.js +0 -79
- package/dist/routing.test.js.map +0 -1
- package/dist/status.d.ts.map +0 -1
- package/dist/status.js.map +0 -1
- package/dist/status.test.d.ts +0 -5
- package/dist/status.test.d.ts.map +0 -1
- package/dist/status.test.js +0 -203
- package/dist/status.test.js.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Events-based memory extraction — reads
|
|
2
|
+
* Events-based memory extraction — reads supported coding-agent transcripts,
|
|
3
3
|
* extracts facts via LLM, and stores them in Qdrant with source: "system".
|
|
4
4
|
*
|
|
5
5
|
* Uses a JSON file for extraction state (high-water byte offsets) instead of SQLite.
|
|
6
|
-
*
|
|
6
|
+
* Copilot session detection uses lock files. Claude Code detection uses
|
|
7
|
+
* top-level JSONL transcripts under ~/.claude/projects.
|
|
7
8
|
*/
|
|
8
|
-
import { readFile, readdir, stat } from "node:fs/promises";
|
|
9
9
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import {
|
|
12
|
+
import { STATE_DIR, EXTRACTION_HEALTH_PATH } from "../config.js";
|
|
13
13
|
import * as qdrant from "./qdrant.js";
|
|
14
14
|
import { chatCompletion } from "../llm/index.js";
|
|
15
15
|
import { detectContradiction } from "./consolidation.js";
|
|
@@ -20,6 +20,7 @@ import { shouldSummarizeEvents, updateSessionSummary } from "./session-summary.j
|
|
|
20
20
|
import { redactStorageText } from "../privacy/redaction.js";
|
|
21
21
|
import { compareSubtype, hasTypedToken, verifyGrounding, verifyVolatilityCoherence } from "./extraction-rules.js";
|
|
22
22
|
import { resolveActorIdentity } from "../provenance/actor.js";
|
|
23
|
+
import { discoverClaudeTranscriptMappings, discoverCopilotTranscriptMappings, extractionStateKey, readNewTranscriptEvents, transcriptLabel, } from "./transcript-sources.js";
|
|
23
24
|
// ── Module state ─────────────────────────────────────────────────────────────
|
|
24
25
|
let logFn = (() => { });
|
|
25
26
|
let lastTickAt = 0;
|
|
@@ -27,11 +28,6 @@ export const setLogger = (fn) => {
|
|
|
27
28
|
logFn = fn;
|
|
28
29
|
};
|
|
29
30
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
30
|
-
const EXTRACTABLE_TYPES = new Set([
|
|
31
|
-
"user.message",
|
|
32
|
-
"assistant.message",
|
|
33
|
-
"session.compaction_complete",
|
|
34
|
-
]);
|
|
35
31
|
export const DEFAULT_EXTRACTION_PROMPT = `You are Bikky's memory extraction agent for open-source coding agents. Extract durable, reusable facts that help a future agent continue work without rereading the whole transcript.
|
|
36
32
|
|
|
37
33
|
## Core rule
|
|
@@ -123,50 +119,6 @@ const upsertExtractionState = (state) => {
|
|
|
123
119
|
states[state.session_id] = state;
|
|
124
120
|
saveExtractionStates(states);
|
|
125
121
|
};
|
|
126
|
-
/**
|
|
127
|
-
* Scan lock files to build PID → Copilot UUID mapping.
|
|
128
|
-
* Copilot CLI writes `inuse.<pid>.lock` in each session directory.
|
|
129
|
-
*/
|
|
130
|
-
const resolveLockFiles = async () => {
|
|
131
|
-
const cfg = loadConfig();
|
|
132
|
-
const copilotStateDir = cfg.watchers.copilot.path;
|
|
133
|
-
const mappings = [];
|
|
134
|
-
try {
|
|
135
|
-
const sessionDirs = await readdir(copilotStateDir, { withFileTypes: true });
|
|
136
|
-
for (const sessionDir of sessionDirs) {
|
|
137
|
-
if (!sessionDir.isDirectory())
|
|
138
|
-
continue;
|
|
139
|
-
const uuid = sessionDir.name;
|
|
140
|
-
const sessionPath = join(copilotStateDir, uuid);
|
|
141
|
-
const entries = await readdir(sessionPath, { withFileTypes: true });
|
|
142
|
-
for (const entry of entries) {
|
|
143
|
-
if (!entry.isFile())
|
|
144
|
-
continue;
|
|
145
|
-
const lockFile = entry.name; // inuse.12345.lock
|
|
146
|
-
const pidMatch = lockFile.match(/^inuse\.(\d+)\.lock$/);
|
|
147
|
-
if (!pidMatch || !uuid)
|
|
148
|
-
continue;
|
|
149
|
-
const pid = parseInt(pidMatch[1], 10);
|
|
150
|
-
const eventsPath = join(copilotStateDir, uuid, "events.jsonl");
|
|
151
|
-
// Verify events.jsonl exists
|
|
152
|
-
try {
|
|
153
|
-
await stat(eventsPath);
|
|
154
|
-
mappings.push({ pid, uuid, eventsPath });
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
// No events.jsonl — skip
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch (e) {
|
|
163
|
-
logFn("WARN", `Lock file scan failed: ${e.message}`);
|
|
164
|
-
}
|
|
165
|
-
return mappings;
|
|
166
|
-
};
|
|
167
|
-
/**
|
|
168
|
-
* Check if a process is still running.
|
|
169
|
-
*/
|
|
170
122
|
const isProcessAlive = (pid) => {
|
|
171
123
|
try {
|
|
172
124
|
process.kill(pid, 0);
|
|
@@ -176,59 +128,6 @@ const isProcessAlive = (pid) => {
|
|
|
176
128
|
return false;
|
|
177
129
|
}
|
|
178
130
|
};
|
|
179
|
-
/**
|
|
180
|
-
* Read new events from events.jsonl starting at the given byte offset.
|
|
181
|
-
* Returns parsed extractable events and the new byte offset.
|
|
182
|
-
*/
|
|
183
|
-
const readNewEvents = async (eventsPath, byteOffset) => {
|
|
184
|
-
const fileStat = await stat(eventsPath);
|
|
185
|
-
if (fileStat.size <= byteOffset) {
|
|
186
|
-
return { events: [], newOffset: byteOffset, totalLines: 0 };
|
|
187
|
-
}
|
|
188
|
-
// Read from byte offset to end of file
|
|
189
|
-
const buf = await readFile(eventsPath);
|
|
190
|
-
const newContent = buf.subarray(byteOffset).toString("utf-8");
|
|
191
|
-
const events = [];
|
|
192
|
-
let totalLines = 0;
|
|
193
|
-
for (const line of newContent.split("\n")) {
|
|
194
|
-
if (!line.trim())
|
|
195
|
-
continue;
|
|
196
|
-
totalLines++;
|
|
197
|
-
try {
|
|
198
|
-
const obj = JSON.parse(line);
|
|
199
|
-
if (!EXTRACTABLE_TYPES.has(obj.type))
|
|
200
|
-
continue;
|
|
201
|
-
const data = obj.data || {};
|
|
202
|
-
let content = "";
|
|
203
|
-
if (obj.type === "user.message") {
|
|
204
|
-
content = data.content || "";
|
|
205
|
-
}
|
|
206
|
-
else if (obj.type === "assistant.message") {
|
|
207
|
-
const parts = [];
|
|
208
|
-
if (data.content)
|
|
209
|
-
parts.push(data.content);
|
|
210
|
-
if (data.reasoningText)
|
|
211
|
-
parts.push(data.reasoningText);
|
|
212
|
-
// Skip reasoningOpaque — encrypted noise
|
|
213
|
-
content = parts.join("\n");
|
|
214
|
-
}
|
|
215
|
-
else if (obj.type === "session.compaction_complete") {
|
|
216
|
-
content = data.summaryContent || "";
|
|
217
|
-
}
|
|
218
|
-
if (content.length > 0) {
|
|
219
|
-
events.push({
|
|
220
|
-
type: obj.type,
|
|
221
|
-
content,
|
|
222
|
-
timestamp: obj.timestamp || new Date().toISOString(),
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
// Malformed line — skip
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return { events, newOffset: fileStat.size, totalLines };
|
|
231
|
-
};
|
|
232
131
|
/**
|
|
233
132
|
* Strip tool-call narration and boilerplate from assistant messages.
|
|
234
133
|
* Keeps substantive content — decisions, explanations, findings.
|
|
@@ -504,7 +403,7 @@ const contentHash = (text) => createHash("sha256").update(text).digest("hex");
|
|
|
504
403
|
* Dedup-check and store extracted facts in Qdrant.
|
|
505
404
|
* Returns count of facts actually inserted.
|
|
506
405
|
*/
|
|
507
|
-
const storeFacts = async (facts, sessionId, config) => {
|
|
406
|
+
const storeFacts = async (facts, sessionId, config, source) => {
|
|
508
407
|
if (!qdrant.isReady()) {
|
|
509
408
|
logFn("WARN", "Extraction: Qdrant not ready, skipping store");
|
|
510
409
|
return 0;
|
|
@@ -514,6 +413,8 @@ const storeFacts = async (facts, sessionId, config) => {
|
|
|
514
413
|
capture_policy_version: CAPTURE_POLICY_VERSION,
|
|
515
414
|
extracted_by_prompt: `${EXTRACTION_PROMPT_DESCRIPTOR.id}@${EXTRACTION_PROMPT_DESCRIPTOR.version}`,
|
|
516
415
|
};
|
|
416
|
+
if (source)
|
|
417
|
+
baseMeta.extraction_source = source;
|
|
517
418
|
const actor = resolveActorIdentity({ config });
|
|
518
419
|
if (actor.actor_label)
|
|
519
420
|
baseMeta.actor_label = actor.actor_label;
|
|
@@ -729,20 +630,38 @@ export const tick = async (config) => {
|
|
|
729
630
|
lastTickAt = now;
|
|
730
631
|
const minEvents = config.daemon.extract_min_events || CAPTURE_TRIGGERS.factExtraction.minEvents;
|
|
731
632
|
try {
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
logFn("INFO", `Extraction tick: ${
|
|
736
|
-
writeExtractionHealth(
|
|
737
|
-
for (const mapping of
|
|
738
|
-
await
|
|
633
|
+
const copilotMappings = await discoverCopilotMappings(config);
|
|
634
|
+
const claudeMappings = await discoverClaudeMappings(config);
|
|
635
|
+
const mappings = [...copilotMappings, ...claudeMappings];
|
|
636
|
+
logFn("INFO", `Extraction tick: ${copilotMappings.length} active copilot session(s), ${claudeMappings.length} claude transcript(s)`);
|
|
637
|
+
writeExtractionHealth({ copilot: copilotMappings.length, claude: claudeMappings.length }, config);
|
|
638
|
+
for (const mapping of mappings) {
|
|
639
|
+
await extractForMapping(mapping, minEvents, config);
|
|
739
640
|
}
|
|
740
641
|
}
|
|
741
642
|
catch (e) {
|
|
742
643
|
logFn("ERROR", `Extraction tick failed: ${e.message}`);
|
|
743
644
|
}
|
|
744
645
|
};
|
|
745
|
-
|
|
646
|
+
const discoverCopilotMappings = async (config) => {
|
|
647
|
+
try {
|
|
648
|
+
return await discoverCopilotTranscriptMappings(config, isProcessAlive);
|
|
649
|
+
}
|
|
650
|
+
catch (e) {
|
|
651
|
+
logFn("WARN", `Copilot transcript scan failed: ${e.message}`);
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const discoverClaudeMappings = async (config) => {
|
|
656
|
+
try {
|
|
657
|
+
return await discoverClaudeTranscriptMappings(config);
|
|
658
|
+
}
|
|
659
|
+
catch (e) {
|
|
660
|
+
logFn("WARN", `Claude transcript scan failed: ${e.message}`);
|
|
661
|
+
return [];
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
function writeExtractionHealth(activeCounts, config) {
|
|
746
665
|
try {
|
|
747
666
|
let existing = {};
|
|
748
667
|
if (existsSync(EXTRACTION_HEALTH_PATH)) {
|
|
@@ -752,11 +671,33 @@ function writeExtractionHealth(activeCount, config) {
|
|
|
752
671
|
catch { /* ignore */ }
|
|
753
672
|
}
|
|
754
673
|
const now = new Date().toISOString();
|
|
674
|
+
const activeCount = activeCounts.copilot + activeCounts.claude;
|
|
675
|
+
const existingSources = existing.sources ?? {};
|
|
676
|
+
const copilotLastActive = activeCounts.copilot > 0
|
|
677
|
+
? now
|
|
678
|
+
: (existingSources.copilot?.last_active_session_at ?? existing.last_active_session_at ?? null);
|
|
679
|
+
const claudeLastActive = activeCounts.claude > 0
|
|
680
|
+
? now
|
|
681
|
+
: (existingSources.claude?.last_active_session_at ?? null);
|
|
755
682
|
const health = {
|
|
756
683
|
last_tick_at: now,
|
|
757
684
|
last_active_session_at: activeCount > 0 ? now : (existing.last_active_session_at ?? null),
|
|
758
685
|
active_session_count: activeCount,
|
|
759
686
|
watcher_path: config.watchers.copilot.path,
|
|
687
|
+
sources: {
|
|
688
|
+
copilot: {
|
|
689
|
+
enabled: config.watchers.copilot.enabled,
|
|
690
|
+
watcher_path: config.watchers.copilot.path,
|
|
691
|
+
active_session_count: activeCounts.copilot,
|
|
692
|
+
last_active_session_at: copilotLastActive,
|
|
693
|
+
},
|
|
694
|
+
claude: {
|
|
695
|
+
enabled: config.watchers.claude.enabled,
|
|
696
|
+
watcher_path: config.watchers.claude.path,
|
|
697
|
+
active_session_count: activeCounts.claude,
|
|
698
|
+
last_active_session_at: claudeLastActive,
|
|
699
|
+
},
|
|
700
|
+
},
|
|
760
701
|
};
|
|
761
702
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
762
703
|
writeFileSync(EXTRACTION_HEALTH_PATH, JSON.stringify(health, null, 2) + "\n");
|
|
@@ -765,32 +706,37 @@ function writeExtractionHealth(activeCount, config) {
|
|
|
765
706
|
logFn("DEBUG", `Failed to write extraction health: ${e.message}`);
|
|
766
707
|
}
|
|
767
708
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
*/
|
|
773
|
-
const extractForUuid = async (mapping, minEvents, config) => {
|
|
774
|
-
const { uuid, eventsPath } = mapping;
|
|
775
|
-
// Use UUID as session_id in extraction_state (prefix with "uuid:" to avoid collisions)
|
|
776
|
-
const stateKey = `uuid:${uuid}`;
|
|
709
|
+
const extractForMapping = async (mapping, minEvents, config) => {
|
|
710
|
+
const { uuid, eventsPath, source } = mapping;
|
|
711
|
+
const stateKey = extractionStateKey(mapping);
|
|
712
|
+
const label = transcriptLabel(mapping);
|
|
777
713
|
let state = getExtractionState(stateKey);
|
|
778
714
|
if (!state) {
|
|
779
715
|
state = {
|
|
780
716
|
session_id: stateKey,
|
|
781
|
-
|
|
717
|
+
source,
|
|
718
|
+
...(source === "copilot" ? { copilot_uuid: uuid } : { claude_session_id: uuid }),
|
|
782
719
|
events_path: eventsPath,
|
|
783
720
|
byte_offset: 0,
|
|
784
721
|
last_extracted_at: null,
|
|
785
722
|
event_count: 0,
|
|
786
723
|
};
|
|
787
724
|
}
|
|
725
|
+
state.source = source;
|
|
726
|
+
if (source === "copilot") {
|
|
727
|
+
state.copilot_uuid = uuid;
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
state.claude_session_id = uuid;
|
|
731
|
+
}
|
|
732
|
+
state.events_path = eventsPath;
|
|
788
733
|
// Read new events
|
|
789
|
-
const { events, newOffset, totalLines } = await
|
|
734
|
+
const { events, newOffset, totalLines } = await readNewTranscriptEvents(eventsPath, state.byte_offset, source);
|
|
790
735
|
const shouldSummarize = shouldSummarizeEvents(events, minEvents);
|
|
791
736
|
if (events.length < minEvents && !shouldSummarize) {
|
|
792
|
-
// Still
|
|
793
|
-
|
|
737
|
+
// Still advance past pure noise, but keep below-threshold conversation
|
|
738
|
+
// events buffered until enough context accumulates for extraction.
|
|
739
|
+
if (events.length === 0 && newOffset > state.byte_offset) {
|
|
794
740
|
state.byte_offset = newOffset;
|
|
795
741
|
state.event_count += totalLines;
|
|
796
742
|
upsertExtractionState(state);
|
|
@@ -802,27 +748,33 @@ const extractForUuid = async (mapping, minEvents, config) => {
|
|
|
802
748
|
let totalFacts = 0;
|
|
803
749
|
for (let i = 0; i < chunks.length; i++) {
|
|
804
750
|
const transcript = buildTranscript(chunks[i]);
|
|
805
|
-
logFn("DEBUG", `Extraction:
|
|
751
|
+
logFn("DEBUG", `Extraction: ${label} — chunk ${i + 1}/${chunks.length}, ${chunks[i].length} events, ${transcript.length} chars`);
|
|
806
752
|
if (events.length >= minEvents) {
|
|
807
753
|
const facts = await extractFacts(transcript, stateKey, config);
|
|
808
754
|
if (facts.length > 0) {
|
|
809
|
-
const stored = await storeFacts(facts, stateKey, config);
|
|
755
|
+
const stored = await storeFacts(facts, stateKey, config, source);
|
|
810
756
|
totalFacts += stored;
|
|
811
757
|
}
|
|
812
758
|
}
|
|
813
759
|
await updateSummaryForTranscript(stateKey, transcript, chunks[i].length, config);
|
|
814
760
|
}
|
|
815
761
|
if (totalFacts > 0) {
|
|
816
|
-
logFn("INFO", `Extraction:
|
|
762
|
+
logFn("INFO", `Extraction: ${label} — ${totalFacts} facts stored (${events.length} events, ${chunks.length} chunk(s))`);
|
|
817
763
|
}
|
|
818
764
|
else {
|
|
819
|
-
logFn("DEBUG", `Extraction:
|
|
765
|
+
logFn("DEBUG", `Extraction: ${label} — ${events.length} events but 0 facts extracted`);
|
|
820
766
|
}
|
|
821
767
|
// Update high-water mark
|
|
822
768
|
state.byte_offset = newOffset;
|
|
823
769
|
state.event_count += totalLines;
|
|
824
770
|
state.last_extracted_at = new Date().toISOString();
|
|
825
|
-
state.
|
|
771
|
+
state.source = source;
|
|
772
|
+
if (source === "copilot") {
|
|
773
|
+
state.copilot_uuid = uuid;
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
state.claude_session_id = uuid;
|
|
777
|
+
}
|
|
826
778
|
state.events_path = eventsPath;
|
|
827
779
|
upsertExtractionState(state);
|
|
828
780
|
return totalFacts;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BikkyConfig } from "../config.js";
|
|
2
|
+
export type TranscriptSource = "copilot" | "claude";
|
|
3
|
+
export interface TranscriptMapping {
|
|
4
|
+
source: TranscriptSource;
|
|
5
|
+
uuid: string;
|
|
6
|
+
eventsPath: string;
|
|
7
|
+
active: boolean;
|
|
8
|
+
pid?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ParsedEvent {
|
|
11
|
+
type: string;
|
|
12
|
+
content: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const extractionStateKey: (mapping: Pick<TranscriptMapping, "source" | "uuid">) => string;
|
|
16
|
+
export declare const transcriptLabel: (mapping: Pick<TranscriptMapping, "source" | "uuid">) => string;
|
|
17
|
+
export declare const discoverCopilotTranscriptMappings: (config: BikkyConfig, isProcessAlive: (pid: number) => boolean) => Promise<TranscriptMapping[]>;
|
|
18
|
+
export declare const discoverClaudeTranscriptMappings: (config: BikkyConfig) => Promise<TranscriptMapping[]>;
|
|
19
|
+
export declare const parseCopilotTranscriptLine: (line: string) => ParsedEvent | null;
|
|
20
|
+
export declare const parseClaudeTranscriptLine: (line: string) => ParsedEvent | null;
|
|
21
|
+
export declare const readNewTranscriptEvents: (eventsPath: string, byteOffset: number, source: TranscriptSource) => Promise<{
|
|
22
|
+
events: ParsedEvent[];
|
|
23
|
+
newOffset: number;
|
|
24
|
+
totalLines: number;
|
|
25
|
+
}>;
|
|
26
|
+
//# sourceMappingURL=transcript-sources.d.ts.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
const COPILOT_EXTRACTABLE_TYPES = new Set([
|
|
4
|
+
"user.message",
|
|
5
|
+
"assistant.message",
|
|
6
|
+
"session.compaction_complete",
|
|
7
|
+
]);
|
|
8
|
+
const isNotFoundError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9
|
+
const readDirIfExists = async (dir) => {
|
|
10
|
+
try {
|
|
11
|
+
return await readdir(dir, { withFileTypes: true });
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
if (isNotFoundError(e))
|
|
15
|
+
return [];
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const extractionStateKey = (mapping) => mapping.source === "copilot" ? `uuid:${mapping.uuid}` : `claude:${mapping.uuid}`;
|
|
20
|
+
export const transcriptLabel = (mapping) => `${mapping.source}:${mapping.uuid.slice(0, 8)}`;
|
|
21
|
+
export const discoverCopilotTranscriptMappings = async (config, isProcessAlive) => {
|
|
22
|
+
if (!config.watchers.copilot.enabled)
|
|
23
|
+
return [];
|
|
24
|
+
const copilotStateDir = config.watchers.copilot.path;
|
|
25
|
+
const mappings = [];
|
|
26
|
+
const sessionDirs = await readDirIfExists(copilotStateDir);
|
|
27
|
+
for (const sessionDir of sessionDirs) {
|
|
28
|
+
if (!sessionDir.isDirectory())
|
|
29
|
+
continue;
|
|
30
|
+
const uuid = sessionDir.name;
|
|
31
|
+
const sessionPath = join(copilotStateDir, uuid);
|
|
32
|
+
const entries = await readDirIfExists(sessionPath);
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (!entry.isFile())
|
|
35
|
+
continue;
|
|
36
|
+
const pidMatch = entry.name.match(/^inuse\.(\d+)\.lock$/);
|
|
37
|
+
if (!pidMatch || !uuid)
|
|
38
|
+
continue;
|
|
39
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
40
|
+
if (!isProcessAlive(pid))
|
|
41
|
+
continue;
|
|
42
|
+
const eventsPath = join(copilotStateDir, uuid, "events.jsonl");
|
|
43
|
+
try {
|
|
44
|
+
await stat(eventsPath);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
if (isNotFoundError(e))
|
|
48
|
+
continue;
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
mappings.push({ source: "copilot", pid, uuid, eventsPath, active: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return mappings;
|
|
55
|
+
};
|
|
56
|
+
export const discoverClaudeTranscriptMappings = async (config) => {
|
|
57
|
+
if (!config.watchers.claude.enabled)
|
|
58
|
+
return [];
|
|
59
|
+
const baseDir = config.watchers.claude.path;
|
|
60
|
+
const mappings = [];
|
|
61
|
+
const entries = await readDirIfExists(baseDir);
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const entryPath = join(baseDir, entry.name);
|
|
64
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
65
|
+
const fileStat = await stat(entryPath);
|
|
66
|
+
mappings.push({
|
|
67
|
+
source: "claude",
|
|
68
|
+
uuid: basename(entry.name, ".jsonl"),
|
|
69
|
+
eventsPath: entryPath,
|
|
70
|
+
active: true,
|
|
71
|
+
mtimeMs: fileStat.mtimeMs,
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!entry.isDirectory())
|
|
76
|
+
continue;
|
|
77
|
+
const projectEntries = await readDirIfExists(entryPath);
|
|
78
|
+
for (const projectEntry of projectEntries) {
|
|
79
|
+
if (!projectEntry.isFile() || !projectEntry.name.endsWith(".jsonl"))
|
|
80
|
+
continue;
|
|
81
|
+
const eventsPath = join(entryPath, projectEntry.name);
|
|
82
|
+
const fileStat = await stat(eventsPath);
|
|
83
|
+
mappings.push({
|
|
84
|
+
source: "claude",
|
|
85
|
+
uuid: basename(projectEntry.name, ".jsonl"),
|
|
86
|
+
eventsPath,
|
|
87
|
+
active: true,
|
|
88
|
+
mtimeMs: fileStat.mtimeMs,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return mappings
|
|
93
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
94
|
+
.map(({ mtimeMs: _mtimeMs, ...mapping }) => mapping);
|
|
95
|
+
};
|
|
96
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
97
|
+
const stringField = (value) => typeof value === "string" ? value : "";
|
|
98
|
+
const claudeTextContent = (content) => {
|
|
99
|
+
if (typeof content === "string")
|
|
100
|
+
return content;
|
|
101
|
+
if (!Array.isArray(content))
|
|
102
|
+
return "";
|
|
103
|
+
const parts = [];
|
|
104
|
+
for (const block of content) {
|
|
105
|
+
if (typeof block === "string") {
|
|
106
|
+
parts.push(block);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!isRecord(block) || block.type !== "text")
|
|
110
|
+
continue;
|
|
111
|
+
const text = stringField(block.text);
|
|
112
|
+
if (text)
|
|
113
|
+
parts.push(text);
|
|
114
|
+
}
|
|
115
|
+
return parts.join("\n");
|
|
116
|
+
};
|
|
117
|
+
export const parseCopilotTranscriptLine = (line) => {
|
|
118
|
+
const obj = JSON.parse(line);
|
|
119
|
+
if (!obj.type || !COPILOT_EXTRACTABLE_TYPES.has(obj.type))
|
|
120
|
+
return null;
|
|
121
|
+
const data = obj.data || {};
|
|
122
|
+
let content = "";
|
|
123
|
+
if (obj.type === "user.message") {
|
|
124
|
+
content = stringField(data.content);
|
|
125
|
+
}
|
|
126
|
+
else if (obj.type === "assistant.message") {
|
|
127
|
+
const parts = [];
|
|
128
|
+
const contentText = stringField(data.content);
|
|
129
|
+
const reasoningText = stringField(data.reasoningText);
|
|
130
|
+
if (contentText)
|
|
131
|
+
parts.push(contentText);
|
|
132
|
+
if (reasoningText)
|
|
133
|
+
parts.push(reasoningText);
|
|
134
|
+
content = parts.join("\n");
|
|
135
|
+
}
|
|
136
|
+
else if (obj.type === "session.compaction_complete") {
|
|
137
|
+
content = stringField(data.summaryContent);
|
|
138
|
+
}
|
|
139
|
+
if (!content)
|
|
140
|
+
return null;
|
|
141
|
+
return {
|
|
142
|
+
type: obj.type,
|
|
143
|
+
content,
|
|
144
|
+
timestamp: obj.timestamp || new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
export const parseClaudeTranscriptLine = (line) => {
|
|
148
|
+
const obj = JSON.parse(line);
|
|
149
|
+
const recordType = obj.type;
|
|
150
|
+
if (recordType !== "user" && recordType !== "assistant")
|
|
151
|
+
return null;
|
|
152
|
+
const message = isRecord(obj.message) ? obj.message : null;
|
|
153
|
+
if (!message)
|
|
154
|
+
return null;
|
|
155
|
+
const role = message.role === "user" || message.role === "assistant" ? message.role : recordType;
|
|
156
|
+
if (role !== "user" && role !== "assistant")
|
|
157
|
+
return null;
|
|
158
|
+
const content = claudeTextContent(message.content).trim();
|
|
159
|
+
if (!content)
|
|
160
|
+
return null;
|
|
161
|
+
return {
|
|
162
|
+
type: role === "user" ? "user.message" : "assistant.message",
|
|
163
|
+
content,
|
|
164
|
+
timestamp: stringField(obj.timestamp) || new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
export const readNewTranscriptEvents = async (eventsPath, byteOffset, source) => {
|
|
168
|
+
const fileStat = await stat(eventsPath);
|
|
169
|
+
if (fileStat.size <= byteOffset) {
|
|
170
|
+
return { events: [], newOffset: byteOffset, totalLines: 0 };
|
|
171
|
+
}
|
|
172
|
+
const buf = await readFile(eventsPath);
|
|
173
|
+
const newContent = buf.subarray(byteOffset).toString("utf-8");
|
|
174
|
+
const events = [];
|
|
175
|
+
let totalLines = 0;
|
|
176
|
+
for (const line of newContent.split("\n")) {
|
|
177
|
+
if (!line.trim())
|
|
178
|
+
continue;
|
|
179
|
+
totalLines++;
|
|
180
|
+
try {
|
|
181
|
+
const event = source === "claude"
|
|
182
|
+
? parseClaudeTranscriptLine(line)
|
|
183
|
+
: parseCopilotTranscriptLine(line);
|
|
184
|
+
if (event)
|
|
185
|
+
events.push(event);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Malformed line — skip it.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { events, newOffset: fileStat.size, totalLines };
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=transcript-sources.js.map
|
package/dist/daemon/watcher.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* ~/.copilot/session-state/ for directories with events.jsonl files.
|
|
2
|
+
* Session watcher helpers for supported coding-agent transcript directories.
|
|
4
3
|
*/
|
|
5
4
|
export interface WatchedSession {
|
|
6
5
|
uuid: string;
|
|
7
6
|
eventsPath: string;
|
|
8
7
|
active: boolean;
|
|
8
|
+
source?: "copilot" | "claude";
|
|
9
9
|
}
|
|
10
10
|
export declare function discoverSessions(): WatchedSession[];
|
|
11
|
+
export declare function discoverClaudeSessions(): WatchedSession[];
|
|
11
12
|
//# sourceMappingURL=watcher.d.ts.map
|
package/dist/daemon/watcher.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* ~/.copilot/session-state/ for directories with events.jsonl files.
|
|
2
|
+
* Session watcher helpers for supported coding-agent transcript directories.
|
|
4
3
|
*/
|
|
5
4
|
import fs from "node:fs";
|
|
6
5
|
import path from "node:path";
|
|
@@ -31,8 +30,58 @@ export function discoverSessions() {
|
|
|
31
30
|
uuid: entry,
|
|
32
31
|
eventsPath,
|
|
33
32
|
active: lockFiles.length > 0,
|
|
33
|
+
source: "copilot",
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
return sessions;
|
|
37
37
|
}
|
|
38
|
+
export function discoverClaudeSessions() {
|
|
39
|
+
const cfg = loadConfig();
|
|
40
|
+
if (!cfg.watchers.claude.enabled)
|
|
41
|
+
return [];
|
|
42
|
+
const baseDir = cfg.watchers.claude.path;
|
|
43
|
+
if (!fs.existsSync(baseDir))
|
|
44
|
+
return [];
|
|
45
|
+
const sessions = [];
|
|
46
|
+
for (const entry of fs.readdirSync(baseDir)) {
|
|
47
|
+
const projectPath = path.join(baseDir, entry);
|
|
48
|
+
let stat;
|
|
49
|
+
try {
|
|
50
|
+
stat = fs.statSync(projectPath);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (stat.isFile() && entry.endsWith(".jsonl")) {
|
|
56
|
+
sessions.push({
|
|
57
|
+
uuid: path.basename(entry, ".jsonl"),
|
|
58
|
+
eventsPath: projectPath,
|
|
59
|
+
active: true,
|
|
60
|
+
source: "claude",
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!stat.isDirectory())
|
|
65
|
+
continue;
|
|
66
|
+
for (const file of fs.readdirSync(projectPath)) {
|
|
67
|
+
const transcriptPath = path.join(projectPath, file);
|
|
68
|
+
let transcriptStat;
|
|
69
|
+
try {
|
|
70
|
+
transcriptStat = fs.statSync(transcriptPath);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!transcriptStat.isFile() || !file.endsWith(".jsonl"))
|
|
76
|
+
continue;
|
|
77
|
+
sessions.push({
|
|
78
|
+
uuid: path.basename(file, ".jsonl"),
|
|
79
|
+
eventsPath: transcriptPath,
|
|
80
|
+
active: true,
|
|
81
|
+
source: "claude",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return sessions;
|
|
86
|
+
}
|
|
38
87
|
//# sourceMappingURL=watcher.js.map
|
package/dist/install.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Write MCP config entries for Copilot and/or Claude Code.
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export interface InstallOptions {
|
|
5
|
+
homeDir?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Defaults to `claude`. Set to null to skip the Claude Code CLI and write the
|
|
8
|
+
* user config file directly.
|
|
9
|
+
*/
|
|
10
|
+
claudeCommand?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function writeInstallConfig(options?: InstallOptions): Promise<void>;
|
|
5
13
|
//# sourceMappingURL=install.d.ts.map
|