@deepwatch/dsh-library 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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/lib/client/components.d.ts +75 -0
- package/lib/client/components.js +60 -0
- package/lib/client/index.d.ts +46 -0
- package/lib/client/index.js +52 -0
- package/lib/client/library-mode.d.ts +37 -0
- package/lib/client/library-mode.js +21 -0
- package/lib/client/read-plane.d.ts +134 -0
- package/lib/client/read-plane.js +193 -0
- package/lib/client/search-view.d.ts +44 -0
- package/lib/client/search-view.js +233 -0
- package/lib/client.js +1882 -0
- package/lib/client.js.map +1 -0
- package/lib/index-store.d.ts +221 -0
- package/lib/index-store.js +570 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +20 -0
- package/lib/search.d.ts +129 -0
- package/lib/search.js +180 -0
- package/lib/sources.d.ts +146 -0
- package/lib/sources.js +135 -0
- package/package.json +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 oxbshw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @deepwatch/dsh-library
|
|
2
|
+
|
|
3
|
+
Library — sources, revisions, index state, search, facets and collections
|
|
4
|
+
|
|
5
|
+
Part of **DeepWatch** — the agent workspace built on the official
|
|
6
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
7
|
+
and powered by [Watch Skill](https://github.com/oxbshw/watch-skill) for perception, evidence, memory and
|
|
8
|
+
independent verification.
|
|
9
|
+
|
|
10
|
+
> **Host plugin — runs beside the agent in the DSH process.**
|
|
11
|
+
> Composed by the bundle.
|
|
12
|
+
|
|
13
|
+
## Exports
|
|
14
|
+
|
|
15
|
+
- `@deepwatch/dsh-library`
|
|
16
|
+
- `@deepwatch/dsh-library/client`
|
|
17
|
+
- `@deepwatch/dsh-library/components`
|
|
18
|
+
- `@deepwatch/dsh-library/index-store`
|
|
19
|
+
- `@deepwatch/dsh-library/library-mode`
|
|
20
|
+
- `@deepwatch/dsh-library/read-plane`
|
|
21
|
+
- `@deepwatch/dsh-library/search-view`
|
|
22
|
+
|
|
23
|
+
## Peers
|
|
24
|
+
|
|
25
|
+
Provided by the host rather than installed here:
|
|
26
|
+
|
|
27
|
+
- `@deepseek-ai/cordis@4.0.2`
|
|
28
|
+
- `@deepseek-ai/dsh-typert-protocol@0.1.1-rc.2`
|
|
29
|
+
- `react@^18.2.0`
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
> **Not on npm yet.** Nothing exists under the `@deepwatch` scope. This
|
|
34
|
+
> package is published for the first time by the `deepwatch-v0.1.0`
|
|
35
|
+
> release; until then the command below resolves nothing, and
|
|
36
|
+
> [the workspace README](https://github.com/oxbshw/watch-skill/tree/main/workspace#readme) has the path
|
|
37
|
+
> that works from a checkout.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
npm install @deepwatch/dsh-library
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Rarely on its own. [`@deepwatch/dsh-bundle`](https://github.com/oxbshw/watch-skill/tree/main/workspace/packages/watch/bundle#readme)
|
|
44
|
+
composes this package with the rest of DeepWatch and is what a profile
|
|
45
|
+
normally depends on; installing this one directly is for embedding a
|
|
46
|
+
single piece in a composition you control.
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Node `^22.19.0 || >=24.0.0`
|
|
51
|
+
- The peers above, supplied by the host composition
|
|
52
|
+
|
|
53
|
+
A Bridge connected to Core, and `libraryRoots` naming the directories the index may read — empty by default, so a deployment that has not said where its evidence lives gets a tool reporting nothing to search rather than one guessing.
|
|
54
|
+
|
|
55
|
+
## Stability
|
|
56
|
+
|
|
57
|
+
`0.1.0` — a stable release.
|
|
58
|
+
|
|
59
|
+
Stable means tested, documented and supported — not 1.0. This is a
|
|
60
|
+
pre-1.0 line, and semantic versioning gives `0.x` no compatibility
|
|
61
|
+
guarantee across minor versions: **a `0.MINOR` bump may change or remove
|
|
62
|
+
surface, and a patch will not.** Depend on it with a tilde range
|
|
63
|
+
(`~0.1.0`) if you want that difference enforced by your lockfile
|
|
64
|
+
rather than by a changelog. The usual major-version promise starts at 1.0.
|
|
65
|
+
|
|
66
|
+
## Side effects
|
|
67
|
+
|
|
68
|
+
Importing a module from this package evaluates no side effects, so a
|
|
69
|
+
bundler may drop what a build does not use. Mounting it in a host is a
|
|
70
|
+
separate matter: what it then reads or writes is governed by the
|
|
71
|
+
workspace boundary and the host's permissions, not by this flag.
|
|
72
|
+
|
|
73
|
+
## Where this fits
|
|
74
|
+
|
|
75
|
+
The catalogue behind the Library screen: sources, revisions, index state, search, facets and collections.
|
|
76
|
+
|
|
77
|
+
The twenty packages and how they compose:
|
|
78
|
+
[the package map](https://github.com/oxbshw/watch-skill/blob/main/workspace/docs/packages.md).
|
|
79
|
+
Running DeepWatch, and the gates a change has to pass:
|
|
80
|
+
[the workspace README](https://github.com/oxbshw/watch-skill/tree/main/workspace#readme).
|
|
81
|
+
|
|
82
|
+
## Attribution
|
|
83
|
+
|
|
84
|
+
Built on DeepSeek Harness · Powered by Watch Skill
|
|
85
|
+
|
|
86
|
+
DeepWatch and Watch Skill are independent projects and are not affiliated
|
|
87
|
+
with or endorsed by DeepSeek. MIT licensed; third-party notices are in
|
|
88
|
+
[THIRD_PARTY_NOTICES.md](https://github.com/oxbshw/watch-skill/blob/main/workspace/THIRD_PARTY_NOTICES.md).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Library surface.
|
|
3
|
+
*
|
|
4
|
+
* Visually and structurally separate from Memory, and that separation is
|
|
5
|
+
* enforced rather than encouraged: this module imports nothing from the memory
|
|
6
|
+
* packages, so a memory record cannot be rendered here even by mistake. The
|
|
7
|
+
* two surfaces answer different questions — what has been seen, and what is
|
|
8
|
+
* believed — and a person needs to be able to tell at a glance which one they
|
|
9
|
+
* are looking at.
|
|
10
|
+
*
|
|
11
|
+
* Every result says how it was found and whether it is still current. A search
|
|
12
|
+
* result that showed neither would be a list of claims about a library whose
|
|
13
|
+
* state the reader cannot check.
|
|
14
|
+
*
|
|
15
|
+
* @module @deepwatch/dsh-library/components
|
|
16
|
+
*/
|
|
17
|
+
import type { ReactNode } from 'react';
|
|
18
|
+
import type { Freshness } from '@deepwatch/dsh-contracts';
|
|
19
|
+
import { type Source, type SourceRevision } from '../sources.js';
|
|
20
|
+
import { type Facets, type SearchHit, type SearchPlan, type SearchResult } from '../search.js';
|
|
21
|
+
/** Props for {@link FreshnessBadge}. */
|
|
22
|
+
export interface FreshnessBadgeProps {
|
|
23
|
+
readonly freshness: Freshness;
|
|
24
|
+
}
|
|
25
|
+
/** Freshness as glyph, word and tone. */
|
|
26
|
+
export declare function FreshnessBadge({ freshness }: FreshnessBadgeProps): ReactNode;
|
|
27
|
+
/** Props for {@link RevisionHistory}. */
|
|
28
|
+
export interface RevisionHistoryProps {
|
|
29
|
+
readonly source: Source;
|
|
30
|
+
readonly onOpen: (revision: SourceRevision) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A source's revisions, newest last.
|
|
34
|
+
*
|
|
35
|
+
* Every revision is listed, including superseded ones, and every one is
|
|
36
|
+
* openable. A history that showed only the current revision would make old
|
|
37
|
+
* evidence unreachable through the interface even though it remains
|
|
38
|
+
* addressable underneath, which is the same failure with extra steps.
|
|
39
|
+
*/
|
|
40
|
+
export declare function RevisionHistory({ source, onOpen }: RevisionHistoryProps): ReactNode;
|
|
41
|
+
/** Props for {@link SearchHitRow}. */
|
|
42
|
+
export interface SearchHitRowProps {
|
|
43
|
+
readonly hit: SearchHit;
|
|
44
|
+
readonly freshness: Freshness;
|
|
45
|
+
readonly onOpen: (hit: SearchHit) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One hit.
|
|
49
|
+
*
|
|
50
|
+
* The retrieval path is on the row, not in a legend. A person reading a
|
|
51
|
+
* semantic hit needs to know it is a semantic hit at the moment they read it,
|
|
52
|
+
* because that is what decides whether they should check it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function SearchHitRow({ hit, freshness, onOpen }: SearchHitRowProps): ReactNode;
|
|
55
|
+
/** Props for {@link FacetPanel}. */
|
|
56
|
+
export interface FacetPanelProps {
|
|
57
|
+
readonly facets: Facets;
|
|
58
|
+
readonly onFilter: (facet: string, value: string) => void;
|
|
59
|
+
}
|
|
60
|
+
/** The facet rail. Only values that actually occur are offered. */
|
|
61
|
+
export declare function FacetPanel({ facets, onFilter }: FacetPanelProps): ReactNode;
|
|
62
|
+
/** Props for {@link LibrarySurface}. */
|
|
63
|
+
export interface LibrarySurfaceProps {
|
|
64
|
+
readonly plan: SearchPlan;
|
|
65
|
+
readonly results: readonly SearchResult[];
|
|
66
|
+
readonly facets: Facets;
|
|
67
|
+
readonly sources: readonly Source[];
|
|
68
|
+
readonly freshnessOf: (hit: SearchHit) => Freshness;
|
|
69
|
+
readonly onOpenHit: (hit: SearchHit) => void;
|
|
70
|
+
readonly onOpenRevision: (revision: SourceRevision) => void;
|
|
71
|
+
readonly onFilter: (facet: string, value: string) => void;
|
|
72
|
+
}
|
|
73
|
+
/** The Library mode body. */
|
|
74
|
+
export declare function LibrarySurface(props: LibrarySurfaceProps): ReactNode;
|
|
75
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { toneFor, tokenFor } from '@deepwatch/dsh-client-brand';
|
|
3
|
+
import { currentRevision, } from '../sources.js';
|
|
4
|
+
import { describeSearch, } from '../search.js';
|
|
5
|
+
/** The glyph half of a freshness state, so colour is never the only signal. */
|
|
6
|
+
const FRESHNESS_GLYPH = {
|
|
7
|
+
current: '●',
|
|
8
|
+
stale: '⌛',
|
|
9
|
+
gap: '⌇',
|
|
10
|
+
expired: '⊘',
|
|
11
|
+
unavailable: '?',
|
|
12
|
+
};
|
|
13
|
+
/** Freshness as glyph, word and tone. */
|
|
14
|
+
export function FreshnessBadge({ freshness }) {
|
|
15
|
+
return (_jsxs("span", { "data-watch-freshness": freshness, style: { color: tokenFor(toneFor(freshness)) }, children: [_jsx("span", { "aria-hidden": "true", children: FRESHNESS_GLYPH[freshness] }), _jsx("span", { children: ` ${freshness}` })] }));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A source's revisions, newest last.
|
|
19
|
+
*
|
|
20
|
+
* Every revision is listed, including superseded ones, and every one is
|
|
21
|
+
* openable. A history that showed only the current revision would make old
|
|
22
|
+
* evidence unreachable through the interface even though it remains
|
|
23
|
+
* addressable underneath, which is the same failure with extra steps.
|
|
24
|
+
*/
|
|
25
|
+
export function RevisionHistory({ source, onOpen }) {
|
|
26
|
+
const current = currentRevision(source);
|
|
27
|
+
return (_jsx("ol", { "data-watch-revisions": source.sourceId, style: { listStyle: 'none', margin: 0, padding: 0 }, children: source.revisions.map(revision => (_jsxs("li", { "data-watch-revision": revision.sourceRevisionId, children: [_jsxs("button", { type: "button", "data-watch-index-state": revision.indexState, "aria-current": current?.sourceRevisionId === revision.sourceRevisionId ? 'true' : undefined, onClick: () => { onOpen(revision); }, style: { font: 'inherit', color: 'inherit', background: 'none', border: 'none', cursor: 'pointer' }, children: [_jsx("span", { dir: "ltr", children: `r${String(revision.revision)}` }), _jsx("span", { children: ` ${revision.indexState}` }), _jsx("time", { dateTime: revision.observedAt, children: ` ${revision.observedAt}` }), current?.sourceRevisionId === revision.sourceRevisionId && _jsx("span", { children: ' · current' })] }), revision.indexError !== null && (_jsx("span", { "data-watch-index-error": "", children: ` ${revision.indexError}` }))] }, revision.sourceRevisionId))) }));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One hit.
|
|
31
|
+
*
|
|
32
|
+
* The retrieval path is on the row, not in a legend. A person reading a
|
|
33
|
+
* semantic hit needs to know it is a semantic hit at the moment they read it,
|
|
34
|
+
* because that is what decides whether they should check it.
|
|
35
|
+
*/
|
|
36
|
+
export function SearchHitRow({ hit, freshness, onOpen }) {
|
|
37
|
+
return (_jsx("li", { "data-watch-hit": hit.sourceRevisionId, "data-watch-path": hit.path, children: _jsxs("button", { type: "button", onClick: () => { onOpen(hit); }, style: { font: 'inherit', color: 'inherit', background: 'none', border: 'none', cursor: 'pointer', textAlign: 'start' }, children: [hit.range !== null && (_jsx("span", { dir: "ltr", style: { fontVariantNumeric: 'tabular-nums' }, children: `${String(Math.floor(hit.range.startMs / 1000))}s ` })), _jsx("span", { dir: "auto", children: hit.text }), _jsx("span", { "data-watch-hit-path": hit.path, children: ` (${hit.path})` }), _jsx(FreshnessBadge, { freshness: freshness })] }) }));
|
|
38
|
+
}
|
|
39
|
+
/** The facet rail. Only values that actually occur are offered. */
|
|
40
|
+
export function FacetPanel({ facets, onFilter }) {
|
|
41
|
+
const groups = [
|
|
42
|
+
['kind', facets.kind],
|
|
43
|
+
['indexState', facets.indexState],
|
|
44
|
+
['collection', facets.collection],
|
|
45
|
+
['script', facets.script],
|
|
46
|
+
['path', facets.path],
|
|
47
|
+
];
|
|
48
|
+
return (_jsx("aside", { "data-watch-facets": "", "aria-label": "Filters", children: groups.map(([name, values]) => (values.length === 0 ? null : (_jsxs("section", { "data-watch-facet": name, children: [_jsx("h3", { style: { font: 'inherit', fontSize: '11px' }, children: name }), _jsx("ul", { style: { listStyle: 'none', margin: 0, padding: 0 }, children: values.map(value => (_jsx("li", { children: _jsx("button", { type: "button", "data-watch-facet-value": value.value, onClick: () => { onFilter(name, value.value); }, style: { font: 'inherit', color: 'inherit', background: 'none', border: 'none', cursor: 'pointer' }, children: `${value.value} (${String(value.count)})` }) }, value.value))) })] }, name)))) }));
|
|
49
|
+
}
|
|
50
|
+
/** The Library mode body. */
|
|
51
|
+
export function LibrarySurface(props) {
|
|
52
|
+
const byId = new Map(props.sources.map(source => [source.sourceId, source]));
|
|
53
|
+
return (_jsxs("section", { "data-watch-library": "", "aria-label": "Library", children: [_jsx("p", { "data-watch-search-plan": props.plan.path, children: describeSearch(props.plan, props.results) }), props.plan.fix !== '' && _jsx("p", { "data-watch-search-fix": "", children: props.plan.fix }), _jsx(FacetPanel, { facets: props.facets, onFilter: props.onFilter }), props.results.length === 0
|
|
54
|
+
? _jsx("p", { "data-watch-library-empty": "", children: "Nothing in the Library matches." })
|
|
55
|
+
: props.results.map(result => {
|
|
56
|
+
const source = byId.get(result.sourceId);
|
|
57
|
+
return (_jsxs("article", { "data-watch-source": result.sourceId, children: [_jsx("h3", { style: { font: 'inherit' }, dir: "auto", children: result.title }), _jsx("span", { "data-watch-source-kind": result.kind, children: result.kind }), !result.current && (_jsx("span", { "data-watch-source-superseded": "", children: ' The source has changed since these were observed.' })), _jsx("ul", { style: { listStyle: 'none', margin: 0, padding: 0 }, children: result.hits.map(hit => (_jsx(SearchHitRow, { hit: hit, freshness: props.freshnessOf(hit), onOpen: props.onOpenHit }, `${hit.sourceRevisionId}:${String(hit.range?.startMs ?? 0)}:${hit.text}`))) }), source !== undefined && (_jsx(RevisionHistory, { source: source, onOpen: props.onOpenRevision }))] }, result.sourceId));
|
|
58
|
+
})] }));
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Library surface, registered into DSH's slots.
|
|
3
|
+
*
|
|
4
|
+
* @module @deepwatch/dsh-library/client
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
export * from './components.js';
|
|
8
|
+
export * from './search-view.js';
|
|
9
|
+
export * from './library-mode.js';
|
|
10
|
+
export * from './read-plane.js';
|
|
11
|
+
export * from '../sources.js';
|
|
12
|
+
export * from '../search.js';
|
|
13
|
+
/**
|
|
14
|
+
* Services this half needs before it can register anything.
|
|
15
|
+
*
|
|
16
|
+
* `remote.watchQuery` as well as `remote`. The Gateway installs each mounted
|
|
17
|
+
* namespace as its own cordis service under that key, so naming it is what
|
|
18
|
+
* makes this plugin wait for the mount rather than load beside it: the bare
|
|
19
|
+
* `remote` resolves as soon as the Gateway's browser half exists — which is
|
|
20
|
+
* before any contribution is mounted — and leaves `ctx.remote` with no
|
|
21
|
+
* `watchQuery` on it. Both are listed because both are read, and cordis
|
|
22
|
+
* refuses a property no `inject` entry claims: reaching `ctx.remote.watchQuery`
|
|
23
|
+
* on the strength of the second entry alone fails the fiber with "cannot get
|
|
24
|
+
* property "remote" without inject".
|
|
25
|
+
*
|
|
26
|
+
* The mount itself belongs to `@deepwatch/dsh-client-remotes`. This package
|
|
27
|
+
* owns the Library capability, and a package that owns a capability does not
|
|
28
|
+
* also own the transport that carries it; when it did, the two depended on
|
|
29
|
+
* each other.
|
|
30
|
+
*/
|
|
31
|
+
export declare const inject: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Register the Library mode body, bound to the read plane it queries.
|
|
34
|
+
*
|
|
35
|
+
* A `conversation.view` entry is handed `{ inspect, onInspectDone }` and
|
|
36
|
+
* nothing else, so a mode body has no way to reach a service on its own. The
|
|
37
|
+
* binding happens here, where the context is: what gets registered is the mode
|
|
38
|
+
* body with the mounted `watchQuery` namespace already supplied.
|
|
39
|
+
*
|
|
40
|
+
* Nothing here is defensive about `ctx.remote.watchQuery`. `inject` above means
|
|
41
|
+
* cordis does not call `apply` until that service exists, so a profile without
|
|
42
|
+
* the mount parks this plugin — no Library tab at all — rather than drawing a
|
|
43
|
+
* tab whose search quietly answers from an empty local index.
|
|
44
|
+
*/
|
|
45
|
+
export declare function apply(ctx: Context): void;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { LibraryModeView } from './library-mode.js';
|
|
3
|
+
export * from './components.js';
|
|
4
|
+
export * from './search-view.js';
|
|
5
|
+
export * from './library-mode.js';
|
|
6
|
+
export * from './read-plane.js';
|
|
7
|
+
export * from '../sources.js';
|
|
8
|
+
export * from '../search.js';
|
|
9
|
+
/**
|
|
10
|
+
* Services this half needs before it can register anything.
|
|
11
|
+
*
|
|
12
|
+
* `remote.watchQuery` as well as `remote`. The Gateway installs each mounted
|
|
13
|
+
* namespace as its own cordis service under that key, so naming it is what
|
|
14
|
+
* makes this plugin wait for the mount rather than load beside it: the bare
|
|
15
|
+
* `remote` resolves as soon as the Gateway's browser half exists — which is
|
|
16
|
+
* before any contribution is mounted — and leaves `ctx.remote` with no
|
|
17
|
+
* `watchQuery` on it. Both are listed because both are read, and cordis
|
|
18
|
+
* refuses a property no `inject` entry claims: reaching `ctx.remote.watchQuery`
|
|
19
|
+
* on the strength of the second entry alone fails the fiber with "cannot get
|
|
20
|
+
* property "remote" without inject".
|
|
21
|
+
*
|
|
22
|
+
* The mount itself belongs to `@deepwatch/dsh-client-remotes`. This package
|
|
23
|
+
* owns the Library capability, and a package that owns a capability does not
|
|
24
|
+
* also own the transport that carries it; when it did, the two depended on
|
|
25
|
+
* each other.
|
|
26
|
+
*/
|
|
27
|
+
export const inject = ['slots', 'remote', 'remote.watchQuery'];
|
|
28
|
+
/**
|
|
29
|
+
* Register the Library mode body, bound to the read plane it queries.
|
|
30
|
+
*
|
|
31
|
+
* A `conversation.view` entry is handed `{ inspect, onInspectDone }` and
|
|
32
|
+
* nothing else, so a mode body has no way to reach a service on its own. The
|
|
33
|
+
* binding happens here, where the context is: what gets registered is the mode
|
|
34
|
+
* body with the mounted `watchQuery` namespace already supplied.
|
|
35
|
+
*
|
|
36
|
+
* Nothing here is defensive about `ctx.remote.watchQuery`. `inject` above means
|
|
37
|
+
* cordis does not call `apply` until that service exists, so a profile without
|
|
38
|
+
* the mount parks this plugin — no Library tab at all — rather than drawing a
|
|
39
|
+
* tab whose search quietly answers from an empty local index.
|
|
40
|
+
*/
|
|
41
|
+
export function apply(ctx) {
|
|
42
|
+
const slots = ctx.slots;
|
|
43
|
+
const reads = ctx.remote.watchQuery;
|
|
44
|
+
/** The Library body, bound to the host that answers for it. */
|
|
45
|
+
const BoundLibraryModeView = (props) => (_jsx(LibraryModeView, { ...props, reads: reads }));
|
|
46
|
+
// Library is a product mode. See the note in the Live surface: registering as
|
|
47
|
+
// a view means DSH renders the tab, not Watch.
|
|
48
|
+
slots.inject('conversation.view', () => {
|
|
49
|
+
slots.register({ name: 'conversation.view', id: 'library', label: 'Library', order: 50 }, BoundLibraryModeView);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Library mode body.
|
|
3
|
+
*
|
|
4
|
+
* It lives in the package that owns the capability rather than in the workspace
|
|
5
|
+
* shell — the shell provides the scaffold every mode shares, and a mode that
|
|
6
|
+
* also needed something back from its own package made the two depend on each
|
|
7
|
+
* other. TypeScript refused the circular project reference, which was the right
|
|
8
|
+
* answer to the wrong arrangement.
|
|
9
|
+
*
|
|
10
|
+
* @module @deepwatch/dsh-library/client/library-mode
|
|
11
|
+
*/
|
|
12
|
+
import type { ReactNode } from 'react';
|
|
13
|
+
import type { ModeViewProps } from '@deepwatch/dsh-workspace/surface';
|
|
14
|
+
import type { IndexableRecord } from '../index-store.js';
|
|
15
|
+
import type { WatchQueryRemote } from './read-plane.js';
|
|
16
|
+
/** What the Library body needs beyond the standard view props. */
|
|
17
|
+
export interface LibraryModeProps extends ModeViewProps {
|
|
18
|
+
/**
|
|
19
|
+
* Records to index.
|
|
20
|
+
*
|
|
21
|
+
* The evidence store stays the source of truth; the index is derived from
|
|
22
|
+
* this and can be thrown away at any time.
|
|
23
|
+
*/
|
|
24
|
+
readonly records?: readonly IndexableRecord[];
|
|
25
|
+
/**
|
|
26
|
+
* The mounted `ctx.remote.watchQuery` namespace, when there is one.
|
|
27
|
+
*
|
|
28
|
+
* Bound by the registration in `./index.tsx`, because a `conversation.view`
|
|
29
|
+
* entry receives only `{ inspect, onInspectDone }` and cannot reach a
|
|
30
|
+
* service itself. Absent when this body is rendered outside a profile — in a
|
|
31
|
+
* test, or a story — and the search then answers from the local index.
|
|
32
|
+
*/
|
|
33
|
+
readonly reads?: WatchQueryRemote | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** The Library mode: everything recorded, and searchable. */
|
|
36
|
+
export declare function LibraryModeView({ inspect, records, reads }?: LibraryModeProps): ReactNode;
|
|
37
|
+
//# sourceMappingURL=library-mode.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { parseVerdict } from '@deepwatch/dsh-contracts';
|
|
3
|
+
import { Facts, ModeSurface, Panel, readToolResult } from '@deepwatch/dsh-workspace/surface';
|
|
4
|
+
import { LibrarySearch } from './search-view.js';
|
|
5
|
+
/** The Library mode: everything recorded, and searchable. */
|
|
6
|
+
export function LibraryModeView({ inspect, records = [], reads } = {}) {
|
|
7
|
+
const selected = parseVerdict(readToolResult(inspect));
|
|
8
|
+
return (_jsxs(ModeSurface, { title: "Library", lead: reads === undefined
|
|
9
|
+
? 'Every source and every piece of evidence this workspace has recorded. '
|
|
10
|
+
+ 'Search runs on this machine — no service, no model, nothing leaves it.'
|
|
11
|
+
: 'Every source and every piece of evidence this workspace has recorded. '
|
|
12
|
+
+ 'Search runs on this workspace’s own host — no service, no model, '
|
|
13
|
+
+ 'nothing leaves the machine it runs on.', children: [selected === null
|
|
14
|
+
? null
|
|
15
|
+
: (_jsx(Panel, { heading: "Selected record", children: _jsx(Facts, { rows: [
|
|
16
|
+
['Verdict', selected.verdict],
|
|
17
|
+
['Reason', selected.reason],
|
|
18
|
+
['Checks', String(selected.checks.length)],
|
|
19
|
+
] }) })), _jsx(LibrarySearch, { records: records, reads: reads })] }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=library-mode.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Library's end of the read plane: what the surface asks, and what it does
|
|
3
|
+
* with the answer.
|
|
4
|
+
*
|
|
5
|
+
* The Host end is `@deepwatch/dsh-tools`, which registers `WatchQueryService`
|
|
6
|
+
* and lets Typert generate a strict Remote from it. The Library does not import
|
|
7
|
+
* that generated artifact and does not mount it. Doing either would make the
|
|
8
|
+
* package that owns the Library capability depend on the package that reads it,
|
|
9
|
+
* which is the cycle `@deepwatch/dsh-client-remotes` exists to remove.
|
|
10
|
+
*
|
|
11
|
+
* So the namespace is described here from the contracts both ends already
|
|
12
|
+
* share. `@deepwatch/dsh-contracts/query/wire` is the single definition of
|
|
13
|
+
* every request and response on this wire — the generated declaration imports
|
|
14
|
+
* its types from exactly that module — and `RemoteResult` is upstream's own
|
|
15
|
+
* envelope. Nothing below restates a shape either side owns.
|
|
16
|
+
*
|
|
17
|
+
* That leaves one thing a shared contract cannot prove: that the namespace
|
|
18
|
+
* really is called `watchQuery` and really carries these two methods. Two
|
|
19
|
+
* things hold it. `@deepwatch/dsh-client-remotes` compares this interface
|
|
20
|
+
* against the generated one at compile time, so a changed signature stops the
|
|
21
|
+
* build; and `tests/remote-client-mount.test.mjs` mounts the real contribution
|
|
22
|
+
* through the real Gateway and calls it, so a changed *name* fails a test
|
|
23
|
+
* rather than a page.
|
|
24
|
+
*
|
|
25
|
+
* @module @deepwatch/dsh-library/client/read-plane
|
|
26
|
+
*/
|
|
27
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
28
|
+
import type { LibraryGetRequest, LibraryGetResponse, LibraryRefreshRequest, CoreHealthRequest, CoreHealthResponse, LibraryRefreshResponse, LibrarySearchRequest, LibrarySearchResponse, ProviderTestRequest, ProviderTestResponse, RouteReadinessRequest, RouteReadinessResponse } from '@deepwatch/dsh-contracts/query/wire';
|
|
29
|
+
import type { IndexHealth, IndexQueryResult } from '../index-store.js';
|
|
30
|
+
/**
|
|
31
|
+
* The `watchQuery` namespace, as the Library calls it.
|
|
32
|
+
*
|
|
33
|
+
* `ctx.remote.watchQuery` is a cordis service the Gateway installs when the
|
|
34
|
+
* contribution is mounted, so a surface that injects `remote.watchQuery` is
|
|
35
|
+
* handed exactly this.
|
|
36
|
+
*/
|
|
37
|
+
export interface WatchQueryRemote {
|
|
38
|
+
readonly librarySearch: (request: LibrarySearchRequest, signal?: AbortSignal) => Promise<RemoteResult<LibrarySearchResponse>>;
|
|
39
|
+
readonly libraryGet: (request: LibraryGetRequest, signal?: AbortSignal) => Promise<RemoteResult<LibraryGetResponse>>;
|
|
40
|
+
/**
|
|
41
|
+
* The only method here with a side effect.
|
|
42
|
+
*
|
|
43
|
+
* A separate operation rather than a flag on a search: a search that might
|
|
44
|
+
* re-read the corpus has a cost nobody can predict, and leaves a caller no
|
|
45
|
+
* way to ask for an answer from what the host already holds.
|
|
46
|
+
*/
|
|
47
|
+
readonly libraryRefresh: (request: LibraryRefreshRequest, signal?: AbortSignal) => Promise<RemoteResult<LibraryRefreshResponse>>;
|
|
48
|
+
/**
|
|
49
|
+
* The state of Watch Core, read from the running Bridge.
|
|
50
|
+
*
|
|
51
|
+
* Not a Library read, and on this namespace because this is the only channel
|
|
52
|
+
* the browser has to the Host. Diagnostics is its caller: before it existed
|
|
53
|
+
* that panel rendered the engine's connection state and version as literals,
|
|
54
|
+
* because there was nowhere to read them from.
|
|
55
|
+
*/
|
|
56
|
+
readonly coreHealth: (request: CoreHealthRequest, signal?: AbortSignal) => Promise<RemoteResult<CoreHealthResponse>>;
|
|
57
|
+
readonly providerTest: (request: ProviderTestRequest, signal?: AbortSignal) => Promise<RemoteResult<ProviderTestResponse>>;
|
|
58
|
+
/**
|
|
59
|
+
* The Host's own verdict on a route, asked without spending anything.
|
|
60
|
+
*
|
|
61
|
+
* Here because this is the browser's only channel to the Host, and because
|
|
62
|
+
* the alternative — a tab remembering a provider test it ran — is a claim
|
|
63
|
+
* about a Host it cannot see.
|
|
64
|
+
*/
|
|
65
|
+
readonly routeReadiness: (request: RouteReadinessRequest, signal?: AbortSignal) => Promise<RemoteResult<RouteReadinessResponse>>;
|
|
66
|
+
}
|
|
67
|
+
/** One row of results, however the surface obtained it. */
|
|
68
|
+
export interface ResultRow {
|
|
69
|
+
/** Stable across a re-render of the same answer. */
|
|
70
|
+
readonly key: string;
|
|
71
|
+
readonly recordId: string;
|
|
72
|
+
readonly title: string;
|
|
73
|
+
readonly kind: string;
|
|
74
|
+
/** Matched text, verbatim. Empty when the answer carried no excerpt. */
|
|
75
|
+
readonly snippets: readonly string[];
|
|
76
|
+
readonly evidenceCount: number;
|
|
77
|
+
/** False when a newer revision of the same source exists. */
|
|
78
|
+
readonly current: boolean;
|
|
79
|
+
}
|
|
80
|
+
/** What one completed search left on the screen. */
|
|
81
|
+
export interface SearchState {
|
|
82
|
+
readonly rows: readonly ResultRow[];
|
|
83
|
+
/** Matches in total, not merely on this page. */
|
|
84
|
+
readonly total: number;
|
|
85
|
+
readonly health: IndexHealth;
|
|
86
|
+
/** Which index generation answered. Zero when the host tracks none. */
|
|
87
|
+
readonly generation: number;
|
|
88
|
+
/** Non-fatal facts about this answer: truncation, staleness, refusal. */
|
|
89
|
+
readonly notes: readonly string[];
|
|
90
|
+
/** Whether the caller may ask for another page of the same answer. */
|
|
91
|
+
readonly pageable: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** What the surface is asking the host for. */
|
|
94
|
+
export interface LibraryQuery {
|
|
95
|
+
readonly text: string;
|
|
96
|
+
/** One modality, or the empty string for all of them. */
|
|
97
|
+
readonly modality: string;
|
|
98
|
+
readonly limit: number;
|
|
99
|
+
readonly deadlineMs: number;
|
|
100
|
+
}
|
|
101
|
+
/** The next correlation id for a Library read. */
|
|
102
|
+
export declare function nextRequestId(): string;
|
|
103
|
+
/**
|
|
104
|
+
* Ask the host, and turn whatever comes back into something renderable.
|
|
105
|
+
*
|
|
106
|
+
* Every outcome is an answer the surface shows rather than an exception it
|
|
107
|
+
* swallows. A refusal, an elapsed deadline and an expired cursor are different
|
|
108
|
+
* facts, and a person acts differently on each, so each keeps its own sentence.
|
|
109
|
+
*/
|
|
110
|
+
export declare function readLibraryPage(reads: WatchQueryRemote, query: LibraryQuery, signal: AbortSignal): Promise<SearchState>;
|
|
111
|
+
/** A local index answer as the same view model, so the surface renders one shape. */
|
|
112
|
+
export declare function fromIndex(result: IndexQueryResult): SearchState;
|
|
113
|
+
/** What a completed refresh left for the surface to say. */
|
|
114
|
+
export interface RefreshState {
|
|
115
|
+
/** True only where the host swapped a new generation into service. */
|
|
116
|
+
readonly refreshed: boolean;
|
|
117
|
+
/** The generation now answering searches. */
|
|
118
|
+
readonly generation: number;
|
|
119
|
+
readonly recordCount: number;
|
|
120
|
+
/** One sentence a person can act on. Empty where there is nothing to say. */
|
|
121
|
+
readonly note: string;
|
|
122
|
+
/** Whether the note describes a failure rather than a result. */
|
|
123
|
+
readonly failed: boolean;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Ask the host to read its roots again.
|
|
127
|
+
*
|
|
128
|
+
* Every outcome is rendered, and none of them is an exception. A refusal, an
|
|
129
|
+
* elapsed deadline, an abandoned rebuild and a failed one are four different
|
|
130
|
+
* facts; so is a rebuild that succeeded and found nothing new. Reporting any
|
|
131
|
+
* of them as "refreshed" would be a control that lies about what it did.
|
|
132
|
+
*/
|
|
133
|
+
export declare function refreshLibrary(reads: WatchQueryRemote, deadlineMs: number, signal: AbortSignal): Promise<RefreshState>;
|
|
134
|
+
//# sourceMappingURL=read-plane.d.ts.map
|