dsh-watcher 0.7.0 → 0.8.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/README.en.md +32 -11
- package/README.md +32 -11
- package/lib/client.js +1086 -886
- package/lib/client.js.map +1 -1
- package/lib/dsh-watcher.js +52 -4
- package/lib/types/client/SidebarTab.d.ts +52 -0
- package/lib/types/client/Watcher.d.ts +47 -0
- package/lib/types/client/history-loader.d.ts +23 -0
- package/lib/types/insights/projection.d.ts +22 -2
- package/package.json +1 -1
package/lib/dsh-watcher.js
CHANGED
|
@@ -2,6 +2,11 @@ import { z } from "zod";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
//#region src/insights/engine.mjs
|
|
4
4
|
/** Read-only DSH 0.2.0-rc.2 log fold. No prompt, reasoning or tool body is retained. */
|
|
5
|
+
const DAY_CAP = 400;
|
|
6
|
+
const localDayKey = (ms) => {
|
|
7
|
+
const d = new Date(ms);
|
|
8
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
9
|
+
};
|
|
5
10
|
const record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
6
11
|
const count = (v) => Number.isSafeInteger(v) && v >= 0;
|
|
7
12
|
const elapsed = (start, end) => start === null ? 0 : Math.max(0, end - start);
|
|
@@ -31,7 +36,7 @@ function emptyStats() {
|
|
|
31
36
|
}
|
|
32
37
|
function initialState(header = {}, inherited = 0) {
|
|
33
38
|
return {
|
|
34
|
-
version:
|
|
39
|
+
version: 2,
|
|
35
40
|
sessionId: String(header.id ?? ""),
|
|
36
41
|
skip: inherited,
|
|
37
42
|
seq: -1,
|
|
@@ -42,6 +47,7 @@ function initialState(header = {}, inherited = 0) {
|
|
|
42
47
|
},
|
|
43
48
|
totals: emptyStats(),
|
|
44
49
|
models: [],
|
|
50
|
+
days: [],
|
|
45
51
|
turn: null,
|
|
46
52
|
open: null,
|
|
47
53
|
tools: [],
|
|
@@ -112,6 +118,33 @@ function books(s, route = s.route) {
|
|
|
112
118
|
...s.turn ? [s.turn.stats] : []
|
|
113
119
|
];
|
|
114
120
|
}
|
|
121
|
+
function creditDay(s, time, route, u) {
|
|
122
|
+
const key = localDayKey(time);
|
|
123
|
+
let day = s.days.find((d) => d.key === key);
|
|
124
|
+
if (!day) {
|
|
125
|
+
day = {
|
|
126
|
+
key,
|
|
127
|
+
tokens: 0,
|
|
128
|
+
models: []
|
|
129
|
+
};
|
|
130
|
+
s.days.push(day);
|
|
131
|
+
s.days.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
132
|
+
}
|
|
133
|
+
day.tokens += u.tokens;
|
|
134
|
+
const effort = typeof route?.effort === "string" ? route.effort : null;
|
|
135
|
+
let row = day.models.find((m) => m.provider === route.provider && m.model === route.model && (m.effort ?? null) === effort);
|
|
136
|
+
if (!row) {
|
|
137
|
+
row = {
|
|
138
|
+
provider: route.provider,
|
|
139
|
+
model: route.model,
|
|
140
|
+
...effort ? { effort } : {},
|
|
141
|
+
tokens: 0
|
|
142
|
+
};
|
|
143
|
+
day.models.push(row);
|
|
144
|
+
}
|
|
145
|
+
row.tokens += u.tokens;
|
|
146
|
+
if (s.days.length > DAY_CAP) s.days = s.days.slice(s.days.length - DAY_CAP);
|
|
147
|
+
}
|
|
115
148
|
function begin(s, d, time, uncertain = false) {
|
|
116
149
|
s.open = {
|
|
117
150
|
turn: d.turn,
|
|
@@ -162,6 +195,7 @@ function settle(s, time, usage, source) {
|
|
|
162
195
|
}
|
|
163
196
|
}
|
|
164
197
|
}
|
|
198
|
+
if (u) creditDay(s, time, route, u);
|
|
165
199
|
s.open = null;
|
|
166
200
|
}
|
|
167
201
|
function applyChunk(open, chunk, time) {
|
|
@@ -368,6 +402,7 @@ function buildView(s) {
|
|
|
368
402
|
updatedAt: s.updatedAt,
|
|
369
403
|
totals: s.totals,
|
|
370
404
|
models: s.models,
|
|
405
|
+
days: s.days,
|
|
371
406
|
turn: s.turn,
|
|
372
407
|
pending: s.open ? {
|
|
373
408
|
turn: s.open.turn,
|
|
@@ -445,8 +480,19 @@ const pending = z.object({
|
|
|
445
480
|
reasoningLast: nullableTime,
|
|
446
481
|
lastContentAt: nullableTime
|
|
447
482
|
}).nullable();
|
|
483
|
+
const dayModels = z.array(z.object({
|
|
484
|
+
provider: z.string(),
|
|
485
|
+
model: z.string(),
|
|
486
|
+
effort: z.string().optional(),
|
|
487
|
+
tokens: number
|
|
488
|
+
}));
|
|
489
|
+
const days = z.array(z.object({
|
|
490
|
+
key: z.string(),
|
|
491
|
+
tokens: number,
|
|
492
|
+
models: dayModels
|
|
493
|
+
}));
|
|
448
494
|
const viewSchema = z.object({
|
|
449
|
-
version: z.literal(
|
|
495
|
+
version: z.literal(2),
|
|
450
496
|
sessionId: z.string(),
|
|
451
497
|
seq: z.number().int().min(-1),
|
|
452
498
|
updatedAt: z.number().finite(),
|
|
@@ -456,6 +502,7 @@ const viewSchema = z.object({
|
|
|
456
502
|
model: z.string(),
|
|
457
503
|
effort: z.string().optional()
|
|
458
504
|
})),
|
|
505
|
+
days,
|
|
459
506
|
turn,
|
|
460
507
|
pending,
|
|
461
508
|
findings: z.array(finding).max(30),
|
|
@@ -471,7 +518,7 @@ const usage = z.object({
|
|
|
471
518
|
exact: z.boolean()
|
|
472
519
|
}).nullable();
|
|
473
520
|
const stateSchema = z.object({
|
|
474
|
-
version: z.literal(
|
|
521
|
+
version: z.literal(2),
|
|
475
522
|
sessionId: z.string(),
|
|
476
523
|
skip: number,
|
|
477
524
|
seq: z.number().int().min(-1),
|
|
@@ -482,6 +529,7 @@ const stateSchema = z.object({
|
|
|
482
529
|
provider: z.string(),
|
|
483
530
|
model: z.string()
|
|
484
531
|
})),
|
|
532
|
+
days,
|
|
485
533
|
turn,
|
|
486
534
|
open: z.object({
|
|
487
535
|
turn: number,
|
|
@@ -516,7 +564,7 @@ const stateSchema = z.object({
|
|
|
516
564
|
function installProjection(ctx) {
|
|
517
565
|
ctx.sessionProjections.register({
|
|
518
566
|
key: "watcherInsights",
|
|
519
|
-
stateVersion:
|
|
567
|
+
stateVersion: 2,
|
|
520
568
|
stateSchema,
|
|
521
569
|
init: (header, inherited) => stateSchema.parse(initialState(header, inherited)),
|
|
522
570
|
apply: (state, event) => reduceEvent(state, event),
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
3
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
4
|
+
/**
|
|
5
|
+
* Minimal structural description of the optional `dsh-better-sidebar` tab
|
|
6
|
+
* registration service (published as `ctx.betterSidebar`, see the peer's
|
|
7
|
+
* `client/service.d.ts`). Declared locally on purpose: this package must
|
|
8
|
+
* typecheck and build without the peer installed, so neither values nor types
|
|
9
|
+
* may be imported from it. Semantics mirrored here: `registerTab` takes one
|
|
10
|
+
* descriptor and returns a disposer that unregisters the tab type — hand it to
|
|
11
|
+
* `ctx.effect` so hot reload cannot register it twice.
|
|
12
|
+
*/
|
|
13
|
+
export interface WatcherSidebarDescriptor {
|
|
14
|
+
/** Unique tab type id; also the host tab's `type` value. */
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
/** Single-instance sugar: opening the tab focuses an existing one. */
|
|
18
|
+
single?: boolean;
|
|
19
|
+
/** + menu sort order (ascending); the host default is 100. */
|
|
20
|
+
order?: number;
|
|
21
|
+
component: (props: WatcherSidebarTabProps) => ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/** The subset of the host's tab component props the Watcher page reads. */
|
|
24
|
+
export interface WatcherSidebarTabProps {
|
|
25
|
+
ctx: ClientContext;
|
|
26
|
+
scope: {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
};
|
|
29
|
+
/** Whether this tab is the active one AND the sidebar panel is open. */
|
|
30
|
+
visible: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Structural face of `ctx.betterSidebar` relevant to this plugin. */
|
|
33
|
+
export interface BetterSidebarService {
|
|
34
|
+
registerTab?: (descriptor: WatcherSidebarDescriptor) => () => void;
|
|
35
|
+
}
|
|
36
|
+
/** Read the optional service off a context without importing the peer. */
|
|
37
|
+
export declare function betterSidebarServiceOf(ctx: ClientContext): BetterSidebarService | undefined;
|
|
38
|
+
/** The Watcher tab descriptor registered into dsh-better-sidebar. */
|
|
39
|
+
export declare function watcherSidebarDescriptor(): WatcherSidebarDescriptor;
|
|
40
|
+
/**
|
|
41
|
+
* The Better Sidebar tab body: the session-header overlay's work picture,
|
|
42
|
+
* ported onto `scope.sessionId` without the portal, the trigger, or the drag
|
|
43
|
+
* frame. Reads the same Session/Conversation stores through their binding
|
|
44
|
+
* faces; the cost/performance HUD and whole-history paging stay explicit (and
|
|
45
|
+
* `visible: false` never starts a load — only the button does).
|
|
46
|
+
*/
|
|
47
|
+
export declare function WatcherSidebarTab({ ctx, sessionId, visible }: {
|
|
48
|
+
ctx: ClientContext;
|
|
49
|
+
sessionId: SessionId;
|
|
50
|
+
visible: boolean;
|
|
51
|
+
}): ReactNode;
|
|
52
|
+
//# sourceMappingURL=SidebarTab.d.ts.map
|
|
@@ -1,9 +1,56 @@
|
|
|
1
1
|
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import type { CompleteHistoryResult } from '../hub/history.ts';
|
|
3
|
+
import { type WorkPicture, type WatcherSnapshot } from '../observation/fold.ts';
|
|
4
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
5
|
+
import type { InsightsView } from '../insights/projection.ts';
|
|
6
|
+
import { type TurnPerformance } from '../observation/performance.ts';
|
|
3
7
|
export interface WatcherInjected {
|
|
4
8
|
loadAllHistory: (signal: AbortSignal) => Promise<CompleteHistoryResult>;
|
|
5
9
|
}
|
|
6
10
|
export type WatcherProps = PropsRuntime<'conversation.session.header.utilities'> & WatcherInjected;
|
|
7
11
|
/** Native session-header utility: exact work picture, typed evidence, no steering. */
|
|
8
12
|
export declare function Watcher(props: WatcherProps): import("react").JSX.Element;
|
|
13
|
+
/**
|
|
14
|
+
* Folds one Session snapshot into the work picture, merging observations that
|
|
15
|
+
* outlive the loaded window. Shared by the session-header overlay and the
|
|
16
|
+
* Better Sidebar tab so both surfaces read the same projection.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useObservedPicture(sessionId: string, snapshot: WatcherSnapshot): {
|
|
19
|
+
picture: WorkPicture;
|
|
20
|
+
performanceByTurn: ReadonlyMap<number, TurnPerformance>;
|
|
21
|
+
};
|
|
22
|
+
export interface WorkPicturePanelProps {
|
|
23
|
+
/** Owning Session identity; changing it resets follow, selection, and history state. */
|
|
24
|
+
sessionId: SessionId;
|
|
25
|
+
/** Folded work picture from {@link useObservedPicture}. */
|
|
26
|
+
picture: WorkPicture;
|
|
27
|
+
performanceByTurn: ReadonlyMap<number, TurnPerformance>;
|
|
28
|
+
/** Whether older history pages remain on the Session. */
|
|
29
|
+
hasMore: boolean;
|
|
30
|
+
/** Whole-session projection totals, when the Host projection is available. */
|
|
31
|
+
stats: {
|
|
32
|
+
turns: number;
|
|
33
|
+
steps: number;
|
|
34
|
+
} | undefined;
|
|
35
|
+
/** Cost/performance HUD input; the header overlay always renders it. */
|
|
36
|
+
insights: InsightsView | undefined;
|
|
37
|
+
/** Never mount the cost/performance HUD (the sidebar tab omits it). */
|
|
38
|
+
hideInsights?: boolean;
|
|
39
|
+
/** Explicit whole-history loader; absent when no Session face is available. */
|
|
40
|
+
loadAllHistory?: (signal: AbortSignal) => Promise<CompleteHistoryResult>;
|
|
41
|
+
/** Gates the live clock: the overlay's open state, or the sidebar tab's visibility. */
|
|
42
|
+
live: boolean;
|
|
43
|
+
/** Overlay-only: freeze the floating panel frame across inspector transitions. */
|
|
44
|
+
onInspectorFrameChange?: () => void;
|
|
45
|
+
/** DOM id prefix for turn landmarks; coexisting surfaces must not share ids. */
|
|
46
|
+
domIdPrefix?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The work picture itself: the docked inspector column plus the turns rail.
|
|
50
|
+
* Extracted from the session-header overlay so the Better Sidebar tab can
|
|
51
|
+
* render the same picture without the portal, the trigger, or the drag frame.
|
|
52
|
+
* Owns every picture-local concern (follow, disclosure, observation mode,
|
|
53
|
+
* history load, inspector lifecycle); positioning stays with the host.
|
|
54
|
+
*/
|
|
55
|
+
export declare function WorkPicturePanel({ sessionId, picture, performanceByTurn, hasMore, stats, insights, hideInsights, loadAllHistory, live, onInspectorFrameChange, domIdPrefix, }: WorkPicturePanelProps): import("react").JSX.Element;
|
|
9
56
|
//# sourceMappingURL=Watcher.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
+
import { type CompleteHistoryResult } from '../hub/history.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The paging verbs the shared loader needs from one Session face. The
|
|
6
|
+
* session-header injection and the Better Sidebar tab both satisfy it from
|
|
7
|
+
* `sessions.binding(id).session`.
|
|
8
|
+
*/
|
|
9
|
+
export interface WatcherHistorySession {
|
|
10
|
+
loadOlder: () => Promise<void>;
|
|
11
|
+
getSnapshot: () => {
|
|
12
|
+
hasMore: boolean;
|
|
13
|
+
loadingOlder: boolean;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build the explicit whole-history loader for one Session: repeat the
|
|
18
|
+
* Session's public, read-only paging verb until the head stops moving, using
|
|
19
|
+
* the same conversation head key the header overlay reports. Nothing starts
|
|
20
|
+
* it automatically — only a user action does.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createCompleteHistoryLoader(ctx: ClientContext, sessionId: SessionId, session: WatcherHistorySession): (signal: AbortSignal) => Promise<CompleteHistoryResult>;
|
|
23
|
+
//# sourceMappingURL=history-loader.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
export declare const viewSchema: z.ZodObject<{
|
|
4
|
-
version: z.ZodLiteral<
|
|
4
|
+
version: z.ZodLiteral<2>;
|
|
5
5
|
sessionId: z.ZodString;
|
|
6
6
|
seq: z.ZodNumber;
|
|
7
7
|
updatedAt: z.ZodNumber;
|
|
@@ -52,6 +52,16 @@ export declare const viewSchema: z.ZodObject<{
|
|
|
52
52
|
model: z.ZodString;
|
|
53
53
|
effort: z.ZodOptional<z.ZodString>;
|
|
54
54
|
}, z.core.$strip>>;
|
|
55
|
+
days: z.ZodArray<z.ZodObject<{
|
|
56
|
+
key: z.ZodString;
|
|
57
|
+
tokens: z.ZodNumber;
|
|
58
|
+
models: z.ZodArray<z.ZodObject<{
|
|
59
|
+
provider: z.ZodString;
|
|
60
|
+
model: z.ZodString;
|
|
61
|
+
effort: z.ZodOptional<z.ZodString>;
|
|
62
|
+
tokens: z.ZodNumber;
|
|
63
|
+
}, z.core.$strip>>;
|
|
64
|
+
}, z.core.$strip>>;
|
|
55
65
|
turn: z.ZodNullable<z.ZodObject<{
|
|
56
66
|
number: z.ZodNumber;
|
|
57
67
|
startedAt: z.ZodNumber;
|
|
@@ -105,7 +115,7 @@ export declare const viewSchema: z.ZodObject<{
|
|
|
105
115
|
findingCount: z.ZodNumber;
|
|
106
116
|
}, z.core.$strip>;
|
|
107
117
|
declare const stateSchema: z.ZodObject<{
|
|
108
|
-
version: z.ZodLiteral<
|
|
118
|
+
version: z.ZodLiteral<2>;
|
|
109
119
|
sessionId: z.ZodString;
|
|
110
120
|
skip: z.ZodNumber;
|
|
111
121
|
seq: z.ZodNumber;
|
|
@@ -161,6 +171,16 @@ declare const stateSchema: z.ZodObject<{
|
|
|
161
171
|
provider: z.ZodString;
|
|
162
172
|
model: z.ZodString;
|
|
163
173
|
}, z.core.$strip>>;
|
|
174
|
+
days: z.ZodArray<z.ZodObject<{
|
|
175
|
+
key: z.ZodString;
|
|
176
|
+
tokens: z.ZodNumber;
|
|
177
|
+
models: z.ZodArray<z.ZodObject<{
|
|
178
|
+
provider: z.ZodString;
|
|
179
|
+
model: z.ZodString;
|
|
180
|
+
effort: z.ZodOptional<z.ZodString>;
|
|
181
|
+
tokens: z.ZodNumber;
|
|
182
|
+
}, z.core.$strip>>;
|
|
183
|
+
}, z.core.$strip>>;
|
|
164
184
|
turn: z.ZodNullable<z.ZodObject<{
|
|
165
185
|
number: z.ZodNumber;
|
|
166
186
|
startedAt: z.ZodNumber;
|