dsh-rewind-plugin 0.20.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/SECURITY.md +80 -183
- package/lib/client.js +417 -387
- package/lib/index.js +223 -192
- package/lib/types/client/action.d.ts +40 -0
- package/lib/types/client/hidden.d.ts +2 -2
- package/lib/types/client/index.d.ts +14 -11
- package/lib/types/client/locales.d.ts +17 -21
- package/lib/types/client/log.d.ts +1 -1
- package/lib/types/client/mount.d.ts +79 -0
- package/lib/types/client/pending-mount.d.ts +36 -0
- package/lib/types/client/pending.d.ts +43 -57
- package/lib/types/client/popover.d.ts +19 -19
- package/lib/types/client/retract.d.ts +32 -0
- package/lib/types/client/rewind-flow.d.ts +86 -0
- package/lib/types/client/styles.d.ts +5 -14
- package/lib/types/snapshot-format.d.ts +479 -0
- package/lib/types/snapshot-fs.d.ts +85 -0
- package/lib/types/snapshot.d.ts +5 -373
- package/package.json +1 -1
- package/lib/types/client/portals.d.ts +0 -301
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ↶ action button: the one message-action control this plugin renders.
|
|
3
|
+
*
|
|
4
|
+
* The A2 mount injects it as a React child of a durable row's actions row; the
|
|
5
|
+
* pending layer (`pending-mount.tsx`) portals the same button, with the retract
|
|
6
|
+
* copy and a marker, into an unclaimed steering bubble.
|
|
7
|
+
*
|
|
8
|
+
* The action only records intent: a click opens the popover variant, which
|
|
9
|
+
* confirms and runs the host command / queue mutation (see `popover.ts`,
|
|
10
|
+
* `rewind-flow.ts`, `retract.ts`); the host's admission stays the authority.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-rewind/client/action
|
|
13
|
+
*/
|
|
14
|
+
import type { ReactNode } from 'react';
|
|
15
|
+
import type { RewindKey } from './locales.ts';
|
|
16
|
+
import type { RowAction, RowActionRenderer } from './mount.tsx';
|
|
17
|
+
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
18
|
+
/** The shared button's inputs. */
|
|
19
|
+
export interface RewindActionButtonProps {
|
|
20
|
+
readonly t: Translate;
|
|
21
|
+
/** Dictionary keys of the tooltip and the aria label. */
|
|
22
|
+
readonly titleKey: RewindKey;
|
|
23
|
+
readonly ariaKey: RewindKey;
|
|
24
|
+
/** Mark the pre-admission variant: the pending layer uses it to skip its own button. */
|
|
25
|
+
readonly pending?: boolean;
|
|
26
|
+
/** Open the action's popover variant, anchored at the button. */
|
|
27
|
+
readonly onOpen: (anchor: HTMLElement) => void;
|
|
28
|
+
}
|
|
29
|
+
/** The row's action chrome, matching the official Copy/branch buttons. */
|
|
30
|
+
export declare function RewindActionButton({ t, titleKey, ariaKey, pending, onOpen }: RewindActionButtonProps): ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Build the A2 mount's action renderer: one button per eligible row, nothing for
|
|
33
|
+
* the rest.
|
|
34
|
+
* @param t - the plugin's own translate function (the action's copy).
|
|
35
|
+
* @param eligible - render-time gate: whether a row's session may offer a rewind at all.
|
|
36
|
+
* @param open - opens the mode popover for one row, anchored at the button.
|
|
37
|
+
* @returns the renderer the mount calls for every user/steering row.
|
|
38
|
+
*/
|
|
39
|
+
export declare function rewindActionRenderer(t: Translate, eligible: (sessionId: string) => boolean, open: (row: RowAction, anchor: HTMLElement) => void): RowActionRenderer;
|
|
40
|
+
export {};
|
|
@@ -83,7 +83,7 @@ export declare function chatSnapshotOf(chatView: {
|
|
|
83
83
|
* filling the composer after a withdraw. Accepts BOTH `user` and `steering`
|
|
84
84
|
* nodes: a plan-mode (`/plan <text>`) input is delivered through the agent
|
|
85
85
|
* inbox next-step and claimed, so it renders as `steering`, and its text must
|
|
86
|
-
* still return to the composer (`
|
|
86
|
+
* still return to the composer (`rewind-flow.ts` `runRewindAndFill`). State absent
|
|
87
87
|
* → undefined; a message with no text blocks → ''. Same text-blocks join the
|
|
88
88
|
* candidate side uses.
|
|
89
89
|
*/
|
|
@@ -125,7 +125,7 @@ export declare function imageRefsOf(content: readonly ImageBlockLike[] | undefin
|
|
|
125
125
|
/**
|
|
126
126
|
* The durable image references of the human message at `seq` in the chat
|
|
127
127
|
* snapshot, in message order. The composer-refill counterpart of
|
|
128
|
-
* `messageTextAt` (see `
|
|
128
|
+
* `messageTextAt` (see `rewind-flow.ts` `runRewindAndFill`): rewinding an
|
|
129
129
|
* image-bearing message restores its pictures into the composer so the
|
|
130
130
|
* withdraw does not silently drop them.
|
|
131
131
|
*
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-rewind client half: the `/rewind` command decoration, the locale
|
|
3
|
-
* registration,
|
|
4
|
-
* per-message
|
|
3
|
+
* registration, the mode popover it opens, the visibility layer, and the
|
|
4
|
+
* per-message rewind action.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* A durable message's action lives in the Chat row's own actions row: `mount.tsx`
|
|
7
|
+
* takes over the official user/steering renderer identity and injects one more
|
|
8
|
+
* child into that row. Unclaimed (pending) steering input has no such seat, so
|
|
9
|
+
* `pending-mount.tsx` attaches the retract action through the only DOM path the
|
|
10
|
+
* plugin keeps. The
|
|
11
|
+
* command decoration (`ctx.commandUi.decorate`) makes a bare `/rewind` (or its
|
|
12
|
+
* alias `/undo`) open the harness's own popupSelect instead of executing; the
|
|
13
|
+
* parameterized `@<seq>` form stays the execution channel, while the popover's
|
|
14
|
+
* impact and candidate reads go over the host's exact `/api` routes. The
|
|
15
|
+
* executed rewind and its composer refill live in `rewind-flow.ts`.
|
|
13
16
|
*
|
|
14
17
|
* @module dsh-rewind/client
|
|
15
18
|
*/
|
|
@@ -40,8 +43,8 @@ export interface ClientContext {
|
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
|
-
* Client plugin body: command decoration + parameterized guard + locale +
|
|
44
|
-
*
|
|
46
|
+
* Client plugin body: command decoration + parameterized guard + locale +
|
|
47
|
+
* visibility + the settings card.
|
|
45
48
|
* @param ctx - client root context carrying `slots`, `sessions`, `locale` and `commandUi`.
|
|
46
49
|
*/
|
|
47
50
|
export declare function apply(ctx: ClientContext): void;
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/** `rewind` namespace dictionaries for the client plugin. */
|
|
2
2
|
/** English dictionary — the canonical key set (the harness fallback language). */
|
|
3
3
|
export declare const en: {
|
|
4
|
-
'button.aria': string;
|
|
5
4
|
'button.title': string;
|
|
5
|
+
'button.aria': string;
|
|
6
6
|
'button.retract.aria': string;
|
|
7
|
-
'button.retract.title': string;
|
|
8
7
|
'popover.title': string;
|
|
9
8
|
'popover.noText': string;
|
|
10
|
-
'popover.retract.title': string;
|
|
11
|
-
'popover.retract.target': string;
|
|
12
|
-
'popover.retract.hint': string;
|
|
13
|
-
'popover.retract.confirm': string;
|
|
14
9
|
'popover.chat': string;
|
|
15
10
|
'popover.chat.hint': string;
|
|
16
11
|
'popover.both': string;
|
|
@@ -18,7 +13,6 @@ export declare const en: {
|
|
|
18
13
|
'popover.checking': string;
|
|
19
14
|
'popover.noChanges': string;
|
|
20
15
|
'popover.backupDisabled': string;
|
|
21
|
-
'popover.cancel': string;
|
|
22
16
|
'popover.impact.loading': string;
|
|
23
17
|
'popover.impact.failed': string;
|
|
24
18
|
'popover.impact.none': string;
|
|
@@ -26,6 +20,10 @@ export declare const en: {
|
|
|
26
20
|
'popover.impact.delete': string;
|
|
27
21
|
'popover.confirm': string;
|
|
28
22
|
'popover.back': string;
|
|
23
|
+
'popover.retract.title': string;
|
|
24
|
+
'popover.retract.target': string;
|
|
25
|
+
'popover.retract.hint': string;
|
|
26
|
+
'popover.cancel': string;
|
|
29
27
|
'backup.title': string;
|
|
30
28
|
'backup.on': string;
|
|
31
29
|
'backup.off': string;
|
|
@@ -72,16 +70,11 @@ export declare const pt: Record<RewindKey, string>;
|
|
|
72
70
|
*/
|
|
73
71
|
export declare const CLIENT_DICTS: {
|
|
74
72
|
en: {
|
|
75
|
-
'button.aria': string;
|
|
76
73
|
'button.title': string;
|
|
74
|
+
'button.aria': string;
|
|
77
75
|
'button.retract.aria': string;
|
|
78
|
-
'button.retract.title': string;
|
|
79
76
|
'popover.title': string;
|
|
80
77
|
'popover.noText': string;
|
|
81
|
-
'popover.retract.title': string;
|
|
82
|
-
'popover.retract.target': string;
|
|
83
|
-
'popover.retract.hint': string;
|
|
84
|
-
'popover.retract.confirm': string;
|
|
85
78
|
'popover.chat': string;
|
|
86
79
|
'popover.chat.hint': string;
|
|
87
80
|
'popover.both': string;
|
|
@@ -89,7 +82,6 @@ export declare const CLIENT_DICTS: {
|
|
|
89
82
|
'popover.checking': string;
|
|
90
83
|
'popover.noChanges': string;
|
|
91
84
|
'popover.backupDisabled': string;
|
|
92
|
-
'popover.cancel': string;
|
|
93
85
|
'popover.impact.loading': string;
|
|
94
86
|
'popover.impact.failed': string;
|
|
95
87
|
'popover.impact.none': string;
|
|
@@ -97,6 +89,10 @@ export declare const CLIENT_DICTS: {
|
|
|
97
89
|
'popover.impact.delete': string;
|
|
98
90
|
'popover.confirm': string;
|
|
99
91
|
'popover.back': string;
|
|
92
|
+
'popover.retract.title': string;
|
|
93
|
+
'popover.retract.target': string;
|
|
94
|
+
'popover.retract.hint': string;
|
|
95
|
+
'popover.cancel': string;
|
|
100
96
|
'backup.title': string;
|
|
101
97
|
'backup.on': string;
|
|
102
98
|
'backup.off': string;
|
|
@@ -114,11 +110,11 @@ export declare const CLIENT_DICTS: {
|
|
|
114
110
|
'cleanup.readonly': string;
|
|
115
111
|
'cleanup.unavailable': string;
|
|
116
112
|
};
|
|
117
|
-
zh: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
118
|
-
ja: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
119
|
-
es: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
120
|
-
fr: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
121
|
-
de: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
122
|
-
ru: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
123
|
-
pt: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.
|
|
113
|
+
zh: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
114
|
+
ja: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
115
|
+
es: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
116
|
+
fr: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
117
|
+
de: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
118
|
+
ru: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
119
|
+
pt: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
|
|
124
120
|
};
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Values accepted (empty/unset = off): `dsh-rewind*` — every namespace;
|
|
13
13
|
* `dsh-rewind:refill` — one subsystem (exact match);
|
|
14
|
-
* `dsh-rewind:refill,dsh-rewind:
|
|
14
|
+
* `dsh-rewind:refill,dsh-rewind:visibility` — several (comma-separated).
|
|
15
15
|
*
|
|
16
16
|
* @module dsh-rewind/client/log
|
|
17
17
|
*/
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2 element-tree mount: one extra action inside the official user/steering
|
|
3
|
+
* message rows.
|
|
4
|
+
*
|
|
5
|
+
* The actions row is built by the official renderer, so the action can only be
|
|
6
|
+
* added while that row is constructed. The mount takes over the slot entry's
|
|
7
|
+
* component identity, calls the official renderer with the props the slot
|
|
8
|
+
* composed for it, and replaces the row element's `actions` constructor: the
|
|
9
|
+
* official constructor runs first, and the actions row it returns receives the
|
|
10
|
+
* extra action through its `extraActions` prop.
|
|
11
|
+
*
|
|
12
|
+
* Both element types (the row and the actions row) are preserved, so React
|
|
13
|
+
* reuses their fibers, and every prop this module does not touch is passed
|
|
14
|
+
* through unchanged. The action becomes a child of the actions row, so it
|
|
15
|
+
* mounts, unmounts and inherits the row's visibility with the Copy button
|
|
16
|
+
* instead of chasing the DOM.
|
|
17
|
+
*
|
|
18
|
+
* Any shape mismatch returns the official element as-is: the worst outcome is a
|
|
19
|
+
* missing action, never a broken row.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-rewind/client/mount
|
|
22
|
+
*/
|
|
23
|
+
import { type ReactNode } from 'react';
|
|
24
|
+
/** One rendered row the action is offered for. */
|
|
25
|
+
export interface RowAction {
|
|
26
|
+
/** Session rendering the row. */
|
|
27
|
+
readonly sessionId: string;
|
|
28
|
+
/** Durable log seq of the message. */
|
|
29
|
+
readonly seq: number;
|
|
30
|
+
/** Message time, epoch ms. */
|
|
31
|
+
readonly time: number;
|
|
32
|
+
/** The row's joined text — the Copy target, known only when the actions row is built. */
|
|
33
|
+
readonly text: string;
|
|
34
|
+
}
|
|
35
|
+
/** Build the action element for one row; `null` leaves the actions row untouched. */
|
|
36
|
+
export type RowActionRenderer = (row: RowAction) => ReactNode;
|
|
37
|
+
/** A registered slot entry, as the live ledger exposes it. */
|
|
38
|
+
interface LedgerEntry {
|
|
39
|
+
/** The registered component identity; replacing it replaces what the row renders. */
|
|
40
|
+
component: unknown;
|
|
41
|
+
readonly options: {
|
|
42
|
+
readonly key?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** The live slot-ledger surface the mount reads. */
|
|
46
|
+
export interface SlotLedger {
|
|
47
|
+
/** Entries of one slot key, in registration order. */
|
|
48
|
+
entries?(key: string): readonly LedgerEntry[];
|
|
49
|
+
/** Registration changes of one slot key (microtask-batched). */
|
|
50
|
+
subscribe?(key: string, fn: () => void): () => void;
|
|
51
|
+
/** Registration path, used only for the version tick. */
|
|
52
|
+
register?(options: {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly key?: string;
|
|
55
|
+
}, component: () => null): () => void;
|
|
56
|
+
}
|
|
57
|
+
/** Mount inputs. */
|
|
58
|
+
export interface RowActionMount {
|
|
59
|
+
readonly slots: SlotLedger;
|
|
60
|
+
readonly render: RowActionRenderer;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Take over the user/steering row components so every rendered row's actions row
|
|
64
|
+
* carries `render`'s element.
|
|
65
|
+
*
|
|
66
|
+
* Ledger methods are always called on `slots` itself: the live service is a
|
|
67
|
+
* proxy that re-binds the caller context per call, so a method detached into a
|
|
68
|
+
* local would lose its `this`.
|
|
69
|
+
*
|
|
70
|
+
* Wrapping runs synchronously with the registration (and at install for entries
|
|
71
|
+
* already registered), so a row's first render already sees the new identity.
|
|
72
|
+
* Outlets re-read an identity only on a registration change, so an already
|
|
73
|
+
* rendered row is nudged once (see `tick`).
|
|
74
|
+
*
|
|
75
|
+
* @param mount - ledger surface and action renderer.
|
|
76
|
+
* @returns teardown restoring the taken-over component identities.
|
|
77
|
+
*/
|
|
78
|
+
export declare function installRowAction({ slots, render }: RowActionMount): () => void;
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pending-steering retract layer: the pre-admission counterpart of the A2
|
|
3
|
+
* action, attached through the DOM.
|
|
4
|
+
*
|
|
5
|
+
* An unclaimed steering bubble is not a slot entry — `ChatFlow` constructs it
|
|
6
|
+
* from the owner's `pendingInputs` — so there is no component identity to take
|
|
7
|
+
* over. While the session's inbox holds user steering rows this layer finds their
|
|
8
|
+
* bubbles inside the session's own content region and portals one retract button
|
|
9
|
+
* into each matched actions row; when the inbox holds none it removes the
|
|
10
|
+
* buttons and disconnects the observer. Outside that window there is no observer
|
|
11
|
+
* and no DOM query — only the inbox subscription, which fires on inbox changes.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-rewind/client/pending-mount
|
|
14
|
+
*/
|
|
15
|
+
import { type ReactNode } from 'react';
|
|
16
|
+
import type { SessionFace } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
17
|
+
import type { MessageImageRef } from './hidden.ts';
|
|
18
|
+
import type { RewindKey } from './locales.ts';
|
|
19
|
+
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
20
|
+
/** The capability inputs the bridge closes over (all identity-stable). */
|
|
21
|
+
export interface PendingBridgeDeps {
|
|
22
|
+
sessionOf(sessionId: string): SessionFace | undefined;
|
|
23
|
+
setComposerText(sessionId: string, text: string): boolean;
|
|
24
|
+
restoreImages?(sessionId: string, images: readonly MessageImageRef[]): Promise<void>;
|
|
25
|
+
t: Translate;
|
|
26
|
+
subscribeLocale(cb: () => void): () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the session-scoped slot entry that runs the pending layer. It renders
|
|
30
|
+
* portals only — never header UI.
|
|
31
|
+
* @param deps - session resolution, composer write, image restore, copy and locale.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createPendingBridge(deps: PendingBridgeDeps): (props: {
|
|
34
|
+
readonly sessionId: string;
|
|
35
|
+
}) => ReactNode;
|
|
36
|
+
export {};
|
|
@@ -1,30 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Pending steering input: the agent's unclaimed `next-step` inbox rows and the
|
|
3
|
+
* contract that pairs them with the rendered pre-admission bubbles.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* rendered attachment block contributes text no inbox row reproduces.
|
|
11
|
-
*
|
|
12
|
-
* The compared text excludes the bubble's actions container and its
|
|
13
|
-
* message-attachment block (see `bubbleTextOf` in portals.tsx).
|
|
14
|
-
*
|
|
15
|
-
* The browser half lives in `portals.tsx`; this module stays DOM-free so the
|
|
16
|
-
* matching contract is unit-testable in a plain node environment.
|
|
5
|
+
* The visible bubble is the harness's own projection of the same inbox row, so
|
|
6
|
+
* the pending retract layer (see `pending-mount.tsx`) has no React seat to take
|
|
7
|
+
* over; it matches rendered rows to inbox rows instead. Matching is index-primary
|
|
8
|
+
* (both sides follow host FIFO order) and verified per row, so one bad row only
|
|
9
|
+
* loses its own button.
|
|
17
10
|
*
|
|
18
11
|
* @module dsh-rewind/client/pending
|
|
19
12
|
*/
|
|
13
|
+
import type { SessionFace } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
20
14
|
import { type ImageBlockLike, type MessageImageRef } from './hidden.ts';
|
|
21
|
-
/** One rendered pending-steering bubble row (only the fields matching reads). */
|
|
22
|
-
export interface PendingRow {
|
|
23
|
-
/** The bubble's message text, excluding the actions container (see module doc). */
|
|
24
|
-
readonly text: string;
|
|
25
|
-
/** Message-attachment blocks the row renders; compared against the item's count. */
|
|
26
|
-
readonly attachments: number;
|
|
27
|
-
}
|
|
28
15
|
/** One wire content block of an inbox row. */
|
|
29
16
|
export interface InboxBlockLike extends ImageBlockLike {
|
|
30
17
|
/** Wire block type (`text` | `image` | `file` | …). */
|
|
@@ -32,78 +19,77 @@ export interface InboxBlockLike extends ImageBlockLike {
|
|
|
32
19
|
/** The block's text, on a `text` block. */
|
|
33
20
|
readonly text?: string;
|
|
34
21
|
}
|
|
35
|
-
/** One pending-inbox row (the
|
|
22
|
+
/** One pending-inbox row (the `inbox` projection element). */
|
|
36
23
|
export interface InboxMessageLike {
|
|
37
24
|
/** Agent-owned inbox occurrence identity; the harness brands it as `MessageId`. */
|
|
38
25
|
readonly id: string;
|
|
39
26
|
/** Wire content blocks, in prompt order. */
|
|
40
27
|
readonly content: readonly InboxBlockLike[];
|
|
41
|
-
/** Message origin; only `kind: 'user'` rows are retractable
|
|
28
|
+
/** Message origin; only `kind: 'user'` rows are retractable. */
|
|
42
29
|
readonly source?: {
|
|
43
30
|
readonly kind?: string;
|
|
44
31
|
} | undefined;
|
|
45
32
|
}
|
|
46
|
-
/**
|
|
47
|
-
* The alpha.2 `inbox` projection value: the agent's pending input folded per
|
|
48
|
-
* target order. Replaces the removed `SessionSnapshot.queue` mirror.
|
|
49
|
-
*/
|
|
33
|
+
/** The `inbox` projection value: the agent's pending input folded per target. */
|
|
50
34
|
export interface InboxLike {
|
|
51
35
|
/** Messages claimed at the next turn boundary. */
|
|
52
36
|
readonly 'next-turn': readonly InboxMessageLike[];
|
|
53
37
|
/** Messages injected at the next step boundary (user or plugin/command). */
|
|
54
38
|
readonly 'next-step': readonly InboxMessageLike[];
|
|
55
39
|
}
|
|
40
|
+
/** One rendered pending-steering bubble row (only the fields matching reads). */
|
|
41
|
+
export interface PendingRow {
|
|
42
|
+
/** The bubble's message text, excluding the actions container. */
|
|
43
|
+
readonly text: string;
|
|
44
|
+
/** Message-attachment blocks the row renders; compared against the item's count. */
|
|
45
|
+
readonly attachments: number;
|
|
46
|
+
}
|
|
56
47
|
/** One steering occurrence derived from the session's inbox projection. */
|
|
57
48
|
export interface PendingSteeringItem {
|
|
58
49
|
readonly id: string;
|
|
59
50
|
/** Complete editable text; null when the message carries no text block at all. */
|
|
60
51
|
readonly text: string | null;
|
|
61
|
-
/** Non-text blocks
|
|
52
|
+
/** Non-text blocks the message carries, in prompt order. */
|
|
62
53
|
readonly attachments: number;
|
|
63
54
|
/** Image references the message carries, in prompt order. */
|
|
64
55
|
readonly images: readonly MessageImageRef[];
|
|
65
|
-
/** Space-collapsed preview with image/file blocks excluded (the harness QueueDock rule). */
|
|
66
|
-
readonly preview: string;
|
|
67
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The session's `inbox` projection value, or undefined before the fold state
|
|
59
|
+
* exists (a session with no agent loop has no `inbox` key). Never throws: an
|
|
60
|
+
* unregistered projection means "no pending rows".
|
|
61
|
+
* @param session - the session face to read.
|
|
62
|
+
*/
|
|
63
|
+
export declare function inboxOf(session: SessionFace): InboxLike | undefined;
|
|
68
64
|
/**
|
|
69
65
|
* Project the inbox `next-step` rows into the fields the retract path needs.
|
|
70
66
|
*
|
|
71
|
-
* ONLY user-sourced rows are steering:
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* rendered submission echoes and cost every later row its button.
|
|
78
|
-
* @param nextStep - the inbox projection's `next-step` list (absent before the
|
|
79
|
-
* fold state exists, hence `undefined`).
|
|
80
|
-
* @returns one item per USER steering row, in host (FIFO) order.
|
|
67
|
+
* ONLY user-sourced rows are steering: a `next-step` row with another source is
|
|
68
|
+
* injected context, never retracted. Because a retract removes the target AND
|
|
69
|
+
* its future, a non-user row must not enter the list either — it would also
|
|
70
|
+
* shift the positional match and cost every later row its button.
|
|
71
|
+
* @param nextStep - the inbox projection's `next-step` list.
|
|
72
|
+
* @returns one item per user steering row, in host (FIFO) order.
|
|
81
73
|
*/
|
|
82
74
|
export declare function steeringItemsOf(nextStep: readonly InboxMessageLike[] | undefined): readonly PendingSteeringItem[];
|
|
83
75
|
/**
|
|
84
|
-
* Pair rows to steering items by index, verifying the match key per
|
|
85
|
-
* visible text
|
|
86
|
-
*
|
|
87
|
-
* is why its ↶ button never mounted — because the rendered attachment block
|
|
88
|
-
* contributes text no inbox row reproduces.
|
|
89
|
-
*
|
|
76
|
+
* Pair rendered rows to steering items by index, verifying the match key per
|
|
77
|
+
* row: the visible text for a text-only message, otherwise the attachment count
|
|
78
|
+
* (the rendered attachment block contributes text no inbox row reproduces).
|
|
90
79
|
* @param rows - pending bubble rows in DOM order (== render order).
|
|
91
80
|
* @param steering - steering items in host order (== render order).
|
|
92
|
-
* @returns the item id for each row, or null for
|
|
93
|
-
* safely (missing counterpart, key mismatch). A bad row never affects the
|
|
94
|
-
* other rows.
|
|
81
|
+
* @returns the item id for each row, or null for a row that cannot be matched.
|
|
95
82
|
*/
|
|
96
83
|
export declare function matchPendingRows(rows: readonly PendingRow[], steering: readonly PendingSteeringItem[]): readonly (string | null)[];
|
|
97
84
|
/**
|
|
98
|
-
* The
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
85
|
+
* The steering ids a retract removes: the target occurrence and every steering
|
|
86
|
+
* message after it, in inbox (FIFO) order. Queued (`next-turn`) messages are
|
|
87
|
+
* deliberately excluded — the harness QueueDock already offers per-item
|
|
88
|
+
* edit/remove, and a rewind must not silently drop messages the user may still
|
|
89
|
+
* want to send.
|
|
103
90
|
* @param steering - steering items in host order (== render order).
|
|
104
|
-
* @param targetId - the
|
|
105
|
-
* @returns the ids to remove, oldest-first; empty when the target is no
|
|
106
|
-
* longer pending (already claimed/consumed).
|
|
91
|
+
* @param targetId - the retract target's inbox occurrence id.
|
|
92
|
+
* @returns the ids to remove, oldest-first; empty when the target is no longer pending.
|
|
107
93
|
*/
|
|
108
94
|
export declare function retractSpan(steering: readonly {
|
|
109
95
|
readonly id: string;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The rewind mode-selection popover (plain DOM, no React). Step two of the
|
|
3
|
-
*
|
|
4
|
-
* offers the two modes. Choosing "both" fetches the impact list over
|
|
5
|
-
* `/api` preview route and shows it before confirming.
|
|
3
|
+
* `/rewind` text flow: the target is already fixed (the picked message); the
|
|
4
|
+
* popover offers the two modes. Choosing "both" fetches the impact list over
|
|
5
|
+
* the host `/api` preview route and shows it before confirming.
|
|
6
6
|
*
|
|
7
7
|
* Keyboard: ↑/↓ move focus across the step's ACTION buttons only (the two
|
|
8
8
|
* modes, or the confirm button on the impact step), Enter activates the
|
|
9
|
-
* focused button (native), Esc is the keyboard twin of the ghost back
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* focused button (native), Esc is the keyboard twin of the ghost back button
|
|
10
|
+
* (impact step) and of cancel (modes step); the ghosts are never in the arrow
|
|
11
|
+
* cycle. The listener runs in the document capture phase so the keys are
|
|
12
|
+
* stolen from the composer while the popover is open.
|
|
13
13
|
*
|
|
14
14
|
* @module dsh-rewind/client/popover
|
|
15
15
|
*/
|
|
@@ -21,27 +21,27 @@ import type { RewindKey } from './locales.ts';
|
|
|
21
21
|
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
22
22
|
export interface PopoverOptions {
|
|
23
23
|
readonly session: SessionFace;
|
|
24
|
-
/**
|
|
24
|
+
/** The target message seq. */
|
|
25
25
|
readonly seq?: number;
|
|
26
|
-
/**
|
|
26
|
+
/** The target message time. */
|
|
27
27
|
readonly time?: number;
|
|
28
|
-
/** Pending variant: retract a pre-sent steering message (single-confirm flow). */
|
|
29
|
-
readonly retract?: {
|
|
30
|
-
readonly itemId: string;
|
|
31
|
-
readonly text: string | null;
|
|
32
|
-
};
|
|
33
|
-
/** Pending variant: executed after the retract confirm closes the popover. */
|
|
34
|
-
readonly onRetract?: () => void;
|
|
35
28
|
readonly preview: string;
|
|
36
|
-
/** The
|
|
29
|
+
/** The element the popover is positioned against (outside-click ignore target). */
|
|
37
30
|
readonly anchor: HTMLElement;
|
|
38
31
|
readonly t: Translate;
|
|
39
32
|
/**
|
|
40
33
|
* Execute one rewind in the given mode. The popover closes itself first;
|
|
41
34
|
* the callback owns the command + composer-refill lifecycle (see
|
|
42
|
-
* runRewindAndFill in
|
|
35
|
+
* runRewindAndFill in rewind-flow.ts).
|
|
43
36
|
*/
|
|
44
37
|
readonly onRewind?: (mode: 'chat' | 'both') => void;
|
|
38
|
+
/** Pending variant: retract a pre-sent steering message (single-confirm flow). */
|
|
39
|
+
readonly retract?: {
|
|
40
|
+
readonly itemId: string;
|
|
41
|
+
readonly text: string | null;
|
|
42
|
+
};
|
|
43
|
+
/** Pending variant: executed after the retract confirm closes the popover. */
|
|
44
|
+
readonly onRetract?: () => void;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Register the composer focus channel (called once by the client plugin).
|
|
@@ -68,6 +68,6 @@ export declare function waitForCommand(session: SessionFace, chatOf: ChatOf, mat
|
|
|
68
68
|
} | null>;
|
|
69
69
|
/** Register the host preview call; null restores the unavailable default. */
|
|
70
70
|
export declare function registerPreviewCall(call: PreviewCall | null): void;
|
|
71
|
-
/** Open the mode-selection popover anchored near the given
|
|
71
|
+
/** Open the mode-selection popover anchored near the given anchor. */
|
|
72
72
|
export declare function openPopover(opts: PopoverOptions): void;
|
|
73
73
|
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pending (unclaimed) rewind: withdraw one pre-sent steering message and
|
|
3
|
+
* every steering message after it — the rollback point's "future" — WITHOUT
|
|
4
|
+
* interrupting the run.
|
|
5
|
+
*
|
|
6
|
+
* The message is still in the agent's next-step inbox, so the loop has not
|
|
7
|
+
* claimed it into any request: removing it cannot change what the model is
|
|
8
|
+
* generating. Stopping the run first (the durable-rewind rule) would throw away
|
|
9
|
+
* in-flight work for an edit the model can never observe. Only the durable
|
|
10
|
+
* rewind needs a stop, because it cuts the model-visible surface.
|
|
11
|
+
*
|
|
12
|
+
* The realistic failure is `queue-item-not-found`: a step boundary claimed the
|
|
13
|
+
* whole batch a moment ago, so the message is durable now and the durable row's
|
|
14
|
+
* rewind action takes over with no gap. Nothing is removed then, and the
|
|
15
|
+
* composer is NOT refilled — that would invite a duplicate send of a message
|
|
16
|
+
* the model has already received.
|
|
17
|
+
*
|
|
18
|
+
* @module dsh-rewind/client/retract
|
|
19
|
+
*/
|
|
20
|
+
import type { SessionFace } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
21
|
+
import type { MessageImageRef } from './hidden.ts';
|
|
22
|
+
/**
|
|
23
|
+
* Withdraw one pending steering message and its steering future through the
|
|
24
|
+
* session's own `updateQueue` channel, then put the target's images and text
|
|
25
|
+
* back into the composer.
|
|
26
|
+
* @param session - the session holding the pending occurrence.
|
|
27
|
+
* @param itemId - the target's inbox occurrence id.
|
|
28
|
+
* @param text - the target's complete text (null when it has no text block).
|
|
29
|
+
* @param setComposerText - writes one session's composer draft.
|
|
30
|
+
* @param restoreImages - restores the target's images into the composer draft.
|
|
31
|
+
*/
|
|
32
|
+
export declare function retractPending(session: SessionFace, itemId: string, text: string | null, setComposerText: (sessionId: string, text: string) => boolean, restoreImages?: (sessionId: string, images: readonly MessageImageRef[]) => Promise<void>): Promise<void>;
|