opencodekit 0.17.13 → 0.18.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/dist/index.js +4 -6
- package/dist/template/.opencode/dcp.jsonc +81 -81
- package/dist/template/.opencode/memory/memory.db +0 -0
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +199 -23
- package/dist/template/.opencode/opencode.json.tui-migration.bak +1380 -0
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/lib/capture.ts +177 -0
- package/dist/template/.opencode/plugin/lib/context.ts +194 -0
- package/dist/template/.opencode/plugin/lib/curator.ts +234 -0
- package/dist/template/.opencode/plugin/lib/db/maintenance.ts +312 -0
- package/dist/template/.opencode/plugin/lib/db/observations.ts +299 -0
- package/dist/template/.opencode/plugin/lib/db/pipeline.ts +520 -0
- package/dist/template/.opencode/plugin/lib/db/schema.ts +356 -0
- package/dist/template/.opencode/plugin/lib/db/types.ts +211 -0
- package/dist/template/.opencode/plugin/lib/distill.ts +376 -0
- package/dist/template/.opencode/plugin/lib/inject.ts +126 -0
- package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +188 -0
- package/dist/template/.opencode/plugin/lib/memory-db.ts +54 -936
- package/dist/template/.opencode/plugin/lib/memory-helpers.ts +202 -0
- package/dist/template/.opencode/plugin/lib/memory-hooks.ts +240 -0
- package/dist/template/.opencode/plugin/lib/memory-tools.ts +341 -0
- package/dist/template/.opencode/plugin/memory.ts +56 -60
- package/dist/template/.opencode/plugin/sessions.ts +372 -93
- package/dist/template/.opencode/tui.json +15 -0
- package/package.json +1 -1
- package/dist/template/.opencode/tool/action-queue.ts +0 -313
- package/dist/template/.opencode/tool/memory-admin.ts +0 -445
- package/dist/template/.opencode/tool/memory-get.ts +0 -143
- package/dist/template/.opencode/tool/memory-read.ts +0 -45
- package/dist/template/.opencode/tool/memory-search.ts +0 -264
- package/dist/template/.opencode/tool/memory-timeline.ts +0 -105
- package/dist/template/.opencode/tool/memory-update.ts +0 -63
- package/dist/template/.opencode/tool/observation.ts +0 -357
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Plugin — Helpers
|
|
3
|
+
*
|
|
4
|
+
* Constants, compaction utilities, and tool formatting helpers.
|
|
5
|
+
* Pure functions — no plugin/closure dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Database } from "bun:sqlite";
|
|
9
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import type { ObservationType } from "./memory-db.js";
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export const VALID_TYPES: ObservationType[] = [
|
|
18
|
+
"decision",
|
|
19
|
+
"bugfix",
|
|
20
|
+
"feature",
|
|
21
|
+
"pattern",
|
|
22
|
+
"discovery",
|
|
23
|
+
"learning",
|
|
24
|
+
"warning",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const TYPE_ICONS: Record<string, string> = {
|
|
28
|
+
decision: "\u2696\uFE0F",
|
|
29
|
+
bugfix: "\uD83D\uDC1B",
|
|
30
|
+
feature: "\u2728",
|
|
31
|
+
pattern: "\uD83D\uDD04",
|
|
32
|
+
discovery: "\uD83D\uDD2D",
|
|
33
|
+
learning: "\uD83D\uDCDA",
|
|
34
|
+
warning: "\u26A0\uFE0F",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const FILE_REF_PATTERNS = [
|
|
38
|
+
/(?:^|\s)(\S+\.(?:ts|tsx|js|jsx|json|md|yaml|yml|toml|sql|sh|py|rs|go)):(\d+)/g,
|
|
39
|
+
/`([^`]+\.(?:ts|tsx|js|jsx|json|md|yaml|yml|toml))`/g,
|
|
40
|
+
/(?:^|\s)(src\/\S+)/gm,
|
|
41
|
+
/(?:^|\s)(\.opencode\/\S+)/gm,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Compaction constants
|
|
45
|
+
export const MAX_SESSION_CONTEXT_CHARS = 3000;
|
|
46
|
+
export const MAX_PROJECT_FILES = 3;
|
|
47
|
+
export const MAX_PROJECT_FILE_CHARS = 900;
|
|
48
|
+
export const MAX_HANDOFF_CHARS = 2500;
|
|
49
|
+
export const MAX_BEADS = 8;
|
|
50
|
+
export const MAX_COMBINED_CONTEXT_CHARS = 10000;
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Compaction Helpers
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
export function truncate(text: string, maxChars: number): string {
|
|
57
|
+
if (text.length <= maxChars) return text;
|
|
58
|
+
return `${text.slice(0, maxChars)}\n...[truncated]`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function safeReadFile(filePath: string): Promise<string> {
|
|
62
|
+
try {
|
|
63
|
+
return await readFile(filePath, "utf-8");
|
|
64
|
+
} catch {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function renderSection(title: string, body: string): string {
|
|
70
|
+
if (!body.trim()) return "";
|
|
71
|
+
return `## ${title}\n${body.trim()}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function readProjectMemoryContext(
|
|
75
|
+
memoryDir: string,
|
|
76
|
+
): Promise<string> {
|
|
77
|
+
const projectDir = path.join(memoryDir, "project");
|
|
78
|
+
let names: string[] = [];
|
|
79
|
+
try {
|
|
80
|
+
names = (await readdir(projectDir))
|
|
81
|
+
.filter((n) => n.endsWith(".md"))
|
|
82
|
+
.sort()
|
|
83
|
+
.slice(0, MAX_PROJECT_FILES);
|
|
84
|
+
} catch {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const chunks: string[] = [];
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
const content = (await safeReadFile(path.join(projectDir, name))).trim();
|
|
91
|
+
if (!content) continue;
|
|
92
|
+
chunks.push(
|
|
93
|
+
`### ${name.replace(/\.md$/, "")}\n${truncate(content, MAX_PROJECT_FILE_CHARS)}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return chunks.join("\n\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function readLatestHandoff(handoffDir: string): Promise<string> {
|
|
100
|
+
let names: string[] = [];
|
|
101
|
+
try {
|
|
102
|
+
names = (await readdir(handoffDir)).filter((n) => n.endsWith(".md"));
|
|
103
|
+
} catch {
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
if (names.length === 0) return "";
|
|
107
|
+
|
|
108
|
+
const withMtime = await Promise.all(
|
|
109
|
+
names.map(async (name) => {
|
|
110
|
+
const fullPath = path.join(handoffDir, name);
|
|
111
|
+
try {
|
|
112
|
+
return { name, fullPath, mtimeMs: (await stat(fullPath)).mtimeMs };
|
|
113
|
+
} catch {
|
|
114
|
+
return { name, fullPath, mtimeMs: 0 };
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
119
|
+
const latest = withMtime[0];
|
|
120
|
+
const content = (await safeReadFile(latest.fullPath)).trim();
|
|
121
|
+
if (!content) return "";
|
|
122
|
+
return `Source: ${latest.name}\n${truncate(content, MAX_HANDOFF_CHARS)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function readInProgressBeads(directory: string): string {
|
|
126
|
+
const dbPath = path.join(directory, ".beads", "beads.db");
|
|
127
|
+
let db: Database | undefined;
|
|
128
|
+
try {
|
|
129
|
+
db = new Database(dbPath, { readonly: true });
|
|
130
|
+
const rows = db
|
|
131
|
+
.query<{ id: string; title: string }, [number]>(
|
|
132
|
+
"SELECT id, title FROM issues WHERE status = 'in_progress' ORDER BY updated_at DESC LIMIT ?",
|
|
133
|
+
)
|
|
134
|
+
.all(MAX_BEADS);
|
|
135
|
+
return rows.length > 0
|
|
136
|
+
? rows.map((r) => `- ${r.id}: ${r.title}`).join("\n")
|
|
137
|
+
: "";
|
|
138
|
+
} catch {
|
|
139
|
+
return "";
|
|
140
|
+
} finally {
|
|
141
|
+
db?.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Tool Helpers
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
export function autoDetectFiles(text: string): string[] {
|
|
150
|
+
const files = new Set<string>();
|
|
151
|
+
for (const pattern of FILE_REF_PATTERNS) {
|
|
152
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
153
|
+
let match: RegExpExecArray | null;
|
|
154
|
+
while ((match = regex.exec(text)) !== null) {
|
|
155
|
+
files.add(match[1]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return [...files];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function parseCSV(value: string | undefined): string[] | undefined {
|
|
162
|
+
if (!value) return undefined;
|
|
163
|
+
return value
|
|
164
|
+
.split(",")
|
|
165
|
+
.map((s) => s.trim())
|
|
166
|
+
.filter((s) => s.length > 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function formatObservation(obs: {
|
|
170
|
+
id: number;
|
|
171
|
+
type: string;
|
|
172
|
+
title: string;
|
|
173
|
+
subtitle?: string | null;
|
|
174
|
+
confidence?: string | null;
|
|
175
|
+
concepts?: string | null;
|
|
176
|
+
files_read?: string | null;
|
|
177
|
+
files_modified?: string | null;
|
|
178
|
+
facts?: string | null;
|
|
179
|
+
narrative?: string | null;
|
|
180
|
+
bead_id?: string | null;
|
|
181
|
+
supersedes?: number | null;
|
|
182
|
+
superseded_by?: number | null;
|
|
183
|
+
source?: string | null;
|
|
184
|
+
created_at?: string | null;
|
|
185
|
+
}): string {
|
|
186
|
+
const icon = TYPE_ICONS[obs.type] ?? "\uD83D\uDCCC";
|
|
187
|
+
const lines = [`${icon} **#${obs.id}** [${obs.type}] ${obs.title}`];
|
|
188
|
+
if (obs.subtitle) lines.push(` _${obs.subtitle}_`);
|
|
189
|
+
if (obs.confidence) lines.push(` Confidence: ${obs.confidence}`);
|
|
190
|
+
if (obs.source && obs.source !== "manual")
|
|
191
|
+
lines.push(` Source: ${obs.source}`);
|
|
192
|
+
if (obs.concepts) lines.push(` Concepts: ${obs.concepts}`);
|
|
193
|
+
if (obs.files_read) lines.push(` Files read: ${obs.files_read}`);
|
|
194
|
+
if (obs.files_modified) lines.push(` Files modified: ${obs.files_modified}`);
|
|
195
|
+
if (obs.facts) lines.push(` Facts: ${obs.facts}`);
|
|
196
|
+
if (obs.bead_id) lines.push(` Bead: ${obs.bead_id}`);
|
|
197
|
+
if (obs.supersedes) lines.push(` Supersedes: #${obs.supersedes}`);
|
|
198
|
+
if (obs.superseded_by) lines.push(` Superseded by: #${obs.superseded_by}`);
|
|
199
|
+
if (obs.narrative) lines.push(`\n${obs.narrative}`);
|
|
200
|
+
if (obs.created_at) lines.push(`\n _Created: ${obs.created_at}_`);
|
|
201
|
+
return lines.join("\n");
|
|
202
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Plugin — Hooks
|
|
3
|
+
*
|
|
4
|
+
* All event handlers, transforms, and compaction logic.
|
|
5
|
+
* Uses factory pattern: createHooks(deps) returns hook definitions.
|
|
6
|
+
*
|
|
7
|
+
* Hook architecture (from @opencode-ai/plugin Hooks interface):
|
|
8
|
+
* - `event` — generic handler for ALL events (session.idle, message.updated, etc.)
|
|
9
|
+
* - Named hooks — separate handlers with (input, output) signature:
|
|
10
|
+
* - "tool.execute.after", "chat.message", "experimental.chat.*", etc.
|
|
11
|
+
*
|
|
12
|
+
* Events NOT in the Hooks interface (handled via generic `event`):
|
|
13
|
+
* - session.idle, session.error, session.created, session.deleted
|
|
14
|
+
* - message.updated, message.removed, message.part.updated, message.part.removed
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { captureMessageMeta, captureMessagePart } from "./capture.js";
|
|
19
|
+
import { manageContext } from "./context.js";
|
|
20
|
+
import { curateFromDistillations } from "./curator.js";
|
|
21
|
+
import { distillSession } from "./distill.js";
|
|
22
|
+
import { buildInjection } from "./inject.js";
|
|
23
|
+
import {
|
|
24
|
+
checkFTS5Available,
|
|
25
|
+
checkpointWAL,
|
|
26
|
+
getDatabaseSizes,
|
|
27
|
+
optimizeFTS5,
|
|
28
|
+
searchObservationsFTS,
|
|
29
|
+
} from "./memory-db.js";
|
|
30
|
+
import {
|
|
31
|
+
MAX_COMBINED_CONTEXT_CHARS,
|
|
32
|
+
MAX_SESSION_CONTEXT_CHARS,
|
|
33
|
+
readInProgressBeads,
|
|
34
|
+
readLatestHandoff,
|
|
35
|
+
readProjectMemoryContext,
|
|
36
|
+
renderSection,
|
|
37
|
+
safeReadFile,
|
|
38
|
+
truncate,
|
|
39
|
+
} from "./memory-helpers.js";
|
|
40
|
+
|
|
41
|
+
interface HookDeps {
|
|
42
|
+
memoryDir: string;
|
|
43
|
+
handoffDir: string;
|
|
44
|
+
directory: string;
|
|
45
|
+
showToast: (
|
|
46
|
+
title: string,
|
|
47
|
+
message: string,
|
|
48
|
+
variant?: "info" | "warning",
|
|
49
|
+
) => Promise<void>;
|
|
50
|
+
log: (message: string, level?: "info" | "warn") => Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createHooks(deps: HookDeps) {
|
|
54
|
+
const { memoryDir, handoffDir, directory, showToast, log } = deps;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
// ================================================================
|
|
58
|
+
// Generic event handler — ALL events route through here
|
|
59
|
+
// Receives: { event: { type, properties? } }
|
|
60
|
+
// ================================================================
|
|
61
|
+
event: async (input: unknown) => {
|
|
62
|
+
const { event } = input as {
|
|
63
|
+
event: {
|
|
64
|
+
type?: string;
|
|
65
|
+
properties?: Record<string, unknown>;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
if (!event?.type) return;
|
|
69
|
+
|
|
70
|
+
// --- Message capture ---
|
|
71
|
+
if (event.type === "message.updated") {
|
|
72
|
+
try {
|
|
73
|
+
captureMessageMeta(
|
|
74
|
+
event.properties as Parameters<typeof captureMessageMeta>[0],
|
|
75
|
+
);
|
|
76
|
+
} catch {
|
|
77
|
+
/* Non-fatal */
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (event.type === "message.part.updated") {
|
|
82
|
+
try {
|
|
83
|
+
captureMessagePart(
|
|
84
|
+
event.properties as Parameters<typeof captureMessagePart>[0],
|
|
85
|
+
);
|
|
86
|
+
} catch {
|
|
87
|
+
/* Non-fatal */
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- Session idle: distill + curate + optimize ---
|
|
92
|
+
if (event.type === "session.idle") {
|
|
93
|
+
const sessionId =
|
|
94
|
+
(event.properties as { sessionID?: string })?.sessionID ??
|
|
95
|
+
(event as unknown as { sessionID?: string })?.sessionID;
|
|
96
|
+
try {
|
|
97
|
+
if (sessionId) distillSession(sessionId);
|
|
98
|
+
curateFromDistillations(sessionId, 5);
|
|
99
|
+
if (checkFTS5Available()) optimizeFTS5();
|
|
100
|
+
const sizes = getDatabaseSizes();
|
|
101
|
+
if (sizes.wal > 1024 * 1024) checkpointWAL();
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
104
|
+
await log(`Idle maintenance failed: ${msg}`, "warn");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --- Session error: warn user ---
|
|
109
|
+
if (event.type === "session.error") {
|
|
110
|
+
await showToast(
|
|
111
|
+
"Session Error",
|
|
112
|
+
"Save important learnings with observation tool",
|
|
113
|
+
"warning",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// ================================================================
|
|
119
|
+
// Named hook: tool.execute.after
|
|
120
|
+
// Receives: (input: { tool, sessionID, callID, args }, output: { title, output, metadata })
|
|
121
|
+
// ================================================================
|
|
122
|
+
"tool.execute.after": async (input: {
|
|
123
|
+
tool?: string;
|
|
124
|
+
sessionID?: string;
|
|
125
|
+
}) => {
|
|
126
|
+
try {
|
|
127
|
+
if (input.tool === "observation" && typeof showToast === "function") {
|
|
128
|
+
await showToast("Saved", "Observation added to memory");
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
/* Toast is cosmetic, never block tool execution */
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// ================================================================
|
|
136
|
+
// LTM injection into system prompt
|
|
137
|
+
// ================================================================
|
|
138
|
+
"experimental.chat.system.transform": async (
|
|
139
|
+
_input: unknown,
|
|
140
|
+
output: { system: string[] },
|
|
141
|
+
) => {
|
|
142
|
+
try {
|
|
143
|
+
const injection = buildInjection(output.system);
|
|
144
|
+
if (injection) output.system.push(injection);
|
|
145
|
+
} catch {
|
|
146
|
+
/* Non-fatal */
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// ================================================================
|
|
151
|
+
// Context window management
|
|
152
|
+
// ================================================================
|
|
153
|
+
"experimental.chat.messages.transform": async (
|
|
154
|
+
_input: unknown,
|
|
155
|
+
output: { messages: unknown[] },
|
|
156
|
+
) => {
|
|
157
|
+
try {
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
+
output.messages = manageContext(output.messages as any) as any;
|
|
160
|
+
} catch {
|
|
161
|
+
/* Non-fatal */
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// ================================================================
|
|
166
|
+
// Compaction — inject session continuity context
|
|
167
|
+
// Receives: (input: { sessionID }, output: { context, prompt? })
|
|
168
|
+
// ================================================================
|
|
169
|
+
"experimental.session.compacting": async (
|
|
170
|
+
input: { sessionID?: string },
|
|
171
|
+
output: { context: string[]; prompt?: string },
|
|
172
|
+
) => {
|
|
173
|
+
const sessionContext = truncate(
|
|
174
|
+
(await safeReadFile(path.join(memoryDir, "session-context.md"))).trim(),
|
|
175
|
+
MAX_SESSION_CONTEXT_CHARS,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const [projectContext, handoffContext] = await Promise.all([
|
|
179
|
+
readProjectMemoryContext(memoryDir),
|
|
180
|
+
readLatestHandoff(handoffDir),
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
const beadsContext = readInProgressBeads(directory);
|
|
184
|
+
|
|
185
|
+
// Add relevant observations for session
|
|
186
|
+
let knowledgeContext = "";
|
|
187
|
+
if (input.sessionID) {
|
|
188
|
+
try {
|
|
189
|
+
const recentObs = searchObservationsFTS("", { limit: 5 });
|
|
190
|
+
if (recentObs.length > 0) {
|
|
191
|
+
knowledgeContext = recentObs
|
|
192
|
+
.map((o) => `- [${o.type}] #${o.id}: ${o.title}`)
|
|
193
|
+
.join("\n");
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
/* Non-fatal */
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const combined = [
|
|
201
|
+
renderSection("Session Continuity", sessionContext),
|
|
202
|
+
renderSection("Active Beads", beadsContext),
|
|
203
|
+
renderSection("Previous Handoff", handoffContext),
|
|
204
|
+
renderSection("Recent Knowledge", knowledgeContext),
|
|
205
|
+
renderSection("Project Memory", projectContext),
|
|
206
|
+
]
|
|
207
|
+
.filter(Boolean)
|
|
208
|
+
.join("\n\n");
|
|
209
|
+
|
|
210
|
+
if (combined) {
|
|
211
|
+
output.context.push(
|
|
212
|
+
`## Session Context\n${truncate(combined, MAX_COMBINED_CONTEXT_CHARS)}\n`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
output.prompt = `${output.prompt ?? ""}
|
|
217
|
+
|
|
218
|
+
<compaction_task>
|
|
219
|
+
Summarize conversation state for reliable continuation after compaction.
|
|
220
|
+
</compaction_task>
|
|
221
|
+
|
|
222
|
+
<compaction_rules>
|
|
223
|
+
- Preserve exact IDs, file paths, and unresolved constraints.
|
|
224
|
+
- Distinguish completed work from current in-progress work.
|
|
225
|
+
- Keep summary concise and execution-focused.
|
|
226
|
+
- If critical context is missing, state uncertainty explicitly.
|
|
227
|
+
</compaction_rules>
|
|
228
|
+
|
|
229
|
+
<compaction_output>
|
|
230
|
+
Include:
|
|
231
|
+
- What was done
|
|
232
|
+
- What is being worked on now
|
|
233
|
+
- Files currently in play
|
|
234
|
+
- Next actions
|
|
235
|
+
- Persistent user constraints/preferences
|
|
236
|
+
</compaction_output>
|
|
237
|
+
`;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|