@deepwatch/dsh-tools 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.
@@ -0,0 +1,237 @@
1
+ /**
2
+ * The read plane, host side: what a Watch mode asks, answered.
3
+ *
4
+ * A `conversation.view` entry is handed `{ inspect, onInspectDone }` and
5
+ * nothing else, so Live, Memory, Library and Compare had no way to obtain
6
+ * their own data. This is the other end of the seam that fixes that, and it is
7
+ * DSH's own: a Typert Remote, dispatched through the Gateway that already
8
+ * owns request correlation, abort signals and structured failure. The client
9
+ * calls `ctx.remote.watchQuery.librarySearch(request, signal)` or
10
+ * `.libraryGet(request, signal)` and awaits a `RemoteResult` carrying one of
11
+ * the concrete outcomes in `@deepwatch/dsh-contracts/query/wire`.
12
+ *
13
+ * One method per read, rather than one `read` over a discriminated union. DSH
14
+ * already routes by method, so a union inside a single entry point would be a
15
+ * second router with its own schema to generate.
16
+ *
17
+ * It reads the same `LibraryIndex` the `watch_library_search` tool reads. One
18
+ * index, one set of semantics, one place where "every term must match" is
19
+ * decided -- two would drift inside a release and disagree about what the
20
+ * library contains, and the disagreement would surface as a person searching
21
+ * the UI and the agent searching the tool getting different answers to the
22
+ * same question.
23
+ *
24
+ * Four things it will not do.
25
+ *
26
+ * It performs no write. Every operation answers a question, and the request
27
+ * union has no member that changes anything, so a surface cannot acquire a
28
+ * side effect and captured or model-generated content reaching these fields
29
+ * cannot become an action.
30
+ *
31
+ * It reads nothing the caller names. Parameters are identifiers from a charset
32
+ * with no separator or colon; the roots come from configuration. A caller
33
+ * cannot point this at a path.
34
+ *
35
+ * It answers within the deadline it was given, or refuses. A slow host must
36
+ * not become a hung surface, and the timer is cleared on every exit so a
37
+ * completed read leaves nothing behind.
38
+ *
39
+ * And it never reports a partial answer as a whole one. A rebuilding or stale
40
+ * index answers `complete: false` with what it has, because a search that
41
+ * quietly returns less than it should is worse than one that says it is
42
+ * behind.
43
+ *
44
+ * @module @deepwatch/dsh-tools/read-plane
45
+ */
46
+ import { type Context } from '@deepseek-ai/cordis';
47
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
48
+ import type { LibraryIndex } from '@deepwatch/dsh-library';
49
+ import type { CoreHealthRequest, CoreHealthResponse, LibraryGetRequest, LibraryGetResponse, LibraryRefreshRequest, LibraryRefreshResponse, LibraryIndexState, LibrarySearchRequest, LibrarySearchResponse, ProviderTestRequest, ProviderTestResponse, RouteReadinessRequest, RouteReadinessResponse } from '@deepwatch/dsh-contracts/query/wire';
50
+ import type { LibraryGenerations } from './library-generations.js';
51
+ /** What the read plane needs from its host. */
52
+ export interface ReadPlaneConfig {
53
+ /**
54
+ * The index to read, built and cached by whoever owns it.
55
+ *
56
+ * A function rather than a value so a rebuild behind the tool is visible
57
+ * here without either side holding a reference to a stale object.
58
+ */
59
+ readonly index: () => LibraryIndex;
60
+ /**
61
+ * Which workspace this host is answering for.
62
+ *
63
+ * Cursors are bound to it, so one issued here cannot be replayed against
64
+ * another workspace's snapshot.
65
+ */
66
+ readonly scope: string;
67
+ /**
68
+ * The one thing allowed to replace the index, when the host has one.
69
+ *
70
+ * Optional because a deployment may compose the read plane over an index it
71
+ * owns by other means. Where it is absent, `libraryRefresh` still exists and
72
+ * answers `refresh_failed` with a reason — which is what a surface should
73
+ * render, rather than a refresh that appears to work and changes nothing.
74
+ */
75
+ readonly generations?: LibraryGenerations;
76
+ }
77
+ /** Flatten one search result into the shape a surface renders. */
78
+ /**
79
+ * The service key, on the Context both faces share.
80
+ *
81
+ * This is not documentation. Typert analyses a package's public export graph
82
+ * and binds a Remote through the Cordis Context declaration; without this the
83
+ * service is discovered as a package and emits no artifact, because nothing
84
+ * ties `WatchQueryService` to the key `watchQuery` that the Gateway exposes as
85
+ * `ctx.remote.watchQuery`.
86
+ */
87
+ declare module '@deepseek-ai/cordis' {
88
+ interface Context {
89
+ watchQuery: WatchQueryService;
90
+ }
91
+ }
92
+ /**
93
+ * The Typert Remote a Watch surface calls.
94
+ *
95
+ * `watchQuery` is both the Cordis service key and the wire namespace, so the
96
+ * client reaches it as `ctx.remote.watchQuery`.
97
+ */
98
+ export declare class WatchQueryService extends TypertRemoteService {
99
+ /**
100
+ * Deliberately not a `#private` field.
101
+ *
102
+ * Cordis hands a Service to callers through a Proxy, and a private field is
103
+ * unreachable through one: the Gateway invoked this method and got
104
+ * "Cannot read private member #config from an object whose class did not
105
+ * declare it". Every direct unit test passed, because a direct call has no
106
+ * proxy in front of it -- which is the whole argument for exercising this
107
+ * through the real Gateway.
108
+ */
109
+ readonly config: ReadPlaneConfig;
110
+ constructor(ctx: Context, config: ReadPlaneConfig);
111
+ /**
112
+ * One concrete method per read, rather than one `read` over a union.
113
+ *
114
+ * DSH already routes by method, so a discriminated union inside a single
115
+ * entry point would be a second router with its own schema to generate.
116
+ * One request type and one response type per method is what Typert emits
117
+ * a strict codec from most directly.
118
+ *
119
+ * `signal` is last, as Typert requires, and is not serialised.
120
+ */
121
+ librarySearch(request: LibrarySearchRequest, signal: AbortSignal): Promise<LibrarySearchResponse>;
122
+ /** One record by id. A direct lookup, not a one-result search. */
123
+ libraryGet(request: LibraryGetRequest, signal: AbortSignal): Promise<LibraryGetResponse>;
124
+ /**
125
+ * Read the roots again, and put the result into service if it is healthy.
126
+ *
127
+ * The only method here with a side effect, and the only one that is not a
128
+ * question. It is a separate method for exactly that reason: a `rebuild`
129
+ * flag on `librarySearch` would make every search a potential re-read of the
130
+ * corpus, and would leave a caller no way to ask for an answer from what the
131
+ * host already has.
132
+ *
133
+ * Genuinely async, unlike its siblings: the rebuild yields between files so
134
+ * a caller that stops waiting can be observed doing so.
135
+ */
136
+ libraryRefresh(request: LibraryRefreshRequest, signal: AbortSignal): Promise<LibraryRefreshResponse>;
137
+ /**
138
+ * What Watch Core is doing right now, read from the running Bridge.
139
+ *
140
+ * The one method here that is not about the Library, and it is here because
141
+ * this is the only channel the browser has to the Host. Diagnostics used to
142
+ * render "Connected over stdio" and a version number as literals in a
143
+ * component, because there was nowhere to read them from.
144
+ *
145
+ * Nothing is defaulted. A value the Bridge has not established is `null`,
146
+ * and the panel renders that as "not reported" -- which is worth less than a
147
+ * real reading and far more than a confident wrong one.
148
+ */
149
+ coreHealth(request: CoreHealthRequest, signal: AbortSignal): Promise<CoreHealthResponse>;
150
+ /** Spend one deliberately tiny provider request only after a person asks. */
151
+ providerTest(request: ProviderTestRequest, signal: AbortSignal): Promise<ProviderTestResponse>;
152
+ /**
153
+ * Whether the Host would serve this route right now, asked without spending
154
+ * anything.
155
+ *
156
+ * The browser half used to answer this from its own memory of a provider
157
+ * test it had run, which is a claim about a Host it cannot see. A tab that
158
+ * stayed open across a Host restart, or across an edit made in another tab,
159
+ * kept drawing a tested badge over a route the Host had already stopped
160
+ * being willing to serve — and the composer it gates opened onto a refusal.
161
+ * There is one answer, and this is where it is read from.
162
+ */
163
+ routeReadiness(request: RouteReadinessRequest, signal: AbortSignal): Promise<RouteReadinessResponse>;
164
+ }
165
+ /**
166
+ * Read the Host's verdict for one route.
167
+ *
168
+ * No network, no provider, no credential. When the provenance row is not
169
+ * composed the honest answer is that nothing here can say, which reads as
170
+ * unproved — the same direction the guard fails in.
171
+ */
172
+ export declare function readRouteReadiness(request: RouteReadinessRequest, ctx: Context): RouteReadinessResponse;
173
+ /** Execute a provider request without returning or logging model output. */
174
+ export declare function testProvider(request: ProviderTestRequest, ctx: Context, signal: AbortSignal): Promise<ProviderTestResponse>;
175
+ /** Route a parsed request to the namespace that answers it. */
176
+ /** Answer a Library read from the index the tool already owns. */
177
+ /** Assemble a snapshot, and say honestly whether it is whole. */
178
+ /** Install the read plane onto a host context. */
179
+ export declare function applyReadPlane(ctx: Context, config: ReadPlaneConfig): void;
180
+ /**
181
+ * Answer a Library search against the shared index.
182
+ *
183
+ * Separate from the Service so the whole path is testable without a DSH
184
+ * runtime, and so the Service stays a Typert adapter with no decisions in it.
185
+ */
186
+ export declare function searchLibrary(request: LibrarySearchRequest, config: ReadPlaneConfig, signal: AbortSignal): LibrarySearchResponse;
187
+ /**
188
+ * The wire state for one index, without collapsing four answers into two.
189
+ *
190
+ * The defect this replaces was a single conditional: anything that was not
191
+ * `ready` became `stale`, so an index over an empty store reported "Index is
192
+ * behind the store". A person looking at a fresh profile was told their
193
+ * Library was out of date with respect to nothing, and the only honest reading
194
+ * of that screen — something is wrong — was the wrong one.
195
+ *
196
+ * `empty` and `stale` are opposite claims. Empty says the index agrees with a
197
+ * store that holds nothing. Stale says the store moved and the index has not
198
+ * caught up. Reporting the first as the second turns "there is nothing here
199
+ * yet" into "you are missing something", which is the difference between a
200
+ * quiet first run and a bug report.
201
+ */
202
+ export declare function wireIndexState(health: LibraryIndex['health'], size: number): LibraryIndexState;
203
+ /**
204
+ * Answer a Library get.
205
+ *
206
+ * `index.record()` is a keyed lookup. Implementing this as a search with
207
+ * `limit: 1` and then checking whether the single result happened to be the
208
+ * requested id reports every record except the top-ranked one as absent.
209
+ */
210
+ export declare function getLibraryRecord(request: LibraryGetRequest, config: ReadPlaneConfig, signal: AbortSignal): LibraryGetResponse;
211
+ /**
212
+ * Rebuild the index, and say what happened to the one already in service.
213
+ *
214
+ * Every outcome leaves a searchable Library, which is why none of them is an
215
+ * exception. A refusal, an elapsed deadline, an abandoned rebuild and a failed
216
+ * one are four different facts, and a surface renders each differently.
217
+ *
218
+ * Separate from the Service for the same reason the reads are: the Service
219
+ * stays a Typert adapter with no decisions in it, and the whole path is
220
+ * testable without a DSH runtime.
221
+ */
222
+ export declare function refreshLibrary(request: LibraryRefreshRequest, config: ReadPlaneConfig, signal: AbortSignal): Promise<LibraryRefreshResponse>;
223
+ /**
224
+ * Read the Bridge's live state, and say honestly where it could not.
225
+ *
226
+ * Separate from the Service for the same reason the Library readers are: the
227
+ * Service is a Typert adapter with no decisions in it, and the whole path is
228
+ * testable without a DSH runtime.
229
+ *
230
+ * The rule this function exists to keep: **no field is defaulted.** A version
231
+ * the Bridge has never received is `null`, not `'unknown'` and not the version
232
+ * this build was compiled against. Diagnostics is the screen people open when
233
+ * they already suspect something is wrong, and it is the last place a
234
+ * plausible substitute belongs.
235
+ */
236
+ export declare function readCoreHealth(request: CoreHealthRequest, ctx: Context, signal: AbortSignal): CoreHealthResponse;
237
+ //# sourceMappingURL=read-plane.d.ts.map