dsh-reference-anything 0.2.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.
Files changed (66) hide show
  1. package/LICENSE +22 -0
  2. package/NOTICE.md +54 -0
  3. package/README.md +228 -0
  4. package/cordis.patch.yml +63 -0
  5. package/images/logo.png +0 -0
  6. package/lib/client/index.d.ts +3 -0
  7. package/lib/client.js +18151 -0
  8. package/lib/contract.d.ts +300 -0
  9. package/lib/contract.js +102 -0
  10. package/lib/errors.d.ts +56 -0
  11. package/lib/errors.js +21 -0
  12. package/lib/host.d.ts +102 -0
  13. package/lib/host.js +55 -0
  14. package/lib/index.d.ts +83 -0
  15. package/lib/index.js +153 -0
  16. package/lib/opencli.d.ts +116 -0
  17. package/lib/opencli.js +356 -0
  18. package/lib/reference.d.ts +49 -0
  19. package/lib/reference.js +228 -0
  20. package/lib/render.d.ts +101 -0
  21. package/lib/render.js +250 -0
  22. package/lib/retain.d.ts +35 -0
  23. package/lib/retain.js +116 -0
  24. package/lib/search.d.ts +75 -0
  25. package/lib/search.js +126 -0
  26. package/lib/serialize.d.ts +22 -0
  27. package/lib/serialize.js +24 -0
  28. package/lib/sources/file.d.ts +100 -0
  29. package/lib/sources/file.js +236 -0
  30. package/lib/sources/web-chat/index.d.ts +114 -0
  31. package/lib/sources/web-chat/index.js +359 -0
  32. package/lib/store/spec.d.ts +312 -0
  33. package/lib/store/spec.js +94 -0
  34. package/lib/store/store.d.ts +189 -0
  35. package/lib/store/store.js +638 -0
  36. package/lib/sync/index.d.ts +91 -0
  37. package/lib/sync/index.js +425 -0
  38. package/lib/tool.d.ts +62 -0
  39. package/lib/tool.js +364 -0
  40. package/lib/typert.d.ts +2 -0
  41. package/lib/typert.js +34 -0
  42. package/lib/types.d.ts +215 -0
  43. package/lib/types.js +12 -0
  44. package/lib/uri-codec.d.ts +5 -0
  45. package/lib/uri-codec.js +39 -0
  46. package/lib/uri.d.ts +59 -0
  47. package/lib/uri.js +86 -0
  48. package/lib/web.d.ts +4 -0
  49. package/lib/web.js +11 -0
  50. package/lib/window.d.ts +23 -0
  51. package/lib/window.js +35 -0
  52. package/lib/wire.d.ts +117 -0
  53. package/lib/wire.js +49 -0
  54. package/lib/workspace.d.ts +18 -0
  55. package/lib/workspace.js +81 -0
  56. package/opencli-plugin/chatgpt.js +125 -0
  57. package/opencli-plugin/claude.js +134 -0
  58. package/opencli-plugin/common.js +307 -0
  59. package/opencli-plugin/deepseek.js +152 -0
  60. package/opencli-plugin/gemini.js +165 -0
  61. package/opencli-plugin/grok.js +123 -0
  62. package/opencli-plugin/kimi.js +123 -0
  63. package/opencli-plugin/opencli-plugin.json +6 -0
  64. package/opencli-plugin/package.json +8 -0
  65. package/opencli-plugin/since-guard.js +45 -0
  66. package/package.json +180 -0
@@ -0,0 +1,359 @@
1
+ import { Context, Service } from '@deepseek-ai/cordis';
2
+ import z from '@deepseek-ai/schemastery';
3
+ import { providerSchema, referenceAnythingDomainSpec, settingsRecordSchema } from "../../store/spec.js";
4
+ import { ConversationStore } from "../../store/store.js";
5
+ import { ConversationSyncManager } from "../../sync/index.js";
6
+ import { OpenCliError, OpenCliRunner, discoverOpenCli, installOpenCli as installOpenCliPackage } from "../../opencli.js";
7
+ import { ReferenceAnythingError } from "../../errors.js";
8
+ import { parseProviderQuery } from "../../search.js";
9
+ export { parseProviderQuery };
10
+ export const name = 'reference-web-chat';
11
+ export const inject = ['references', 'storageDomain'];
12
+ /** Window a catch-up pass waits in after arming, so it misses the startup rush. */
13
+ const CATCH_UP_MIN_MS = 30_000;
14
+ const CATCH_UP_MAX_MS = 90_000;
15
+ export const Config = z.object({
16
+ timeoutMs: z.number().step(1).min(1_000).default(60_000),
17
+ maxStdoutBytes: z.number().step(1).min(65_536).default(32 * 1024 * 1024),
18
+ });
19
+ const PROVIDERS = ['chatgpt', 'claude', 'gemini', 'deepseek', 'grok', 'kimi'];
20
+ export default class WebChatHistoryService extends Service {
21
+ config;
22
+ static inject = inject;
23
+ static Config = Config;
24
+ id = 'web-chat';
25
+ storeValue;
26
+ syncValue;
27
+ autoSyncInterval;
28
+ autoSyncCatchUp;
29
+ liveAttachments = new Map();
30
+ constructor(ctx, config = {}) {
31
+ super(ctx, 'referenceChatHistory');
32
+ this.config = config;
33
+ }
34
+ async [Service.init]() {
35
+ const domain = await this.ctx.storageDomain.open(referenceAnythingDomainSpec);
36
+ this.ctx.effect(() => () => domain.close(), 'reference-web-chat.domainClose');
37
+ this.storeValue = new ConversationStore(domain);
38
+ // The schema default also applies to installations created before modes
39
+ // existed. Enforce that default immediately so legacy mirrored bodies do
40
+ // not survive silently under a metadata-only setting.
41
+ if (this.store.settings.historyMode === 'metadata-only') {
42
+ await this.store.setSettings(this.store.settings);
43
+ await this.store.clearMirrorContent();
44
+ }
45
+ this.syncValue = new ConversationSyncManager(this.storeValue, () => new OpenCliRunner({
46
+ executable: this.store.settings.opencliPath, profile: this.store.settings.profile,
47
+ timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes,
48
+ }), this.ctx.logger);
49
+ await this.store.collectExpired();
50
+ this.ctx.references.registerSource(this);
51
+ this.ctx.effect(() => () => { this.clearAutoSync(); }, 'reference-web-chat.autoSyncCleanup');
52
+ this.armAutoSync();
53
+ }
54
+ get store() {
55
+ if (!this.storeValue)
56
+ throw new Error('Web chat history service is not initialized');
57
+ return this.storeValue;
58
+ }
59
+ get sync() {
60
+ if (!this.syncValue)
61
+ throw new Error('Web chat history service is not initialized');
62
+ return this.syncValue;
63
+ }
64
+ async available() { return this.storeValue !== undefined; }
65
+ async list(query, limit) {
66
+ const parsed = parseProviderQuery(query);
67
+ // No excerpt here: this surface feeds the model, and mirrored conversation
68
+ // text may only reach it inside `reference_read`'s untrusted-data envelope.
69
+ return this.store.list(parsed.query, parsed.provider, limit).map(({ key, row }) => ({
70
+ ref: { source: this.id, id: key }, label: row.title, origin: row.url,
71
+ updatedAt: timestamp(row.updatedAt), provider: row.provider,
72
+ messageCount: row.messageCount, partial: row.partial, syncedAt: timestamp(row.syncedAt),
73
+ }));
74
+ }
75
+ async read(ref, window, signal) {
76
+ if (ref.source !== this.id)
77
+ throw new Error(`web-chat cannot read source ${ref.source}`);
78
+ if (this.store.settings.historyMode === 'metadata-only')
79
+ return this.readRemote(ref.id, window, signal);
80
+ return this.store.read(ref.id, window);
81
+ }
82
+ /** Resolve an attachment locator captured by a recent metadata-only read. Never persisted. */
83
+ liveAttachment(conversationKey, attachmentId) {
84
+ const key = `${conversationKey}\0${attachmentId}`;
85
+ const hit = this.liveAttachments.get(key);
86
+ if (!hit || hit.expiresAt <= Date.now()) {
87
+ this.liveAttachments.delete(key);
88
+ return undefined;
89
+ }
90
+ return hit.attachment;
91
+ }
92
+ async readRemote(conversationKey, window, signal) {
93
+ const conversation = this.store.conversations.get(conversationKey);
94
+ if (!conversation || conversation.remoteMissing) {
95
+ throw new ReferenceAnythingError('conversation is missing from the local active-account index; ask the user to sync its provider and reselect it from the refreshed @ list, then retry', 'REFERENCE_NOT_FOUND');
96
+ }
97
+ let end = window.before;
98
+ if (window.cursor !== undefined)
99
+ end = decodeLiveCursor(window.cursor, conversationKey);
100
+ let rows;
101
+ try {
102
+ rows = await this.runner().detail(conversation.provider, conversation.externalId, signal, conversation.accountScope);
103
+ }
104
+ catch (error) {
105
+ if (signal?.aborted)
106
+ throw signal.reason;
107
+ if (error instanceof OpenCliError && error.code === 'PROVIDER_ACCOUNT_MISMATCH') {
108
+ throw new ReferenceAnythingError(`conversation belongs to a different logged-in ${conversation.provider} account; ask the user to sync ${conversation.provider} and reselect it from the refreshed @ list, then retry`, 'REFERENCE_ACCOUNT_MISMATCH', { cause: error });
109
+ }
110
+ throw new ReferenceAnythingError(`could not fetch the conversation from ${conversation.provider}; ask the user to confirm the browser connection and login, sync ${conversation.provider}, and then retry`, 'REFERENCE_READ_FAILED', { cause: error });
111
+ }
112
+ const turns = projectRemoteTurns(rows);
113
+ const boundedEnd = Math.max(0, Math.min(end ?? turns.length, turns.length));
114
+ const start = Math.max(0, boundedEnd - Math.max(1, Math.trunc(window.limit)));
115
+ const expiresAt = Date.now() + 60 * 60_000;
116
+ for (const turn of turns.slice(start, boundedEnd))
117
+ for (const attachment of turn.attachments ?? []) {
118
+ this.liveAttachments.set(`${conversationKey}\0${attachment.attachmentId}`, { attachment, expiresAt });
119
+ }
120
+ const nextCursor = start > 0 ? encodeLiveCursor(conversationKey, start) : undefined;
121
+ return {
122
+ ref: { source: this.id, id: conversationKey }, label: conversation.title, origin: conversation.url,
123
+ updatedAt: timestamp(conversation.updatedAt), provider: conversation.provider,
124
+ partial: conversation.partial || rows.some(row => row.partial), capturedAt: Date.now(),
125
+ body: {
126
+ kind: 'conversation', items: turns.slice(start, boundedEnd), startIndex: start,
127
+ totalTurns: turns.length, hasOlder: start > 0, ...(nextCursor ? { nextCursor } : {}),
128
+ },
129
+ };
130
+ }
131
+ async search(query, provider, limit, signal) {
132
+ signal?.throwIfAborted();
133
+ const parsed = parseProviderQuery(query);
134
+ return this.store
135
+ .list(parsed.query, provider ?? parsed.provider, Math.max(1, Math.min(100, limit)))
136
+ .map(({ key, row, via, snippet }) => ({
137
+ uriId: key, provider: row.provider, title: row.title, url: row.url, updatedAt: row.updatedAt,
138
+ turnCount: row.messageCount, partial: row.partial, syncedAt: row.syncedAt,
139
+ matchedVia: via, ...(snippet ? { snippet } : {}),
140
+ }));
141
+ }
142
+ stats() { return this.store.stats(PROVIDERS); }
143
+ storageStats() { return this.store.storageStats(); }
144
+ /** Paginated browse for the management list — unlike {@link search}, includes `remoteMissing` rows. */
145
+ browse(query, provider, limit, offset) {
146
+ const { items, total } = this.store.page(query, provider, Math.max(1, Math.min(100, limit)), Math.max(0, offset));
147
+ return {
148
+ total,
149
+ items: items.map(([uriId, row]) => ({
150
+ uriId, provider: row.provider, title: row.title, url: row.url, updatedAt: row.updatedAt,
151
+ turnCount: row.messageCount, partial: row.partial, syncedAt: row.syncedAt, remoteMissing: row.remoteMissing,
152
+ })),
153
+ };
154
+ }
155
+ /** Permanently delete a synced conversation; refused while a sync could resurrect it mid-delete. */
156
+ async remove(uriId) {
157
+ if (this.sync.isRunning()) {
158
+ throw new ReferenceAnythingError('cannot delete a conversation while a sync is in progress', 'REFERENCE_SYNC_IN_PROGRESS');
159
+ }
160
+ return this.store.remove(uriId);
161
+ }
162
+ async removeProvider(provider) {
163
+ if (this.sync.isRunning())
164
+ throw new ReferenceAnythingError('cannot clear provider data while a sync is in progress', 'REFERENCE_SYNC_IN_PROGRESS');
165
+ return this.store.removeProvider(provider);
166
+ }
167
+ async removeOlderThan(days) {
168
+ if (this.sync.isRunning())
169
+ throw new ReferenceAnythingError('cannot clear old data while a sync is in progress', 'REFERENCE_SYNC_IN_PROGRESS');
170
+ return this.store.removeOlderThan(days);
171
+ }
172
+ async removeRemoteMissing() {
173
+ if (this.sync.isRunning())
174
+ throw new ReferenceAnythingError('cannot clear remote-missing data while a sync is in progress', 'REFERENCE_SYNC_IN_PROGRESS');
175
+ return this.store.removeRemoteMissing();
176
+ }
177
+ async removeOldAccounts() {
178
+ if (this.sync.isRunning())
179
+ throw new ReferenceAnythingError('cannot clear old-account data while a sync is in progress', 'REFERENCE_SYNC_IN_PROGRESS');
180
+ return this.store.removeOldAccounts();
181
+ }
182
+ /** Durable per-provider sync status, independent of any single in-flight job. */
183
+ syncStates() { return this.store.syncStateSummary(); }
184
+ getSettings() { return this.store.settings; }
185
+ async restartDaemon(signal) { await this.runner().restartDaemon(signal); return true; }
186
+ async discoverOpenCli(signal) {
187
+ return discoverOpenCli(this.store.settings.opencliPath, signal);
188
+ }
189
+ async installOpenCli(signal) {
190
+ const discovery = await installOpenCliPackage(signal);
191
+ if (this.store.settings.opencliPath !== discovery.executable) {
192
+ await this.store.setSettings({ ...this.store.settings, opencliPath: discovery.executable });
193
+ }
194
+ return discovery;
195
+ }
196
+ async updateSettings(value) {
197
+ const settings = settingsRecordSchema.parse(value);
198
+ const enteringMetadataOnly = settings.historyMode === 'metadata-only' && this.store.settings.historyMode !== 'metadata-only';
199
+ await this.store.setSettings(settings);
200
+ if (enteringMetadataOnly)
201
+ await this.store.clearMirrorContent();
202
+ this.armAutoSync();
203
+ return settings;
204
+ }
205
+ health(signal) {
206
+ const settings = this.store.settings;
207
+ return new OpenCliRunner({ executable: settings.opencliPath, profile: settings.profile,
208
+ timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes }).health(signal);
209
+ }
210
+ profiles(signal) {
211
+ const settings = this.store.settings;
212
+ return new OpenCliRunner({ executable: settings.opencliPath, profile: '',
213
+ timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes }).profiles(signal);
214
+ }
215
+ runner() {
216
+ const settings = this.store.settings;
217
+ return new OpenCliRunner({ executable: settings.opencliPath, profile: settings.profile,
218
+ timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes });
219
+ }
220
+ async installAdapter(signal) {
221
+ const settings = this.store.settings;
222
+ const runner = new OpenCliRunner({ executable: settings.opencliPath, profile: settings.profile,
223
+ timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes });
224
+ await runner.installPlugin(new URL('../../../opencli-plugin/', import.meta.url).href, signal);
225
+ return true;
226
+ }
227
+ /**
228
+ * (Re)arm the background sync timer from the current settings.
229
+ *
230
+ * Clears its own timers rather than swapping a `ctx.effect` disposer:
231
+ * disposing is asynchronous, so re-arming through it left a window — one
232
+ * that a couple of quick settings saves is enough to hit — where two
233
+ * intervals were live at once.
234
+ */
235
+ armAutoSync() {
236
+ this.clearAutoSync();
237
+ const settings = this.store.settings;
238
+ if (!settings.autoSync && !settings.syncOnStartup)
239
+ return;
240
+ const period = settings.autoSyncMinutes * 60_000;
241
+ const tick = () => {
242
+ if (this.sync.isRunning()) {
243
+ this.ctx.logger.info('reference sync: skipping the auto-sync tick, a sync is still running');
244
+ return;
245
+ }
246
+ const providers = this.sync.eligibleProviders(settings.enabledProviders);
247
+ if (providers.length === 0) {
248
+ this.ctx.logger.info('reference sync: no provider is eligible for auto-sync right now');
249
+ return;
250
+ }
251
+ this.ctx.logger.info(`reference sync: auto-syncing ${providers.join(', ')}`);
252
+ this.sync.start(providers, 'incremental', {
253
+ // The tick's own gate is `isRunning`, so a job that never finishes
254
+ // would silently cancel every later tick.
255
+ deadlineMs: period * 2,
256
+ });
257
+ };
258
+ // Jitter keeps every provider — and every dsh instance sharing a default
259
+ // interval — off the same instant.
260
+ if (settings.autoSync) {
261
+ this.autoSyncInterval = setInterval(tick, jitter(period));
262
+ this.autoSyncInterval.unref?.();
263
+ }
264
+ const delay = this.catchUpDelay(period);
265
+ if (delay !== undefined) {
266
+ this.autoSyncCatchUp = setTimeout(tick, delay);
267
+ this.autoSyncCatchUp.unref?.();
268
+ }
269
+ }
270
+ /**
271
+ * How long to wait before a catch-up pass, or undefined when the last one
272
+ * is recent enough that the regular interval will do.
273
+ *
274
+ * A plain interval only ever fires a full period after arming, so enabling
275
+ * background sync — or restarting dsh after a day off — used to mean the
276
+ * mirror stayed stale for up to a day before anything happened.
277
+ */
278
+ catchUpDelay(period) {
279
+ let newest = 0;
280
+ for (const [, row] of this.store.syncStates.entries()) {
281
+ const at = Date.parse(row.lastSyncAt || '');
282
+ if (!Number.isNaN(at) && at > newest)
283
+ newest = at;
284
+ }
285
+ const elapsed = Date.now() - newest;
286
+ if (newest > 0 && elapsed < period)
287
+ return undefined;
288
+ // Not immediately: startup is already the busiest moment in the process,
289
+ // and nothing here is urgent to the second.
290
+ return CATCH_UP_MIN_MS + Math.random() * (CATCH_UP_MAX_MS - CATCH_UP_MIN_MS);
291
+ }
292
+ clearAutoSync() {
293
+ if (this.autoSyncInterval) {
294
+ clearInterval(this.autoSyncInterval);
295
+ this.autoSyncInterval = undefined;
296
+ }
297
+ if (this.autoSyncCatchUp) {
298
+ clearTimeout(this.autoSyncCatchUp);
299
+ this.autoSyncCatchUp = undefined;
300
+ }
301
+ }
302
+ }
303
+ /** Spread a period by ±10% so providers and instances do not all fire together. */
304
+ function jitter(period) {
305
+ return Math.round(period * (0.9 + Math.random() * 0.2));
306
+ }
307
+ function timestamp(value) {
308
+ const numeric = Number(value);
309
+ if (Number.isFinite(numeric) && value.trim())
310
+ return numeric < 10_000_000_000 ? numeric * 1_000 : numeric;
311
+ const parsed = Date.parse(value);
312
+ return Number.isNaN(parsed) ? undefined : parsed;
313
+ }
314
+ function projectRemoteTurns(rows) {
315
+ return rows.filter(row => row.activeBranch !== false).sort((a, b) => a.ordinal - b.ordinal).flatMap(row => {
316
+ const attachments = parseLiveAttachments(row.attachmentsJson);
317
+ const text = String(row.text || '');
318
+ if (!text.trim() && attachments.length === 0)
319
+ return [];
320
+ return [{ role: row.role, text, ...(attachments.length ? { attachments } : {}) }];
321
+ });
322
+ }
323
+ function parseLiveAttachments(raw) {
324
+ try {
325
+ const parsed = JSON.parse(raw || '[]');
326
+ if (!Array.isArray(parsed))
327
+ return [];
328
+ return parsed.map((value, index) => {
329
+ const row = value && typeof value === 'object' ? value : {};
330
+ const name = String(row.name ?? 'attachment');
331
+ const mimeType = String(row.mimeType ?? '');
332
+ const locator = typeof row.locator === 'string' && row.locator ? row.locator : undefined;
333
+ const status = row.status === 'available' || row.status === 'expired' ? row.status : 'unavailable';
334
+ return {
335
+ attachmentId: String(row.attachmentId ?? row.id ?? index),
336
+ kind: row.kind === 'image' || row.type === 'image' || mimeType.startsWith('image/') ? 'image' : 'file',
337
+ name, mimeType, size: Math.max(0, Number(row.size ?? 0) || 0), status,
338
+ ...(locator ? { locator } : {}),
339
+ };
340
+ });
341
+ }
342
+ catch {
343
+ return [];
344
+ }
345
+ }
346
+ function encodeLiveCursor(ref, nextOrdinal) {
347
+ return Buffer.from(JSON.stringify({ v: 2, ref, nextOrdinal })).toString('base64url');
348
+ }
349
+ function decodeLiveCursor(value, expectedRef) {
350
+ try {
351
+ const row = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
352
+ if (row.v !== 2 || row.ref !== expectedRef || !Number.isInteger(row.nextOrdinal) || Number(row.nextOrdinal) < 0)
353
+ throw new Error();
354
+ return Number(row.nextOrdinal);
355
+ }
356
+ catch {
357
+ throw new ReferenceAnythingError('live reference cursor is malformed or belongs to another conversation', 'REFERENCE_INVALID_CURSOR');
358
+ }
359
+ }
@@ -0,0 +1,312 @@
1
+ import { z } from 'zod';
2
+ import { providerSchema, settingsRecordSchema } from '../wire.ts';
3
+ import type { ChatProvider, SettingsRecord } from '../wire.ts';
4
+ export { providerSchema, settingsRecordSchema };
5
+ export type { ChatProvider, SettingsRecord };
6
+ export declare const storedAttachmentSchema: z.ZodObject<{
7
+ attachmentId: z.ZodString;
8
+ kind: z.ZodDefault<z.ZodEnum<{
9
+ file: "file";
10
+ image: "image";
11
+ }>>;
12
+ name: z.ZodString;
13
+ mimeType: z.ZodString;
14
+ size: z.ZodNumber;
15
+ status: z.ZodEnum<{
16
+ available: "available";
17
+ unavailable: "unavailable";
18
+ expired: "expired";
19
+ }>;
20
+ locator: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ export type StoredAttachment = z.infer<typeof storedAttachmentSchema>;
23
+ export declare const storedTurnSchema: z.ZodObject<{
24
+ ordinal: z.ZodNumber;
25
+ messageId: z.ZodString;
26
+ parentId: z.ZodString;
27
+ branchId: z.ZodString;
28
+ role: z.ZodEnum<{
29
+ user: "user";
30
+ assistant: "assistant";
31
+ }>;
32
+ text: z.ZodString;
33
+ createdAt: z.ZodString;
34
+ attachments: z.ZodArray<z.ZodObject<{
35
+ attachmentId: z.ZodString;
36
+ kind: z.ZodDefault<z.ZodEnum<{
37
+ file: "file";
38
+ image: "image";
39
+ }>>;
40
+ name: z.ZodString;
41
+ mimeType: z.ZodString;
42
+ size: z.ZodNumber;
43
+ status: z.ZodEnum<{
44
+ available: "available";
45
+ unavailable: "unavailable";
46
+ expired: "expired";
47
+ }>;
48
+ locator: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>>;
50
+ }, z.core.$strip>;
51
+ export type StoredTurn = z.infer<typeof storedTurnSchema>;
52
+ export declare const conversationRecordSchema: z.ZodObject<{
53
+ provider: z.ZodEnum<{
54
+ chatgpt: "chatgpt";
55
+ claude: "claude";
56
+ gemini: "gemini";
57
+ deepseek: "deepseek";
58
+ grok: "grok";
59
+ kimi: "kimi";
60
+ }>;
61
+ accountScope: z.ZodString;
62
+ externalId: z.ZodString;
63
+ title: z.ZodString;
64
+ url: z.ZodString;
65
+ currentRevision: z.ZodOptional<z.ZodString>;
66
+ createdAt: z.ZodString;
67
+ updatedAt: z.ZodString;
68
+ messageCount: z.ZodNumber;
69
+ partial: z.ZodBoolean;
70
+ remoteMissing: z.ZodBoolean;
71
+ syncedAt: z.ZodString;
72
+ }, z.core.$strip>;
73
+ export type ConversationRecord = z.infer<typeof conversationRecordSchema>;
74
+ export declare const revisionRecordSchema: z.ZodObject<{
75
+ conversationKey: z.ZodString;
76
+ revision: z.ZodString;
77
+ contentHash: z.ZodString;
78
+ turnCount: z.ZodNumber;
79
+ activeBranch: z.ZodString;
80
+ chunkKeys: z.ZodArray<z.ZodString>;
81
+ partial: z.ZodBoolean;
82
+ syncedAt: z.ZodString;
83
+ expiresAt: z.ZodString;
84
+ }, z.core.$strip>;
85
+ export type RevisionRecord = z.infer<typeof revisionRecordSchema>;
86
+ export declare const turnChunkRecordSchema: z.ZodObject<{
87
+ conversationKey: z.ZodString;
88
+ revision: z.ZodString;
89
+ index: z.ZodNumber;
90
+ turns: z.ZodArray<z.ZodObject<{
91
+ ordinal: z.ZodNumber;
92
+ messageId: z.ZodString;
93
+ parentId: z.ZodString;
94
+ branchId: z.ZodString;
95
+ role: z.ZodEnum<{
96
+ user: "user";
97
+ assistant: "assistant";
98
+ }>;
99
+ text: z.ZodString;
100
+ createdAt: z.ZodString;
101
+ attachments: z.ZodArray<z.ZodObject<{
102
+ attachmentId: z.ZodString;
103
+ kind: z.ZodDefault<z.ZodEnum<{
104
+ file: "file";
105
+ image: "image";
106
+ }>>;
107
+ name: z.ZodString;
108
+ mimeType: z.ZodString;
109
+ size: z.ZodNumber;
110
+ status: z.ZodEnum<{
111
+ available: "available";
112
+ unavailable: "unavailable";
113
+ expired: "expired";
114
+ }>;
115
+ locator: z.ZodOptional<z.ZodString>;
116
+ }, z.core.$strip>>;
117
+ }, z.core.$strip>>;
118
+ }, z.core.$strip>;
119
+ export type TurnChunkRecord = z.infer<typeof turnChunkRecordSchema>;
120
+ export declare const attachmentRecordSchema: z.ZodObject<{
121
+ attachmentId: z.ZodString;
122
+ kind: z.ZodDefault<z.ZodEnum<{
123
+ file: "file";
124
+ image: "image";
125
+ }>>;
126
+ name: z.ZodString;
127
+ mimeType: z.ZodString;
128
+ size: z.ZodNumber;
129
+ status: z.ZodEnum<{
130
+ available: "available";
131
+ unavailable: "unavailable";
132
+ expired: "expired";
133
+ }>;
134
+ locator: z.ZodOptional<z.ZodString>;
135
+ conversationKey: z.ZodString;
136
+ revision: z.ZodString;
137
+ ordinal: z.ZodNumber;
138
+ }, z.core.$strip>;
139
+ export type AttachmentRecord = z.infer<typeof attachmentRecordSchema>;
140
+ export declare const syncStateRecordSchema: z.ZodObject<{
141
+ provider: z.ZodEnum<{
142
+ chatgpt: "chatgpt";
143
+ claude: "claude";
144
+ gemini: "gemini";
145
+ deepseek: "deepseek";
146
+ grok: "grok";
147
+ kimi: "kimi";
148
+ }>;
149
+ profile: z.ZodString;
150
+ accountScope: z.ZodString;
151
+ cursor: z.ZodString;
152
+ status: z.ZodEnum<{
153
+ failed: "failed";
154
+ cancelled: "cancelled";
155
+ running: "running";
156
+ idle: "idle";
157
+ }>;
158
+ lastSyncAt: z.ZodString;
159
+ lastCompleteScanAt: z.ZodString;
160
+ error: z.ZodString;
161
+ completed: z.ZodNumber;
162
+ total: z.ZodNumber;
163
+ consecutiveFailures: z.ZodDefault<z.ZodNumber>;
164
+ nextEligibleAt: z.ZodDefault<z.ZodString>;
165
+ }, z.core.$strip>;
166
+ export type SyncStateRecord = z.infer<typeof syncStateRecordSchema>;
167
+ export declare const referenceAnythingDomainSpec: {
168
+ name: string;
169
+ version: number;
170
+ global: {
171
+ schema: z.ZodObject<{
172
+ opencliPath: z.ZodString;
173
+ profile: z.ZodString;
174
+ detailConcurrency: z.ZodNumber;
175
+ autoSync: z.ZodDefault<z.ZodBoolean>;
176
+ syncOnStartup: z.ZodDefault<z.ZodBoolean>;
177
+ autoSyncMinutes: z.ZodDefault<z.ZodNumber>;
178
+ historyMode: z.ZodDefault<z.ZodEnum<{
179
+ "metadata-only": "metadata-only";
180
+ "offline-mirror": "offline-mirror";
181
+ }>>;
182
+ enabledProviders: z.ZodDefault<z.ZodArray<z.ZodEnum<{
183
+ chatgpt: "chatgpt";
184
+ claude: "claude";
185
+ gemini: "gemini";
186
+ deepseek: "deepseek";
187
+ grok: "grok";
188
+ kimi: "kimi";
189
+ }>>>;
190
+ maxReadTurns: z.ZodDefault<z.ZodNumber>;
191
+ inputRenderMode: z.ZodDefault<z.ZodEnum<{
192
+ pill: "pill";
193
+ "raw-text": "raw-text";
194
+ }>>;
195
+ picker: z.ZodOptional<z.ZodObject<{
196
+ commands: z.ZodObject<{
197
+ enabled: z.ZodBoolean;
198
+ order: z.ZodNumber;
199
+ limit: z.ZodNumber;
200
+ }, z.core.$strip>;
201
+ skills: z.ZodObject<{
202
+ enabled: z.ZodBoolean;
203
+ order: z.ZodNumber;
204
+ limit: z.ZodNumber;
205
+ }, z.core.$strip>;
206
+ files: z.ZodObject<{
207
+ enabled: z.ZodBoolean;
208
+ order: z.ZodNumber;
209
+ limit: z.ZodNumber;
210
+ }, z.core.$strip>;
211
+ sessions: z.ZodObject<{
212
+ enabled: z.ZodBoolean;
213
+ order: z.ZodNumber;
214
+ limit: z.ZodNumber;
215
+ }, z.core.$strip>;
216
+ conversations: z.ZodObject<{
217
+ enabled: z.ZodBoolean;
218
+ order: z.ZodNumber;
219
+ limit: z.ZodNumber;
220
+ }, z.core.$strip>;
221
+ }, z.core.$strip>>;
222
+ }, z.core.$strip>;
223
+ initial: {
224
+ opencliPath: string;
225
+ profile: string;
226
+ detailConcurrency: number;
227
+ autoSync: boolean;
228
+ syncOnStartup: boolean;
229
+ autoSyncMinutes: number;
230
+ historyMode: "metadata-only";
231
+ enabledProviders: ("chatgpt" | "claude" | "gemini" | "deepseek" | "grok" | "kimi")[];
232
+ maxReadTurns: number;
233
+ inputRenderMode: "pill";
234
+ };
235
+ };
236
+ tables: {
237
+ conversations: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
238
+ provider: "chatgpt" | "claude" | "gemini" | "deepseek" | "grok" | "kimi";
239
+ accountScope: string;
240
+ externalId: string;
241
+ title: string;
242
+ url: string;
243
+ createdAt: string;
244
+ updatedAt: string;
245
+ messageCount: number;
246
+ partial: boolean;
247
+ remoteMissing: boolean;
248
+ syncedAt: string;
249
+ currentRevision?: string | undefined;
250
+ }>;
251
+ revisions: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
252
+ conversationKey: string;
253
+ revision: string;
254
+ contentHash: string;
255
+ turnCount: number;
256
+ activeBranch: string;
257
+ chunkKeys: string[];
258
+ partial: boolean;
259
+ syncedAt: string;
260
+ expiresAt: string;
261
+ }>;
262
+ turn_chunks: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
263
+ conversationKey: string;
264
+ revision: string;
265
+ index: number;
266
+ turns: {
267
+ ordinal: number;
268
+ messageId: string;
269
+ parentId: string;
270
+ branchId: string;
271
+ role: "user" | "assistant";
272
+ text: string;
273
+ createdAt: string;
274
+ attachments: {
275
+ attachmentId: string;
276
+ kind: "file" | "image";
277
+ name: string;
278
+ mimeType: string;
279
+ size: number;
280
+ status: "available" | "unavailable" | "expired";
281
+ locator?: string | undefined;
282
+ }[];
283
+ }[];
284
+ }>;
285
+ attachments: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
286
+ attachmentId: string;
287
+ kind: "file" | "image";
288
+ name: string;
289
+ mimeType: string;
290
+ size: number;
291
+ status: "available" | "unavailable" | "expired";
292
+ conversationKey: string;
293
+ revision: string;
294
+ ordinal: number;
295
+ locator?: string | undefined;
296
+ }>;
297
+ sync_states: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
298
+ provider: "chatgpt" | "claude" | "gemini" | "deepseek" | "grok" | "kimi";
299
+ profile: string;
300
+ accountScope: string;
301
+ cursor: string;
302
+ status: "failed" | "cancelled" | "running" | "idle";
303
+ lastSyncAt: string;
304
+ lastCompleteScanAt: string;
305
+ error: string;
306
+ completed: number;
307
+ total: number;
308
+ consecutiveFailures: number;
309
+ nextEligibleAt: string;
310
+ }>;
311
+ };
312
+ };