@cassius0924/dsh-usage-dashboard 0.8.3 → 0.9.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.
@@ -1,94 +0,0 @@
1
- /**
2
- * The peak-window breaker gate.
3
- *
4
- * DeepSeek's `agent/pre-step` is a waterfall: a listener receives the proposed
5
- * step and either calls `next()` (let it proceed) or withholds it (park). This
6
- * gate parks steps only when all conditions hold:
7
- *
8
- * - the master switch is on,
9
- * - the step's agent (its session) opted in via its own switch,
10
- * - a peak window is open,
11
- * - the turn was started by a person (`source.kind === 'user'`).
12
- *
13
- * Parked steps resume when the window settles at 18:00, when the user lets them
14
- * continue from the UI, or when the owning turn is cancelled (`signal` aborts —
15
- * abort must let the loop unwind normally instead of throwing here).
16
- *
17
- * Steps after the first one in a turn can carry no messages, so the "was this a
18
- * user turn" decision is remembered per `(session, turn)`. The same memory
19
- * records a manual release so the user's "continue now" is not undone by the
20
- * very next step of that turn. Both are bounded: sessions come and go, but the
21
- * map must not grow forever on a long-lived host.
22
- *
23
- * Timer handling is split behind `BreakerTimerFace` so tests can drive a fake
24
- * clock; the production face uses a real `setTimeout`, unrefs it (a parked turn
25
- * must never keep the process alive) and re-arms long waits in chunks, because
26
- * `setTimeout` cannot represent delays above `MAX_BREAKER_TIMER_DELAY_MS`.
27
- */
28
- import { type BreakerSettings } from './contract.ts';
29
- import { type BreakerMessageFace } from './breaker.ts';
30
- /** The timer seam; production uses `systemBreakerTimers`, tests a fake clock. */
31
- export interface BreakerTimerFace {
32
- schedule(callback: () => void, delayMs: number): unknown;
33
- cancel(handle: unknown): void;
34
- }
35
- /** `setTimeout`'s documented maximum delay; longer waits are re-armed. */
36
- export declare const MAX_BREAKER_TIMER_DELAY_MS = 2147483647;
37
- /** Production timers: a real unref'd `setTimeout`. */
38
- export declare const systemBreakerTimers: BreakerTimerFace;
39
- /** Clamp one wait into the range a single `setTimeout` can carry. */
40
- export declare const clampBreakerTimerDelay: (delayMs: number) => number;
41
- export interface BreakerGateOptions {
42
- /** Settings to start from; the host loads persisted ones asynchronously. */
43
- initialSettings?: BreakerSettings;
44
- /** Clock seam, epoch ms; defaults to `Date.now`. */
45
- now?: () => number;
46
- /** Timer seam; defaults to the real clock. */
47
- timers?: BreakerTimerFace;
48
- }
49
- export declare class BreakerGate {
50
- #private;
51
- constructor(options?: BreakerGateOptions);
52
- get settings(): BreakerSettings;
53
- /** Steps parked right now, across every session. */
54
- get held(): number;
55
- /** Session ids with at least one parked step, sorted for the wire projection. */
56
- get heldSessions(): string[];
57
- /**
58
- * Park one proposed step until it may proceed. Resolves immediately when the
59
- * step is not subject to the breaker (session not opted in, off-peak,
60
- * machine-started turn, turn already released by hand) or when the turn is
61
- * aborted/disposed.
62
- *
63
- * The listener wrapping this must call `next()` after it resolves — this
64
- * method never throws and never returns a decision of its own.
65
- *
66
- * `sessionId` is the agent id from the payload; an agent without one cannot
67
- * be matched against the opt-in list, so its steps pass.
68
- */
69
- hold(sessionId: string | undefined, turn: number, messages: readonly BreakerMessageFace[], signal: AbortSignal): Promise<void>;
70
- /**
71
- * Let parked steps continue now. `sessionId` narrows it to one session; no
72
- * argument releases every parked step (the UI's "continue all" button). The
73
- * released turns are remembered, because re-parking their next step would
74
- * contradict the user's instruction.
75
- * @returns how many parked steps were released.
76
- */
77
- release(sessionId?: string): number;
78
- /**
79
- * Apply new settings. Turning off the master switch — or a session's own
80
- * switch — releases the affected parked steps immediately and without
81
- * remembering a bypass: that is the user's decision, not a request for one
82
- * particular turn to run.
83
- */
84
- update(settings: BreakerSettings): void;
85
- /** Release everything and stop the timer; called when the plugin unloads. */
86
- dispose(): void;
87
- /** (Re)arm the single settle timer for the current peak window. */
88
- private armTimer;
89
- private onTimer;
90
- private clearTimer;
91
- private releaseRecord;
92
- private markReleased;
93
- private rememberTurn;
94
- }
@@ -1,28 +0,0 @@
1
- import { type BreakerSettings } from './contract.ts';
2
- /**
3
- * Resolve the settings file path from the environment. The env parameter is
4
- * injectable so tests can exercise the `DSH_HOME` branch without touching the
5
- * process environment.
6
- * @param env - environment to read `DSH_HOME` from; defaults to `process.env`.
7
- */
8
- export declare function breakerSettingsPath(env?: NodeJS.ProcessEnv): string;
9
- /** Shared IO options; the defaults are what the running plugin uses. */
10
- export interface BreakerSettingsIoOptions {
11
- /** Explicit file path, bypassing {@link breakerSettingsPath}. */
12
- filePath?: string;
13
- /** Warning sink, so tests can capture instead of printing. */
14
- warn?: (message: string) => void;
15
- }
16
- /**
17
- * Read persisted settings. A missing file is the first-run case and returns
18
- * the defaults silently; unreadable, malformed, or structurally invalid
19
- * content warns and returns the defaults.
20
- */
21
- export declare function loadBreakerSettings(options?: BreakerSettingsIoOptions): Promise<BreakerSettings>;
22
- /**
23
- * Persist settings atomically. The temp file is created beside the target and
24
- * renamed over it, so readers only ever see a complete document.
25
- * @param settings - master switch and session opt-ins; normalized before writing.
26
- * @param options - explicit path, if any.
27
- */
28
- export declare function saveBreakerSettings(settings: BreakerSettings, options?: Pick<BreakerSettingsIoOptions, 'filePath'>): Promise<void>;
@@ -1,49 +0,0 @@
1
- /**
2
- * Peak-window breaker decisions — pure functions, no timers or IO.
3
- *
4
- * DeepSeek charges peak rates in Beijing time 09:00–12:00 and 14:00–18:00 on
5
- * weekdays (see `pricing.ts`, which owns that definition). A session that opted
6
- * in parks its user-started steps while a peak window is open and lets them
7
- * continue at 18:00 — the end of the day's last peak window — so one pause
8
- * covers both windows without a lunch-time exception.
9
- *
10
- * All time math is done in Beijing time (UTC+8) rather than the machine's local
11
- * zone, matching the price table.
12
- */
13
- import { type BreakerData, type BreakerSettings } from './contract.ts';
14
- /** Peak windows in Beijing time, display-only copy shared with `pricingInfo`. */
15
- export declare const BREAKER_PEAK_WINDOWS: readonly ["09:00–12:00", "14:00–18:00"];
16
- /**
17
- * The one field of a message this module inspects: its producer. Only
18
- * `kind === 'user'` means a person started the turn in a chat; scheduled
19
- * tasks, goal continuations and subagent relays all carry another kind and are
20
- * therefore never parked.
21
- */
22
- export interface BreakerMessageFace {
23
- source?: {
24
- kind?: string;
25
- };
26
- }
27
- /**
28
- * When the current peak window settles: 18:00 Beijing time on the same day.
29
- * Outside peak there is nothing to wait for, so the answer is `now`.
30
- *
31
- * A peak window can only be open between 09:00 and 18:00 on a weekday, so the
32
- * same-day 18:00 instant is always in the future here. Converting through
33
- * UTC+8 schedules the same instant on a host in another timezone.
34
- */
35
- export declare function nextResumeAt(nowMs: number): number;
36
- /**
37
- * Whether these proposed-step messages belong to a turn a person started. A
38
- * single user-produced message is enough: a step that carries it is part of
39
- * the user's request, whatever else the batch holds.
40
- */
41
- export declare function isUserTurn(messages: readonly BreakerMessageFace[]): boolean;
42
- /**
43
- * A copy holding exactly the contract fields, with session ids deduplicated,
44
- * filtered to the charset sessions can actually have, and sorted so persisted
45
- * documents and API projections are stable.
46
- */
47
- export declare function normalizeBreakerSettings(settings: BreakerSettings): BreakerSettings;
48
- /** Project live gate state into the wire shape the client renders. */
49
- export declare function breakerProject(settings: BreakerSettings, nowMs: number, holding: readonly string[]): BreakerData;
@@ -1,21 +0,0 @@
1
- /**
2
- * 高峰暂停 pill in the composer dock, beside the host's own
3
- * 「会话统计」/「Token 用量」pills and this plugin's 「会话费用」pill. It is the
4
- * switch for the current conversation only: on means this session's
5
- * user-started turns pause during Beijing-time peak pricing and resume at
6
- * 18:00. The 额度 page lists every session that opted in and carries the
7
- * master switch; this pill is where a single conversation opts in.
8
- *
9
- * The three states are told apart by three channels at once, never colour
10
- * alone: copy (「高峰暂停 · 关」/「高峰暂停 · 开」/「已暂停 · 18:00」), glyph
11
- * (outline / solid) and colour (neutral / primary / warn).
12
- *
13
- * Skin and geometry mirror `./session-cost-pill.tsx` (same slot, same
14
- * `.dq-cost-pill`), so the dock reads as one row. The switch writes
15
- * optimistically, then takes the host's returned projection as truth; a
16
- * failure rolls back and is surfaced through `title`/`aria-label` only.
17
- */
18
- import { type ReactElement } from 'react';
19
- export declare function BreakerPill({ sessionId }: {
20
- sessionId?: string;
21
- }): ReactElement | null;
@@ -1,8 +0,0 @@
1
- import type { BreakerData } from '../contract.ts';
2
- /** Latest host projection, or null before the first read lands. */
3
- export declare function getBreakerData(): BreakerData | null;
4
- /** Replace the projection and notify both surfaces. */
5
- export declare function publishBreakerData(data: BreakerData | null): void;
6
- export declare function subscribeBreakerData(listener: () => void): () => void;
7
- /** Subscribe the calling component to the shared projection. */
8
- export declare function useBreakerData(): BreakerData | null;
@@ -1,60 +0,0 @@
1
- /**
2
- * Client-side helpers for the session-scoped peak breaker: response-shape
3
- * validation (the host half may not be restarted yet, so an absent or
4
- * malformed payload must degrade to「状态暂不可用」instead of crashing),
5
- * per-session projections for optimistic switches, and pure display
6
- * formatting that is independent of the browser timezone.
7
- */
8
- import { type BreakerData, type BreakerResponse } from '../contract.ts';
9
- /**
10
- * Narrow a response to one whose every field the UI dereferences exists.
11
- * Mirrors `usageIsUsable` in ./api.ts: a stale host that does not serve the
12
- * breaker route (404) or an older payload shape falls through to the neutral
13
- * copy rather than being rendered as if it were current.
14
- */
15
- export declare function breakerIsUsable(res: BreakerResponse | null | undefined): res is BreakerResponse & {
16
- data: BreakerData;
17
- };
18
- /**
19
- * `HH:mm` in Beijing time for an ISO instant, or null when absent/unparseable.
20
- *
21
- * Computed through the fixed +08:00 offset rather than `Intl`/local Date
22
- * getters: the resume clock is defined in Beijing time, and the browser may be
23
- * in any timezone (same approach as ./budget.ts's month boundary).
24
- */
25
- export declare function beijingClock(iso: string | null | undefined): string | null;
26
- /** Whether `sessionId` itself opted in, ignoring the master switch. */
27
- export declare function isSessionOptedIn(data: BreakerData | null, sessionId: string | null | undefined): boolean;
28
- /** Whether `sessionId`'s opt-in is in force: its own switch and the master switch. */
29
- export declare function isSessionEnabled(data: BreakerData | null, sessionId: string | null | undefined): boolean;
30
- /** Switch a session's breaker can be in; `holding` means it is parked right now. */
31
- export type SessionBreakerState = 'off' | 'on' | 'holding';
32
- /**
33
- * Per-session state for the composer pill. Holding is only reported for an
34
- * enabled session: a release can race a stale projection, and a session the
35
- * host no longer lists must read as off regardless of the holding list. With
36
- * the master switch off nothing is in force, so the pill reads off too.
37
- */
38
- export declare function sessionBreakerState(data: BreakerData | null, sessionId: string | null | undefined): SessionBreakerState;
39
- /**
40
- * Projection with one session switched on/off, used for the optimistic state
41
- * before the host answers. Disabling also drops the session from `holding`:
42
- * off means nothing is parked for it.
43
- */
44
- export declare function withSessionToggled(data: BreakerData | null, sessionId: string, enabled: boolean): BreakerData | null;
45
- /**
46
- * Projection with the master switch flipped, used for the optimistic state
47
- * before the host answers. Off means nothing is parked — per-session switches
48
- * are kept, so the 额度 page can still list and manage them.
49
- */
50
- export declare function withMasterToggled(data: BreakerData | null, enabled: boolean): BreakerData | null;
51
- /** Title fallback for a session whose title has not been read yet. */
52
- export declare function shortSessionId(sessionId: string): string;
53
- /**
54
- * Which status row the 额度 page's breaker card shows. `unavailable` means the
55
- * host route could not be read (or answered with a shape this client cannot
56
- * use); `masterOff` outranks the peak phases because nothing pauses then. The
57
- * peak phases depend only on the host clock and holding list.
58
- */
59
- export type BreakerPhase = 'unavailable' | 'masterOff' | 'offPeak' | 'peak' | 'peakHolding';
60
- export declare function breakerPhase(data: BreakerData | null): BreakerPhase;