dsh-watcher 0.4.3 → 0.5.1

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.
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { createHash } from "node:crypto";
3
3
  //#region src/insights/engine.mjs
4
- /** Read-only DSH 0.1.5-rc.2 log fold. No prompt, reasoning or tool body is retained. */
4
+ /** Read-only DSH 0.1.5-rc.3 log fold. No prompt, reasoning or tool body is retained. */
5
5
  const record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
6
6
  const count = (v) => Number.isSafeInteger(v) && v >= 0;
7
7
  const elapsed = (start, end) => start === null ? 0 : Math.max(0, end - start);
@@ -30,7 +30,7 @@ function emptyStats() {
30
30
  };
31
31
  }
32
32
  function initialState(header = {}, inherited = 0) {
33
- const s = {
33
+ return {
34
34
  version: 1,
35
35
  sessionId: String(header.id ?? ""),
36
36
  skip: inherited,
@@ -49,8 +49,6 @@ function initialState(header = {}, inherited = 0) {
49
49
  findings: [],
50
50
  findingCount: 0
51
51
  };
52
- s.wire = buildView(s);
53
- return s;
54
52
  }
55
53
  function normalizeUsage(value) {
56
54
  if (!record(value) || !count(value.inputTokens) || !count(value.outputTokens)) return null;
@@ -244,7 +242,6 @@ function reduceEvent(state, event) {
244
242
  if ((event.type === "assistant/attempt" || event.type === "assistant/message") && d.stream !== void 0 && !Array.isArray(d.stream)) return state;
245
243
  if (event.type === "assistant/attempt" && (!state.open || state.open.turn !== d.turn || state.open.step !== d.step)) return state;
246
244
  const s = structuredClone(state);
247
- s.wire = state.wire;
248
245
  s.seq = event.seq;
249
246
  s.updatedAt = event.time;
250
247
  if (event.type === "request/header") {
@@ -332,14 +329,14 @@ function reduceEvent(state, event) {
332
329
  };
333
330
  s.previousFailure = failure;
334
331
  if (failure.seqs.length >= 3) {
335
- const id = `repeat:${d.turn}:${failure.seqs[0]}`;
332
+ const id = `repeat:${d.turn}:${t.signature.slice(0, 16)}`;
336
333
  const finding = {
337
334
  id,
338
335
  kind: "repeated-failure",
339
336
  turn: d.turn,
340
337
  seqs: failure.seqs,
341
338
  steps: failure.steps,
342
- title: `同一操作连续失败 ${failure.seqs.length} 次`,
339
+ title: `第 ${d.turn} 轮 · 同一操作连续失败 ${failure.seqs.length} 次`,
343
340
  detail: "参数与错误结果指纹相同;这是重复失败证据,不是确定的空转或质量判决。"
344
341
  };
345
342
  const old = s.findings.findIndex((f) => f.id === id);
@@ -360,9 +357,9 @@ function reduceEvent(state, event) {
360
357
  s.tools = [];
361
358
  s.previousFailure = null;
362
359
  }
363
- s.wire = buildView(s);
364
360
  return s;
365
361
  }
362
+ const wireCache = /* @__PURE__ */ new WeakMap();
366
363
  function buildView(s) {
367
364
  return {
368
365
  version: s.version,
@@ -385,7 +382,12 @@ function buildView(s) {
385
382
  };
386
383
  }
387
384
  function viewOf(s) {
388
- return s.wire;
385
+ let wire = wireCache.get(s);
386
+ if (wire === void 0) {
387
+ wire = buildView(s);
388
+ wireCache.set(s, wire);
389
+ }
390
+ return wire;
389
391
  }
390
392
  //#endregion
391
393
  //#region src/insights/projection.ts
@@ -509,8 +511,7 @@ const stateSchema = z.object({
509
511
  steps: z.array(number)
510
512
  }).nullable(),
511
513
  findings: z.array(finding).max(30),
512
- findingCount: number,
513
- wire: viewSchema
514
+ findingCount: number
514
515
  });
515
516
  function installProjection(ctx) {
516
517
  ctx.sessionProjections.register({
@@ -528,8 +529,12 @@ function installProjection(ctx) {
528
529
  try {
529
530
  for (const session of c.sessions.list?.() ?? []) try {
530
531
  c.sessionProjections.snapshot(session);
531
- } catch {}
532
- } catch {}
532
+ } catch (error) {
533
+ c.logger?.warn?.(`dsh-watcher: projection warm-up failed for a session: ${String(error)}`);
534
+ }
535
+ } catch (error) {
536
+ c.logger?.warn?.(`dsh-watcher: session warm-up skipped: ${String(error)}`);
537
+ }
533
538
  });
534
539
  }
535
540
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-watcher",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "Read-only DSH session insights and local model usage statistics",
5
5
  "type": "module",
6
6
  "main": "lib/dsh-watcher.js",
@@ -71,34 +71,35 @@
71
71
  "license": "MIT",
72
72
  "peerDependencies": {
73
73
  "@deepseek-ai/cordis": "^4.0.2",
74
- "@deepseek-ai/dsh-client-ui-chat": "^0.1.2-rc.1",
75
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
76
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
77
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-rc.1",
78
- "@deepseek-ai/dsh-client-ui-session": "^0.1.2-rc.1",
79
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
80
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
81
- "@deepseek-ai/dsh-session-stats": "^0.1.2-rc.1",
82
- "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1",
83
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-rc.1",
84
- "@deepseek-ai/dsh-api-remotes": "^0.1.2-rc.1"
74
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.3",
75
+ "@deepseek-ai/dsh-client-ui-chat": "^0.1.5-rc.3",
76
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.3",
77
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.3",
78
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.3",
79
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.3",
80
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.3",
81
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.3",
82
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.3",
83
+ "@deepseek-ai/dsh-session-projection": "^0.1.5-rc.3",
84
+ "@deepseek-ai/dsh-session-stats": "^0.1.5-rc.3"
85
85
  },
86
86
  "dependencies": {
87
87
  "zod": "^4.4.3"
88
88
  },
89
89
  "devDependencies": {
90
- "@deepseek-ai/cordis": "^4.0.2",
91
- "@deepseek-ai/dsh-client-ui-chat": "0.1.2-rc.1",
92
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1",
93
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
94
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.2-rc.1",
95
- "@deepseek-ai/dsh-client-ui-session": "0.1.2-rc.1",
96
- "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1",
97
- "@deepseek-ai/dsh-session": "0.1.2-rc.1",
98
- "@deepseek-ai/dsh-session-stats": "0.1.2-rc.1",
99
- "@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
100
- "@deepseek-ai/dsh-client-ui-settings": "0.1.2-rc.1",
101
- "@deepseek-ai/dsh-api-remotes": "0.1.2-rc.1",
90
+ "@deepseek-ai/cordis": "4.0.2",
91
+ "@deepseek-ai/dsh-api-remotes": "0.1.5-rc.3",
92
+ "@deepseek-ai/dsh-client-ui-chat": "0.1.5-rc.3",
93
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.3",
94
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.3",
95
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.3",
96
+ "@deepseek-ai/dsh-client-ui-session": "0.1.5-rc.3",
97
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.3",
98
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.3",
99
+ "@deepseek-ai/dsh-session": "0.1.5-rc.3",
100
+ "@deepseek-ai/dsh-session-projection": "0.1.5-rc.3",
101
+ "@deepseek-ai/dsh-session-stats": "0.1.5-rc.3",
102
+ "@types/node": "^22.20.4",
102
103
  "@types/react": "~18.3.1",
103
104
  "@types/react-dom": "~18.3.0",
104
105
  "react": "^18.2.0",
@@ -1,18 +0,0 @@
1
- import type { InsightsView } from '../insights/projection.ts';
2
- type Evidence = {
3
- turn: number;
4
- steps: number[];
5
- seqs: number[];
6
- };
7
- export declare function SessionInsights({ value, now, running, waiting, onEvidence }: {
8
- value: InsightsView | undefined;
9
- now: number;
10
- running: boolean;
11
- waiting: boolean;
12
- onEvidence: (e: Evidence) => void;
13
- }): import("react").JSX.Element | null;
14
- export declare function InsightsSettings(props: {
15
- remote?: any;
16
- }): import("react").JSX.Element;
17
- export {};
18
- //# sourceMappingURL=Insights.d.ts.map
@@ -1,7 +0,0 @@
1
- import type { InsightsView } from '../insights/projection.ts';
2
- export declare function TimingPanel({ stats, scope, tokensPerSecond, }: {
3
- stats: InsightsView['totals'];
4
- scope: 'turn' | 'session';
5
- tokensPerSecond?: number | null;
6
- }): import("react").JSX.Element;
7
- //# sourceMappingURL=TimingPanel.d.ts.map
@@ -1,9 +0,0 @@
1
- import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
- import type { CompleteHistoryResult } from '../hub/history.ts';
3
- export interface WatcherInjected {
4
- loadAllHistory: (signal: AbortSignal) => Promise<CompleteHistoryResult>;
5
- }
6
- export type WatcherProps = PropsRuntime<'conversation.session.header.utilities'> & WatcherInjected;
7
- /** Native session-header utility: exact work picture, typed evidence, no steering. */
8
- export declare function Watcher(props: WatcherProps): import("react").JSX.Element;
9
- //# sourceMappingURL=Watcher.d.ts.map
@@ -1,22 +0,0 @@
1
- export type DisclosureDepth = 'overview' | 'detail';
2
- export type DisclosureLayer = 'phase' | 'step' | 'cluster' | 'model' | 'reasoning';
3
- type LayerOverrides = Readonly<Record<DisclosureLayer, Readonly<Record<string, boolean>>>>;
4
- export interface DisclosureState {
5
- readonly depth: DisclosureDepth;
6
- readonly turns: Readonly<Record<number, boolean>>;
7
- readonly layers: LayerOverrides;
8
- }
9
- export declare function createDisclosureState(depth?: DisclosureDepth): DisclosureState;
10
- /** Overview keeps the automatic Turn policy; detail opens every Turn by default. */
11
- export declare function turnDisclosureOpen(state: DisclosureState, turn: number, overviewDefaultOpen: boolean): boolean;
12
- /** Overview stops at phase headers; detail opens every nested level by default. */
13
- export declare function layerDisclosureOpen(state: DisclosureState, layer: DisclosureLayer, key: string): boolean;
14
- export declare function setLayerDisclosure(state: DisclosureState, layer: DisclosureLayer, key: string, open: boolean): DisclosureState;
15
- export declare function toggleTurnDisclosure(state: DisclosureState, turn: number, overviewDefaultOpen: boolean): DisclosureState;
16
- export declare function toggleLayerDisclosure(state: DisclosureState, layer: DisclosureLayer, key: string): DisclosureState;
17
- /** Choosing a depth applies it immediately instead of inheriting stale manual folds. */
18
- export declare function chooseDisclosureDepth(depth: DisclosureDepth): DisclosureState;
19
- /** Session-specific ids may change, while the user's chosen depth remains useful. */
20
- export declare function resetDisclosureOverrides(state: DisclosureState): DisclosureState;
21
- export {};
22
- //# sourceMappingURL=disclosure-depth.d.ts.map
@@ -1,9 +0,0 @@
1
- import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
- export declare const name = "dsh-watcher-client";
3
- export declare const inject: string[];
4
- /**
5
- * Native session-header utility. Order 50 sits after Session log (0)
6
- * and before the files-panel toggle (110). No overlay glyph.
7
- */
8
- export declare function apply(ctx: ClientContext): void;
9
- //# sourceMappingURL=index.d.ts.map
@@ -1,11 +0,0 @@
1
- import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
- import { type ModelStepTrace } from '../observation/model-trace.ts';
3
- declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
4
- interface ConversationStepDataMap {
5
- /** Provider-exposed model activity retained for one Step. */
6
- 'dsh-watcher-model-stage': ModelStepTrace;
7
- }
8
- }
9
- /** Register the Step-scoped, read-only model-stage projection. */
10
- export declare function registerModelTraceDefinition(ctx: ClientContext): void;
11
- //# sourceMappingURL=model-trace-definition.d.ts.map
@@ -1,15 +0,0 @@
1
- import type { WorkItem, WorkStep } from '../observation/fold.ts';
2
- import type { ModelStepTrace } from '../observation/model-trace.ts';
3
- type StepTimelineOccurrence = {
4
- readonly kind: 'occurrence';
5
- readonly item: WorkItem;
6
- readonly occurrenceIndex: number;
7
- };
8
- export type StepTimelineEntry = StepTimelineOccurrence | {
9
- readonly kind: 'model';
10
- readonly trace: ModelStepTrace;
11
- };
12
- /** Compose the visible rows owned by one authoritative DSH Step. */
13
- export declare function stepTimelineEntries(step: Pick<WorkStep, 'items' | 'model'>): readonly StepTimelineEntry[];
14
- export {};
15
- //# sourceMappingURL=step-timeline.d.ts.map
@@ -1,6 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const name = "dsh-watcher";
3
- export declare const inject: string[];
4
- /** Host-owned replayable statistics; no new transport or model-facing writes. */
5
- export declare function apply(ctx: Context): void;
6
- //# sourceMappingURL=dsh-watcher.d.ts.map
@@ -1,40 +0,0 @@
1
- import type { WorkItem, WorkStatus } from '../observation/fold.ts';
2
- export type WorkClusterBasis = 'mutable-target' | 'shared-target' | 'exact-call' | 'single';
3
- export interface WorkStatusCounts {
4
- running: number;
5
- waiting: number;
6
- success: number;
7
- failure: number;
8
- returned: number;
9
- interrupted: number;
10
- unknown: number;
11
- }
12
- export type NonEmptyWorkItems = readonly [WorkItem, ...WorkItem[]];
13
- /** A reversible analysis projection. `items` always retains the source records. */
14
- export interface WorkCluster {
15
- id: string;
16
- basis: WorkClusterBasis;
17
- title: string;
18
- items: NonEmptyWorkItems;
19
- executionCount: number;
20
- stepCount: number;
21
- retryCount: number;
22
- iterationCount: number;
23
- latestStatus: WorkStatus;
24
- statusCounts: Readonly<WorkStatusCounts>;
25
- }
26
- /**
27
- * Group one phase for analysis without changing evidence identity or order.
28
- *
29
- * - Mutable calls may group by operation + target so changed inputs remain
30
- * comparable as iterations.
31
- * - Reads may group by one exact file target so different line windows stay
32
- * comparable.
33
- * - Search, Glob, Grep, Bash, and every other tool require exact normalized
34
- * arguments. Sharing a cwd, broad path, tool name, or translated title is
35
- * never enough.
36
- * - Messages and otherwise unsigned records remain singletons.
37
- */
38
- export declare function clusterWorkItems(items: readonly WorkItem[]): readonly WorkCluster[];
39
- export declare function clusterOutcomeSummary(cluster: WorkCluster): string;
40
- //# sourceMappingURL=aggregation.d.ts.map
@@ -1,27 +0,0 @@
1
- import type { WorkPicture } from '../observation/fold.ts';
2
- export interface FollowSnapshot {
3
- follow: boolean;
4
- unread: number;
5
- selectedId: string | null;
6
- }
7
- export interface RailScrollState {
8
- atBottom: boolean;
9
- }
10
- /**
11
- * Follow versus pin state for the work rail.
12
- *
13
- * The rail follows the newest recorded occurrence by default. Selecting
14
- * history or scrolling away pins the viewport; later occurrences and result
15
- * updates increment `unread` without yanking the reader away from evidence.
16
- */
17
- export declare function createFollow(): {
18
- snapshot: () => FollowSnapshot;
19
- /** Counts appended occurrences or a settled live result while pinned. */
20
- onPicture(picture: Pick<WorkPicture, "nodes">): FollowSnapshot;
21
- onSelect(id: string): FollowSnapshot;
22
- onScroll({ atBottom }: RailScrollState): FollowSnapshot;
23
- setFollow(next: boolean): FollowSnapshot;
24
- backToLatest(): FollowSnapshot;
25
- reset(): FollowSnapshot;
26
- };
27
- //# sourceMappingURL=follow.d.ts.map
@@ -1,30 +0,0 @@
1
- /** Minimal public facts needed to page one shared RC8 Session window. */
2
- export interface HistoryWindowState {
3
- hasMore: boolean;
4
- loadingOlder: boolean;
5
- headKey: string;
6
- }
7
- export type CompleteHistoryResult = {
8
- kind: 'complete';
9
- pages: number;
10
- } | {
11
- kind: 'cancelled';
12
- pages: number;
13
- } | {
14
- kind: 'blocked';
15
- pages: number;
16
- reason: 'busy' | 'no-progress' | 'page-limit';
17
- };
18
- export interface CompleteHistoryOptions {
19
- read: () => HistoryWindowState;
20
- loadOlder: () => Promise<void>;
21
- signal: AbortSignal;
22
- maxPages?: number;
23
- }
24
- /**
25
- * Pull every older RC8 page in order. The Session remains the sole owner of
26
- * history continuity; this helper only repeats its public, read-only paging
27
- * verb and fails closed if one request does not advance the visible head.
28
- */
29
- export declare function loadCompleteHistory({ read, loadOlder, signal, maxPages, }: CompleteHistoryOptions): Promise<CompleteHistoryResult>;
30
- //# sourceMappingURL=history.d.ts.map
@@ -1,10 +0,0 @@
1
- import type { WorkGroup, WorkStatus, WorkTurn } from '../observation/fold.ts';
2
- /** Overview progress is deliberately separate from execution evidence status. */
3
- export type OverviewState = 'active' | 'current' | 'waiting' | 'failure' | 'interrupted' | 'settled' | 'partial';
4
- export declare const OVERVIEW_STATE_LABEL: Readonly<Record<OverviewState, string>>;
5
- /** Project evidence status into the one question overview markers answer: where should the user look? */
6
- export declare function overviewStateOf(status: WorkStatus, isLatest: boolean): OverviewState;
7
- export declare function groupOverviewSummary(group: WorkGroup): string;
8
- export declare function turnOverviewSummary(turn: WorkTurn): string;
9
- export declare function turnNeedsDefaultDisclosure(state: OverviewState, isLatest: boolean): boolean;
10
- //# sourceMappingURL=overview.d.ts.map
@@ -1,4 +0,0 @@
1
- /** Stable model entry for consumers that need Watcher's truthful work picture. */
2
- export { EMPTY_PICTURE, foldEvents, foldSnapshot, normalizedSignature, pairTools, parseJsonl, } from '../observation/fold.ts';
3
- export type { WorkGroup, WorkItem, WorkPhase, WorkPicture, WorkPresentation, WorkStatus, WorkStep, WorkTurn, } from '../observation/fold.ts';
4
- //# sourceMappingURL=watcher.d.ts.map