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
|
@@ -1,19 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Session Tools
|
|
3
|
-
*
|
|
3
|
+
* Direct SQLite access for fast, ranked session search.
|
|
4
4
|
*
|
|
5
5
|
* Core tools:
|
|
6
|
-
* - find_sessions:
|
|
7
|
-
* - read_session:
|
|
6
|
+
* - find_sessions: Multi-word AND search with relevance ranking
|
|
7
|
+
* - read_session: Full transcript with keyword filtering
|
|
8
|
+
*
|
|
9
|
+
* Requires Bun runtime (uses bun:sqlite for zero-dep DB access).
|
|
8
10
|
*/
|
|
9
11
|
|
|
12
|
+
import { Database } from "bun:sqlite";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import { join } from "node:path";
|
|
10
15
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
11
16
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
// --- Configuration ---
|
|
19
|
+
|
|
20
|
+
const SEARCH_CONFIG = {
|
|
21
|
+
roles: ["user", "assistant"],
|
|
22
|
+
defaultLimit: 6,
|
|
23
|
+
maxLimit: 12,
|
|
24
|
+
snippetsPerSession: 2,
|
|
25
|
+
snippetLength: 220,
|
|
26
|
+
sinceHours: 24 * 180, // 180-day lookback window
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
const TRANSCRIPT_CONFIG = {
|
|
30
|
+
roles: ["user", "assistant"],
|
|
31
|
+
defaultLimit: 80,
|
|
32
|
+
maxLimit: 120,
|
|
33
|
+
maxCharsPerEntry: 600,
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
// --- DB helpers ---
|
|
37
|
+
|
|
38
|
+
const resolveDbPath = (): string => {
|
|
39
|
+
// 1. Env override
|
|
40
|
+
if (process.env.OPENCODE_DB_PATH) return process.env.OPENCODE_DB_PATH;
|
|
41
|
+
|
|
42
|
+
// 2. CLI introspection — ask the running binary where the DB is
|
|
43
|
+
try {
|
|
44
|
+
const result = spawnSync("opencode", ["db", "path"], {
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
47
|
+
});
|
|
48
|
+
if (result.status === 0) {
|
|
49
|
+
const p = (result.stdout || "").trim();
|
|
50
|
+
if (p) return p;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
/* fall through */
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 3. XDG fallback
|
|
57
|
+
return join(
|
|
58
|
+
process.env.HOME || "",
|
|
59
|
+
".local",
|
|
60
|
+
"share",
|
|
61
|
+
"opencode",
|
|
62
|
+
"opencode.db",
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Resolved once at module load — no per-request resolution cost. */
|
|
67
|
+
const DEFAULT_DB_PATH = resolveDbPath();
|
|
68
|
+
|
|
69
|
+
const openReadonlyDb = (): { db: Database | null; error: string | null } => {
|
|
70
|
+
try {
|
|
71
|
+
return {
|
|
72
|
+
db: new Database(DEFAULT_DB_PATH, {
|
|
73
|
+
readonly: true,
|
|
74
|
+
create: false,
|
|
75
|
+
strict: true,
|
|
76
|
+
}),
|
|
77
|
+
error: null,
|
|
78
|
+
};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
db: null,
|
|
82
|
+
error: err instanceof Error ? err.message : String(err),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Escape SQL LIKE wildcards for safe pattern matching. */
|
|
88
|
+
const escapeLike = (value: string): string =>
|
|
89
|
+
value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
90
|
+
|
|
91
|
+
const formatTime = (ms: number): string => new Date(ms).toISOString();
|
|
92
|
+
|
|
93
|
+
const ROLE_LIST = SEARCH_CONFIG.roles.map((r) => `'${r}'`).join(",");
|
|
94
|
+
|
|
95
|
+
// --- Plugin ---
|
|
96
|
+
|
|
97
|
+
export const SessionsPlugin: Plugin = async () => {
|
|
14
98
|
return {
|
|
15
99
|
tool: {
|
|
16
|
-
/** Like AmpCode's find_thread */
|
|
17
100
|
find_sessions: tool({
|
|
18
101
|
description: `Search sessions by keyword.
|
|
19
102
|
|
|
@@ -27,53 +110,174 @@ find_sessions({ query: "auth", limit: 5 })`,
|
|
|
27
110
|
.describe("Max results (default: 10)"),
|
|
28
111
|
},
|
|
29
112
|
async execute(args: { query: string; limit?: number }) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
113
|
+
const trimmed = args.query.trim();
|
|
114
|
+
if (!trimmed) {
|
|
115
|
+
return JSON.stringify({
|
|
116
|
+
error: "INVALID_QUERY",
|
|
117
|
+
message: "Query cannot be empty",
|
|
118
|
+
dbPath: DEFAULT_DB_PATH,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
34
121
|
|
|
35
|
-
|
|
122
|
+
const { db, error } = openReadonlyDb();
|
|
123
|
+
if (!db) {
|
|
124
|
+
return JSON.stringify({
|
|
125
|
+
error: "DB_OPEN_FAILED",
|
|
126
|
+
message: error,
|
|
127
|
+
dbPath: DEFAULT_DB_PATH,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
36
130
|
|
|
37
|
-
|
|
38
|
-
|
|
131
|
+
try {
|
|
132
|
+
const words = trimmed.toLowerCase().split(/\s+/).filter(Boolean);
|
|
133
|
+
const limit = Math.min(
|
|
134
|
+
args.limit || SEARCH_CONFIG.defaultLimit,
|
|
135
|
+
SEARCH_CONFIG.maxLimit,
|
|
136
|
+
);
|
|
137
|
+
const cutoffMs = Date.now() - SEARCH_CONFIG.sinceHours * 3_600_000;
|
|
39
138
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const matches = messageData.filter(
|
|
48
|
-
(m: any) =>
|
|
49
|
-
m.info &&
|
|
50
|
-
JSON.stringify(m.info)
|
|
51
|
-
.toLowerCase()
|
|
52
|
-
.includes(args.query.toLowerCase()),
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
if (matches.length > 0) {
|
|
56
|
-
const excerpt = extractContent(matches[0].info) || "";
|
|
57
|
-
results.push(
|
|
58
|
-
`**${session.id}** - ${session.title || "Untitled"}\n Matches: ${matches.length}\n ${excerpt.substring(0, 100)}...`,
|
|
59
|
-
);
|
|
60
|
-
}
|
|
139
|
+
// Phase 1: Rank sessions by match count + recency
|
|
140
|
+
const likeClauses = words
|
|
141
|
+
.map(
|
|
142
|
+
() =>
|
|
143
|
+
`lower(json_extract(p.data, '$.text')) LIKE ? ESCAPE '\\'`,
|
|
144
|
+
)
|
|
145
|
+
.join(" AND ");
|
|
61
146
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
147
|
+
const rankSql = `
|
|
148
|
+
SELECT s.id AS session_id, s.title, s.directory,
|
|
149
|
+
COUNT(*) AS match_count,
|
|
150
|
+
MAX(p.time_created) AS last_match_ms
|
|
151
|
+
FROM part p
|
|
152
|
+
JOIN message m ON m.id = p.message_id
|
|
153
|
+
JOIN session s ON s.id = p.session_id
|
|
154
|
+
WHERE json_extract(p.data, '$.type') = 'text'
|
|
155
|
+
AND json_extract(p.data, '$.text') IS NOT NULL
|
|
156
|
+
AND json_extract(m.data, '$.role') IN (${ROLE_LIST})
|
|
157
|
+
AND ${likeClauses}
|
|
158
|
+
AND p.time_created >= ?
|
|
159
|
+
GROUP BY s.id
|
|
160
|
+
ORDER BY COUNT(*) DESC, MAX(p.time_created) DESC
|
|
161
|
+
LIMIT ?`;
|
|
162
|
+
|
|
163
|
+
const likeParams = words.map((w) => `%${escapeLike(w)}%`);
|
|
164
|
+
const rankParams = [...likeParams, cutoffMs, limit];
|
|
165
|
+
const sessions = db.prepare(rankSql).all(...rankParams) as Array<{
|
|
166
|
+
session_id: string;
|
|
167
|
+
title: string;
|
|
168
|
+
directory: string;
|
|
169
|
+
match_count: number;
|
|
170
|
+
last_match_ms: number;
|
|
171
|
+
}>;
|
|
172
|
+
|
|
173
|
+
if (sessions.length === 0) {
|
|
174
|
+
return JSON.stringify({
|
|
175
|
+
query: trimmed,
|
|
176
|
+
sessions: [],
|
|
177
|
+
stats: { totalSessions: 0, totalMatches: 0 },
|
|
178
|
+
});
|
|
66
179
|
}
|
|
67
|
-
}
|
|
68
180
|
|
|
69
|
-
|
|
70
|
-
|
|
181
|
+
// Phase 2: Extract snippets per session
|
|
182
|
+
const snippetSql = `
|
|
183
|
+
SELECT p.session_id, p.time_created,
|
|
184
|
+
json_extract(m.data, '$.role') AS role,
|
|
185
|
+
substr(json_extract(p.data, '$.text'), 1, ${SEARCH_CONFIG.snippetLength}) AS snippet
|
|
186
|
+
FROM part p
|
|
187
|
+
JOIN message m ON m.id = p.message_id
|
|
188
|
+
WHERE json_extract(p.data, '$.type') = 'text'
|
|
189
|
+
AND json_extract(p.data, '$.text') IS NOT NULL
|
|
190
|
+
AND json_extract(m.data, '$.role') IN (${ROLE_LIST})
|
|
191
|
+
AND ${likeClauses}
|
|
192
|
+
AND p.time_created >= ?
|
|
193
|
+
AND p.session_id = ?
|
|
194
|
+
ORDER BY p.time_created DESC
|
|
195
|
+
LIMIT ?`;
|
|
196
|
+
|
|
197
|
+
const results = sessions.map((s) => {
|
|
198
|
+
const snippetParams = [
|
|
199
|
+
...likeParams,
|
|
200
|
+
cutoffMs,
|
|
201
|
+
s.session_id,
|
|
202
|
+
SEARCH_CONFIG.snippetsPerSession,
|
|
203
|
+
];
|
|
204
|
+
const snippets = db
|
|
205
|
+
.prepare(snippetSql)
|
|
206
|
+
.all(...snippetParams) as Array<{
|
|
207
|
+
time_created: number;
|
|
208
|
+
role: string;
|
|
209
|
+
snippet: string;
|
|
210
|
+
}>;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
sessionId: s.session_id,
|
|
214
|
+
title: s.title,
|
|
215
|
+
directory: s.directory,
|
|
216
|
+
matchCount: s.match_count,
|
|
217
|
+
lastMatch: formatTime(s.last_match_ms),
|
|
218
|
+
snippets: snippets.map((sn) => ({
|
|
219
|
+
time: formatTime(sn.time_created),
|
|
220
|
+
role: sn.role,
|
|
221
|
+
text: sn.snippet,
|
|
222
|
+
})),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
71
225
|
|
|
72
|
-
|
|
226
|
+
const totalMatches = results.reduce(
|
|
227
|
+
(sum, r) => sum + r.matchCount,
|
|
228
|
+
0,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return JSON.stringify({
|
|
232
|
+
query: trimmed,
|
|
233
|
+
filters: {
|
|
234
|
+
roles: SEARCH_CONFIG.roles,
|
|
235
|
+
sinceHours: SEARCH_CONFIG.sinceHours,
|
|
236
|
+
snippetsPerSession: SEARCH_CONFIG.snippetsPerSession,
|
|
237
|
+
snippetLength: SEARCH_CONFIG.snippetLength,
|
|
238
|
+
},
|
|
239
|
+
stats: {
|
|
240
|
+
totalSessions: results.length,
|
|
241
|
+
totalMatches,
|
|
242
|
+
},
|
|
243
|
+
sessions: results,
|
|
244
|
+
nextStep: {
|
|
245
|
+
message:
|
|
246
|
+
"Use read_session to get the full transcript. Present options to the user with the question tool.",
|
|
247
|
+
suggestedCalls: results.slice(0, 3).map((s) => ({
|
|
248
|
+
tool: "read_session",
|
|
249
|
+
args: {
|
|
250
|
+
session_id: s.sessionId,
|
|
251
|
+
limit: 60,
|
|
252
|
+
order: "asc",
|
|
253
|
+
},
|
|
254
|
+
})),
|
|
255
|
+
suggestedQuestionCall: {
|
|
256
|
+
tool: "question",
|
|
257
|
+
args: {
|
|
258
|
+
questions: [
|
|
259
|
+
{
|
|
260
|
+
header: "Pick session",
|
|
261
|
+
question:
|
|
262
|
+
"Which session should I open for full transcript context?",
|
|
263
|
+
options: results.slice(0, 8).map((s, i) => ({
|
|
264
|
+
label: `${s.sessionId.slice(0, 24)}${i === 0 ? " (Recommended)" : ""}`,
|
|
265
|
+
description:
|
|
266
|
+
s.title || s.directory || `${s.matchCount} matches`,
|
|
267
|
+
})),
|
|
268
|
+
multiple: false,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
} finally {
|
|
276
|
+
db.close();
|
|
277
|
+
}
|
|
73
278
|
},
|
|
74
279
|
}),
|
|
75
280
|
|
|
76
|
-
/** Like AmpCode's read_thread */
|
|
77
281
|
read_session: tool({
|
|
78
282
|
description: `Read session messages.
|
|
79
283
|
|
|
@@ -84,66 +288,141 @@ read_session({ session_id: "abc123", focus: "auth" })`,
|
|
|
84
288
|
session_id: tool.schema.string().describe("Session ID"),
|
|
85
289
|
focus: tool.schema.string().optional().describe("Filter by keyword"),
|
|
86
290
|
},
|
|
87
|
-
async execute(args: {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let summary = `# ${session.data.title || "Untitled"}\n`;
|
|
100
|
-
summary += `ID: ${session.data.id}\n`;
|
|
101
|
-
summary += `Created: ${session.data.time?.created ? new Date(session.data.time.created).toLocaleString() : "Unknown"}\n`;
|
|
102
|
-
summary += `Messages: ${messageData.length}\n\n`;
|
|
103
|
-
|
|
104
|
-
if (args.focus) {
|
|
105
|
-
const focusLower = args.focus.toLowerCase();
|
|
106
|
-
const relevant = messageData.filter(
|
|
107
|
-
(m: any) =>
|
|
108
|
-
m.info &&
|
|
109
|
-
JSON.stringify(m.info).toLowerCase().includes(focusLower),
|
|
110
|
-
);
|
|
111
|
-
summary += `## Matching "${args.focus}" (${relevant.length})\n\n`;
|
|
112
|
-
relevant.slice(0, 5).forEach((m: any, i: number) => {
|
|
113
|
-
summary += `${i + 1}. **${m.info.role}**: ${extractContent(m.info).substring(0, 200)}\n\n`;
|
|
291
|
+
async execute(args: {
|
|
292
|
+
session_id: string;
|
|
293
|
+
focus?: string;
|
|
294
|
+
limit?: number;
|
|
295
|
+
order?: string;
|
|
296
|
+
}) {
|
|
297
|
+
const { db, error } = openReadonlyDb();
|
|
298
|
+
if (!db) {
|
|
299
|
+
return JSON.stringify({
|
|
300
|
+
error: "DB_OPEN_FAILED",
|
|
301
|
+
message: error,
|
|
302
|
+
dbPath: DEFAULT_DB_PATH,
|
|
114
303
|
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Session metadata with project join
|
|
308
|
+
const session = db
|
|
309
|
+
.prepare(
|
|
310
|
+
`SELECT s.id, s.title, s.directory, s.slug,
|
|
311
|
+
s.time_created, s.time_updated,
|
|
312
|
+
p.worktree, p.name AS project_name
|
|
313
|
+
FROM session s
|
|
314
|
+
LEFT JOIN project p ON p.id = s.project_id
|
|
315
|
+
WHERE s.id = ?
|
|
316
|
+
LIMIT 1`,
|
|
317
|
+
)
|
|
318
|
+
.get(args.session_id) as {
|
|
319
|
+
id: string;
|
|
320
|
+
title: string;
|
|
321
|
+
directory: string;
|
|
322
|
+
slug: string;
|
|
323
|
+
time_created: number;
|
|
324
|
+
time_updated: number;
|
|
325
|
+
worktree: string;
|
|
326
|
+
project_name: string;
|
|
327
|
+
} | null;
|
|
328
|
+
|
|
329
|
+
if (!session) {
|
|
330
|
+
return JSON.stringify({
|
|
331
|
+
sessionId: args.session_id,
|
|
332
|
+
found: false,
|
|
333
|
+
message: "Session not found",
|
|
334
|
+
});
|
|
123
335
|
}
|
|
124
336
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
337
|
+
const entryLimit = Math.min(
|
|
338
|
+
args.limit || TRANSCRIPT_CONFIG.defaultLimit,
|
|
339
|
+
TRANSCRIPT_CONFIG.maxLimit,
|
|
128
340
|
);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
341
|
+
const sortOrder = args.order === "desc" ? "DESC" : "ASC";
|
|
342
|
+
|
|
343
|
+
// Build transcript query with optional focus filter
|
|
344
|
+
const params: (string | number)[] = [args.session_id];
|
|
345
|
+
let focusClauses = "";
|
|
346
|
+
|
|
347
|
+
if (args.focus) {
|
|
348
|
+
const words = args.focus
|
|
349
|
+
.toLowerCase()
|
|
350
|
+
.split(/\s+/)
|
|
351
|
+
.filter(Boolean);
|
|
352
|
+
for (const word of words) {
|
|
353
|
+
focusClauses += ` AND lower(json_extract(p.data, '$.text')) LIKE ? ESCAPE '\\'`;
|
|
354
|
+
params.push(`%${escapeLike(word)}%`);
|
|
355
|
+
}
|
|
132
356
|
}
|
|
133
|
-
}
|
|
134
357
|
|
|
135
|
-
|
|
358
|
+
params.push(entryLimit);
|
|
359
|
+
|
|
360
|
+
const entries = db
|
|
361
|
+
.prepare(
|
|
362
|
+
`SELECT p.id AS part_id, p.message_id, p.time_created,
|
|
363
|
+
json_extract(m.data, '$.role') AS role,
|
|
364
|
+
substr(json_extract(p.data, '$.text'), 1, ${TRANSCRIPT_CONFIG.maxCharsPerEntry}) AS text
|
|
365
|
+
FROM part p
|
|
366
|
+
JOIN message m ON m.id = p.message_id
|
|
367
|
+
WHERE p.session_id = ?
|
|
368
|
+
AND json_extract(p.data, '$.type') = 'text'
|
|
369
|
+
AND json_extract(m.data, '$.role') IN (${ROLE_LIST})
|
|
370
|
+
AND json_extract(p.data, '$.text') IS NOT NULL
|
|
371
|
+
AND length(trim(json_extract(p.data, '$.text'))) > 0
|
|
372
|
+
${focusClauses}
|
|
373
|
+
ORDER BY p.time_created ${sortOrder}
|
|
374
|
+
LIMIT ?`,
|
|
375
|
+
)
|
|
376
|
+
.all(...params) as Array<{
|
|
377
|
+
part_id: string;
|
|
378
|
+
message_id: string;
|
|
379
|
+
time_created: number;
|
|
380
|
+
role: string;
|
|
381
|
+
text: string;
|
|
382
|
+
}>;
|
|
383
|
+
|
|
384
|
+
return JSON.stringify({
|
|
385
|
+
sessionId: args.session_id,
|
|
386
|
+
found: true,
|
|
387
|
+
session: {
|
|
388
|
+
title: session.title,
|
|
389
|
+
slug: session.slug,
|
|
390
|
+
directory: session.directory,
|
|
391
|
+
projectName: session.project_name,
|
|
392
|
+
projectWorktree: session.worktree,
|
|
393
|
+
timeCreated: session.time_created
|
|
394
|
+
? formatTime(session.time_created)
|
|
395
|
+
: null,
|
|
396
|
+
timeUpdated: session.time_updated
|
|
397
|
+
? formatTime(session.time_updated)
|
|
398
|
+
: null,
|
|
399
|
+
},
|
|
400
|
+
filters: {
|
|
401
|
+
roles: TRANSCRIPT_CONFIG.roles,
|
|
402
|
+
order: sortOrder.toLowerCase(),
|
|
403
|
+
maxCharsPerEntry: TRANSCRIPT_CONFIG.maxCharsPerEntry,
|
|
404
|
+
...(args.focus ? { focus: args.focus } : {}),
|
|
405
|
+
},
|
|
406
|
+
stats: {
|
|
407
|
+
entriesReturned: entries.length,
|
|
408
|
+
limit: entryLimit,
|
|
409
|
+
},
|
|
410
|
+
entries: entries.map((e) => ({
|
|
411
|
+
partId: e.part_id,
|
|
412
|
+
messageId: e.message_id,
|
|
413
|
+
timeMs: e.time_created,
|
|
414
|
+
time: formatTime(e.time_created),
|
|
415
|
+
role: e.role,
|
|
416
|
+
text: e.text,
|
|
417
|
+
})),
|
|
418
|
+
});
|
|
419
|
+
} finally {
|
|
420
|
+
db.close();
|
|
421
|
+
}
|
|
136
422
|
},
|
|
137
423
|
}),
|
|
138
424
|
},
|
|
139
425
|
};
|
|
140
426
|
};
|
|
141
427
|
|
|
142
|
-
|
|
143
|
-
if (!messageInfo) return "[No info]";
|
|
144
|
-
if (typeof messageInfo.summary === "object" && messageInfo.summary !== null) {
|
|
145
|
-
if (messageInfo.summary.title) return messageInfo.summary.title;
|
|
146
|
-
if (messageInfo.summary.body) return messageInfo.summary.body;
|
|
147
|
-
}
|
|
148
|
-
return "[No content]";
|
|
149
|
-
}
|
|
428
|
+
export default SessionsPlugin;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
3
|
+
"keybinds": {
|
|
4
|
+
"command_list": ";",
|
|
5
|
+
"leader": "`",
|
|
6
|
+
"session_child_cycle": "ctrl+alt+right",
|
|
7
|
+
"session_child_cycle_reverse": "ctrl+alt+left",
|
|
8
|
+
"session_compact": "ctrl+k"
|
|
9
|
+
},
|
|
10
|
+
"scroll_speed": 3,
|
|
11
|
+
"scroll_acceleration": {
|
|
12
|
+
"enabled": true
|
|
13
|
+
},
|
|
14
|
+
"diff_style": "auto"
|
|
15
|
+
}
|
package/package.json
CHANGED