dsh-reference-anything 0.3.0 → 0.3.2
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/LICENSE +22 -22
- package/NOTICE.md +52 -25
- package/README.md +339 -286
- package/cordis.patch.yml +89 -18
- package/lib/client.js +13538 -591
- package/lib/contract.d.ts +155 -16
- package/lib/contract.js +68 -7
- package/lib/download-directory.d.ts +18 -0
- package/lib/download-directory.js +55 -0
- package/lib/errors.d.ts +4 -0
- package/lib/host.d.ts +96 -8
- package/lib/host.js +88 -19
- package/lib/index.d.ts +22 -2
- package/lib/index.js +57 -7
- package/lib/opencli.d.ts +5 -3
- package/lib/opencli.js +13 -6
- package/lib/openlist/host.d.ts +157 -0
- package/lib/openlist/host.js +873 -0
- package/lib/openlist/index.d.ts +84 -0
- package/lib/openlist/index.js +507 -0
- package/lib/render.d.ts +12 -7
- package/lib/render.js +27 -21
- package/lib/sources/cloud-drive/cache.d.ts +60 -0
- package/lib/sources/cloud-drive/cache.js +107 -0
- package/lib/sources/cloud-drive/index.d.ts +190 -0
- package/lib/sources/cloud-drive/index.js +526 -0
- package/lib/sources/cloud-drive/providers/http.d.ts +27 -0
- package/lib/sources/cloud-drive/providers/http.js +73 -0
- package/lib/sources/cloud-drive/providers/openlist.d.ts +59 -0
- package/lib/sources/cloud-drive/providers/openlist.js +535 -0
- package/lib/sources/cloud-drive/registry.d.ts +53 -0
- package/lib/sources/cloud-drive/registry.js +101 -0
- package/lib/sources/cloud-drive/types.d.ts +169 -0
- package/lib/sources/cloud-drive/types.js +17 -0
- package/lib/sources/file.d.ts +2 -0
- package/lib/sources/file.js +2 -0
- package/lib/sources/local-agent/adapters/claude-code.d.ts +16 -0
- package/lib/sources/local-agent/adapters/claude-code.js +171 -0
- package/lib/sources/local-agent/adapters/codex.d.ts +20 -0
- package/lib/sources/local-agent/adapters/codex.js +204 -0
- package/lib/sources/local-agent/adapters/cursor.d.ts +23 -0
- package/lib/sources/local-agent/adapters/cursor.js +121 -0
- package/lib/sources/local-agent/adapters/gemini-cli.d.ts +20 -0
- package/lib/sources/local-agent/adapters/gemini-cli.js +141 -0
- package/lib/sources/local-agent/adapters/grokbuild.d.ts +22 -0
- package/lib/sources/local-agent/adapters/grokbuild.js +76 -0
- package/lib/sources/local-agent/adapters/hermes.d.ts +19 -0
- package/lib/sources/local-agent/adapters/hermes.js +85 -0
- package/lib/sources/local-agent/adapters/kimi.d.ts +28 -0
- package/lib/sources/local-agent/adapters/kimi.js +261 -0
- package/lib/sources/local-agent/adapters/openclaw.d.ts +21 -0
- package/lib/sources/local-agent/adapters/openclaw.js +120 -0
- package/lib/sources/local-agent/adapters/opencode.d.ts +44 -0
- package/lib/sources/local-agent/adapters/opencode.js +236 -0
- package/lib/sources/local-agent/adapters/pi.d.ts +26 -0
- package/lib/sources/local-agent/adapters/pi.js +242 -0
- package/lib/sources/local-agent/adapters/qoder.d.ts +19 -0
- package/lib/sources/local-agent/adapters/qoder.js +93 -0
- package/lib/sources/local-agent/adapters/reasonix.d.ts +23 -0
- package/lib/sources/local-agent/adapters/reasonix.js +90 -0
- package/lib/sources/local-agent/adapters/shared.d.ts +163 -0
- package/lib/sources/local-agent/adapters/shared.js +415 -0
- package/lib/sources/local-agent/adapters/sqlite-shared.d.ts +92 -0
- package/lib/sources/local-agent/adapters/sqlite-shared.js +215 -0
- package/lib/sources/local-agent/adapters/zcode.d.ts +41 -0
- package/lib/sources/local-agent/adapters/zcode.js +159 -0
- package/lib/sources/local-agent/index.d.ts +222 -0
- package/lib/sources/local-agent/index.js +656 -0
- package/lib/sources/local-agent/page.d.ts +55 -0
- package/lib/sources/local-agent/page.js +83 -0
- package/lib/sources/local-agent/path.d.ts +8 -0
- package/lib/sources/local-agent/path.js +43 -0
- package/lib/sources/local-agent/registry.d.ts +68 -0
- package/lib/sources/local-agent/registry.js +116 -0
- package/lib/sources/local-agent/scan.d.ts +175 -0
- package/lib/sources/local-agent/scan.js +472 -0
- package/lib/sources/local-agent/sqlite.d.ts +45 -0
- package/lib/sources/local-agent/sqlite.js +113 -0
- package/lib/sources/local-agent/store.d.ts +93 -0
- package/lib/sources/local-agent/store.js +113 -0
- package/lib/sources/local-agent/types.d.ts +338 -0
- package/lib/sources/local-agent/types.js +20 -0
- package/lib/sources/web-chat/index.d.ts +5 -1
- package/lib/sources/web-chat/index.js +47 -8
- package/lib/store/spec.d.ts +50 -4
- package/lib/store/spec.js +2 -2
- package/lib/sync/index.d.ts +1 -0
- package/lib/sync/index.js +28 -8
- package/lib/tool.d.ts +5 -0
- package/lib/tool.js +418 -45
- package/lib/typert.js +13 -2
- package/lib/types.d.ts +28 -2
- package/lib/update.d.ts +6 -0
- package/lib/update.js +29 -1
- package/lib/web.js +5 -1
- package/lib/wire.d.ts +80 -9
- package/lib/wire.js +30 -4
- package/opencli-plugin/chatgpt.js +30 -27
- package/opencli-plugin/claude.js +12 -8
- package/opencli-plugin/common.js +266 -187
- package/opencli-plugin/deepseek.js +8 -5
- package/opencli-plugin/gemini.js +42 -38
- package/opencli-plugin/grok.js +22 -21
- package/opencli-plugin/kimi.js +34 -31
- package/opencli-plugin/opencli-plugin.json +6 -6
- package/opencli-plugin/package.json +8 -8
- package/package.json +182 -167
- package/lib/profile-mode.d.ts +0 -30
- package/lib/profile-mode.js +0 -90
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent title bookmarks for local agent transcripts.
|
|
3
|
+
*
|
|
4
|
+
* The `@` menu fires per keystroke against a corpus measured here at 2.9 GB
|
|
5
|
+
* across 753 files. Reading even a bounded probe from each of them on that path
|
|
6
|
+
* is out of the question, so what a probe learned once is written down: after
|
|
7
|
+
* the first sighting, a transcript renders from this table with no file read at
|
|
8
|
+
* all.
|
|
9
|
+
*
|
|
10
|
+
* A bookmark is valid only when **both** `mtimeMs` and `size` still match a
|
|
11
|
+
* fresh stat. Either alone is forgeable by ordinary activity — an editor can
|
|
12
|
+
* rewrite a file to the same length, and a filesystem with coarse timestamps
|
|
13
|
+
* can leave mtime unmoved across an append within the same tick.
|
|
14
|
+
*
|
|
15
|
+
* This opens its **own** domain rather than adding a table to
|
|
16
|
+
* `reference_anything`. `DomainService.open()` throws `already-open`, and the
|
|
17
|
+
* web-chat service already holds that domain open; sharing it would make this
|
|
18
|
+
* plugin unloadable without that one.
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-reference-anything/local-agent/store
|
|
21
|
+
*/
|
|
22
|
+
import type { Domain, KvTable } from '@deepseek-ai/dsh-storage-domain';
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
/** What one probe learned about a transcript, and the stat it was true for. */
|
|
25
|
+
export declare const agentBookmarkSchema: z.ZodObject<{
|
|
26
|
+
kind: z.ZodString;
|
|
27
|
+
relPath: z.ZodString;
|
|
28
|
+
mtimeMs: z.ZodNumber;
|
|
29
|
+
size: z.ZodNumber;
|
|
30
|
+
title: z.ZodDefault<z.ZodString>;
|
|
31
|
+
cwd: z.ZodDefault<z.ZodString>;
|
|
32
|
+
firstPrompt: z.ZodDefault<z.ZodString>;
|
|
33
|
+
createdAt: z.ZodDefault<z.ZodNumber>;
|
|
34
|
+
empty: z.ZodDefault<z.ZodBoolean>;
|
|
35
|
+
indexedAt: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export type AgentBookmark = z.infer<typeof agentBookmarkSchema>;
|
|
38
|
+
/** Storage for this plugin alone, versioned independently of the web-chat one. */
|
|
39
|
+
export declare const localAgentDomainSpec: {
|
|
40
|
+
name: string;
|
|
41
|
+
version: number;
|
|
42
|
+
tables: {
|
|
43
|
+
bookmarks: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
|
|
44
|
+
kind: string;
|
|
45
|
+
relPath: string;
|
|
46
|
+
mtimeMs: number;
|
|
47
|
+
size: number;
|
|
48
|
+
title: string;
|
|
49
|
+
cwd: string;
|
|
50
|
+
firstPrompt: string;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
empty: boolean;
|
|
53
|
+
indexedAt: number;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
type LocalAgentDomain = Domain<typeof localAgentDomainSpec>;
|
|
58
|
+
/** Reads and writes transcript bookmarks. */
|
|
59
|
+
export declare class AgentBookmarkStore {
|
|
60
|
+
readonly domain: LocalAgentDomain;
|
|
61
|
+
readonly bookmarks: KvTable<string, AgentBookmark>;
|
|
62
|
+
constructor(domain: LocalAgentDomain);
|
|
63
|
+
/**
|
|
64
|
+
* Key one transcript.
|
|
65
|
+
*
|
|
66
|
+
* `NUL` separates the two halves because it is the one byte a path cannot
|
|
67
|
+
* contain, so no `kind`/`relPath` pair can collide with another.
|
|
68
|
+
*/
|
|
69
|
+
static key(kind: string, relPath: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Look up a bookmark that still describes the file on disk.
|
|
72
|
+
* @param kind - the agent whose transcript this is.
|
|
73
|
+
* @param relPath - its root-relative path.
|
|
74
|
+
* @param mtimeMs - modification time from a fresh stat.
|
|
75
|
+
* @param size - size in bytes from the same stat.
|
|
76
|
+
* @returns the bookmark, or undefined when absent or stale.
|
|
77
|
+
*/
|
|
78
|
+
fresh(kind: string, relPath: string, mtimeMs: number, size: number): AgentBookmark | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Record what a probe learned.
|
|
81
|
+
* @param bookmark - the bookmark to persist, already stamped with its stat.
|
|
82
|
+
*/
|
|
83
|
+
remember(bookmark: AgentBookmark): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Drop bookmarks for transcripts that are no longer on disk.
|
|
86
|
+
*
|
|
87
|
+
* Without this the table grows for the life of the installation, since a
|
|
88
|
+
* deleted transcript is simply never asked about again.
|
|
89
|
+
* @param live - keys currently present, from a directory scan.
|
|
90
|
+
*/
|
|
91
|
+
forgetMissing(live: ReadonlySet<string>): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent title bookmarks for local agent transcripts.
|
|
3
|
+
*
|
|
4
|
+
* The `@` menu fires per keystroke against a corpus measured here at 2.9 GB
|
|
5
|
+
* across 753 files. Reading even a bounded probe from each of them on that path
|
|
6
|
+
* is out of the question, so what a probe learned once is written down: after
|
|
7
|
+
* the first sighting, a transcript renders from this table with no file read at
|
|
8
|
+
* all.
|
|
9
|
+
*
|
|
10
|
+
* A bookmark is valid only when **both** `mtimeMs` and `size` still match a
|
|
11
|
+
* fresh stat. Either alone is forgeable by ordinary activity — an editor can
|
|
12
|
+
* rewrite a file to the same length, and a filesystem with coarse timestamps
|
|
13
|
+
* can leave mtime unmoved across an append within the same tick.
|
|
14
|
+
*
|
|
15
|
+
* This opens its **own** domain rather than adding a table to
|
|
16
|
+
* `reference_anything`. `DomainService.open()` throws `already-open`, and the
|
|
17
|
+
* web-chat service already holds that domain open; sharing it would make this
|
|
18
|
+
* plugin unloadable without that one.
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-reference-anything/local-agent/store
|
|
21
|
+
*/
|
|
22
|
+
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
/** What one probe learned about a transcript, and the stat it was true for. */
|
|
25
|
+
export const agentBookmarkSchema = z.object({
|
|
26
|
+
/** Agent whose transcript this is; a string here so an unknown kind parses. */
|
|
27
|
+
kind: z.string(),
|
|
28
|
+
/** Path relative to its root — the stable half of the reference id. */
|
|
29
|
+
relPath: z.string(),
|
|
30
|
+
/** Stat this bookmark was derived from; both must match to trust it. */
|
|
31
|
+
mtimeMs: z.number(),
|
|
32
|
+
size: z.number().int().nonnegative(),
|
|
33
|
+
/** Display name. Empty means the probe found none and the caller falls back. */
|
|
34
|
+
title: z.string().default(''),
|
|
35
|
+
/** Recorded working directory, for workspace scoping. */
|
|
36
|
+
cwd: z.string().default(''),
|
|
37
|
+
/** Opening prompt, matched during ranking alongside the title. */
|
|
38
|
+
firstPrompt: z.string().default(''),
|
|
39
|
+
/** When the transcript was started, in Unix epoch milliseconds; 0 if unknown. */
|
|
40
|
+
createdAt: z.number().default(0),
|
|
41
|
+
/**
|
|
42
|
+
* The whole transcript was read and folded to no turns at all.
|
|
43
|
+
*
|
|
44
|
+
* Aborted sessions are common — a window opened and `/clear`ed leaves a file
|
|
45
|
+
* of pure CLI plumbing, 12 of 249 transcripts on the machine this was
|
|
46
|
+
* measured against. Listing one offers the user something that can only fail
|
|
47
|
+
* when picked, so discovery drops it. Defaults false so a bookmark written
|
|
48
|
+
* before this field existed reads as "not known to be empty" and still lists.
|
|
49
|
+
*/
|
|
50
|
+
empty: z.boolean().default(false),
|
|
51
|
+
/** When this bookmark was written, in Unix epoch milliseconds. */
|
|
52
|
+
indexedAt: z.number().default(0),
|
|
53
|
+
});
|
|
54
|
+
/** Storage for this plugin alone, versioned independently of the web-chat one. */
|
|
55
|
+
export const localAgentDomainSpec = defineDomain({
|
|
56
|
+
name: 'reference_local_agents',
|
|
57
|
+
version: 1,
|
|
58
|
+
tables: {
|
|
59
|
+
bookmarks: domainTable(agentBookmarkSchema),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
/** Reads and writes transcript bookmarks. */
|
|
63
|
+
export class AgentBookmarkStore {
|
|
64
|
+
domain;
|
|
65
|
+
bookmarks;
|
|
66
|
+
constructor(domain) {
|
|
67
|
+
this.domain = domain;
|
|
68
|
+
this.bookmarks = domain.table('bookmarks');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Key one transcript.
|
|
72
|
+
*
|
|
73
|
+
* `NUL` separates the two halves because it is the one byte a path cannot
|
|
74
|
+
* contain, so no `kind`/`relPath` pair can collide with another.
|
|
75
|
+
*/
|
|
76
|
+
static key(kind, relPath) {
|
|
77
|
+
return `${kind}\0${relPath}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Look up a bookmark that still describes the file on disk.
|
|
81
|
+
* @param kind - the agent whose transcript this is.
|
|
82
|
+
* @param relPath - its root-relative path.
|
|
83
|
+
* @param mtimeMs - modification time from a fresh stat.
|
|
84
|
+
* @param size - size in bytes from the same stat.
|
|
85
|
+
* @returns the bookmark, or undefined when absent or stale.
|
|
86
|
+
*/
|
|
87
|
+
fresh(kind, relPath, mtimeMs, size) {
|
|
88
|
+
const row = this.bookmarks.get(AgentBookmarkStore.key(kind, relPath));
|
|
89
|
+
if (row === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
return row.mtimeMs === mtimeMs && row.size === size ? row : undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Record what a probe learned.
|
|
95
|
+
* @param bookmark - the bookmark to persist, already stamped with its stat.
|
|
96
|
+
*/
|
|
97
|
+
async remember(bookmark) {
|
|
98
|
+
await this.bookmarks.put(AgentBookmarkStore.key(bookmark.kind, bookmark.relPath), bookmark);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Drop bookmarks for transcripts that are no longer on disk.
|
|
102
|
+
*
|
|
103
|
+
* Without this the table grows for the life of the installation, since a
|
|
104
|
+
* deleted transcript is simply never asked about again.
|
|
105
|
+
* @param live - keys currently present, from a directory scan.
|
|
106
|
+
*/
|
|
107
|
+
async forgetMissing(live) {
|
|
108
|
+
for (const [key] of this.bookmarks.entries()) {
|
|
109
|
+
if (!live.has(key))
|
|
110
|
+
await this.bookmarks.delete(key);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocabulary for reading the transcripts other coding agents leave on disk.
|
|
3
|
+
*
|
|
4
|
+
* Every supported agent writes its own record shape, but all of them are
|
|
5
|
+
* append-only logs of one conversation, so the plugin folds each into the same
|
|
6
|
+
* two-role projection the rest of the package already speaks. The types here
|
|
7
|
+
* are deliberately free of `node:fs` and cordis: an adapter is a pure fold over
|
|
8
|
+
* records, which is what lets the streaming reader in `scan.ts` bound its
|
|
9
|
+
* memory and lets the tests drive an adapter from an array of literals.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-reference-anything/local-agent/types
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* One supported on-disk transcript format.
|
|
15
|
+
*
|
|
16
|
+
* Named after the agent rather than the encoding, because two agents that both
|
|
17
|
+
* write JSONL still disagree about every field inside it.
|
|
18
|
+
*/
|
|
19
|
+
export type AgentKind =
|
|
20
|
+
/** Claude Code, `~/.claude/projects/<slug>/<uuid>.jsonl`. */
|
|
21
|
+
'claude-code'
|
|
22
|
+
/** Codex CLI rollouts, `~/.codex/sessions/<y>/<m>/<d>/rollout-*.jsonl`. */
|
|
23
|
+
| 'codex'
|
|
24
|
+
/** Cursor agent transcripts, `~/.cursor/projects/<slug>/agent-transcripts/<id>/<id>.jsonl`. */
|
|
25
|
+
| 'cursor'
|
|
26
|
+
/** Qoder, `~/.qoder/projects/<slug>/<sessionId>.jsonl`. */
|
|
27
|
+
| 'qoder'
|
|
28
|
+
/** Reasonix, `~/.reasonix/sessions/<stem>.jsonl`. */
|
|
29
|
+
| 'reasonix'
|
|
30
|
+
/** OpenClaw, `~/.openclaw/agents/<agent>/sessions/<id>.jsonl`. */
|
|
31
|
+
| 'openclaw'
|
|
32
|
+
/** Kimi CLI, `~/.kimi/sessions/**\/wire.jsonl` and `~/.kimi-code/sessions/**\/wire.jsonl`. */
|
|
33
|
+
| 'kimi'
|
|
34
|
+
/** Grok Build, `~/.grok/sessions/<slug>/<id>/chat_history.jsonl`. */
|
|
35
|
+
| 'grokbuild'
|
|
36
|
+
/** Hermes, `~/.hermes/sessions/<id>.jsonl`. */
|
|
37
|
+
| 'hermes'
|
|
38
|
+
/** Gemini CLI, `~/.gemini/history/<slot>/chats/session-*.json` — one JSON document. */
|
|
39
|
+
| 'gemini-cli'
|
|
40
|
+
/** Pi, `~/.pi/agent/sessions/--<cwd>--/<stamp>_<uuid>.jsonl` — a branch tree. */
|
|
41
|
+
| 'pi'
|
|
42
|
+
/** opencode, `~/.local/share/opencode/opencode.db` — SQLite, many sessions in one file. */
|
|
43
|
+
| 'opencode'
|
|
44
|
+
/** mimocode, `~/.local/share/mimocode/mimocode.db` — an opencode fork, same schema. */
|
|
45
|
+
| 'mimocode'
|
|
46
|
+
/** zcode, `~/.zcode/cli/db/db.sqlite` — SQLite with its own session shape. */
|
|
47
|
+
| 'zcode';
|
|
48
|
+
/**
|
|
49
|
+
* How much of a tool call and its result survive projection.
|
|
50
|
+
*
|
|
51
|
+
* A coding transcript is mostly tool traffic, so this is the single knob with
|
|
52
|
+
* the largest effect on what a read costs: `'drop'` hides that any work
|
|
53
|
+
* happened, `'full'` keeps the exact recorded arguments, `'summarize'` bounds
|
|
54
|
+
* them, and `'elide'` keeps the shape of the work at a fixed ~15 bytes.
|
|
55
|
+
*/
|
|
56
|
+
export type ToolCallMode = 'full' | 'elide' | 'summarize' | 'drop';
|
|
57
|
+
/** How a transcript is projected into turns; fixed for the whole of one read. */
|
|
58
|
+
export interface ConvertOptions {
|
|
59
|
+
/** Keep assistant reasoning blocks, which are usually noise and sometimes the answer. */
|
|
60
|
+
readonly includeThinking: boolean;
|
|
61
|
+
/** How much of each tool call and result survives. */
|
|
62
|
+
readonly toolCalls: ToolCallMode;
|
|
63
|
+
/** How much of each tool result survives. */
|
|
64
|
+
readonly toolResults: ToolCallMode;
|
|
65
|
+
/** Keep records belonging to a spawned subagent rather than the main thread. */
|
|
66
|
+
readonly includeSidechains: boolean;
|
|
67
|
+
/** Drop harness-injected preambles that were never typed by a person. */
|
|
68
|
+
readonly stripEnvironmentPreamble: boolean;
|
|
69
|
+
/** Longest single tool-call or result summary, in characters, under `'summarize'`. */
|
|
70
|
+
readonly toolSummaryChars: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* One projected turn.
|
|
74
|
+
*
|
|
75
|
+
* Structurally a {@link ConversationItem} minus attachments, kept separate so
|
|
76
|
+
* adapters stay independent of the package's public wire types.
|
|
77
|
+
*/
|
|
78
|
+
export interface ParsedTurn {
|
|
79
|
+
readonly role: 'user' | 'assistant';
|
|
80
|
+
/** Visible text; never empty, because an empty turn tells a reader nothing. */
|
|
81
|
+
readonly text: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fold state carried across one file's records.
|
|
85
|
+
*
|
|
86
|
+
* Small and concrete on purpose. An opaque per-adapter bag would push the
|
|
87
|
+
* assistant-run merging rule — the one rule that decides what "a turn" even
|
|
88
|
+
* means — into each adapter separately. Each field below is here because a
|
|
89
|
+
* shipped adapter needs it, not because a future one might.
|
|
90
|
+
*/
|
|
91
|
+
export interface AdapterState {
|
|
92
|
+
/**
|
|
93
|
+
* Fragments of the assistant run still open.
|
|
94
|
+
*
|
|
95
|
+
* Agents emit text, reasoning, and each tool call as separate records, so a
|
|
96
|
+
* single reply arrives as a run of ten or twenty of them. They are buffered
|
|
97
|
+
* here and joined into one turn when the next user turn arrives or the file
|
|
98
|
+
* ends; without that, a turn limit would page through tool plumbing.
|
|
99
|
+
*/
|
|
100
|
+
readonly pending: string[];
|
|
101
|
+
/**
|
|
102
|
+
* Raw records a whole-document format is holding until the file ends.
|
|
103
|
+
*
|
|
104
|
+
* Most formats decide each turn from one record and never touch this. Three
|
|
105
|
+
* cannot: Gemini CLI writes a single JSON object per session, Pi stores a
|
|
106
|
+
* branch tree whose active path is only knowable from the last entry
|
|
107
|
+
* backwards, and both must therefore see everything before emitting
|
|
108
|
+
* anything. They accumulate here and emit the whole conversation from
|
|
109
|
+
* {@link TranscriptAdapter.flush}. Bounded by `maxScanBytes`, which is what
|
|
110
|
+
* {@link TranscriptAdapter.document} exists to enforce.
|
|
111
|
+
*/
|
|
112
|
+
readonly held: string[];
|
|
113
|
+
/**
|
|
114
|
+
* One-shot markers for folds a single record cannot decide.
|
|
115
|
+
*
|
|
116
|
+
* Kimi's newer wire format, for one, carries a `context.append_message` that
|
|
117
|
+
* is a real user turn only when no `turn.prompt` has opened one yet — a fact
|
|
118
|
+
* about the records already seen, not about this record. Keys are private to
|
|
119
|
+
* the adapter that writes them.
|
|
120
|
+
*/
|
|
121
|
+
readonly seen: Set<string>;
|
|
122
|
+
/**
|
|
123
|
+
* A compaction boundary was crossed.
|
|
124
|
+
*
|
|
125
|
+
* What came before it is genuinely gone from the file, so this becomes the
|
|
126
|
+
* snapshot's `partial` flag rather than merely its `hasOlder`.
|
|
127
|
+
*/
|
|
128
|
+
compacted: boolean;
|
|
129
|
+
}
|
|
130
|
+
/** What a bounded head-and-tail probe can learn without reading the whole file. */
|
|
131
|
+
export interface TranscriptHead {
|
|
132
|
+
/** Best available human-facing name; absent when the probe found none. */
|
|
133
|
+
readonly title?: string;
|
|
134
|
+
/** Working directory the agent recorded, used to scope listing to this workspace. */
|
|
135
|
+
readonly cwd?: string;
|
|
136
|
+
/** The conversation's opening user prompt, for ranking and for a title fallback. */
|
|
137
|
+
readonly firstPrompt?: string;
|
|
138
|
+
/** Session start in Unix epoch milliseconds, when the format records one. */
|
|
139
|
+
readonly createdAt?: number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A pure reader for one transcript format.
|
|
143
|
+
*
|
|
144
|
+
* The interface is line-local rather than whole-file because a transcript can
|
|
145
|
+
* be hundreds of megabytes: `read()` streams and folds, holding only the
|
|
146
|
+
* requested window. A whole-file entry point is then a trivial loop over
|
|
147
|
+
* {@link TranscriptAdapter.step}, which is what the tests use.
|
|
148
|
+
*/
|
|
149
|
+
export interface TranscriptAdapter {
|
|
150
|
+
/** Registry key; also the `kind` half of every reference id this adapter owns. */
|
|
151
|
+
readonly kind: AgentKind;
|
|
152
|
+
/** Name shown beside a candidate in the `@` menu. */
|
|
153
|
+
readonly displayName: string;
|
|
154
|
+
/**
|
|
155
|
+
* Where this agent keeps its transcripts.
|
|
156
|
+
* @param home - the user's home directory.
|
|
157
|
+
* @returns absolute directories to walk, most canonical first.
|
|
158
|
+
*/
|
|
159
|
+
defaultRoots(home: string): readonly string[];
|
|
160
|
+
/**
|
|
161
|
+
* Whether a discovered file could be this format, from its path alone.
|
|
162
|
+
* @param relativePath - path below the root, using forward slashes.
|
|
163
|
+
* @returns whether the file is worth opening. No I/O.
|
|
164
|
+
*/
|
|
165
|
+
matches(relativePath: string): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Whether this format must be read whole before it yields any turn.
|
|
168
|
+
*
|
|
169
|
+
* A line-local adapter folds each record as it arrives and can be read from
|
|
170
|
+
* a byte region at the end of a file it never fully opens. A document
|
|
171
|
+
* adapter cannot: half of a JSON object is not a smaller JSON object, and
|
|
172
|
+
* half of a branch tree is not a shorter conversation. Marking it here lets
|
|
173
|
+
* the reader refuse an oversized file with a reason instead of streaming it
|
|
174
|
+
* to a silent zero turns.
|
|
175
|
+
*/
|
|
176
|
+
readonly document?: boolean;
|
|
177
|
+
/** Fresh fold state for one file. */
|
|
178
|
+
createState(): AdapterState;
|
|
179
|
+
/**
|
|
180
|
+
* Fold one record into zero or more completed turns.
|
|
181
|
+
*
|
|
182
|
+
* Returns only turns that are *finished*: an assistant run stays buffered in
|
|
183
|
+
* `state.pending` until something closes it, so a caller that stops early
|
|
184
|
+
* must call {@link TranscriptAdapter.flush}.
|
|
185
|
+
* @param line - one raw line; malformed JSON is skipped, never thrown.
|
|
186
|
+
* @param state - fold state, mutated in place.
|
|
187
|
+
* @param options - projection settings.
|
|
188
|
+
* @returns turns completed by this record, in order.
|
|
189
|
+
*/
|
|
190
|
+
step(line: string, state: AdapterState, options: ConvertOptions): readonly ParsedTurn[];
|
|
191
|
+
/**
|
|
192
|
+
* Close the transcript.
|
|
193
|
+
*
|
|
194
|
+
* For a line-local adapter this only closes the trailing assistant run. For
|
|
195
|
+
* a {@link TranscriptAdapter.document} one it is where the entire fold
|
|
196
|
+
* happens, which is why it takes the projection settings: a document adapter
|
|
197
|
+
* that defaulted them would ignore `includeThinking` and `toolCalls`.
|
|
198
|
+
* @param state - fold state, mutated in place.
|
|
199
|
+
* @param options - projection settings, the same ones `step` was given.
|
|
200
|
+
* @returns the turns this closed, in order.
|
|
201
|
+
*/
|
|
202
|
+
flush(state: AdapterState, options: ConvertOptions): readonly ParsedTurn[];
|
|
203
|
+
/**
|
|
204
|
+
* Derive metadata from bounded slices of the file's start and end.
|
|
205
|
+
* @param headLines - complete lines from the start of the file.
|
|
206
|
+
* @param tailLines - complete lines from the end of the file.
|
|
207
|
+
* @returns what those slices could establish; every field optional.
|
|
208
|
+
*/
|
|
209
|
+
head(headLines: readonly string[], tailLines: readonly string[]): TranscriptHead;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The narrow slice of SQLite a database-backed adapter is allowed to use.
|
|
213
|
+
*
|
|
214
|
+
* Deliberately not `DatabaseSync`. Keeping the adapters behind this interface
|
|
215
|
+
* is what lets them stay in the same pure tier as the line-local ones: they
|
|
216
|
+
* import no `node:sqlite`, so they load on a Node that has none, and a test can
|
|
217
|
+
* drive one from a literal table instead of a temporary file. It is read-only
|
|
218
|
+
* by construction — there is no `run`, so no adapter can write to a database
|
|
219
|
+
* the user's own agent owns.
|
|
220
|
+
*/
|
|
221
|
+
export interface SqliteReader {
|
|
222
|
+
/**
|
|
223
|
+
* Run one parameterised query.
|
|
224
|
+
* @param sql - the statement; every value must come through `params`.
|
|
225
|
+
* @param params - bound values, in order.
|
|
226
|
+
* @returns every row, as plain objects keyed by column name.
|
|
227
|
+
*/
|
|
228
|
+
all(sql: string, ...params: readonly (string | number)[]): readonly Record<string, unknown>[];
|
|
229
|
+
/**
|
|
230
|
+
* Column names of one table, for probing between schema variants.
|
|
231
|
+
*
|
|
232
|
+
* Two of the three supported databases are the same project one fork apart
|
|
233
|
+
* and differ by a single column, so asking is cheaper than maintaining a
|
|
234
|
+
* version table that neither of them writes.
|
|
235
|
+
* @param table - the table to describe.
|
|
236
|
+
* @returns its column names; empty when the table does not exist.
|
|
237
|
+
*/
|
|
238
|
+
columns(table: string): ReadonlySet<string>;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* One conversation inside a database that holds many.
|
|
242
|
+
*
|
|
243
|
+
* This is what a directory scan learns about a file for the line-local
|
|
244
|
+
* formats, except that here it comes back from an indexed query rather than
|
|
245
|
+
* from a bounded read of the file's two ends — so there is nothing to amortize
|
|
246
|
+
* and no bookmark to write.
|
|
247
|
+
*/
|
|
248
|
+
export interface TranscriptSession {
|
|
249
|
+
/** Primary key inside its database; the second half of the reference id. */
|
|
250
|
+
readonly id: string;
|
|
251
|
+
/** Human-facing name the agent recorded; empty when it recorded none. */
|
|
252
|
+
readonly title: string;
|
|
253
|
+
/** Working directory the session ran in, for workspace scoping; may be empty. */
|
|
254
|
+
readonly cwd: string;
|
|
255
|
+
/** Session start in Unix epoch milliseconds; 0 when unknown. */
|
|
256
|
+
readonly createdAt: number;
|
|
257
|
+
/** Last activity in Unix epoch milliseconds, used to order the menu. */
|
|
258
|
+
readonly updatedAt: number;
|
|
259
|
+
}
|
|
260
|
+
/** One conversation folded out of a database. */
|
|
261
|
+
export interface SessionTurns {
|
|
262
|
+
readonly items: readonly ParsedTurn[];
|
|
263
|
+
/** Older messages were left unread because the record cap was reached. */
|
|
264
|
+
readonly truncated: boolean;
|
|
265
|
+
/** The agent compacted this session, so what came before is gone from the database too. */
|
|
266
|
+
readonly compacted: boolean;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* A pure reader for one database-backed transcript format.
|
|
270
|
+
*
|
|
271
|
+
* Separate from {@link TranscriptAdapter} rather than a mode of it, because the
|
|
272
|
+
* two disagree about what a transcript even is. A JSONL file is one
|
|
273
|
+
* conversation and can be folded a record at a time from any byte offset; a
|
|
274
|
+
* SQLite database is many conversations and is only addressable by query. The
|
|
275
|
+
* shared parts — the kind, where it lives, how a turn is shaped — stay shared;
|
|
276
|
+
* the fold does not.
|
|
277
|
+
*/
|
|
278
|
+
export interface QueryAdapter {
|
|
279
|
+
/** Registry key; also the `kind` half of every reference id this adapter owns. */
|
|
280
|
+
readonly kind: AgentKind;
|
|
281
|
+
/** Name shown beside a candidate in the `@` menu. */
|
|
282
|
+
readonly displayName: string;
|
|
283
|
+
/** Discriminant: marks this adapter as database-backed rather than line-local. */
|
|
284
|
+
readonly query: true;
|
|
285
|
+
/**
|
|
286
|
+
* Directories holding this agent's database.
|
|
287
|
+
* @param home - the user's home directory.
|
|
288
|
+
* @returns absolute directories to walk, most canonical first.
|
|
289
|
+
*/
|
|
290
|
+
defaultRoots(home: string): readonly string[];
|
|
291
|
+
/**
|
|
292
|
+
* Whether a discovered file is this agent's database, from its path alone.
|
|
293
|
+
*
|
|
294
|
+
* Must reject SQLite's `-wal` and `-shm` sidecars, which sit beside the
|
|
295
|
+
* database and are not databases.
|
|
296
|
+
* @param relativePath - path below the root, using forward slashes.
|
|
297
|
+
* @returns whether the file is worth opening. No I/O.
|
|
298
|
+
*/
|
|
299
|
+
matches(relativePath: string): boolean;
|
|
300
|
+
/**
|
|
301
|
+
* Every conversation the database holds, newest activity first.
|
|
302
|
+
* @param db - a read-only reader over the database.
|
|
303
|
+
* @param limit - most sessions to return.
|
|
304
|
+
* @returns the sessions, already filtered of whatever this format considers noise.
|
|
305
|
+
*/
|
|
306
|
+
sessions(db: SqliteReader, limit: number): readonly TranscriptSession[];
|
|
307
|
+
/**
|
|
308
|
+
* Describe one conversation by id.
|
|
309
|
+
*
|
|
310
|
+
* Needed because a database-backed reference is read without a listing having
|
|
311
|
+
* happened first — the model may hold an id from a previous turn — and the
|
|
312
|
+
* row that names a session is the only thing that can label it. Unlike
|
|
313
|
+
* {@link sessions} this applies no discovery filter: a caller holding an id
|
|
314
|
+
* has already got past whatever the menu chose not to show.
|
|
315
|
+
* @param db - a read-only reader over the database.
|
|
316
|
+
* @param sessionId - the conversation to describe.
|
|
317
|
+
* @returns its row, or undefined when the database has no such session.
|
|
318
|
+
*/
|
|
319
|
+
session(db: SqliteReader, sessionId: string): TranscriptSession | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* Fold one conversation into turns, oldest first.
|
|
322
|
+
* @param db - a read-only reader over the database.
|
|
323
|
+
* @param sessionId - the conversation to read.
|
|
324
|
+
* @param options - projection settings.
|
|
325
|
+
* @param maxRecords - most message rows to materialize; older ones are dropped first,
|
|
326
|
+
* which mirrors what the line-local reader does when a file is too large to stream.
|
|
327
|
+
* @returns the turns, and what was lost getting them.
|
|
328
|
+
*/
|
|
329
|
+
turns(db: SqliteReader, sessionId: string, options: ConvertOptions, maxRecords: number): SessionTurns;
|
|
330
|
+
}
|
|
331
|
+
/** Either kind of reader, as discovery and reading hold them. */
|
|
332
|
+
export type AgentAdapter = TranscriptAdapter | QueryAdapter;
|
|
333
|
+
/**
|
|
334
|
+
* Narrow an adapter to the database-backed shape.
|
|
335
|
+
* @param adapter - either kind of reader.
|
|
336
|
+
* @returns whether it reads a database rather than a line-local file.
|
|
337
|
+
*/
|
|
338
|
+
export declare function isQueryAdapter(adapter: AgentAdapter): adapter is QueryAdapter;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocabulary for reading the transcripts other coding agents leave on disk.
|
|
3
|
+
*
|
|
4
|
+
* Every supported agent writes its own record shape, but all of them are
|
|
5
|
+
* append-only logs of one conversation, so the plugin folds each into the same
|
|
6
|
+
* two-role projection the rest of the package already speaks. The types here
|
|
7
|
+
* are deliberately free of `node:fs` and cordis: an adapter is a pure fold over
|
|
8
|
+
* records, which is what lets the streaming reader in `scan.ts` bound its
|
|
9
|
+
* memory and lets the tests drive an adapter from an array of literals.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-reference-anything/local-agent/types
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Narrow an adapter to the database-backed shape.
|
|
15
|
+
* @param adapter - either kind of reader.
|
|
16
|
+
* @returns whether it reads a database rather than a line-local file.
|
|
17
|
+
*/
|
|
18
|
+
export function isQueryAdapter(adapter) {
|
|
19
|
+
return adapter.query === true;
|
|
20
|
+
}
|
|
@@ -48,10 +48,14 @@ export default class WebChatHistoryService extends Service implements ReferenceS
|
|
|
48
48
|
static inject: string[];
|
|
49
49
|
static Config: z<Config>;
|
|
50
50
|
readonly id = "web-chat";
|
|
51
|
+
/** Mirrored browser chats are outside the task's workspace: read only what the user named. */
|
|
52
|
+
readonly requiresGrant = true;
|
|
51
53
|
private storeValue?;
|
|
52
54
|
private syncValue?;
|
|
53
55
|
private autoSyncInterval?;
|
|
54
56
|
private autoSyncCatchUp?;
|
|
57
|
+
private settingsUpdateQueue;
|
|
58
|
+
private validateDownloadDirectoryValue;
|
|
55
59
|
private readonly packageUpdates;
|
|
56
60
|
private readonly liveAttachments;
|
|
57
61
|
constructor(ctx: Context, config?: Config);
|
|
@@ -80,7 +84,6 @@ export default class WebChatHistoryService extends Service implements ReferenceS
|
|
|
80
84
|
/** Permanently delete a synced conversation; refused while a sync could resurrect it mid-delete. */
|
|
81
85
|
remove(uriId: string): Promise<boolean>;
|
|
82
86
|
removeProvider(provider: ChatProvider): Promise<number>;
|
|
83
|
-
removeOlderThan(days: number): Promise<number>;
|
|
84
87
|
removeRemoteMissing(): Promise<number>;
|
|
85
88
|
removeOldAccounts(): Promise<number>;
|
|
86
89
|
/** Durable per-provider sync status, independent of any single in-flight job. */
|
|
@@ -93,6 +96,7 @@ export default class WebChatHistoryService extends Service implements ReferenceS
|
|
|
93
96
|
discoverOpenCli(signal?: AbortSignal): Promise<OpenCliDiscovery>;
|
|
94
97
|
installOpenCli(signal?: AbortSignal): Promise<OpenCliDiscovery>;
|
|
95
98
|
updateSettings(value: unknown): Promise<SettingsRecord>;
|
|
99
|
+
private applySettings;
|
|
96
100
|
health(signal?: AbortSignal): Promise<import("../../opencli.ts").OpenCliHealth>;
|
|
97
101
|
quickHealth(signal?: AbortSignal): Promise<import("../../opencli.ts").OpenCliHealth>;
|
|
98
102
|
profiles(signal?: AbortSignal): Promise<import("../../opencli.ts").BrowserProfile[]>;
|