@yadsh/dsh-qa-surface 0.1.1 → 0.2.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.
- package/CHANGELOG.md +68 -0
- package/README.md +162 -14
- package/SPEC-dsh-qa-surface.md +600 -96
- package/capability-policy.json +5 -0
- package/compatibility.json +7 -1
- package/cordis.patch.yml +11 -0
- package/docs/ARCHITECTURE.md +49 -7
- package/docs/COMPATIBILITY.md +5 -4
- package/docs/CONFIGURATION.md +96 -2
- package/lib/attestation.js +10 -0
- package/lib/attestation.js.map +1 -0
- package/lib/client/QaConfigController.js +111 -4
- package/lib/client/QaConfigController.js.map +1 -1
- package/lib/client/QaSessionController.js +501 -119
- package/lib/client/QaSessionController.js.map +1 -1
- package/lib/client/QaSurface.js +137 -20
- package/lib/client/QaSurface.js.map +1 -1
- package/lib/client/QaTranscriptAdapter.js +534 -45
- package/lib/client/QaTranscriptAdapter.js.map +1 -1
- package/lib/client/attestation.js +60 -0
- package/lib/client/attestation.js.map +1 -0
- package/lib/client/chat-index.js +86 -0
- package/lib/client/chat-index.js.map +1 -0
- package/lib/client/components/Markdown.js +75 -5
- package/lib/client/components/Markdown.js.map +1 -1
- package/lib/client/components/QaAgentsDrawer.js +40 -0
- package/lib/client/components/QaAgentsDrawer.js.map +1 -0
- package/lib/client/components/QaComposer.js +132 -15
- package/lib/client/components/QaComposer.js.map +1 -1
- package/lib/client/components/QaMessage.js +184 -10
- package/lib/client/components/QaMessage.js.map +1 -1
- package/lib/client/components/QaSidebar.js +124 -0
- package/lib/client/components/QaSidebar.js.map +1 -0
- package/lib/client/components/QaSourcesDrawer.js +46 -0
- package/lib/client/components/QaSourcesDrawer.js.map +1 -0
- package/lib/client/components/QaWorkGroup.js +135 -0
- package/lib/client/components/QaWorkGroup.js.map +1 -0
- package/lib/client/components/VariantSwitcher.js +29 -0
- package/lib/client/components/VariantSwitcher.js.map +1 -0
- package/lib/client/components/format.js +48 -0
- package/lib/client/components/format.js.map +1 -0
- package/lib/client/index.js +77 -34
- package/lib/client/index.js.map +1 -1
- package/lib/client/styles.js +308 -43
- package/lib/client/styles.js.map +1 -1
- package/lib/client/types.js +13 -0
- package/lib/client/types.js.map +1 -0
- package/lib/client/wait-for.js +49 -0
- package/lib/client/wait-for.js.map +1 -0
- package/lib/client.js +8228 -383
- package/lib/client.js.map +1 -1
- package/lib/config.js +61 -9
- package/lib/config.js.map +1 -1
- package/lib/index.js +166 -70
- package/lib/index.js.map +1 -1
- package/lib/lockdown-policy.js +20 -0
- package/lib/lockdown-policy.js.map +1 -0
- package/lib/resolve-config.js +122 -10
- package/lib/resolve-config.js.map +1 -1
- package/lib/secure-session.js +170 -0
- package/lib/secure-session.js.map +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +191 -0
- package/lib/typert.remote-client.d.ts +24 -0
- package/lib/typert.remote-client.js +129 -0
- package/lib/types/attestation.d.ts +17 -0
- package/lib/types/client/QaConfigController.d.ts +34 -2
- package/lib/types/client/QaSessionController.d.ts +106 -19
- package/lib/types/client/QaSurface.d.ts +5 -4
- package/lib/types/client/QaTranscriptAdapter.d.ts +17 -2
- package/lib/types/client/attestation.d.ts +25 -0
- package/lib/types/client/chat-index.d.ts +28 -0
- package/lib/types/client/components/Markdown.d.ts +7 -3
- package/lib/types/client/components/QaAgentsDrawer.d.ts +25 -0
- package/lib/types/client/components/QaComposer.d.ts +12 -2
- package/lib/types/client/components/QaMessage.d.ts +14 -1
- package/lib/types/client/components/QaSidebar.d.ts +41 -0
- package/lib/types/client/components/QaSourcesDrawer.d.ts +8 -0
- package/lib/types/client/components/QaWorkGroup.d.ts +19 -0
- package/lib/types/client/components/VariantSwitcher.d.ts +19 -0
- package/lib/types/client/components/format.d.ts +9 -0
- package/lib/types/client/index.d.ts +1 -5
- package/lib/types/client/types.d.ts +29 -0
- package/lib/types/client/wait-for.d.ts +11 -0
- package/lib/types/index.d.ts +22 -6
- package/lib/types/lockdown-policy.d.ts +18 -0
- package/lib/types/secure-session.d.ts +22 -0
- package/lib/types/types.d.ts +148 -1
- package/package.json +29 -3
|
@@ -1,59 +1,146 @@
|
|
|
1
|
-
import type { HostDescriptionSource
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
type QaSessionsApi = Pick<IApiClient["sessions"], "create" | "selectModel">;
|
|
5
|
-
export interface StorageLike {
|
|
6
|
-
getItem(key: string): string | null;
|
|
7
|
-
setItem(key: string, value: string): void;
|
|
8
|
-
removeItem(key: string): void;
|
|
9
|
-
}
|
|
1
|
+
import type { HostDescriptionSource } from "@deepseek-ai/dsh-client-connection/client";
|
|
2
|
+
import type { QaImageDraft, QaSessionState, ResolvedQaSurfaceConfig } from "../types.js";
|
|
3
|
+
import type { QaSecureSession, QaSessions, QaSessionsApi, StorageLike } from "./types.js";
|
|
10
4
|
export interface QaSessionControllerOptions {
|
|
11
|
-
readonly sessions:
|
|
5
|
+
readonly sessions: QaSessions;
|
|
12
6
|
readonly api: QaSessionsApi;
|
|
13
7
|
readonly connection: HostDescriptionSource;
|
|
14
8
|
readonly config: ResolvedQaSurfaceConfig;
|
|
9
|
+
readonly secureSession: QaSecureSession;
|
|
15
10
|
readonly storage?: StorageLike;
|
|
16
11
|
readonly timeoutMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Minimum spacing between projections of a running turn's stream frames.
|
|
14
|
+
* The host runtime already batches deltas to one notification per
|
|
15
|
+
* animation frame; this ceiling keeps the render path flat on long
|
|
16
|
+
* transcripts and weak hardware. Zero disables the spacing.
|
|
17
|
+
*/
|
|
18
|
+
readonly streamIntervalMs?: number;
|
|
17
19
|
}
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* The only module that couples QA behavior to DSH Session/client APIs. It owns
|
|
22
|
+
* the chat lifecycle state machine; the observable wait, browser-local chat
|
|
23
|
+
* bookkeeping and policy-attestation details live in their own modules.
|
|
24
|
+
*/
|
|
19
25
|
export declare class QaSessionController {
|
|
20
26
|
private readonly listeners;
|
|
21
27
|
private readonly sessions;
|
|
22
28
|
private readonly api;
|
|
23
29
|
private readonly connection;
|
|
24
30
|
private readonly config;
|
|
25
|
-
private readonly
|
|
31
|
+
private readonly secureSessionRemote;
|
|
32
|
+
private readonly chats;
|
|
26
33
|
private readonly timeoutMs;
|
|
34
|
+
private readonly streamIntervalMs;
|
|
27
35
|
private state;
|
|
28
36
|
private session;
|
|
29
37
|
private unsubscribeSession;
|
|
30
38
|
private readonly unsubscribeConnection;
|
|
31
39
|
private ensuring;
|
|
40
|
+
private materializing;
|
|
32
41
|
private operationError;
|
|
33
42
|
private admissionPending;
|
|
43
|
+
private policyReady;
|
|
44
|
+
private drafting;
|
|
45
|
+
private streamFlushTimer;
|
|
46
|
+
/** The chat session to return to when a subagent view closes. */
|
|
47
|
+
private chatSessionId;
|
|
48
|
+
private viewingSubagent;
|
|
34
49
|
private connectedOnce;
|
|
35
50
|
private disposed;
|
|
36
51
|
private generation;
|
|
52
|
+
private chatsRevision;
|
|
37
53
|
constructor(options: QaSessionControllerOptions);
|
|
38
54
|
getSnapshot: () => QaSessionState;
|
|
39
55
|
subscribe: (listener: () => void) => (() => void);
|
|
40
56
|
ensureSession(): Promise<void>;
|
|
41
|
-
send(text: string): Promise<boolean>;
|
|
57
|
+
send(text: string, images?: readonly QaImageDraft[]): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Ask for a fresh variant of the previous answer. The session has no
|
|
60
|
+
* truncation seam, so this sends the hidden regeneration instruction as an
|
|
61
|
+
* ordinary prompt: the model produces a follow-up turn, the projection
|
|
62
|
+
* hides the marker message, and the consecutive turns read as variants of
|
|
63
|
+
* the original question (switched in the surface).
|
|
64
|
+
*/
|
|
65
|
+
regenerate(): Promise<boolean>;
|
|
42
66
|
stop(): Promise<void>;
|
|
43
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Enter the "new chat" draft: show an empty composer without creating any
|
|
69
|
+
* session. Nothing appears in the chat list and no Host session is spent
|
|
70
|
+
* until the first prompt is actually sent, which materializes the session
|
|
71
|
+
* lazily ({@link materializeDraft}). Fixed-policy deployments cannot draft.
|
|
72
|
+
*/
|
|
73
|
+
startDraft(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Turn the current draft into a real attested session. Runs inside the
|
|
76
|
+
* send flow; concurrent sends share one materialization. A refusal here is
|
|
77
|
+
* reported at once — a fresh session's first attestation is the
|
|
78
|
+
* deployment's only chance to learn the reason.
|
|
79
|
+
*/
|
|
80
|
+
private materializeDraft;
|
|
81
|
+
/**
|
|
82
|
+
* Open one of this browser's indexed chats. The id must be present in the
|
|
83
|
+
* host session list and pass policy attestation; an unknown id is forgotten
|
|
84
|
+
* from the index. Fixed-policy deployments cannot switch.
|
|
85
|
+
*/
|
|
86
|
+
switchTo(sessionId: string): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Watch a subagent's transcript live. The binding is read-only by
|
|
89
|
+
* construction: no attestation runs (attestation admits sending, and the
|
|
90
|
+
* composer stays disabled here), the chat index is untouched, and
|
|
91
|
+
* {@link closeSubagent} returns to the chat the panel was opened from.
|
|
92
|
+
*/
|
|
93
|
+
viewSubagent(id: string, title: string): Promise<void>;
|
|
94
|
+
/** Leave the subagent transcript and re-open the chat it was launched from. */
|
|
95
|
+
closeSubagent(): Promise<void>;
|
|
96
|
+
/** This browser's indexed chat ids, most recently used first. */
|
|
97
|
+
chatIds(): readonly string[];
|
|
98
|
+
/** The bound session id, or null while no chat is bound. */
|
|
99
|
+
activeSessionId(): string | null;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve one durable image attachment of the bound session into a
|
|
102
|
+
* browser-usable URL. The caller caches; failures surface as broken views.
|
|
103
|
+
*/
|
|
104
|
+
readImage(attachmentId: string): Promise<string>;
|
|
105
|
+
/**
|
|
106
|
+
* Remove one chat from this browser's index. Deleting the chat that is
|
|
107
|
+
* currently open also forgets the persisted id and starts a fresh attested
|
|
108
|
+
* session (when the deployment allows session resets); the Host-side
|
|
109
|
+
* session itself is not touched — no host deletion seam exists.
|
|
110
|
+
*/
|
|
111
|
+
deleteChat(id: string): Promise<void>;
|
|
112
|
+
/** Drop one chat id from this browser's index. */
|
|
113
|
+
forgetChat(sessionId: string): void;
|
|
44
114
|
dispose(): void;
|
|
45
115
|
private ensureSessionNow;
|
|
116
|
+
/**
|
|
117
|
+
* Recovery ladder after a refused send-time attestation. The prompt was
|
|
118
|
+
* never admitted, so nothing here can duplicate it. Step 1 re-opens the
|
|
119
|
+
* session: a Host that idled the agent out dismantles its tool view, and
|
|
120
|
+
* re-opening re-materializes it. Step 2 — when that refused session is
|
|
121
|
+
* still blank (it never received the prompt, so there is nothing to lose) —
|
|
122
|
+
* starts a fresh attested session instead. A session with history is left
|
|
123
|
+
* alone: recreating it would silently drop the conversation.
|
|
124
|
+
*/
|
|
125
|
+
private recoverForSend;
|
|
46
126
|
private createSession;
|
|
47
127
|
private bind;
|
|
48
128
|
private unbind;
|
|
49
129
|
private waitForConnection;
|
|
130
|
+
/**
|
|
131
|
+
* Project one session notification. A running turn's frames arrive at
|
|
132
|
+
* animation-frame cadence, so re-projections are spaced at least
|
|
133
|
+
* {@link streamIntervalMs} apart: the first frame of a window projects at
|
|
134
|
+
* once and further frames are absorbed (the memoized render path replays
|
|
135
|
+
* nothing, so absorbing a frame only defers it). Everything outside a
|
|
136
|
+
* running turn - phase flips, errors, turn completion - projects
|
|
137
|
+
* immediately.
|
|
138
|
+
*/
|
|
139
|
+
private publishSessionUpdate;
|
|
140
|
+
private clearStreamFlush;
|
|
50
141
|
private publish;
|
|
51
142
|
private fail;
|
|
52
|
-
private
|
|
53
|
-
private readPersisted;
|
|
54
|
-
private persist;
|
|
55
|
-
private clearPersisted;
|
|
143
|
+
private attestPolicy;
|
|
56
144
|
private emit;
|
|
57
145
|
}
|
|
58
|
-
export type { SessionListState };
|
|
59
146
|
//# sourceMappingURL=QaSessionController.d.ts.map
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import type { HostDescriptionSource
|
|
2
|
-
import type { ISessions } from "@deepseek-ai/dsh-client-runtime/client";
|
|
1
|
+
import type { HostDescriptionSource } from "@deepseek-ai/dsh-client-connection/client";
|
|
3
2
|
import type { InjectFace, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
|
|
4
3
|
import type { QaConfigController } from "./QaConfigController.js";
|
|
5
4
|
import type { QaRouteController } from "./QaRouteController.js";
|
|
5
|
+
import type { QaSecureSession, QaSessions, QaSessionsApi } from "./types.js";
|
|
6
6
|
export interface QaSurfaceFace {
|
|
7
7
|
readonly route: QaRouteController;
|
|
8
8
|
readonly config: QaConfigController;
|
|
9
|
-
readonly sessions:
|
|
10
|
-
readonly api:
|
|
9
|
+
readonly sessions: QaSessions;
|
|
10
|
+
readonly api: QaSessionsApi;
|
|
11
11
|
readonly connection: HostDescriptionSource;
|
|
12
|
+
readonly secureSession: QaSecureSession;
|
|
12
13
|
}
|
|
13
14
|
type QaSurfaceProps = PropsRuntime<"shell.overlay"> & InjectFace<QaSurfaceFace>;
|
|
14
15
|
export declare function QaSurface(props: QaSurfaceProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import type { ConversationSnapshot } from "@deepseek-ai/dsh-client-runtime/client";
|
|
2
|
-
import type { QaMessage } from "../types.js";
|
|
3
|
-
/**
|
|
2
|
+
import type { QaMessage, QaSource } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The hidden regeneration prompt. Regenerate sends this as an ordinary
|
|
5
|
+
* prompt — the session has no truncation seam, so a "variant" is a real
|
|
6
|
+
* follow-up turn — and the projection drops it from the visible transcript,
|
|
7
|
+
* leaving the consecutive answer turns to read as variants of one question.
|
|
8
|
+
*/
|
|
9
|
+
export declare const QA_REGENERATE_MARKER = "\u041F\u0435\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439 \u0441\u0432\u043E\u0439 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439 \u043E\u0442\u0432\u0435\u0442 \u2014 \u0434\u0430\u0439 \u043D\u043E\u0432\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442, \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044F \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439.";
|
|
10
|
+
/** Project end-user messages plus optional operator-approved work detail. */
|
|
4
11
|
export declare function projectTranscript(snapshot: ConversationSnapshot, options?: {
|
|
5
12
|
readonly showToolActivity?: boolean;
|
|
13
|
+
readonly showReasoning?: boolean;
|
|
6
14
|
}): readonly QaMessage[];
|
|
15
|
+
/**
|
|
16
|
+
* Collect the sources this chat has actually touched, in first-use order:
|
|
17
|
+
* fetched pages, searches, and files read. This is the same tool activity the
|
|
18
|
+
* work groups render, projected as a flat citation-style list, so it carries
|
|
19
|
+
* no information beyond `ui.showToolActivity`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function projectSources(snapshot: ConversationSnapshot): readonly QaSource[];
|
|
7
22
|
//# sourceMappingURL=QaTranscriptAdapter.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { QaLockdownProof, ResolvedQaSurfaceConfig } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Browser-side reading of Host policy attestation: one error type the session
|
|
4
|
+
* lifecycle branches on, plus pure diagnostics for the console. The specific
|
|
5
|
+
* mismatch facts stay in the Host logs; a refusal only reveals the coarse
|
|
6
|
+
* reason class and an operator hint.
|
|
7
|
+
*/
|
|
8
|
+
export declare class QaPolicyAttestationError extends Error {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Extract the coarse attestation reason code from a Host wire failure, if the
|
|
13
|
+
* failure carries the marker.
|
|
14
|
+
*/
|
|
15
|
+
export declare function attestationReasonOf(failure: {
|
|
16
|
+
readonly message?: string;
|
|
17
|
+
} | undefined | null): string | null;
|
|
18
|
+
export declare function attestationHint(reason: string | null): string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a Host proof answers the deployment lockdown config this browser
|
|
21
|
+
* holds. Every field must match; the allow-list is compared as exact JSON so
|
|
22
|
+
* order differences refuse too, mirroring the Host's pinned list.
|
|
23
|
+
*/
|
|
24
|
+
export declare function proofMatchesConfig(proof: QaLockdownProof, lockdown: ResolvedQaSurfaceConfig["lockdown"], sessionId: string): boolean;
|
|
25
|
+
//# sourceMappingURL=attestation.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { StorageLike } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Browser-local chat bookkeeping: a most-recently-used index of chat ids for
|
|
4
|
+
* the sidebar plus the id of the chat this browser reopens on load. Both live
|
|
5
|
+
* under the deployment's storage key and route path, and every access
|
|
6
|
+
* tolerates a denied or broken store — persistence must never prevent a real
|
|
7
|
+
* DSH session.
|
|
8
|
+
*/
|
|
9
|
+
export declare class QaChatIndex {
|
|
10
|
+
private readonly storage;
|
|
11
|
+
private readonly sessionKey;
|
|
12
|
+
private readonly chatKey;
|
|
13
|
+
constructor(storage: StorageLike | undefined, prefix: string);
|
|
14
|
+
/** This browser's indexed chat ids, most recently used first. */
|
|
15
|
+
chatIds(): readonly string[];
|
|
16
|
+
/**
|
|
17
|
+
* Add one chat to the index without reordering anything. Display order is
|
|
18
|
+
* the host's `updatedAt`, so merely opening a chat must not move it — the
|
|
19
|
+
* index is membership only.
|
|
20
|
+
*/
|
|
21
|
+
addChat(sessionId: string): void;
|
|
22
|
+
forgetChat(sessionId: string): void;
|
|
23
|
+
/** The persisted chat id this browser reopens on load, or null. */
|
|
24
|
+
activeId(): string | null;
|
|
25
|
+
saveActive(sessionId: string): void;
|
|
26
|
+
clearActive(): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=chat-index.d.ts.map
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Deliberately small, HTML-free Markdown renderer for assistant-visible text.
|
|
3
|
+
* Memoized on the text value: stream updates re-render only the message whose
|
|
4
|
+
* text actually changed, so committed history is never re-parsed.
|
|
5
|
+
*/
|
|
6
|
+
export declare const Markdown: import("react").MemoExoticComponent<({ text, }: {
|
|
3
7
|
readonly text: string;
|
|
4
|
-
})
|
|
8
|
+
}) => import("react/jsx-runtime").JSX.Element>;
|
|
5
9
|
//# sourceMappingURL=Markdown.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SessionSummary } from "@deepseek-ai/dsh-client-runtime/client";
|
|
2
|
+
/** One row of the agents panel: a subagent session of the open chat. */
|
|
3
|
+
export interface QaSubagentRow {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly running: boolean;
|
|
7
|
+
readonly completed: boolean;
|
|
8
|
+
readonly meta: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Direct subagent children of the open chat, running first. Reads the host
|
|
12
|
+
* session list's lineage (parentId + origin), so no extra subscription beyond
|
|
13
|
+
* the list the surface already follows.
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectSubagents(byId: Readonly<Record<string, SessionSummary>>, rootId: string | null, now?: number): readonly QaSubagentRow[];
|
|
16
|
+
export interface QaAgentsDrawerProps {
|
|
17
|
+
readonly agents: readonly QaSubagentRow[];
|
|
18
|
+
/** The subagent currently watched, if any. */
|
|
19
|
+
readonly activeId: string | null;
|
|
20
|
+
readonly onView: (id: string, title: string) => void;
|
|
21
|
+
readonly onClose: () => void;
|
|
22
|
+
}
|
|
23
|
+
/** Right-hand panel listing the chat's subagents. */
|
|
24
|
+
export declare function QaAgentsDrawer({ agents, activeId, onView, onClose, }: QaAgentsDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=QaAgentsDrawer.d.ts.map
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import type { QaImageDraft } from "../../types.js";
|
|
1
2
|
export interface QaComposerProps {
|
|
2
3
|
readonly placeholder: string;
|
|
4
|
+
readonly quickQuestions?: readonly string[];
|
|
3
5
|
readonly canSend: boolean;
|
|
4
6
|
readonly canStop: boolean;
|
|
5
7
|
readonly running: boolean;
|
|
6
8
|
readonly showStop: boolean;
|
|
7
|
-
readonly
|
|
9
|
+
readonly status: string | null;
|
|
10
|
+
readonly images: readonly QaImageDraft[];
|
|
11
|
+
readonly onImagesChange: (images: readonly QaImageDraft[]) => void;
|
|
12
|
+
readonly onSend: (text: string, images: readonly QaImageDraft[]) => Promise<boolean>;
|
|
8
13
|
readonly onStop: () => Promise<void>;
|
|
9
14
|
}
|
|
10
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Memoized with the default shallow compare: every prop is a scalar, a
|
|
17
|
+
* stable array or a stable callback, so a stream of transcript frames never
|
|
18
|
+
* re-renders the composer and typing stays responsive while an answer runs.
|
|
19
|
+
*/
|
|
20
|
+
export declare const QaComposer: import("react").MemoExoticComponent<(props: QaComposerProps) => import("react/jsx-runtime").JSX.Element>;
|
|
11
21
|
//# sourceMappingURL=QaComposer.d.ts.map
|
|
@@ -3,6 +3,19 @@ export interface QaMessageProps {
|
|
|
3
3
|
readonly message: QaMessageModel;
|
|
4
4
|
readonly renderMarkdown: boolean;
|
|
5
5
|
readonly showTimestamp: boolean;
|
|
6
|
+
/** Storage prefix persisted message ratings live under. */
|
|
7
|
+
readonly stateKey?: string;
|
|
8
|
+
/** Ask for a fresh variant of this answer; omit to hide the control. */
|
|
9
|
+
readonly onRegenerate?: () => void;
|
|
10
|
+
/** Resolve one durable attachment into a viewable URL. */
|
|
11
|
+
readonly resolveImage?: (attachmentId: string) => Promise<string>;
|
|
6
12
|
}
|
|
7
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Whether two transcript messages render identically. The transcript
|
|
15
|
+
* projection rebuilds every message object on each published frame, so the
|
|
16
|
+
* memoized row compares rendered content instead of references; string
|
|
17
|
+
* comparison is by value, which keeps fresh copies cheap to recognize.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sameMessage(a: QaMessageModel, b: QaMessageModel): boolean;
|
|
20
|
+
export declare const QaMessage: import("react").MemoExoticComponent<({ message, renderMarkdown, showTimestamp, stateKey, onRegenerate, resolveImage, }: QaMessageProps) => import("react/jsx-runtime").JSX.Element>;
|
|
8
21
|
//# sourceMappingURL=QaMessage.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SessionSummary } from "@deepseek-ai/dsh-client-runtime/client";
|
|
2
|
+
/** One renderable row of the chat-history sidebar. */
|
|
3
|
+
export interface QaChatRow {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly running: boolean;
|
|
7
|
+
readonly active: boolean;
|
|
8
|
+
readonly meta: string;
|
|
9
|
+
readonly updatedAt: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Project this browser's indexed chat ids onto the host session list, most
|
|
13
|
+
* recently updated first. Opening a chat is not an update: only the host's
|
|
14
|
+
* `updatedAt` (fresh messages) orders the list. Ids the host no longer lists
|
|
15
|
+
* are skipped (the controller prunes them on switch).
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildChatRows(chatIds: readonly string[], byId: Readonly<Record<string, SessionSummary>>, activeId: string | null, now?: number): readonly QaChatRow[];
|
|
18
|
+
export interface QaSidebarProps {
|
|
19
|
+
readonly rows: readonly QaChatRow[];
|
|
20
|
+
/** Deployment brand shown next to the logo (also while collapsed). */
|
|
21
|
+
readonly title: string;
|
|
22
|
+
/** Optional image logo; a built-in mark is drawn when null. */
|
|
23
|
+
readonly logoUrl: string | null;
|
|
24
|
+
/** Storage prefix used to remember the collapsed state across reloads. */
|
|
25
|
+
readonly stateKey: string;
|
|
26
|
+
readonly showNewChat: boolean;
|
|
27
|
+
readonly busy: boolean;
|
|
28
|
+
readonly onSwitch: (sessionId: string) => void;
|
|
29
|
+
readonly onNewChat: () => void;
|
|
30
|
+
/** Removes a chat from this browser's index; omit to hide the control. */
|
|
31
|
+
readonly onDelete?: (sessionId: string) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether two sidebar row lists show the same thing. Rows are rebuilt on
|
|
35
|
+
* every surface render (relative timestamps drift), so the memoized sidebar
|
|
36
|
+
* compares by content and skips frames that only move the transcript.
|
|
37
|
+
*/
|
|
38
|
+
export declare function sameChatRows(a: readonly QaChatRow[], b: readonly QaChatRow[]): boolean;
|
|
39
|
+
/** The per-browser chat history shown beside the QA conversation. */
|
|
40
|
+
export declare const QaSidebar: import("react").MemoExoticComponent<(props: QaSidebarProps) => import("react/jsx-runtime").JSX.Element>;
|
|
41
|
+
//# sourceMappingURL=QaSidebar.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { QaSource } from "../../types.js";
|
|
2
|
+
export interface QaSourcesDrawerProps {
|
|
3
|
+
readonly sources: readonly QaSource[];
|
|
4
|
+
readonly onClose: () => void;
|
|
5
|
+
}
|
|
6
|
+
/** Right-hand panel listing tool-derived sources with a detail pane. */
|
|
7
|
+
export declare function QaSourcesDrawer({ sources, onClose }: QaSourcesDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=QaSourcesDrawer.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { QaWorkItem } from "../../types.js";
|
|
2
|
+
export interface QaWorkGroupProps {
|
|
3
|
+
readonly status: "running" | "complete";
|
|
4
|
+
readonly startedAt?: number;
|
|
5
|
+
readonly endedAt?: number;
|
|
6
|
+
readonly items: readonly QaWorkItem[];
|
|
7
|
+
readonly renderMarkdown: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const THINKING_PHRASES: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* Whether two work items render identically. The transcript projection
|
|
12
|
+
* rebuilds every item object on each published frame, so memoized work rows
|
|
13
|
+
* compare by rendered content instead of by reference.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sameWorkItem(a: QaWorkItem, b: QaWorkItem): boolean;
|
|
16
|
+
/** Whether two work item lists render identically, item for item. */
|
|
17
|
+
export declare function sameWorkItems(a: readonly QaWorkItem[], b: readonly QaWorkItem[]): boolean;
|
|
18
|
+
export declare const QaWorkGroup: import("react").MemoExoticComponent<({ status, startedAt, endedAt, items, renderMarkdown, }: QaWorkGroupProps) => import("react/jsx-runtime").JSX.Element>;
|
|
19
|
+
//# sourceMappingURL=QaWorkGroup.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { QaMessage } from "../../types.js";
|
|
2
|
+
export interface QaVariantGroup {
|
|
3
|
+
/** The user message that anchors the group. */
|
|
4
|
+
readonly groupId: string;
|
|
5
|
+
/** Answer turns triggered by that message, in order. */
|
|
6
|
+
readonly turns: readonly number[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Group answer turns under the user message that triggered them. The session
|
|
10
|
+
* has no truncation seam, so a regenerated answer is a real follow-up turn;
|
|
11
|
+
* consecutive turns after one user message read as its variants.
|
|
12
|
+
*/
|
|
13
|
+
export declare function collectVariantGroups(messages: readonly QaMessage[]): readonly QaVariantGroup[];
|
|
14
|
+
export declare function VariantSwitcher({ count, offset, onStep, }: {
|
|
15
|
+
readonly count: number;
|
|
16
|
+
readonly offset: number;
|
|
17
|
+
readonly onStep: (offset: number) => void;
|
|
18
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=VariantSwitcher.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Shared time formatting for the QA surface UI. */
|
|
2
|
+
/** `только что`, `5 мин`, `3 ч`, `2 дн` or a ru-RU date past the week. */
|
|
3
|
+
export declare function relativeTime(timestamp: number, now: number): string;
|
|
4
|
+
/** `9 сент 15:44` - compact ru-RU day/month plus time. */
|
|
5
|
+
export declare function formatDayTime(timestamp: number): string;
|
|
6
|
+
/** `1,1 с` below ten seconds, `8 с` above. */
|
|
7
|
+
export declare function formatSeconds(ms: number): string;
|
|
8
|
+
export declare function formatWorkDuration(durationMs: number): string;
|
|
9
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
import type { ConnectionHandle } from "@deepseek-ai/dsh-client-connection/client";
|
|
3
|
-
import type { ISessions } from "@deepseek-ai/dsh-client-runtime/client";
|
|
4
|
-
import type { SettingsScopeBinder } from "@deepseek-ai/dsh-client-ui-settings/client";
|
|
5
3
|
declare module "@deepseek-ai/cordis" {
|
|
6
4
|
interface Context {
|
|
7
5
|
connection: ConnectionHandle;
|
|
8
|
-
sessions: ISessions;
|
|
9
|
-
settingsScope: SettingsScopeBinder;
|
|
10
6
|
}
|
|
11
7
|
}
|
|
12
8
|
export declare const inject: string[];
|
|
13
9
|
/** Register the route-aware, full-frame QA entry in the additive overlay slot. */
|
|
14
|
-
export declare function apply(ctx: Context): void
|
|
10
|
+
export declare function apply(ctx: Context): Promise<() => Promise<void>>;
|
|
15
11
|
export { QaConfigController } from "./QaConfigController.js";
|
|
16
12
|
export { QaRouteController, matchesQaRoute } from "./QaRouteController.js";
|
|
17
13
|
export { QaSessionController } from "./QaSessionController.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IApiClient } from "@deepseek-ai/dsh-client-connection/client";
|
|
2
|
+
import type { ISessions, SessionRuntime } from "@deepseek-ai/dsh-client-runtime/client";
|
|
3
|
+
import type { QaLockdownProof, QaSessionState } from "../types.js";
|
|
4
|
+
import type { SessionFace } from "@deepseek-ai/dsh-client-runtime/client";
|
|
5
|
+
/** Wire content one prompt carries: text plus base64 image uploads. */
|
|
6
|
+
export type QaPromptContent = Parameters<SessionFace["prompt"]>[0];
|
|
7
|
+
/** DSH session runtime surface the QA controller is allowed to touch. */
|
|
8
|
+
export type QaSessions = ISessions & Pick<SessionRuntime, "create">;
|
|
9
|
+
/** Narrow client API slice used to pin the preset and model of a fresh chat. */
|
|
10
|
+
export type QaSessionsApi = Pick<IApiClient["sessions"], "selectModel"> & {
|
|
11
|
+
readonly selectAgentPreset: IApiClient["agentPresets"]["select"];
|
|
12
|
+
};
|
|
13
|
+
/** Attestation channel to the Host admission boundary (`qaSurface/secureSession`). */
|
|
14
|
+
export type QaSecureSession = (sessionId: string) => Promise<{
|
|
15
|
+
readonly ok: true;
|
|
16
|
+
readonly value: QaLockdownProof;
|
|
17
|
+
} | {
|
|
18
|
+
readonly ok: false;
|
|
19
|
+
readonly error: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
/** Minimal persistence contract; backed by `window.localStorage` in the app. */
|
|
22
|
+
export interface StorageLike {
|
|
23
|
+
getItem(key: string): string | null;
|
|
24
|
+
setItem(key: string, value: string): void;
|
|
25
|
+
removeItem(key: string): void;
|
|
26
|
+
}
|
|
27
|
+
/** The state projected while no QA chat is bound (route inactive or booting). */
|
|
28
|
+
export declare const QA_SESSION_IDLE_STATE: QaSessionState;
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve with the first snapshot of an observable source that satisfies
|
|
3
|
+
* `predicate`, or reject after `timeoutMs`. This is the DSH-runtime glue the
|
|
4
|
+
* QA surface needs instead of ad-hoc sleeps; the source only has to expose
|
|
5
|
+
* the getSnapshot/subscribe store pair used across the client bundle.
|
|
6
|
+
*/
|
|
7
|
+
export declare function waitFor<T>(source: {
|
|
8
|
+
getSnapshot(): T;
|
|
9
|
+
subscribe(listener: () => void): () => void;
|
|
10
|
+
}, predicate: (value: T) => boolean, timeoutMs: number): Promise<T>;
|
|
11
|
+
//# sourceMappingURL=wait-for.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
2
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
import type { QaLockdownProof, QaSurfaceConfig, ResolvedQaSurfaceConfig } from "./types.js";
|
|
3
4
|
export declare const name = "qa-surface";
|
|
4
|
-
export declare const inject:
|
|
5
|
+
export declare const inject: string[];
|
|
5
6
|
export declare const QA_SURFACE_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
6
7
|
export declare const Config: import("@deepseek-ai/schemastery").default<QaSurfaceConfig>;
|
|
7
8
|
declare module "@deepseek-ai/cordis" {
|
|
@@ -9,21 +10,36 @@ declare module "@deepseek-ai/cordis" {
|
|
|
9
10
|
qaSurface: QaSurface;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
|
-
/**
|
|
13
|
-
export declare class QaSurface extends
|
|
14
|
-
static inject:
|
|
13
|
+
/** Host companion: validates config, owns the admission boundary and the route. */
|
|
14
|
+
export declare class QaSurface extends TypertRemoteService {
|
|
15
|
+
static inject: string[];
|
|
15
16
|
static Config: import("@deepseek-ai/schemastery").default<QaSurfaceConfig>;
|
|
16
17
|
private source;
|
|
17
18
|
private readonly logger;
|
|
19
|
+
private readonly admission;
|
|
18
20
|
private webServer;
|
|
19
21
|
private disposeRoute;
|
|
20
22
|
private routeKey;
|
|
21
23
|
constructor(ctx: Context, entry?: QaSurfaceConfig);
|
|
22
24
|
getConfig(): ResolvedQaSurfaceConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Serve the effective QA configuration to the browser. The DSH gateway pins
|
|
27
|
+
* settings RPCs to loopback, so a browser served over the LAN always sees
|
|
28
|
+
* the settings namespace as unavailable; this method is the config channel
|
|
29
|
+
* such a browser falls back to. It is read-only projection — the admission
|
|
30
|
+
* boundary stays in {@link secureSession}.
|
|
31
|
+
*/
|
|
32
|
+
describe(): ResolvedQaSurfaceConfig;
|
|
33
|
+
/** Pin and attest the effective policy. The browser supplies identity only. */
|
|
34
|
+
secureSession(sessionId: string): QaLockdownProof;
|
|
23
35
|
private refreshRoute;
|
|
24
36
|
}
|
|
25
37
|
export { ConfigSchema, DEFAULT_QA_SURFACE_CONFIG, normalizeRoutePath, resolveConfig, } from "./config.js";
|
|
38
|
+
export { QaAttestationError } from "./attestation.js";
|
|
39
|
+
export type { QaAttestationReason } from "./attestation.js";
|
|
26
40
|
export { registerQaNavigationRoute } from "./host-route.js";
|
|
41
|
+
export { qaToolDenial, qaToolPolicyPlan } from "./lockdown-policy.js";
|
|
42
|
+
export { QaPolicyAdmission } from "./secure-session.js";
|
|
27
43
|
export type * from "./types.js";
|
|
28
44
|
export default QaSurface;
|
|
29
45
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface QaToolPolicyPlan {
|
|
2
|
+
/** Exact names that must remain visible after the scoped restriction. */
|
|
3
|
+
readonly allow: readonly string[];
|
|
4
|
+
/** Configured names absent from the agent's composed tool view. */
|
|
5
|
+
readonly unknown: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Validate an allow-list against the complete agent-scoped view.
|
|
9
|
+
*
|
|
10
|
+
* Preset tools live on an ancestor scope, not necessarily in the global tool
|
|
11
|
+
* layer. The restriction must therefore retain the configured names verbatim;
|
|
12
|
+
* deriving it from the global view would hide every preset-owned tool after a
|
|
13
|
+
* successful first attestation and make the next attestation fail.
|
|
14
|
+
*/
|
|
15
|
+
export declare function qaToolPolicyPlan(allow: readonly string[], isMounted: (toolName: string) => boolean): QaToolPolicyPlan;
|
|
16
|
+
/** Monotonic execution decision shared by inherited, scoped and transport tools. */
|
|
17
|
+
export declare function qaToolDenial(allowed: ReadonlySet<string>, toolName: string): string | undefined;
|
|
18
|
+
//# sourceMappingURL=lockdown-policy.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { PluginLogger } from "@yadsh/dsh-plugin-log";
|
|
3
|
+
import type { QaLockdownProof, ResolvedQaSurfaceConfig } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Host-side QA policy admission boundary — the only browser-callable operation
|
|
6
|
+
* that turns a session into a locked QA session. It verifies the immutable
|
|
7
|
+
* session composition against the effective deployment config, refuses to
|
|
8
|
+
* adopt a non-QA session with user history, validates the named permission
|
|
9
|
+
* preset, pins the scoped tool restriction plus the monotonic execution guard,
|
|
10
|
+
* and returns a sanitized proof. Every refusal carries only a coarse reason.
|
|
11
|
+
*/
|
|
12
|
+
export declare class QaPolicyAdmission {
|
|
13
|
+
private readonly ctx;
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly logger;
|
|
16
|
+
private readonly appliedPolicies;
|
|
17
|
+
constructor(ctx: Context, config: () => ResolvedQaSurfaceConfig, logger: PluginLogger);
|
|
18
|
+
secureSession(sessionId: string): QaLockdownProof;
|
|
19
|
+
/** Detach every pinned tool policy; wired as a disposal effect by the entry. */
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=secure-session.d.ts.map
|