elepha 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +375 -0
- package/README.md +135 -0
- package/bin/elepha.js +2 -0
- package/dist/adapters/base.js +600 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude-code.js +260 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.js +499 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/discriminators.js +39 -0
- package/dist/adapters/discriminators.js.map +1 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/internal-command.js +3 -0
- package/dist/adapters/internal-command.js.map +1 -0
- package/dist/cli/backup-wizard.js +97 -0
- package/dist/cli/backup-wizard.js.map +1 -0
- package/dist/cli/capture-service.js +80 -0
- package/dist/cli/capture-service.js.map +1 -0
- package/dist/cli/commands/backfills.js +336 -0
- package/dist/cli/commands/backfills.js.map +1 -0
- package/dist/cli/commands/backup.js +234 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.js +66 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/consent.js +237 -0
- package/dist/cli/commands/consent.js.map +1 -0
- package/dist/cli/commands/daemon-control.js +76 -0
- package/dist/cli/commands/daemon-control.js.map +1 -0
- package/dist/cli/commands/doctor.js +31 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/hook.js +24 -0
- package/dist/cli/commands/hook.js.map +1 -0
- package/dist/cli/commands/import.js +611 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/inspect.js +54 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/install.js +49 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/internal.js +16 -0
- package/dist/cli/commands/internal.js.map +1 -0
- package/dist/cli/commands/mcp.js +12 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/projects.js +26 -0
- package/dist/cli/commands/projects.js.map +1 -0
- package/dist/cli/commands/purge.js +216 -0
- package/dist/cli/commands/purge.js.map +1 -0
- package/dist/cli/commands/reingest.js +80 -0
- package/dist/cli/commands/reingest.js.map +1 -0
- package/dist/cli/commands/rekey.js +58 -0
- package/dist/cli/commands/rekey.js.map +1 -0
- package/dist/cli/commands/restore.js +376 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/rollup.js +130 -0
- package/dist/cli/commands/rollup.js.map +1 -0
- package/dist/cli/commands/sanitize.js +72 -0
- package/dist/cli/commands/sanitize.js.map +1 -0
- package/dist/cli/commands/segment.js +153 -0
- package/dist/cli/commands/segment.js.map +1 -0
- package/dist/cli/commands/self-update.js +26 -0
- package/dist/cli/commands/self-update.js.map +1 -0
- package/dist/cli/commands/start.js +53 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/stats.js +42 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/status.js +73 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/uninstall.js +23 -0
- package/dist/cli/commands/uninstall.js.map +1 -0
- package/dist/cli/config-wizard.js +104 -0
- package/dist/cli/config-wizard.js.map +1 -0
- package/dist/cli/destructive-op.js +32 -0
- package/dist/cli/destructive-op.js.map +1 -0
- package/dist/cli/import-wizard.js +89 -0
- package/dist/cli/import-wizard.js.map +1 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-wizard.js +80 -0
- package/dist/cli/init-wizard.js.map +1 -0
- package/dist/cli/init.js +159 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/launcher-probe.js +71 -0
- package/dist/cli/launcher-probe.js.map +1 -0
- package/dist/cli/project-path.js +16 -0
- package/dist/cli/project-path.js.map +1 -0
- package/dist/cli/purge-wizard.js +200 -0
- package/dist/cli/purge-wizard.js.map +1 -0
- package/dist/cli/restore-wizard.js +101 -0
- package/dist/cli/restore-wizard.js.map +1 -0
- package/dist/cli/shared.js +128 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/status.js +22 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/wordmark.js +15 -0
- package/dist/cli/wordmark.js.map +1 -0
- package/dist/config/constants.js +115 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/env.js +168 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/memory-config.js +63 -0
- package/dist/config/memory-config.js.map +1 -0
- package/dist/config/paths.js +343 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/settings.js +132 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/config/version.js +12 -0
- package/dist/config/version.js.map +1 -0
- package/dist/daemon/failure-window.js +38 -0
- package/dist/daemon/failure-window.js.map +1 -0
- package/dist/daemon/heartbeat.js +40 -0
- package/dist/daemon/heartbeat.js.map +1 -0
- package/dist/daemon/index.js +873 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/log-rotation.js +27 -0
- package/dist/daemon/log-rotation.js.map +1 -0
- package/dist/daemon/readability-guard.js +89 -0
- package/dist/daemon/readability-guard.js.map +1 -0
- package/dist/daemon/rollup-service.js +239 -0
- package/dist/daemon/rollup-service.js.map +1 -0
- package/dist/daemon/update-check.js +87 -0
- package/dist/daemon/update-check.js.map +1 -0
- package/dist/daemon/work-queue.js +28 -0
- package/dist/daemon/work-queue.js.map +1 -0
- package/dist/discovery/session-projects.js +242 -0
- package/dist/discovery/session-projects.js.map +1 -0
- package/dist/hooks/codex-trust.js +80 -0
- package/dist/hooks/codex-trust.js.map +1 -0
- package/dist/hooks/common.js +81 -0
- package/dist/hooks/common.js.map +1 -0
- package/dist/hooks/hook-log.js +38 -0
- package/dist/hooks/hook-log.js.map +1 -0
- package/dist/hooks/installer.js +200 -0
- package/dist/hooks/installer.js.map +1 -0
- package/dist/hooks/session-start.js +321 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/hooks/user-prompt-submit.js +248 -0
- package/dist/hooks/user-prompt-submit.js.map +1 -0
- package/dist/install/binary.js +72 -0
- package/dist/install/binary.js.map +1 -0
- package/dist/install/config-file.js +119 -0
- package/dist/install/config-file.js.map +1 -0
- package/dist/install/daemon-health.js +48 -0
- package/dist/install/daemon-health.js.map +1 -0
- package/dist/install/daemon-service.js +309 -0
- package/dist/install/daemon-service.js.map +1 -0
- package/dist/install/doctor.js +180 -0
- package/dist/install/doctor.js.map +1 -0
- package/dist/install/health-checks.js +90 -0
- package/dist/install/health-checks.js.map +1 -0
- package/dist/install/installer.js +411 -0
- package/dist/install/installer.js.map +1 -0
- package/dist/install/launchd-backend.js +261 -0
- package/dist/install/launchd-backend.js.map +1 -0
- package/dist/install/launcher.js +224 -0
- package/dist/install/launcher.js.map +1 -0
- package/dist/install/markers.js +14 -0
- package/dist/install/markers.js.map +1 -0
- package/dist/install/platform.js +29 -0
- package/dist/install/platform.js.map +1 -0
- package/dist/install/present-tools.js +16 -0
- package/dist/install/present-tools.js.map +1 -0
- package/dist/install/self-update.js +120 -0
- package/dist/install/self-update.js.map +1 -0
- package/dist/install/service-backend.js +36 -0
- package/dist/install/service-backend.js.map +1 -0
- package/dist/install/service-manifest.js +36 -0
- package/dist/install/service-manifest.js.map +1 -0
- package/dist/install/status.js +58 -0
- package/dist/install/status.js.map +1 -0
- package/dist/install/systemd-backend.js +225 -0
- package/dist/install/systemd-backend.js.map +1 -0
- package/dist/markers.js +5 -0
- package/dist/markers.js.map +1 -0
- package/dist/mcp/installer.js +131 -0
- package/dist/mcp/installer.js.map +1 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-context.js +3 -0
- package/dist/mcp/tools/get-context.js.map +1 -0
- package/dist/mcp/tools/list-projects.js +3 -0
- package/dist/mcp/tools/list-projects.js.map +1 -0
- package/dist/mcp/tools/save-memory.js +3 -0
- package/dist/mcp/tools/save-memory.js.map +1 -0
- package/dist/mcp/tools.js +288 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/rendering/raw-turn-renderer.js +85 -0
- package/dist/rendering/raw-turn-renderer.js.map +1 -0
- package/dist/security/file-permissions.js +15 -0
- package/dist/security/file-permissions.js.map +1 -0
- package/dist/security/provider-transcript.js +74 -0
- package/dist/security/provider-transcript.js.map +1 -0
- package/dist/security/sanitize.js +210 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/security/self-ingestion.js +47 -0
- package/dist/security/self-ingestion.js.map +1 -0
- package/dist/security/sentinel.js +19 -0
- package/dist/security/sentinel.js.map +1 -0
- package/dist/security/subprocess-allowlist.js +421 -0
- package/dist/security/subprocess-allowlist.js.map +1 -0
- package/dist/serving/instructions.js +33 -0
- package/dist/serving/instructions.js.map +1 -0
- package/dist/serving/lexical-recall.js +249 -0
- package/dist/serving/lexical-recall.js.map +1 -0
- package/dist/serving/session-reader.js +302 -0
- package/dist/serving/session-reader.js.map +1 -0
- package/dist/storage/backfill-runner.js +34 -0
- package/dist/storage/backfill-runner.js.map +1 -0
- package/dist/storage/backup.js +46 -0
- package/dist/storage/backup.js.map +1 -0
- package/dist/storage/candidate-validator.js +77 -0
- package/dist/storage/candidate-validator.js.map +1 -0
- package/dist/storage/consent-store.js +198 -0
- package/dist/storage/consent-store.js.map +1 -0
- package/dist/storage/custom-title-backfill.js +68 -0
- package/dist/storage/custom-title-backfill.js.map +1 -0
- package/dist/storage/db.js +312 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/external-agent-import-purge.js +182 -0
- package/dist/storage/external-agent-import-purge.js.map +1 -0
- package/dist/storage/first-prompt-search-backfill.js +76 -0
- package/dist/storage/first-prompt-search-backfill.js.map +1 -0
- package/dist/storage/first-prompt-search.js +7 -0
- package/dist/storage/first-prompt-search.js.map +1 -0
- package/dist/storage/injection-store.js +37 -0
- package/dist/storage/injection-store.js.map +1 -0
- package/dist/storage/memory-store.js +428 -0
- package/dist/storage/memory-store.js.map +1 -0
- package/dist/storage/project-resolver.js +294 -0
- package/dist/storage/project-resolver.js.map +1 -0
- package/dist/storage/project-store.js +104 -0
- package/dist/storage/project-store.js.map +1 -0
- package/dist/storage/rendered-chars-backfill.js +85 -0
- package/dist/storage/rendered-chars-backfill.js.map +1 -0
- package/dist/storage/resegmentation.js +582 -0
- package/dist/storage/resegmentation.js.map +1 -0
- package/dist/storage/rollup-store.js +246 -0
- package/dist/storage/rollup-store.js.map +1 -0
- package/dist/storage/root-commit-backfill.js +32 -0
- package/dist/storage/root-commit-backfill.js.map +1 -0
- package/dist/storage/sanitize-backfill.js +184 -0
- package/dist/storage/sanitize-backfill.js.map +1 -0
- package/dist/storage/segmentation.js +87 -0
- package/dist/storage/segmentation.js.map +1 -0
- package/dist/storage/session-fields-backfill.js +196 -0
- package/dist/storage/session-fields-backfill.js.map +1 -0
- package/dist/storage/session-read-model.js +130 -0
- package/dist/storage/session-read-model.js.map +1 -0
- package/dist/storage/session-store.js +200 -0
- package/dist/storage/session-store.js.map +1 -0
- package/dist/storage/session-title-backfill.js +75 -0
- package/dist/storage/session-title-backfill.js.map +1 -0
- package/dist/storage/session-title.js +71 -0
- package/dist/storage/session-title.js.map +1 -0
- package/dist/storage/shown-session-list-store.js +33 -0
- package/dist/storage/shown-session-list-store.js.map +1 -0
- package/dist/storage/stats.js +30 -0
- package/dist/storage/stats.js.map +1 -0
- package/dist/storage/turn-store.js +202 -0
- package/dist/storage/turn-store.js.map +1 -0
- package/dist/storage/ulid.js +17 -0
- package/dist/storage/ulid.js.map +1 -0
- package/dist/summarizer/anthropic-call.js +76 -0
- package/dist/summarizer/anthropic-call.js.map +1 -0
- package/dist/summarizer/call-log.js +120 -0
- package/dist/summarizer/call-log.js.map +1 -0
- package/dist/summarizer/haiku-provider.js +136 -0
- package/dist/summarizer/haiku-provider.js.map +1 -0
- package/dist/summarizer/pricing.js +8 -0
- package/dist/summarizer/pricing.js.map +1 -0
- package/dist/summarizer/prompt-cache.js +6 -0
- package/dist/summarizer/prompt-cache.js.map +1 -0
- package/dist/summarizer/prompt.js +47 -0
- package/dist/summarizer/prompt.js.map +1 -0
- package/dist/summarizer/provider-config.js +25 -0
- package/dist/summarizer/provider-config.js.map +1 -0
- package/dist/summarizer/rollup-prompt.js +225 -0
- package/dist/summarizer/rollup-prompt.js.map +1 -0
- package/dist/summarizer/rollup-provider.js +224 -0
- package/dist/summarizer/rollup-provider.js.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/util/error.js +5 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/fs.js +97 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/relative-time.js +12 -0
- package/dist/util/relative-time.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { INTERNAL_COMMAND_TAGS } from '../adapters/internal-command.js';
|
|
2
|
+
import { MAX_TITLE_CHARS } from '../config/constants.js';
|
|
3
|
+
import { stripShellSyntax } from '../security/sanitize.js';
|
|
4
|
+
export const UNTITLED_EPISODE = 'Untitled episode';
|
|
5
|
+
const COMPACTION_SUMMARY_PREFIX = 'this session is being continued from a previous conversation that ran out of context.';
|
|
6
|
+
const CODEX_HISTORY_REVIEW_PREFIX = 'the following is the codex agent history whose request action you are assessing';
|
|
7
|
+
function cleanTitle(value) {
|
|
8
|
+
const cleaned = stripShellSyntax(value).replace(/\s+/g, ' ').trim();
|
|
9
|
+
return cleaned.length <= MAX_TITLE_CHARS ? cleaned : `${cleaned.slice(0, MAX_TITLE_CHARS - 1)}…`;
|
|
10
|
+
}
|
|
11
|
+
function promptTitle(value) {
|
|
12
|
+
const prompt = value.trim();
|
|
13
|
+
const firstToken = prompt.split(/\s+/, 1)[0] ?? '';
|
|
14
|
+
if (prompt === '' ||
|
|
15
|
+
/^\/[A-Za-z0-9][A-Za-z0-9_-]*$/.test(firstToken) ||
|
|
16
|
+
INTERNAL_COMMAND_TAGS.test(prompt) ||
|
|
17
|
+
prompt.toLowerCase().startsWith('elepha:') ||
|
|
18
|
+
prompt.toLowerCase() === 'compact' ||
|
|
19
|
+
prompt.toLowerCase().startsWith(COMPACTION_SUMMARY_PREFIX) ||
|
|
20
|
+
prompt.toLowerCase().startsWith(CODEX_HISTORY_REVIEW_PREFIX)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const lines = prompt.split(/\r?\n/).filter((line) => line.trim() !== '');
|
|
24
|
+
const firstLine = lines[0] ?? prompt;
|
|
25
|
+
const firstLineWithoutHeading = firstLine.replace(/^#+\s+/, '');
|
|
26
|
+
let title = cleanTitle(firstLineWithoutHeading);
|
|
27
|
+
if (title.length < 20) {
|
|
28
|
+
title =
|
|
29
|
+
firstLineWithoutHeading === firstLine
|
|
30
|
+
? cleanTitle(prompt)
|
|
31
|
+
: (lines
|
|
32
|
+
.slice(1)
|
|
33
|
+
.map((line) => cleanTitle(line.replace(/^#+\s+/, '')))
|
|
34
|
+
.find((lineTitle) => lineTitle.length >= 20) ?? cleanTitle(prompt));
|
|
35
|
+
}
|
|
36
|
+
return title === '' ? undefined : title;
|
|
37
|
+
}
|
|
38
|
+
function aiTitle(value) {
|
|
39
|
+
if (value === undefined) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const title = cleanTitle(value);
|
|
43
|
+
return title === '' ? undefined : title;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves the title for one persisted segment without reading a transcript
|
|
47
|
+
* at serve time. An ai-title replaces the fallback only in the segment that
|
|
48
|
+
* emitted it; a later segment begins with no inherited state.
|
|
49
|
+
*/
|
|
50
|
+
export function titleForTurn(currentTitle, turn, includeAiTitle) {
|
|
51
|
+
const prompt = promptTitle(turn.userMessage);
|
|
52
|
+
const generated = includeAiTitle ? aiTitle(turn.aiTitle) : undefined;
|
|
53
|
+
if (generated !== undefined && (prompt !== undefined || (currentTitle !== null && currentTitle !== UNTITLED_EPISODE))) {
|
|
54
|
+
return generated;
|
|
55
|
+
}
|
|
56
|
+
if (currentTitle === null || currentTitle === UNTITLED_EPISODE) {
|
|
57
|
+
return prompt ?? UNTITLED_EPISODE;
|
|
58
|
+
}
|
|
59
|
+
return currentTitle;
|
|
60
|
+
}
|
|
61
|
+
/** Derives the closed title rule for a fully known segment during backfill. */
|
|
62
|
+
export function titleForSegment(turns, includeAiTitle) {
|
|
63
|
+
let firstAiTitle;
|
|
64
|
+
let firstPrompt;
|
|
65
|
+
for (const turn of turns) {
|
|
66
|
+
firstAiTitle ??= includeAiTitle ? aiTitle(turn.aiTitle) : undefined;
|
|
67
|
+
firstPrompt ??= promptTitle(turn.userMessage);
|
|
68
|
+
}
|
|
69
|
+
return firstPrompt === undefined ? UNTITLED_EPISODE : (firstAiTitle ?? firstPrompt);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=session-title.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-title.js","sourceRoot":"","sources":["../../src/storage/session-title.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class ShownSessionListStore {
|
|
2
|
+
replaceList;
|
|
3
|
+
listForChat;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.replaceList = db.prepare(`INSERT INTO shown_session_lists (tool, native_session_id, session_ids)
|
|
6
|
+
VALUES (?, ?, ?)
|
|
7
|
+
ON CONFLICT(tool, native_session_id) DO UPDATE SET session_ids = excluded.session_ids`);
|
|
8
|
+
this.listForChat = db.prepare('SELECT session_ids FROM shown_session_lists WHERE tool = ? AND native_session_id = ?');
|
|
9
|
+
}
|
|
10
|
+
/** Replaces the complete ordered list so one chat can only open what it was most recently shown. */
|
|
11
|
+
replace(tool, nativeSessionId, sessionIds) {
|
|
12
|
+
this.replaceList.run(tool, nativeSessionId, JSON.stringify(sessionIds));
|
|
13
|
+
}
|
|
14
|
+
/** Undefined means no list has ever been shown; an empty array is a shown list with no rows. */
|
|
15
|
+
forChat(tool, nativeSessionId) {
|
|
16
|
+
const row = this.listForChat.get(tool, nativeSessionId);
|
|
17
|
+
if (row === undefined) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
let decoded;
|
|
21
|
+
try {
|
|
22
|
+
decoded = JSON.parse(row.session_ids);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error('stored shown session list is invalid');
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(decoded) || !decoded.every((id) => Number.isSafeInteger(id) && id > 0)) {
|
|
28
|
+
throw new Error('stored shown session list is invalid');
|
|
29
|
+
}
|
|
30
|
+
return decoded;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=shown-session-list-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shown-session-list-store.js","sourceRoot":"","sources":["../../src/storage/shown-session-list-store.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,qBAAqB;IACb,WAAW,CAAY;IACvB,WAAW,CAAY;IAExC,YAAY,EAAY;QACpB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,OAAO,CACzB;;mGAEuF,CAC1F,CAAC;QACF,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,sFAAsF,CAAC,CAAC;IAC1H,CAAC;IAED,oGAAoG;IACpG,OAAO,CAAC,IAAc,EAAE,eAAuB,EAAE,UAA6B;QAC1E,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,gGAAgG;IAChG,OAAO,CAAC,IAAc,EAAE,eAAuB;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,CAAoC,CAAC;QAC3F,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,OAAmB,CAAC;IAC/B,CAAC;CACJ"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Dogfooding instrumentation - temporary scaffolding to size real usage
|
|
2
|
+
// (Phase 2 design questions: rows/session, decision quality signal proxy,
|
|
3
|
+
// pending_items accumulation rate, files_touched miss rate per tool).
|
|
4
|
+
// Not part of the served-memory feature surface.
|
|
5
|
+
export function median(values) {
|
|
6
|
+
if (values.length === 0) {
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
10
|
+
const mid = Math.floor(sorted.length / 2);
|
|
11
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
12
|
+
}
|
|
13
|
+
export function minMedianMax(values) {
|
|
14
|
+
if (values.length === 0) {
|
|
15
|
+
return { min: 0, median: 0, max: 0, count: 0 };
|
|
16
|
+
}
|
|
17
|
+
return { min: Math.min(...values), median: median(values), max: Math.max(...values), count: values.length };
|
|
18
|
+
}
|
|
19
|
+
const DURATION_RE = /^(\d+)([mhd])$/;
|
|
20
|
+
/** Parses shorthand like "24h", "7d", "30m", or falls back to a plain ISO date string. Returns an ISO timestamp. */
|
|
21
|
+
export function parseSince(input) {
|
|
22
|
+
const m = DURATION_RE.exec(input);
|
|
23
|
+
if (!m) {
|
|
24
|
+
return new Date(input).toISOString();
|
|
25
|
+
}
|
|
26
|
+
const amount = Number(m[1]);
|
|
27
|
+
const unitMs = { m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]];
|
|
28
|
+
return new Date(Date.now() - amount * unitMs).toISOString();
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/storage/stats.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,0EAA0E;AAC1E,sEAAsE;AACtE,iDAAiD;AAEjD,MAAM,UAAU,MAAM,CAAC,MAAgB;IACnC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,CAAC;IACb,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAgB;IACzC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAChH,CAAC;AAED,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC,oHAAoH;AACpH,MAAM,UAAU,UAAU,CAAC,KAAa;IACpC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,EAAE,CAAC;QACL,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAoB,CAAC,CAAC;IACnF,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { dedupePaths } from '../config/paths.js';
|
|
2
|
+
import { RAW_TURN_SEPARATOR, renderRawTurn } from '../rendering/raw-turn-renderer.js';
|
|
3
|
+
import { escapeShellSyntax, stripShellSyntax } from '../security/sanitize.js';
|
|
4
|
+
import { firstPromptSearch } from './first-prompt-search.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rule 3 for a per-turn decision. Both fields take the ESCAPE policy, not
|
|
7
|
+
* strip: a decision may legitimately need to name the syntax it ruled out.
|
|
8
|
+
* `why` stays null when the transcript gave no reason - see TurnDecision.
|
|
9
|
+
*/
|
|
10
|
+
export function sanitizeTurnDecision(d) {
|
|
11
|
+
return { what: escapeShellSyntax(d.what), why: d.why === null ? null : escapeShellSyntax(d.why) };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Reads a stored decisions column, which holds EITHER the current
|
|
15
|
+
* `{what, why}` objects or the bare strings every row written before per-turn
|
|
16
|
+
* rationale capture used. Legacy strings become `why: null`, which is the
|
|
17
|
+
* truth about them: no reason was ever captured, and the rationale those rows
|
|
18
|
+
* appear to have was manufactured downstream by the rollup model.
|
|
19
|
+
*
|
|
20
|
+
* Migrating the column in place was the alternative and it would be a lie -
|
|
21
|
+
* it would have to invent the `why` it is supposed to be recording.
|
|
22
|
+
*/
|
|
23
|
+
export function hydrateTurnDecisions(raw) {
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
if (!Array.isArray(parsed)) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return parsed.flatMap((d) => {
|
|
35
|
+
if (typeof d === 'string') {
|
|
36
|
+
return [{ what: d, why: null }];
|
|
37
|
+
}
|
|
38
|
+
if (d && typeof d === 'object' && typeof d.what === 'string') {
|
|
39
|
+
const rec = d;
|
|
40
|
+
return [{ what: rec.what, why: typeof rec.why === 'string' && rec.why.trim() !== '' ? rec.why : null }];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export class TurnStore {
|
|
46
|
+
db;
|
|
47
|
+
sessions;
|
|
48
|
+
stmts;
|
|
49
|
+
constructor(db, sessions) {
|
|
50
|
+
this.db = db;
|
|
51
|
+
this.sessions = sessions;
|
|
52
|
+
this.stmts = {
|
|
53
|
+
insertMemory: db.prepare(`INSERT OR IGNORE INTO memories
|
|
54
|
+
(project_id, session_id, turn_index, tool, turn_started_at, decisions, files_touched, pending_items, created_at, summarizer_status, has_external_content)
|
|
55
|
+
VALUES (@project_id, @session_id, @turn_index, @tool, @turn_started_at, @decisions, @files_touched, @pending_items, @now, @summarizer_status, @has_external_content)`),
|
|
56
|
+
// Reingest path: overwrites an existing row instead of ignoring the
|
|
57
|
+
// conflict, so a naive re-run can't silently no-op against rows
|
|
58
|
+
// already occupying (session_id, turn_index) from the broken
|
|
59
|
+
// pipeline. Never
|
|
60
|
+
// touches sessions.cursor - reingest is orthogonal to the live
|
|
61
|
+
// daemon's forward-ingestion cursor, safe to run alongside it.
|
|
62
|
+
reingestMemory: db.prepare(`INSERT INTO memories
|
|
63
|
+
(project_id, session_id, turn_index, tool, turn_started_at, decisions, files_touched, pending_items, created_at, summarizer_status, reingested_at, has_external_content)
|
|
64
|
+
VALUES (@project_id, @session_id, @turn_index, @tool, @turn_started_at, @decisions, @files_touched, @pending_items, @now, @summarizer_status, @now, @has_external_content)
|
|
65
|
+
ON CONFLICT (session_id, turn_index) DO UPDATE SET
|
|
66
|
+
decisions = excluded.decisions,
|
|
67
|
+
files_touched = excluded.files_touched,
|
|
68
|
+
pending_items = excluded.pending_items,
|
|
69
|
+
summarizer_status = excluded.summarizer_status,
|
|
70
|
+
reingested_at = excluded.reingested_at,
|
|
71
|
+
has_external_content = excluded.has_external_content`),
|
|
72
|
+
hasMemoryForNativeTurn: db.prepare(`SELECT 1 FROM memories m
|
|
73
|
+
JOIN sessions s ON s.id = m.session_id
|
|
74
|
+
WHERE s.tool = ? AND s.native_id = ? AND m.turn_index = ?
|
|
75
|
+
LIMIT 1`),
|
|
76
|
+
isTranscriptPurged: db.prepare('SELECT 1 FROM purged_transcripts WHERE tool = ? AND native_id = ?'),
|
|
77
|
+
listRecentMemories: db.prepare('SELECT * FROM memories WHERE project_id = ? ORDER BY turn_started_at DESC LIMIT ?'),
|
|
78
|
+
renderedStats: db.prepare('SELECT rendered_chars, rendered_turns FROM sessions WHERE id = ?'),
|
|
79
|
+
incrementRenderedStats: db.prepare(`UPDATE sessions
|
|
80
|
+
SET rendered_chars = CASE WHEN rendered_chars IS NULL OR rendered_chars = 0 THEN ? ELSE rendered_chars + ? END,
|
|
81
|
+
rendered_turns = CASE WHEN rendered_turns IS NULL THEN NULL ELSE rendered_turns + 1 END
|
|
82
|
+
WHERE id = ?`),
|
|
83
|
+
setFirstPromptSearch: db.prepare(`UPDATE sessions SET first_prompt_search = ?
|
|
84
|
+
WHERE id = ? AND first_prompt_search IS NULL
|
|
85
|
+
AND ? = (SELECT MIN(turn_index) FROM memories WHERE session_id = ?)`),
|
|
86
|
+
reingestFirstPromptSearch: db.prepare(`UPDATE sessions SET first_prompt_search = ?
|
|
87
|
+
WHERE id = ? AND ? = (SELECT MIN(turn_index) FROM memories WHERE session_id = ?)`),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Source turn indexes remain native-file-global across segments. This
|
|
92
|
+
* protects a whole already-processed batch being replayed after a cut:
|
|
93
|
+
* UNIQUE(session_id, turn_index) alone cannot see that turn N lives in an
|
|
94
|
+
* older segment. It is a dedupe lookup only; boundary evidence still comes
|
|
95
|
+
* exclusively from the active session row.
|
|
96
|
+
*/
|
|
97
|
+
hasMemoryForNativeTurn(tool, nativeId, turnIndex) {
|
|
98
|
+
return this.stmts.hasMemoryForNativeTurn.get(tool, nativeId, turnIndex) !== undefined;
|
|
99
|
+
}
|
|
100
|
+
/** Most recent turn_started_at across all memories - the "when did it last actually ingest something" signal for `elepha status`. */
|
|
101
|
+
getLastIngestedAt() {
|
|
102
|
+
const row = this.db.prepare('SELECT MAX(turn_started_at) as last FROM memories').get();
|
|
103
|
+
return row.last ?? undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Persists one turn's summary and advances the session cursor in a single
|
|
107
|
+
* transaction. This is the live-ingestion path only - INSERT OR IGNORE on
|
|
108
|
+
* UNIQUE(session_id, turn_index) makes a duplicate scan of an
|
|
109
|
+
* already-stored turn (overlapping watch events, see daemon/index.ts) a
|
|
110
|
+
* no-op instead of a duplicate row, safe only because the cursor advance
|
|
111
|
+
* is atomic with it (a two-statement version turns this dedupe guard into
|
|
112
|
+
* silent data loss). For deliberately overwriting an already-stored turn
|
|
113
|
+
* with a re-summarized result, use reingestTurn instead - IGNORE here
|
|
114
|
+
* would silently discard the fix.
|
|
115
|
+
*/
|
|
116
|
+
recordTurn(turn, sessionDbId, projectId, summary) {
|
|
117
|
+
const run = this.db.transaction(() => this.recordTurnInTransaction(turn, sessionDbId, projectId, summary));
|
|
118
|
+
return run();
|
|
119
|
+
}
|
|
120
|
+
/** Records a live turn while an enclosing ingestion transaction owns its session row. */
|
|
121
|
+
recordTurnInTransaction(turn, sessionDbId, projectId, summary) {
|
|
122
|
+
if (this.stmts.isTranscriptPurged.get(turn.tool, turn.sessionId) !== undefined) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const now = new Date().toISOString();
|
|
126
|
+
const info = this.stmts.insertMemory.run({
|
|
127
|
+
project_id: projectId,
|
|
128
|
+
session_id: sessionDbId,
|
|
129
|
+
turn_index: turn.turnIndex,
|
|
130
|
+
tool: turn.tool,
|
|
131
|
+
turn_started_at: turn.startedAt,
|
|
132
|
+
decisions: JSON.stringify(summary.decisions.map(sanitizeTurnDecision)),
|
|
133
|
+
files_touched: JSON.stringify(dedupePaths(turn.toolCalls.flatMap((c) => c.filePaths))),
|
|
134
|
+
pending_items: JSON.stringify(summary.pending_items.map(stripShellSyntax)),
|
|
135
|
+
now,
|
|
136
|
+
summarizer_status: summary.status,
|
|
137
|
+
has_external_content: turn.hasExternalContent ? 1 : 0,
|
|
138
|
+
});
|
|
139
|
+
this.sessions.advanceSessionCursorAt(sessionDbId, turn.cursor, now);
|
|
140
|
+
this.sessions.updateTrailingState(sessionDbId, turn);
|
|
141
|
+
if (info.changes > 0) {
|
|
142
|
+
this.stmts.setFirstPromptSearch.run(firstPromptSearch(turn.userMessage), sessionDbId, turn.turnIndex, sessionDbId);
|
|
143
|
+
this.sessions.updateSessionTitle(sessionDbId, turn);
|
|
144
|
+
this.recordRenderedTurn(sessionDbId, turn);
|
|
145
|
+
}
|
|
146
|
+
return info.changes > 0;
|
|
147
|
+
}
|
|
148
|
+
recordRenderedTurn(sessionDbId, turn) {
|
|
149
|
+
const stats = this.stmts.renderedStats.get(sessionDbId);
|
|
150
|
+
const rendered = renderRawTurn(turn, (stats.rendered_turns ?? 0) + 1);
|
|
151
|
+
if (rendered !== null) {
|
|
152
|
+
this.stmts.incrementRenderedStats.run(rendered.length + 1, rendered.length + RAW_TURN_SEPARATOR.length, sessionDbId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Overwrites an existing (session_id, turn_index) row with a fresh
|
|
157
|
+
* summary - the `elepha reingest` maintenance path. Deliberately does NOT
|
|
158
|
+
* touch sessions.cursor: reingest re-derives turns from byte 0 of the
|
|
159
|
+
* source file independently of the live daemon's forward cursor, and
|
|
160
|
+
* must never regress or advance it. Uses INSERT ... ON CONFLICT DO UPDATE
|
|
161
|
+
* rather than delete-then-insert so there is no window where the row is
|
|
162
|
+
* gone - a crash mid-reingest leaves either the old or the new value,
|
|
163
|
+
* never neither.
|
|
164
|
+
*/
|
|
165
|
+
reingestTurn(turn, sessionDbId, projectId, summary) {
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
this.stmts.reingestMemory.run({
|
|
168
|
+
project_id: projectId,
|
|
169
|
+
session_id: sessionDbId,
|
|
170
|
+
turn_index: turn.turnIndex,
|
|
171
|
+
tool: turn.tool,
|
|
172
|
+
turn_started_at: turn.startedAt,
|
|
173
|
+
decisions: JSON.stringify(summary.decisions.map(sanitizeTurnDecision)),
|
|
174
|
+
files_touched: JSON.stringify(dedupePaths(turn.toolCalls.flatMap((c) => c.filePaths))),
|
|
175
|
+
pending_items: JSON.stringify(summary.pending_items.map(stripShellSyntax)),
|
|
176
|
+
now,
|
|
177
|
+
summarizer_status: summary.status,
|
|
178
|
+
has_external_content: turn.hasExternalContent ? 1 : 0,
|
|
179
|
+
});
|
|
180
|
+
this.stmts.reingestFirstPromptSearch.run(firstPromptSearch(turn.userMessage), sessionDbId, turn.turnIndex, sessionDbId);
|
|
181
|
+
}
|
|
182
|
+
/** Every turn of one session, in turn order - the rollup input. */
|
|
183
|
+
listMemoriesForSession(sessionId) {
|
|
184
|
+
const rows = this.db.prepare('SELECT * FROM memories WHERE session_id = ? ORDER BY turn_index').all(sessionId);
|
|
185
|
+
return rows.map((r) => ({
|
|
186
|
+
...r,
|
|
187
|
+
decisions: hydrateTurnDecisions(r.decisions),
|
|
188
|
+
files_touched: JSON.parse(r.files_touched),
|
|
189
|
+
pending_items: JSON.parse(r.pending_items),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
listRecentMemories(projectId, limit = 20) {
|
|
193
|
+
const rows = this.stmts.listRecentMemories.all(projectId, limit);
|
|
194
|
+
return rows.map((r) => ({
|
|
195
|
+
...r,
|
|
196
|
+
decisions: hydrateTurnDecisions(r.decisions),
|
|
197
|
+
files_touched: JSON.parse(r.files_touched),
|
|
198
|
+
pending_items: JSON.parse(r.pending_items),
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=turn-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-store.js","sourceRoot":"","sources":["../../src/storage/turn-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
3
|
+
function encode(value, length) {
|
|
4
|
+
let out = '';
|
|
5
|
+
for (let i = 0; i < length; i++) {
|
|
6
|
+
out = CROCKFORD[Number(value & 31n)] + out;
|
|
7
|
+
value >>= 5n;
|
|
8
|
+
}
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
/** Generates a canonical 26-character ULID without adding a runtime dependency. */
|
|
12
|
+
export function newUlid(now = Date.now()) {
|
|
13
|
+
const time = encode(BigInt(now), 10);
|
|
14
|
+
const randomness = randomBytes(10).reduce((value, byte) => (value << 8n) | BigInt(byte), 0n);
|
|
15
|
+
return `${time}${encode(randomness, 16)}`;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ulid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ulid.js","sourceRoot":"","sources":["../../src/storage/ulid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc;IACzC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAC3C,KAAK,KAAK,EAAE,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7F,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { errorMessage } from '../util/error.js';
|
|
3
|
+
import { sanitizeErrorMessage } from './call-log.js';
|
|
4
|
+
export const FENCE_RE = /```(?:json)?\s*([\s\S]*?)\s*```/;
|
|
5
|
+
export function extractText(message) {
|
|
6
|
+
return message.content
|
|
7
|
+
.filter((block) => block.type === 'text')
|
|
8
|
+
.map((block) => block.text)
|
|
9
|
+
.join('');
|
|
10
|
+
}
|
|
11
|
+
/** Returns direct, fenced, then outermost-brace candidates in the established fallback order. */
|
|
12
|
+
export function extractJsonCandidate(raw) {
|
|
13
|
+
const candidates = [raw.trim()];
|
|
14
|
+
const fenced = raw.match(FENCE_RE);
|
|
15
|
+
if (fenced) {
|
|
16
|
+
candidates.push(fenced[1].trim());
|
|
17
|
+
}
|
|
18
|
+
const start = raw.indexOf('{');
|
|
19
|
+
const end = raw.lastIndexOf('}');
|
|
20
|
+
if (start !== -1 && end > start) {
|
|
21
|
+
candidates.push(raw.slice(start, end + 1));
|
|
22
|
+
}
|
|
23
|
+
return candidates;
|
|
24
|
+
}
|
|
25
|
+
export async function callAnthropicJson({ client, model, callLog, system, user, maxTokens, parse, job, attempt, }) {
|
|
26
|
+
const startedAt = Date.now();
|
|
27
|
+
try {
|
|
28
|
+
const message = await client.messages.create({
|
|
29
|
+
model,
|
|
30
|
+
max_tokens: maxTokens,
|
|
31
|
+
system,
|
|
32
|
+
messages: [{ role: 'user', content: user }],
|
|
33
|
+
});
|
|
34
|
+
const rawText = extractText(message);
|
|
35
|
+
const output = parse(rawText);
|
|
36
|
+
const status = output ? 'ok' : 'parse_error';
|
|
37
|
+
const truncated = message.stop_reason === 'max_tokens';
|
|
38
|
+
callLog.append({
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
job,
|
|
41
|
+
latencyMs: Date.now() - startedAt,
|
|
42
|
+
inputTokens: message.usage.input_tokens,
|
|
43
|
+
cacheCreationInputTokens: message.usage.cache_creation_input_tokens ?? 0,
|
|
44
|
+
cacheReadInputTokens: message.usage.cache_read_input_tokens ?? 0,
|
|
45
|
+
outputTokens: message.usage.output_tokens,
|
|
46
|
+
attempt,
|
|
47
|
+
rateLimited: false,
|
|
48
|
+
// Truncation and malformed output need different fixes (bigger
|
|
49
|
+
// budget vs. better prompt), so they must not log identically.
|
|
50
|
+
// "ran out of output budget" and "emitted something unparseable"
|
|
51
|
+
// need to be told apart: one is fixed by raising max_tokens, the
|
|
52
|
+
// other by changing the prompt.
|
|
53
|
+
error: !output && truncated ? `output truncated at max_tokens (${maxTokens})` : null,
|
|
54
|
+
status,
|
|
55
|
+
});
|
|
56
|
+
return { status, output, rawText, truncated };
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const rateLimited = err instanceof Anthropic.APIError && err.status === 429;
|
|
60
|
+
callLog.append({
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
job,
|
|
63
|
+
latencyMs: Date.now() - startedAt,
|
|
64
|
+
inputTokens: null,
|
|
65
|
+
cacheCreationInputTokens: null,
|
|
66
|
+
cacheReadInputTokens: null,
|
|
67
|
+
outputTokens: null,
|
|
68
|
+
attempt,
|
|
69
|
+
rateLimited,
|
|
70
|
+
error: sanitizeErrorMessage(errorMessage(err)),
|
|
71
|
+
status: 'api_error',
|
|
72
|
+
});
|
|
73
|
+
return { status: 'api_error', truncated: false };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=anthropic-call.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-call.js","sourceRoot":"","sources":["../../src/summarizer/anthropic-call.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Dogfooding instrumentation - one JSON-line per summarizer call, rotated by
|
|
2
|
+
// date and retained for a bounded diagnostic window. Answers: does cost or
|
|
3
|
+
// rate limiting bite under real parallel-project use?
|
|
4
|
+
import { appendFileSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { SUMMARIZER_LOG_RETENTION_DAYS } from '../config/constants.js';
|
|
7
|
+
import { elephaLogDir } from '../config/paths.js';
|
|
8
|
+
import { hardenDir, hardenFile } from '../security/file-permissions.js';
|
|
9
|
+
export function defaultCallLogDir() {
|
|
10
|
+
return elephaLogDir();
|
|
11
|
+
}
|
|
12
|
+
const MAX_ERROR_LENGTH = 300;
|
|
13
|
+
// API client errors can echo request headers or key material back in their
|
|
14
|
+
// message text (e.g. a 401 quoting the rejected Authorization header).
|
|
15
|
+
// Redact anything key/token-shaped before it reaches disk - this is a
|
|
16
|
+
// backstop, not a parser, so it's deliberately broad rather than tied to one
|
|
17
|
+
// provider's key format.
|
|
18
|
+
const BEARER_RE = /Bearer\s+\S+/gi;
|
|
19
|
+
const KEY_LIKE_RE = /\b[A-Za-z0-9_-]{20,}\b/g;
|
|
20
|
+
const SUMMARIZER_LOG_NAME_RE = /^summarizer-(\d{4}-\d{2}-\d{2})\.log$/;
|
|
21
|
+
const DEFAULT_DEPENDENCIES = {
|
|
22
|
+
today: () => new Date().toISOString().slice(0, 10),
|
|
23
|
+
readText: (file) => readFileSync(file, 'utf8'),
|
|
24
|
+
removeFile: (file) => unlinkSync(file),
|
|
25
|
+
};
|
|
26
|
+
function validIsoDate(value) {
|
|
27
|
+
const parsed = new Date(`${value}T00:00:00.000Z`);
|
|
28
|
+
return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
function datedLogFilenameDate(filename) {
|
|
31
|
+
const match = SUMMARIZER_LOG_NAME_RE.exec(filename);
|
|
32
|
+
return match ? validIsoDate(match[1]) : undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Truncates and redacts key/token-shaped substrings from an error message before it's logged. */
|
|
35
|
+
export function sanitizeErrorMessage(message) {
|
|
36
|
+
let out = message.replace(BEARER_RE, 'Bearer [redacted]').replace(KEY_LIKE_RE, '[redacted]');
|
|
37
|
+
if (out.length > MAX_ERROR_LENGTH) {
|
|
38
|
+
out = `${out.slice(0, MAX_ERROR_LENGTH)}…[truncated]`;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
export class SummarizerCallLog {
|
|
43
|
+
dir;
|
|
44
|
+
dependencies;
|
|
45
|
+
constructor(dir = defaultCallLogDir(), dependencies = {}) {
|
|
46
|
+
this.dir = dir;
|
|
47
|
+
this.dependencies = { ...DEFAULT_DEPENDENCIES, ...dependencies };
|
|
48
|
+
}
|
|
49
|
+
fileFor(date) {
|
|
50
|
+
return path.join(this.dir, `summarizer-${date.toISOString().slice(0, 10)}.log`);
|
|
51
|
+
}
|
|
52
|
+
append(entry) {
|
|
53
|
+
mkdirSync(this.dir, { recursive: true });
|
|
54
|
+
hardenDir(this.dir);
|
|
55
|
+
const file = this.fileFor(new Date(entry.timestamp));
|
|
56
|
+
appendFileSync(file, `${JSON.stringify(entry)}\n`);
|
|
57
|
+
hardenFile(file);
|
|
58
|
+
try {
|
|
59
|
+
this.pruneExpiredFiles();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Diagnostic retention is best-effort and must not affect a summarizer call.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
pruneExpiredFiles() {
|
|
66
|
+
const cutoff = new Date(`${this.dependencies.today()}T00:00:00.000Z`);
|
|
67
|
+
cutoff.setUTCHours(0, 0, 0, 0);
|
|
68
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - SUMMARIZER_LOG_RETENTION_DAYS);
|
|
69
|
+
const cutoffDate = cutoff.toISOString().slice(0, 10);
|
|
70
|
+
for (const filename of readdirSync(this.dir)) {
|
|
71
|
+
const fileDate = datedLogFilenameDate(filename);
|
|
72
|
+
if (!fileDate || fileDate >= cutoffDate) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
this.dependencies.removeFile(path.join(this.dir, filename));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Keep pruning other expired logs if one file cannot be removed.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Reads logged call entries with timestamp >= sinceIso. Dated files older than the threshold day are rejected before opening. */
|
|
84
|
+
readEntriesSince(sinceIso) {
|
|
85
|
+
let files;
|
|
86
|
+
try {
|
|
87
|
+
files = readdirSync(this.dir).filter((f) => f.startsWith('summarizer-') && f.endsWith('.log'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const today = this.dependencies.today();
|
|
93
|
+
const sinceDate = validIsoDate(sinceIso.slice(0, 10)) ?? today;
|
|
94
|
+
const entries = [];
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
// Unknown shapes are treated as current so a future naming change
|
|
97
|
+
// cannot make diagnostic entries disappear silently.
|
|
98
|
+
if ((datedLogFilenameDate(file) ?? today) < sinceDate) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const text = this.dependencies.readText(path.join(this.dir, file));
|
|
102
|
+
for (const line of text.split('\n')) {
|
|
103
|
+
if (!line.trim()) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const entry = JSON.parse(line);
|
|
108
|
+
if (entry.timestamp >= sinceIso) {
|
|
109
|
+
entries.push(entry);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// malformed line - skip rather than fail the whole read
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=call-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-log.js","sourceRoot":"","sources":["../../src/summarizer/call-log.ts"],"names":[],"mappings":"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"}
|