@chance722/dsh-inbox 0.1.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/README.zh.md +182 -0
  4. package/cordis.patch.yml +11 -0
  5. package/lib/cli.js +259 -0
  6. package/lib/client.js +3823 -0
  7. package/lib/index.js +3718 -0
  8. package/lib/types/cli.d.ts +49 -0
  9. package/lib/types/client/card.d.ts +52 -0
  10. package/lib/types/client/dock.d.ts +52 -0
  11. package/lib/types/client/heading.d.ts +54 -0
  12. package/lib/types/client/index.d.ts +24 -0
  13. package/lib/types/client/manual.d.ts +22 -0
  14. package/lib/types/client/scheme.d.ts +47 -0
  15. package/lib/types/host/capture.d.ts +117 -0
  16. package/lib/types/host/classify/model.d.ts +112 -0
  17. package/lib/types/host/classify/redact.d.ts +18 -0
  18. package/lib/types/host/classify/rules.d.ts +58 -0
  19. package/lib/types/host/command.d.ts +18 -0
  20. package/lib/types/host/crypto/secret-box.d.ts +60 -0
  21. package/lib/types/host/index.d.ts +21 -0
  22. package/lib/types/host/link-title.d.ts +64 -0
  23. package/lib/types/host/remote/auto-push.d.ts +37 -0
  24. package/lib/types/host/remote/merge.d.ts +80 -0
  25. package/lib/types/host/remote/pull.d.ts +60 -0
  26. package/lib/types/host/remote/push.d.ts +101 -0
  27. package/lib/types/host/remote/remove.d.ts +65 -0
  28. package/lib/types/host/remote/writer.d.ts +43 -0
  29. package/lib/types/host/rpc.d.ts +36 -0
  30. package/lib/types/host/s3/client.d.ts +216 -0
  31. package/lib/types/host/s3/probe.d.ts +28 -0
  32. package/lib/types/host/tools.d.ts +56 -0
  33. package/lib/types/host/ui/config.d.ts +54 -0
  34. package/lib/types/host/vault/lease.d.ts +54 -0
  35. package/lib/types/host/vault/query.d.ts +38 -0
  36. package/lib/types/host/vault/spec.d.ts +193 -0
  37. package/lib/types/host/vault/vault.d.ts +269 -0
  38. package/lib/types/host/webdav/client.d.ts +102 -0
  39. package/lib/types/host/webdav/config.d.ts +129 -0
  40. package/lib/types/host/webdav/probe.d.ts +22 -0
  41. package/lib/types/host/webdav/run.d.ts +34 -0
  42. package/lib/types/shared/constants.d.ts +23 -0
  43. package/lib/types/shared/panel-wire.d.ts +474 -0
  44. package/lib/types/shared/vocabulary.d.ts +39 -0
  45. package/package.json +101 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The smallest S3 surface the inbox needs: list one prefix, read one object.
3
+ *
4
+ * Signing is implemented rather than imported. A dependency would be the
5
+ * obvious move, but this is ~60 lines of well-specified HMAC chaining, and the
6
+ * alternative is shipping a second network stack inside a vault plugin whose
7
+ * whole point is that nothing unexpected leaves the machine.
8
+ *
9
+ * Assumptions that keep it small, each of them honest about its limit:
10
+ * - **path-style** addressing (`<endpoint>/<bucket>/<key>`), which is what
11
+ * non-AWS S3 deployments such as 数据胶囊 accept;
12
+ * - **SigV4** (`AWS4-HMAC-SHA256`), with the region configurable because the
13
+ * signature covers it even when the server ignores it;
14
+ * - `UNSIGNED-PAYLOAD` is *not* used: every body we send is empty, so the
15
+ * payload hash is the SHA-256 of nothing.
16
+ */
17
+ /** What the user fills in. The secret never lives here. */
18
+ export interface S3Config {
19
+ /** e.g. `https://s3.cstcloud.cn` — no bucket, no trailing slash needed. */
20
+ endpoint: string;
21
+ bucket: string;
22
+ /** Covered by the signature; defaults to `us-east-1` for non-AWS servers. */
23
+ region?: string;
24
+ /** Only v4 is implemented; the field exists so the choice is visible. */
25
+ signatureVersion?: string;
26
+ /** What to send as `User-Agent`; empty falls back to the plugin's own. */
27
+ userAgent?: string;
28
+ }
29
+ /**
30
+ * The identity this request presents.
31
+ *
32
+ * Not cosmetic: 数据胶囊 binds an access key to an application and answers
33
+ * every request that does not claim to be that application with a body-less
34
+ * 401 — identical, from the outside, to a wrong secret. The signature never
35
+ * covers this header, so it can be set freely.
36
+ *
37
+ * @param config - endpoint, bucket, region, optional user agent.
38
+ * @returns the header value to send.
39
+ */
40
+ export declare function userAgentOf(config: S3Config): string;
41
+ /** One object found under the prefix. */
42
+ export interface RemoteObject {
43
+ key: string;
44
+ lastModified?: string;
45
+ bytes?: number;
46
+ }
47
+ /** The subset of `fetch` this module uses, so tests can stand in for it. */
48
+ export interface S3ResponseLike {
49
+ ok: boolean;
50
+ status: number;
51
+ text(): Promise<string>;
52
+ arrayBuffer(): Promise<ArrayBuffer>;
53
+ headers?: {
54
+ get(name: string): string | null;
55
+ };
56
+ }
57
+ export type S3FetchLike = (url: string, init: {
58
+ method: string;
59
+ headers: Record<string, string>;
60
+ body?: Uint8Array;
61
+ }) => Promise<S3ResponseLike>;
62
+ /** Everything a call needs besides the configuration. */
63
+ export interface S3Deps {
64
+ fetch: S3FetchLike;
65
+ accessKeyId: string;
66
+ accessKeySecret: string;
67
+ /** Injected so tests are not clock-dependent. */
68
+ now?: Date;
69
+ }
70
+ /** `YYYYMMDDTHHMMSSZ`, the only date format SigV4 accepts. */
71
+ export declare function amzDate(now: Date): string;
72
+ /** The pieces of a signed request, exposed so a test can look at them. */
73
+ export interface SignedRequest {
74
+ url: string;
75
+ headers: Record<string, string>;
76
+ canonicalRequest: string;
77
+ signature: string;
78
+ }
79
+ /** `RFC 1123` date, the only one SigV2 accepts. */
80
+ export declare function rfc1123(now: Date): string;
81
+ /**
82
+ * Sign one request with **SigV2**.
83
+ *
84
+ * Not legacy for its own sake: gateways that describe their older clients as
85
+ * needing "SSL/TLS and path-style addressing" are usually v2-only, and a v4
86
+ * request to one of them is answered with a bare 401 — which is exactly the
87
+ * shape of the failure this implementation exists to fix.
88
+ *
89
+ * SigV2 signs a different thing than v4: the date, the content type, and the
90
+ * *canonicalized resource* — and only the query parameters on the sub-resource
91
+ * list take part, not ordinary ones like `prefix`.
92
+ *
93
+ * @param config - endpoint, bucket, region.
94
+ * @param deps - credentials, fetch, clock.
95
+ * @param method - HTTP method.
96
+ * @param key - object key, empty for a bucket operation.
97
+ * @param query - query parameters.
98
+ * @returns the URL, headers, and the string that was signed.
99
+ */
100
+ export declare function signRequestV2(config: S3Config, deps: S3Deps, method: string, key: string, query?: Record<string, string>): SignedRequest & {
101
+ stringToSign: string;
102
+ };
103
+ /**
104
+ * Sign one request.
105
+ *
106
+ * @param config - endpoint, bucket, region.
107
+ * @param deps - credentials, fetch, clock.
108
+ * @param method - HTTP method.
109
+ * @param key - object key, empty for a bucket operation.
110
+ * @param query - query parameters, already encoded values.
111
+ * @returns the URL, headers, and the intermediate values (for tests).
112
+ */
113
+ export declare function signRequest(config: S3Config, deps: S3Deps, method: string, key: string, query?: Record<string, string>,
114
+ /**
115
+ * The bytes a write is about to send.
116
+ *
117
+ * SigV4 signs the *payload*: a PUT signed as if it were empty is refused by
118
+ * every strict gateway, which is why reads (no body) and writes (a body) must
119
+ * go through the same function with the difference made explicit.
120
+ */
121
+ body?: Uint8Array): SignedRequest;
122
+ /**
123
+ * SigV4 with the **minimum** signed headers: `host` and `x-amz-date` only.
124
+ *
125
+ * The full form also signs `x-amz-content-sha256`, which is what AWS expects.
126
+ * A gateway that implements a subset of v4 recomputes the canonical request
127
+ * from the two headers it knows and then rejects every signature we send —
128
+ * which is indistinguishable from a wrong key until you try this.
129
+ *
130
+ * @param config - endpoint, bucket, region.
131
+ * @param deps - credentials, fetch, clock.
132
+ * @param method - HTTP method.
133
+ * @param key - object key.
134
+ * @param query - query parameters.
135
+ * @returns the signed request.
136
+ */
137
+ export declare function signRequestV4Minimal(config: S3Config, deps: S3Deps, method: string, key: string, query?: Record<string, string>): SignedRequest;
138
+ /**
139
+ * SigV4 with **`UNSIGNED-PAYLOAD`** as the content hash — what the official SDKs
140
+ * send for a GET over HTTPS.
141
+ *
142
+ * This is the third and last shape worth trying: the full form signs the hash of
143
+ * the empty body, the minimal form omits the header, and this one declares the
144
+ * payload deliberately unsigned. A gateway that validates the *value* rather
145
+ * than recomputing it accepts only this one.
146
+ *
147
+ * @param config - endpoint, bucket, region.
148
+ * @param deps - credentials, fetch, clock.
149
+ * @param method - HTTP method.
150
+ * @param key - object key.
151
+ * @param query - query parameters.
152
+ * @returns the signed request.
153
+ */
154
+ export declare function signRequestUnsignedPayload(config: S3Config, deps: S3Deps, method: string, key: string, query?: Record<string, string>): SignedRequest;
155
+ /**
156
+ * Read a ListObjectsV2 answer.
157
+ *
158
+ * @param xml - the response body.
159
+ * @returns the objects it listed.
160
+ */
161
+ export declare function parseListing(xml: string): RemoteObject[];
162
+ /**
163
+ * List objects under a prefix.
164
+ *
165
+ * Tries ListObjects **V2** first and falls back to **V1** when the server
166
+ * refuses it. Both answer with the same `<Contents>` shape, so the parser does
167
+ * not care; what differs is that plenty of gateways implement only V1 and
168
+ * answer a `list-type=2` request with a server error rather than an S3 error —
169
+ * which is exactly the shape of a 500 that says "unknown runtime exception".
170
+ *
171
+ * @param config - endpoint, bucket, region.
172
+ * @param prefix - key prefix, e.g. `inbox/`.
173
+ * @param deps - credentials, fetch, clock.
174
+ * @returns the objects found.
175
+ */
176
+ export declare function listPrefix(config: S3Config, prefix: string, deps: S3Deps): Promise<RemoteObject[]>;
177
+ /** Fetch one object's bytes, with its content type when the server sends one. */
178
+ export declare function readObject(config: S3Config, key: string, deps: S3Deps): Promise<{
179
+ bytes: Uint8Array;
180
+ contentType: string;
181
+ }>;
182
+ /**
183
+ * Write one object.
184
+ *
185
+ * The payload goes into the signature (see {@link signRequest}), which is the
186
+ * one thing that makes a write different from every read this client has done
187
+ * until now: a gateway that checks the payload hash refuses a PUT signed as if
188
+ * it had no body. `content-length` and `content-type` ride along unsigned —
189
+ * they are not part of `signedHeaders`, and servers accept exactly that.
190
+ *
191
+ * @param config - endpoint, bucket, region, signature version.
192
+ * @param key - object key.
193
+ * @param body - the bytes to store.
194
+ * @param deps - credentials, fetch, clock.
195
+ * @param contentType - what the bytes are, when the caller knows.
196
+ */
197
+ export declare function putObject(config: S3Config, key: string, body: Uint8Array, deps: S3Deps, contentType?: string): Promise<void>;
198
+ /**
199
+ * Remove one object.
200
+ *
201
+ * Idempotent on purpose: S3 answers 204 for a key that was never there, and the
202
+ * callers here delete objects they *believe* exist (a record's files, an
203
+ * attachment's bytes) — a missing one is the state they wanted, not a failure.
204
+ *
205
+ * @param config - endpoint, bucket, region, signature version.
206
+ * @param key - object key.
207
+ * @param deps - credentials, fetch, clock.
208
+ */
209
+ export declare function deleteObject(config: S3Config, key: string, deps: S3Deps): Promise<void>;
210
+ /**
211
+ * Pick the signer for a configuration.
212
+ *
213
+ * @param config - endpoint, bucket, region, signatureVersion.
214
+ * @returns the signer to use.
215
+ */
216
+ export declare function signer(config: S3Config): (config: S3Config, deps: S3Deps, method: string, key: string, query?: Record<string, string>, body?: Uint8Array) => SignedRequest;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A connection self-test that tries every shape of request a list could take.
3
+ *
4
+ * Guessing at a gateway's quirks from a single 500 costs one round trip per
5
+ * guess. Asking it five questions at once costs one, and the answer says which
6
+ * shape it accepts — or, when none work, that the problem is not the shape.
7
+ *
8
+ * Read-only by construction: every probe is a GET, and none of them write.
9
+ */
10
+ import { type S3Config, type S3Deps } from './client.js';
11
+ /** One probe's outcome. */
12
+ export interface ProbeResult {
13
+ /** What this probe asked for, in Chinese. */
14
+ label: string;
15
+ url: string;
16
+ status: number;
17
+ /** A short excerpt of the answer: the code that explains a refusal. */
18
+ detail: string;
19
+ }
20
+ /**
21
+ * Ask the gateway a handful of questions that differ only in shape.
22
+ *
23
+ * @param config - endpoint, bucket, region, signature version.
24
+ * @param deps - credentials, fetch, clock.
25
+ * @param prefix - the configured prefix, so its handling is under test too.
26
+ * @returns one result per probe, in the order they were tried.
27
+ */
28
+ export declare function probeS3(config: S3Config, deps: S3Deps, prefix: string): Promise<ProbeResult[]>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The model-facing tools: what the vault looks like from inside a conversation.
3
+ *
4
+ * Two rules shape every string these tools return.
5
+ *
6
+ * 1. **Credentials never leave the vault.** A record classified as `secret`
7
+ * answers with a refusal, not with its text — the model must not be able to
8
+ * read one out by asking, and the session log must not collect it.
9
+ * 2. **Attachment bytes stay in the vault by default.** A tool result is model
10
+ * visible and persisted, so an image is described by a marker the panel and
11
+ * the tool card resolve locally; the picture itself is not part of the
12
+ * result. That keeps a pasted ID document out of the cloud even when the
13
+ * model is the one that fetched the record. The single exception is
14
+ * `inbox_get` with `withImage: true` — the user asking "look at the picture
15
+ * and tell me what it is" is a request the model cannot honour otherwise,
16
+ * and it is opt-in per call, by name, never the default.
17
+ */
18
+ import type { Context } from '@deepseek-ai/cordis';
19
+ import { type AttachmentSummary, type EntrySummary } from '../shared/panel-wire.js';
20
+ import type { Item } from './vault/spec.js';
21
+ import type { Vault } from './vault/vault.js';
22
+ /** How many records one search answers with before saying "there are more". */
23
+ export declare const SEARCH_PAGE = 10;
24
+ /** How much of a stored text the model reads; the rest stays in the vault. */
25
+ export declare const TEXT_BUDGET = 1000;
26
+ /** Marker the tool card turns into a thumbnail; the bytes stay off the wire. */
27
+ export declare function attachmentMarker(attachmentId: string): string;
28
+ /**
29
+ * Render a search answer.
30
+ *
31
+ * @param entries - the page of matches, newest first.
32
+ * @param matched - how many records matched in total.
33
+ * @returns the model-facing text.
34
+ */
35
+ export declare function formatSearch(entries: readonly Item[], matched: number,
36
+ /** The image marker a line should carry, when the caller can find one. */
37
+ pictureOf?: (item: Item) => string | undefined): string;
38
+ /**
39
+ * Render one record in full, under the two rules above.
40
+ *
41
+ * @param vault - the open vault, for attachment metadata.
42
+ * @param item - the record to describe.
43
+ * @returns the model-facing text.
44
+ */
45
+ export declare function formatDetail(vault: Vault, item: Item): string;
46
+ /** Project one record down to what a card needs (kept for the card contract). */
47
+ export declare function summaryOf(item: Item): EntrySummary;
48
+ /** Attachment summary projection, shared with the panel wire. */
49
+ export declare function attachmentsOf(vault: Vault, item: Item): AttachmentSummary[];
50
+ /**
51
+ * Register the vault's model-facing tools.
52
+ *
53
+ * @param ctx - host context carrying the tool registry.
54
+ * @param vault - reads the currently open vault, which may not be open yet.
55
+ */
56
+ export declare function registerInboxTools(ctx: Context, vault: () => Vault | undefined): void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The panel's own preferences — how it looks, not what it holds.
3
+ *
4
+ * Deliberately a namespace of its own rather than more fields on the remote
5
+ * namespace: "which WebDAV server do I pull from" and "how dense should the
6
+ * list be" are different questions that happen to share a storage service, and
7
+ * merging them would make the remote config the home of unrelated UI state.
8
+ *
9
+ * It lives in dsh's settings (not `localStorage`) for the same reason the
10
+ * theme's font size does: a preference is user state, and the user should find
11
+ * it where they find their other settings.
12
+ */
13
+ import type { Context } from '@deepseek-ai/cordis';
14
+ import z from '@deepseek-ai/schemastery';
15
+ import { type UiPrefs } from '../../shared/panel-wire.js';
16
+ /** Namespace this plugin owns for panel preferences. */
17
+ export declare const UI_SETTINGS_NAMESPACE = "dsh-inbox-ui";
18
+ /** What a fresh install looks like: two columns of cards, the roomier of the two. */
19
+ export declare const DEFAULT_UI_PREFS: UiPrefs;
20
+ /** The namespace's schema; every field optional so a partial layer is valid. */
21
+ export declare const UiPrefsSchema: z<Schemastery.ObjectS<{
22
+ listMode: z<"grid" | "compact", "grid" | "compact">;
23
+ }>, Schemastery.ObjectT<{
24
+ listMode: z<"grid" | "compact", "grid" | "compact">;
25
+ }>>;
26
+ /**
27
+ * Declare the namespace. Registering more than once in a process throws, so a
28
+ * second activation of the plugin simply keeps the first declaration.
29
+ *
30
+ * @param ctx - host context.
31
+ */
32
+ export declare function installUiSettings(ctx: Context): void;
33
+ /**
34
+ * Read the panel preferences, falling back to the defaults.
35
+ *
36
+ * @param ctx - host context.
37
+ * @returns the preferences, plus whether a settings service was there at all.
38
+ */
39
+ export declare function readUiPrefs(ctx: Context): UiPrefs & {
40
+ settingsAvailable: boolean;
41
+ };
42
+ /**
43
+ * Persist a preference change.
44
+ *
45
+ * @param ctx - host context.
46
+ * @param patch - what to change; an absent field changes nothing.
47
+ * @returns whether the write went through, and why not when it did not.
48
+ */
49
+ export declare function saveUiPrefs(ctx: Context, patch: {
50
+ listMode?: string;
51
+ }): {
52
+ ok: boolean;
53
+ reason?: string;
54
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * One vault per process, however many times this plugin gets loaded.
3
+ *
4
+ * dsh composes the plugin **twice**: the profile bundle gives the host half (the
5
+ * panel needs it there), and the agent preset lists it again so a session's
6
+ * assistant can see `inbox_search` / `inbox_get` — the tool registry is
7
+ * per-session, so a plugin that is only in the profile is invisible to the
8
+ * model. Two loads, one process, and `ctx.storageDomain` enforces **single-open
9
+ * per domain name** (`DomainError: domain 'dsh_inbox' is already open`, thrown
10
+ * from the facility's `reserved` set). The second instance therefore failed to
11
+ * open and every tool it registered answered 「仓库没有打开」 — measured
12
+ * 2026-09-20 in a real session, with the panel working fine at the same time
13
+ * (that one was the profile instance, holding the domain).
14
+ *
15
+ * So the domain is a process resource, not a per-instance one. The first caller
16
+ * opens it and becomes the owner; every later caller borrows the same {@link Vault}
17
+ * and the last release closes it. Two consequences worth knowing:
18
+ *
19
+ * - **One vault, one key.** The panel's unlock and the conversation tools now
20
+ * share the in-memory key, which is what "unlock once" always meant.
21
+ * - **The pull on open happens once per process**, for whoever opened it. Every
22
+ * later session starts against the same live data, and 「刷新」 is still the
23
+ * way to pull again.
24
+ *
25
+ * The registry lives on `globalThis` rather than module scope: two copies of the
26
+ * package (a versioned install plus a linked checkout, say) would otherwise each
27
+ * keep their own map and the collision would come back.
28
+ */
29
+ import type { Context } from '@deepseek-ai/cordis';
30
+ import { Vault } from './vault.js';
31
+ /**
32
+ * A consumer's claim on the process's vault.
33
+ *
34
+ * `current` is undefined while the domain is still loading and stays undefined
35
+ * when the open failed — a caller must report that, not pretend the vault is
36
+ * empty.
37
+ */
38
+ export interface VaultLease {
39
+ /** The vault, once it is open. */
40
+ current(): Vault | undefined;
41
+ /** The failure message, when the open attempt ended badly. */
42
+ failure(): string | undefined;
43
+ /** Give the claim back; the last one out closes the vault. */
44
+ release(): Promise<void>;
45
+ }
46
+ /**
47
+ * Claim the process's vault, opening it if this is the first claim.
48
+ *
49
+ * @param ctx - host context carrying the storage domain facility.
50
+ * @param onOpened - called once, by the instance that actually opened the
51
+ * domain (the first one), for work that belongs to "the vault just came up".
52
+ * @returns the lease; the caller releases it from its own disposer.
53
+ */
54
+ export declare function leaseVault(ctx: Context, onOpened?: (vault: Vault) => void): VaultLease;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure filtering over items.
3
+ *
4
+ * The official storage form is a key-value domain with no query language, so
5
+ * selection is an in-memory pass. At this vault's scale (thousands of records)
6
+ * that is the right trade: no index to build, no migration to run, and the
7
+ * whole rule set is testable without any storage at all.
8
+ */
9
+ import type { Category, Kind } from '../../shared/vocabulary.js';
10
+ import type { Item } from './spec.js';
11
+ export interface ItemQuery {
12
+ /** Case-insensitive substring match over title, linkTitle, text, url, note and tags. */
13
+ text?: string;
14
+ categories?: readonly Category[];
15
+ kinds?: readonly Kind[];
16
+ /** Filter to records flagged 待看 (or explicitly to those not flagged). */
17
+ watchLater?: boolean;
18
+ /** Every listed tag must be present (AND). */
19
+ tags?: readonly string[];
20
+ /** Soft-deleted items are excluded unless this is true. */
21
+ includeDeleted?: boolean;
22
+ limit?: number;
23
+ offset?: number;
24
+ }
25
+ /**
26
+ * Select and order items.
27
+ *
28
+ * Newest first — a vault is read as a timeline far more often than as an
29
+ * alphabetical list. `limit`/`offset` apply after ordering, so paging is stable
30
+ * as long as nothing is written in between.
31
+ *
32
+ * @param items - the candidate records, in any order.
33
+ * @param query - filters, paging and ordering input.
34
+ * @returns the matching records, newest first.
35
+ */
36
+ export declare function selectItems(items: readonly Item[], query?: ItemQuery): Item[];
37
+ /** Count the records flagged 待看 — the number the sidebar badge would show. */
38
+ export declare function countWatchLater(items: readonly Item[]): number;
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The vault's domain declaration: identity, version, layout, and the zod
3
+ * schemas every stored record must satisfy.
4
+ *
5
+ * Layout is `per-record` — one document per item — for two reasons that matter
6
+ * to a personal vault: a write only rewrites the item it touched, and a single
7
+ * damaged document cannot take the whole vault down with it.
8
+ *
9
+ * The default invalid-record behaviour is kept (the whole `open` rejects),
10
+ * because these records are authoritative user data: silently skipping one
11
+ * would hide a real problem. Additive schema changes should extend
12
+ * `compatibleVersions` instead of loosening this.
13
+ */
14
+ import { z } from 'zod';
15
+ export declare const itemSchema: z.ZodObject<{
16
+ id: z.ZodString;
17
+ kind: z.ZodEnum<{
18
+ link: "link";
19
+ text: "text";
20
+ image: "image";
21
+ file: "file";
22
+ }>;
23
+ category: z.ZodEnum<{
24
+ image: "image";
25
+ idea: "idea";
26
+ article: "article";
27
+ media: "media";
28
+ document: "document";
29
+ secret: "secret";
30
+ other: "other";
31
+ }>;
32
+ categorySource: z.ZodOptional<z.ZodEnum<{
33
+ rule: "rule";
34
+ model: "model";
35
+ user: "user";
36
+ }>>;
37
+ watchLater: z.ZodOptional<z.ZodBoolean>;
38
+ source: z.ZodEnum<{
39
+ panel: "panel";
40
+ chat: "chat";
41
+ webdav: "webdav";
42
+ import: "import";
43
+ }>;
44
+ createdAt: z.ZodString;
45
+ updatedAt: z.ZodString;
46
+ title: z.ZodOptional<z.ZodString>;
47
+ linkTitle: z.ZodOptional<z.ZodString>;
48
+ linkTitleError: z.ZodOptional<z.ZodString>;
49
+ text: z.ZodOptional<z.ZodString>;
50
+ secret: z.ZodOptional<z.ZodString>;
51
+ secretDigest: z.ZodOptional<z.ZodString>;
52
+ url: z.ZodOptional<z.ZodString>;
53
+ platform: z.ZodOptional<z.ZodString>;
54
+ note: z.ZodOptional<z.ZodString>;
55
+ deletedAt: z.ZodOptional<z.ZodString>;
56
+ tags: z.ZodArray<z.ZodString>;
57
+ attachmentIds: z.ZodArray<z.ZodString>;
58
+ }, z.core.$strip>;
59
+ export declare const attachmentSchema: z.ZodObject<{
60
+ id: z.ZodString;
61
+ storeId: z.ZodString;
62
+ mime: z.ZodString;
63
+ bytes: z.ZodNumber;
64
+ createdAt: z.ZodString;
65
+ filename: z.ZodOptional<z.ZodString>;
66
+ width: z.ZodOptional<z.ZodNumber>;
67
+ height: z.ZodOptional<z.ZodNumber>;
68
+ sha256: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strip>;
70
+ /**
71
+ * One global slot per domain. Sync state lives here because it belongs to the
72
+ * vault as a whole, not to any item; M6 fills it in.
73
+ */
74
+ export declare const vaultGlobalSchema: z.ZodObject<{
75
+ sync: z.ZodObject<{
76
+ lastPullAt: z.ZodOptional<z.ZodString>;
77
+ lastPushAt: z.ZodOptional<z.ZodString>;
78
+ cursor: z.ZodOptional<z.ZodString>;
79
+ }, z.core.$strip>;
80
+ master: z.ZodOptional<z.ZodObject<{
81
+ version: z.ZodNumber;
82
+ salt: z.ZodString;
83
+ kdf: z.ZodObject<{
84
+ n: z.ZodNumber;
85
+ r: z.ZodNumber;
86
+ p: z.ZodNumber;
87
+ }, z.core.$strip>;
88
+ verifier: z.ZodString;
89
+ }, z.core.$strip>>;
90
+ model: z.ZodOptional<z.ZodObject<{
91
+ day: z.ZodString;
92
+ calls: z.ZodNumber;
93
+ tokens: z.ZodNumber;
94
+ last: z.ZodOptional<z.ZodString>;
95
+ }, z.core.$strip>>;
96
+ }, z.core.$strip>;
97
+ export type Item = z.infer<typeof itemSchema>;
98
+ export type Attachment = z.infer<typeof attachmentSchema>;
99
+ export type VaultGlobal = z.infer<typeof vaultGlobalSchema>;
100
+ /**
101
+ * Domain name doubles as the backend unit name: `<DSH_HOME>/storages/dsh_inbox/…`.
102
+ * `defineDomain` enforces `/^[a-z][a-z0-9_]*$/` at module load — no hyphens.
103
+ */
104
+ export declare const vaultSpec: {
105
+ name: string;
106
+ /**
107
+ * Version 2 added the optional `categorySource`; version 3 swaps
108
+ * `status` for `watchLater` and drops the `待看` tag. Both older shapes still
109
+ * validate — the removed `status` key is simply ignored, and `watchLater` is
110
+ * optional — and `Vault.open` rewrites them once so the flag is real.
111
+ *
112
+ * Version 4 adds the optional `linkTitle` (the fetched page headline). It is
113
+ * a pure addition, so every older record still validates unchanged.
114
+ * Version 4 adds the optional `linkTitle` (the fetched page headline), version
115
+ * 5 the optional `linkTitleError` that explains a miss. Both are pure
116
+ * additions, so every older record still validates unchanged.
117
+ *
118
+ * Version 6 adds the optional `secret` / `secretDigest`: a credential's text
119
+ * moves out of `text` and into a sealed envelope. Also a pure addition — a
120
+ * version-5 record with plaintext simply gets migrated on the next unlock.
121
+ *
122
+ * Version 7 adds `sync.lastPushAt`: the cursor that keeps a push to "what
123
+ * changed since last time" instead of re-uploading the vault on every pass.
124
+ * A `global` field, so no record shape changes at all.
125
+ */
126
+ version: number;
127
+ compatibleVersions: number[];
128
+ layout: "per-record";
129
+ global: {
130
+ schema: z.ZodObject<{
131
+ sync: z.ZodObject<{
132
+ lastPullAt: z.ZodOptional<z.ZodString>;
133
+ lastPushAt: z.ZodOptional<z.ZodString>;
134
+ cursor: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strip>;
136
+ master: z.ZodOptional<z.ZodObject<{
137
+ version: z.ZodNumber;
138
+ salt: z.ZodString;
139
+ kdf: z.ZodObject<{
140
+ n: z.ZodNumber;
141
+ r: z.ZodNumber;
142
+ p: z.ZodNumber;
143
+ }, z.core.$strip>;
144
+ verifier: z.ZodString;
145
+ }, z.core.$strip>>;
146
+ model: z.ZodOptional<z.ZodObject<{
147
+ day: z.ZodString;
148
+ calls: z.ZodNumber;
149
+ tokens: z.ZodNumber;
150
+ last: z.ZodOptional<z.ZodString>;
151
+ }, z.core.$strip>>;
152
+ }, z.core.$strip>;
153
+ initial: {
154
+ sync: {};
155
+ };
156
+ };
157
+ tables: {
158
+ items: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
159
+ id: string;
160
+ kind: "link" | "text" | "image" | "file";
161
+ category: "image" | "idea" | "article" | "media" | "document" | "secret" | "other";
162
+ source: "panel" | "chat" | "webdav" | "import";
163
+ createdAt: string;
164
+ updatedAt: string;
165
+ tags: string[];
166
+ attachmentIds: string[];
167
+ categorySource?: "rule" | "model" | "user" | undefined;
168
+ watchLater?: boolean | undefined;
169
+ title?: string | undefined;
170
+ linkTitle?: string | undefined;
171
+ linkTitleError?: string | undefined;
172
+ text?: string | undefined;
173
+ secret?: string | undefined;
174
+ secretDigest?: string | undefined;
175
+ url?: string | undefined;
176
+ platform?: string | undefined;
177
+ note?: string | undefined;
178
+ deletedAt?: string | undefined;
179
+ }>;
180
+ attachments: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
181
+ id: string;
182
+ storeId: string;
183
+ mime: string;
184
+ bytes: number;
185
+ createdAt: string;
186
+ filename?: string | undefined;
187
+ width?: number | undefined;
188
+ height?: number | undefined;
189
+ sha256?: string | undefined;
190
+ }>;
191
+ };
192
+ };
193
+ export type VaultSpec = typeof vaultSpec;