@1agents/session-reader 0.6.2 → 0.7.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/README.md +12 -0
- package/cordis.patch.yml +4 -0
- package/dist/bin/1session.js +26 -1
- package/dist/src/dsh/adapter.d.ts +18 -0
- package/dist/src/dsh/adapter.js +198 -0
- package/dist/src/dsh/client.d.ts +2 -0
- package/dist/src/dsh/client.js +18997 -0
- package/dist/src/dsh/index.d.ts +2 -0
- package/dist/src/dsh/index.js +2 -0
- package/dist/src/dsh/plugin.d.ts +3 -0
- package/dist/src/dsh/plugin.js +392 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/resolver.d.ts +7 -0
- package/dist/src/resolver.js +13 -8
- package/dist/src/store/read.d.ts +6 -2
- package/dist/src/store/read.js +14 -12
- package/dist/src/web/api.d.ts +72 -0
- package/dist/src/web/api.js +126 -0
- package/dist/src/web/app.d.ts +1 -0
- package/dist/src/web/app.js +18975 -0
- package/dist/src/web/server.d.ts +20 -0
- package/dist/src/web/server.js +126 -0
- package/package.json +25 -4
- package/skills/1session/SKILL.md +2 -1
- package/skills/1session/references/cli.md +16 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { resolveSession, searchSessions, buildOverview, summarizeTurns, turnDetail, eventDetails, } from '../index.js';
|
|
3
|
+
import { handleSessionApi } from '../web/api.js';
|
|
4
|
+
import { convertSessionToDshEvents } from './adapter.js';
|
|
5
|
+
export const name = 'session-reader';
|
|
6
|
+
export const inject = ['tools', 'webServer'];
|
|
7
|
+
export function apply(ctx) {
|
|
8
|
+
// 1. Register tools if ctx.tools is present
|
|
9
|
+
if (ctx.tools && typeof ctx.tools.register === 'function') {
|
|
10
|
+
// session_search
|
|
11
|
+
ctx.tools.register({
|
|
12
|
+
name: 'session_search',
|
|
13
|
+
description: 'Search past AI coding sessions across Claude Code, Codex, Antigravity, Grok and DSH. By default searches sessions in the current working directory / workspace.',
|
|
14
|
+
parameters: {
|
|
15
|
+
query: { type: 'string', required: true, description: 'Keywords, file paths, or error messages to search for' },
|
|
16
|
+
workspace: { type: 'string', description: 'Filter by workspace path (optional, defaults to current session workspace)' },
|
|
17
|
+
scope: { type: 'string', description: 'Search scope: cwd (default, current session directory), global (all workspaces), or specific path' },
|
|
18
|
+
since: { type: 'string', description: 'Time filter, e.g. 24h, 7d, 30d' },
|
|
19
|
+
kind: { type: 'string', description: 'Filter by kind: user, assistant, thinking, tool_call, tool_result' },
|
|
20
|
+
limit: { type: 'number', description: 'Max number of sessions to return' },
|
|
21
|
+
},
|
|
22
|
+
output: {
|
|
23
|
+
schema: { type: 'array' },
|
|
24
|
+
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
25
|
+
},
|
|
26
|
+
async execute(args, exec) {
|
|
27
|
+
const currentCwd = exec?.agent?.session?.header?.cwd ?? process.cwd();
|
|
28
|
+
let targetWorkspace;
|
|
29
|
+
if (args.scope === 'global') {
|
|
30
|
+
targetWorkspace = undefined;
|
|
31
|
+
}
|
|
32
|
+
else if (args.workspace) {
|
|
33
|
+
targetWorkspace = args.workspace;
|
|
34
|
+
}
|
|
35
|
+
else if (args.scope && args.scope !== 'cwd') {
|
|
36
|
+
targetWorkspace = args.scope;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Default to current session PWD
|
|
40
|
+
targetWorkspace = currentCwd;
|
|
41
|
+
}
|
|
42
|
+
const hits = await searchSessions(args.query, {
|
|
43
|
+
workspace: targetWorkspace,
|
|
44
|
+
since: args.since,
|
|
45
|
+
kinds: args.kind ? [args.kind] : undefined,
|
|
46
|
+
limit: args.limit,
|
|
47
|
+
});
|
|
48
|
+
return hits.map((h) => ({
|
|
49
|
+
sessionId: h.session.id,
|
|
50
|
+
provider: h.session.provider,
|
|
51
|
+
workspace: h.session.workspace,
|
|
52
|
+
title: h.session.title,
|
|
53
|
+
updatedAt: h.session.updatedAt,
|
|
54
|
+
matchCount: h.matches.length,
|
|
55
|
+
matches: h.matches.slice(0, 5).map((m) => ({
|
|
56
|
+
turn: m.turn,
|
|
57
|
+
kind: m.kind,
|
|
58
|
+
excerpt: m.excerpt,
|
|
59
|
+
})),
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
// session_overview
|
|
64
|
+
ctx.tools.register({
|
|
65
|
+
name: 'session_overview',
|
|
66
|
+
description: 'Get statistics, files modified, commands executed, and end-state facts of a past session.',
|
|
67
|
+
parameters: {
|
|
68
|
+
sessionId: { type: 'string', required: true, description: 'Session ID or 8-char prefix' },
|
|
69
|
+
},
|
|
70
|
+
output: {
|
|
71
|
+
schema: { type: 'object' },
|
|
72
|
+
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
73
|
+
},
|
|
74
|
+
async execute(args) {
|
|
75
|
+
const resolved = await resolveSession(args.sessionId);
|
|
76
|
+
if (!resolved)
|
|
77
|
+
throw new Error(`Session not found: ${args.sessionId}`);
|
|
78
|
+
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
79
|
+
const overview = buildOverview(normalized);
|
|
80
|
+
return {
|
|
81
|
+
id: normalized.ref.id,
|
|
82
|
+
provider: normalized.ref.provider,
|
|
83
|
+
title: normalized.ref.title,
|
|
84
|
+
workspace: normalized.ref.workspace,
|
|
85
|
+
markdown: overview.markdown,
|
|
86
|
+
stats: overview.stats,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
// session_turns
|
|
91
|
+
ctx.tools.register({
|
|
92
|
+
name: 'session_turns',
|
|
93
|
+
description: 'Get turn-by-turn summary of what was discussed and done in a session.',
|
|
94
|
+
parameters: {
|
|
95
|
+
sessionId: { type: 'string', required: true, description: 'Session ID or 8-char prefix' },
|
|
96
|
+
},
|
|
97
|
+
output: {
|
|
98
|
+
schema: { type: 'array' },
|
|
99
|
+
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
100
|
+
},
|
|
101
|
+
async execute(args) {
|
|
102
|
+
const resolved = await resolveSession(args.sessionId);
|
|
103
|
+
if (!resolved)
|
|
104
|
+
throw new Error(`Session not found: ${args.sessionId}`);
|
|
105
|
+
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
106
|
+
return summarizeTurns(normalized);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
// session_turn_detail
|
|
110
|
+
ctx.tools.register({
|
|
111
|
+
name: 'session_turn_detail',
|
|
112
|
+
description: 'Inspect full untruncated events, tool calls, and results for a specific turn in a session.',
|
|
113
|
+
parameters: {
|
|
114
|
+
sessionId: { type: 'string', required: true, description: 'Session ID or 8-char prefix' },
|
|
115
|
+
turn: { type: 'number', required: true, description: 'Turn number' },
|
|
116
|
+
events: { type: 'string', description: 'Event selector, e.g. "11", "11-13", "11,15,22"' },
|
|
117
|
+
},
|
|
118
|
+
output: {
|
|
119
|
+
schema: { oneOf: [{ type: 'object' }, { type: 'array' }] },
|
|
120
|
+
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
121
|
+
},
|
|
122
|
+
async execute(args) {
|
|
123
|
+
const resolved = await resolveSession(args.sessionId);
|
|
124
|
+
if (!resolved)
|
|
125
|
+
throw new Error(`Session not found: ${args.sessionId}`);
|
|
126
|
+
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
127
|
+
if (args.events) {
|
|
128
|
+
return await eventDetails(normalized, args.events);
|
|
129
|
+
}
|
|
130
|
+
return turnDetail(normalized, args.turn);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
// session_open_in_dsh
|
|
134
|
+
ctx.tools.register({
|
|
135
|
+
name: 'session_open_in_dsh',
|
|
136
|
+
description: 'Load a cross-agent historical session into DSH native sessions and automatically assign it to the DSH workspace.',
|
|
137
|
+
parameters: {
|
|
138
|
+
sessionId: { type: 'string', required: true, description: 'Session ID to import into DSH' },
|
|
139
|
+
currentCwd: { type: 'string', description: 'Optional current session working directory fallback' },
|
|
140
|
+
},
|
|
141
|
+
output: {
|
|
142
|
+
schema: { type: 'object' },
|
|
143
|
+
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
144
|
+
},
|
|
145
|
+
async execute(args, exec) {
|
|
146
|
+
const resolved = await resolveSession(args.sessionId);
|
|
147
|
+
if (!resolved)
|
|
148
|
+
throw new Error(`Session not found: ${args.sessionId}`);
|
|
149
|
+
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
150
|
+
const dshEvents = convertSessionToDshEvents(normalized);
|
|
151
|
+
const rawId = normalized.ref.id;
|
|
152
|
+
const sanitizedId = rawId.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
153
|
+
const dshSessionId = sanitizedId.startsWith('session-') ? sanitizedId : `session-${sanitizedId}`;
|
|
154
|
+
const currentSessionCwd = exec?.agent?.session?.header?.cwd ?? args.currentCwd;
|
|
155
|
+
let targetCwd;
|
|
156
|
+
if (normalized.ref.workspace && typeof normalized.ref.workspace === 'string' && normalized.ref.workspace.startsWith('/')) {
|
|
157
|
+
try {
|
|
158
|
+
const st = await fs.promises.stat(normalized.ref.workspace);
|
|
159
|
+
if (st.isDirectory()) {
|
|
160
|
+
targetCwd = await fs.promises.realpath(normalized.ref.workspace);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch { }
|
|
164
|
+
}
|
|
165
|
+
if (!targetCwd) {
|
|
166
|
+
const fallback = currentSessionCwd || process.cwd();
|
|
167
|
+
try {
|
|
168
|
+
const st = await fs.promises.stat(fallback);
|
|
169
|
+
if (st.isDirectory()) {
|
|
170
|
+
targetCwd = await fs.promises.realpath(fallback);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch { }
|
|
174
|
+
}
|
|
175
|
+
if (!targetCwd)
|
|
176
|
+
targetCwd = process.cwd();
|
|
177
|
+
const createdAt = normalized.ref.createdAt ? Date.parse(normalized.ref.createdAt) : Date.now();
|
|
178
|
+
const header = {
|
|
179
|
+
version: 2,
|
|
180
|
+
id: dshSessionId,
|
|
181
|
+
cwd: targetCwd,
|
|
182
|
+
createdAt: isNaN(createdAt) ? Date.now() : createdAt,
|
|
183
|
+
isSeeded: false,
|
|
184
|
+
delegationDepth: 0,
|
|
185
|
+
};
|
|
186
|
+
const persistence = ctx.get ? ctx.get('sessionPersistence') : ctx.sessionPersistence;
|
|
187
|
+
if (persistence && typeof persistence.create === 'function') {
|
|
188
|
+
try {
|
|
189
|
+
const existingStat = await persistence.stat(dshSessionId);
|
|
190
|
+
if (!existingStat) {
|
|
191
|
+
const handle = await persistence.create(header);
|
|
192
|
+
await handle.append(dshEvents);
|
|
193
|
+
await handle.flush();
|
|
194
|
+
await handle.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
console.warn('[session-reader] persistence notice:', e?.message || e);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const sessions = ctx.get ? ctx.get('sessions') : ctx.sessions;
|
|
202
|
+
if (sessions && typeof sessions.create === 'function') {
|
|
203
|
+
try {
|
|
204
|
+
if (!sessions.get(dshSessionId)) {
|
|
205
|
+
sessions.create(dshSessionId, { seed: dshEvents, meta: header });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
console.warn('[session-reader] sessions.create notice:', e?.message || e);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const workspaceRegistry = ctx.get ? (ctx.get('workspaceRegistry') ?? ctx.workspaceRegistry) : ctx.workspaceRegistry;
|
|
213
|
+
let attachedWorkspaceId;
|
|
214
|
+
if (workspaceRegistry && typeof workspaceRegistry.create === 'function') {
|
|
215
|
+
try {
|
|
216
|
+
const ws = await workspaceRegistry.create(targetCwd);
|
|
217
|
+
if (ws && typeof ws.attachSession === 'function') {
|
|
218
|
+
await ws.attachSession(dshSessionId);
|
|
219
|
+
attachedWorkspaceId = ws.id;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch (wsErr) {
|
|
223
|
+
console.warn('[session-reader] workspace attach notice:', wsErr?.message || wsErr);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
dshSessionId,
|
|
229
|
+
eventCount: dshEvents.length,
|
|
230
|
+
title: normalized.ref.title,
|
|
231
|
+
workspace: targetCwd,
|
|
232
|
+
workspaceId: attachedWorkspaceId,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// 2. Register HTTP routes if ctx.webServer is present
|
|
238
|
+
if (ctx.webServer && typeof ctx.webServer.register === 'function') {
|
|
239
|
+
ctx.webServer.register({
|
|
240
|
+
kind: 'prefix',
|
|
241
|
+
path: '/api/session-reader',
|
|
242
|
+
handler: async (req, res) => {
|
|
243
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
244
|
+
const pathname = url.pathname.replace(/^\/api\/session-reader/, '');
|
|
245
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
246
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
247
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
248
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
249
|
+
if (req.method === 'OPTIONS') {
|
|
250
|
+
res.statusCode = 204;
|
|
251
|
+
res.end();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
if (pathname === '/open-in-dsh') {
|
|
256
|
+
let sessionId = url.searchParams.get('id');
|
|
257
|
+
let currentCwd = url.searchParams.get('currentCwd') ?? undefined;
|
|
258
|
+
if (!sessionId && (req.method === 'POST' || req.method === 'PUT')) {
|
|
259
|
+
const body = await new Promise((resolve) => {
|
|
260
|
+
let data = '';
|
|
261
|
+
req.on('data', chunk => data += chunk);
|
|
262
|
+
req.on('end', () => {
|
|
263
|
+
try {
|
|
264
|
+
resolve(JSON.parse(data));
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
resolve({});
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
req.on('error', () => resolve({}));
|
|
271
|
+
});
|
|
272
|
+
sessionId = body?.sessionId || body?.id;
|
|
273
|
+
currentCwd = body?.currentCwd || currentCwd;
|
|
274
|
+
}
|
|
275
|
+
if (!sessionId) {
|
|
276
|
+
res.statusCode = 400;
|
|
277
|
+
res.end(JSON.stringify({ error: 'Missing sessionId' }));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const resolved = await resolveSession(sessionId);
|
|
281
|
+
if (!resolved) {
|
|
282
|
+
res.statusCode = 404;
|
|
283
|
+
res.end(JSON.stringify({ error: `Session not found: ${sessionId}` }));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const normalized = await resolved.adapter.parse(resolved.candidate);
|
|
287
|
+
const dshEvents = convertSessionToDshEvents(normalized);
|
|
288
|
+
const rawId = normalized.ref.id;
|
|
289
|
+
const sanitizedId = rawId.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
290
|
+
const dshSessionId = sanitizedId.startsWith('session-') ? sanitizedId : `session-${sanitizedId}`;
|
|
291
|
+
let targetCwd;
|
|
292
|
+
if (normalized.ref.workspace && typeof normalized.ref.workspace === 'string' && normalized.ref.workspace.startsWith('/')) {
|
|
293
|
+
try {
|
|
294
|
+
const st = await fs.promises.stat(normalized.ref.workspace);
|
|
295
|
+
if (st.isDirectory()) {
|
|
296
|
+
targetCwd = await fs.promises.realpath(normalized.ref.workspace);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch { }
|
|
300
|
+
}
|
|
301
|
+
if (!targetCwd) {
|
|
302
|
+
const fallback = currentCwd || process.cwd();
|
|
303
|
+
try {
|
|
304
|
+
const st = await fs.promises.stat(fallback);
|
|
305
|
+
if (st.isDirectory()) {
|
|
306
|
+
targetCwd = await fs.promises.realpath(fallback);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch { }
|
|
310
|
+
}
|
|
311
|
+
if (!targetCwd)
|
|
312
|
+
targetCwd = process.cwd();
|
|
313
|
+
const createdAt = normalized.ref.createdAt ? Date.parse(normalized.ref.createdAt) : Date.now();
|
|
314
|
+
const header = {
|
|
315
|
+
version: 2,
|
|
316
|
+
id: dshSessionId,
|
|
317
|
+
cwd: targetCwd,
|
|
318
|
+
createdAt: isNaN(createdAt) ? Date.now() : createdAt,
|
|
319
|
+
isSeeded: false,
|
|
320
|
+
delegationDepth: 0,
|
|
321
|
+
};
|
|
322
|
+
// 1. Persistence if available
|
|
323
|
+
const persistence = ctx.get ? ctx.get('sessionPersistence') : ctx.sessionPersistence;
|
|
324
|
+
if (persistence && typeof persistence.create === 'function') {
|
|
325
|
+
try {
|
|
326
|
+
const existingStat = await persistence.stat(dshSessionId);
|
|
327
|
+
if (!existingStat) {
|
|
328
|
+
const handle = await persistence.create(header);
|
|
329
|
+
await handle.append(dshEvents);
|
|
330
|
+
await handle.flush();
|
|
331
|
+
await handle.close();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (persistErr) {
|
|
335
|
+
console.warn('[session-reader] persistence notice:', persistErr?.message || persistErr);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// 2. In-memory sessions if available
|
|
339
|
+
const sessions = ctx.get ? ctx.get('sessions') : ctx.sessions;
|
|
340
|
+
if (sessions && typeof sessions.create === 'function') {
|
|
341
|
+
try {
|
|
342
|
+
if (!sessions.get(dshSessionId)) {
|
|
343
|
+
sessions.create(dshSessionId, {
|
|
344
|
+
seed: dshEvents,
|
|
345
|
+
meta: header,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (sessionErr) {
|
|
350
|
+
console.warn('[session-reader] sessions.create notice:', sessionErr?.message || sessionErr);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// 3. Workspace attachment if workspaceRegistry is available
|
|
354
|
+
const workspaceRegistry = ctx.get ? (ctx.get('workspaceRegistry') ?? ctx.workspaceRegistry) : ctx.workspaceRegistry;
|
|
355
|
+
let attachedWorkspaceId;
|
|
356
|
+
if (workspaceRegistry && typeof workspaceRegistry.create === 'function') {
|
|
357
|
+
try {
|
|
358
|
+
const ws = await workspaceRegistry.create(targetCwd);
|
|
359
|
+
if (ws && typeof ws.attachSession === 'function') {
|
|
360
|
+
await ws.attachSession(dshSessionId);
|
|
361
|
+
attachedWorkspaceId = ws.id;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (wsErr) {
|
|
365
|
+
console.warn('[session-reader] workspace attach notice:', wsErr?.message || wsErr);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
res.statusCode = 200;
|
|
369
|
+
res.end(JSON.stringify({
|
|
370
|
+
success: true,
|
|
371
|
+
dshSessionId,
|
|
372
|
+
eventCount: dshEvents.length,
|
|
373
|
+
title: normalized.ref.title,
|
|
374
|
+
workspace: targetCwd,
|
|
375
|
+
workspaceId: attachedWorkspaceId,
|
|
376
|
+
}));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const handled = await handleSessionApi(pathname, url, res, { fallbackCwd: process.cwd() });
|
|
380
|
+
if (handled)
|
|
381
|
+
return;
|
|
382
|
+
res.statusCode = 404;
|
|
383
|
+
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
res.statusCode = 500;
|
|
387
|
+
res.end(JSON.stringify({ error: err?.message ?? String(err) }));
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -20,3 +20,5 @@ export { EDGE_VERSION, EXTRACTOR_VERSION, PARSER_VERSION, SCHEMA_VERSION, } from
|
|
|
20
20
|
export { SKILL_NAME, agentTargets, bundledSkillDir, describeState, installSkill, skillStatus, uninstallSkill, type AgentStatus, type AgentTarget, type EntryState, type InstallMode, type InstallOptions, type InstallResult, type SkillAgent, type UninstallResult, } from './skill.js';
|
|
21
21
|
export { buildManifest, nodeIdentity, sessionUri, SESSION_CAPABILITIES, type AccessDescriptor, type NetworkService, type NodeManifest, } from './serve/node.js';
|
|
22
22
|
export { createServer, serve, type ServeOptions } from './serve/http.js';
|
|
23
|
+
export { convertSessionToDshEvents, type DshSessionEvent, type ConvertOptions } from './dsh/adapter.js';
|
|
24
|
+
export { apply, inject, name } from './dsh/index.js';
|
package/dist/src/index.js
CHANGED
|
@@ -18,3 +18,5 @@ export { EDGE_VERSION, EXTRACTOR_VERSION, PARSER_VERSION, SCHEMA_VERSION, } from
|
|
|
18
18
|
export { SKILL_NAME, agentTargets, bundledSkillDir, describeState, installSkill, skillStatus, uninstallSkill, } from './skill.js';
|
|
19
19
|
export { buildManifest, nodeIdentity, sessionUri, SESSION_CAPABILITIES, } from './serve/node.js';
|
|
20
20
|
export { createServer, serve } from './serve/http.js';
|
|
21
|
+
export { convertSessionToDshEvents } from './dsh/adapter.js';
|
|
22
|
+
export { apply, inject, name } from './dsh/index.js';
|
package/dist/src/resolver.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ export interface ListOptions {
|
|
|
10
10
|
* after the first run. Set false to read the source files directly.
|
|
11
11
|
*/
|
|
12
12
|
useIndex?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When true (default), fingerprint-sweep every on-disk candidate so the
|
|
15
|
+
* listing is complete and fresh. Set false to query the index only — no
|
|
16
|
+
* parse, no all-files sweep. The DSH panel uses this so opening it does
|
|
17
|
+
* not load every session; click a row to load that one.
|
|
18
|
+
*/
|
|
19
|
+
refresh?: boolean;
|
|
13
20
|
/**
|
|
14
21
|
* How many session files may be opened. Defaults to a small budget so an
|
|
15
22
|
* unfiltered `list` stays cheap; pass `Infinity` when completeness matters
|
package/dist/src/resolver.js
CHANGED
|
@@ -107,6 +107,18 @@ async function listIndexedSessions(options) {
|
|
|
107
107
|
const { listSessionRows, refOf } = await import('./store/read.js');
|
|
108
108
|
const db = await openStore();
|
|
109
109
|
const sinceMs = parseSince(options.since);
|
|
110
|
+
const scoped = options.workspace ? canonicalizePath(options.workspace) : undefined;
|
|
111
|
+
const query = {
|
|
112
|
+
...(scoped && scoped !== '/' ? { workspace: scoped } : {}),
|
|
113
|
+
...(options.provider ? { provider: options.provider } : {}),
|
|
114
|
+
...(sinceMs ? { sinceMs } : {}),
|
|
115
|
+
limit: options.limit ?? 20,
|
|
116
|
+
};
|
|
117
|
+
// Skip the all-files fingerprint sweep: the caller only wants whatever
|
|
118
|
+
// the index already knows. Used by the DSH panel's lazy listing.
|
|
119
|
+
if (options.refresh === false) {
|
|
120
|
+
return listSessionRows(db, query).map(refOf);
|
|
121
|
+
}
|
|
110
122
|
const ids = [];
|
|
111
123
|
for (const adapter of adapters) {
|
|
112
124
|
if (options.provider && adapter.provider !== options.provider)
|
|
@@ -119,14 +131,7 @@ async function listIndexedSessions(options) {
|
|
|
119
131
|
ids.push(result.id);
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
|
-
|
|
123
|
-
return listSessionRows(db, {
|
|
124
|
-
ids,
|
|
125
|
-
...(scoped && scoped !== '/' ? { workspace: scoped } : {}),
|
|
126
|
-
...(options.provider ? { provider: options.provider } : {}),
|
|
127
|
-
...(sinceMs ? { sinceMs } : {}),
|
|
128
|
-
limit: options.limit ?? 20,
|
|
129
|
-
}).map(refOf);
|
|
134
|
+
return listSessionRows(db, { ids, ...query }).map(refOf);
|
|
130
135
|
}
|
|
131
136
|
export async function findSessionsByWorkspace(workspacePath, options = {}) {
|
|
132
137
|
return listRecentSessions({ ...options, limit: options.limit ?? 50, workspace: workspacePath });
|
package/dist/src/store/read.d.ts
CHANGED
|
@@ -26,8 +26,12 @@ export declare function sessionRow(db: DatabaseSync, id: string): SessionRow | u
|
|
|
26
26
|
/** Resolves a full id, an id prefix, or a native id against the index. */
|
|
27
27
|
export declare function findSessionRow(db: DatabaseSync, needle: string): SessionRow | undefined;
|
|
28
28
|
export interface ListQuery {
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Ids seen on disk during this sweep; rows outside it are stale and skipped.
|
|
31
|
+
* Omit to list from the whole index (lazy UI listings that skip the sweep).
|
|
32
|
+
* An explicit empty array still means "nothing in scope".
|
|
33
|
+
*/
|
|
34
|
+
ids?: string[];
|
|
31
35
|
/** Canonical root; matches the directory itself and everything under it. */
|
|
32
36
|
workspace?: string;
|
|
33
37
|
provider?: string;
|
package/dist/src/store/read.js
CHANGED
|
@@ -22,15 +22,18 @@ export function findSessionRow(db, needle) {
|
|
|
22
22
|
* workspace never silently loses its older sessions to a candidate cap.
|
|
23
23
|
*/
|
|
24
24
|
export function listSessionRows(db, query) {
|
|
25
|
-
|
|
26
|
-
return [];
|
|
27
|
-
db.exec('DROP TABLE IF EXISTS temp.list_scope');
|
|
28
|
-
db.exec('CREATE TEMP TABLE list_scope (id TEXT PRIMARY KEY)');
|
|
29
|
-
const insert = db.prepare('INSERT OR IGNORE INTO temp.list_scope (id) VALUES (?)');
|
|
30
|
-
for (const id of query.ids)
|
|
31
|
-
insert.run(id);
|
|
32
|
-
const where = ['id IN (SELECT id FROM temp.list_scope)'];
|
|
25
|
+
const where = [];
|
|
33
26
|
const params = [];
|
|
27
|
+
if (query.ids) {
|
|
28
|
+
if (!query.ids.length)
|
|
29
|
+
return [];
|
|
30
|
+
db.exec('DROP TABLE IF EXISTS temp.list_scope');
|
|
31
|
+
db.exec('CREATE TEMP TABLE list_scope (id TEXT PRIMARY KEY)');
|
|
32
|
+
const insert = db.prepare('INSERT OR IGNORE INTO temp.list_scope (id) VALUES (?)');
|
|
33
|
+
for (const id of query.ids)
|
|
34
|
+
insert.run(id);
|
|
35
|
+
where.push('id IN (SELECT id FROM temp.list_scope)');
|
|
36
|
+
}
|
|
34
37
|
if (query.workspace) {
|
|
35
38
|
// Prefix comparison rather than LIKE: real paths contain `_`, a LIKE wildcard.
|
|
36
39
|
where.push('(workspace = ? OR substr(workspace, 1, ?) = ?)');
|
|
@@ -45,11 +48,10 @@ export function listSessionRows(db, query) {
|
|
|
45
48
|
params.push(query.sinceMs);
|
|
46
49
|
}
|
|
47
50
|
params.push(query.limit ?? 20);
|
|
48
|
-
|
|
49
|
-
.prepare(`SELECT * FROM sessions WHERE ${where.join(' AND ')} ` +
|
|
51
|
+
const sql = `SELECT * FROM sessions${where.length ? ` WHERE ${where.join(' AND ')}` : ''} ` +
|
|
50
52
|
// Undated rows sort last instead of first, which is where DESC puts NULL.
|
|
51
|
-
'ORDER BY (ended_at IS NULL), ended_at DESC LIMIT ?'
|
|
52
|
-
|
|
53
|
+
'ORDER BY (ended_at IS NULL), ended_at DESC LIMIT ?';
|
|
54
|
+
return db.prepare(sql).all(...params);
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
55
57
|
* Turn starts for one indexed session, without rebuilding it.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ServerResponse } from 'node:http';
|
|
2
|
+
import type { NormalizedSession } from '../types.js';
|
|
3
|
+
export interface SessionApiOptions {
|
|
4
|
+
/** Used when the client asks for scope=cwd but does not send ?workspace=. */
|
|
5
|
+
fallbackCwd: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveListWorkspace(scope: string | null, workspace: string | null, fallbackCwd: string): string | undefined;
|
|
8
|
+
/** Chat-preview turns. Built only after the user clicks a session. */
|
|
9
|
+
export declare function previewTurns(normalized: NormalizedSession): {
|
|
10
|
+
no: number;
|
|
11
|
+
turn: number;
|
|
12
|
+
status: import("../types.js").TurnStatus;
|
|
13
|
+
prompt: string;
|
|
14
|
+
userPrompt: string;
|
|
15
|
+
outcome: string;
|
|
16
|
+
assistantReply: string;
|
|
17
|
+
thinking: string;
|
|
18
|
+
thinkingBlocks: string[];
|
|
19
|
+
toolCalls: any[];
|
|
20
|
+
files: string[];
|
|
21
|
+
commands: number;
|
|
22
|
+
errors: number;
|
|
23
|
+
startedAt: string | undefined;
|
|
24
|
+
endedAt: string | undefined;
|
|
25
|
+
durationMs: number | undefined;
|
|
26
|
+
}[];
|
|
27
|
+
export declare function sessionPreview(sessionId: string): Promise<{
|
|
28
|
+
ref: import("../types.js").SessionRef;
|
|
29
|
+
stats: import("../types.js").ProviderStats;
|
|
30
|
+
overview: import("../types.js").SessionOverview;
|
|
31
|
+
turns: {
|
|
32
|
+
no: number;
|
|
33
|
+
turn: number;
|
|
34
|
+
status: import("../types.js").TurnStatus;
|
|
35
|
+
prompt: string;
|
|
36
|
+
userPrompt: string;
|
|
37
|
+
outcome: string;
|
|
38
|
+
assistantReply: string;
|
|
39
|
+
thinking: string;
|
|
40
|
+
thinkingBlocks: string[];
|
|
41
|
+
toolCalls: any[];
|
|
42
|
+
files: string[];
|
|
43
|
+
commands: number;
|
|
44
|
+
errors: number;
|
|
45
|
+
startedAt: string | undefined;
|
|
46
|
+
endedAt: string | undefined;
|
|
47
|
+
durationMs: number | undefined;
|
|
48
|
+
}[];
|
|
49
|
+
files: {
|
|
50
|
+
displayPath: string;
|
|
51
|
+
path: string;
|
|
52
|
+
host?: string;
|
|
53
|
+
operation: string;
|
|
54
|
+
turn: number;
|
|
55
|
+
eventIndex: number;
|
|
56
|
+
timestamp?: string;
|
|
57
|
+
provenance: import("../types.js").Provenance;
|
|
58
|
+
extractor: string;
|
|
59
|
+
group: import("../types.js").FileGroup;
|
|
60
|
+
}[];
|
|
61
|
+
}>;
|
|
62
|
+
export declare function listSessionsForUi(options: {
|
|
63
|
+
limit?: number;
|
|
64
|
+
provider?: string;
|
|
65
|
+
since?: string;
|
|
66
|
+
workspace?: string;
|
|
67
|
+
}): Promise<import("../types.js").SessionRef[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Shared JSON API used by the DSH plugin and `1session web`.
|
|
70
|
+
* Path is already stripped of `/api/session-reader`. Returns false if unmatched.
|
|
71
|
+
*/
|
|
72
|
+
export declare function handleSessionApi(pathname: string, url: URL, res: ServerResponse, options: SessionApiOptions): Promise<boolean>;
|