@yadsh/dsh-qa-surface 0.8.0 → 0.9.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 (157) hide show
  1. package/README.md +113 -3
  2. package/compatibility.json +5 -0
  3. package/lib/access/capability-catalog.js +32 -3
  4. package/lib/access/capability-catalog.js.map +1 -1
  5. package/lib/access/service.js +45 -17
  6. package/lib/access/service.js.map +1 -1
  7. package/lib/accounts/errors.js.map +1 -1
  8. package/lib/accounts/store.js +14 -0
  9. package/lib/accounts/store.js.map +1 -1
  10. package/lib/admin/permissions.js +4 -0
  11. package/lib/admin/permissions.js.map +1 -1
  12. package/lib/admin/quality-store.js +71 -0
  13. package/lib/admin/quality-store.js.map +1 -1
  14. package/lib/admin/service.js +106 -1
  15. package/lib/admin/service.js.map +1 -1
  16. package/lib/admin/session-log.js +66 -5
  17. package/lib/admin/session-log.js.map +1 -1
  18. package/lib/client/QaSessionController.js +359 -37
  19. package/lib/client/QaSessionController.js.map +1 -1
  20. package/lib/client/QaSurface.js +26 -4
  21. package/lib/client/QaSurface.js.map +1 -1
  22. package/lib/client/QaTranscriptAdapter.js +36 -0
  23. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  24. package/lib/client/admin/QaAdmin.js +9 -1
  25. package/lib/client/admin/QaAdmin.js.map +1 -1
  26. package/lib/client/admin/pages/Conversations.js +33 -7
  27. package/lib/client/admin/pages/Conversations.js.map +1 -1
  28. package/lib/client/admin/shared.js +9 -0
  29. package/lib/client/admin/shared.js.map +1 -1
  30. package/lib/client/admin/styles.js +2 -0
  31. package/lib/client/admin/styles.js.map +1 -1
  32. package/lib/client/audit/QaAuditDialog.js +74 -0
  33. package/lib/client/audit/QaAuditDialog.js.map +1 -0
  34. package/lib/client/audit/QaAuditRowBadge.js +17 -0
  35. package/lib/client/audit/QaAuditRowBadge.js.map +1 -0
  36. package/lib/client/audit/controller.js +39 -0
  37. package/lib/client/audit/controller.js.map +1 -0
  38. package/lib/client/audit/types.js +36 -0
  39. package/lib/client/audit/types.js.map +1 -0
  40. package/lib/client/audit/use-chat-audits.js +139 -0
  41. package/lib/client/audit/use-chat-audits.js.map +1 -0
  42. package/lib/client/components/QaChangelog.js +43 -0
  43. package/lib/client/components/QaChangelog.js.map +1 -1
  44. package/lib/client/components/QaComposer.js +63 -3
  45. package/lib/client/components/QaComposer.js.map +1 -1
  46. package/lib/client/components/QaMessage.js +16 -0
  47. package/lib/client/components/QaMessage.js.map +1 -1
  48. package/lib/client/components/QaQuestions.js +1 -1
  49. package/lib/client/components/QaQuestions.js.map +1 -1
  50. package/lib/client/components/QaSidebar.js +12 -1
  51. package/lib/client/components/QaSidebar.js.map +1 -1
  52. package/lib/client/index.js +22 -2
  53. package/lib/client/index.js.map +1 -1
  54. package/lib/client/project-session-state.js +15 -2
  55. package/lib/client/project-session-state.js.map +1 -1
  56. package/lib/client/prompt-content.js +26 -0
  57. package/lib/client/prompt-content.js.map +1 -1
  58. package/lib/client/settings/card.js +2 -2
  59. package/lib/client/settings/card.js.map +1 -1
  60. package/lib/client/settings/sections/lockdown.js +1 -6
  61. package/lib/client/settings/sections/lockdown.js.map +1 -1
  62. package/lib/client/settings/sections/slash.js +57 -0
  63. package/lib/client/settings/sections/slash.js.map +1 -0
  64. package/lib/client/settings/sections/status.js +9 -1
  65. package/lib/client/settings/sections/status.js.map +1 -1
  66. package/lib/client/settings/sections.js +1 -0
  67. package/lib/client/settings/sections.js.map +1 -1
  68. package/lib/client/slash/SlashPalette.js +48 -0
  69. package/lib/client/slash/SlashPalette.js.map +1 -0
  70. package/lib/client/slash/copy.js +54 -0
  71. package/lib/client/slash/copy.js.map +1 -0
  72. package/lib/client/slash/slash-router.js +49 -0
  73. package/lib/client/slash/slash-router.js.map +1 -0
  74. package/lib/client/slash/useSlashNavigation.js +95 -0
  75. package/lib/client/slash/useSlashNavigation.js.map +1 -0
  76. package/lib/client/styles.js +51 -1
  77. package/lib/client/styles.js.map +1 -1
  78. package/lib/client/types.js +11 -0
  79. package/lib/client/types.js.map +1 -1
  80. package/lib/client.js +4231 -551
  81. package/lib/client.js.map +1 -1
  82. package/lib/config-resolvers/basics.js +11 -2
  83. package/lib/config-resolvers/basics.js.map +1 -1
  84. package/lib/config-resolvers/defaults.js +22 -0
  85. package/lib/config-resolvers/defaults.js.map +1 -1
  86. package/lib/config-resolvers/lockdown.js +8 -2
  87. package/lib/config-resolvers/lockdown.js.map +1 -1
  88. package/lib/config-resolvers/slash-commands.js +60 -0
  89. package/lib/config-resolvers/slash-commands.js.map +1 -0
  90. package/lib/config.js +61 -2
  91. package/lib/config.js.map +1 -1
  92. package/lib/index.js +158 -1
  93. package/lib/index.js.map +1 -1
  94. package/lib/personal-skills/service.js +16 -3
  95. package/lib/personal-skills/service.js.map +1 -1
  96. package/lib/provenance/host-store.js +18 -1
  97. package/lib/provenance/host-store.js.map +1 -1
  98. package/lib/questions.js +90 -43
  99. package/lib/questions.js.map +1 -1
  100. package/lib/resolve-config.js +5 -0
  101. package/lib/resolve-config.js.map +1 -1
  102. package/lib/secure-session.js +24 -0
  103. package/lib/secure-session.js.map +1 -1
  104. package/lib/slash/catalog.js +77 -0
  105. package/lib/slash/catalog.js.map +1 -0
  106. package/lib/slash/matcher.js +100 -0
  107. package/lib/slash/matcher.js.map +1 -0
  108. package/lib/slash/parser.js +79 -0
  109. package/lib/slash/parser.js.map +1 -0
  110. package/lib/slash/policy.js +72 -0
  111. package/lib/slash/policy.js.map +1 -0
  112. package/lib/slash/remotes.js +272 -0
  113. package/lib/slash/remotes.js.map +1 -0
  114. package/lib/typert.host.js +318 -53
  115. package/lib/typert.remote-client.d.ts +7 -1
  116. package/lib/typert.remote-client.js +247 -51
  117. package/lib/types/access/capability-catalog.d.ts +28 -1
  118. package/lib/types/access/service.d.ts +33 -4
  119. package/lib/types/accounts/errors.d.ts +7 -1
  120. package/lib/types/accounts/store.d.ts +12 -0
  121. package/lib/types/admin/quality-store.d.ts +18 -0
  122. package/lib/types/admin/service.d.ts +34 -2
  123. package/lib/types/admin/session-log.d.ts +65 -1
  124. package/lib/types/client/QaSessionController.d.ts +84 -2
  125. package/lib/types/client/QaSurface.d.ts +14 -1
  126. package/lib/types/client/admin/pages/Conversations.d.ts +4 -0
  127. package/lib/types/client/audit/QaAuditDialog.d.ts +21 -0
  128. package/lib/types/client/audit/QaAuditRowBadge.d.ts +18 -0
  129. package/lib/types/client/audit/controller.d.ts +27 -0
  130. package/lib/types/client/audit/types.d.ts +72 -0
  131. package/lib/types/client/audit/use-chat-audits.d.ts +20 -0
  132. package/lib/types/client/components/QaComposer.d.ts +12 -2
  133. package/lib/types/client/components/QaQuestions.d.ts +7 -0
  134. package/lib/types/client/components/QaSidebar.d.ts +8 -0
  135. package/lib/types/client/project-session-state.d.ts +3 -1
  136. package/lib/types/client/prompt-content.d.ts +9 -1
  137. package/lib/types/client/settings/sections/slash.d.ts +16 -0
  138. package/lib/types/client/settings/sections.d.ts +1 -0
  139. package/lib/types/client/slash/SlashPalette.d.ts +24 -0
  140. package/lib/types/client/slash/copy.d.ts +30 -0
  141. package/lib/types/client/slash/slash-router.d.ts +67 -0
  142. package/lib/types/client/slash/useSlashNavigation.d.ts +47 -0
  143. package/lib/types/client/styles.d.ts +9 -0
  144. package/lib/types/client/types.d.ts +16 -1
  145. package/lib/types/config-resolvers/slash-commands.d.ts +20 -0
  146. package/lib/types/index.d.ts +78 -1
  147. package/lib/types/personal-skills/service.d.ts +5 -0
  148. package/lib/types/provenance/host-store.d.ts +13 -0
  149. package/lib/types/questions.d.ts +8 -0
  150. package/lib/types/slash/catalog.d.ts +56 -0
  151. package/lib/types/slash/matcher.d.ts +51 -0
  152. package/lib/types/slash/parser.d.ts +46 -0
  153. package/lib/types/slash/policy.d.ts +40 -0
  154. package/lib/types/slash/remotes.d.ts +35 -0
  155. package/lib/types/types.d.ts +200 -5
  156. package/package.json +7 -3
  157. package/scripts/attach-workspace-sessions.mjs +472 -0
@@ -0,0 +1,20 @@
1
+ import type { QaAuditApi, QaSessionAudit, QaAuditSummary } from "./types.js";
2
+ /** How often the chat list re-asks which chats have an audit. */
3
+ export declare const AUDIT_POLL_MS = 30000;
4
+ /**
5
+ * Poll the audit summary for every chat id.
6
+ *
7
+ * @param api - the Remote facade, or `null` when the audit plugin is absent.
8
+ * @param sessionIds - the chats currently listed.
9
+ */
10
+ export declare function useChatAudits(api: QaAuditApi | null, sessionIds: readonly string[]): ReadonlyMap<string, QaAuditSummary>;
11
+ /** The dialog's own load: one audit's documents, fetched when it opens. */
12
+ export interface QaAuditLoad {
13
+ readonly loading: boolean;
14
+ readonly audit: QaSessionAudit | null;
15
+ readonly error: string | null;
16
+ }
17
+ export declare function useSessionAuditDocument(api: QaAuditApi | null, sessionId: string | null, open: boolean,
18
+ /** Reload key; the summary's `modifiedAt` changes exactly when bytes do. */
19
+ modifiedAt: string | null): QaAuditLoad;
20
+ //# sourceMappingURL=use-chat-audits.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { QaAttachmentDraft } from "../../types.js";
1
+ import type { QaAttachmentDraft, QaSlashView, ResolvedQaSlashCommands } from "../../types.js";
2
2
  import { type QaAttachmentLimits } from "../attachments.js";
3
3
  import type { QaQuickQuestion } from "../types.js";
4
4
  export interface QaComposerProps {
@@ -13,8 +13,18 @@ export interface QaComposerProps {
13
13
  readonly attachments: readonly QaAttachmentDraft[];
14
14
  /** Deployment policy: accepted kinds, ceilings and the paste threshold. */
15
15
  readonly limits: QaAttachmentLimits;
16
+ /** Slash catalog of this chat; inert while the master switch is off. */
17
+ readonly slash: QaSlashView;
18
+ readonly slashPolicy: ResolvedQaSlashCommands["palette"];
16
19
  readonly onAttachmentsChange: (attachments: readonly QaAttachmentDraft[]) => void;
17
- readonly onSend: (text: string, attachments: readonly QaAttachmentDraft[]) => Promise<boolean>;
20
+ /**
21
+ * `pick` names the entry the user chose in the palette, while the draft
22
+ * still reads as its invocation. It is what disambiguates a skill and a
23
+ * command that share a name; `null` means the draft alone decides.
24
+ */
25
+ readonly onSend: (text: string, attachments: readonly QaAttachmentDraft[], pick: string | null) => Promise<boolean>;
26
+ /** Called when the palette opens, so the caller can revalidate the catalog. */
27
+ readonly onSlashOpen: () => void;
18
28
  readonly onStop: () => Promise<void>;
19
29
  }
20
30
  /**
@@ -3,6 +3,13 @@ export interface QaQuestionsProps {
3
3
  readonly questions: readonly QaPendingQuestion[];
4
4
  readonly onAnswer: (requestId: string, answers: readonly QaQuestionAnswerItem[]) => Promise<void>;
5
5
  readonly onCancel: (requestId: string) => Promise<void>;
6
+ /**
7
+ * Stop the turn the questions belong to. The form takes the composer's
8
+ * place, so the run's own stop action moves here: an operator who does not
9
+ * want to answer must still be able to end the turn instead.
10
+ */
11
+ readonly onStop?: () => Promise<void>;
12
+ readonly canStop?: boolean;
6
13
  }
7
14
  /**
8
15
  * The questions a running turn waits on. Answers are the operator's own: the
@@ -1,4 +1,5 @@
1
1
  import type { SessionSummary } from "@deepseek-ai/dsh-api-session-controller/client";
2
+ import { type QaAuditSummary } from "../audit/types.js";
2
3
  /** One renderable row of the chat-history sidebar. */
3
4
  export interface QaChatRow {
4
5
  readonly id: string;
@@ -53,6 +54,13 @@ export interface QaSidebarProps {
53
54
  readonly onNewChat: () => void;
54
55
  /** Removes a chat from this browser's index; omit to hide the control. */
55
56
  readonly onDelete?: (sessionId: string) => void;
57
+ /**
58
+ * Audit summaries by chat id, for the row badge. Empty when the audit plugin
59
+ * is not installed — the badge is then absent rather than empty.
60
+ */
61
+ readonly audits?: ReadonlyMap<string, QaAuditSummary>;
62
+ /** Opens the audit dialog for a chat; omit when audits are unavailable. */
63
+ readonly onAudit?: (sessionId: string) => void;
56
64
  /** The signed-in account; omit when accounts are disabled. */
57
65
  readonly account?: {
58
66
  readonly email: string;
@@ -1,6 +1,6 @@
1
1
  import type { SessionFace } from "@deepseek-ai/dsh-api-session-controller/client";
2
2
  import type { ConversationSnapshot } from "@deepseek-ai/dsh-client-ui-conversation/client";
3
- import type { QaPendingApproval, QaPendingQuestion, QaSessionState, QaSubagentView, QaTurnSources, ResolvedQaSurfaceConfig } from "../types.js";
3
+ import type { QaPendingApproval, QaPendingQuestion, QaSessionState, QaSlashView, QaSubagentView, QaTurnSources, ResolvedQaSurfaceConfig } from "../types.js";
4
4
  /** Everything the bound-chat projection reads; the computation is pure. */
5
5
  export interface QaBoundProjectionInput {
6
6
  readonly connected: boolean;
@@ -23,6 +23,8 @@ export interface QaBoundProjectionInput {
23
23
  readonly admissionPending: boolean;
24
24
  readonly chatsRevision: number;
25
25
  readonly viewingSubagent: QaSubagentView | null;
26
+ /** Slash catalog state of this chat; the controller owns its lifetime. */
27
+ readonly slash: QaSlashView;
26
28
  readonly config: ResolvedQaSurfaceConfig;
27
29
  /**
28
30
  * The chat's subagent display names keyed by session id, read from the
@@ -1,5 +1,5 @@
1
1
  import type { SessionFace } from "@deepseek-ai/dsh-api-session-controller/client";
2
- import type { QaAttachmentDraft, QaFileDraft } from "../types.js";
2
+ import type { QaAttachmentDraft, QaFileDraft, QaSlashSubmitAttachment } from "../types.js";
3
3
  import type { QaFileUpload, QaPromptContent } from "./types.js";
4
4
  /**
5
5
  * Stage every pending file on the Host and collect the receipts the prompt
@@ -26,5 +26,13 @@ export declare function stageQaFiles(service: QaFileUpload | undefined, session:
26
26
  * staging failed elsewhere) degrades to an empty receipt rather than
27
27
  * dropping the attachment.
28
28
  */
29
+ /**
30
+ * The same two shapes for a human command, which admits attachments through
31
+ * the shared store rather than through the prompt: an image rides encoded, a
32
+ * file rides the receipt its staging minted. An attachment whose staging
33
+ * failed is dropped instead of being sent as an empty receipt — the command
34
+ * would otherwise read a handle that resolves to nothing.
35
+ */
36
+ export declare function buildQaSlashAttachments(attachments: readonly QaAttachmentDraft[], receipts: ReadonlyMap<string, string>): readonly QaSlashSubmitAttachment[];
29
37
  export declare function buildQaPromptContent(prompt: string, attachments: readonly QaAttachmentDraft[], receipts: ReadonlyMap<string, string>): QaPromptContent;
30
38
  //# sourceMappingURL=prompt-content.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Slash interface: the master switch, the two admission policies and the
3
+ * palette itself.
4
+ *
5
+ * The allow lists stay name-based, which is what the deployment config stores
6
+ * — an operator writes `generate-tkp`, never an id. They are plain lists
7
+ * rather than checkboxes over a discovered catalog because the two halves are
8
+ * discovered differently and only one of them can be enumerated here at all:
9
+ * skills are a registry lookup, while a command is only resolvable against a
10
+ * live chat's agent, so this card could not honestly offer a fixed list of
11
+ * command names to tick.
12
+ */
13
+ import { type ConfigProps } from "./common.js";
14
+ /** Master switch, per-kind admission policy and palette presentation. */
15
+ export declare function SlashSection(props: ConfigProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=slash.d.ts.map
@@ -19,6 +19,7 @@ export { EmbeddingSection } from "./sections/embedding.js";
19
19
  export { InterfaceSection } from "./sections/interface.js";
20
20
  export { LockdownSection } from "./sections/lockdown.js";
21
21
  export { SessionSection } from "./sections/session.js";
22
+ export { SlashSection } from "./sections/slash.js";
22
23
  export { SourcesSection } from "./sections/sources.js";
23
24
  export { StatusSection } from "./sections/status.js";
24
25
  export type { StatusProps } from "./sections/status.js";
@@ -0,0 +1,24 @@
1
+ import type { QaSlashCatalogEntry, QaSlashCommandSurface, QaSlashView } from "../../types.js";
2
+ export interface QaSlashPaletteProps {
3
+ readonly rows: readonly QaSlashCatalogEntry[];
4
+ readonly activeId: string | null;
5
+ readonly state: QaSlashView["state"];
6
+ readonly error: string | null;
7
+ readonly commandSurface: QaSlashCommandSurface;
8
+ readonly showDescriptions: boolean;
9
+ readonly showKindBadge: boolean;
10
+ readonly idPrefix: string;
11
+ readonly onHover: (index: number) => void;
12
+ readonly onPick: (entry: QaSlashCatalogEntry) => void;
13
+ }
14
+ /**
15
+ * The palette itself: a listbox over rows the composer has already filtered
16
+ * and ranked. It owns no state — an empty list means "nothing matches", and
17
+ * the surface line says why when the catalog could not be read at all.
18
+ *
19
+ * Options are picked on `click` and never take focus (`onMouseDown` prevents
20
+ * the default) so the caret stays in the textarea: the keyboard keeps working
21
+ * straight after a click, and Escape still closes what the mouse opened.
22
+ */
23
+ export declare const QaSlashPalette: import("react").MemoExoticComponent<(props: QaSlashPaletteProps) => import("react/jsx-runtime").JSX.Element>;
24
+ //# sourceMappingURL=SlashPalette.d.ts.map
@@ -0,0 +1,30 @@
1
+ import type { QaSlashCatalogEntry, QaSlashExecution } from "../../types.js";
2
+ import type { QaSlashRoute } from "./slash-router.js";
3
+ /**
4
+ * The user-facing copy of every slash decision. One module because these
5
+ * strings are the whole interface between a refusal and the person reading it:
6
+ * a refusal that does not say what to do next is indistinguishable from a bug.
7
+ */
8
+ /** The refusal that predates the feature; unchanged, so nothing regresses. */
9
+ export declare const SLASH_DISABLED_COPY = "\u041A\u043E\u043C\u0430\u043D\u0434\u044B \u0441\u043E \u0441\u043B\u0435\u0448\u0435\u043C \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0432 \u0440\u0435\u0436\u0438\u043C\u0435 \u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A\u0430.";
10
+ export declare function slashRefusalCopy(route: Extract<QaSlashRoute, {
11
+ kind: "unknown" | "ambiguous" | "unavailable";
12
+ }>): string;
13
+ /**
14
+ * A gesture inside an ordinary prompt that this deployment withholds. Stated
15
+ * as a fact about the deployment, not as a fault of the message, because the
16
+ * message itself is fine — it is one word of it that will not take effect.
17
+ */
18
+ export declare function slashWithheldCopy(names: readonly string[]): string;
19
+ export declare function slashExecuteFailureCopy(entry: QaSlashCatalogEntry): string;
20
+ /** Copy for a refusal the Host decided, keyed by its reason. */
21
+ export declare function slashHostRefusalCopy(refusal: Extract<QaSlashExecution, {
22
+ kind: "refused";
23
+ }>): string;
24
+ /**
25
+ * Attachments the chosen command refuses. Checked before dispatch so the
26
+ * composer keeps the draft and the files; the Host checks it again and stays
27
+ * the authority.
28
+ */
29
+ export declare function slashAttachmentsCopy(entry: QaSlashCatalogEntry): string;
30
+ //# sourceMappingURL=copy.d.ts.map
@@ -0,0 +1,67 @@
1
+ import type { QaSlashCatalogEntry } from "../../types.js";
2
+ /**
3
+ * Where one composer draft should go.
4
+ *
5
+ * The composer used to have a single path — `sendPrompt` — with a guard that
6
+ * turned every `/`-leading line away. That guard is now the `disabled` case
7
+ * below, and everything else is decided here, in one place, from the draft and
8
+ * the catalog alone. Keeping it pure is what makes the ambiguous, unknown and
9
+ * withheld cases testable without a browser or a Host.
10
+ */
11
+ export type QaSlashRoute =
12
+ /**
13
+ * Ordinary text: the model gets it, exactly as before this feature. It may
14
+ * still name a skill this deployment withholds — the turn runs, and the
15
+ * surface says so rather than letting the user believe it took effect.
16
+ */
17
+ {
18
+ readonly kind: "prompt";
19
+ readonly withheld?: readonly string[];
20
+ }
21
+ /** A user-invocable skill: the native prompt path carries the gesture. */
22
+ | {
23
+ readonly kind: "skill";
24
+ readonly entry: QaSlashCatalogEntry;
25
+ }
26
+ /** A human command: the Host's command runtime answers it, the model never sees it. */
27
+ | {
28
+ readonly kind: "command";
29
+ readonly entry: QaSlashCatalogEntry;
30
+ }
31
+ /** Nothing by that name is offered here. */
32
+ | {
33
+ readonly kind: "unknown";
34
+ readonly name: string;
35
+ }
36
+ /** A skill and a command share the name; only the user may choose. */
37
+ | {
38
+ readonly kind: "ambiguous";
39
+ readonly name: string;
40
+ readonly entries: readonly QaSlashCatalogEntry[];
41
+ }
42
+ /** The catalog could not be read, so no `/name` can be classified. */
43
+ | {
44
+ readonly kind: "unavailable";
45
+ readonly name: string;
46
+ }
47
+ /** The deployment runs with the slash interface off. */
48
+ | {
49
+ readonly kind: "disabled";
50
+ };
51
+ export interface ResolveSlashRouteInput {
52
+ readonly text: string;
53
+ readonly enabled: boolean;
54
+ readonly catalogReady: boolean;
55
+ readonly entries: readonly QaSlashCatalogEntry[];
56
+ /** User-invocable skills this deployment withholds from this chat. */
57
+ readonly deniedSkills: readonly string[];
58
+ /**
59
+ * The entry the user picked in the palette, while the draft still reads as
60
+ * its invocation. A picked identity outranks name resolution: when a skill
61
+ * and a command share a name, the user already answered the only question
62
+ * the router would otherwise have to guess at.
63
+ */
64
+ readonly picked?: string | null;
65
+ }
66
+ export declare function resolveSlashRoute(input: ResolveSlashRouteInput): QaSlashRoute;
67
+ //# sourceMappingURL=slash-router.d.ts.map
@@ -0,0 +1,47 @@
1
+ import type { QaSlashCatalogEntry, QaSlashView } from "../../types.js";
2
+ export interface QaSlashNavigation {
3
+ /** True while the palette is on screen. */
4
+ readonly open: boolean;
5
+ readonly query: string;
6
+ /** The rows the palette renders, best match first. */
7
+ readonly rows: readonly QaSlashCatalogEntry[];
8
+ /** Index into `rows`; -1 when there is nothing to select. */
9
+ readonly active: number;
10
+ readonly activeId: string | null;
11
+ /** Move the selection by one row, wrapping at both ends. */
12
+ readonly move: (delta: number) => void;
13
+ /** Point the selection at one row, as a tap does. */
14
+ readonly hover: (index: number) => void;
15
+ /** Insert the active row's invocation into the draft. */
16
+ readonly pickActive: () => void;
17
+ /** Insert one row, as a click or a tap does. */
18
+ readonly pick: (entry: QaSlashCatalogEntry) => void;
19
+ /** Dismiss the palette for this draft. */
20
+ readonly dismiss: () => void;
21
+ /** The identity the user picked, while the draft still reads as its call. */
22
+ readonly picked: QaSlashCatalogEntry | undefined;
23
+ }
24
+ export interface UseSlashNavigationOptions {
25
+ readonly draft: string;
26
+ readonly slash: QaSlashView;
27
+ readonly maxVisible: number;
28
+ readonly fuzzySearch: boolean;
29
+ /** Set the draft to `text` and put the caret at its end. */
30
+ readonly setDraft: (text: string) => void;
31
+ /** Monotonic token; a change re-opens a palette the user had dismissed. */
32
+ readonly reopen: number;
33
+ }
34
+ /**
35
+ * Palette state for one composer draft.
36
+ *
37
+ * The palette is a pure function of the draft: it is open while the draft is
38
+ * `/` plus a name still being typed, and closes the moment the user types a
39
+ * space and starts writing arguments. Nothing has to be kept in sync — which
40
+ * is also why switching chats (the composer is keyed by chat and remounts)
41
+ * cannot leave a palette behind.
42
+ *
43
+ * Selection is one index over the filtered rows; the arrow keys, the mouse and
44
+ * the touch screen all move the same number.
45
+ */
46
+ export declare function useSlashNavigation(options: UseSlashNavigationOptions): QaSlashNavigation;
47
+ //# sourceMappingURL=useSlashNavigation.d.ts.map
@@ -1,4 +1,13 @@
1
1
  /** Every `--dsh-qa-*` custom property the root rule declares. */
2
2
  export declare const QA_BRAND_TOKEN_NAMES: readonly string[];
3
3
  export declare const QA_SURFACE_STYLES: string;
4
+ /**
5
+ * Everything the QA overlay paints, as one sheet for one `<style>` tag.
6
+ *
7
+ * The shared audit components keep their own stylesheet in
8
+ * `@yadsh/dsh-audit-ui`; it is folded in here so the overlay is self-contained
9
+ * — the audit dialog must look right whether or not the audit plugin's own
10
+ * client bundle happens to have injected the same rules into the document.
11
+ */
12
+ export declare const QA_OVERLAY_STYLES: string;
4
13
  //# sourceMappingURL=styles.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import type { ClientRemote } from "@deepseek-ai/dsh-api-gateway/client";
2
2
  import type { ISessions, SessionFace } from "@deepseek-ai/dsh-api-session-controller/client";
3
3
  import type { UiConversation } from "@deepseek-ai/dsh-client-ui-conversation/client";
4
- import type { QaAccountProfileInput, QaAccountSession, QaAccountStartersInput, QaAccountStarter, QaAccountUserPublic, QaAccessAdminSnapshot, QaApprovalDecision, QaClaimResult, QaLockdownProof, QaOwnershipEntry, QaPendingApproval, QaPendingQuestion, QaQuestionAnswerItem, QaSessionState, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillSummary, QaSkillToolDescriptor, QaSkillValidation, QaSourceFilePreview, QaTurnSources, QaWhoamiResult, QaCapabilitySelection, QaCurrentAccess, QaSessionAccess, QaSkillActivationRecord, QaSkillAssignmentOverride, QaSubrole, QaUserAccess, QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaConversationDetail, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaFeedbackQuery, QaFeedbackRow, QaMessageFeedback, QaMessageFeedbackInput, QaQualityMetrics, QaReviewQueueItem, QaReviewQueueRow, QaUserQuery } from "../types.js";
4
+ import type { QaAccountProfileInput, QaAccountSession, QaAccountStartersInput, QaAccountStarter, QaAccountUserPublic, QaAccessAdminSnapshot, QaApprovalDecision, QaClaimResult, QaLockdownProof, QaOwnershipEntry, QaPendingApproval, QaPendingQuestion, QaQuestionAnswerItem, QaSessionState, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillSummary, QaSkillToolDescriptor, QaSkillValidation, QaSourceFilePreview, QaTurnSources, QaWhoamiResult, QaCapabilitySelection, QaCurrentAccess, QaSessionAccess, QaSkillActivationRecord, QaSkillAssignmentOverride, QaSubrole, QaUserAccess, QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaConversationDetail, QaConversationDeletion, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaFeedbackQuery, QaFeedbackRow, QaMessageFeedback, QaMessageFeedbackInput, QaQualityMetrics, QaReviewQueueItem, QaReviewQueueRow, QaSlashCatalog, QaSlashExecution, QaSlashSubmitAttachment, QaUserQuery } from "../types.js";
5
5
  /** Wire content one prompt carries: text plus base64 image uploads. */
6
6
  export type QaPromptContent = Parameters<SessionFace["prompt"]>[0];
7
7
  /** DSH session runtime surface the QA controller is allowed to touch. */
@@ -58,6 +58,11 @@ export interface QaAdminApi {
58
58
  updateUser(token: string, userId: string, update: QaAdminUserUpdate): Promise<RemoteResult<QaAdminUserDetail>>;
59
59
  conversations(token: string, query: QaConversationQuery, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaConversationSummary>>>;
60
60
  conversation(token: string, conversationId: string): Promise<RemoteResult<QaConversationDetail>>;
61
+ /**
62
+ * Remove the conversation from the deployment, stored logs included. Only an
63
+ * administrator may call it; the sidebar's own delete is unrelated.
64
+ */
65
+ deleteConversation(token: string, conversationId: string): Promise<RemoteResult<QaConversationDeletion>>;
61
66
  feedback(token: string, query: QaFeedbackQuery, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaFeedbackRow>>>;
62
67
  /** The signed-in user's own verdict on one of their assistant messages. */
63
68
  rateMessage(token: string, conversationId: string, messageId: string, input: QaMessageFeedbackInput): Promise<RemoteResult<QaMessageFeedback>>;
@@ -107,6 +112,16 @@ export interface QaSourceApi {
107
112
  readonly error: unknown;
108
113
  }>;
109
114
  }
115
+ /**
116
+ * The slash half of the Host namespace. The catalog comes back already cut
117
+ * down by the deployment policy and the chat's role; `execute` takes the raw
118
+ * line, because admission re-derives the name from it rather than trusting
119
+ * anything the browser says about which entry it means.
120
+ */
121
+ export interface QaSlashApi {
122
+ catalog(token: string, sessionId: string): Promise<RemoteResult<QaSlashCatalog>>;
123
+ execute(token: string, sessionId: string, line: string, attachments: readonly QaSlashSubmitAttachment[]): Promise<RemoteResult<QaSlashExecution>>;
124
+ }
110
125
  /** A source API with the account token already bound at the call site. */
111
126
  export type QaBoundSourceApi = {
112
127
  sources(sessionId: string): Promise<{
@@ -0,0 +1,20 @@
1
+ import type { QaSurfaceConfig, ResolvedQaSlashCommands } from "../types.js";
2
+ /** Palette ceilings; a palette longer than the viewport is unusable on mobile. */
3
+ export declare const QA_SLASH_MAX_VISIBLE_MIN = 1;
4
+ export declare const QA_SLASH_MAX_VISIBLE_MAX = 100;
5
+ /**
6
+ * Resolve the slash interface policy.
7
+ *
8
+ * The master switch lives in lockdown, so it arrives as a neighbor rather than
9
+ * being re-read here — one owner per decision. Two consequences are deliberate:
10
+ *
11
+ * - An upgrade from a deployment that had already set
12
+ * `lockdown.allowSlashCommands: true` but knows nothing about this section
13
+ * keeps working: skills fall back to `all` (every user-invocable skill the
14
+ * session already exposed) and commands stay denied. The flag is reported as
15
+ * `legacyDefaults` so the Host can say so once, out loud.
16
+ * - A deployment that declares the section and leaves the switch off changes
17
+ * nothing at all: the resolved policy is inert until the switch is on.
18
+ */
19
+ export declare function resolveSlashCommands(input: QaSurfaceConfig, allowSlashCommands: boolean): ResolvedQaSlashCommands;
20
+ //# sourceMappingURL=slash-commands.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
2
2
  import type { Context } from "@deepseek-ai/cordis";
3
3
  import type { QaTurnSources } from "./provenance/types.js";
4
- import type { QaAccountProfileInput, QaAccountSession, QaAccountStartersInput, QaAccountUserPublic, QaApprovalDecision, QaClaimResult, QaLockdownProof, QaOwnershipEntry, QaPendingApproval, QaPendingQuestion, QaQuestionAnswerItem, QaSurfaceConfig, ResolvedQaSurfaceConfig, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillSummary, QaSkillToolDescriptor, QaSkillValidation, QaSourceFilePreview, QaWhoamiResult, QaPrincipal, QaAccessAdminSnapshot, QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaCapabilitySelection, QaConversationDetail, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaCurrentAccess, QaFeedbackQuery, QaFeedbackRow, QaMessageFeedback, QaMessageFeedbackInput, QaQualityMetrics, QaReviewQueueItem, QaReviewQueueRow, QaSessionAccess, QaSkillActivationRecord, QaSkillAssignmentOverride, QaSubrole, QaUserAccess, QaUserQuery } from "./types.js";
4
+ import type { QaAccountProfileInput, QaAccountSession, QaAccountStartersInput, QaAccountUserPublic, QaApprovalDecision, QaClaimResult, QaLockdownProof, QaOwnershipEntry, QaPendingApproval, QaPendingQuestion, QaQuestionAnswerItem, QaSurfaceConfig, ResolvedQaSurfaceConfig, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillSummary, QaSkillToolDescriptor, QaSkillValidation, QaSourceFilePreview, QaWhoamiResult, QaPrincipal, QaAccessAdminSnapshot, QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaCapabilitySelection, QaConversationDetail, QaConversationDeletion, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaCurrentAccess, QaFeedbackQuery, QaFeedbackRow, QaMessageFeedback, QaMessageFeedbackInput, QaQualityMetrics, QaReviewQueueItem, QaReviewQueueRow, QaSessionAccess, QaSlashCatalog, QaSlashExecution, QaSlashSubmitAttachment, QaSkillActivationRecord, QaSkillAssignmentOverride, QaSubrole, QaUserAccess, QaUserQuery } from "./types.js";
5
5
  export declare const name = "qa-surface";
6
6
  export declare const inject: string[];
7
7
  import { QA_SURFACE_SETTINGS_NAMESPACE } from "./shared/settings.js";
@@ -38,6 +38,12 @@ export declare class QaSurface extends TypertRemoteService {
38
38
  private readonly personalSkills;
39
39
  /** Personal-skill remote bodies; the wire signatures stay on this class. */
40
40
  private readonly skillRemotes;
41
+ /**
42
+ * Slash-catalog and command-admission bodies. Admission, not execution:
43
+ * the native skill and command runtimes stay the only things that know what
44
+ * a `/name` means.
45
+ */
46
+ private readonly slashRemotes;
41
47
  private readonly launchToken;
42
48
  /** Root session principals attested for their owner, never for admin viewing. */
43
49
  private readonly integrationPrincipals;
@@ -52,6 +58,25 @@ export declare class QaSurface extends TypertRemoteService {
52
58
  constructor(ctx: Context, entry?: QaSurfaceConfig);
53
59
  /** The quality store, opening its file the first time anyone needs it. */
54
60
  private quality;
61
+ /**
62
+ * The standing scope of the preset QA chats run under, so an administrator's
63
+ * capability catalog is read the way a chat reads it. Everything a preset
64
+ * mounts — its skill catalog, its tool family — is registered in that scope,
65
+ * and a global-only read showed the operator an almost empty page. Resolving
66
+ * the preset composes it but starts no agent, no session and no turn;
67
+ * without a pinned preset there is nothing to borrow, and the read stays
68
+ * global.
69
+ */
70
+ private qaPresetScope;
71
+ /**
72
+ * Drop what the quality layer kept about chats that no longer exist in the
73
+ * Harness. The ownership sweep calls this with the ids it reclaimed: the
74
+ * record it removes is one thing the deployment held about a chat, and
75
+ * ratings, reviews and queue entries are the rest. Failure is logged and
76
+ * swallowed — housekeeping runs inside a review read, and a store that
77
+ * cannot be opened must not fail the page that triggered it.
78
+ */
79
+ private dropVanishedChats;
55
80
  /** Resolve browser authentication to the caller only; never accepts an id. */
56
81
  principalForToken(token: string): QaPrincipal | undefined;
57
82
  /** Resolve only a directly QA-owned root session; delegated children fail closed. */
@@ -59,6 +84,21 @@ export declare class QaSurface extends TypertRemoteService {
59
84
  /** Admit tools that independently enforce the same QA principal boundary. */
60
85
  registerPrincipalScopedTools(names: readonly string[]): () => void;
61
86
  getConfig(): ResolvedQaSurfaceConfig;
87
+ /**
88
+ * Ownership plus the role's user-skill grant for one slash read.
89
+ *
90
+ * Ownership comes first and for the same reason the rest of the plugin does
91
+ * it: a catalog names the skills a chat can reach, and a foreign browser has
92
+ * no business enumerating that. With accounts off there is no identity to
93
+ * check and the deployment's lockdown is the boundary, exactly as it is for
94
+ * every other remote.
95
+ *
96
+ * The grant mirrors `installQaSkillPolicy` to the letter — an empty
97
+ * `userSkills` list means the role keeps no separate user list, not that it
98
+ * grants nothing — so the palette can never offer a skill the typed gesture
99
+ * would refuse, nor hide one it would accept.
100
+ */
101
+ private slashSessionGrant;
62
102
  /**
63
103
  * Self-service signup; the first account ever created becomes admin. The
64
104
  * display name derives from the email: the generated client enforces exact
@@ -139,6 +179,12 @@ export declare class QaSurface extends TypertRemoteService {
139
179
  adminConversations(token: string, query: QaConversationQuery, cursor: string | null, limit: number | null): Promise<QaAdminPage<QaConversationSummary>>;
140
180
  /** One conversation with its messages, tool calls and review state. */
141
181
  adminConversation(token: string, conversationId: string): Promise<QaConversationDetail>;
182
+ /**
183
+ * Remove one conversation from the deployment. Admin-only, audited, and the
184
+ * only path that deletes what a person wrote — the sidebar's own delete
185
+ * stays what it always was, a per-browser row.
186
+ */
187
+ adminDeleteConversation(token: string, conversationId: string): Promise<QaConversationDeletion>;
142
188
  adminFeedback(token: string, query: QaFeedbackQuery, cursor: string | null, limit: number | null): Promise<QaAdminPage<QaFeedbackRow>>;
143
189
  /**
144
190
  * Rate one of the caller's own assistant messages. The token names the only
@@ -216,7 +262,38 @@ export declare class QaSurface extends TypertRemoteService {
216
262
  skillsTools(token: string): {
217
263
  readonly tools: readonly QaSkillToolDescriptor[];
218
264
  };
265
+ /**
266
+ * The slash entries this browser may offer for one chat: the session's
267
+ * user-invocable skills and its agent's effective commands, already cut down
268
+ * by the deployment policy and by the chat's role. The browser filters and
269
+ * ranks this list; it never widens it.
270
+ *
271
+ * Enabled is reported rather than assumed, so a page that loaded before the
272
+ * operator flipped the master switch learns the truth instead of showing an
273
+ * empty palette forever.
274
+ */
275
+ slashCatalog(token: string, sessionId: string): Promise<QaSlashCatalog>;
276
+ /**
277
+ * Run one human command line. The line is the whole request — the browser
278
+ * sends no name, no kind and no descriptor, so there is nothing for it to
279
+ * forge; admission re-derives the name and re-checks the policy against the
280
+ * deployment's own config.
281
+ *
282
+ * A command never becomes a model message: the native runtime answers it and
283
+ * writes `command/run`/`command/done` into the session log, which is where
284
+ * the transcript row is read from.
285
+ */
286
+ slashExecute(token: string, sessionId: string, line: string, attachments: readonly QaSlashSubmitAttachment[]): Promise<QaSlashExecution>;
219
287
  private refreshRoute;
288
+ /**
289
+ * A deployment that set `lockdown.allowSlashCommands: true` before this
290
+ * section existed keeps the behaviour it asked for — skills at `all`,
291
+ * commands denied — and is told so once per configuration change, because
292
+ * "skills appeared after the upgrade" is otherwise indistinguishable from a
293
+ * defect. Commands are never part of that fallback: an upgrade must not hand
294
+ * the user a control-plane command nobody named.
295
+ */
296
+ private warnLegacySlashDefaults;
220
297
  /**
221
298
  * The document pipeline moved out of this plugin into `@yadsh/dsh-documents`.
222
299
  * A deployment whose profile still carries the old `documents` section would
@@ -92,6 +92,11 @@ export declare class QaPersonalSkills {
92
92
  * The skills one session cwd exposes to DSH. A file the parser rejects, or
93
93
  * one whose name disagrees with its directory, is reported in the editor and
94
94
  * never handed to the model as a half-valid skill.
95
+ *
96
+ * This is the model-facing discovery path, so it degrades to empty instead
97
+ * of throwing: a provider that throws marks the whole catalog snapshot
98
+ * incomplete, and the harness then withholds the available-skills section
99
+ * from every session — not just this account's skills.
95
100
  */
96
101
  discover(cwd: string): readonly QaStoredSkill[];
97
102
  /** Re-read one skill of a session cwd, re-checking the same boundary. */
@@ -44,6 +44,19 @@ export declare class QaProvenanceHost {
44
44
  * restart. Growth of the file is bounded by the snapshot store's caps.
45
45
  */
46
46
  private forget;
47
+ private forgetById;
48
+ /**
49
+ * Drop everything kept about one chat, in memory and in storage alike.
50
+ *
51
+ * Unlike {@link forget}, which runs on every teardown of a chat that still
52
+ * exists and deliberately keeps its stored turns, this is for a chat that is
53
+ * gone: nothing will reopen it, so the durable shard goes with it. The
54
+ * administrative deletion calls this in the same act that removes the
55
+ * session's logs.
56
+ *
57
+ * @param sessionId - the chat whose collected sources are dropped.
58
+ */
59
+ dropSession(sessionId: string): void;
47
60
  private forgetAll;
48
61
  bundles(sessionId: string): readonly QaTurnSources[];
49
62
  sourceAllowed(sessionId: string, path: string): boolean;
@@ -33,6 +33,12 @@ export interface QaQuestionAnswer {
33
33
  }
34
34
  /** What the model reads when the deployment does not answer questions. */
35
35
  export declare const QA_QUESTIONS_UNSUPPORTED = "questions are not answered in this assistant view; ask the user in plain text instead";
36
+ /**
37
+ * The harness tool that produces these requests. A deployment that parks
38
+ * questions still has to let the attested session call it: this plugin never
39
+ * adds the name to a tool policy on its own.
40
+ */
41
+ export declare const QA_ASK_QUESTION_TOOL = "ask_user_question";
36
42
  /**
37
43
  * User questions in the QA view.
38
44
  *
@@ -84,5 +90,7 @@ export declare class QaQuestionGate {
84
90
  private park;
85
91
  /** Fail the questions one session's agent owns; a disposed agent cannot ask. */
86
92
  private settleOwner;
93
+ /** Fail the questions one agent parked; an idle agent cannot receive them. */
94
+ private settleAgent;
87
95
  }
88
96
  //# sourceMappingURL=questions.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The unified presentation model behind the palette.
3
+ *
4
+ * Two native catalogs arrive here — the session's user-invocable skills and
5
+ * the session agent's effective commands — and leave as one list of entries
6
+ * whose identity is `skill:<name>` / `command:<name>`. The prefix is the whole
7
+ * point: both registries may publish a `plan`, and a palette that keyed rows
8
+ * by bare name would let one of them silently replace the other.
9
+ *
10
+ * Pure on purpose. Nothing here reads a session, a service or a file: the host
11
+ * remote gathers the native inputs, this module decides what the palette sees,
12
+ * and the tests drive it with plain arrays.
13
+ */
14
+ import type { QaSlashCatalogEntry, QaSlashCommandSurface } from "../types.js";
15
+ import { type QaSlashPolicy } from "./policy.js";
16
+ export declare const QA_SLASH_SKILL_PREFIX = "skill:";
17
+ export declare const QA_SLASH_COMMAND_PREFIX = "command:";
18
+ /** One user-invocable skill, as the skills registry published it. */
19
+ export interface QaSlashNativeSkill {
20
+ readonly name: string;
21
+ readonly description: string;
22
+ readonly whenToUse?: string;
23
+ /** Whether the model is also offered this skill. */
24
+ readonly modelInvocable?: boolean;
25
+ }
26
+ /** One effective command descriptor, as the command registry published it. */
27
+ export interface QaSlashNativeCommand {
28
+ readonly name: string;
29
+ readonly description: string;
30
+ readonly inputHint?: string;
31
+ readonly acceptsAttachments?: boolean;
32
+ }
33
+ export interface BuildSlashCatalogOptions {
34
+ readonly skills: readonly QaSlashNativeSkill[];
35
+ readonly commands: readonly QaSlashNativeCommand[];
36
+ readonly skillPolicy: QaSlashPolicy;
37
+ readonly commandPolicy: QaSlashPolicy;
38
+ /**
39
+ * The session role's own user-skill grant, when the access system is on.
40
+ * `undefined` means the role system has no opinion, which is its state on a
41
+ * deployment that never enabled roles — not "grant nothing".
42
+ */
43
+ readonly grantedSkills?: readonly string[] | undefined;
44
+ readonly commandSurface: QaSlashCommandSurface;
45
+ /** Native entries arrive alphabetically; this makes the order explicit. */
46
+ readonly localeCompare?: (left: string, right: string) => number;
47
+ }
48
+ /** The routing identity of one native name; never the bare name. */
49
+ export declare function slashEntryId(kind: QaSlashCatalogEntry["kind"], name: string): string;
50
+ /**
51
+ * Build the palette the browser is allowed to show. Skills lead, commands
52
+ * follow: the two kinds stay contiguous so the palette can group them without
53
+ * re-sorting, and a name collision keeps both rows.
54
+ */
55
+ export declare function buildSlashCatalog(options: BuildSlashCatalogOptions): readonly QaSlashCatalogEntry[];
56
+ //# sourceMappingURL=catalog.d.ts.map