@yadsh/dsh-qa-surface 0.2.0 → 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 (161) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/README.md +87 -12
  3. package/compatibility.json +11 -3
  4. package/docs/ARCHITECTURE.md +1 -1
  5. package/docs/COMPATIBILITY.md +1 -1
  6. package/docs/CONFIGURATION.md +88 -1
  7. package/docs/SOURCES-MIGRATION.md +28 -0
  8. package/docs/SOURCES-PROVENANCE-AUDIT.md +88 -0
  9. package/docs/SPEC-dsh-qa-surface-accounts.md +219 -0
  10. package/docs/SPEC-dsh-qa-surface-sources-provenance.md +1692 -0
  11. package/lib/accounts/index.js +2 -0
  12. package/lib/accounts/index.js.map +1 -0
  13. package/lib/accounts/store.js +478 -0
  14. package/lib/accounts/store.js.map +1 -0
  15. package/lib/attestation.js.map +1 -1
  16. package/lib/cli.js +143 -0
  17. package/lib/cli.js.map +1 -0
  18. package/lib/client/QaAccountsController.js +329 -0
  19. package/lib/client/QaAccountsController.js.map +1 -0
  20. package/lib/client/QaSessionController.js +203 -127
  21. package/lib/client/QaSessionController.js.map +1 -1
  22. package/lib/client/QaSurface.js +234 -72
  23. package/lib/client/QaSurface.js.map +1 -1
  24. package/lib/client/QaTranscriptAdapter.js +27 -241
  25. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  26. package/lib/client/attestation.js +6 -0
  27. package/lib/client/attestation.js.map +1 -1
  28. package/lib/client/components/Markdown.js +54 -13
  29. package/lib/client/components/Markdown.js.map +1 -1
  30. package/lib/client/components/QaAuthGate.js +40 -0
  31. package/lib/client/components/QaAuthGate.js.map +1 -0
  32. package/lib/client/components/QaChangelog.js +112 -0
  33. package/lib/client/components/QaChangelog.js.map +1 -0
  34. package/lib/client/components/QaMessage.js +47 -17
  35. package/lib/client/components/QaMessage.js.map +1 -1
  36. package/lib/client/components/QaSidebar.js +63 -20
  37. package/lib/client/components/QaSidebar.js.map +1 -1
  38. package/lib/client/components/QaSourcesDrawer.js +113 -32
  39. package/lib/client/components/QaSourcesDrawer.js.map +1 -1
  40. package/lib/client/components/QaTurnRail.js +281 -0
  41. package/lib/client/components/QaTurnRail.js.map +1 -0
  42. package/lib/client/components/QaWelcomeNotice.js +125 -0
  43. package/lib/client/components/QaWelcomeNotice.js.map +1 -0
  44. package/lib/client/components/QaWidthHandle.js +142 -0
  45. package/lib/client/components/QaWidthHandle.js.map +1 -0
  46. package/lib/client/components/source-refs.js +78 -0
  47. package/lib/client/components/source-refs.js.map +1 -0
  48. package/lib/client/create-session.js +51 -0
  49. package/lib/client/create-session.js.map +1 -0
  50. package/lib/client/index.js +100 -12
  51. package/lib/client/index.js.map +1 -1
  52. package/lib/client/project-session-state.js +78 -0
  53. package/lib/client/project-session-state.js.map +1 -0
  54. package/lib/client/session-admission.js +38 -0
  55. package/lib/client/session-admission.js.map +1 -0
  56. package/lib/client/session-assets.js +52 -0
  57. package/lib/client/session-assets.js.map +1 -0
  58. package/lib/client/session-sources.js +61 -0
  59. package/lib/client/session-sources.js.map +1 -0
  60. package/lib/client/settlement.js +24 -0
  61. package/lib/client/settlement.js.map +1 -0
  62. package/lib/client/styles.js +151 -9
  63. package/lib/client/styles.js.map +1 -1
  64. package/lib/client/tool-presentation.js +102 -0
  65. package/lib/client/tool-presentation.js.map +1 -0
  66. package/lib/client/turn-sources.js +98 -0
  67. package/lib/client/turn-sources.js.map +1 -0
  68. package/lib/client/types.js +2 -0
  69. package/lib/client/types.js.map +1 -1
  70. package/lib/client/use-session-ui-state.js +68 -0
  71. package/lib/client/use-session-ui-state.js.map +1 -0
  72. package/lib/client/use-transcript-view.js +44 -0
  73. package/lib/client/use-transcript-view.js.map +1 -0
  74. package/lib/client.js +4649 -861
  75. package/lib/client.js.map +1 -1
  76. package/lib/config.js +218 -102
  77. package/lib/config.js.map +1 -1
  78. package/lib/entry-redirect.js +44 -0
  79. package/lib/entry-redirect.js.map +1 -0
  80. package/lib/host-route.js +25 -1
  81. package/lib/host-route.js.map +1 -1
  82. package/lib/index.js +209 -23
  83. package/lib/index.js.map +1 -1
  84. package/lib/launch-token.js +42 -0
  85. package/lib/launch-token.js.map +1 -0
  86. package/lib/provenance/collector.js +40 -0
  87. package/lib/provenance/collector.js.map +1 -0
  88. package/lib/provenance/connectors.js +147 -0
  89. package/lib/provenance/connectors.js.map +1 -0
  90. package/lib/provenance/dedupe.js +89 -0
  91. package/lib/provenance/dedupe.js.map +1 -0
  92. package/lib/provenance/extractors.js +340 -0
  93. package/lib/provenance/extractors.js.map +1 -0
  94. package/lib/provenance/file-preview.js +39 -0
  95. package/lib/provenance/file-preview.js.map +1 -0
  96. package/lib/provenance/host-store.js +387 -0
  97. package/lib/provenance/host-store.js.map +1 -0
  98. package/lib/provenance/index.js +9 -0
  99. package/lib/provenance/index.js.map +1 -0
  100. package/lib/provenance/normalize.js +128 -0
  101. package/lib/provenance/normalize.js.map +1 -0
  102. package/lib/provenance/reported.js +53 -0
  103. package/lib/provenance/reported.js.map +1 -0
  104. package/lib/provenance/types.js +2 -0
  105. package/lib/provenance/types.js.map +1 -0
  106. package/lib/resolve-config.js +161 -0
  107. package/lib/resolve-config.js.map +1 -1
  108. package/lib/secure-session.js +50 -8
  109. package/lib/secure-session.js.map +1 -1
  110. package/lib/typert.host.js +599 -6
  111. package/lib/typert.remote-client.d.ts +19 -3
  112. package/lib/typert.remote-client.js +471 -3
  113. package/lib/types/accounts/index.d.ts +2 -0
  114. package/lib/types/accounts/store.d.ts +121 -0
  115. package/lib/types/attestation.d.ts +1 -1
  116. package/lib/types/cli.d.ts +9 -0
  117. package/lib/types/client/QaAccountsController.d.ts +95 -0
  118. package/lib/types/client/QaConfigController.d.ts +1 -1
  119. package/lib/types/client/QaSessionController.d.ts +46 -7
  120. package/lib/types/client/QaSurface.d.ts +8 -3
  121. package/lib/types/client/QaTranscriptAdapter.d.ts +5 -10
  122. package/lib/types/client/components/Markdown.d.ts +11 -1
  123. package/lib/types/client/components/QaAgentsDrawer.d.ts +1 -1
  124. package/lib/types/client/components/QaAuthGate.d.ts +15 -0
  125. package/lib/types/client/components/QaChangelog.d.ts +21 -0
  126. package/lib/types/client/components/QaMessage.d.ts +10 -3
  127. package/lib/types/client/components/QaSidebar.d.ts +30 -2
  128. package/lib/types/client/components/QaSourcesDrawer.d.ts +15 -3
  129. package/lib/types/client/components/QaTurnRail.d.ts +59 -0
  130. package/lib/types/client/components/QaWelcomeNotice.d.ts +27 -0
  131. package/lib/types/client/components/QaWidthHandle.d.ts +36 -0
  132. package/lib/types/client/components/source-refs.d.ts +16 -0
  133. package/lib/types/client/create-session.d.ts +16 -0
  134. package/lib/types/client/index.d.ts +1 -0
  135. package/lib/types/client/project-session-state.d.ts +30 -0
  136. package/lib/types/client/session-admission.d.ts +23 -0
  137. package/lib/types/client/session-assets.d.ts +20 -0
  138. package/lib/types/client/session-sources.d.ts +28 -0
  139. package/lib/types/client/settlement.d.ts +6 -0
  140. package/lib/types/client/tool-presentation.d.ts +6 -0
  141. package/lib/types/client/turn-sources.d.ts +14 -0
  142. package/lib/types/client/types.d.ts +73 -9
  143. package/lib/types/client/use-session-ui-state.d.ts +35 -0
  144. package/lib/types/client/use-transcript-view.d.ts +20 -0
  145. package/lib/types/entry-redirect.d.ts +20 -0
  146. package/lib/types/host-route.d.ts +14 -1
  147. package/lib/types/index.d.ts +47 -3
  148. package/lib/types/launch-token.d.ts +21 -0
  149. package/lib/types/provenance/collector.d.ts +26 -0
  150. package/lib/types/provenance/connectors.d.ts +11 -0
  151. package/lib/types/provenance/dedupe.d.ts +6 -0
  152. package/lib/types/provenance/extractors.d.ts +14 -0
  153. package/lib/types/provenance/file-preview.d.ts +10 -0
  154. package/lib/types/provenance/host-store.d.ts +54 -0
  155. package/lib/types/provenance/index.d.ts +10 -0
  156. package/lib/types/provenance/normalize.d.ts +13 -0
  157. package/lib/types/provenance/reported.d.ts +5 -0
  158. package/lib/types/provenance/types.d.ts +78 -0
  159. package/lib/types/secure-session.d.ts +11 -2
  160. package/lib/types/types.d.ts +167 -11
  161. package/package.json +58 -41
@@ -0,0 +1,121 @@
1
+ import type { QaAccountRole, QaAccountSession, QaAccountUserPublic, QaClaimResult, QaOwnershipEntry, QaWhoamiResult } from "../types.js";
2
+ /**
3
+ * Coarse, wire-safe account failure codes. They ride the `(reason: <code>)`
4
+ * marker pattern the attestation path established; the browser maps them to
5
+ * audience-safe copy and the precise cause stays in the Host logs.
6
+ */
7
+ export type QaAccountsErrorReason = "auth-required" | "admin-required" | "invalid-credentials" | "account-disabled" | "email-taken" | "invalid-email" | "invalid-display-name" | "invalid-role" | "weak-password" | "registration-disabled" | "rate-limited" | "session-owned-elsewhere";
8
+ export declare class QaAccountsError extends Error {
9
+ readonly reason: QaAccountsErrorReason;
10
+ constructor(reason: QaAccountsErrorReason, message: string);
11
+ }
12
+ interface StoredUser {
13
+ readonly id: string;
14
+ readonly email: string;
15
+ readonly displayName: string;
16
+ readonly role: QaAccountRole;
17
+ /** scrypt material: hex salt + hex hash. */
18
+ readonly passwordHash: {
19
+ readonly salt: string;
20
+ readonly hash: string;
21
+ };
22
+ readonly createdAt: string;
23
+ lastLoginAt: string | null;
24
+ /** Disabled accounts are refused at login and lose their live tokens. */
25
+ disabled?: boolean;
26
+ /**
27
+ * Bumped to invalidate every token ever issued to the account: the value
28
+ * rides the token payload and must match at verification time.
29
+ */
30
+ tokenVersion?: number;
31
+ }
32
+ export interface QaAccountsOptions {
33
+ readonly sessionTtlDays: number;
34
+ readonly allowRegistration: boolean;
35
+ /** Login/registration attempts accepted per rolling minute, store-wide. */
36
+ readonly maxAuthAttemptsPerMinute?: number;
37
+ }
38
+ /** The accounts file lives next to the DSH home the launcher exports. */
39
+ export declare function defaultAccountsFilePath(): string;
40
+ /**
41
+ * Host authority for QA accounts: the file-backed user list, the session
42
+ * ownership map and the HMAC account tokens. All mutations are synchronous
43
+ * and persisted atomically (temp file + rename); LAN scale keeps this trivial.
44
+ */
45
+ export declare class QaAccounts {
46
+ readonly filePath: string;
47
+ private file;
48
+ private readonly authAttempts;
49
+ private readonly maxAuthAttemptsPerMinute;
50
+ private readonly sessionTtlDays;
51
+ private readonly allowRegistration;
52
+ /** mtime+size of the file as of the last load; the external-change probe. */
53
+ private lastLoadStamp;
54
+ constructor(filePath: string | undefined, options: QaAccountsOptions);
55
+ /**
56
+ * The qa-accounts CLI runs in its own process against the same file. Re-read
57
+ * it whenever it changed on disk, before every read or read-modify-write:
58
+ * without this a running Host would keep honoring tokens the CLI revoked
59
+ * and its next save would resurrect the CLI's change from a stale snapshot.
60
+ * One file, one writer at a time remains the deployment's discipline; LAN
61
+ * scale keeps the stat per operation trivial.
62
+ */
63
+ private reloadIfChanged;
64
+ private load;
65
+ private persist;
66
+ private save;
67
+ private pruneAuthAttempts;
68
+ private assertAuthBudget;
69
+ private userByEmail;
70
+ register(email: string, password: string, displayName?: string): QaAccountSession;
71
+ login(email: string, password: string): QaAccountSession;
72
+ whoami(token: string): QaWhoamiResult;
73
+ /** Resolve a token to its user, or null for absent/invalid/expired ones. */
74
+ verifyToken(token: string): string | null;
75
+ private mintToken;
76
+ /** The account behind a token, or an auth-required refusal. */
77
+ requireUser(token: string): StoredUser;
78
+ /**
79
+ * Ownership gate for one session: claim unowned sessions for the requesting
80
+ * user (first come, first served — the pre-accounts migration path), refuse
81
+ * foreign sessions unless the caller administers the deployment.
82
+ */
83
+ ensureSessionAccess(token: string, sessionId: string): QaAccountUserPublic;
84
+ /** Bulk-claim a browser's local chat index; foreign ids come back as conflicts. */
85
+ claimSessions(token: string, sessionIds: readonly string[]): QaClaimResult;
86
+ /** Session ids owned by the token's user, most recently claimed first. */
87
+ ownedSessionIds(token: string): readonly string[];
88
+ /**
89
+ * Every ownership entry with the owner's display name resolved at read
90
+ * time, oldest claim first. Admin-only: this is the cross-user view the
91
+ * admin sidebar groups chats by; ordinary accounts get a dedicated refusal.
92
+ */
93
+ listOwnership(token: string): readonly QaOwnershipEntry[];
94
+ /** Every account, file order; for `qa-accounts list`. */
95
+ listUsers(): readonly {
96
+ readonly email: string;
97
+ readonly displayName: string;
98
+ readonly role: QaAccountRole;
99
+ readonly disabled: boolean;
100
+ readonly createdAt: string;
101
+ readonly lastLoginAt: string | null;
102
+ }[];
103
+ /** Create an account outside the self-registration gate. */
104
+ addUser(email: string, password: string, options?: {
105
+ displayName?: string;
106
+ role?: QaAccountRole;
107
+ }): QaAccountUserPublic;
108
+ private editUser;
109
+ /** Grant or revoke the admin role. */
110
+ setUserRole(email: string, role: QaAccountRole): QaAccountUserPublic;
111
+ /**
112
+ * Disable an account: future logins are refused and every live token is
113
+ * invalidated (the token version bumps). `enable` does not resurrect old
114
+ * tokens — the account signs in again.
115
+ */
116
+ setUserDisabled(email: string, disabled: boolean): QaAccountUserPublic;
117
+ /** Invalidate every token of the account (password-leak response). */
118
+ revokeTokens(email: string): QaAccountUserPublic;
119
+ }
120
+ export {};
121
+ //# sourceMappingURL=store.d.ts.map
@@ -8,7 +8,7 @@
8
8
  * audience, precise enough for an operator reading the browser console to
9
9
  * know which class of mismatch to look for in the Host logs.
10
10
  */
11
- export type QaAttestationReason = "agent-unavailable" | "workspace-unavailable" | "composition-mismatch" | "permission-preset" | "adoption-refused" | "unknown-tools" | "attestation-failed";
11
+ export type QaAttestationReason = "agent-unavailable" | "workspace-unavailable" | "composition-mismatch" | "permission-preset" | "adoption-refused" | "unknown-tools" | "auth-required" | "session-owned-elsewhere" | "attestation-failed";
12
12
  /** Host rejection whose wire message carries the coarse reason marker. */
13
13
  export declare class QaAttestationError extends Error {
14
14
  readonly reason: QaAttestationReason;
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ export interface CliIo {
3
+ out(line: string): void;
4
+ err(line: string): void;
5
+ }
6
+ /** Execute one management command; returns the process exit code. */
7
+ export declare function run(argv: readonly string[], io: CliIo, readPassword?: () => string): number;
8
+ export declare function main(argv: readonly string[], io?: CliIo, readPassword?: () => string): number;
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1,95 @@
1
+ import type { QaAccountUserPublic, QaOwnershipEntry, ResolvedQaSurfaceConfig } from "../types.js";
2
+ import type { QaAccountsApi, StorageLike } from "./types.js";
3
+ export type QaAccountsSnapshot = {
4
+ readonly stage: "checking";
5
+ } | {
6
+ readonly stage: "gate";
7
+ readonly mode: "login" | "register";
8
+ readonly busy: boolean;
9
+ readonly error: string | null;
10
+ } | {
11
+ readonly stage: "authed";
12
+ readonly user: QaAccountUserPublic;
13
+ /**
14
+ * Server-known chat ids; the sidebar list authority. Own chats always,
15
+ * plus every mapped chat for an admin (the cross-user grouping view).
16
+ */
17
+ readonly ownedIds: readonly string[];
18
+ /**
19
+ * The full ownership map; admins only, empty for ordinary accounts and
20
+ * whenever the admin listing was refused or unavailable.
21
+ */
22
+ readonly ownership: readonly QaOwnershipEntry[];
23
+ /** Bumped whenever ownedIds or ownership changes; re-projects lists. */
24
+ readonly ownedRevision: number;
25
+ };
26
+ /**
27
+ * Audience-safe copy for the coarse account refusal codes; anything unknown
28
+ * falls back to the generic line.
29
+ */
30
+ export declare function accountsErrorMessage(code: string | null): string;
31
+ /** Extract the coarse account reason code from a Host wire failure. */
32
+ export declare function accountsReasonOf(failure: unknown): string | null;
33
+ export interface QaAccountsControllerOptions {
34
+ readonly remote: QaAccountsApi;
35
+ readonly storage: StorageLike | undefined;
36
+ /** Config snapshot getter; the token key follows the deployment identity. */
37
+ readonly config: () => ResolvedQaSurfaceConfig;
38
+ /**
39
+ * The legacy per-browser chat index at login time, for the one-shot
40
+ * ownership migration; conflicts are dropped through {@link forgetChat}.
41
+ */
42
+ readonly legacyChatIds?: () => readonly string[];
43
+ readonly forgetChat?: (sessionId: string) => void;
44
+ }
45
+ /**
46
+ * Browser-side account lifecycle: token persistence, the checking → gate →
47
+ * authed state machine, and the ownership bookkeeping the sidebar list is
48
+ * built from. Anonymous/failed identity never blocks a deployment with
49
+ * accounts disabled — the caller simply does not mount this controller.
50
+ */
51
+ export declare class QaAccountsController {
52
+ private readonly options;
53
+ private readonly listeners;
54
+ private snapshot;
55
+ private tokenValue;
56
+ private disposed;
57
+ constructor(options: QaAccountsControllerOptions);
58
+ getSnapshot: () => QaAccountsSnapshot;
59
+ subscribe: (listener: () => void) => (() => void);
60
+ dispose(): void;
61
+ /** The bearer token threaded into gated remotes; null while anonymous. */
62
+ /** The bearer token threaded into gated remotes; null while anonymous. */
63
+ token(): string | null;
64
+ /** Server-owned chat ids (empty until authed); falls back to nothing. */
65
+ ownedIds(): readonly string[];
66
+ /** Boot probe: restore the stored token and ask the Host who it is. */
67
+ start(): Promise<void>;
68
+ login(email: string, password: string): Promise<void>;
69
+ register(email: string, password: string): Promise<void>;
70
+ setMode(mode: "login" | "register"): void;
71
+ /** Drop the token and return to the gate (logout or expired identity). */
72
+ signOut(): void;
73
+ /** Claim one freshly created chat so the ownership map stays current. */
74
+ claimNewSession(sessionId: string): Promise<void>;
75
+ private authenticate;
76
+ /** Own the account, migrate the legacy index, then project authed. */
77
+ private enterSession;
78
+ /** Refresh the owned list after index changes; cheap enough to re-pull. */
79
+ refreshOwned(): Promise<void>;
80
+ /** The admin cross-user ownership view; failures degrade to an empty map. */
81
+ private fetchOwnership;
82
+ /** Session id → owner display name; empty unless an admin is signed in. */
83
+ ownerNames(): ReadonlyMap<string, string>;
84
+ /**
85
+ * The chat owner's display name for author labels: admins see it on
86
+ * foreign chats only; the owner themself and ordinary accounts see none.
87
+ */
88
+ messageAuthorOf(sessionId: string): string | undefined;
89
+ private readStoredToken;
90
+ private saveStoredToken;
91
+ private clearStoredToken;
92
+ private tokenKey;
93
+ private publish;
94
+ }
95
+ //# sourceMappingURL=QaAccountsController.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { SettingsScope } from "@deepseek-ai/dsh-client-runtime/client";
1
+ import type { SettingsScope } from "@deepseek-ai/dsh-client-ui-settings/client";
2
2
  import type { QaSurfaceConfig, ResolvedQaSurfaceConfig } from "../types.js";
3
3
  export interface QaConfigSnapshot {
4
4
  readonly status: "loading" | "ready" | "unavailable" | "error";
@@ -1,13 +1,33 @@
1
- import type { HostDescriptionSource } from "@deepseek-ai/dsh-client-connection/client";
1
+ import type { ConnectionGenerationState } from "@deepseek-ai/dsh-client-connection/client";
2
2
  import type { QaImageDraft, QaSessionState, ResolvedQaSurfaceConfig } from "../types.js";
3
- import type { QaSecureSession, QaSessions, QaSessionsApi, StorageLike } from "./types.js";
3
+ import type { QaConversation, QaSecureSession, QaSessions, QaSessionsApi, QaSourceApi, StorageLike } from "./types.js";
4
+ export interface QaAccountsFacade {
5
+ /** The account bearer token, or null while anonymous. */
6
+ readonly token: () => string | null;
7
+ /** Server-owned chat ids; the list authority while accounts are on. */
8
+ readonly ownedIds: () => readonly string[];
9
+ /**
10
+ * The chat owner's display name for author labels; admins see it on
11
+ * foreign chats only, everyone else gets undefined.
12
+ */
13
+ readonly messageAuthorOf: (sessionId: string) => string | undefined;
14
+ /** Called after a new chat binds, so ownership stays current. */
15
+ readonly onSessionCreated: (sessionId: string) => void;
16
+ /** Called when the Host refuses with auth-required (expired/rotated). */
17
+ readonly onAuthRequired: () => void;
18
+ }
4
19
  export interface QaSessionControllerOptions {
5
20
  readonly sessions: QaSessions;
6
21
  readonly api: QaSessionsApi;
7
- readonly connection: HostDescriptionSource;
22
+ /** Conversation assembly feeding the transcript projection. */
23
+ readonly conversation: QaConversation;
24
+ readonly connection: ConnectionGenerationState;
8
25
  readonly config: ResolvedQaSurfaceConfig;
9
26
  readonly secureSession: QaSecureSession;
27
+ readonly sourceApi?: QaSourceApi;
10
28
  readonly storage?: StorageLike;
29
+ /** Present while the deployment gates QA users with accounts. */
30
+ readonly accounts?: QaAccountsFacade;
11
31
  readonly timeoutMs?: number;
12
32
  /**
13
33
  * Minimum spacing between projections of a running turn's stream frames.
@@ -26,15 +46,22 @@ export declare class QaSessionController {
26
46
  private readonly listeners;
27
47
  private readonly sessions;
28
48
  private readonly api;
49
+ private readonly conversation;
29
50
  private readonly connection;
30
51
  private readonly config;
31
52
  private readonly secureSessionRemote;
53
+ private readonly sourceApi;
32
54
  private readonly chats;
55
+ private readonly accounts;
56
+ /** Per-chat attachment URL cache; blob URLs die with the chat binding. */
57
+ private readonly assets;
33
58
  private readonly timeoutMs;
34
59
  private readonly streamIntervalMs;
35
60
  private state;
36
61
  private session;
62
+ private conversationBinding;
37
63
  private unsubscribeSession;
64
+ private unsubscribeChat;
38
65
  private readonly unsubscribeConnection;
39
66
  private ensuring;
40
67
  private materializing;
@@ -50,6 +77,8 @@ export declare class QaSessionController {
50
77
  private disposed;
51
78
  private generation;
52
79
  private chatsRevision;
80
+ /** Host-side provenance of the bound chat, merged into the projection. */
81
+ private readonly hostSources;
53
82
  constructor(options: QaSessionControllerOptions);
54
83
  getSnapshot: () => QaSessionState;
55
84
  subscribe: (listener: () => void) => (() => void);
@@ -93,15 +122,19 @@ export declare class QaSessionController {
93
122
  viewSubagent(id: string, title: string): Promise<void>;
94
123
  /** Leave the subagent transcript and re-open the chat it was launched from. */
95
124
  closeSubagent(): Promise<void>;
96
- /** This browser's indexed chat ids, most recently used first. */
125
+ /** This browser's chat ids: the owned list when accounts are on, else the
126
+ * local index. Both intersect the host session list at projection time. */
97
127
  chatIds(): readonly string[];
98
128
  /** The bound session id, or null while no chat is bound. */
99
129
  activeSessionId(): string | null;
100
130
  /**
101
131
  * Resolve one durable image attachment of the bound session into a
102
- * browser-usable URL. The caller caches; failures surface as broken views.
132
+ * browser-usable URL. Resolutions are cached per chat in the asset
133
+ * repository (attachment ids repeat across chats) and revoked when the chat
134
+ * is unbound; failures surface as broken views and retry on demand.
103
135
  */
104
136
  readImage(attachmentId: string): Promise<string>;
137
+ private readAttachmentUrl;
105
138
  /**
106
139
  * Remove one chat from this browser's index. Deleting the chat that is
107
140
  * currently open also forgets the persisted id and starts a fresh attested
@@ -120,10 +153,10 @@ export declare class QaSessionController {
120
153
  * re-opening re-materializes it. Step 2 — when that refused session is
121
154
  * still blank (it never received the prompt, so there is nothing to lose) —
122
155
  * starts a fresh attested session instead. A session with history is left
123
- * alone: recreating it would silently drop the conversation.
156
+ * alone: recreating it would silently drop the conversation. Returns the
157
+ * attested session id, or null when nothing was attested.
124
158
  */
125
159
  private recoverForSend;
126
- private createSession;
127
160
  private bind;
128
161
  private unbind;
129
162
  private waitForConnection;
@@ -140,6 +173,12 @@ export declare class QaSessionController {
140
173
  private clearStreamFlush;
141
174
  private publish;
142
175
  private fail;
176
+ /**
177
+ * Attest the currently bound session. `ok` carries the session id the proof
178
+ * was issued for; `stale` means the binding changed while the request was
179
+ * in flight, so nothing was written — the newer binding manages its own
180
+ * attestation; `refused` is a genuine admission refusal of this binding.
181
+ */
143
182
  private attestPolicy;
144
183
  private emit;
145
184
  }
@@ -1,15 +1,20 @@
1
- import type { HostDescriptionSource } from "@deepseek-ai/dsh-client-connection/client";
1
+ import type { ConnectionGenerationState } from "@deepseek-ai/dsh-client-connection/client";
2
2
  import type { InjectFace, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
3
3
  import type { QaConfigController } from "./QaConfigController.js";
4
4
  import type { QaRouteController } from "./QaRouteController.js";
5
- import type { QaSecureSession, QaSessions, QaSessionsApi } from "./types.js";
5
+ import type { QaAccountsController } from "./QaAccountsController.js";
6
+ import type { QaConversation, QaSecureSession, QaSessions, QaSessionsApi, QaSourceApi } from "./types.js";
6
7
  export interface QaSurfaceFace {
7
8
  readonly route: QaRouteController;
8
9
  readonly config: QaConfigController;
9
10
  readonly sessions: QaSessions;
11
+ readonly conversation: QaConversation;
10
12
  readonly api: QaSessionsApi;
11
- readonly connection: HostDescriptionSource;
13
+ readonly connection: ConnectionGenerationState;
12
14
  readonly secureSession: QaSecureSession;
15
+ readonly sourceApi: QaSourceApi;
16
+ /** Present when the deployment mounts the QA account gate. */
17
+ readonly accounts?: QaAccountsController;
13
18
  }
14
19
  type QaSurfaceProps = PropsRuntime<"shell.overlay"> & InjectFace<QaSurfaceFace>;
15
20
  export declare function QaSurface(props: QaSurfaceProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,5 +1,5 @@
1
- import type { ConversationSnapshot } from "@deepseek-ai/dsh-client-runtime/client";
2
- import type { QaMessage, QaSource } from "../types.js";
1
+ import type { ConversationSnapshot } from "@deepseek-ai/dsh-client-ui-conversation/client";
2
+ import type { QaMessage } from "../types.js";
3
3
  /**
4
4
  * The hidden regeneration prompt. Regenerate sends this as an ordinary
5
5
  * prompt — the session has no truncation seam, so a "variant" is a real
@@ -8,15 +8,10 @@ import type { QaMessage, QaSource } from "../types.js";
8
8
  */
9
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
10
  /** Project end-user messages plus optional operator-approved work detail. */
11
- export declare function projectTranscript(snapshot: ConversationSnapshot, options?: {
11
+ export declare function projectTranscript(snapshot: ConversationSnapshot | undefined, options?: {
12
+ /** Live turn lifecycle from the bound Session (drives running-row status). */
13
+ readonly running?: boolean;
12
14
  readonly showToolActivity?: boolean;
13
15
  readonly showReasoning?: boolean;
14
16
  }): 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[];
22
17
  //# sourceMappingURL=QaTranscriptAdapter.d.ts.map
@@ -1,9 +1,19 @@
1
+ import type { QaSource } from "../../types.js";
2
+ import { type QaSourceRefs } from "./source-refs.js";
3
+ /** Inline source awareness handed down from the owning assistant message. */
4
+ export interface MarkdownSourceContext {
5
+ readonly refs?: QaSourceRefs;
6
+ /** Open a path-backed source in the sources drawer's detail view. */
7
+ readonly onSourceOpen?: (source: QaSource) => void;
8
+ }
1
9
  /**
2
10
  * Deliberately small, HTML-free Markdown renderer for assistant-visible text.
3
11
  * Memoized on the text value: stream updates re-render only the message whose
4
12
  * text actually changed, so committed history is never re-parsed.
5
13
  */
6
- export declare const Markdown: import("react").MemoExoticComponent<({ text, }: {
14
+ export declare const Markdown: import("react").MemoExoticComponent<({ text, sourceRefs, onSourceOpen, }: {
7
15
  readonly text: string;
16
+ readonly sourceRefs?: QaSourceRefs;
17
+ readonly onSourceOpen?: (source: QaSource) => void;
8
18
  }) => import("react/jsx-runtime").JSX.Element>;
9
19
  //# sourceMappingURL=Markdown.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { SessionSummary } from "@deepseek-ai/dsh-client-runtime/client";
1
+ import type { SessionSummary } from "@deepseek-ai/dsh-api-session-controller/client";
2
2
  /** One row of the agents panel: a subagent session of the open chat. */
3
3
  export interface QaSubagentRow {
4
4
  readonly id: string;
@@ -0,0 +1,15 @@
1
+ import type { QaAccountsController, QaAccountsSnapshot } from "../QaAccountsController.js";
2
+ export interface QaAuthGateProps {
3
+ readonly accounts: QaAccountsController;
4
+ readonly snapshot: QaAccountsSnapshot;
5
+ readonly title: string;
6
+ readonly logoUrl: string | null;
7
+ readonly allowRegistration: boolean;
8
+ }
9
+ /**
10
+ * The full-frame login/registration card shown instead of the chat while the
11
+ * deployment has accounts enabled and the browser holds no valid identity.
12
+ * Errors are the Host's coarse codes rendered as audience-safe copy.
13
+ */
14
+ export declare function QaAuthGate(props: QaAuthGateProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=QaAuthGate.d.ts.map
@@ -0,0 +1,21 @@
1
+ export declare const QA_VERSION: string;
2
+ export interface QaChangelogEntry {
3
+ readonly version: string;
4
+ readonly date: string;
5
+ readonly sections: readonly {
6
+ readonly title: string;
7
+ readonly items: readonly string[];
8
+ }[];
9
+ }
10
+ export declare const QA_CHANGELOG: readonly QaChangelogEntry[];
11
+ /** Props of the {@link QaChangelogModal} dialog. */
12
+ export interface QaChangelogModalProps {
13
+ readonly open: boolean;
14
+ readonly onClose: () => void;
15
+ }
16
+ /**
17
+ * The version/changelog dialog opened from the sidebar footer. Rendering is
18
+ * a no-op while closed; Escape and a backdrop click close it.
19
+ */
20
+ export declare function QaChangelogModal(props: QaChangelogModalProps): import("react/jsx-runtime").JSX.Element | null;
21
+ //# sourceMappingURL=QaChangelog.d.ts.map
@@ -1,14 +1,21 @@
1
- import type { QaMessage as QaMessageModel } from "../../types.js";
1
+ import type { QaMessage as QaMessageModel, QaSource, QaTurnSources } from "../../types.js";
2
2
  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. */
6
+ /**
7
+ * Storage prefix persisted message ratings live under. Callers scope it to
8
+ * the chat: message ids repeat across chats (`assistant:<seq>`).
9
+ */
7
10
  readonly stateKey?: string;
8
11
  /** Ask for a fresh variant of this answer; omit to hide the control. */
9
12
  readonly onRegenerate?: () => void;
10
13
  /** Resolve one durable attachment into a viewable URL. */
11
14
  readonly resolveImage?: (attachmentId: string) => Promise<string>;
15
+ /** Open the drawer at the exact canonical snapshot shown in this footer. */
16
+ readonly onOpenSources?: (sources: readonly QaSource[], complete: boolean, incompleteOrigins: QaTurnSources["incompleteOrigins"]) => void;
17
+ /** Open one path-backed source's detail (an inline footnote click). */
18
+ readonly onSourceDetail?: (source: QaSource) => void;
12
19
  }
13
20
  /**
14
21
  * Whether two transcript messages render identically. The transcript
@@ -17,5 +24,5 @@ export interface QaMessageProps {
17
24
  * comparison is by value, which keeps fresh copies cheap to recognize.
18
25
  */
19
26
  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>;
27
+ export declare const QaMessage: import("react").MemoExoticComponent<({ message, renderMarkdown, showTimestamp, stateKey, onRegenerate, resolveImage, onOpenSources, onSourceDetail, }: QaMessageProps) => import("react/jsx-runtime").JSX.Element>;
21
28
  //# sourceMappingURL=QaMessage.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { SessionSummary } from "@deepseek-ai/dsh-client-runtime/client";
1
+ import type { SessionSummary } from "@deepseek-ai/dsh-api-session-controller/client";
2
2
  /** One renderable row of the chat-history sidebar. */
3
3
  export interface QaChatRow {
4
4
  readonly id: string;
@@ -7,6 +7,8 @@ export interface QaChatRow {
7
7
  readonly active: boolean;
8
8
  readonly meta: string;
9
9
  readonly updatedAt: number;
10
+ /** Chat owner's display name; present in the admin ownership view only. */
11
+ readonly ownerName?: string;
10
12
  }
11
13
  /**
12
14
  * Project this browser's indexed chat ids onto the host session list, most
@@ -14,9 +16,29 @@ export interface QaChatRow {
14
16
  * `updatedAt` (fresh messages) orders the list. Ids the host no longer lists
15
17
  * are skipped (the controller prunes them on switch).
16
18
  */
17
- export declare function buildChatRows(chatIds: readonly string[], byId: Readonly<Record<string, SessionSummary>>, activeId: string | null, now?: number): readonly QaChatRow[];
19
+ export declare function buildChatRows(chatIds: readonly string[], byId: Readonly<Record<string, SessionSummary>>, activeId: string | null, ownerNameOf?: (sessionId: string) => string | undefined, now?: number): readonly QaChatRow[];
20
+ /** One per-owner section of the admin sidebar grouping. */
21
+ export interface QaSidebarSection {
22
+ /**
23
+ * Section header; chats nobody claimed group under "Без владельца". An
24
+ * empty name renders no header (the flat, non-grouped mode).
25
+ */
26
+ readonly name: string;
27
+ readonly rows: readonly QaChatRow[];
28
+ }
29
+ /**
30
+ * Group rows into per-owner sections for the admin view. Sections order by
31
+ * their freshest row so the busiest users float up; unclaimed chats go last.
32
+ * Row order inside a section is already the global updatedAt order.
33
+ */
34
+ export declare function buildOwnerSections(rows: readonly QaChatRow[]): readonly QaSidebarSection[];
18
35
  export interface QaSidebarProps {
19
36
  readonly rows: readonly QaChatRow[];
37
+ /**
38
+ * Render per-owner sections instead of the flat list; the admin ownership
39
+ * view. Rows without an owner name fall into a trailing section.
40
+ */
41
+ readonly groupByOwner?: boolean;
20
42
  /** Deployment brand shown next to the logo (also while collapsed). */
21
43
  readonly title: string;
22
44
  /** Optional image logo; a built-in mark is drawn when null. */
@@ -29,6 +51,12 @@ export interface QaSidebarProps {
29
51
  readonly onNewChat: () => void;
30
52
  /** Removes a chat from this browser's index; omit to hide the control. */
31
53
  readonly onDelete?: (sessionId: string) => void;
54
+ /** The signed-in account; omit when accounts are disabled. */
55
+ readonly account?: {
56
+ readonly email: string;
57
+ readonly role: string;
58
+ readonly onLogout: () => void;
59
+ };
32
60
  }
33
61
  /**
34
62
  * Whether two sidebar row lists show the same thing. Rows are rebuilt on
@@ -1,8 +1,20 @@
1
- import type { QaSource } from "../../types.js";
1
+ import type { QaSource, QaTurnSources, ResolvedQaSurfaceConfig } from "../../types.js";
2
+ import type { QaBoundSourceApi } from "../types.js";
3
+ export declare function SourceIcon({ kind }: {
4
+ readonly kind: QaSource["kind"];
5
+ }): import("react/jsx-runtime").JSX.Element;
2
6
  export interface QaSourcesDrawerProps {
3
7
  readonly sources: readonly QaSource[];
8
+ readonly complete: boolean;
9
+ readonly incompleteOrigins?: QaTurnSources["incompleteOrigins"];
10
+ readonly sessionId: string | null;
11
+ readonly sourceApi: QaBoundSourceApi;
12
+ readonly display: ResolvedQaSurfaceConfig["sources"]["display"];
13
+ readonly filePreview: ResolvedQaSurfaceConfig["sources"]["filePreview"];
14
+ /** Open straight onto this source's detail (an inline footnote click). */
15
+ readonly initialDetail?: QaSource | null;
4
16
  readonly onClose: () => void;
5
17
  }
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;
18
+ /** Canonical grouped source panel and safe local-file preview. */
19
+ export declare function QaSourcesDrawer({ sources, complete, incompleteOrigins, sessionId, sourceApi, display, filePreview, initialDetail, onClose, }: QaSourcesDrawerProps): import("react/jsx-runtime").JSX.Element;
8
20
  //# sourceMappingURL=QaSourcesDrawer.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { type RefObject } from "react";
2
+ import type { QaMessage } from "../../types.js";
3
+ /**
4
+ * One mark of the turn rail: the user prompt that opens the turn, a bounded
5
+ * answer preview for the hover tooltip, and the message id that anchors the
6
+ * jump (the prompt's message slot in the transcript).
7
+ */
8
+ export interface QaTurnRailItem {
9
+ /** 1-based position in the visible transcript; also the React key. */
10
+ readonly turn: number;
11
+ /** The user message id that opens the turn (scroll anchor). */
12
+ readonly id: string;
13
+ readonly prompt: string;
14
+ readonly response: string;
15
+ }
16
+ /** Transcript attribute marking the message slot that opens a turn. */
17
+ export declare const QA_TURN_ANCHOR_ATTRIBUTE = "data-dsh-qa-turn-anchor";
18
+ /** Scroll distance from the floor at which the rail still calls it "at bottom". */
19
+ export declare const QA_TURN_FOLLOW_PX = 96;
20
+ /** The reading line: reading offset from the scrollport top while scrolling. */
21
+ export declare const QA_TURN_READING_PX = 96;
22
+ /**
23
+ * Rail ladder of the visible transcript: every user prompt opens a turn, the
24
+ * first non-empty answer after it fills the response preview. Hidden variant
25
+ * answers never reach this list because callers pass the already-filtered
26
+ * messages.
27
+ */
28
+ export declare function buildTurnRailItems(messages: readonly QaMessage[]): readonly QaTurnRailItem[];
29
+ /** Deep equality guard so the memoized rail skips streaming commits. */
30
+ export declare function sameTurnRailItems(left: readonly QaTurnRailItem[], right: readonly QaTurnRailItem[]): boolean;
31
+ /**
32
+ * Turn owning the transcript's reading line, or the newest one while the
33
+ * reader follows the live tail. Anchors above the line are scanned in flow
34
+ * order; the last one at or above it owns the mark.
35
+ */
36
+ export declare function computeActiveTurn(scroller: HTMLElement, items: readonly QaTurnRailItem[]): number | null;
37
+ export interface QaTurnRailProps {
38
+ readonly items: readonly QaTurnRailItem[];
39
+ readonly activeTurn: number | null;
40
+ /** Turn still generating; its mark pulses. */
41
+ readonly busyTurn: number | null;
42
+ /** The transcript scroller the rail floats over and measures its band from. */
43
+ readonly scrollerRef: RefObject<HTMLDivElement | null>;
44
+ readonly onNavigate: (item: QaTurnRailItem) => void;
45
+ }
46
+ declare function QaTurnRailView({ items, activeTurn, busyTurn, scrollerRef, onNavigate, }: QaTurnRailProps): import("react/jsx-runtime").JSX.Element | null;
47
+ /**
48
+ * Fixed-pitch rail of every turn of the visible transcript, floating over the
49
+ * transcript's right gutter: the active mark follows the reading line, a
50
+ * hover/focus preview shows the prompt and answer, and a click lands the
51
+ * transcript on that turn. Memoized like the harness rail — the transcript
52
+ * re-renders on every streaming delta, and the rail only changes when a turn
53
+ * is added or becomes active.
54
+ */
55
+ export declare const QaTurnRail: import("react").MemoExoticComponent<typeof QaTurnRailView>;
56
+ /** Land the transcript on a turn's prompt row, harness-style. */
57
+ export declare function turnScrollTarget(scroller: HTMLElement, row: HTMLElement): number;
58
+ export {};
59
+ //# sourceMappingURL=QaTurnRail.d.ts.map