dsh-brief-sidebar 0.3.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.
Files changed (40) hide show
  1. package/README.md +178 -0
  2. package/cordis.patch.yml +18 -0
  3. package/dsh.plugin.json +16 -0
  4. package/lib/client.js +960 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.mjs +5620 -0
  7. package/lib/index.mjs.map +1 -0
  8. package/lib/types/client/BriefIcon.d.ts +13 -0
  9. package/lib/types/client/SummaryTab.d.ts +18 -0
  10. package/lib/types/client/TodoBoardTab.d.ts +31 -0
  11. package/lib/types/client/brief/board.d.ts +50 -0
  12. package/lib/types/client/brief/dock-shadow.d.ts +49 -0
  13. package/lib/types/client/brief/use-todos.d.ts +13 -0
  14. package/lib/types/client/file-open.d.ts +50 -0
  15. package/lib/types/client/index.d.ts +23 -0
  16. package/lib/types/client/locales.d.ts +53 -0
  17. package/lib/types/client/summary/DeliverablesSection.d.ts +18 -0
  18. package/lib/types/client/summary/deliverables.d.ts +40 -0
  19. package/lib/types/client/use-projection.d.ts +54 -0
  20. package/lib/types/index.d.ts +27 -0
  21. package/lib/types/projection/deliverables-fold.d.ts +85 -0
  22. package/lib/types/projection/keys.d.ts +25 -0
  23. package/lib/types/projection/register.d.ts +9 -0
  24. package/package.json +94 -0
  25. package/src/client/BriefIcon.tsx +56 -0
  26. package/src/client/SummaryTab.tsx +73 -0
  27. package/src/client/TodoBoardTab.tsx +188 -0
  28. package/src/client/brief/board.ts +104 -0
  29. package/src/client/brief/dock-shadow.tsx +74 -0
  30. package/src/client/brief/use-todos.ts +27 -0
  31. package/src/client/file-open.ts +108 -0
  32. package/src/client/index.tsx +144 -0
  33. package/src/client/locales.ts +78 -0
  34. package/src/client/summary/DeliverablesSection.tsx +192 -0
  35. package/src/client/summary/deliverables.ts +93 -0
  36. package/src/client/use-projection.ts +117 -0
  37. package/src/index.ts +32 -0
  38. package/src/projection/deliverables-fold.ts +206 -0
  39. package/src/projection/keys.ts +27 -0
  40. package/src/projection/register.ts +99 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tab glyph for the task board.
3
+ *
4
+ * Skin contract: plugin-drawn glyphs take their colour from the `--dsw-alias-*`
5
+ * tokens — no colour literals anywhere in this file.
6
+ */
7
+ import type { ReactNode } from 'react';
8
+ /**
9
+ * A checklist mark: three rows with ticks and text rails — the same status
10
+ * vocabulary the board paints.
11
+ * @param size - square edge in px (the host passes its own tab-icon size).
12
+ */
13
+ export declare function BriefIcon(size: number): ReactNode;
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { TodoScope } from './TodoBoardTab';
4
+ export interface SummaryTabProps {
5
+ /** The DSH locale lookup, passed down from `apply`. */
6
+ t: (key: string) => string;
7
+ /** The client root context (the tab body's only way to reach services). */
8
+ ctx?: Context;
9
+ /** The session this tab is scoped to. */
10
+ scope?: TodoScope;
11
+ /** Whether the tab is the active one AND the panel is open. */
12
+ visible?: boolean;
13
+ }
14
+ /**
15
+ * The summary tab.
16
+ * @param props - translation, client context, session scope, visibility.
17
+ */
18
+ export declare function SummaryTab(props: SummaryTabProps): ReactNode;
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ /** The session scope better-sidebar hands a tab body. */
4
+ export interface TodoScope {
5
+ readonly sessionId?: string;
6
+ }
7
+ export interface TodoSectionProps {
8
+ /** The DSH locale lookup, passed down from `apply`. */
9
+ t: (key: string) => string;
10
+ /** The client root context (the tab body's only way to reach services). */
11
+ ctx?: Context;
12
+ /** The session this tab is scoped to. */
13
+ scope?: TodoScope;
14
+ }
15
+ /** Centred message block used by every non-list state. */
16
+ export declare function Notice(props: {
17
+ title: string;
18
+ detail?: string;
19
+ }): ReactNode;
20
+ /**
21
+ * The progress section. The subscription is unconditional (a plain listener,
22
+ * not IO); the three-state contract of the projection is preserved verbatim.
23
+ * @param props - translation, client context, session scope.
24
+ */
25
+ export declare function TodoSection(props: TodoSectionProps): ReactNode;
26
+ /**
27
+ * Backwards-compatible alias for the pre-summary export name. The component
28
+ * no longer owns the full-height shell — that moved to `SummaryTab` — so the
29
+ * alias exists only to keep import sites honest during the transition.
30
+ */
31
+ export declare const TodoBoardTab: typeof TodoSection;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The `todos` projection — reading and classifying it.
3
+ *
4
+ * The Host computes this projection (`dsh-tool-todo` registers the key) and
5
+ * pushes finished whole values; the client never folds it. This module is the
6
+ * read side of that contract and is deliberately PURE — no React, no context,
7
+ * no IO — so the mapping can be tested directly.
8
+ */
9
+ /** The projection's three-state lifecycle (`TodoItem` in `dsh-tool-todo`). */
10
+ export type TodoStatus = 'pending' | 'in_progress' | 'completed';
11
+ /** One entry of the `todos` projection. */
12
+ export interface TodoItem {
13
+ readonly content: string;
14
+ readonly status: TodoStatus;
15
+ }
16
+ /** Locale key for one status label. */
17
+ export declare function statusKey(status: TodoStatus): string;
18
+ /**
19
+ * Narrow a raw projection value into a todo list.
20
+ *
21
+ * Three outcomes are kept apart on purpose, mirroring the store's own contract:
22
+ * - `undefined` — the capability is ABSENT (no session, the host unit is
23
+ * unmounted, or no baseline/frame has carried the key yet);
24
+ * - `[]` — the projection is present and the list is empty (`null` on the wire);
25
+ * - a list — real entries.
26
+ *
27
+ * A malformed entry is dropped rather than thrown on: the projection is a
28
+ * trusted Host value, but a shape drift must not blank the whole board.
29
+ *
30
+ * @param value - the raw `unknown` snapshot of the `todos` face.
31
+ * @returns the validated list, or `undefined` when the capability is absent.
32
+ */
33
+ export declare function readTodos(value: unknown): readonly TodoItem[] | undefined;
34
+ /** Per-status counts over one list. */
35
+ export interface TodoCounts {
36
+ readonly total: number;
37
+ readonly completed: number;
38
+ readonly inProgress: number;
39
+ readonly pending: number;
40
+ }
41
+ /** Count a todo list by status. */
42
+ export declare function countTodos(todos: readonly TodoItem[]): TodoCounts;
43
+ /** How many entries are not finished — the tab badge. */
44
+ export declare function unfinishedCount(todos: readonly TodoItem[]): number;
45
+ /**
46
+ * The one-line progress summary, composed from parts so no interpolation
47
+ * machinery is needed (the locale binder is a plain key → string lookup).
48
+ * Mirrors the official dock's own label.
49
+ */
50
+ export declare function progressLine(counts: TodoCounts, t: (key: string) => string): string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Hiding the official todo dock.
3
+ *
4
+ * `conversation.input.dock` is a LIST slot, and its cells are keyed by each
5
+ * entry's `id`. The slot core keeps one live entry per cell — entries are
6
+ * sorted by `priority` ascending (then by `order`) and the first live entry of
7
+ * a cell is the one the outlet renders, so a SECOND registration with the same
8
+ * `id` at a LOWER priority shadows the first. The official todo entry
9
+ * (`conversation-todo-dock` in `dsh-client-ui-conversation`) registers
10
+ * `{ id: 'todo', order: 0 }` with the default priority 0, so
11
+ * `{ id: 'todo', priority: -1 }` wins the cell and the official panel never
12
+ * renders while this plugin is mounted.
13
+ *
14
+ * This is the same mechanism `dsh-input-traffic` uses on the sibling `queue`
15
+ * cell (`{ id: 'queue', priority: -1 }`), so it is a proven seam on this host
16
+ * generation, not a guess.
17
+ *
18
+ * Binding to a built-in id is a real coupling: if upstream renames the cell,
19
+ * the shadow silently stops winning and the official dock simply reappears
20
+ * (fail-open — the data is still shown, just twice). `slots.inject` is used
21
+ * rather than a bare `register` because the slot is declared by a built-in
22
+ * entry whose lifetime we do not control: `inject` waits for the declaration
23
+ * and re-installs the entry if the declaration is ever recreated.
24
+ *
25
+ * Renders nothing on purpose: the todo UI now lives in this plugin's sidebar
26
+ * tab. Return a summary chip here instead to keep a one-line entry point in the
27
+ * composer band.
28
+ */
29
+ import type { ReactNode } from 'react';
30
+ import type { Context } from '@deepseek-ai/cordis';
31
+ /** The built-in cell id of the official todo dock. */
32
+ export declare const TODO_CELL_ID = "todo";
33
+ /** Lower than the built-in's default 0 — the winner of the `todo` cell. */
34
+ export declare const TODO_SHADOW_PRIORITY = -1;
35
+ /** Structural view of the client slots service (avoids a second type graph). */
36
+ export interface SlotsLike {
37
+ inject(key: string, callback: () => (() => void) | undefined): () => void;
38
+ register(options: Record<string, unknown>, component: (props: unknown) => ReactNode): () => void;
39
+ }
40
+ /**
41
+ * Register the shadowing entry.
42
+ *
43
+ * The caller wraps this in `ctx.effect(...)` so the returned disposer rides the
44
+ * plugin fiber: unloading the plugin restores the official dock.
45
+ *
46
+ * @param ctx - the client root context.
47
+ * @returns the disposer, or undefined when the slots service is unreachable.
48
+ */
49
+ export declare function registerTodoDockShadow(ctx: Context): (() => void) | undefined;
@@ -0,0 +1,13 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { TodoItem } from './board';
3
+ /** The projection key registered by `dsh-tool-todo`. */
4
+ export declare const TODOS_KEY = "todos";
5
+ /**
6
+ * Subscribe to one session's todo list.
7
+ *
8
+ * @param ctx - the client root context.
9
+ * @param sessionId - the session whose projection is shown.
10
+ * @returns the list, `[]` for a present-but-empty projection, or `undefined`
11
+ * while the capability is absent.
12
+ */
13
+ export declare function useTodos(ctx: Context | undefined, sessionId: string | undefined): readonly TodoItem[] | undefined;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Opening a produced file's preview in the right Sidebar — the same channel
3
+ * the conversation flow uses for its produced-file chips and inline mentions
4
+ * (ui-chat `openFile` → `ctx.sidebarRight.openResource` with a
5
+ * `dsh-resource://file/…` address).
6
+ *
7
+ * The address grammar is ported verbatim from the harness's browser-safe
8
+ * `@deepseek-ai/dsh-util-workspace-path` (`file-address.ts` / `index.ts`),
9
+ * because the plugin does not compile against that package: a relative path,
10
+ * or an absolute path inside the session's workspace, is addressed under the
11
+ * session's scope (`dsh-resource://file/session/<id>/<path>`); an absolute
12
+ * path elsewhere keeps its absolute spelling in the same session's address.
13
+ * Which tab type claims the address is the Sidebar's decision, not ours.
14
+ *
15
+ * The `sidebarRight` service is consumed structurally and treated as OPTIONAL:
16
+ * where it is absent the section renders plain rows instead of links, the same
17
+ * graceful degradation as the deliverables projection itself.
18
+ */
19
+ import type { Context } from '@deepseek-ai/cordis';
20
+ /** Structural view of the right-Sidebar service this plugin needs. */
21
+ export interface SidebarRightLike {
22
+ openResource(url: string, options?: {
23
+ params?: {
24
+ line?: number;
25
+ };
26
+ }): void;
27
+ }
28
+ /**
29
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`)
30
+ * or Windows drive or UNC.
31
+ */
32
+ export declare function isAbsoluteWorkspacePath(path: string): boolean;
33
+ /**
34
+ * The address for a path as a caller holds it.
35
+ * @param sessionId - the Session the path is read in.
36
+ * @param cwd - that Session's workspace root, when known.
37
+ * @param path - absolute or workspace-relative path, in either separator spelling.
38
+ */
39
+ export declare function fileAddressFor(sessionId: string, cwd: string | undefined, path: string): string;
40
+ /**
41
+ * Build a sidebar file opener for one session, or `undefined` when the
42
+ * environment cannot open previews (no `sidebarRight` service, no session).
43
+ * The cwd is re-read per call inside the opener, so a workspace switch between
44
+ * renders is picked up on the next click.
45
+ *
46
+ * @param ctx - the client root context.
47
+ * @param sessionId - the session whose workspace resolves paths.
48
+ * @returns an opener that routes a produced path to the Sidebar preview.
49
+ */
50
+ export declare function sidebarFileOpener(ctx: Context | undefined, sessionId: string | undefined): ((path: string) => void) | undefined;
@@ -0,0 +1,23 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /**
3
+ * Tab type id. Package-prefixed so it cannot collide with a built-in type.
4
+ * Unchanged since the 0.1.0 board tab: the summary tab REPLACES the board tab
5
+ * in place, so an opened or pinned tab survives the upgrade.
6
+ */
7
+ export declare const TAB_ID = "dsh-brief-sidebar:board";
8
+ /**
9
+ * Position in the host's new-tab guide.
10
+ *
11
+ * Built-ins on this host generation sit at editor 10 / git 20 / subagent 30 /
12
+ * sidechat 35 / terminal 40 / browser 50. 15 places a per-session task board
13
+ * right after the editor, which is where a reader looks for session state.
14
+ */
15
+ export declare const TAB_ORDER = 15;
16
+ /** Services required before `apply` runs. */
17
+ export declare const inject: string[];
18
+ /**
19
+ * Browser-face apply.
20
+ *
21
+ * @param ctx - the client root context.
22
+ */
23
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Plugin-owned i18n dictionaries.
3
+ *
4
+ * Consumer plugins must NOT reach into better-sidebar's internal `t()` or its
5
+ * `betterSidebar` dictionary namespace, so this plugin registers its own
6
+ * namespace through the DSH `locale` service. `dsh-brief-sidebar` is outside the
7
+ * `LocaleNamespaceMap` merge table, which is exactly what the untyped
8
+ * `register(ns, dicts)` overload is for — but the dictionaries themselves stay
9
+ * strictly paired: `zh` is the key-set source of truth and `en` is typed as
10
+ * `Record<keyof typeof zh, string>`, so a missing or extra English key is a
11
+ * COMPILE error rather than a runtime fallback to the raw key.
12
+ *
13
+ * Every key referenced anywhere in `src/**` must exist in both dictionaries;
14
+ * `tests/purity.spec.ts` asserts that, so a typo in a `t('…')` call cannot
15
+ * ship as an untranslated literal.
16
+ */
17
+ /** The namespace owned by this plugin (own vocabulary, own lifecycle). */
18
+ export declare const NS = "dsh-brief-sidebar";
19
+ /** Simplified Chinese dictionary — the key-set source of truth. */
20
+ export declare const zh: {
21
+ 'tab.title': string;
22
+ 'tab.desc': string;
23
+ 'section.progress': string;
24
+ 'section.deliverables': string;
25
+ 'board.empty': string;
26
+ 'board.emptyHint': string;
27
+ 'board.unavailable': string;
28
+ 'board.unavailableHint': string;
29
+ 'status.pending': string;
30
+ 'status.inProgress': string;
31
+ 'status.completed': string;
32
+ 'deliverables.empty': string;
33
+ 'deliverables.emptyHint': string;
34
+ 'deliverables.sessionTotal': string;
35
+ 'deliverable.codeplanTag': string;
36
+ };
37
+ /** The key union: what a `t('…')` call may name inside this namespace. */
38
+ export type BriefKey = keyof typeof zh;
39
+ /** English dictionary, checked complete against the zh key set. */
40
+ export declare const en: Record<BriefKey, string>;
41
+ /**
42
+ * Both locales in the shape the locale service consumes. Registered in ONE
43
+ * call: the registry rejects a duplicate `(namespace, locale)` pair and the
44
+ * per-locale form would leave a half-registered namespace if the second call
45
+ * threw.
46
+ */
47
+ export declare const dictionaries: Record<string, Record<string, string>>;
48
+ /**
49
+ * Expand the one `{count}` placeholder the deliverables totals line uses. The
50
+ * locale binder is a plain key → string lookup (no interpolation machinery),
51
+ * mirroring how `progressLine` composes its summary from parts.
52
+ */
53
+ export declare function expandCount(template: string, count: number): string;
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ export interface DeliverablesSectionProps {
4
+ /** The DSH locale lookup, passed down from `apply`. */
5
+ t: (key: string) => string;
6
+ /** The client root context (the tab body's only way to reach services). */
7
+ ctx?: Context;
8
+ /** The session this tab is scoped to. */
9
+ scope?: {
10
+ readonly sessionId?: string;
11
+ };
12
+ }
13
+ /**
14
+ * The deliverables section. The subscription is unconditional (a plain
15
+ * listener, not IO).
16
+ * @param props - translation, client context, session scope.
17
+ */
18
+ export declare function DeliverablesSection(props: DeliverablesSectionProps): ReactNode;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The `dshSummaryDeliverables` projection — reading and classifying it.
3
+ *
4
+ * The Host computes this projection (this plugin's own node half registers the
5
+ * unit) and pushes finished whole values; the client never folds it. This
6
+ * module is the read side of that contract and is deliberately PURE — no
7
+ * React, no context, no IO — so the narrowing and the codeplan classification
8
+ * can be tested directly.
9
+ */
10
+ import type { DeliverablesView } from '../../projection/keys';
11
+ /**
12
+ * Narrow a raw projection value into a deliverables view.
13
+ *
14
+ * Two outcomes are kept apart on purpose, mirroring the store's contract:
15
+ * - `undefined` — the capability is ABSENT (the host half never registered —
16
+ * e.g. an older composition — or no frame carried the key yet);
17
+ * - a view — the projection is live. A view with `latest: null` and
18
+ * `sessionTotal: 0` IS the empty state; the fold starts publishing as soon
19
+ * as the unit registers, so "present but empty" is a real value here, not a
20
+ * sentinel.
21
+ *
22
+ * @param value - the raw `unknown` snapshot of the deliverables face.
23
+ * @returns the validated view, or `undefined` when the capability is absent.
24
+ */
25
+ export declare function readDeliverables(value: unknown): DeliverablesView | undefined;
26
+ /** Whether a produced path is a codeplan artifact. */
27
+ export declare function isCodeplanPath(path: string): boolean;
28
+ /**
29
+ * Split a codeplan path into its task name and file name.
30
+ *
31
+ * @param path - a produced path; need not actually be a codeplan artifact.
32
+ * @returns task and file names, or `null` when the path is not under the
33
+ * codeplan plans directory (or names no file beneath the task folder).
34
+ */
35
+ export declare function splitCodeplanPath(path: string): {
36
+ task: string;
37
+ file: string;
38
+ } | null;
39
+ /** The final path segment, under either separator. */
40
+ export declare function basename(path: string): string;
@@ -0,0 +1,54 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /** Structural view of a projection face (`ObservableSnapshot<unknown>`). */
3
+ export interface ProjectionFace {
4
+ getSnapshot(): unknown;
5
+ subscribe(listener: () => void): () => void;
6
+ }
7
+ /** Structural view of one session binding's projection store. */
8
+ export interface ProjectionsLike {
9
+ faceOf(key: string): ProjectionFace;
10
+ }
11
+ /** Structural view of the client Session Controller face this plugin uses. */
12
+ export interface SessionsLike {
13
+ binding(id: string): {
14
+ readonly session: {
15
+ readonly projections: ProjectionsLike;
16
+ };
17
+ } | undefined;
18
+ }
19
+ /**
20
+ * Resolve one projection face for one session.
21
+ *
22
+ * `ctx.get('sessions')` is the safe accessor (a direct `ctx.sessions` read can
23
+ * throw on a context that does not carry the service yet).
24
+ *
25
+ * @param ctx - the client root context handed to the tab body.
26
+ * @param sessionId - the tab's session scope; undefined means "no session".
27
+ * @param key - the projection key to resolve.
28
+ * @returns the projection face, or {@link ABSENT} when unreachable.
29
+ */
30
+ export declare function resolveFace(ctx: Context | undefined, sessionId: string | undefined, key: string): ProjectionFace;
31
+ /**
32
+ * Read one frame of a projection without subscribing.
33
+ *
34
+ * The one-shot sibling of {@link useProjectionValue}, for consumers that are
35
+ * not React components — the tab badge is a plain function called on every
36
+ * tab-bar render, so it must neither hold a subscription nor allocate one.
37
+ *
38
+ * @param ctx - the client root context.
39
+ * @param sessionId - the session whose projection is read.
40
+ * @param key - the projection key to read.
41
+ * @returns the raw snapshot: `undefined` while the capability is absent; the
42
+ * semantics of any other value belong to the projection's own contract.
43
+ */
44
+ export declare function projectionSnapshot(ctx: Context | undefined, sessionId: string | undefined, key: string): unknown;
45
+ /**
46
+ * Subscribe to one session projection by key.
47
+ *
48
+ * @param ctx - the client root context.
49
+ * @param sessionId - the session whose projection is shown.
50
+ * @param key - the projection key to subscribe to.
51
+ * @returns the raw snapshot value, identity-stable across frames the host did
52
+ * not change. Narrowing is the caller's job.
53
+ */
54
+ export declare function useProjectionValue(ctx: Context | undefined, sessionId: string | undefined, key: string): unknown;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Node half of the brief sidebar plugin.
3
+ *
4
+ * The Cordis loader needs an entry point for the profile row; the browser half
5
+ * (`./client`, served by `dsh-client-modules`) renders the summary tab.
6
+ *
7
+ * Since the summary upgrade, this half owns ONE behaviour: contributing the
8
+ * `dshSummaryDeliverables` projection unit (see `./projection/register`), which
9
+ * folds successful mutation calls into a live produced-file feed. The
10
+ * contribution is optional — `registerDeliverablesProjection` waits for the
11
+ * `sessionProjections` service via `ctx.inject`, so a composition without the
12
+ * registry still loads this profile row and simply hides the deliverables
13
+ * section client-side.
14
+ *
15
+ * In particular this half still never touches the filesystem at load time: a
16
+ * profile row is instantiated on the boot path, and a synchronous read there
17
+ * would delay the host's startup. The fold is event-driven and stores paths
18
+ * only.
19
+ */
20
+ import type { Context } from '@deepseek-ai/cordis';
21
+ /** Profile row identity. */
22
+ export declare const name = "dsh-brief-sidebar";
23
+ /**
24
+ * Node-face apply.
25
+ * @param ctx - the host context the loader hands this profile row.
26
+ */
27
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Host-side fold of produced-file facts — the projection unit state for
3
+ * `dshSummaryDeliverables`.
4
+ *
5
+ * The vocabulary is the one the harness's own deliverables experience uses
6
+ * (`ui-deliverables/turn-deliverables.ts`): the arguments of successful
7
+ * first-party mutation calls (`write`, `edit`, and the mutating
8
+ * `str_replace_editor` commands), never presentation data or prose. Reads,
9
+ * deletes, unsupported tools, malformed calls, and failed results contribute
10
+ * nothing; a file written and then edited in the same turn is one entry, in
11
+ * first-seen order.
12
+ *
13
+ * This module is deliberately PURE — no cordis, no zod, no IO — so the fold
14
+ * can be tested directly against hand-written event fixtures. The projection
15
+ * registry drives it once per committed `session/event`, which is what makes
16
+ * the produced list update mid-turn, the gap the official turn-tail row has.
17
+ */
18
+ /** One bucket: the paths produced so far in one turn, first-seen order. */
19
+ export interface DeliverablesTurnBucket {
20
+ readonly turn: number;
21
+ readonly paths: readonly string[];
22
+ }
23
+ /** Fold state. Plain JSON, so the projection cache can persist it. */
24
+ export interface DeliverablesState {
25
+ /** Turn number of the open (or latest seen) turn; 0 before the first turn. */
26
+ readonly turn: number;
27
+ /** Open turn's `callId → mutation path` map (`null` = not a mutation call). */
28
+ readonly calls: Readonly<Record<string, {
29
+ readonly turn: number;
30
+ readonly path: string | null;
31
+ }>>;
32
+ /** Paths accumulated in the latest turn so far (`null` before the first turn). */
33
+ readonly latest: DeliverablesTurnBucket | null;
34
+ /** Every distinct produced path, most-recent-first, capped (see CAPS). */
35
+ readonly sessionPaths: readonly string[];
36
+ /** Distinct produced paths over the whole session, uncapped. */
37
+ readonly sessionTotal: number;
38
+ }
39
+ /**
40
+ * Wire-view capacity bounds. The projection rides every event frame, so the
41
+ * lists are capped; the uncapped `sessionTotal` keeps the summary truthful
42
+ * beyond the cap.
43
+ */
44
+ export declare const CAPS: {
45
+ /** Latest-turn paths kept in the wire view. */
46
+ readonly latest: 50;
47
+ /** Session-wide paths kept in the wire view (most recent win). */
48
+ readonly session: 100;
49
+ };
50
+ /** The fold's initial state: nothing has happened yet. */
51
+ export declare function initDeliverablesState(): DeliverablesState;
52
+ /**
53
+ * Structural view of one committed session event. The registry hands the
54
+ * host's discriminated union; the fold only needs `type` and `data`, and the
55
+ * tool-event shapes mirror `dsh-session` types: `tool/call` carries
56
+ * `{ turn, step, callId, name, arguments }`, `tool/result` carries
57
+ * `{ turn, step, message: { content: [{ isError? }], source: { callId } } }`,
58
+ * `turn/start` carries `{ turn }`.
59
+ */
60
+ export interface FoldEvent {
61
+ readonly type: string;
62
+ readonly data: unknown;
63
+ }
64
+ /**
65
+ * The next state after one committed event. Events the fold does not own
66
+ * return the SAME reference — an unchanged reference produces zero downstream
67
+ * work in the registry's `Object.is` drive.
68
+ *
69
+ * @param state - the state covering all prior events.
70
+ * @param event - the next committed session event.
71
+ * @returns the next state (same reference when nothing the fold tracks moved).
72
+ */
73
+ export declare function applyDeliverablesEvent(state: DeliverablesState, event: FoldEvent): DeliverablesState;
74
+ /**
75
+ * Extract the path from a supported first-party mutation call.
76
+ *
77
+ * Ported from the harness deliverables fold so both carriers agree on what a
78
+ * "produced file" is. Session `tool/call` events are root calls; PTC dispatch
79
+ * children do not enter this fold independently.
80
+ *
81
+ * @param name - wire tool name.
82
+ * @param argsRaw - model-produced JSON arguments.
83
+ * @returns the mutation path, or null when the call is not a supported mutation.
84
+ */
85
+ export declare function mutationPath(name: string, argsRaw: string): string | null;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The projection key this plugin contributes to the session-projection
3
+ * registry, and its wire view.
4
+ *
5
+ * The key is namespaced with the plugin name on purpose: the registry refuses
6
+ * to share a key across differing `stateVersion`s, so a collision with a
7
+ * future host-side deliverables unit would be a hard load error, not a silent
8
+ * shadow. The view type is declared structurally (the plugin has no compile
9
+ * dependency on `@deepseek-ai/dsh-session-projection`); the registry accepts
10
+ * any string key at runtime.
11
+ */
12
+ /** The projection key registered by this plugin's host half. */
13
+ export declare const DELIVERABLES_KEY = "dshSummaryDeliverables";
14
+ /** Client view of the produced-file fold (JSON-safe). */
15
+ export interface DeliverablesView {
16
+ /** Paths produced in the latest turn, first-seen order (capped). */
17
+ readonly latest: {
18
+ readonly turn: number;
19
+ readonly paths: readonly string[];
20
+ } | null;
21
+ /** Distinct produced paths session-wide, most-recent-first (capped). */
22
+ readonly sessionPaths: readonly string[];
23
+ /** Distinct produced paths session-wide, uncapped — the truthful total. */
24
+ readonly sessionTotal: number;
25
+ }
@@ -0,0 +1,9 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /**
3
+ * Register the deliverables projection as an optional contribution.
4
+ *
5
+ * @param ctx - the host context the plugin's `apply` receives.
6
+ * @returns nothing; the registration's disposer rides the injected fiber, so
7
+ * unloading the plugin removes the key (clients then read capability absence).
8
+ */
9
+ export declare function registerDeliverablesProjection(ctx: Context): void;