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,136 @@
|
|
|
1
|
+
// Haiku-backed SummarizationProvider. Sole implementation for this phase -
|
|
2
|
+
// kept behind the SummarizationProvider interface (see ../types) so a local
|
|
3
|
+
// provider (e.g. Ollama) can be added later without touching call sites.
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { DEFAULT_ANTHROPIC_MODEL, TURN_SUMMARIZATION_MAX_TOKENS, TURN_SUMMARIZATION_RETRY_MAX_TOKENS } from '../config/constants.js';
|
|
7
|
+
import { callAnthropicJson, extractJsonCandidate } from './anthropic-call.js';
|
|
8
|
+
import { SummarizerCallLog } from './call-log.js';
|
|
9
|
+
import { buildRepairUserContent, buildSummarizationUserContent, SUMMARIZATION_SYSTEM_PROMPT } from './prompt.js';
|
|
10
|
+
import { cacheableSystemPrompt } from './prompt-cache.js';
|
|
11
|
+
// Decisions are {what, why} now, but the schema also accepts a bare string.
|
|
12
|
+
// That is not laxity: format instructions are a suggestion, not a contract.
|
|
13
|
+
// This model demonstrably reverts to the older shape,
|
|
14
|
+
// and every already-stored row uses it. A bare string means "no reason given",
|
|
15
|
+
// which is exactly what `why: null` encodes.
|
|
16
|
+
const decisionSchema = z.union([
|
|
17
|
+
z.string().transform((what) => ({ what, why: null })),
|
|
18
|
+
z.object({
|
|
19
|
+
what: z.string().min(1),
|
|
20
|
+
// An empty or whitespace `why` is the model padding rather than
|
|
21
|
+
// answering; normalize it to null so "no reason recorded" has one
|
|
22
|
+
// representation instead of three.
|
|
23
|
+
why: z
|
|
24
|
+
.string()
|
|
25
|
+
.nullish()
|
|
26
|
+
.transform((w) => (w && w.trim() !== '' ? w : null)),
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
const outputSchema = z.object({
|
|
30
|
+
decisions: z.array(decisionSchema),
|
|
31
|
+
pending_items: z.array(z.string()),
|
|
32
|
+
});
|
|
33
|
+
const EMPTY_OUTPUT = { decisions: [], pending_items: [], status: 'empty_turn' };
|
|
34
|
+
// Per-turn extraction is small by design, but 512 was tight enough that dense
|
|
35
|
+
// turns truncated mid-JSON and lost the extraction entirely - and the retry
|
|
36
|
+
// re-requested at the same ceiling, so it truncated again. Both numbers matter:
|
|
37
|
+
// the first keeps the common case cheap, the second gives a truncated call room
|
|
38
|
+
// to actually finish on the retry.
|
|
39
|
+
function tryParse(candidate) {
|
|
40
|
+
try {
|
|
41
|
+
const result = outputSchema.safeParse(JSON.parse(candidate));
|
|
42
|
+
return result.success ? result.data : undefined;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Tries the raw text as-is, then a fenced block if present, then the outermost {...} span - cheap, local, no API call. Exported for direct unit testing - see test/summarizer/haiku-provider.test.ts. */
|
|
49
|
+
// Model output body extraction is deliberately over-tolerant, not because the
|
|
50
|
+
// prompt is ambiguous (it explicitly says "no markdown fences, no
|
|
51
|
+
// commentary") but because Haiku wraps output in a ```json fence anyway on
|
|
52
|
+
// every observed call - confirmed live in fixed-sampling runs, 100% reproduction.
|
|
53
|
+
// This is a contract mismatch between what we ask for and what the model
|
|
54
|
+
// reliably does, not a stochastic glitch, so a plain identical retry never
|
|
55
|
+
// recovers it - see the repair-pass fallback in summarize() below.
|
|
56
|
+
export function parseOutput(raw) {
|
|
57
|
+
for (const candidate of extractJsonCandidate(raw)) {
|
|
58
|
+
const parsed = tryParse(candidate);
|
|
59
|
+
if (parsed) {
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
export class HaikuSummarizationProvider {
|
|
66
|
+
client;
|
|
67
|
+
model;
|
|
68
|
+
callLog;
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
this.client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});
|
|
71
|
+
this.model = options.model ?? DEFAULT_ANTHROPIC_MODEL;
|
|
72
|
+
this.callLog = options.callLog ?? new SummarizerCallLog();
|
|
73
|
+
}
|
|
74
|
+
async summarize(input) {
|
|
75
|
+
if (input.userMessage.trim() === '' && input.assistantText.trim() === '') {
|
|
76
|
+
return EMPTY_OUTPUT;
|
|
77
|
+
}
|
|
78
|
+
const userContent = buildSummarizationUserContent(input.userMessage, input.assistantText);
|
|
79
|
+
// Attempt 1: the real request. Attempt 2 only fires if attempt 1
|
|
80
|
+
// failed, and its shape depends on WHY attempt 1 failed - see below.
|
|
81
|
+
// Resending identical input after a deterministic formatting
|
|
82
|
+
// failure never recovers it, so attempt 2 is never a plain repeat.
|
|
83
|
+
const first = await this.call(1, userContent, TURN_SUMMARIZATION_MAX_TOKENS);
|
|
84
|
+
if (first.status === 'ok') {
|
|
85
|
+
return first.output;
|
|
86
|
+
}
|
|
87
|
+
if (first.status === 'api_error') {
|
|
88
|
+
// Stochastic failure (network blip, 429, transient 5xx) - a plain
|
|
89
|
+
// retry of the same request is the correct response here.
|
|
90
|
+
const retry = await this.call(2, userContent, TURN_SUMMARIZATION_MAX_TOKENS);
|
|
91
|
+
return retry.output;
|
|
92
|
+
}
|
|
93
|
+
if (first.truncated) {
|
|
94
|
+
// The model ran out of output budget mid-JSON. The input was fine
|
|
95
|
+
// and the output was on its way to being valid, so the repair
|
|
96
|
+
// prompt is the wrong tool: re-request the SAME thing with room to
|
|
97
|
+
// finish. Retrying at the same budget just truncates again, which
|
|
98
|
+
// is exactly what was happening - two attempts, both capped, both
|
|
99
|
+
// discarded.
|
|
100
|
+
const bigger = await this.call(2, userContent, TURN_SUMMARIZATION_RETRY_MAX_TOKENS);
|
|
101
|
+
return bigger.output;
|
|
102
|
+
}
|
|
103
|
+
// parse_error with a complete response: the content didn't parse. Don't
|
|
104
|
+
// resend the same prompt - send the model its own broken output and
|
|
105
|
+
// ask it to fix it. Meaningfully different input, so it can actually
|
|
106
|
+
// recover instead of reproducing the same fence-wrapped failure.
|
|
107
|
+
const repair = await this.call(2, buildRepairUserContent(first.rawText ?? ''), TURN_SUMMARIZATION_RETRY_MAX_TOKENS);
|
|
108
|
+
return repair.output;
|
|
109
|
+
}
|
|
110
|
+
async call(attempt, userContent, maxTokens) {
|
|
111
|
+
const result = await callAnthropicJson({
|
|
112
|
+
client: this.client,
|
|
113
|
+
model: this.model,
|
|
114
|
+
callLog: this.callLog,
|
|
115
|
+
system: cacheableSystemPrompt(SUMMARIZATION_SYSTEM_PROMPT),
|
|
116
|
+
user: userContent,
|
|
117
|
+
maxTokens,
|
|
118
|
+
parse: parseOutput,
|
|
119
|
+
job: 'turn_extraction',
|
|
120
|
+
attempt,
|
|
121
|
+
});
|
|
122
|
+
if (result.output) {
|
|
123
|
+
return { status: result.status, output: { ...result.output, status: result.status } };
|
|
124
|
+
}
|
|
125
|
+
if (result.status === 'api_error') {
|
|
126
|
+
return { status: result.status, output: { ...EMPTY_OUTPUT, status: result.status } };
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
status: result.status,
|
|
130
|
+
output: { ...EMPTY_OUTPUT, status: result.status },
|
|
131
|
+
rawText: result.rawText,
|
|
132
|
+
truncated: result.truncated,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=haiku-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"haiku-provider.js","sourceRoot":"","sources":["../../src/summarizer/haiku-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Haiku 4.5 pricing, USD per token. Update if the model/pricing changes -
|
|
2
|
+
// used only for dogfooding cost reporting (elepha reingest, elepha status),
|
|
3
|
+
// never in the ingestion hot path.
|
|
4
|
+
import { ANTHROPIC_INPUT_USD_PER_TOKEN, ANTHROPIC_OUTPUT_USD_PER_TOKEN } from '../config/constants.js';
|
|
5
|
+
export function estimateCostUsd(inputTokens, outputTokens) {
|
|
6
|
+
return inputTokens * ANTHROPIC_INPUT_USD_PER_TOKEN + outputTokens * ANTHROPIC_OUTPUT_USD_PER_TOKEN;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/summarizer/pricing.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,mCAAmC;AACnC,OAAO,EAAE,6BAA6B,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AAEvG,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,YAAoB;IACrE,OAAO,WAAW,GAAG,6BAA6B,GAAG,YAAY,GAAG,8BAA8B,CAAC;AACvG,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Anthropic prompt caching is prefix-based. Keep the cache breakpoint on the
|
|
2
|
+
// static system block so per-turn/session content remains a variable suffix.
|
|
3
|
+
export function cacheableSystemPrompt(text) {
|
|
4
|
+
return [{ type: 'text', text, cache_control: { type: 'ephemeral' } }];
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=prompt-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-cache.js","sourceRoot":"","sources":["../../src/summarizer/prompt-cache.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAE7E,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAC9C,OAAO,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,WAAoB,EAAE,EAAE,CAAC,CAAC;AAC5F,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Prompt for the per-turn summarization call. Model only judges decisions and
|
|
2
|
+
// pending_items - files_touched is computed deterministically from toolCalls
|
|
3
|
+
// upstream (see memory-store.recordTurn) and never asked of the model.
|
|
4
|
+
import { MAX_SUMMARIZATION_FIELD_CHARS } from '../config/constants.js';
|
|
5
|
+
export const SUMMARIZATION_SYSTEM_PROMPT = `You extract structured memory from one turn of a coding-AI session (a user
|
|
6
|
+
request and the assistant's reply). Output strict JSON only, matching:
|
|
7
|
+
{"decisions": [{"what": string, "why": string|null}], "pending_items": string[]}
|
|
8
|
+
|
|
9
|
+
decisions — concrete choices, conclusions, or approvals made in this turn
|
|
10
|
+
(e.g. "chose SQLite over Postgres for local storage", "user approved schema
|
|
11
|
+
X"). Omit vague or purely exploratory statements. Empty array if none.
|
|
12
|
+
|
|
13
|
+
"what" is the choice. "why" is the reason it was made, IF the turn actually
|
|
14
|
+
gives one — quote the substance of the reason, do not restate "what" in other
|
|
15
|
+
words. If the turn states a choice without giving a reason, set "why" to null.
|
|
16
|
+
Never invent a rationale: null is a correct and useful answer, and a plausible
|
|
17
|
+
guess is worse than nothing because a later reader cannot tell them apart.
|
|
18
|
+
|
|
19
|
+
pending_items — unresolved questions, explicit TODOs, or next steps stated
|
|
20
|
+
or clearly implied by this turn. Empty array if the turn was fully resolved.
|
|
21
|
+
|
|
22
|
+
Rules: JSON only, no markdown fences, no commentary. Never invent facts not
|
|
23
|
+
present in the input. If the turn has no substantive content (slash command,
|
|
24
|
+
one-word ack), return {"decisions": [], "pending_items": []}.`;
|
|
25
|
+
function truncate(text) {
|
|
26
|
+
if (text.length <= MAX_SUMMARIZATION_FIELD_CHARS) {
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
const head = Math.floor(MAX_SUMMARIZATION_FIELD_CHARS / 3);
|
|
30
|
+
const tail = MAX_SUMMARIZATION_FIELD_CHARS - head;
|
|
31
|
+
const omitted = text.length - head - tail;
|
|
32
|
+
return `${text.slice(0, head)}\n…[${omitted} chars omitted]…\n${text.slice(-tail)}`;
|
|
33
|
+
}
|
|
34
|
+
export function buildSummarizationUserContent(userMessage, assistantText) {
|
|
35
|
+
return `<user_message>${truncate(userMessage)}</user_message>\n<assistant_reply>${truncate(assistantText)}</assistant_reply>`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Repair-pass prompt: sends the model its own malformed output back and asks
|
|
39
|
+
* for a fix. Deliberately NOT a resend of the original request - retrying
|
|
40
|
+
* identical input against a deterministic formatting failure just
|
|
41
|
+
* reproduces the same failure. This is meaningfully different input, so it
|
|
42
|
+
* can actually recover a call the first attempt garbled.
|
|
43
|
+
*/
|
|
44
|
+
export function buildRepairUserContent(malformedOutput) {
|
|
45
|
+
return `Your previous response did not parse as JSON:\n${truncate(malformedOutput)}\n\nReturn ONLY the corrected JSON matching {"decisions": [{"what": string, "why": string|null}], "pending_items": string[]}. No markdown code fences, no commentary, just the raw JSON object.`;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/summarizer/prompt.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,uEAAuE;AAEvE,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAEvE,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;;;;;;;;;;;;;8DAmBmB,CAAC;AAE/D,SAAS,QAAQ,CAAC,IAAY;IAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,6BAA6B,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,GAAG,CAAC,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,6BAA6B,GAAG,IAAI,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1C,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,OAAO,qBAAqB,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,WAAmB,EAAE,aAAqB;IACpF,OAAO,iBAAiB,QAAQ,CAAC,WAAW,CAAC,qCAAqC,QAAQ,CAAC,aAAa,CAAC,oBAAoB,CAAC;AAClI,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,eAAuB;IAC1D,OAAO,kDAAkD,QAAQ,CAAC,eAAe,CAAC,iMAAiM,CAAC;AACxR,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HaikuSummarizationProvider } from './haiku-provider.js';
|
|
2
|
+
import { HaikuRollupProvider } from './rollup-provider.js';
|
|
3
|
+
export const ANTHROPIC_PROVIDER_NAME = 'Anthropic';
|
|
4
|
+
/**
|
|
5
|
+
* An API key is the current explicit provider configuration boundary.
|
|
6
|
+
* Merely constructing an SDK client is not configuration: without a key the
|
|
7
|
+
* daemon must stay capture-only and make no attempted provider calls.
|
|
8
|
+
*/
|
|
9
|
+
export function synthesisProviderName(env = process.env) {
|
|
10
|
+
return env.ANTHROPIC_API_KEY?.trim() ? ANTHROPIC_PROVIDER_NAME : undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Creates both provider jobs together; provider selection can split per job when another implementation lands. */
|
|
13
|
+
export function createConfiguredSynthesisProviders(env = process.env) {
|
|
14
|
+
const apiKey = env.ANTHROPIC_API_KEY?.trim();
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const model = env.ANTHROPIC_MODEL?.trim() || undefined;
|
|
19
|
+
return {
|
|
20
|
+
name: ANTHROPIC_PROVIDER_NAME,
|
|
21
|
+
turnExtraction: new HaikuSummarizationProvider({ apiKey, model }),
|
|
22
|
+
rollupMerge: new HaikuRollupProvider({ apiKey, model }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=provider-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-config.js","sourceRoot":"","sources":["../../src/summarizer/provider-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,CAAC,MAAM,uBAAuB,GAAG,WAAW,CAAC;AAQnD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACtE,OAAO,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/E,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,kCAAkC,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACnF,MAAM,MAAM,GAAG,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IACvD,OAAO;QACH,IAAI,EAAE,uBAAuB;QAC7B,cAAc,EAAE,IAAI,0BAA0B,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjE,WAAW,EAAE,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;KAC1D,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Prompts for session-level rollups. Two shapes: a fresh rollup over a whole
|
|
2
|
+
// session, and an incremental merge that folds newly-arrived turns into an
|
|
3
|
+
// existing rollup without re-reading the turns already accounted for.
|
|
4
|
+
import { MAX_ROLLUP_BATCH_CHARS, MAX_ROLLUP_CARRY_CHARS } from '../config/constants.js';
|
|
5
|
+
/**
|
|
6
|
+
* Per-call budget for the turns half of a rollup or merge prompt. Turns are
|
|
7
|
+
* CHUNKED to fit this, never truncated to fit it - see chunkTurns().
|
|
8
|
+
*
|
|
9
|
+
* This file used to end with `truncate(renderTurns(turns), 12000)`, which cut
|
|
10
|
+
* from the END: on any large session the newest turns never reached the model
|
|
11
|
+
* at all. That was the mechanical half of the merge-loss bug (24 of 29
|
|
12
|
+
* merge-capable rollups missing decisions their own turn rows contain), and it
|
|
13
|
+
* demonstrates why a bound must drop the oldest and say so: the wrong
|
|
14
|
+
* behaviour is what `slice` gives you for free.
|
|
15
|
+
*/
|
|
16
|
+
/** Budget for the carried previous rollup in a merge prompt. */
|
|
17
|
+
/**
|
|
18
|
+
* Marker emitted whenever a genuine ceiling binds. Deliberately visible to the
|
|
19
|
+
* model and greppable in logs: a silent drop is indistinguishable from a turn
|
|
20
|
+
* that had nothing to say.
|
|
21
|
+
*/
|
|
22
|
+
function omittedMarker(n, what) {
|
|
23
|
+
return `[${n} earlier ${what} omitted - batch budget]`;
|
|
24
|
+
}
|
|
25
|
+
// `why` is the entire point of the decisions array: "decided against animating
|
|
26
|
+
// borderRadius" is useless without "because the scroll lock resets
|
|
27
|
+
// ScrollTrigger; pause/resume instead". A decision whose reason isn't in the
|
|
28
|
+
// transcript must be dropped rather than padded with a restatement of `what` -
|
|
29
|
+
// the caller treats a missing `why` as a summarizer failure, not as an empty
|
|
30
|
+
// string to be tolerated.
|
|
31
|
+
const DECISIONS_CONTRACT = `decisions — array of {"what": string, "why": string}. "what" is the choice made;
|
|
32
|
+
"why" is the reason it was made, drawn from the transcript. Never restate "what"
|
|
33
|
+
as "why", and never invent a rationale: if the transcript does not give a reason
|
|
34
|
+
for a choice, omit that decision entirely rather than emitting a hollow one.`;
|
|
35
|
+
export const ROLLUP_SYSTEM_PROMPT = `You summarize one complete session of a coding-AI transcript into a durable
|
|
36
|
+
record another AI will later read to regain context. Output strict JSON only:
|
|
37
|
+
{"title": string, "summary": string, "decisions": [{"what": string, "why": string}], "pending_items": string[]}
|
|
38
|
+
|
|
39
|
+
title — one short line, scannable in a list of sessions. Name the actual work,
|
|
40
|
+
not the tool ("Fix ScrollTrigger reset on modal open", not "Debugging session").
|
|
41
|
+
|
|
42
|
+
summary — 2-3 sentences: what was worked on, what state it ended in.
|
|
43
|
+
|
|
44
|
+
${DECISIONS_CONTRACT}
|
|
45
|
+
Include "turn_index" on every decision, copied from the <turn index="…"> it came
|
|
46
|
+
from. Omit it only if you genuinely cannot tell.
|
|
47
|
+
|
|
48
|
+
pending_items — what was still unresolved when the session ended. Empty if
|
|
49
|
+
everything was concluded.
|
|
50
|
+
|
|
51
|
+
Rules: JSON only, no markdown fences, no commentary. Never invent facts absent
|
|
52
|
+
from the input.`;
|
|
53
|
+
/**
|
|
54
|
+
* Ordering contract. Measured cause of the merge-loss bug, in the ~17% of it
|
|
55
|
+
* the model was actually responsible for (the other ~83% was end-truncation,
|
|
56
|
+
* fixed in code above): asked to "carry forward and add", the model reliably
|
|
57
|
+
* re-emitted the previous decisions and stopped, treating the new turns as
|
|
58
|
+
* already covered.
|
|
59
|
+
*
|
|
60
|
+
* This instruction is a HINT, not the mechanism. `attributeDecisions` in
|
|
61
|
+
* rollup-provider.ts assigns provenance in code and the store sorts by it, so
|
|
62
|
+
* a model that ignores every word below still cannot bury the new decisions -
|
|
63
|
+
* which is the point, because it already ignored a politer version.
|
|
64
|
+
*/
|
|
65
|
+
const ORDERING_CONTRACT = `Ordering: the returned "decisions" array must END with the decisions established
|
|
66
|
+
by the NEW turns, in the order they occurred. Never drop a decision from the new
|
|
67
|
+
turns to make room for an older one — if something must be dropped, drop the
|
|
68
|
+
oldest. Include "turn_index" on every decision, copied from the <turn index="…">
|
|
69
|
+
it came from; use the previous rollup's own turn_index for decisions carried
|
|
70
|
+
forward, and omit it only if you genuinely cannot tell.`;
|
|
71
|
+
export const ROLLUP_MERGE_SYSTEM_PROMPT = `You are updating an existing session summary because the session was reopened
|
|
72
|
+
and new turns were appended. You receive the previous rollup and only the NEW
|
|
73
|
+
turns. Produce the updated rollup for the session as a whole. Output strict JSON
|
|
74
|
+
only:
|
|
75
|
+
{"title": string, "summary": string, "decisions": [{"what": string, "why": string, "turn_index": number}], "pending_items": string[]}
|
|
76
|
+
|
|
77
|
+
Carry forward everything from the previous rollup that is still accurate. Add
|
|
78
|
+
what the new turns establish. Crucially: DROP any pending_item the new turns
|
|
79
|
+
resolved, and do not duplicate a decision that is already present.
|
|
80
|
+
|
|
81
|
+
${ORDERING_CONTRACT}
|
|
82
|
+
|
|
83
|
+
${DECISIONS_CONTRACT}
|
|
84
|
+
|
|
85
|
+
Rules: JSON only, no markdown fences, no commentary. Never invent facts absent
|
|
86
|
+
from the input.`;
|
|
87
|
+
export function renderTurn(t) {
|
|
88
|
+
return [
|
|
89
|
+
`<turn index="${t.turnIndex}" at="${t.startedAt}">`,
|
|
90
|
+
t.decisions.length ? `decisions: ${t.decisions.join(' | ')}` : '',
|
|
91
|
+
t.pendingItems.length ? `pending: ${t.pendingItems.join(' | ')}` : '',
|
|
92
|
+
t.filesTouched.length ? `files: ${t.filesTouched.join(', ')}` : '',
|
|
93
|
+
'</turn>',
|
|
94
|
+
]
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.join('\n');
|
|
97
|
+
}
|
|
98
|
+
function renderTurns(turns) {
|
|
99
|
+
return turns.map(renderTurn).join('\n');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reduces ONE turn that is on its own too large for a batch. This is the only
|
|
103
|
+
* irreducible case - a batch of one cannot be split further - so it is the one
|
|
104
|
+
* place a genuine ceiling still binds.
|
|
105
|
+
*
|
|
106
|
+
* It binds from the OLDEST end (decisions and pending items are dropped from
|
|
107
|
+
* the front) and leaves a visible marker. `filesTouched` is dropped wholesale
|
|
108
|
+
* first: it is
|
|
109
|
+
* the cheapest content per character and is recomputed deterministically from
|
|
110
|
+
* the turn rows anyway, so the model losing sight of it costs nothing.
|
|
111
|
+
*/
|
|
112
|
+
export function shrinkOversizedTurn(turn, maxChars) {
|
|
113
|
+
let candidate = { ...turn, filesTouched: [] };
|
|
114
|
+
if (renderTurn(candidate).length <= maxChars) {
|
|
115
|
+
return { turn: candidate, omitted: 0 };
|
|
116
|
+
}
|
|
117
|
+
let omitted = 0;
|
|
118
|
+
// Drop from the front - oldest first - one item at a time, alternating so a
|
|
119
|
+
// turn heavy on one list does not lose the other entirely.
|
|
120
|
+
while (renderTurn(candidate).length > maxChars && candidate.decisions.length + candidate.pendingItems.length > 1) {
|
|
121
|
+
if (candidate.pendingItems.length >= candidate.decisions.length && candidate.pendingItems.length > 0) {
|
|
122
|
+
candidate = { ...candidate, pendingItems: candidate.pendingItems.slice(1) };
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
candidate = { ...candidate, decisions: candidate.decisions.slice(1) };
|
|
126
|
+
}
|
|
127
|
+
omitted++;
|
|
128
|
+
}
|
|
129
|
+
if (omitted > 0) {
|
|
130
|
+
candidate = { ...candidate, decisions: [omittedMarker(omitted, 'items in this turn'), ...candidate.decisions] };
|
|
131
|
+
}
|
|
132
|
+
return { turn: candidate, omitted };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Splits turns into batches that each render within `maxChars`, IN ORDER, with
|
|
136
|
+
* no turn ever discarded. This replaces end-truncation: a 204-turn session
|
|
137
|
+
* becomes 20 sequential merge calls instead of one call that saw the first 15
|
|
138
|
+
* turns and nothing else.
|
|
139
|
+
*
|
|
140
|
+
* A session at or below the budget - the overwhelming majority, given Codex
|
|
141
|
+
* median 6 turns and Claude Code median 1 - yields exactly one batch and one
|
|
142
|
+
* model call, so the normal path is unchanged.
|
|
143
|
+
*/
|
|
144
|
+
export function chunkTurns(turns, maxChars = MAX_ROLLUP_BATCH_CHARS) {
|
|
145
|
+
const batches = [];
|
|
146
|
+
let current = [];
|
|
147
|
+
let currentChars = 0;
|
|
148
|
+
let currentOmitted = 0;
|
|
149
|
+
const flush = () => {
|
|
150
|
+
if (current.length > 0) {
|
|
151
|
+
batches.push({ turns: current, omitted: currentOmitted });
|
|
152
|
+
current = [];
|
|
153
|
+
currentChars = 0;
|
|
154
|
+
currentOmitted = 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
for (const turn of turns) {
|
|
158
|
+
let entry = turn;
|
|
159
|
+
let omitted = 0;
|
|
160
|
+
let size = renderTurn(entry).length + 1;
|
|
161
|
+
if (size > maxChars) {
|
|
162
|
+
const shrunk = shrinkOversizedTurn(turn, maxChars);
|
|
163
|
+
entry = shrunk.turn;
|
|
164
|
+
omitted = shrunk.omitted;
|
|
165
|
+
size = renderTurn(entry).length + 1;
|
|
166
|
+
}
|
|
167
|
+
if (currentChars + size > maxChars && current.length > 0) {
|
|
168
|
+
flush();
|
|
169
|
+
}
|
|
170
|
+
current.push(entry);
|
|
171
|
+
currentChars += size;
|
|
172
|
+
currentOmitted += omitted;
|
|
173
|
+
}
|
|
174
|
+
flush();
|
|
175
|
+
return batches.length > 0 ? batches : [{ turns: [], omitted: 0 }];
|
|
176
|
+
}
|
|
177
|
+
export function buildRollupUserContent(turns) {
|
|
178
|
+
// No truncation: callers pass a batch that chunkTurns() already bounded.
|
|
179
|
+
return renderTurns(turns);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Renders the carried rollup within MAX_CARRY_CHARS.
|
|
183
|
+
*
|
|
184
|
+
* This is a genuine ceiling (the carried rollup grows without bound as a
|
|
185
|
+
* session runs), so it gets the same treatment as the brief one layer up:
|
|
186
|
+
* keep the NEWEST decisions, shorten the summary before dropping any decision,
|
|
187
|
+
* and mark what went. The previous version char-truncated the whole block from
|
|
188
|
+
* the end, which cut the newest decisions and the pending list off entirely -
|
|
189
|
+
* feeding the merge model an input that had already lost the thing the merge
|
|
190
|
+
* was supposed to preserve.
|
|
191
|
+
*/
|
|
192
|
+
export function renderPreviousRollup(previous, maxChars = MAX_ROLLUP_CARRY_CHARS) {
|
|
193
|
+
const render = (summary, decisions, dropped) => [
|
|
194
|
+
'<previous_rollup>',
|
|
195
|
+
`title: ${previous.title}`,
|
|
196
|
+
`summary: ${summary}`,
|
|
197
|
+
`decisions: ${[
|
|
198
|
+
...(dropped > 0 ? [omittedMarker(dropped, 'decisions')] : []),
|
|
199
|
+
...decisions.map((d) => `${d.what} (because ${d.why})`),
|
|
200
|
+
].join(' | ') || '(none)'}`,
|
|
201
|
+
`pending_items: ${previous.pendingItems.join(' | ') || '(none)'}`,
|
|
202
|
+
'</previous_rollup>',
|
|
203
|
+
].join('\n');
|
|
204
|
+
let summary = previous.summary;
|
|
205
|
+
let decisions = previous.decisions;
|
|
206
|
+
let dropped = 0;
|
|
207
|
+
if (render(summary, decisions, dropped).length <= maxChars) {
|
|
208
|
+
return render(summary, decisions, dropped);
|
|
209
|
+
}
|
|
210
|
+
// 1. Shorten the summary first - it is prose, and losing half a sentence
|
|
211
|
+
// costs less than losing a decision with its rationale.
|
|
212
|
+
if (summary.length > 240) {
|
|
213
|
+
summary = `${summary.slice(0, 240)}…`;
|
|
214
|
+
}
|
|
215
|
+
// 2. Then drop decisions from the OLDEST end, never the newest.
|
|
216
|
+
while (render(summary, decisions, dropped).length > maxChars && decisions.length > 1) {
|
|
217
|
+
decisions = decisions.slice(1);
|
|
218
|
+
dropped++;
|
|
219
|
+
}
|
|
220
|
+
return render(summary, decisions, dropped);
|
|
221
|
+
}
|
|
222
|
+
export function buildRollupMergeUserContent(previous, newTurns) {
|
|
223
|
+
return `${renderPreviousRollup(previous)}\n<new_turns>\n${renderTurns(newTurns)}\n</new_turns>`;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=rollup-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollup-prompt.js","sourceRoot":"","sources":["../../src/summarizer/rollup-prompt.ts"],"names":[],"mappings":"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"}
|