opencode-lcm 0.11.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/CHANGELOG.md +83 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/archive-transform.d.ts +45 -0
- package/dist/archive-transform.js +81 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +16 -0
- package/dist/doctor.d.ts +22 -0
- package/dist/doctor.js +44 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +306 -0
- package/dist/logging.d.ts +14 -0
- package/dist/logging.js +28 -0
- package/dist/options.d.ts +3 -0
- package/dist/options.js +217 -0
- package/dist/preview-providers.d.ts +20 -0
- package/dist/preview-providers.js +246 -0
- package/dist/privacy.d.ts +16 -0
- package/dist/privacy.js +92 -0
- package/dist/search-ranking.d.ts +12 -0
- package/dist/search-ranking.js +98 -0
- package/dist/sql-utils.d.ts +31 -0
- package/dist/sql-utils.js +80 -0
- package/dist/store-artifacts.d.ts +50 -0
- package/dist/store-artifacts.js +374 -0
- package/dist/store-retention.d.ts +39 -0
- package/dist/store-retention.js +90 -0
- package/dist/store-search.d.ts +37 -0
- package/dist/store-search.js +298 -0
- package/dist/store-snapshot.d.ts +133 -0
- package/dist/store-snapshot.js +325 -0
- package/dist/store-types.d.ts +14 -0
- package/dist/store-types.js +5 -0
- package/dist/store.d.ts +316 -0
- package/dist/store.js +3673 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +35 -0
- package/dist/utils.js +414 -0
- package/dist/workspace-path.d.ts +1 -0
- package/dist/workspace-path.js +15 -0
- package/dist/worktree-key.d.ts +1 -0
- package/dist/worktree-key.js +6 -0
- package/package.json +61 -0
- package/src/archive-transform.ts +147 -0
- package/src/bun-sqlite.d.ts +18 -0
- package/src/constants.ts +20 -0
- package/src/doctor.ts +83 -0
- package/src/index.ts +330 -0
- package/src/logging.ts +41 -0
- package/src/options.ts +297 -0
- package/src/preview-providers.ts +298 -0
- package/src/privacy.ts +122 -0
- package/src/search-ranking.ts +145 -0
- package/src/sql-utils.ts +107 -0
- package/src/store-artifacts.ts +666 -0
- package/src/store-retention.ts +152 -0
- package/src/store-search.ts +440 -0
- package/src/store-snapshot.ts +582 -0
- package/src/store-types.ts +16 -0
- package/src/store.ts +4926 -0
- package/src/types.ts +132 -0
- package/src/utils.ts +444 -0
- package/src/workspace-path.ts +20 -0
- package/src/worktree-key.ts +5 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
import { resolveOptions } from './options.js';
|
|
3
|
+
import { SqliteLcmStore } from './store.js';
|
|
4
|
+
export const OpencodeLcmPlugin = async (ctx, rawOptions) => {
|
|
5
|
+
const options = resolveOptions(rawOptions);
|
|
6
|
+
const store = new SqliteLcmStore(ctx.directory, options);
|
|
7
|
+
await store.init();
|
|
8
|
+
return {
|
|
9
|
+
event: async ({ event }) => {
|
|
10
|
+
await store.captureDeferred(event);
|
|
11
|
+
},
|
|
12
|
+
tool: {
|
|
13
|
+
lcm_status: tool({
|
|
14
|
+
description: 'Show archived LCM capture stats',
|
|
15
|
+
args: {},
|
|
16
|
+
async execute() {
|
|
17
|
+
const stats = await store.stats();
|
|
18
|
+
const lines = [
|
|
19
|
+
`schema_version=${stats.schemaVersion}`,
|
|
20
|
+
`total_events=${stats.totalEvents}`,
|
|
21
|
+
`session_count=${stats.sessionCount}`,
|
|
22
|
+
`root_sessions=${stats.rootSessionCount}`,
|
|
23
|
+
`branched_sessions=${stats.branchedSessionCount}`,
|
|
24
|
+
`pinned_sessions=${stats.pinnedSessionCount}`,
|
|
25
|
+
`worktrees=${stats.worktreeCount}`,
|
|
26
|
+
`latest_event_at=${stats.latestEventAt ?? 'n/a'}`,
|
|
27
|
+
`summary_nodes=${stats.summaryNodeCount}`,
|
|
28
|
+
`summary_states=${stats.summaryStateCount}`,
|
|
29
|
+
`artifacts=${stats.artifactCount}`,
|
|
30
|
+
`artifact_blobs=${stats.artifactBlobCount}`,
|
|
31
|
+
`shared_artifact_blobs=${stats.sharedArtifactBlobCount}`,
|
|
32
|
+
`orphan_artifact_blobs=${stats.orphanArtifactBlobCount}`,
|
|
33
|
+
`default_grep_scope=${options.scopeDefaults.grep}`,
|
|
34
|
+
`default_describe_scope=${options.scopeDefaults.describe}`,
|
|
35
|
+
`scope_profiles=${options.scopeProfiles.length}`,
|
|
36
|
+
`retention_stale_session_days=${options.retention.staleSessionDays ?? 'disabled'}`,
|
|
37
|
+
`retention_deleted_session_days=${options.retention.deletedSessionDays ?? 'disabled'}`,
|
|
38
|
+
`retention_orphan_blob_days=${options.retention.orphanBlobDays ?? 'disabled'}`,
|
|
39
|
+
`automatic_retrieval_enabled=${options.automaticRetrieval.enabled}`,
|
|
40
|
+
`automatic_retrieval_max_chars=${options.automaticRetrieval.maxChars}`,
|
|
41
|
+
`automatic_retrieval_min_tokens=${options.automaticRetrieval.minTokens}`,
|
|
42
|
+
`automatic_retrieval_message_hits=${options.automaticRetrieval.maxMessageHits}`,
|
|
43
|
+
`automatic_retrieval_summary_hits=${options.automaticRetrieval.maxSummaryHits}`,
|
|
44
|
+
`automatic_retrieval_artifact_hits=${options.automaticRetrieval.maxArtifactHits}`,
|
|
45
|
+
`automatic_retrieval_scope_order=${options.automaticRetrieval.scopeOrder.join(',')}`,
|
|
46
|
+
`automatic_retrieval_scope_budgets=session:${options.automaticRetrieval.scopeBudgets.session},root:${options.automaticRetrieval.scopeBudgets.root},worktree:${options.automaticRetrieval.scopeBudgets.worktree},all:${options.automaticRetrieval.scopeBudgets.all}`,
|
|
47
|
+
`automatic_retrieval_stop_target_hits=${options.automaticRetrieval.stop.targetHits}`,
|
|
48
|
+
`automatic_retrieval_stop_on_first_scope_with_hits=${options.automaticRetrieval.stop.stopOnFirstScopeWithHits}`,
|
|
49
|
+
`fresh_tail_messages=${options.freshTailMessages}`,
|
|
50
|
+
`min_messages_for_transform=${options.minMessagesForTransform}`,
|
|
51
|
+
`large_content_threshold=${options.largeContentThreshold}`,
|
|
52
|
+
`binary_preview_providers=${options.binaryPreviewProviders.join(',')}`,
|
|
53
|
+
`preview_byte_peek=${options.previewBytePeek}`,
|
|
54
|
+
`privacy_exclude_tool_prefixes=${options.privacy.excludeToolPrefixes.join(',')}`,
|
|
55
|
+
`privacy_exclude_path_patterns=${options.privacy.excludePathPatterns.length}`,
|
|
56
|
+
`privacy_redact_patterns=${options.privacy.redactPatterns.length}`,
|
|
57
|
+
...Object.entries(stats.eventTypes)
|
|
58
|
+
.sort((a, b) => b[1] - a[1])
|
|
59
|
+
.slice(0, 10)
|
|
60
|
+
.map(([type, count]) => `${type}=${count}`),
|
|
61
|
+
];
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
lcm_resume: tool({
|
|
66
|
+
description: 'Show the latest archived resume note',
|
|
67
|
+
args: {
|
|
68
|
+
sessionID: tool.schema.string().optional(),
|
|
69
|
+
},
|
|
70
|
+
async execute(args) {
|
|
71
|
+
return await store.resume(args.sessionID);
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
lcm_grep: tool({
|
|
75
|
+
description: 'Search archived LCM capture with scope',
|
|
76
|
+
args: {
|
|
77
|
+
query: tool.schema.string().min(1),
|
|
78
|
+
sessionID: tool.schema.string().optional(),
|
|
79
|
+
scope: tool.schema.string().optional(),
|
|
80
|
+
limit: tool.schema.number().int().min(1).max(20).optional(),
|
|
81
|
+
},
|
|
82
|
+
async execute(args) {
|
|
83
|
+
const results = await store.grep({
|
|
84
|
+
query: args.query,
|
|
85
|
+
sessionID: args.sessionID,
|
|
86
|
+
scope: args.scope,
|
|
87
|
+
limit: args.limit ?? 5,
|
|
88
|
+
});
|
|
89
|
+
if (results.length === 0)
|
|
90
|
+
return 'No archived matches found.';
|
|
91
|
+
return results
|
|
92
|
+
.map((result) => {
|
|
93
|
+
const suffix = result.sessionID ? ` session=${result.sessionID}` : '';
|
|
94
|
+
return `[${result.type}]${suffix} ${result.snippet}`;
|
|
95
|
+
})
|
|
96
|
+
.join('\n\n');
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
lcm_describe: tool({
|
|
100
|
+
description: 'Summarize archived session capture with scope',
|
|
101
|
+
args: {
|
|
102
|
+
sessionID: tool.schema.string().optional(),
|
|
103
|
+
scope: tool.schema.string().optional(),
|
|
104
|
+
},
|
|
105
|
+
async execute(args) {
|
|
106
|
+
return await store.describe({
|
|
107
|
+
sessionID: args.sessionID,
|
|
108
|
+
scope: args.scope,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
lcm_lineage: tool({
|
|
113
|
+
description: 'Show archived branch lineage for a session',
|
|
114
|
+
args: {
|
|
115
|
+
sessionID: tool.schema.string().optional(),
|
|
116
|
+
},
|
|
117
|
+
async execute(args) {
|
|
118
|
+
return await store.lineage(args.sessionID);
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
lcm_pin_session: tool({
|
|
122
|
+
description: 'Pin a session so retention pruning will skip it',
|
|
123
|
+
args: {
|
|
124
|
+
sessionID: tool.schema.string().optional(),
|
|
125
|
+
reason: tool.schema.string().optional(),
|
|
126
|
+
},
|
|
127
|
+
async execute(args) {
|
|
128
|
+
return await store.pinSession({
|
|
129
|
+
sessionID: args.sessionID,
|
|
130
|
+
reason: args.reason,
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
lcm_unpin_session: tool({
|
|
135
|
+
description: 'Remove a session retention pin',
|
|
136
|
+
args: {
|
|
137
|
+
sessionID: tool.schema.string().optional(),
|
|
138
|
+
},
|
|
139
|
+
async execute(args) {
|
|
140
|
+
return await store.unpinSession({
|
|
141
|
+
sessionID: args.sessionID,
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
lcm_expand: tool({
|
|
146
|
+
description: 'Expand archived summary nodes into targeted descendants or raw messages',
|
|
147
|
+
args: {
|
|
148
|
+
sessionID: tool.schema.string().optional(),
|
|
149
|
+
nodeID: tool.schema.string().optional(),
|
|
150
|
+
query: tool.schema.string().optional(),
|
|
151
|
+
depth: tool.schema.number().int().min(1).max(4).optional(),
|
|
152
|
+
messageLimit: tool.schema.number().int().min(1).max(20).optional(),
|
|
153
|
+
includeRaw: tool.schema.boolean().optional(),
|
|
154
|
+
},
|
|
155
|
+
async execute(args) {
|
|
156
|
+
return await store.expand({
|
|
157
|
+
sessionID: args.sessionID,
|
|
158
|
+
nodeID: args.nodeID,
|
|
159
|
+
query: args.query,
|
|
160
|
+
depth: args.depth,
|
|
161
|
+
messageLimit: args.messageLimit,
|
|
162
|
+
includeRaw: args.includeRaw,
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
lcm_artifact: tool({
|
|
167
|
+
description: 'View externalized archived content by artifact ID',
|
|
168
|
+
args: {
|
|
169
|
+
artifactID: tool.schema.string().min(1),
|
|
170
|
+
chars: tool.schema.number().int().min(200).max(20000).optional(),
|
|
171
|
+
},
|
|
172
|
+
async execute(args) {
|
|
173
|
+
return await store.artifact({
|
|
174
|
+
artifactID: args.artifactID,
|
|
175
|
+
chars: args.chars,
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
lcm_blob_stats: tool({
|
|
180
|
+
description: 'Show deduplicated artifact blob stats',
|
|
181
|
+
args: {
|
|
182
|
+
limit: tool.schema.number().int().min(1).max(20).optional(),
|
|
183
|
+
},
|
|
184
|
+
async execute(args) {
|
|
185
|
+
return await store.blobStats({
|
|
186
|
+
limit: args.limit,
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
lcm_blob_gc: tool({
|
|
191
|
+
description: 'Preview or delete orphaned artifact blobs',
|
|
192
|
+
args: {
|
|
193
|
+
apply: tool.schema.boolean().optional(),
|
|
194
|
+
limit: tool.schema.number().int().min(1).max(50).optional(),
|
|
195
|
+
},
|
|
196
|
+
async execute(args) {
|
|
197
|
+
return await store.gcBlobs({
|
|
198
|
+
apply: args.apply,
|
|
199
|
+
limit: args.limit,
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
}),
|
|
203
|
+
lcm_doctor: tool({
|
|
204
|
+
description: 'Inspect or repair archive summaries and indexes',
|
|
205
|
+
args: {
|
|
206
|
+
apply: tool.schema.boolean().optional(),
|
|
207
|
+
sessionID: tool.schema.string().optional(),
|
|
208
|
+
limit: tool.schema.number().int().min(1).max(50).optional(),
|
|
209
|
+
},
|
|
210
|
+
async execute(args) {
|
|
211
|
+
return await store.doctor({
|
|
212
|
+
apply: args.apply,
|
|
213
|
+
sessionID: args.sessionID,
|
|
214
|
+
limit: args.limit,
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
lcm_retention_report: tool({
|
|
219
|
+
description: 'Preview stale-session and orphan-blob retention candidates',
|
|
220
|
+
args: {
|
|
221
|
+
staleSessionDays: tool.schema.number().min(0).optional(),
|
|
222
|
+
deletedSessionDays: tool.schema.number().min(0).optional(),
|
|
223
|
+
orphanBlobDays: tool.schema.number().min(0).optional(),
|
|
224
|
+
limit: tool.schema.number().int().min(1).max(50).optional(),
|
|
225
|
+
},
|
|
226
|
+
async execute(args) {
|
|
227
|
+
return await store.retentionReport({
|
|
228
|
+
staleSessionDays: args.staleSessionDays,
|
|
229
|
+
deletedSessionDays: args.deletedSessionDays,
|
|
230
|
+
orphanBlobDays: args.orphanBlobDays,
|
|
231
|
+
limit: args.limit,
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
235
|
+
lcm_retention_prune: tool({
|
|
236
|
+
description: 'Preview or apply stale-session and orphan-blob retention pruning',
|
|
237
|
+
args: {
|
|
238
|
+
apply: tool.schema.boolean().optional(),
|
|
239
|
+
staleSessionDays: tool.schema.number().min(0).optional(),
|
|
240
|
+
deletedSessionDays: tool.schema.number().min(0).optional(),
|
|
241
|
+
orphanBlobDays: tool.schema.number().min(0).optional(),
|
|
242
|
+
limit: tool.schema.number().int().min(1).max(50).optional(),
|
|
243
|
+
},
|
|
244
|
+
async execute(args) {
|
|
245
|
+
return await store.retentionPrune({
|
|
246
|
+
apply: args.apply,
|
|
247
|
+
staleSessionDays: args.staleSessionDays,
|
|
248
|
+
deletedSessionDays: args.deletedSessionDays,
|
|
249
|
+
orphanBlobDays: args.orphanBlobDays,
|
|
250
|
+
limit: args.limit,
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
lcm_export_snapshot: tool({
|
|
255
|
+
description: 'Export a portable long-memory snapshot to a JSON file',
|
|
256
|
+
args: {
|
|
257
|
+
filePath: tool.schema.string().min(1),
|
|
258
|
+
sessionID: tool.schema.string().optional(),
|
|
259
|
+
scope: tool.schema.string().optional(),
|
|
260
|
+
},
|
|
261
|
+
async execute(args) {
|
|
262
|
+
return await store.exportSnapshot({
|
|
263
|
+
filePath: args.filePath,
|
|
264
|
+
sessionID: args.sessionID,
|
|
265
|
+
scope: args.scope,
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
}),
|
|
269
|
+
lcm_import_snapshot: tool({
|
|
270
|
+
description: 'Import a portable long-memory snapshot from a JSON file',
|
|
271
|
+
args: {
|
|
272
|
+
filePath: tool.schema.string().min(1),
|
|
273
|
+
mode: tool.schema.string().optional(),
|
|
274
|
+
worktreeMode: tool.schema.string().optional(),
|
|
275
|
+
},
|
|
276
|
+
async execute(args) {
|
|
277
|
+
return await store.importSnapshot({
|
|
278
|
+
filePath: args.filePath,
|
|
279
|
+
mode: args.mode === 'merge' ? 'merge' : 'replace',
|
|
280
|
+
worktreeMode: args.worktreeMode === 'preserve' || args.worktreeMode === 'current'
|
|
281
|
+
? args.worktreeMode
|
|
282
|
+
: 'auto',
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
},
|
|
287
|
+
'experimental.chat.messages.transform': async (_input, output) => {
|
|
288
|
+
await store.transformMessages(output.messages);
|
|
289
|
+
},
|
|
290
|
+
'experimental.chat.system.transform': async (_input, output) => {
|
|
291
|
+
const hint = store.systemHint();
|
|
292
|
+
if (!hint)
|
|
293
|
+
return;
|
|
294
|
+
output.system.push(hint);
|
|
295
|
+
},
|
|
296
|
+
'experimental.session.compacting': async (input, output) => {
|
|
297
|
+
const note = await store.buildCompactionContext(input.sessionID);
|
|
298
|
+
if (!note)
|
|
299
|
+
return;
|
|
300
|
+
if (output.context.some((entry) => entry.includes('LCM prototype resume note')))
|
|
301
|
+
return;
|
|
302
|
+
output.context.push(note);
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
export default OpencodeLcmPlugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight structured logging interface for opencode-lcm.
|
|
3
|
+
* Silent by default so plugin logs do not corrupt the host terminal UI.
|
|
4
|
+
*/
|
|
5
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
6
|
+
export interface Logger {
|
|
7
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
8
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
9
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
10
|
+
error(message: string, context?: Record<string, unknown>): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function setLogger(logger: Logger): void;
|
|
13
|
+
export declare function getLogger(): Logger;
|
|
14
|
+
export declare function isStartupLoggingEnabled(): boolean;
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight structured logging interface for opencode-lcm.
|
|
3
|
+
* Silent by default so plugin logs do not corrupt the host terminal UI.
|
|
4
|
+
*/
|
|
5
|
+
function isTruthyEnvFlag(value) {
|
|
6
|
+
if (!value)
|
|
7
|
+
return false;
|
|
8
|
+
const normalized = value.trim().toLowerCase();
|
|
9
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
10
|
+
}
|
|
11
|
+
const silentLogger = {
|
|
12
|
+
debug() { },
|
|
13
|
+
info() { },
|
|
14
|
+
warn() { },
|
|
15
|
+
error() { },
|
|
16
|
+
};
|
|
17
|
+
let currentLogger = silentLogger;
|
|
18
|
+
export function setLogger(logger) {
|
|
19
|
+
currentLogger = logger;
|
|
20
|
+
}
|
|
21
|
+
export function getLogger() {
|
|
22
|
+
return currentLogger;
|
|
23
|
+
}
|
|
24
|
+
export function isStartupLoggingEnabled() {
|
|
25
|
+
if (typeof process !== 'object' || !process?.env)
|
|
26
|
+
return false;
|
|
27
|
+
return isTruthyEnvFlag(process.env.OPENCODE_LCM_STARTUP_LOG);
|
|
28
|
+
}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const DEFAULT_INTEROP = {
|
|
2
|
+
contextMode: true,
|
|
3
|
+
neverOverrideCompactionPrompt: true,
|
|
4
|
+
ignoreToolPrefixes: ['ctx_'],
|
|
5
|
+
};
|
|
6
|
+
const DEFAULT_SCOPE_DEFAULTS = {
|
|
7
|
+
grep: 'session',
|
|
8
|
+
describe: 'session',
|
|
9
|
+
};
|
|
10
|
+
const DEFAULT_RETENTION = {
|
|
11
|
+
staleSessionDays: undefined,
|
|
12
|
+
deletedSessionDays: 30,
|
|
13
|
+
orphanBlobDays: 14,
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_PRIVACY = {
|
|
16
|
+
excludeToolPrefixes: [],
|
|
17
|
+
excludePathPatterns: [],
|
|
18
|
+
redactPatterns: [],
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_AUTOMATIC_RETRIEVAL = {
|
|
21
|
+
enabled: true,
|
|
22
|
+
maxChars: 900,
|
|
23
|
+
minTokens: 2,
|
|
24
|
+
maxMessageHits: 2,
|
|
25
|
+
maxSummaryHits: 1,
|
|
26
|
+
maxArtifactHits: 1,
|
|
27
|
+
scopeOrder: ['session', 'root', 'worktree'],
|
|
28
|
+
scopeBudgets: {
|
|
29
|
+
session: 16,
|
|
30
|
+
root: 12,
|
|
31
|
+
worktree: 8,
|
|
32
|
+
all: 6,
|
|
33
|
+
},
|
|
34
|
+
stop: {
|
|
35
|
+
targetHits: 3,
|
|
36
|
+
stopOnFirstScopeWithHits: false,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export const DEFAULT_OPTIONS = {
|
|
40
|
+
interop: DEFAULT_INTEROP,
|
|
41
|
+
scopeDefaults: DEFAULT_SCOPE_DEFAULTS,
|
|
42
|
+
scopeProfiles: [],
|
|
43
|
+
retention: DEFAULT_RETENTION,
|
|
44
|
+
privacy: DEFAULT_PRIVACY,
|
|
45
|
+
automaticRetrieval: DEFAULT_AUTOMATIC_RETRIEVAL,
|
|
46
|
+
compactContextLimit: 1200,
|
|
47
|
+
systemHint: true,
|
|
48
|
+
freshTailMessages: 10,
|
|
49
|
+
minMessagesForTransform: 16,
|
|
50
|
+
summaryCharBudget: 1500,
|
|
51
|
+
partCharBudget: 160,
|
|
52
|
+
largeContentThreshold: 1200,
|
|
53
|
+
artifactPreviewChars: 220,
|
|
54
|
+
artifactViewChars: 4000,
|
|
55
|
+
binaryPreviewProviders: [
|
|
56
|
+
'fingerprint',
|
|
57
|
+
'byte-peek',
|
|
58
|
+
'image-dimensions',
|
|
59
|
+
'pdf-metadata',
|
|
60
|
+
'zip-metadata',
|
|
61
|
+
],
|
|
62
|
+
previewBytePeek: 16,
|
|
63
|
+
};
|
|
64
|
+
function asRecord(value) {
|
|
65
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
66
|
+
return undefined;
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function asBoolean(value, fallback) {
|
|
70
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
71
|
+
}
|
|
72
|
+
function asNumber(value, fallback) {
|
|
73
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
74
|
+
}
|
|
75
|
+
function asNonNegativeNumber(value, fallback) {
|
|
76
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
77
|
+
}
|
|
78
|
+
function asOptionalNumber(value) {
|
|
79
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
80
|
+
}
|
|
81
|
+
function asStringArray(value, fallback) {
|
|
82
|
+
if (!Array.isArray(value))
|
|
83
|
+
return fallback;
|
|
84
|
+
const next = value.filter((item) => typeof item === 'string' && item.length > 0);
|
|
85
|
+
return next.length > 0 ? next : fallback;
|
|
86
|
+
}
|
|
87
|
+
function asScopeName(value, fallback) {
|
|
88
|
+
return value === 'session' || value === 'root' || value === 'worktree' || value === 'all'
|
|
89
|
+
? value
|
|
90
|
+
: fallback;
|
|
91
|
+
}
|
|
92
|
+
function asScopeNameArray(value, fallback) {
|
|
93
|
+
if (!Array.isArray(value))
|
|
94
|
+
return fallback;
|
|
95
|
+
const result = [];
|
|
96
|
+
for (const item of value) {
|
|
97
|
+
if (item !== 'session' && item !== 'root' && item !== 'worktree' && item !== 'all')
|
|
98
|
+
continue;
|
|
99
|
+
if (result.includes(item))
|
|
100
|
+
continue;
|
|
101
|
+
result.push(item);
|
|
102
|
+
}
|
|
103
|
+
return result.length > 0 ? result : fallback;
|
|
104
|
+
}
|
|
105
|
+
function asScopeDefaults(value, fallback) {
|
|
106
|
+
const record = asRecord(value);
|
|
107
|
+
return {
|
|
108
|
+
grep: asScopeName(record?.grep, fallback.grep),
|
|
109
|
+
describe: asScopeName(record?.describe, fallback.describe),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function asScopeProfiles(value) {
|
|
113
|
+
if (!Array.isArray(value))
|
|
114
|
+
return [];
|
|
115
|
+
const result = [];
|
|
116
|
+
for (const item of value) {
|
|
117
|
+
const record = asRecord(item);
|
|
118
|
+
const worktree = typeof record?.worktree === 'string' && record.worktree.length > 0
|
|
119
|
+
? record.worktree
|
|
120
|
+
: undefined;
|
|
121
|
+
if (!worktree)
|
|
122
|
+
continue;
|
|
123
|
+
result.push({
|
|
124
|
+
worktree,
|
|
125
|
+
grep: record?.grep === undefined
|
|
126
|
+
? undefined
|
|
127
|
+
: asScopeName(record.grep, DEFAULT_SCOPE_DEFAULTS.grep),
|
|
128
|
+
describe: record?.describe === undefined
|
|
129
|
+
? undefined
|
|
130
|
+
: asScopeName(record.describe, DEFAULT_SCOPE_DEFAULTS.describe),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function asRetentionOptions(value, fallback) {
|
|
136
|
+
const record = asRecord(value);
|
|
137
|
+
return {
|
|
138
|
+
staleSessionDays: record?.staleSessionDays === undefined
|
|
139
|
+
? fallback.staleSessionDays
|
|
140
|
+
: asOptionalNumber(record.staleSessionDays),
|
|
141
|
+
deletedSessionDays: record?.deletedSessionDays === undefined
|
|
142
|
+
? fallback.deletedSessionDays
|
|
143
|
+
: asOptionalNumber(record.deletedSessionDays),
|
|
144
|
+
orphanBlobDays: record?.orphanBlobDays === undefined
|
|
145
|
+
? fallback.orphanBlobDays
|
|
146
|
+
: asOptionalNumber(record.orphanBlobDays),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function asPrivacyOptions(value, fallback) {
|
|
150
|
+
const record = asRecord(value);
|
|
151
|
+
return {
|
|
152
|
+
excludeToolPrefixes: asStringArray(record?.excludeToolPrefixes, fallback.excludeToolPrefixes),
|
|
153
|
+
excludePathPatterns: asStringArray(record?.excludePathPatterns, fallback.excludePathPatterns),
|
|
154
|
+
redactPatterns: asStringArray(record?.redactPatterns, fallback.redactPatterns),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function asAutomaticRetrievalOptions(value, fallback) {
|
|
158
|
+
const record = asRecord(value);
|
|
159
|
+
return {
|
|
160
|
+
enabled: asBoolean(record?.enabled, fallback.enabled),
|
|
161
|
+
maxChars: asNumber(record?.maxChars, fallback.maxChars),
|
|
162
|
+
minTokens: asNumber(record?.minTokens, fallback.minTokens),
|
|
163
|
+
maxMessageHits: asNumber(record?.maxMessageHits, fallback.maxMessageHits),
|
|
164
|
+
maxSummaryHits: asNumber(record?.maxSummaryHits, fallback.maxSummaryHits),
|
|
165
|
+
maxArtifactHits: asNumber(record?.maxArtifactHits, fallback.maxArtifactHits),
|
|
166
|
+
scopeOrder: asScopeNameArray(record?.scopeOrder, fallback.scopeOrder),
|
|
167
|
+
scopeBudgets: asAutomaticRetrievalScopeBudgets(record?.scopeBudgets, fallback.scopeBudgets),
|
|
168
|
+
stop: asAutomaticRetrievalStopOptions(record?.stop, fallback.stop),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function asAutomaticRetrievalScopeBudgets(value, fallback) {
|
|
172
|
+
const record = asRecord(value);
|
|
173
|
+
return {
|
|
174
|
+
session: asNonNegativeNumber(record?.session, fallback.session),
|
|
175
|
+
root: asNonNegativeNumber(record?.root, fallback.root),
|
|
176
|
+
worktree: asNonNegativeNumber(record?.worktree, fallback.worktree),
|
|
177
|
+
all: asNonNegativeNumber(record?.all, fallback.all),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function asAutomaticRetrievalStopOptions(value, fallback) {
|
|
181
|
+
const record = asRecord(value);
|
|
182
|
+
return {
|
|
183
|
+
targetHits: asNonNegativeNumber(record?.targetHits, fallback.targetHits),
|
|
184
|
+
stopOnFirstScopeWithHits: asBoolean(record?.stopOnFirstScopeWithHits, fallback.stopOnFirstScopeWithHits),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function resolveOptions(raw) {
|
|
188
|
+
const options = asRecord(raw);
|
|
189
|
+
const interop = asRecord(options?.interop);
|
|
190
|
+
const scopeDefaults = asScopeDefaults(options?.scopeDefaults, DEFAULT_SCOPE_DEFAULTS);
|
|
191
|
+
return {
|
|
192
|
+
interop: {
|
|
193
|
+
contextMode: asBoolean(interop?.contextMode, DEFAULT_INTEROP.contextMode),
|
|
194
|
+
neverOverrideCompactionPrompt: asBoolean(interop?.neverOverrideCompactionPrompt, DEFAULT_INTEROP.neverOverrideCompactionPrompt),
|
|
195
|
+
ignoreToolPrefixes: asStringArray(interop?.ignoreToolPrefixes, DEFAULT_INTEROP.ignoreToolPrefixes),
|
|
196
|
+
},
|
|
197
|
+
scopeDefaults,
|
|
198
|
+
scopeProfiles: asScopeProfiles(options?.scopeProfiles),
|
|
199
|
+
retention: asRetentionOptions(options?.retention, DEFAULT_RETENTION),
|
|
200
|
+
privacy: asPrivacyOptions(options?.privacy, DEFAULT_PRIVACY),
|
|
201
|
+
automaticRetrieval: asAutomaticRetrievalOptions(options?.automaticRetrieval, DEFAULT_AUTOMATIC_RETRIEVAL),
|
|
202
|
+
compactContextLimit: asNumber(options?.compactContextLimit, DEFAULT_OPTIONS.compactContextLimit),
|
|
203
|
+
systemHint: asBoolean(options?.systemHint, DEFAULT_OPTIONS.systemHint),
|
|
204
|
+
storeDir: typeof options?.storeDir === 'string' && options.storeDir.length > 0
|
|
205
|
+
? options.storeDir
|
|
206
|
+
: undefined,
|
|
207
|
+
freshTailMessages: asNumber(options?.freshTailMessages, DEFAULT_OPTIONS.freshTailMessages),
|
|
208
|
+
minMessagesForTransform: asNumber(options?.minMessagesForTransform, DEFAULT_OPTIONS.minMessagesForTransform),
|
|
209
|
+
summaryCharBudget: asNumber(options?.summaryCharBudget, DEFAULT_OPTIONS.summaryCharBudget),
|
|
210
|
+
partCharBudget: asNumber(options?.partCharBudget, DEFAULT_OPTIONS.partCharBudget),
|
|
211
|
+
largeContentThreshold: asNumber(options?.largeContentThreshold, DEFAULT_OPTIONS.largeContentThreshold),
|
|
212
|
+
artifactPreviewChars: asNumber(options?.artifactPreviewChars, DEFAULT_OPTIONS.artifactPreviewChars),
|
|
213
|
+
artifactViewChars: asNumber(options?.artifactViewChars, DEFAULT_OPTIONS.artifactViewChars),
|
|
214
|
+
binaryPreviewProviders: asStringArray(options?.binaryPreviewProviders, DEFAULT_OPTIONS.binaryPreviewProviders),
|
|
215
|
+
previewBytePeek: asNumber(options?.previewBytePeek, DEFAULT_OPTIONS.previewBytePeek),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Part } from '@opencode-ai/sdk';
|
|
2
|
+
type FilePart = Extract<Part, {
|
|
3
|
+
type: 'file';
|
|
4
|
+
}>;
|
|
5
|
+
type PreviewContext = {
|
|
6
|
+
workspaceDirectory: string;
|
|
7
|
+
file: FilePart;
|
|
8
|
+
category: string;
|
|
9
|
+
extension?: string;
|
|
10
|
+
mime?: string;
|
|
11
|
+
enabledProviders: string[];
|
|
12
|
+
bytePeek: number;
|
|
13
|
+
};
|
|
14
|
+
type PreviewOutput = {
|
|
15
|
+
metadata: Record<string, unknown>;
|
|
16
|
+
lines: string[];
|
|
17
|
+
summaryBits: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function runBinaryPreviewProviders(context: PreviewContext): Promise<PreviewOutput>;
|
|
20
|
+
export {};
|