@mingozhou/dsh-replay 0.4.1 → 0.4.2
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.md +225 -148
- package/README.zh.md +225 -154
- package/assets/icon.png +0 -0
- package/lib/client.js +954 -2418
- package/lib/core.js +557 -86
- package/lib/index.js +1043 -51
- package/lib/types/src/client/api.d.ts +44 -0
- package/lib/types/src/client/components/AuditView.d.ts +2 -1
- package/lib/types/src/client/components/CompareView.d.ts +2 -1
- package/lib/types/src/client/components/DashboardView.d.ts +8 -0
- package/lib/types/src/client/components/ExportDialog.d.ts +9 -0
- package/lib/types/src/client/components/ReplayApp.d.ts +4 -2
- package/lib/types/src/client/components/ReplayModal.d.ts +1 -1
- package/lib/types/src/client/components/SessionPicker.d.ts +4 -3
- package/lib/types/src/client/components/TimelineView.d.ts +2 -1
- package/lib/types/src/client/export.d.ts +6 -1
- package/lib/types/src/client/i18n.d.ts +8 -1
- package/lib/types/src/client/pricing.d.ts +6 -0
- package/lib/types/src/core/audit.d.ts +18 -0
- package/lib/types/src/core/compare.d.ts +8 -3
- package/lib/types/src/core/cost.d.ts +21 -3
- package/lib/types/src/core/index.d.ts +1 -0
- package/lib/types/src/core/redact.d.ts +40 -0
- package/lib/types/src/core/timeline.d.ts +9 -0
- package/lib/types/src/index.d.ts +52 -1
- package/lib/viewer.js +282 -29
- package/package.json +118 -107
- package/scripts/build.mjs +7 -1
- package/src/client/api.ts +114 -10
- package/src/client/components/AuditView.tsx +89 -10
- package/src/client/components/CompareView.tsx +89 -13
- package/src/client/components/DashboardView.tsx +232 -0
- package/src/client/components/ExportDialog.tsx +136 -0
- package/src/client/components/ItemDetail.tsx +36 -4
- package/src/client/components/OverviewView.tsx +150 -34
- package/src/client/components/ReplayApp.tsx +207 -60
- package/src/client/components/ReplayModal.tsx +41 -6
- package/src/client/components/SessionPicker.tsx +55 -27
- package/src/client/components/TimelineView.tsx +106 -41
- package/src/client/export.ts +20 -4
- package/src/client/i18n.ts +266 -45
- package/src/client/index.ts +7 -5
- package/src/client/mascot-image.ts +1 -1
- package/src/client/pricing.ts +90 -0
- package/src/client/styles.css +250 -1
- package/src/client/viewer-entry.tsx +25 -4
- package/src/core/audit.ts +274 -67
- package/src/core/compare.ts +0 -0
- package/src/core/cost.ts +94 -16
- package/src/core/index.ts +1 -0
- package/src/core/redact.ts +182 -0
- package/src/core/timeline.ts +107 -12
- package/src/index.ts +290 -54
|
@@ -2,10 +2,49 @@ import type { ParsedSession, SessionHeader } from '../core/types.js';
|
|
|
2
2
|
export interface SessionEntry {
|
|
3
3
|
header: SessionHeader;
|
|
4
4
|
title?: string;
|
|
5
|
+
live?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SessionSummaryData {
|
|
8
|
+
events: number;
|
|
9
|
+
turns: number;
|
|
10
|
+
steps: number;
|
|
11
|
+
toolCalls: number;
|
|
12
|
+
toolErrors: number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
models: string[];
|
|
15
|
+
tokens: {
|
|
16
|
+
input: number;
|
|
17
|
+
output: number;
|
|
18
|
+
cacheRead: number;
|
|
19
|
+
cacheWrite: number;
|
|
20
|
+
reasoning: number;
|
|
21
|
+
billedInput: number;
|
|
22
|
+
};
|
|
23
|
+
modelTotals: Record<string, {
|
|
24
|
+
input: number;
|
|
25
|
+
output: number;
|
|
26
|
+
cacheRead: number;
|
|
27
|
+
cacheWrite: number;
|
|
28
|
+
reasoning: number;
|
|
29
|
+
billedInput: number;
|
|
30
|
+
}>;
|
|
31
|
+
findings: number;
|
|
32
|
+
critical: number;
|
|
33
|
+
serious: number;
|
|
34
|
+
warning: number;
|
|
35
|
+
info: number;
|
|
36
|
+
}
|
|
37
|
+
export interface SessionSummaryEntry {
|
|
38
|
+
header: SessionHeader;
|
|
39
|
+
title?: string;
|
|
40
|
+
live?: boolean;
|
|
41
|
+
summary?: SessionSummaryData;
|
|
42
|
+
error?: string;
|
|
5
43
|
}
|
|
6
44
|
export interface ReplayApi {
|
|
7
45
|
listSessions: () => Promise<SessionEntry[]>;
|
|
8
46
|
getSession: (id: string) => Promise<ParsedSession>;
|
|
47
|
+
getSummaries?: (limit?: number) => Promise<SessionSummaryEntry[]>;
|
|
9
48
|
/** Source of the standalone viewer bundle, for HTML export (optional). */
|
|
10
49
|
getViewerSource?: () => Promise<string>;
|
|
11
50
|
}
|
|
@@ -15,6 +54,7 @@ export declare class HttpReplayApi implements ReplayApi {
|
|
|
15
54
|
private fetchJson;
|
|
16
55
|
listSessions(): Promise<SessionEntry[]>;
|
|
17
56
|
getSession(id: string): Promise<ParsedSession>;
|
|
57
|
+
getSummaries(limit?: number): Promise<SessionSummaryEntry[]>;
|
|
18
58
|
getViewerSource(): Promise<string>;
|
|
19
59
|
}
|
|
20
60
|
/** Demo/testing implementation over raw JSONL texts keyed by session id. */
|
|
@@ -36,8 +76,12 @@ export declare class StaticReplayApi implements ReplayApi {
|
|
|
36
76
|
getViewerSource(): Promise<string>;
|
|
37
77
|
listSessions(): Promise<SessionEntry[]>;
|
|
38
78
|
getSession(id: string): Promise<ParsedSession>;
|
|
79
|
+
getSummaries(limit?: number): Promise<SessionSummaryEntry[]>;
|
|
39
80
|
}
|
|
40
81
|
export declare function formatDuration(ms: number): string;
|
|
41
82
|
export declare function formatTokens(n: number): string;
|
|
83
|
+
/** Clock offset from the session start: mm:ss, or h:mm:ss for long sessions. */
|
|
42
84
|
export declare function formatClock(time: number, startTime: number): string;
|
|
85
|
+
/** Human relative time for the session picker ("3 days ago", "2 months ago"). */
|
|
86
|
+
export declare function formatRelativeTime(time: number, now?: number, lang?: 'en' | 'zh'): string;
|
|
43
87
|
export declare function shortId(id: string): string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AuditReport } from '../../core/audit.js';
|
|
3
3
|
import type { TimelineModel } from '../../core/timeline.js';
|
|
4
|
-
export declare function AuditView({ report, model, onJump, }: {
|
|
4
|
+
export declare function AuditView({ report, model, onJump, sessionId, }: {
|
|
5
5
|
report: AuditReport;
|
|
6
6
|
model: TimelineModel;
|
|
7
7
|
onJump: (seq: number) => void;
|
|
8
|
+
sessionId?: string;
|
|
8
9
|
}): React.ReactElement;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ParsedSession } from '../../core/types.js';
|
|
3
3
|
import { type ReplayApi, type SessionEntry } from '../api.js';
|
|
4
|
-
export declare function CompareView({ api, session, entries, title, }: {
|
|
4
|
+
export declare function CompareView({ api, session, entries, title, onOpenSession, }: {
|
|
5
5
|
api: ReplayApi;
|
|
6
6
|
session: ParsedSession;
|
|
7
7
|
entries: SessionEntry[];
|
|
8
8
|
title?: string;
|
|
9
|
+
onOpenSession?: (id: string) => void;
|
|
9
10
|
}): React.ReactElement;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type SessionEntry, type SessionSummaryEntry } from '../api.js';
|
|
3
|
+
export declare function DashboardView({ entries, summaries, loading, onPick, }: {
|
|
4
|
+
entries: SessionEntry[];
|
|
5
|
+
summaries: SessionSummaryEntry[];
|
|
6
|
+
loading: boolean;
|
|
7
|
+
onPick: (id: string) => void;
|
|
8
|
+
}): React.ReactElement;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ParsedSession } from '../../core/types.js';
|
|
3
|
+
import { type ReplayApi } from '../api.js';
|
|
4
|
+
export declare function ExportDialog({ api, session, title, onClose, }: {
|
|
5
|
+
api: ReplayApi;
|
|
6
|
+
session: ParsedSession;
|
|
7
|
+
title?: string;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
}): React.ReactElement;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type ReplayApi } from '../api.js';
|
|
3
3
|
import { type Lang } from '../i18n.js';
|
|
4
|
-
export declare function ReplayApp({ api, sessionId, initialLang, onSwitchSession, }: {
|
|
4
|
+
export declare function ReplayApp({ api, sessionId, initialLang, onSwitchSession, standalone, }: {
|
|
5
5
|
api: ReplayApi;
|
|
6
|
-
/** Omit to start on the session
|
|
6
|
+
/** Omit to start on the session archive (modal / standalone modes). */
|
|
7
7
|
sessionId?: string;
|
|
8
8
|
/** Override the detected language ('en' | 'zh'). */
|
|
9
9
|
initialLang?: Lang;
|
|
10
10
|
/** Optional host-provided navigation; falls back to internal state. */
|
|
11
11
|
onSwitchSession?: (id: string) => void;
|
|
12
|
+
/** Exported HTML viewer: one session, no archive/forks/compare/export. */
|
|
13
|
+
standalone?: boolean;
|
|
12
14
|
}): React.ReactElement;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ReplayApi } from '../api.js';
|
|
3
|
-
/** Full-screen modal hosting the
|
|
3
|
+
/** Full-screen modal hosting the session review app, opened from the sidebar. */
|
|
4
4
|
export declare function ReplayModal({ api, onClose, closeLabel, }: {
|
|
5
5
|
api: ReplayApi;
|
|
6
6
|
onClose: () => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type
|
|
3
|
-
export declare function SessionPicker({
|
|
4
|
-
|
|
2
|
+
import { type SessionEntry } from '../api.js';
|
|
3
|
+
export declare function SessionPicker({ entries, loading, onPick, }: {
|
|
4
|
+
entries: SessionEntry[];
|
|
5
|
+
loading?: boolean;
|
|
5
6
|
onPick: (id: string) => void;
|
|
6
7
|
}): React.ReactElement;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { TimelineModel } from '../../core/timeline.js';
|
|
3
|
-
export declare function TimelineView({ model, jumpToSeq, }: {
|
|
3
|
+
export declare function TimelineView({ model, jumpToSeq, jumpNonce, }: {
|
|
4
4
|
model: TimelineModel;
|
|
5
5
|
jumpToSeq?: number;
|
|
6
|
+
jumpNonce?: number;
|
|
6
7
|
}): React.ReactElement;
|
|
@@ -9,5 +9,10 @@ export interface ExportSession {
|
|
|
9
9
|
events: ParsedSession['events'];
|
|
10
10
|
title?: string;
|
|
11
11
|
}
|
|
12
|
-
export
|
|
12
|
+
export interface BuildReplayOptions {
|
|
13
|
+
lang?: 'en' | 'zh';
|
|
14
|
+
title?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildReplayHtml(viewerSource: string, sessions: ExportSession[], options?: BuildReplayOptions): string;
|
|
13
17
|
export declare function downloadReplayHtml(html: string, filename: string): void;
|
|
18
|
+
export declare function formatBytes(bytes: number): string;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lightweight EN/中文 i18n for the
|
|
2
|
+
* Lightweight EN/中文 i18n for the review UI. The dictionary is plain data;
|
|
3
3
|
* `useI18n()` provides `t(key, params)` plus the current language and setter.
|
|
4
4
|
* Language resolution: explicit choice (persisted) > browser language > en.
|
|
5
|
+
*
|
|
6
|
+
* A tiny external store keeps the sidebar button, the modal chrome and the
|
|
7
|
+
* embedded app in sync when the language is toggled from any of them.
|
|
5
8
|
*/
|
|
6
9
|
import React from 'react';
|
|
7
10
|
export type Lang = 'en' | 'zh';
|
|
@@ -9,6 +12,10 @@ export type T = (key: string, params?: Record<string, string | number>) => strin
|
|
|
9
12
|
export declare function makeT(lang: Lang): T;
|
|
10
13
|
export declare function detectLang(): Lang;
|
|
11
14
|
export declare function persistLang(lang: Lang): void;
|
|
15
|
+
export declare function getLang(): Lang;
|
|
16
|
+
export declare function setLang(lang: Lang): void;
|
|
17
|
+
export declare function subscribeLang(listener: () => void): () => void;
|
|
18
|
+
export declare function useLang(): Lang;
|
|
12
19
|
export interface I18n {
|
|
13
20
|
lang: Lang;
|
|
14
21
|
t: T;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type PricingEntry, type PricingRate } from '../core/cost.js';
|
|
2
|
+
export declare function getPricing(): PricingEntry[];
|
|
3
|
+
export declare function setRate(label: string, rate: PricingRate): void;
|
|
4
|
+
export declare function resetPricing(): void;
|
|
5
|
+
export declare function subscribePricing(listener: () => void): () => void;
|
|
6
|
+
export declare function usePricing(): PricingEntry[];
|
|
@@ -3,14 +3,24 @@
|
|
|
3
3
|
* with a severity, grouped for the audit view. Rules are data, not code —
|
|
4
4
|
* extend `DEFAULT_RULES` or pass your own list.
|
|
5
5
|
*
|
|
6
|
+
* Design goals, in order:
|
|
7
|
+
* 1. Signal, not activity. A clean session should produce zero findings;
|
|
8
|
+
* routine file writes and ordinary network reads are not "risks".
|
|
9
|
+
* 2. Windows is a first-class platform. `pwsh`, PowerShell aliases and
|
|
10
|
+
* Windows paths are scanned just like bash on Unix.
|
|
11
|
+
* 3. Baseline is not a finding. Every session starts with a permission
|
|
12
|
+
* preset and sandbox mode; only a later *change* is interesting.
|
|
13
|
+
*
|
|
6
14
|
* Severity vocabulary mirrors a standard status palette:
|
|
7
15
|
* 'critical' | 'serious' | 'warning' | 'info'.
|
|
8
16
|
*/
|
|
9
17
|
import type { ParsedSession, SessionEvent } from './types.js';
|
|
10
18
|
export type Severity = 'critical' | 'serious' | 'warning' | 'info';
|
|
19
|
+
export type AuditCategory = 'shell' | 'file' | 'network' | 'approval' | 'config' | 'error' | 'secret';
|
|
11
20
|
export interface AuditFinding {
|
|
12
21
|
ruleId: string;
|
|
13
22
|
severity: Severity;
|
|
23
|
+
category?: AuditCategory;
|
|
14
24
|
seq: number;
|
|
15
25
|
time: number;
|
|
16
26
|
/** English title (stable fallback). */
|
|
@@ -22,6 +32,7 @@ export interface AuditFinding {
|
|
|
22
32
|
}
|
|
23
33
|
export interface AuditRule {
|
|
24
34
|
id: string;
|
|
35
|
+
category?: AuditCategory;
|
|
25
36
|
severity: Severity | ((event: SessionEvent) => Severity);
|
|
26
37
|
/** Return a finding title when the event matches, undefined otherwise. */
|
|
27
38
|
match: (event: SessionEvent) => {
|
|
@@ -29,11 +40,18 @@ export interface AuditRule {
|
|
|
29
40
|
detail?: string;
|
|
30
41
|
messageKey?: string;
|
|
31
42
|
params?: Record<string, string>;
|
|
43
|
+
category?: AuditCategory;
|
|
32
44
|
} | undefined;
|
|
33
45
|
}
|
|
34
46
|
export interface AuditReport {
|
|
35
47
|
findings: AuditFinding[];
|
|
36
48
|
counts: Record<Severity, number>;
|
|
49
|
+
/** Number of raw events scanned. */
|
|
50
|
+
scanned: number;
|
|
51
|
+
/** Number of rules evaluated. */
|
|
52
|
+
rules: number;
|
|
53
|
+
/** Baseline/duplicate config events deliberately ignored. */
|
|
54
|
+
suppressed: number;
|
|
37
55
|
}
|
|
38
56
|
export declare const DEFAULT_RULES: AuditRule[];
|
|
39
57
|
export declare function auditSession(session: ParsedSession, rules?: AuditRule[]): AuditReport;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ParsedSession } from './types.js';
|
|
1
|
+
import type { ParsedSession, SessionEvent } from './types.js';
|
|
2
2
|
export interface CompareStats {
|
|
3
3
|
id: string;
|
|
4
4
|
title?: string;
|
|
@@ -15,6 +15,7 @@ export interface CompareStats {
|
|
|
15
15
|
models: string[];
|
|
16
16
|
toolMix: Record<string, number>;
|
|
17
17
|
}
|
|
18
|
+
export type CompareRelation = 'same' | 'a-parent-of-b' | 'b-parent-of-a' | 'siblings' | 'none';
|
|
18
19
|
export interface CompareResult {
|
|
19
20
|
a: CompareStats;
|
|
20
21
|
b: CompareStats;
|
|
@@ -22,10 +23,14 @@ export interface CompareResult {
|
|
|
22
23
|
toolNames: string[];
|
|
23
24
|
/** First seq at which the two logs differ (comparing type+data), if computable. */
|
|
24
25
|
divergenceSeq?: number;
|
|
25
|
-
/**
|
|
26
|
+
/** The first differing event on each side, for a side-by-side context view. */
|
|
27
|
+
divergenceA?: SessionEvent;
|
|
28
|
+
divergenceB?: SessionEvent;
|
|
29
|
+
/** True when one session is an ancestor/relative of the other (any depth). */
|
|
26
30
|
related: boolean;
|
|
31
|
+
relation: CompareRelation;
|
|
27
32
|
}
|
|
28
33
|
export declare function compareSessions(a: ParsedSession, b: ParsedSession, titles?: {
|
|
29
34
|
a?: string;
|
|
30
35
|
b?: string;
|
|
31
|
-
}): CompareResult;
|
|
36
|
+
}, parentOf?: (id: string) => string | undefined): CompareResult;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cost estimation from token totals. Prices are USD per 1M tokens and are
|
|
3
|
-
* ESTIMATES — provider list prices change;
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* ESTIMATES — provider list prices change; the UI lets you override them.
|
|
4
|
+
* Cache-write falls back to the input rate when a provider doesn't price it
|
|
5
|
+
* separately.
|
|
6
6
|
*/
|
|
7
7
|
import type { TokenTotals } from './timeline.js';
|
|
8
8
|
export interface PricingRate {
|
|
@@ -19,6 +19,9 @@ export interface PricingEntry {
|
|
|
19
19
|
match: RegExp;
|
|
20
20
|
rate: PricingRate;
|
|
21
21
|
label: string;
|
|
22
|
+
/** ISO date the default rates were last checked. */
|
|
23
|
+
updatedAt?: string;
|
|
24
|
+
source?: string;
|
|
22
25
|
}
|
|
23
26
|
/** Editable defaults — first match wins, most specific first. */
|
|
24
27
|
export declare const DEFAULT_PRICING: PricingEntry[];
|
|
@@ -26,5 +29,20 @@ export interface CostEstimate {
|
|
|
26
29
|
usd: number;
|
|
27
30
|
label: string;
|
|
28
31
|
}
|
|
32
|
+
export interface CostPart extends CostEstimate {
|
|
33
|
+
model: string;
|
|
34
|
+
totals: TokenTotals;
|
|
35
|
+
}
|
|
36
|
+
export interface CostEstimateDetailed extends CostEstimate {
|
|
37
|
+
parts: CostPart[];
|
|
38
|
+
}
|
|
29
39
|
export declare function estimateCost(totals: TokenTotals, models: string[], pricing?: PricingEntry[]): CostEstimate | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Per-model cost. A session can switch models mid-run; charging every token at
|
|
42
|
+
* the first matching rate over- or under-estimates the bill.
|
|
43
|
+
*/
|
|
44
|
+
export declare function estimateCostDetailed(modelTotals: Record<string, TokenTotals>, pricing?: PricingEntry[]): CostEstimateDetailed | undefined;
|
|
30
45
|
export declare function formatUsd(usd: number): string;
|
|
46
|
+
/** Approximate CNY amount at a caller-supplied exchange rate. */
|
|
47
|
+
export declare function formatCny(usd: number, rate?: number): string;
|
|
48
|
+
export declare function formatMoney(usd: number, currency: 'USD' | 'CNY', rate?: number): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret redaction for anything that leaves the process: exported HTML,
|
|
3
|
+
* audit details pasted into a report, and screenshots of the UI.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately conservative and pattern-based. It is a safety net,
|
|
6
|
+
* not a guarantee: the export dialog still warns that session logs can contain
|
|
7
|
+
* arbitrary user data.
|
|
8
|
+
*/
|
|
9
|
+
import type { ParsedSession } from './types.js';
|
|
10
|
+
export interface RedactionResult {
|
|
11
|
+
text: string;
|
|
12
|
+
count: number;
|
|
13
|
+
kinds: string[];
|
|
14
|
+
}
|
|
15
|
+
/** Mask secrets in one string. */
|
|
16
|
+
export declare function redactText(input: string): RedactionResult;
|
|
17
|
+
/**
|
|
18
|
+
* True when the text looks like it contains a credential. Deliberately
|
|
19
|
+
* narrower than `redactText`: an email address or a home path is privacy
|
|
20
|
+
* sensitive, but not a reason to raise a critical audit finding.
|
|
21
|
+
*/
|
|
22
|
+
export declare function containsSecret(input: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Deep-redact a JSON-like value. Keys are preserved; string values are
|
|
25
|
+
* redacted. Depth is bounded so a hostile log cannot exhaust the stack.
|
|
26
|
+
*/
|
|
27
|
+
export declare function redactValue<T>(value: T, depth?: number): {
|
|
28
|
+
value: T;
|
|
29
|
+
count: number;
|
|
30
|
+
};
|
|
31
|
+
export interface RedactSessionResult {
|
|
32
|
+
session: ParsedSession;
|
|
33
|
+
count: number;
|
|
34
|
+
kinds: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clone and redact a parsed session. Event envelopes (seq/time/type) are
|
|
38
|
+
* preserved so the viewer still works; only payload strings are masked.
|
|
39
|
+
*/
|
|
40
|
+
export declare function redactSession(session: ParsedSession): RedactSessionResult;
|
|
@@ -12,6 +12,8 @@ export interface ToolCallInfo {
|
|
|
12
12
|
/** Raw argument JSON string exactly as the model produced it. */
|
|
13
13
|
argumentsRaw: string;
|
|
14
14
|
argumentsPretty?: string;
|
|
15
|
+
/** Short, human-readable summary (command/path/url/query) for the rail. */
|
|
16
|
+
summary?: string;
|
|
15
17
|
resultText?: string;
|
|
16
18
|
error?: {
|
|
17
19
|
name: string;
|
|
@@ -75,6 +77,8 @@ export interface TimelineModel {
|
|
|
75
77
|
/** Flat, seq-ordered list of everything renderable. */
|
|
76
78
|
items: TimelineItem[];
|
|
77
79
|
totals: TokenTotals;
|
|
80
|
+
/** Token totals split by model, for cache-aware multi-model cost. */
|
|
81
|
+
modelTotals: Record<string, TokenTotals>;
|
|
78
82
|
/** Per assistant-step usage samples in order, for the token strip. */
|
|
79
83
|
usageSamples: {
|
|
80
84
|
seq: number;
|
|
@@ -93,4 +97,9 @@ export interface TimelineModel {
|
|
|
93
97
|
/** Latest `session/title` event payload, when the log carries one. */
|
|
94
98
|
title?: string;
|
|
95
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Turn tool arguments into a label a human can scan. The raw JSON stays
|
|
102
|
+
* available in the detail panel; the rail should not be a wall of escapes.
|
|
103
|
+
*/
|
|
104
|
+
export declare function summarizeToolArguments(name: string, raw: string): string;
|
|
96
105
|
export declare function buildTimeline(session: ParsedSession): TimelineModel;
|
package/lib/types/src/index.d.ts
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
* Registers a small read-only HTTP API on the harness web server that the
|
|
5
5
|
* browser half (and nothing else) consumes:
|
|
6
6
|
*
|
|
7
|
-
* GET /replay/api/sessions → { sessions: [{ header, title }] }
|
|
7
|
+
* GET /replay/api/sessions → { sessions: [{ header, title, live }] }
|
|
8
8
|
* GET /replay/api/session/<id> → { header, events }
|
|
9
|
+
* GET /replay/api/summary?limit=30 → aggregate stats for the dashboard
|
|
10
|
+
* GET /replay/api/viewer.js → standalone viewer bundle
|
|
9
11
|
*
|
|
10
12
|
* Reads go through `ctx.sessionQuery` (live-preferred corpus), so an open
|
|
11
13
|
* session replays with its in-memory state, not a stale disk artifact.
|
|
14
|
+
*
|
|
15
|
+
* The API is intentionally loopback-only by default: session logs can contain
|
|
16
|
+
* secrets, and this route is registered directly on the web server rather than
|
|
17
|
+
* through the authenticated Connection fetch bridge.
|
|
12
18
|
*/
|
|
13
19
|
import type { Context } from '@deepseek-ai/cordis';
|
|
14
20
|
export declare const name = "dsh-replay";
|
|
@@ -16,5 +22,50 @@ export declare const inject: string[];
|
|
|
16
22
|
export interface Config {
|
|
17
23
|
/** URL prefix for the replay API (exact-prefix route). */
|
|
18
24
|
routePrefix?: string;
|
|
25
|
+
/** Allow non-loopback clients. Off by default because the route is unauthenticated. */
|
|
26
|
+
allowRemote?: boolean;
|
|
27
|
+
/** Maximum sessions included in one dashboard summary request. */
|
|
28
|
+
summaryLimit?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SessionEntry {
|
|
31
|
+
header: Record<string, unknown>;
|
|
32
|
+
title?: string;
|
|
33
|
+
live?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface SessionSummary {
|
|
36
|
+
header: Record<string, unknown>;
|
|
37
|
+
title?: string;
|
|
38
|
+
live?: boolean;
|
|
39
|
+
summary?: {
|
|
40
|
+
events: number;
|
|
41
|
+
turns: number;
|
|
42
|
+
steps: number;
|
|
43
|
+
toolCalls: number;
|
|
44
|
+
toolErrors: number;
|
|
45
|
+
durationMs: number;
|
|
46
|
+
models: string[];
|
|
47
|
+
tokens: {
|
|
48
|
+
input: number;
|
|
49
|
+
output: number;
|
|
50
|
+
cacheRead: number;
|
|
51
|
+
cacheWrite: number;
|
|
52
|
+
reasoning: number;
|
|
53
|
+
billedInput: number;
|
|
54
|
+
};
|
|
55
|
+
modelTotals: Record<string, {
|
|
56
|
+
input: number;
|
|
57
|
+
output: number;
|
|
58
|
+
cacheRead: number;
|
|
59
|
+
cacheWrite: number;
|
|
60
|
+
reasoning: number;
|
|
61
|
+
billedInput: number;
|
|
62
|
+
}>;
|
|
63
|
+
findings: number;
|
|
64
|
+
critical: number;
|
|
65
|
+
serious: number;
|
|
66
|
+
warning: number;
|
|
67
|
+
info: number;
|
|
68
|
+
};
|
|
69
|
+
error?: string;
|
|
19
70
|
}
|
|
20
71
|
export declare function apply(ctx: Context, config?: Config): void;
|