@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.
Files changed (84) hide show
  1. package/dist/agents/expert-backend.js +1 -1
  2. package/dist/agents/expert-backend.js.map +1 -1
  3. package/dist/agents/expert-frontend.js +1 -1
  4. package/dist/agents/expert-frontend.js.map +1 -1
  5. package/dist/agents/expert-ops.js +1 -1
  6. package/dist/agents/expert-ops.js.map +1 -1
  7. package/dist/agents/expert.js +1 -1
  8. package/dist/agents/expert.js.map +1 -1
  9. package/dist/agents/lead.d.ts +1 -1
  10. package/dist/agents/lead.d.ts.map +1 -1
  11. package/dist/agents/lead.js +34 -7
  12. package/dist/agents/lead.js.map +1 -1
  13. package/dist/agents/monitor.d.ts +1 -1
  14. package/dist/agents/monitor.d.ts.map +1 -1
  15. package/dist/agents/monitor.js +22 -33
  16. package/dist/agents/monitor.js.map +1 -1
  17. package/dist/agents/reviewer.js +1 -1
  18. package/dist/agents/reviewer.js.map +1 -1
  19. package/dist/agents/scout.js +2 -2
  20. package/dist/agents/scout.js.map +1 -1
  21. package/dist/background/manager.d.ts +27 -0
  22. package/dist/background/manager.d.ts.map +1 -1
  23. package/dist/background/manager.js +161 -27
  24. package/dist/background/manager.js.map +1 -1
  25. package/dist/plugin/hooks/cadence.d.ts +3 -1
  26. package/dist/plugin/hooks/cadence.d.ts.map +1 -1
  27. package/dist/plugin/hooks/cadence.js +167 -66
  28. package/dist/plugin/hooks/cadence.js.map +1 -1
  29. package/dist/plugin/hooks/compaction-utils.d.ts +48 -0
  30. package/dist/plugin/hooks/compaction-utils.d.ts.map +1 -0
  31. package/dist/plugin/hooks/compaction-utils.js +259 -0
  32. package/dist/plugin/hooks/compaction-utils.js.map +1 -0
  33. package/dist/plugin/hooks/params.d.ts +1 -1
  34. package/dist/plugin/hooks/params.d.ts.map +1 -1
  35. package/dist/plugin/hooks/params.js +5 -1
  36. package/dist/plugin/hooks/params.js.map +1 -1
  37. package/dist/plugin/hooks/session-memory.d.ts +2 -1
  38. package/dist/plugin/hooks/session-memory.d.ts.map +1 -1
  39. package/dist/plugin/hooks/session-memory.js +97 -48
  40. package/dist/plugin/hooks/session-memory.js.map +1 -1
  41. package/dist/plugin/plugin.d.ts.map +1 -1
  42. package/dist/plugin/plugin.js +31 -9
  43. package/dist/plugin/plugin.js.map +1 -1
  44. package/dist/sqlite/index.d.ts +1 -1
  45. package/dist/sqlite/index.d.ts.map +1 -1
  46. package/dist/sqlite/queries.d.ts +1 -0
  47. package/dist/sqlite/queries.d.ts.map +1 -1
  48. package/dist/sqlite/queries.js +4 -0
  49. package/dist/sqlite/queries.js.map +1 -1
  50. package/dist/sqlite/reader.d.ts +11 -1
  51. package/dist/sqlite/reader.d.ts.map +1 -1
  52. package/dist/sqlite/reader.js +62 -0
  53. package/dist/sqlite/reader.js.map +1 -1
  54. package/dist/sqlite/types.d.ts +40 -0
  55. package/dist/sqlite/types.d.ts.map +1 -1
  56. package/dist/tools/background.d.ts +2 -0
  57. package/dist/tools/background.d.ts.map +1 -1
  58. package/dist/tools/background.js +2 -0
  59. package/dist/tools/background.js.map +1 -1
  60. package/dist/types.d.ts +36 -0
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js +10 -0
  63. package/dist/types.js.map +1 -1
  64. package/package.json +3 -3
  65. package/src/agents/expert-backend.ts +1 -1
  66. package/src/agents/expert-frontend.ts +1 -1
  67. package/src/agents/expert-ops.ts +1 -1
  68. package/src/agents/expert.ts +1 -1
  69. package/src/agents/lead.ts +34 -7
  70. package/src/agents/monitor.ts +22 -33
  71. package/src/agents/reviewer.ts +1 -1
  72. package/src/agents/scout.ts +2 -2
  73. package/src/background/manager.ts +167 -32
  74. package/src/plugin/hooks/cadence.ts +184 -66
  75. package/src/plugin/hooks/compaction-utils.ts +291 -0
  76. package/src/plugin/hooks/params.ts +10 -1
  77. package/src/plugin/hooks/session-memory.ts +109 -47
  78. package/src/plugin/plugin.ts +47 -10
  79. package/src/sqlite/index.ts +4 -0
  80. package/src/sqlite/queries.ts +5 -0
  81. package/src/sqlite/reader.ts +69 -0
  82. package/src/sqlite/types.ts +40 -0
  83. package/src/tools/background.ts +6 -0
  84. 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
- * Get the current git branch name.
7
- */
8
- async function getCurrentBranch(): Promise<string> {
9
- try {
10
- const proc = Bun.spawn(['git', 'branch', '--show-current'], {
11
- stdout: 'pipe',
12
- stderr: 'pipe',
13
- });
14
- const stdout = await new Response(proc.stdout).text();
15
- await proc.exited;
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
- _config: CoderConfig,
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
- * This gets included in OpenCode's generated summary.
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
- // Get current git branch
157
- const branch = await getCurrentBranch();
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
- // Get active background tasks for this session
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 backgroundTaskContext = '';
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
- backgroundTaskContext = `
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
- output.context.push(`
184
- ## Session Memory
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
- This session's context is being saved to persistent memory.
187
- Session record location: \`session:${sessionId}\` in agentuity-opencode-memory
188
- Current branch: ${branch}
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
- **Planning State (if active):**
191
- If this session has planning active (user requested "track progress" or similar), the session record contains:
192
- - \`planning.prdKey\` - Link to PRD if one exists
193
- - \`planning.objective\` - What we're trying to accomplish
194
- - \`planning.phases\` - Current phases with status and notes
195
- - \`planning.findings\` - Discoveries made during work
196
- - \`planning.errors\` - Failures to avoid repeating
197
- ${backgroundTaskContext}
198
- After compaction:
199
- 1. Memory will save this summary to the session record
200
- 2. If planning is active, Memory should update planning.progress with this compaction
201
- 3. Memory will apply inline reasoning if significant patterns/corrections emerged
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
  }
@@ -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(ctx, coderConfig, backgroundManager, dbReader);
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(ctx, coderConfig, backgroundManager);
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 && cadenceHooks.isActiveCadenceSession(sessionId)) {
234
- await cadenceHooks.onEvent(input);
235
- } else if (sessionId) {
236
- // Non-Cadence sessions - handle session.compacted for checkpointing
237
- await sessionMemoryHooks.onEvent(
238
- input as { event: { type: string; properties?: Record<string, unknown> } }
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,
@@ -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,
@@ -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;
@@ -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
 
@@ -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
+ }
@@ -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 {