@huanlin/dsh-focus-chat 0.1.23

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.
Files changed (52) hide show
  1. package/README.md +73 -0
  2. package/README.zh.md +71 -0
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +6051 -0
  5. package/lib/index.js +6 -0
  6. package/lib/types/client/apply.d.ts +12 -0
  7. package/lib/types/client/contract/props.d.ts +78 -0
  8. package/lib/types/client/index.d.ts +10 -0
  9. package/lib/types/client/locales.d.ts +437 -0
  10. package/lib/types/client/model/feedback-controller.d.ts +161 -0
  11. package/lib/types/client/model/flow.d.ts +76 -0
  12. package/lib/types/client/model/index.d.ts +5 -0
  13. package/lib/types/client/model/text.d.ts +42 -0
  14. package/lib/types/client/model/todo.d.ts +38 -0
  15. package/lib/types/client/model/tools.d.ts +35 -0
  16. package/lib/types/client/model/turn-slice.d.ts +38 -0
  17. package/lib/types/client/model/types.d.ts +289 -0
  18. package/lib/types/client/view/FocusView.d.ts +9 -0
  19. package/lib/types/client/view/chrome/ImageLightbox.d.ts +26 -0
  20. package/lib/types/client/view/chrome/MessageActions.d.ts +24 -0
  21. package/lib/types/client/view/chrome/MessageFeedbackActions.d.ts +46 -0
  22. package/lib/types/client/view/chrome/MessageImage.d.ts +46 -0
  23. package/lib/types/client/view/chrome/NavRail.d.ts +21 -0
  24. package/lib/types/client/view/chrome/ReferenceIcon.d.ts +17 -0
  25. package/lib/types/client/view/chrome/RunningStatus.d.ts +7 -0
  26. package/lib/types/client/view/chrome/TurnNavigator.d.ts +14 -0
  27. package/lib/types/client/view/helpers/format.d.ts +30 -0
  28. package/lib/types/client/view/helpers/icons.d.ts +9 -0
  29. package/lib/types/client/view/helpers/image-labels.d.ts +13 -0
  30. package/lib/types/client/view/helpers/message.d.ts +13 -0
  31. package/lib/types/client/view/helpers/terminal.d.ts +20 -0
  32. package/lib/types/client/view/rows/CommandRow.d.ts +12 -0
  33. package/lib/types/client/view/rows/CompactionRow.d.ts +24 -0
  34. package/lib/types/client/view/rows/ContextRow.d.ts +22 -0
  35. package/lib/types/client/view/rows/FlowRow.d.ts +20 -0
  36. package/lib/types/client/view/rows/RemoteTurnRow.d.ts +33 -0
  37. package/lib/types/client/view/rows/RetryRow.d.ts +10 -0
  38. package/lib/types/client/view/rows/SystemPromptRow.d.ts +12 -0
  39. package/lib/types/client/view/rows/ThinkRow.d.ts +16 -0
  40. package/lib/types/client/view/rows/ToolCallRow.d.ts +8 -0
  41. package/lib/types/client/view/rows/ToolGroupRow.d.ts +11 -0
  42. package/lib/types/client/view/rows/TurnFoldRow.d.ts +27 -0
  43. package/lib/types/client/view/rows/TurnTailRow.d.ts +16 -0
  44. package/lib/types/client/view/rows/TurnUsageDisclosure.d.ts +9 -0
  45. package/lib/types/client/view/rows/UserBubble.d.ts +18 -0
  46. package/lib/types/client/view/rows/group-title.d.ts +32 -0
  47. package/lib/types/client/view/rows/produced-fit.d.ts +14 -0
  48. package/lib/types/host/rpc.d.ts +63 -0
  49. package/lib/types/host/turn-index.d.ts +40 -0
  50. package/lib/types/index.d.ts +3 -0
  51. package/lib/types/protocol.d.ts +94 -0
  52. package/package.json +109 -0
@@ -0,0 +1,22 @@
1
+ import type { MarkdownLabels } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { FocusTranslate } from '../../contract/props.ts';
3
+ import type { FocusFlowItem } from '../../model/types.ts';
4
+ /** Logged context-injection row (the chat ContextInjectionRow chrome: header, source, form body). */
5
+ export declare const ContextRow: import("react").MemoExoticComponent<({ item, t, mdLabels }: {
6
+ item: Extract<FocusFlowItem, {
7
+ kind: "message";
8
+ }>;
9
+ t: FocusTranslate;
10
+ mdLabels: MarkdownLabels;
11
+ }) => import("react").JSX.Element>;
12
+ /** One running turn's context batch: consecutive context injections under a
13
+ * single collapsed line (the completed turn folds them into the turn fold
14
+ * instead; expanding here reveals the individual ContextRows). */
15
+ export declare const ContextFoldRow: import("react").MemoExoticComponent<({ item, t, mdLabels }: {
16
+ item: Extract<FocusFlowItem, {
17
+ kind: "context-fold";
18
+ }>;
19
+ t: FocusTranslate;
20
+ mdLabels: MarkdownLabels;
21
+ }) => import("react").JSX.Element>;
22
+ /** User / steering bubble row (the chat UserStyleBubble chrome: image gallery, chips, clock, copy). */
@@ -0,0 +1,20 @@
1
+ import type { MarkdownLabels, MarkdownFileMentions } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { FocusTranslate } from '../../contract/props.ts';
3
+ import type { FocusFlowItem } from '../../model/types.ts';
4
+ import { type ImageLoader } from '../chrome/MessageImage.tsx';
5
+ import type { FocusFeedbackActions } from '../chrome/MessageFeedbackActions.tsx';
6
+ export declare const FlowRow: import("react").MemoExoticComponent<({ item, t, mdLabels, openFile, forkAt, mentionsByKey, loadImage, feedback, isLoopback }: {
7
+ item: FocusFlowItem;
8
+ t: FocusTranslate;
9
+ mdLabels: MarkdownLabels;
10
+ openFile: (path: string) => void;
11
+ forkAt: (seq: number) => void;
12
+ /** Inline file-mention vocabulary per assistant node key (closing prose). */
13
+ mentionsByKey: ReadonlyMap<string, MarkdownFileMentions | undefined>;
14
+ loadImage: ImageLoader;
15
+ /** Per-message feedback verbs (the assistant-actions strip's business face). */
16
+ feedback: FocusFeedbackActions;
17
+ isLoopback: boolean;
18
+ }) => import("react").JSX.Element | null>;
19
+ /** Stable React key for one flow item. */
20
+ export declare function flowKey(item: FocusFlowItem): string;
@@ -0,0 +1,33 @@
1
+ import type { MarkdownLabels, MarkdownFileMentions } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { FocusTranslate } from '../../contract/props.ts';
3
+ import type { FocusFlowItem } from '../../model/types.ts';
4
+ import { type TurnSlice } from '../../model/turn-slice.ts';
5
+ import type { ImageLoader } from '../chrome/MessageImage.tsx';
6
+ import type { FocusFeedbackActions } from '../chrome/MessageFeedbackActions.tsx';
7
+ /**
8
+ * One completed pre-window turn: the opening user bubbles, the fold line (the
9
+ * local turn fold's exact chrome), and the closing reply rendered as a real
10
+ * assistant row from the Host index's carried message. A boundary turn — its
11
+ * closing still inside the window — keeps the real closing reply and turn tail
12
+ * rendered from the window rows below the fold instead. Expanding fetches the
13
+ * turn's event slice through the injected callback and renders the projected
14
+ * rows — the same FlowRow/TurnTailRow chrome the window flow draws. A failed
15
+ * fetch surfaces inline with a retry.
16
+ */
17
+ export declare const RemoteTurnRow: import("react").MemoExoticComponent<({ item, slice, onExpand, t, mdLabels, openFile, forkAt, mentionsByKey, loadImage, feedback, isLoopback, }: {
18
+ item: Extract<FocusFlowItem, {
19
+ kind: "remote-turn";
20
+ }>;
21
+ /** The cached projection for this turn; absent until first expansion. */
22
+ slice: TurnSlice | undefined;
23
+ /** Fetch, project, and cache this turn's slice; resolves when cached. */
24
+ onExpand: (turn: number) => Promise<void>;
25
+ t: FocusTranslate;
26
+ mdLabels: MarkdownLabels;
27
+ openFile: (path: string) => void;
28
+ forkAt: (seq: number) => void;
29
+ mentionsByKey: ReadonlyMap<string, MarkdownFileMentions | undefined>;
30
+ loadImage: ImageLoader;
31
+ feedback: FocusFeedbackActions;
32
+ isLoopback: boolean;
33
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,10 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ import type { FocusFlowItem } from '../../model/types.ts';
3
+ export declare const RetryRow: import("react").MemoExoticComponent<({ item, t }: {
4
+ item: Extract<FocusFlowItem, {
5
+ kind: "retry";
6
+ }>;
7
+ t: FocusTranslate;
8
+ }) => import("react").JSX.Element>;
9
+ /** One completed turn's work line: every intermediate assistant row and tool
10
+ * run folded under `工作了 X 分 Y 秒`, expandable back to the full rows. */
@@ -0,0 +1,12 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ /**
3
+ * One complete system prompt as a collapsed disclosure (the official
4
+ * SystemPromptRow chrome: browse icon, "System prompt" title, and an
5
+ * expanded body holding the full model-visible prompt text with its real
6
+ * line breaks).
7
+ */
8
+ export declare const SystemPromptRow: import("react").MemoExoticComponent<({ text, t }: {
9
+ /** Complete model-visible prompt text. */
10
+ text: string;
11
+ t: FocusTranslate;
12
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,16 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ /**
3
+ * One Think disclosure, mirroring the chat reasoning row: the first line
4
+ * once settled; while the reasoning is the streaming tail the collapsed row
5
+ * grows a four-line end-following preview under the header — the height
6
+ * animates open and shut, and the running sweep washes the whole segment;
7
+ * the body expands on click.
8
+ */
9
+ export declare const ThinkRow: import("react").MemoExoticComponent<({ text, running, title, t }: {
10
+ text: string;
11
+ /** Whether the reasoning is still the streaming tail. */
12
+ running: boolean;
13
+ /** Row title: the plain Think label, or the duration for a standalone row. */
14
+ title: string;
15
+ t: FocusTranslate;
16
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,8 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ import type { FocusToolRow } from '../../model/types.ts';
3
+ /** One Tool call row inside an expanded group: the chat ToolRow chrome (title · summary, cards, IN/OUT). */
4
+ export declare const ToolCallRow: import("react").MemoExoticComponent<({ row, t, openFile }: {
5
+ row: FocusToolRow;
6
+ t: FocusTranslate;
7
+ openFile: (path: string) => void;
8
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,11 @@
1
+ import type { MarkdownLabels } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { FocusTranslate } from '../../contract/props.ts';
3
+ import type { FocusToolGroup } from '../../model/types.ts';
4
+ /** One folded run of Tool calls: the step-summary line with its metrics. */
5
+ export declare const ToolGroupRow: import("react").MemoExoticComponent<({ group, t, mdLabels, openFile }: {
6
+ group: FocusToolGroup;
7
+ t: FocusTranslate;
8
+ mdLabels: MarkdownLabels;
9
+ openFile: (path: string) => void;
10
+ }) => import("react").JSX.Element | null>;
11
+ /** The chat IconActions chrome: copy, optional branch, and an optional date-aware clock. */
@@ -0,0 +1,27 @@
1
+ import type { MarkdownLabels, MarkdownFileMentions } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { FocusTranslate } from '../../contract/props.ts';
3
+ import type { FocusFlowItem } from '../../model/types.ts';
4
+ import type { ImageLoader } from '../chrome/MessageImage.tsx';
5
+ import type { FocusFeedbackActions } from '../chrome/MessageFeedbackActions.tsx';
6
+ /**
7
+ * One completed turn's work line, drawn with the official turn-process
8
+ * chrome: a bare label button with a trailing chevron and the l2 separator
9
+ * underneath — no leading icon, the official 14px/24px label type — expanding
10
+ * the turn's folded rows. The measurement (worked duration, stopped state)
11
+ * stays the focus view's reading; the turn-process node's counts ride the
12
+ * model.
13
+ */
14
+ export declare const TurnFoldRow: import("react").MemoExoticComponent<({ item, t, mdLabels, openFile, forkAt, mentionsByKey, loadImage, feedback, isLoopback }: {
15
+ item: Extract<FocusFlowItem, {
16
+ kind: "turn-fold";
17
+ }>;
18
+ t: FocusTranslate;
19
+ mdLabels: MarkdownLabels;
20
+ openFile: (path: string) => void;
21
+ forkAt: (seq: number) => void;
22
+ mentionsByKey: ReadonlyMap<string, MarkdownFileMentions | undefined>;
23
+ loadImage: ImageLoader;
24
+ /** Per-message feedback verbs (the assistant-actions strip's business face). */
25
+ feedback: FocusFeedbackActions;
26
+ isLoopback: boolean;
27
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,16 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ import type { FocusFlowItem } from '../../model/types.ts';
3
+ import { type FocusFeedbackActions } from '../chrome/MessageFeedbackActions.tsx';
4
+ /** One completed turn's footer: the measured produced-files lane and the chat actions chrome. */
5
+ export declare const TurnTailRow: import("react").MemoExoticComponent<({ item, openFile, forkAt, feedback, t, isLoopback }: {
6
+ item: Extract<FocusFlowItem, {
7
+ kind: "turn-tail";
8
+ }>;
9
+ openFile: (path: string) => void;
10
+ forkAt: (seq: number) => void;
11
+ /** Per-message feedback verbs (the assistant-actions strip's business face). */
12
+ feedback: FocusFeedbackActions;
13
+ t: FocusTranslate;
14
+ isLoopback: boolean;
15
+ }) => import("react").JSX.Element>;
16
+ /** One Host-authoritative pending steering item (the chat pending bubble shape). */
@@ -0,0 +1,9 @@
1
+ import type { TurnTokenUsage } from '@deepseek-ai/dsh-client-ui-chat/src/client/contract/chat-nodes.ts';
2
+ import type { FocusTranslate } from '../../contract/props.ts';
3
+ /** Compact per-Turn usage summary with an opt-in bucket breakdown (the
4
+ * official TurnUsageDisclosure chrome: data icon, summary line, and the
5
+ * exact bucket list on expand). */
6
+ export declare const TurnUsageDisclosure: import("react").MemoExoticComponent<({ usage, t }: {
7
+ usage: TurnTokenUsage;
8
+ t: FocusTranslate;
9
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,18 @@
1
+ import type { MarkdownLabels } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
3
+ import type { FocusTranslate } from '../../contract/props.ts';
4
+ import type { FocusFlowItem } from '../../model/types.ts';
5
+ import { type ImageLoader } from '../chrome/MessageImage.tsx';
6
+ export declare const MessageRow: import("react").MemoExoticComponent<({ item, t, mdLabels, loadImage }: {
7
+ item: Extract<FocusFlowItem, {
8
+ kind: "message";
9
+ }>;
10
+ t: FocusTranslate;
11
+ mdLabels: MarkdownLabels;
12
+ loadImage: ImageLoader;
13
+ }) => import("react").JSX.Element>;
14
+ export declare const PendingSteeringBubble: import("react").MemoExoticComponent<({ content, t, loadImage }: {
15
+ content: readonly ContentBlock[];
16
+ t: FocusTranslate;
17
+ loadImage: ImageLoader;
18
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,32 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ import type { FocusToolGroup } from '../../model/types.ts';
3
+ /** The step-summary line parts (pre-casing): the thinking duration leads,
4
+ * then the absorbed context count, the metric families with per-family
5
+ * failure tallies, and the metric-less tool calls as a "called N tools"
6
+ * segment. While the run executes the line is replaced by the running
7
+ * call's own row title. */
8
+ /** One summary-line segment: plain text plus an optional failure tally
9
+ * (parentheses included) rendered in the error color. */
10
+ export interface GroupTitleSegment {
11
+ text: string;
12
+ failed?: string | undefined;
13
+ }
14
+ export declare function groupTitleParts(group: FocusToolGroup, t: FocusTranslate): GroupTitleSegment[];
15
+ /** PR67 sentence style: the first visible segment is capitalized, every
16
+ * later segment starts lowercase (a no-op for the zh line). */
17
+ export declare function caseSegments(segments: GroupTitleSegment[]): GroupTitleSegment[];
18
+ /** One metric family's summary segment with PR67 failure semantics for the
19
+ * failure-carrying families: the count reads successful calls, a mixed
20
+ * family appends its failure tally (red, parentheses included), and a
21
+ * family that failed outright reads its singular failed phrase or the count
22
+ * with an all-failed suffix. The file families read a plain count — their
23
+ * tallies are never shown. */
24
+ export declare function metricPart(parts: GroupTitleSegment[], total: number, failed: number, family: MetricFamily, t: FocusTranslate): void;
25
+ /** A metric family the summary line aggregates (locale key stem). */
26
+ export type MetricFamily = 'commands' | 'edits' | 'searches';
27
+ /** One agentic family's count segment (delegation / todo / goal / workflow /
28
+ * skill / question / plan / background job): plain count, no failure tally
29
+ * (the chat's family literals; the todo and goal literals drop the count). */
30
+ export declare function agentPart(parts: GroupTitleSegment[], n: number, family: 'subagents' | 'todos' | 'goals' | 'workflows' | 'skills' | 'questions' | 'plans' | 'jobs', t: FocusTranslate): void;
31
+ /** The count segment of one metric family, with the singular form for one. */
32
+ export declare function countSegment(family: MetricFamily, n: number, t: FocusTranslate): string;
@@ -0,0 +1,14 @@
1
+ import type { FocusTranslate } from '../../contract/props.ts';
2
+ /** Files past this stay counted but unlisted: a refactor turn must not bury the answer. */
3
+ export declare const PRODUCED_SHOWN = 6;
4
+ /**
5
+ * Select the largest prefix whose measured chips and exact remainder fit.
6
+ * @param available - usable width of the one-line file lane.
7
+ * @param gap - computed flex gap between adjacent visible items.
8
+ * @param chipWidths - measured widths for the candidate file chips.
9
+ * @param moreWidthsByShown - exact localized remainder width for each shown count.
10
+ * @returns Number of leading chips to render.
11
+ */
12
+ export declare function fitProducedFiles(available: number, gap: number, chipWidths: readonly number[], moreWidthsByShown: readonly (number | undefined)[]): number;
13
+ /** The exact localized overflow remainder ("+ N 个文件" / "+ N files"). */
14
+ export declare function moreLabel(t: FocusTranslate, count: number): string;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Host-side RPC dispatch for the focus channel: wire validation, observation
3
+ * leases, and error mapping on top of {@link computeTurnIndex}. Pure function
4
+ * `computeTurnIndex` owns the derivation; this module owns the lease and the
5
+ * dispatch. The two services are declared structurally: the host- and
6
+ * client-connection faces cannot share one tsc program, and this module only
7
+ * needs the RPC registry and one exact observation read.
8
+ * @module dsh-focus-chat/host/rpc
9
+ */
10
+ import type { Context } from '@deepseek-ai/cordis';
11
+ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session/types';
12
+ import type { FocusRpcValue } from '../protocol.ts';
13
+ /** One exact observation lease over a session log (the SessionObservation face this module reads). */
14
+ export interface FocusObservation {
15
+ /** The session's durable events, in seq order. */
16
+ readonly events: readonly SessionEvent[];
17
+ /** Last observed event seq, or -1 for an empty log. */
18
+ readonly cursor: number;
19
+ /** Release the lease (a prepared read holds a persistence borrow). */
20
+ [Symbol.dispose]: () => void;
21
+ }
22
+ /** The observation read face this module consumes (the SessionQueryEngine's exact read). */
23
+ export interface FocusSessionQuery {
24
+ observeSession(sessionId: SessionId, options?: {
25
+ signal?: AbortSignal;
26
+ projectionMode?: 'all' | 'none';
27
+ }): Promise<FocusObservation>;
28
+ }
29
+ /** The connection face this module consumes: the RPC channel registry. */
30
+ export interface FocusHostConnection {
31
+ readonly rpc: {
32
+ handle(channel: string, handler: (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<FocusRpcResult>): () => Promise<void>;
33
+ };
34
+ }
35
+ /** The channel's connection-generic result pair. */
36
+ export type FocusRpcResult = {
37
+ readonly ok: true;
38
+ readonly value: FocusRpcValue;
39
+ } | {
40
+ readonly ok: false;
41
+ readonly error: {
42
+ readonly code: string;
43
+ readonly message: string;
44
+ readonly details: object;
45
+ };
46
+ };
47
+ /**
48
+ * Handle one focus channel request: observe the session under a lease, index
49
+ * or slice, and release the lease before answering.
50
+ * @param ctx - host context carrying `sessionQuery` and `connection`.
51
+ * @param endpoint - channel-relative endpoint name.
52
+ * @param payload - channel-owned request payload.
53
+ * @param signal - caller cancellation for this request.
54
+ * @returns the endpoint-owned success/error result.
55
+ */
56
+ export declare function handleFocusRpc(ctx: Context, endpoint: string, payload: unknown, signal: AbortSignal): Promise<FocusRpcResult>;
57
+ /**
58
+ * Register the focus RPC channel on the host connection. The registration is
59
+ * an effect: the caller wraps it, so plugin unload removes the channel.
60
+ * @param ctx - host context carrying the connection and session-query services.
61
+ * @returns the channel's disposer.
62
+ */
63
+ export declare function registerFocusRpc(ctx: Context): () => Promise<void>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Host-side turn index: one O(n) scan over the durable session log that
3
+ * derives every completed turn's condensed summary. Pure function — the RPC
4
+ * layer owns observation leases and dispatch; this module owns the derivation.
5
+ * @module dsh-focus-chat/host/turn-index
6
+ */
7
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
8
+ import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
9
+ import type { TurnSummary } from '../protocol.ts';
10
+ /** The complete turn index over one session log. */
11
+ export interface TurnIndex {
12
+ /** Completed turns in log order; a running or crash-opened turn is absent. */
13
+ readonly turns: readonly TurnSummary[];
14
+ /** Last observed event seq of the log, or -1 for an empty log. */
15
+ readonly cursor: number;
16
+ }
17
+ /** One settled `assistant/message` event reduced to the fields the flow rows render. */
18
+ export interface ClosingMessage {
19
+ /** Seq of the `assistant/message` event. */
20
+ readonly seq: number;
21
+ /** Unix epoch ms of the message event. */
22
+ readonly time: number;
23
+ /** Durable message id (the per-message actions' address). */
24
+ readonly messageId: string;
25
+ /** Exact model-facing blocks of the reply. */
26
+ readonly content: readonly ContentBlock[];
27
+ }
28
+ /**
29
+ * Derive the turn index from one session log.
30
+ *
31
+ * Position cursor: every event between `turn/start(T)` and `turn/end(T)`
32
+ * belongs to turn `T` — user messages and other events without a turn field
33
+ * read their membership from position. Only completed turns (a matching
34
+ * `turn/end` was seen) enter the index: a running turn, and a turn whose
35
+ * start was abandoned by a later boundary, stay out. The log is append-only,
36
+ * so an index over a prefix of the log never invalidates.
37
+ * @param events - the session's durable events, in seq order.
38
+ * @returns one turn per completed turn, plus the log cursor.
39
+ */
40
+ export declare function computeTurnIndex(events: readonly SessionEvent[]): TurnIndex;
@@ -0,0 +1,3 @@
1
+ /** Host loader entry for the browser-only focus-view plugin. */
2
+ /** Provides no host-side behavior. */
3
+ export declare function apply(): void;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Wire contract of the focus RPC channel, shared by the host and client
3
+ * halves (type-only: every import here is erased from both bundles).
4
+ *
5
+ * The channel rides the Connection RPC registry at `/focus-chat-api` with
6
+ * two endpoints:
7
+ * - `focus/turnIndex` `{ sessionId }` → `{ turns, cursor }`
8
+ * - `focus/turnEvents` `{ sessionId, turn }` → `{ startSeq, endSeq, events }`
9
+ * @module dsh-focus-chat/protocol
10
+ */
11
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
12
+ import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
13
+ /**
14
+ * One event entry of the durable session log, serialized verbatim. The
15
+ * plugin-merged event vocabulary (`command/*`, `compaction/*`, `llm/retry`,
16
+ * `tool/code-dispatch*`, …) rides the same merge-extensible `SessionEventMap`
17
+ * the host wrote, so consumers read extended kinds structurally.
18
+ */
19
+ export type SessionEventEntry = SessionEvent;
20
+ /** One turn-opening user message, reduced to the durable fields the collapsed row renders. */
21
+ export interface TurnOpeningMessage {
22
+ /** Seq of the `user/message` event. */
23
+ seq: number;
24
+ /** Unix epoch ms of the message event. */
25
+ time: number;
26
+ /** Constant `'user'`: the opening lane only ever collects user-source messages. */
27
+ role: 'user';
28
+ /** Exact model-facing blocks of the message. */
29
+ content: readonly ContentBlock[];
30
+ }
31
+ /** One completed turn's condensed facts, derived once per session log on the Host. */
32
+ export interface TurnSummary {
33
+ /** The turn number (`turn/start` payload). */
34
+ turn: number;
35
+ /** Seq of the `turn/start` event (inclusive slice start). */
36
+ startSeq: number;
37
+ /** Seq of the `turn/end` event (inclusive slice end). */
38
+ endSeq: number;
39
+ /** Unix epoch ms of the `turn/start` event. */
40
+ startTime: number;
41
+ /** Unix epoch ms of the `turn/end` event. */
42
+ endTime: number;
43
+ /** The turn ended interrupted: the fold line reads "用户 X 后停止". */
44
+ stopped: boolean;
45
+ /** Last text-bearing assistant reply's event seq; null when the turn has none. */
46
+ closingSeq: number | null;
47
+ /** Durable message id of that reply; null when the turn has none. */
48
+ closingMessageId: string | null;
49
+ /**
50
+ * That reply's full durable message, carried so a turn whose rows lie beyond
51
+ * the loaded window still renders the real reply directly; null when the
52
+ * turn has none.
53
+ */
54
+ closingTime: number | null;
55
+ closingContent: readonly ContentBlock[] | null;
56
+ /** User-source messages before the turn's first assistant activity, in log order. */
57
+ opening: readonly TurnOpeningMessage[];
58
+ }
59
+ /** Payload of `focus/turnIndex`. */
60
+ export interface TurnIndexRequest {
61
+ /** Non-empty logical session id. */
62
+ sessionId: string;
63
+ }
64
+ /** Result of `focus/turnIndex`: every completed turn's summary plus the log cursor. */
65
+ export interface TurnIndexResponse {
66
+ /** Completed turns in log order; a running or crashed-open turn is absent. */
67
+ turns: readonly TurnSummary[];
68
+ /** Last observed event seq of the session log, or -1 for an empty log. */
69
+ cursor: number;
70
+ }
71
+ /** Payload of `focus/turnEvents`. */
72
+ export interface TurnEventsRequest {
73
+ /** Non-empty logical session id. */
74
+ sessionId: string;
75
+ /** Non-negative turn number that must exist in the turn index. */
76
+ turn: number;
77
+ }
78
+ /**
79
+ * Result of `focus/turnEvents`: the complete durable event slice of one turn,
80
+ * closed interval `[startSeq..endSeq]` — boundary events, messages, chunks,
81
+ * tool calls and results, commands, compaction lifecycle, retries, headers.
82
+ */
83
+ export interface TurnEventsResponse {
84
+ /** Seq of the turn's `turn/start` event. */
85
+ startSeq: number;
86
+ /** Seq of the turn's `turn/end` event. */
87
+ endSeq: number;
88
+ /** Every log event whose seq falls inside the closed interval, in seq order. */
89
+ events: readonly SessionEventEntry[];
90
+ }
91
+ /** The channel's response value union (the RPC result `value`). */
92
+ export type FocusRpcValue = TurnIndexResponse | TurnEventsResponse;
93
+ /** The shared RPC channel the host registers and the client calls. */
94
+ export declare const FOCUS_RPC_CHANNEL = "/focus-chat-api";
package/package.json ADDED
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "@huanlin/dsh-focus-chat",
3
+ "version": "0.1.23",
4
+ "description": "Claude Code-style focus view for the dsh web GUI: the conversation's condensed reading surface — tool calls folded into expandable metric summaries, chat 1:1 row chrome, and streaming think rows",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./lib/types/index.d.ts",
10
+ "default": "./lib/index.js"
11
+ },
12
+ "./client": {
13
+ "types": "./lib/types/client/index.d.ts",
14
+ "default": "./lib/client.js"
15
+ },
16
+ "./src/*": "./src/*",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "dsh": {
20
+ "bundle": {
21
+ "patch": "./cordis.patch.yml"
22
+ },
23
+ "client": {
24
+ "inject": [
25
+ "@deepseek-ai/dsh-client-locale",
26
+ "@deepseek-ai/dsh-client-ui-conversation",
27
+ "@deepseek-ai/dsh-client-ui-chat",
28
+ "@deepseek-ai/dsh-client-connection",
29
+ "@deepseek-ai/dsh-api-remotes"
30
+ ],
31
+ "platform": "web"
32
+ }
33
+ },
34
+ "dshClient": {
35
+ "inject": [
36
+ "@deepseek-ai/dsh-client-locale",
37
+ "@deepseek-ai/dsh-client-ui-conversation",
38
+ "@deepseek-ai/dsh-client-ui-chat",
39
+ "@deepseek-ai/dsh-client-connection",
40
+ "@deepseek-ai/dsh-api-remotes"
41
+ ],
42
+ "platform": "web"
43
+ },
44
+ "peerDependencies": {
45
+ "@deepseek-ai/cordis": ">=4.0.1",
46
+ "@deepseek-ai/dsh-api-remotes": ">=0.1.2-rc.1",
47
+ "@deepseek-ai/dsh-attachment": ">=0.1.2-rc.1",
48
+ "@deepseek-ai/dsh-client-connection": ">=0.1.2-rc.1",
49
+ "@deepseek-ai/dsh-client-locale": ">=0.1.2-rc.1",
50
+ "@deepseek-ai/dsh-client-ui-chat": ">=0.1.2-rc.1",
51
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.2-rc.1",
52
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.2-rc.1",
53
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.2-rc.1",
54
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.2-rc.1",
55
+ "@deepseek-ai/dsh-llm": ">=0.1.2-rc.1",
56
+ "@deepseek-ai/dsh-message-feedback": ">=0.1.2-rc.1",
57
+ "@deepseek-ai/dsh-session": ">=0.1.2-rc.1",
58
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.2-rc.1",
59
+ "@deepseek-ai/dsh-util-workspace-path": ">=0.1.2-rc.1"
60
+ },
61
+ "devDependencies": {
62
+ "@deepseek-ai/cordis": "^4.0.1",
63
+ "@deepseek-ai/dsh-api-remotes": "0.1.2-rc.1",
64
+ "@deepseek-ai/dsh-attachment": "^0.1.2-rc.1",
65
+ "@deepseek-ai/dsh-client-connection": "^0.1.2-rc.1",
66
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
67
+ "@deepseek-ai/dsh-client-test-runtime": "0.1.0-rc.7",
68
+ "@deepseek-ai/dsh-client-ui-attachment": "^0.1.2-rc.1",
69
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
70
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
71
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-rc.1",
72
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
73
+ "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
74
+ "@deepseek-ai/dsh-message-feedback": "0.1.2-rc.1",
75
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1",
76
+ "@testing-library/dom": "^10.4.0",
77
+ "@testing-library/react": "^16.1.0",
78
+ "@types/node": "^22.0.0",
79
+ "@types/react": "~18.3.1",
80
+ "@types/react-dom": "~18.3.0",
81
+ "jsdom": "^25.0.0",
82
+ "lightningcss": "^1.25.0",
83
+ "react": "^18.2.0",
84
+ "react-dom": "^18.2.0",
85
+ "tsdown": "^0.22.2",
86
+ "typescript": "^6.0.3",
87
+ "use-sync-external-store": "1.2.0",
88
+ "vitest": "^4.1.8"
89
+ },
90
+ "license": "BSD-3-Clause",
91
+ "types": "lib/types/index.d.ts",
92
+ "files": [
93
+ "lib/index.js",
94
+ "lib/client.js",
95
+ "lib/types/**/*.d.ts",
96
+ "cordis.patch.yml"
97
+ ],
98
+ "publishConfig": {
99
+ "access": "public"
100
+ },
101
+ "scripts": {
102
+ "build": "tsc -p tsconfig.build.json && tsdown",
103
+ "bundle": "tsdown",
104
+ "watch": "tsdown --watch",
105
+ "typecheck": "tsc --noEmit",
106
+ "test": "vitest run",
107
+ "clean": "rm -rf lib"
108
+ }
109
+ }