@agentuity/opencode 1.0.14 → 1.0.16
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/agents/expert-backend.js +1 -1
- package/dist/agents/expert-backend.js.map +1 -1
- package/dist/agents/expert-frontend.js +1 -1
- package/dist/agents/expert-frontend.js.map +1 -1
- package/dist/agents/expert-ops.js +1 -1
- package/dist/agents/expert-ops.js.map +1 -1
- package/dist/agents/expert.js +1 -1
- package/dist/agents/expert.js.map +1 -1
- package/dist/agents/lead.d.ts +1 -1
- package/dist/agents/lead.d.ts.map +1 -1
- package/dist/agents/lead.js +34 -7
- package/dist/agents/lead.js.map +1 -1
- package/dist/agents/monitor.d.ts +1 -1
- package/dist/agents/monitor.d.ts.map +1 -1
- package/dist/agents/monitor.js +22 -33
- package/dist/agents/monitor.js.map +1 -1
- package/dist/agents/reviewer.js +1 -1
- package/dist/agents/reviewer.js.map +1 -1
- package/dist/agents/scout.js +2 -2
- package/dist/agents/scout.js.map +1 -1
- package/dist/background/manager.d.ts +27 -0
- package/dist/background/manager.d.ts.map +1 -1
- package/dist/background/manager.js +161 -27
- package/dist/background/manager.js.map +1 -1
- package/dist/plugin/hooks/cadence.d.ts +3 -1
- package/dist/plugin/hooks/cadence.d.ts.map +1 -1
- package/dist/plugin/hooks/cadence.js +167 -66
- package/dist/plugin/hooks/cadence.js.map +1 -1
- package/dist/plugin/hooks/compaction-utils.d.ts +48 -0
- package/dist/plugin/hooks/compaction-utils.d.ts.map +1 -0
- package/dist/plugin/hooks/compaction-utils.js +259 -0
- package/dist/plugin/hooks/compaction-utils.js.map +1 -0
- package/dist/plugin/hooks/params.d.ts +1 -1
- package/dist/plugin/hooks/params.d.ts.map +1 -1
- package/dist/plugin/hooks/params.js +5 -1
- package/dist/plugin/hooks/params.js.map +1 -1
- package/dist/plugin/hooks/session-memory.d.ts +2 -1
- package/dist/plugin/hooks/session-memory.d.ts.map +1 -1
- package/dist/plugin/hooks/session-memory.js +97 -48
- package/dist/plugin/hooks/session-memory.js.map +1 -1
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +31 -9
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/sqlite/index.d.ts +1 -1
- package/dist/sqlite/index.d.ts.map +1 -1
- package/dist/sqlite/queries.d.ts +1 -0
- package/dist/sqlite/queries.d.ts.map +1 -1
- package/dist/sqlite/queries.js +4 -0
- package/dist/sqlite/queries.js.map +1 -1
- package/dist/sqlite/reader.d.ts +11 -1
- package/dist/sqlite/reader.d.ts.map +1 -1
- package/dist/sqlite/reader.js +62 -0
- package/dist/sqlite/reader.js.map +1 -1
- package/dist/sqlite/types.d.ts +40 -0
- package/dist/sqlite/types.d.ts.map +1 -1
- package/dist/tools/background.d.ts +2 -0
- package/dist/tools/background.d.ts.map +1 -1
- package/dist/tools/background.js +2 -0
- package/dist/tools/background.js.map +1 -1
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
- package/src/agents/expert-backend.ts +1 -1
- package/src/agents/expert-frontend.ts +1 -1
- package/src/agents/expert-ops.ts +1 -1
- package/src/agents/expert.ts +1 -1
- package/src/agents/lead.ts +34 -7
- package/src/agents/monitor.ts +22 -33
- package/src/agents/reviewer.ts +1 -1
- package/src/agents/scout.ts +2 -2
- package/src/background/manager.ts +167 -32
- package/src/plugin/hooks/cadence.ts +184 -66
- package/src/plugin/hooks/compaction-utils.ts +291 -0
- package/src/plugin/hooks/params.ts +10 -1
- package/src/plugin/hooks/session-memory.ts +109 -47
- package/src/plugin/plugin.ts +47 -10
- package/src/sqlite/index.ts +4 -0
- package/src/sqlite/queries.ts +5 -0
- package/src/sqlite/reader.ts +69 -0
- package/src/sqlite/types.ts +40 -0
- package/src/tools/background.ts +6 -0
- package/src/types.ts +30 -0
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
import type { CoderConfig } from '../../types';
|
|
3
3
|
import type { BackgroundManager } from '../../background';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return stdout.trim() || 'unknown';
|
|
17
|
-
} catch {
|
|
18
|
-
return 'unknown';
|
|
19
|
-
}
|
|
20
|
-
}
|
|
4
|
+
import type { OpenCodeDBReader } from '../../sqlite';
|
|
5
|
+
import type { CompactionStats } from '../../sqlite/types';
|
|
6
|
+
import {
|
|
7
|
+
getCurrentBranch,
|
|
8
|
+
buildCustomCompactionPrompt,
|
|
9
|
+
fetchAndFormatPlanningState,
|
|
10
|
+
getImageDescriptions,
|
|
11
|
+
getRecentToolCallSummaries,
|
|
12
|
+
storePreCompactionSnapshot,
|
|
13
|
+
formatCompactionDiagnostics,
|
|
14
|
+
countListItems,
|
|
15
|
+
} from './compaction-utils';
|
|
21
16
|
|
|
22
17
|
export interface SessionMemoryHooks {
|
|
23
18
|
onEvent: (input: {
|
|
@@ -38,8 +33,9 @@ export interface SessionMemoryHooks {
|
|
|
38
33
|
*/
|
|
39
34
|
export function createSessionMemoryHooks(
|
|
40
35
|
ctx: PluginInput,
|
|
41
|
-
|
|
42
|
-
backgroundManager?: BackgroundManager
|
|
36
|
+
config: CoderConfig,
|
|
37
|
+
backgroundManager?: BackgroundManager,
|
|
38
|
+
dbReader?: OpenCodeDBReader
|
|
43
39
|
): SessionMemoryHooks {
|
|
44
40
|
const log = (msg: string) => {
|
|
45
41
|
ctx.client.app.log({
|
|
@@ -144,7 +140,8 @@ Then continue with the current task if there is one.`,
|
|
|
144
140
|
|
|
145
141
|
/**
|
|
146
142
|
* Inject Memory system info during compaction.
|
|
147
|
-
*
|
|
143
|
+
* Uses output.prompt to REPLACE the default compaction prompt with
|
|
144
|
+
* enriched context (planning state, images, tool calls, diagnostics).
|
|
148
145
|
*/
|
|
149
146
|
async onCompacting(
|
|
150
147
|
input: { sessionID: string },
|
|
@@ -153,12 +150,43 @@ Then continue with the current task if there is one.`,
|
|
|
153
150
|
const sessionId = input.sessionID;
|
|
154
151
|
log(`Compacting session ${sessionId}`);
|
|
155
152
|
|
|
156
|
-
//
|
|
157
|
-
const
|
|
153
|
+
// Config flags for compaction behavior
|
|
154
|
+
const compactionCfg = config?.compaction ?? {};
|
|
155
|
+
const useCustomPrompt = compactionCfg.customPrompt !== false;
|
|
156
|
+
const useInlinePlanning = compactionCfg.inlinePlanning !== false;
|
|
157
|
+
const useImageAwareness = compactionCfg.imageAwareness !== false;
|
|
158
|
+
const useSnapshotToKV = compactionCfg.snapshotToKV !== false;
|
|
159
|
+
const maxTokens = compactionCfg.maxContextTokens ?? 4000;
|
|
160
|
+
|
|
161
|
+
// 1. Build custom compaction instructions
|
|
162
|
+
const instructions = useCustomPrompt ? buildCustomCompactionPrompt('regular') : null;
|
|
163
|
+
|
|
164
|
+
// 2. Gather enrichment data in parallel
|
|
165
|
+
const toolCallLimit = config?.compaction?.toolCallSummaryLimit ?? 5;
|
|
166
|
+
const [branch, planningState, imageDescs, toolSummaries] = await Promise.all([
|
|
167
|
+
getCurrentBranch(),
|
|
168
|
+
useInlinePlanning ? fetchAndFormatPlanningState(sessionId) : Promise.resolve(null),
|
|
169
|
+
useImageAwareness
|
|
170
|
+
? Promise.resolve(getImageDescriptions(dbReader ?? null, sessionId))
|
|
171
|
+
: Promise.resolve(null),
|
|
172
|
+
Promise.resolve(getRecentToolCallSummaries(dbReader ?? null, sessionId, toolCallLimit)),
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
// 3. Build session state section
|
|
176
|
+
const sessionStateSection = `## Session Memory
|
|
177
|
+
|
|
178
|
+
This session's context is being saved to persistent memory.
|
|
179
|
+
Session record location: \`session:${sessionId}\` in agentuity-opencode-memory
|
|
180
|
+
Current branch: ${branch}
|
|
158
181
|
|
|
159
|
-
|
|
182
|
+
After compaction:
|
|
183
|
+
1. Memory will save this summary to the session record
|
|
184
|
+
2. If planning is active, Memory should update planning.progress with this compaction
|
|
185
|
+
3. Memory will apply inline reasoning if significant patterns/corrections emerged`;
|
|
186
|
+
|
|
187
|
+
// 4. Build background tasks section
|
|
160
188
|
const tasks = backgroundManager?.getTasksByParent(sessionId) ?? [];
|
|
161
|
-
let
|
|
189
|
+
let backgroundSection: string | null = null;
|
|
162
190
|
|
|
163
191
|
if (tasks.length > 0) {
|
|
164
192
|
const taskList = tasks
|
|
@@ -168,38 +196,72 @@ Then continue with the current task if there is one.`,
|
|
|
168
196
|
)
|
|
169
197
|
.join('\n');
|
|
170
198
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
## Active Background Tasks
|
|
199
|
+
backgroundSection = `## Active Background Tasks
|
|
174
200
|
|
|
175
201
|
This session has ${tasks.length} background task(s) running in separate sessions:
|
|
176
202
|
${taskList}
|
|
177
203
|
|
|
178
204
|
**CRITICAL:** Task IDs and session IDs persist across compaction - these tasks are still running.
|
|
179
|
-
Use \`agentuity_background_output({ task_id: "..." })\` to check their status
|
|
180
|
-
`;
|
|
205
|
+
Use \`agentuity_background_output({ task_id: "..." })\` to check their status.`;
|
|
181
206
|
}
|
|
182
207
|
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
// 5. Combine everything into the full prompt
|
|
209
|
+
const sections: string[] = [];
|
|
210
|
+
if (instructions) sections.push(instructions);
|
|
211
|
+
sections.push(sessionStateSection);
|
|
212
|
+
if (backgroundSection) sections.push(backgroundSection);
|
|
213
|
+
if (planningState) sections.push(planningState);
|
|
214
|
+
if (imageDescs) sections.push(imageDescs);
|
|
215
|
+
if (toolSummaries) sections.push(toolSummaries);
|
|
216
|
+
|
|
217
|
+
// 6. Add diagnostics
|
|
218
|
+
const stats: CompactionStats = {
|
|
219
|
+
planningPhasesCount: countListItems(planningState),
|
|
220
|
+
backgroundTasksCount: tasks.length,
|
|
221
|
+
imageDescriptionsCount: countListItems(imageDescs),
|
|
222
|
+
toolCallSummariesCount: countListItems(toolSummaries),
|
|
223
|
+
estimatedTokens: Math.ceil(sections.join('\n\n').length / 4),
|
|
224
|
+
};
|
|
225
|
+
const diagnostics = formatCompactionDiagnostics(stats);
|
|
226
|
+
if (diagnostics) sections.push(diagnostics);
|
|
227
|
+
|
|
228
|
+
// 7. Enforce token budget
|
|
229
|
+
let fullPrompt = sections.join('\n\n');
|
|
230
|
+
const estimatedTokens = Math.ceil(fullPrompt.length / 4);
|
|
231
|
+
if (maxTokens > 0 && estimatedTokens > maxTokens) {
|
|
232
|
+
// Trim least-critical sections first
|
|
233
|
+
const trimOrder = [diagnostics, toolSummaries, imageDescs, planningState].filter(
|
|
234
|
+
Boolean
|
|
235
|
+
);
|
|
236
|
+
let trimmed = [...sections];
|
|
237
|
+
for (const candidate of trimOrder) {
|
|
238
|
+
if (Math.ceil(trimmed.join('\n\n').length / 4) <= maxTokens) break;
|
|
239
|
+
trimmed = trimmed.filter((s) => s !== candidate);
|
|
240
|
+
}
|
|
241
|
+
fullPrompt = trimmed.join('\n\n');
|
|
242
|
+
}
|
|
185
243
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
244
|
+
// 8. Set the full prompt or push to context
|
|
245
|
+
if (useCustomPrompt) {
|
|
246
|
+
output.prompt = fullPrompt;
|
|
247
|
+
} else {
|
|
248
|
+
output.context.push(fullPrompt);
|
|
249
|
+
}
|
|
189
250
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
251
|
+
// 9. Store pre-compaction snapshot to KV (fire-and-forget)
|
|
252
|
+
if (useSnapshotToKV) {
|
|
253
|
+
storePreCompactionSnapshot(sessionId, {
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
sessionId,
|
|
256
|
+
planningState: planningState ? { raw: planningState } : undefined,
|
|
257
|
+
backgroundTasks: tasks.map((t) => ({
|
|
258
|
+
id: t.id,
|
|
259
|
+
description: t.description || 'No description',
|
|
260
|
+
status: t.status,
|
|
261
|
+
})),
|
|
262
|
+
branch,
|
|
263
|
+
}).catch(() => {}); // Fire and forget
|
|
264
|
+
}
|
|
203
265
|
},
|
|
204
266
|
};
|
|
205
267
|
}
|
package/src/plugin/plugin.ts
CHANGED
|
@@ -92,10 +92,14 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
|
|
|
92
92
|
const resolvedDbPath = resolveOpenCodeDBPath();
|
|
93
93
|
const dbReader = new OpenCodeDBReader(resolvedDbPath ? { dbPath: resolvedDbPath } : undefined);
|
|
94
94
|
|
|
95
|
+
// Shared Map: chat.params stores the user's message text per session,
|
|
96
|
+
// chat.message reads it for trigger detection (avoids scanning model output).
|
|
97
|
+
const lastUserMessages = new Map<string, string>();
|
|
98
|
+
|
|
95
99
|
const sessionHooks = createSessionHooks(ctx, coderConfig);
|
|
96
100
|
const toolHooks = createToolHooks(ctx, coderConfig);
|
|
97
101
|
const keywordHooks = createKeywordHooks(ctx, coderConfig);
|
|
98
|
-
const paramsHooks = createParamsHooks(ctx, coderConfig);
|
|
102
|
+
const paramsHooks = createParamsHooks(ctx, coderConfig, lastUserMessages);
|
|
99
103
|
const tmuxManager = coderConfig.tmux?.enabled
|
|
100
104
|
? new TmuxSessionManager(ctx, coderConfig.tmux, {
|
|
101
105
|
onLog: (message) =>
|
|
@@ -157,11 +161,22 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
|
|
|
157
161
|
});
|
|
158
162
|
|
|
159
163
|
// Create hooks that need backgroundManager for task reference injection during compaction
|
|
160
|
-
const cadenceHooks = createCadenceHooks(
|
|
164
|
+
const cadenceHooks = createCadenceHooks(
|
|
165
|
+
ctx,
|
|
166
|
+
coderConfig,
|
|
167
|
+
backgroundManager,
|
|
168
|
+
dbReader,
|
|
169
|
+
lastUserMessages
|
|
170
|
+
);
|
|
161
171
|
|
|
162
172
|
// Session memory hooks handle checkpointing and compaction for non-Cadence sessions
|
|
163
173
|
// Orchestration (deciding which module handles which session) happens below in the hooks
|
|
164
|
-
const sessionMemoryHooks = createSessionMemoryHooks(
|
|
174
|
+
const sessionMemoryHooks = createSessionMemoryHooks(
|
|
175
|
+
ctx,
|
|
176
|
+
coderConfig,
|
|
177
|
+
backgroundManager,
|
|
178
|
+
dbReader
|
|
179
|
+
);
|
|
165
180
|
|
|
166
181
|
const configHandler = createConfigHandler(coderConfig);
|
|
167
182
|
|
|
@@ -230,16 +245,28 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
|
|
|
230
245
|
}
|
|
231
246
|
// Orchestrate: route to appropriate module based on session type
|
|
232
247
|
const sessionId = extractSessionIdFromEvent(input);
|
|
233
|
-
if (sessionId
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
)
|
|
248
|
+
if (sessionId) {
|
|
249
|
+
// Try lazy restore from KV if not in memory (survives plugin restarts)
|
|
250
|
+
if (!cadenceHooks.isActiveCadenceSession(sessionId)) {
|
|
251
|
+
await cadenceHooks.tryRestoreFromKV(sessionId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (cadenceHooks.isActiveCadenceSession(sessionId)) {
|
|
255
|
+
await cadenceHooks.onEvent(input);
|
|
256
|
+
} else {
|
|
257
|
+
// Non-Cadence sessions - handle session.compacted for checkpointing
|
|
258
|
+
await sessionMemoryHooks.onEvent(
|
|
259
|
+
input as { event: { type: string; properties?: Record<string, unknown> } }
|
|
260
|
+
);
|
|
261
|
+
}
|
|
240
262
|
}
|
|
241
263
|
},
|
|
242
264
|
'experimental.session.compacting': async (input, output) => {
|
|
265
|
+
// Try lazy restore from KV if not in memory (survives plugin restarts)
|
|
266
|
+
if (!cadenceHooks.isActiveCadenceSession(input.sessionID)) {
|
|
267
|
+
await cadenceHooks.tryRestoreFromKV(input.sessionID);
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
// Orchestrate: route to appropriate module based on session type
|
|
244
271
|
if (cadenceHooks.isActiveCadenceSession(input.sessionID)) {
|
|
245
272
|
await cadenceHooks.onCompacting(input, output);
|
|
@@ -318,6 +345,16 @@ function createConfigHandler(
|
|
|
318
345
|
};
|
|
319
346
|
}
|
|
320
347
|
|
|
348
|
+
// Compaction config: increase reserved token buffer to accommodate our enriched
|
|
349
|
+
// compaction prompts (planning state, image descriptions, tool summaries, diagnostics).
|
|
350
|
+
// Default OpenCode reserved buffer is too small for the context we inject.
|
|
351
|
+
const existingCompaction = (config.compaction ?? {}) as Record<string, unknown>;
|
|
352
|
+
const existingReserved = existingCompaction.reserved;
|
|
353
|
+
config.compaction = {
|
|
354
|
+
...existingCompaction,
|
|
355
|
+
reserved: typeof existingReserved === 'number' ? existingReserved : 40_000,
|
|
356
|
+
};
|
|
357
|
+
|
|
321
358
|
config.command = {
|
|
322
359
|
...(config.command as Record<string, CommandDefinition> | undefined),
|
|
323
360
|
...commands,
|
package/src/sqlite/index.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export { OpenCodeDBReader } from './reader';
|
|
2
2
|
export type {
|
|
3
|
+
CompactionStats,
|
|
3
4
|
DBMessage,
|
|
5
|
+
DBNonTextPart,
|
|
4
6
|
DBPart,
|
|
5
7
|
DBSession,
|
|
6
8
|
DBTextPart,
|
|
7
9
|
DBTodo,
|
|
8
10
|
DBToolCall,
|
|
11
|
+
DBToolCallSummary,
|
|
9
12
|
MessageTokens,
|
|
10
13
|
OpenCodeDBConfig,
|
|
14
|
+
PreCompactionSnapshot,
|
|
11
15
|
SessionCostSummary,
|
|
12
16
|
SessionStatus,
|
|
13
17
|
SessionSummary,
|
package/src/sqlite/queries.ts
CHANGED
|
@@ -47,4 +47,9 @@ export const QUERIES = {
|
|
|
47
47
|
SEARCH_SESSIONS: `SELECT id, project_id, parent_id, slug, directory, title, version, share_url, summary_additions, summary_deletions, summary_files, summary_diffs, time_created, time_updated, time_compacting, time_archived FROM session WHERE title LIKE ? COLLATE NOCASE ORDER BY time_updated DESC`,
|
|
48
48
|
|
|
49
49
|
SEARCH_SESSIONS_LIMITED: `SELECT id, project_id, parent_id, slug, directory, title, version, share_url, summary_additions, summary_deletions, summary_files, summary_diffs, time_created, time_updated, time_compacting, time_archived FROM session WHERE title LIKE ? COLLATE NOCASE ORDER BY time_updated DESC LIMIT ?`,
|
|
50
|
+
|
|
51
|
+
GET_NON_TEXT_PARTS: `SELECT * FROM part WHERE session_id = ?
|
|
52
|
+
AND json_valid(data)
|
|
53
|
+
AND json_extract(data, '$.type') != 'text'
|
|
54
|
+
ORDER BY time_created DESC LIMIT ?`,
|
|
50
55
|
} as const;
|
package/src/sqlite/reader.ts
CHANGED
|
@@ -5,10 +5,12 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { QUERIES } from './queries';
|
|
6
6
|
import type {
|
|
7
7
|
DBMessage,
|
|
8
|
+
DBNonTextPart,
|
|
8
9
|
DBSession,
|
|
9
10
|
DBTextPart,
|
|
10
11
|
DBTodo,
|
|
11
12
|
DBToolCall,
|
|
13
|
+
DBToolCallSummary,
|
|
12
14
|
MessageTokens,
|
|
13
15
|
OpenCodeDBConfig,
|
|
14
16
|
SessionCostSummary,
|
|
@@ -226,6 +228,39 @@ function mapTextPart(row: PartRow): DBTextPart | null {
|
|
|
226
228
|
};
|
|
227
229
|
}
|
|
228
230
|
|
|
231
|
+
function mapNonTextPart(row: PartRow): DBNonTextPart | null {
|
|
232
|
+
const payload = safeParseJSON<PartData>(row.data);
|
|
233
|
+
if (!payload || !payload.type || payload.type === 'text') return null;
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
id: row.id,
|
|
237
|
+
messageId: row.message_id,
|
|
238
|
+
type: payload.type,
|
|
239
|
+
toolName: payload.tool,
|
|
240
|
+
timestamp: new Date(row.time_created).toISOString(),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function mapToolCallSummary(row: PartRow): DBToolCallSummary | null {
|
|
245
|
+
const payload = safeParseJSON<PartData>(row.data);
|
|
246
|
+
if (!payload || (payload.type !== 'tool' && payload.type !== 'tool-invocation')) return null;
|
|
247
|
+
|
|
248
|
+
const state = payload.state ?? {};
|
|
249
|
+
const inputStr =
|
|
250
|
+
state.input !== undefined ? String(JSON.stringify(state.input)).slice(0, 200) : undefined;
|
|
251
|
+
const outputStr =
|
|
252
|
+
state.output !== undefined ? String(JSON.stringify(state.output)).slice(0, 200) : undefined;
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
id: row.id,
|
|
256
|
+
messageId: row.message_id,
|
|
257
|
+
toolName: payload.tool ?? 'unknown',
|
|
258
|
+
input: inputStr,
|
|
259
|
+
output: outputStr,
|
|
260
|
+
timestamp: new Date(row.time_created).toISOString(),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
229
264
|
function isNotNull<T>(value: T | null): value is T {
|
|
230
265
|
return value !== null;
|
|
231
266
|
}
|
|
@@ -475,6 +510,40 @@ export class OpenCodeDBReader {
|
|
|
475
510
|
}
|
|
476
511
|
}
|
|
477
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Get non-text parts (images, files, tool calls) for a session.
|
|
515
|
+
* Useful for describing attachments during compaction.
|
|
516
|
+
*/
|
|
517
|
+
getNonTextParts(sessionId: string): DBNonTextPart[] {
|
|
518
|
+
if (!this.ensureOpen()) return [];
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
const statement = this.getStatement('GET_NON_TEXT_PARTS');
|
|
522
|
+
const rows = statement?.all(sessionId, DEFAULT_LIMIT) as PartRow[] | null;
|
|
523
|
+
return rows ? rows.map(mapNonTextPart).filter(isNotNull) : [];
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.warn('[OpenCodeDBReader] Failed to get non-text parts', error);
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Get recent tool calls for a session (newest first).
|
|
532
|
+
* Returns concise summaries for compaction context.
|
|
533
|
+
*/
|
|
534
|
+
getRecentToolCalls(sessionId: string, limit: number = 5): DBToolCallSummary[] {
|
|
535
|
+
if (!this.ensureOpen()) return [];
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const statement = this.getStatement('GET_TOOL_HISTORY');
|
|
539
|
+
const rows = statement?.all(sessionId, limit) as PartRow[] | null;
|
|
540
|
+
return rows ? rows.map(mapToolCallSummary).filter(isNotNull) : [];
|
|
541
|
+
} catch (error) {
|
|
542
|
+
console.warn('[OpenCodeDBReader] Failed to get recent tool calls', error);
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
478
547
|
getTodos(sessionId: string): DBTodo[] {
|
|
479
548
|
if (!this.ensureOpen()) return [];
|
|
480
549
|
|
package/src/sqlite/types.ts
CHANGED
|
@@ -119,3 +119,43 @@ export interface OpenCodeDBConfig {
|
|
|
119
119
|
dbPath?: string;
|
|
120
120
|
enableSchemaValidation?: boolean;
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
/** Non-text message part (image, file attachment, etc.) */
|
|
124
|
+
export interface DBNonTextPart {
|
|
125
|
+
id: string;
|
|
126
|
+
messageId: string;
|
|
127
|
+
type: string;
|
|
128
|
+
toolName?: string;
|
|
129
|
+
timestamp?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Tool call summary for compaction context */
|
|
133
|
+
export interface DBToolCallSummary {
|
|
134
|
+
id: string;
|
|
135
|
+
messageId: string;
|
|
136
|
+
toolName: string;
|
|
137
|
+
input?: string;
|
|
138
|
+
output?: string;
|
|
139
|
+
timestamp: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Stats about what compaction preserved */
|
|
143
|
+
export interface CompactionStats {
|
|
144
|
+
planningPhasesCount: number;
|
|
145
|
+
backgroundTasksCount: number;
|
|
146
|
+
imageDescriptionsCount: number;
|
|
147
|
+
toolCallSummariesCount: number;
|
|
148
|
+
estimatedTokens: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Pre-compaction state snapshot stored to KV */
|
|
152
|
+
export interface PreCompactionSnapshot {
|
|
153
|
+
timestamp: string;
|
|
154
|
+
sessionId: string;
|
|
155
|
+
planningState?: Record<string, unknown>;
|
|
156
|
+
backgroundTasks?: Array<{ id: string; description: string; status: string }>;
|
|
157
|
+
imageDescriptions?: string[];
|
|
158
|
+
toolCallSummaries?: string[];
|
|
159
|
+
cadenceState?: Record<string, unknown>;
|
|
160
|
+
branch?: string;
|
|
161
|
+
}
|
package/src/tools/background.ts
CHANGED
|
@@ -33,6 +33,7 @@ export function createBackgroundTools(manager: BackgroundManager): {
|
|
|
33
33
|
context: ToolContext
|
|
34
34
|
) => Promise<{
|
|
35
35
|
taskId: string;
|
|
36
|
+
sessionId?: string;
|
|
36
37
|
status: string;
|
|
37
38
|
message: string;
|
|
38
39
|
}>;
|
|
@@ -43,6 +44,7 @@ export function createBackgroundTools(manager: BackgroundManager): {
|
|
|
43
44
|
args: typeof BackgroundOutputArgsSchema;
|
|
44
45
|
execute: (args: BackgroundOutputArgs) => Promise<{
|
|
45
46
|
taskId: string;
|
|
47
|
+
sessionId?: string;
|
|
46
48
|
status: string;
|
|
47
49
|
result?: string;
|
|
48
50
|
error?: string;
|
|
@@ -68,6 +70,7 @@ export function createBackgroundTools(manager: BackgroundManager): {
|
|
|
68
70
|
context: ToolContext
|
|
69
71
|
): Promise<{
|
|
70
72
|
taskId: string;
|
|
73
|
+
sessionId?: string;
|
|
71
74
|
status: string;
|
|
72
75
|
message: string;
|
|
73
76
|
}> {
|
|
@@ -82,6 +85,7 @@ export function createBackgroundTools(manager: BackgroundManager): {
|
|
|
82
85
|
|
|
83
86
|
return {
|
|
84
87
|
taskId: task.id,
|
|
88
|
+
sessionId: task.sessionId,
|
|
85
89
|
status: task.status,
|
|
86
90
|
message:
|
|
87
91
|
task.status === 'error'
|
|
@@ -97,6 +101,7 @@ export function createBackgroundTools(manager: BackgroundManager): {
|
|
|
97
101
|
args: BackgroundOutputArgsSchema,
|
|
98
102
|
async execute(args: BackgroundOutputArgs): Promise<{
|
|
99
103
|
taskId: string;
|
|
104
|
+
sessionId?: string;
|
|
100
105
|
status: string;
|
|
101
106
|
result?: string;
|
|
102
107
|
error?: string;
|
|
@@ -111,6 +116,7 @@ export function createBackgroundTools(manager: BackgroundManager): {
|
|
|
111
116
|
}
|
|
112
117
|
return {
|
|
113
118
|
taskId: task.id,
|
|
119
|
+
sessionId: task.sessionId,
|
|
114
120
|
status: task.status,
|
|
115
121
|
result: task.result,
|
|
116
122
|
error: task.error,
|
package/src/types.ts
CHANGED
|
@@ -151,6 +151,24 @@ export const AgentModelConfigSchema = z.object({
|
|
|
151
151
|
maxSteps: z.number().optional(),
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
/** Configuration for compaction behavior */
|
|
155
|
+
export interface CompactionConfig {
|
|
156
|
+
/** Use custom compaction prompt tailored to our agent system (default: true) */
|
|
157
|
+
customPrompt?: boolean;
|
|
158
|
+
/** Inline planning state from KV into compaction context (default: true) */
|
|
159
|
+
inlinePlanning?: boolean;
|
|
160
|
+
/** Detect and describe images/attachments (default: true) */
|
|
161
|
+
imageAwareness?: boolean;
|
|
162
|
+
/** Number of recent tool calls to summarize (default: 5, 0 to disable) */
|
|
163
|
+
toolCallSummaryLimit?: number;
|
|
164
|
+
/** Store pre-compaction snapshot to KV for recovery (default: true) */
|
|
165
|
+
snapshotToKV?: boolean;
|
|
166
|
+
/** Max tokens budget for ALL injected compaction context combined (default: 4000) */
|
|
167
|
+
maxContextTokens?: number;
|
|
168
|
+
/** Reserved token buffer for compaction prompts (default: 40000). Must not exceed OpenCode's max context window. */
|
|
169
|
+
reserved?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
154
172
|
export interface CoderConfig {
|
|
155
173
|
org?: string;
|
|
156
174
|
disabledMcps?: string[];
|
|
@@ -159,6 +177,7 @@ export interface CoderConfig {
|
|
|
159
177
|
background?: BackgroundTaskConfig;
|
|
160
178
|
skills?: SkillsConfig;
|
|
161
179
|
tmux?: TmuxConfig;
|
|
180
|
+
compaction?: CompactionConfig;
|
|
162
181
|
}
|
|
163
182
|
|
|
164
183
|
export const BackgroundTaskConfigSchema = z.object({
|
|
@@ -182,6 +201,16 @@ export const TmuxConfigSchema = z.object({
|
|
|
182
201
|
agentPaneMinWidth: z.number(),
|
|
183
202
|
});
|
|
184
203
|
|
|
204
|
+
export const CompactionConfigSchema = z.object({
|
|
205
|
+
customPrompt: z.boolean().optional(),
|
|
206
|
+
inlinePlanning: z.boolean().optional(),
|
|
207
|
+
imageAwareness: z.boolean().optional(),
|
|
208
|
+
toolCallSummaryLimit: z.number().optional(),
|
|
209
|
+
snapshotToKV: z.boolean().optional(),
|
|
210
|
+
maxContextTokens: z.number().optional(),
|
|
211
|
+
reserved: z.number().optional(),
|
|
212
|
+
});
|
|
213
|
+
|
|
185
214
|
export const CoderConfigSchema = z.object({
|
|
186
215
|
org: z.string().optional(),
|
|
187
216
|
disabledMcps: z.array(z.string()).optional(),
|
|
@@ -189,6 +218,7 @@ export const CoderConfigSchema = z.object({
|
|
|
189
218
|
background: BackgroundTaskConfigSchema.optional(),
|
|
190
219
|
skills: SkillsConfigSchema.optional(),
|
|
191
220
|
tmux: TmuxConfigSchema.optional(),
|
|
221
|
+
compaction: CompactionConfigSchema.optional(),
|
|
192
222
|
});
|
|
193
223
|
|
|
194
224
|
export interface McpConfig {
|