@yadsh/dsh-qa-surface 0.11.2 → 0.13.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 (85) hide show
  1. package/lib/access/service.js +2 -1
  2. package/lib/access/service.js.map +1 -1
  3. package/lib/admin/service.js +254 -52
  4. package/lib/admin/service.js.map +1 -1
  5. package/lib/client/QaAccountsController.js +19 -1
  6. package/lib/client/QaAccountsController.js.map +1 -1
  7. package/lib/client/QaSurface.js +9 -2
  8. package/lib/client/QaSurface.js.map +1 -1
  9. package/lib/client/admin/pages/Conversations.js +9 -2
  10. package/lib/client/admin/pages/Conversations.js.map +1 -1
  11. package/lib/client/admin/pages/Overview.js +1 -1
  12. package/lib/client/admin/pages/Overview.js.map +1 -1
  13. package/lib/client/admin/pages/Quality.js +1 -1
  14. package/lib/client/admin/pages/Quality.js.map +1 -1
  15. package/lib/client/admin/pages/Review.js +8 -4
  16. package/lib/client/admin/pages/Review.js.map +1 -1
  17. package/lib/client/admin/shared.js +42 -4
  18. package/lib/client/admin/shared.js.map +1 -1
  19. package/lib/client/components/QaChangelog.js +52 -9
  20. package/lib/client/components/QaChangelog.js.map +1 -1
  21. package/lib/client/components/QaSidebar.js +13 -15
  22. package/lib/client/components/QaSidebar.js.map +1 -1
  23. package/lib/client/index.js +18 -4
  24. package/lib/client/index.js.map +1 -1
  25. package/lib/client/markdown/MermaidBlock.js +80 -0
  26. package/lib/client/markdown/MermaidBlock.js.map +1 -0
  27. package/lib/client/markdown/render.js +4 -0
  28. package/lib/client/markdown/render.js.map +1 -1
  29. package/lib/client/rating-harvest.js +138 -0
  30. package/lib/client/rating-harvest.js.map +1 -0
  31. package/lib/client/settings/card.js +2 -2
  32. package/lib/client/settings/card.js.map +1 -1
  33. package/lib/client/settings/sections/docs.js +41 -0
  34. package/lib/client/settings/sections/docs.js.map +1 -0
  35. package/lib/client/settings/sections.js +1 -0
  36. package/lib/client/settings/sections.js.map +1 -1
  37. package/lib/client/styles.js +24 -2
  38. package/lib/client/styles.js.map +1 -1
  39. package/lib/client/types.js.map +1 -1
  40. package/lib/client.js +2526 -232
  41. package/lib/client.js.map +1 -1
  42. package/lib/config-resolvers/defaults.js +4 -0
  43. package/lib/config-resolvers/defaults.js.map +1 -1
  44. package/lib/config-resolvers/integration.js +7 -7
  45. package/lib/config-resolvers/integration.js.map +1 -1
  46. package/lib/config-resolvers/tools.js +25 -0
  47. package/lib/config-resolvers/tools.js.map +1 -1
  48. package/lib/config.js +15 -0
  49. package/lib/config.js.map +1 -1
  50. package/lib/index.js +32 -10
  51. package/lib/index.js.map +1 -1
  52. package/lib/provenance/extractors.js +1 -1
  53. package/lib/provenance/extractors.js.map +1 -1
  54. package/lib/provenance/host-store.js +5 -1
  55. package/lib/provenance/host-store.js.map +1 -1
  56. package/lib/qa-tools/catalog.js +13 -2
  57. package/lib/qa-tools/catalog.js.map +1 -1
  58. package/lib/qa-tools/docs-tools.js +329 -31
  59. package/lib/qa-tools/docs-tools.js.map +1 -1
  60. package/lib/qa-tools/index.js +1 -0
  61. package/lib/qa-tools/index.js.map +1 -1
  62. package/lib/secure-session.js +11 -13
  63. package/lib/secure-session.js.map +1 -1
  64. package/lib/shared/docs-version.js +19 -0
  65. package/lib/shared/docs-version.js.map +1 -0
  66. package/lib/typert.host.js +140 -73
  67. package/lib/typert.remote-client.d.ts +11 -9
  68. package/lib/typert.remote-client.js +119 -67
  69. package/lib/types/admin/service.d.ts +60 -9
  70. package/lib/types/client/QaAccountsController.d.ts +12 -1
  71. package/lib/types/client/admin/shared.d.ts +16 -1
  72. package/lib/types/client/markdown/MermaidBlock.d.ts +6 -0
  73. package/lib/types/client/rating-harvest.d.ts +49 -0
  74. package/lib/types/client/settings/sections/docs.d.ts +15 -0
  75. package/lib/types/client/settings/sections.d.ts +1 -0
  76. package/lib/types/client/types.d.ts +11 -4
  77. package/lib/types/config-resolvers/integration.d.ts +5 -5
  78. package/lib/types/config-resolvers/tools.d.ts +9 -0
  79. package/lib/types/index.d.ts +18 -6
  80. package/lib/types/qa-tools/catalog.d.ts +6 -1
  81. package/lib/types/qa-tools/docs-tools.d.ts +17 -2
  82. package/lib/types/qa-tools/index.d.ts +6 -1
  83. package/lib/types/shared/docs-version.d.ts +15 -0
  84. package/lib/types/types.d.ts +42 -0
  85. package/package.json +5 -4
@@ -2,9 +2,9 @@ import type { PluginLogger } from "@yadsh/dsh-plugin-log";
2
2
  import type { QaAccounts } from "../accounts/store.js";
3
3
  import type { QaAccessService } from "../access/service.js";
4
4
  import type { QaRoleRepository } from "../access/role-repository.js";
5
- import type { QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminSkillScope, QaAdminSkillsView, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaConversationDetail, QaConversationDeletion, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaFeedbackQuery, QaFeedbackRow, QaMessageFeedback, QaMessageFeedbackInput, QaPasswordResetRequest, QaQualityMetrics, QaReviewQueueItem, QaReviewQueueRow, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillToolDescriptor, QaSkillValidation, QaUserQuery } from "../types.js";
5
+ import type { QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminSkillScope, QaAdminSkillsView, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaConversationDetail, QaConversationDeletion, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaFeedbackHarvestEntry, QaFeedbackHarvestResult, QaFeedbackQuery, QaFeedbackRow, QaMessageFeedback, QaMessageFeedbackInput, QaPasswordResetRequest, QaQualityMetrics, QaReviewQueueItem, QaReviewQueueRow, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillToolDescriptor, QaSkillValidation, QaUserQuery } from "../types.js";
6
6
  import type { QaPersonalSkills } from "../personal-skills/index.js";
7
- import type { QaQualityStore } from "./quality-store.js";
7
+ import { type QaQualityStore } from "./quality-store.js";
8
8
  import type { QaAdminRedactor } from "./redaction.js";
9
9
  import type { QaSessionLogReader, QaStoredSessionEraser } from "./session-log.js";
10
10
  /**
@@ -52,6 +52,8 @@ export interface QaAdminServiceOptions {
52
52
  export declare class QaAdminService {
53
53
  private readonly options;
54
54
  private readonly redactor;
55
+ /** The aggregate pass running now, shared by the callers waiting for it. */
56
+ private scanning;
55
57
  private readonly transcripts;
56
58
  constructor(options: QaAdminServiceOptions);
57
59
  private requireAccounts;
@@ -74,11 +76,46 @@ export declare class QaAdminService {
74
76
  * costs a listing per interval at most.
75
77
  */
76
78
  private index;
77
- /** One conversation's projected log, cached for the TTL unless asked fresh. */
79
+ /**
80
+ * One conversation's projected log, held across calls.
81
+ *
82
+ * A log the Harness no longer holds is finished — nothing can append to it —
83
+ * so its projection stays valid until the cache evicts it or the conversation
84
+ * is deleted. Only a log this process is still writing is re-read once the TTL
85
+ * expires. A cache hit counts as a use: an aggregate pass walks the whole
86
+ * window, and evicting by read time would drop the conversations the pages ask
87
+ * for most often and then read them again.
88
+ *
89
+ * The one answer this can hold back is a conversation resumed and finished
90
+ * between two page loads: its counters lag until the log is read for another
91
+ * reason. The conversation itself is never affected — the detail view always
92
+ * reads the log — and what the aggregate pages show instead is the newest
93
+ * projection of every finished conversation, which is the cheaper lie.
94
+ */
78
95
  private transcript;
79
- private facts;
80
- overview(token: string): Promise<QaAdminOverview>;
81
- metrics(token: string): Promise<QaQualityMetrics>;
96
+ /** Insert or re-touch one projection, keeping the cache inside its budget. */
97
+ private retain;
98
+ /**
99
+ * The aggregate rows of the newest scan window, read in one pass.
100
+ *
101
+ * The message counts and the tool failures come from the same projections, so
102
+ * one walk fills both. While a walk runs, a caller that wants the same window
103
+ * joins it rather than starting a second scan of the deployment's logs, and the
104
+ * walk stops once no caller waits for it: a scan nobody is reading is only ever
105
+ * a load on the store.
106
+ */
107
+ private scanOf;
108
+ /**
109
+ * Walk one scan window, filling the counts and the failure signals as it goes.
110
+ *
111
+ * `waiting` is the caller set of the pass this walk belongs to: the walk gives
112
+ * up between two reads once every caller has left, so a reviewer who abandons a
113
+ * page that has not answered does not keep the deployment reading logs for an
114
+ * answer nobody will see.
115
+ */
116
+ private walk;
117
+ overview(token: string, signal?: AbortSignal): Promise<QaAdminOverview>;
118
+ metrics(token: string, signal?: AbortSignal): Promise<QaQualityMetrics>;
82
119
  private metricsOf;
83
120
  /** One attention line per condition worth an administrator's time. */
84
121
  private alertsOf;
@@ -108,7 +145,7 @@ export declare class QaAdminService {
108
145
  private assertAdminSurvives;
109
146
  /** Validate an assignment against the enabled roles before storing it. */
110
147
  private exactAssignment;
111
- conversations(token: string, query: QaConversationQuery, cursor: string | undefined, limit: number | undefined): Promise<QaAdminPage<QaConversationSummary>>;
148
+ conversations(token: string, query: QaConversationQuery, cursor: string | undefined, limit: number | undefined, signal?: AbortSignal): Promise<QaAdminPage<QaConversationSummary>>;
112
149
  private summaryOf;
113
150
  conversation(token: string, conversationId: string): Promise<QaConversationDetail>;
114
151
  /**
@@ -130,17 +167,31 @@ export declare class QaAdminService {
130
167
  */
131
168
  deleteConversation(token: string, conversationId: string): Promise<QaConversationDeletion>;
132
169
  private failuresOf;
133
- private failureSignals;
134
170
  /**
135
171
  * Record the caller's verdict on one assistant message. The conversation must
136
172
  * be the caller's own: a rating is the user's own signal, and nobody rates
137
173
  * someone else's answer on their behalf.
138
174
  */
139
175
  rateMessage(token: string, conversationId: string, messageId: string, input: QaMessageFeedbackInput): QaMessageFeedback;
176
+ /**
177
+ * Replay the ratings a browser still holds from the window in which a thumbs
178
+ * never reached the Host.
179
+ *
180
+ * A harvest never overwrites. The browser's map stores no timestamp, so an
181
+ * entry may well be a rating the same user has since filed together with a
182
+ * reason and a comment; replacing that record with the bare thumbs would
183
+ * delete exactly what a reviewer reads. An entry the Host already answers for
184
+ * is counted as present and left alone.
185
+ *
186
+ * One unusable or foreign entry costs that entry alone: a browser keeps
187
+ * ratings of chats it no longer owns, and the rest of the replay must not be
188
+ * lost because of them.
189
+ */
190
+ harvestFeedback(token: string, entries: readonly QaFeedbackHarvestEntry[]): QaFeedbackHarvestResult;
140
191
  feedback(token: string, query: QaFeedbackQuery, cursor: string | undefined, limit: number | undefined): Promise<QaAdminPage<QaFeedbackRow>>;
141
192
  private feedbackRows;
142
193
  private queueRows;
143
- reviewQueue(token: string, cursor: string | undefined, limit: number | undefined): Promise<QaAdminPage<QaReviewQueueRow>>;
194
+ reviewQueue(token: string, cursor: string | undefined, limit: number | undefined, signal?: AbortSignal): Promise<QaAdminPage<QaReviewQueueRow>>;
144
195
  /** Park a conversation for review without rating it. */
145
196
  queueConversation(token: string, conversationId: string, messageId: string | undefined): QaReviewQueueItem;
146
197
  /** Save a reviewer's verdict, and take the item out of the queue when done. */
@@ -49,6 +49,17 @@ export interface QaAccountsControllerOptions {
49
49
  */
50
50
  readonly legacyChatIds?: () => readonly string[];
51
51
  readonly forgetChat?: (sessionId: string) => void;
52
+ /**
53
+ * Replay this browser's locally held ratings onto the account it just signed
54
+ * in as, for the chats that account owns. Runs once per account: the surface
55
+ * is usable while it is in flight, and a failure costs only the retry the
56
+ * next login performs.
57
+ */
58
+ readonly harvestRatings?: (context: {
59
+ readonly token: string;
60
+ readonly accountId: string;
61
+ readonly ownedIds: readonly string[];
62
+ }) => Promise<void>;
52
63
  }
53
64
  /**
54
65
  * Browser-side account lifecycle: token persistence, the checking → gate →
@@ -145,7 +156,7 @@ export declare class QaAccountsController {
145
156
  */
146
157
  revokeServiceToken(tokenId: string): Promise<QaIntegrationTokenResult<null>>;
147
158
  private authenticate;
148
- /** Own the account, migrate the legacy index, then project authed. */
159
+ /** Own the account, migrate the legacy index, replay held ratings, then auth. */
149
160
  private enterSession;
150
161
  /** Refresh the owned list after index changes; cheap enough to re-pull. */
151
162
  refreshOwned(): Promise<void>;
@@ -7,12 +7,27 @@ export declare function errorMessage(error: unknown): string;
7
7
  * and no deployment detail leaks into the browser.
8
8
  */
9
9
  export declare function adminErrorMessage(error: unknown): string;
10
+ /**
11
+ * One cancellable request slot. A page that starts a second request has stopped
12
+ * waiting for the first, and a page that is gone waits for nothing at all —
13
+ * either way the abandoned call should end instead of holding its connection and
14
+ * the deployment's log reader open. The aggregate pages take minutes to answer on
15
+ * a real store, so an abandoned page used to keep working long after the reviewer
16
+ * moved on, and the calls it queued behind it paid for that.
17
+ */
18
+ export declare function useRequestSlot(): {
19
+ readonly start: () => AbortController;
20
+ readonly cancel: () => void;
21
+ };
10
22
  /**
11
23
  * Load one administrative resource, keeping the previous value visible while
12
24
  * it refreshes. Reviewers page through lists while new conversations arrive;
13
25
  * blanking the table on every reload would make that unusable.
26
+ *
27
+ * `load` receives the cancellation of the call it is making: pass it on to the
28
+ * Remote, and a page the reviewer left stops being read on the server.
14
29
  */
15
- export declare function useAdminResource<T>(load: () => Promise<RemoteResult<T>>, deps: readonly unknown[]): {
30
+ export declare function useAdminResource<T>(load: (signal: AbortSignal) => Promise<RemoteResult<T>>, deps: readonly unknown[]): {
16
31
  readonly data: T | undefined;
17
32
  readonly error: string | undefined;
18
33
  readonly loading: boolean;
@@ -0,0 +1,6 @@
1
+ /** Remove executable and remotely loading SVG features before DOM insertion. */
2
+ export declare function sanitizeMermaidSvg(svg: string): Element;
3
+ export declare const MermaidBlock: import("react").MemoExoticComponent<({ code, }: {
4
+ readonly code: string;
5
+ }) => import("react/jsx-runtime").JSX.Element>;
6
+ //# sourceMappingURL=MermaidBlock.d.ts.map
@@ -0,0 +1,49 @@
1
+ import type { QaFeedbackHarvestEntry, QaFeedbackHarvestResult } from "../types.js";
2
+ import type { RemoteResult, StorageLike } from "./types.js";
3
+ /** `StorageLike` plus the enumeration a browser's `localStorage` offers. */
4
+ export interface HarvestStorage extends StorageLike {
5
+ readonly length: number;
6
+ key(index: number): string | null;
7
+ }
8
+ export type QaRatingHarvestStatus =
9
+ /** This account was already read out; nothing is offered again. */
10
+ "already-harvested"
11
+ /** Nothing addressable was found, so the account is marked as read. */
12
+ | "nothing-to-harvest" | "harvested"
13
+ /** A request failed; the marker stays unset and the next login retries. */
14
+ | "failed";
15
+ export interface QaRatingHarvestReport {
16
+ readonly status: QaRatingHarvestStatus;
17
+ /** Ratings this browser offered for that account. */
18
+ readonly entries: number;
19
+ /** Ratings the Host stored. What it already held counts as nothing. */
20
+ readonly recorded: number;
21
+ }
22
+ /** Whether this browser has already been read out for that account. */
23
+ export declare function ratingsHarvested(storage: HarvestStorage, namespace: string, accountId: string): boolean;
24
+ /**
25
+ * Every rating this browser holds for that namespace, addressed by the
26
+ * conversation and by the durable log position the Host files a verdict under.
27
+ *
28
+ * An answer row's browser id is `assistant:<position>`, so those keys are
29
+ * addressable. Anything else — a work row, a half-streamed answer, an id the
30
+ * Host named rather than numbered — cannot be traced back to a log position,
31
+ * and is left where it is instead of guessed at.
32
+ */
33
+ export declare function collectLocalRatings(storage: HarvestStorage, namespace: string): readonly QaFeedbackHarvestEntry[];
34
+ /**
35
+ * Read this browser's ratings out through `send`, once per account.
36
+ *
37
+ * `send` is the Host call; the caller chooses the token and the remote it
38
+ * travels on. The marker is written only after every batch landed, so a
39
+ * half-failed harvest is retried by the next login rather than lost.
40
+ */
41
+ export declare function harvestLocalRatings(options: {
42
+ readonly storage: HarvestStorage;
43
+ readonly namespace: string;
44
+ readonly accountId: string;
45
+ /** The chats the account owns; a rating of anyone else's chat is not offered. */
46
+ readonly ownedIds: readonly string[];
47
+ readonly send: (batch: readonly QaFeedbackHarvestEntry[]) => Promise<RemoteResult<QaFeedbackHarvestResult>>;
48
+ }): Promise<QaRatingHarvestReport>;
49
+ //# sourceMappingURL=rating-harvest.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Documentation: which edition of the corpus a question is answered from.
3
+ *
4
+ * A published corpus carries several editions of the same module under
5
+ * `docs/<module>/<version>/…`, and a chat asked about "the product" reads
6
+ * whichever edition it meets first. A stand that knows which edition it is
7
+ * about names it here, and `docs_search` stays inside it unless the call itself
8
+ * names a `version` or narrows `path`. The switch keeps the value while turning
9
+ * the behaviour off, so a stand can stop defaulting without losing the version
10
+ * it configured.
11
+ */
12
+ import { type ConfigProps } from "./common.js";
13
+ /** The documentation corpus the QA tools read, and the edition they default to. */
14
+ export declare function DocsSection(props: ConfigProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=docs.d.ts.map
@@ -15,6 +15,7 @@ export { AccessSection } from "./sections/access.js";
15
15
  export { AccountsSection } from "./sections/accounts.js";
16
16
  export { AttachmentsSection } from "./sections/attachments.js";
17
17
  export { BrandingSection } from "./sections/branding.js";
18
+ export { DocsSection } from "./sections/docs.js";
18
19
  export { EmbeddingSection } from "./sections/embedding.js";
19
20
  export { InterfaceSection } from "./sections/interface.js";
20
21
  export { LockdownSection } from "./sections/lockdown.js";
@@ -52,7 +52,14 @@ export interface QaAccessApi {
52
52
  * the page renders.
53
53
  */
54
54
  export interface QaAdminApi {
55
- overview(token: string): Promise<RemoteResult<QaAdminOverview>>;
55
+ /**
56
+ * The four aggregate pages (`overview`, `conversations`, `reviewQueue`,
57
+ * `metrics`) take the caller's cancellation: each of them reads the newest
58
+ * conversation logs of the deployment, which is minutes of work on a real
59
+ * store, and a page that has been left must stop waiting for the answer
60
+ * instead of holding the request — and the deployment's log reader — open.
61
+ */
62
+ overview(token: string, signal?: AbortSignal): Promise<RemoteResult<QaAdminOverview>>;
56
63
  users(token: string, query: QaUserQuery, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaAdminUserRow>>>;
57
64
  user(token: string, userId: string): Promise<RemoteResult<QaAdminUserDetail>>;
58
65
  updateUser(token: string, userId: string, update: QaAdminUserUpdate): Promise<RemoteResult<QaAdminUserDetail>>;
@@ -66,7 +73,7 @@ export interface QaAdminApi {
66
73
  * account's live sessions end with the token-version bump.
67
74
  */
68
75
  resetPassword(token: string, userId: string, password: string): Promise<RemoteResult<QaAdminUserDetail>>;
69
- conversations(token: string, query: QaConversationQuery, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaConversationSummary>>>;
76
+ conversations(token: string, query: QaConversationQuery, cursor: string | null, limit: number | null, signal?: AbortSignal): Promise<RemoteResult<QaAdminPage<QaConversationSummary>>>;
70
77
  conversation(token: string, conversationId: string): Promise<RemoteResult<QaConversationDetail>>;
71
78
  /**
72
79
  * Remove the conversation from the deployment, stored logs included. Only an
@@ -76,10 +83,10 @@ export interface QaAdminApi {
76
83
  feedback(token: string, query: QaFeedbackQuery, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaFeedbackRow>>>;
77
84
  /** The signed-in user's own verdict on one of their assistant messages. */
78
85
  rateMessage(token: string, conversationId: string, messageId: string, input: QaMessageFeedbackInput): Promise<RemoteResult<QaMessageFeedback>>;
79
- reviewQueue(token: string, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaReviewQueueRow>>>;
86
+ reviewQueue(token: string, cursor: string | null, limit: number | null, signal?: AbortSignal): Promise<RemoteResult<QaAdminPage<QaReviewQueueRow>>>;
80
87
  queueConversation(token: string, conversationId: string, messageId: string | null): Promise<RemoteResult<QaReviewQueueItem>>;
81
88
  saveReview(token: string, input: QaConversationReviewInput): Promise<RemoteResult<QaConversationReview>>;
82
- metrics(token: string): Promise<RemoteResult<QaQualityMetrics>>;
89
+ metrics(token: string, signal?: AbortSignal): Promise<RemoteResult<QaQualityMetrics>>;
83
90
  audit(token: string, query: QaAuditQuery, cursor: string | null, limit: number | null): Promise<RemoteResult<QaAdminPage<QaAdminAuditEvent>>>;
84
91
  /**
85
92
  * Skill files an administrator may edit: the deployment's shared store, or
@@ -4,13 +4,13 @@ type ResolvedIntegration = ResolvedQaSurfaceConfig["integration"];
4
4
  export declare const QA_INTEGRATION_TTL_DAYS_MIN = 1;
5
5
  export declare const QA_INTEGRATION_TTL_DAYS_MAX = 3650;
6
6
  /**
7
- * Floor only: how long an integration may wait for its answer is the operator's
8
- * call. The deployment spends no budget of its own by waiting, and the ceiling
9
- * this constant used to carry (10 minutes) truncated exactly the long questions
10
- * an integration exists for — a nineteen-minute analysis came back as
11
- * "escalate" while the answer was still being written.
7
+ * The operator may choose any wait Node can represent faithfully. The former
8
+ * ten-minute product ceiling cut off legitimate long analyses, while values
9
+ * above the runtime ceiling wrap to an almost immediate timer.
12
10
  */
13
11
  export declare const QA_INTEGRATION_TIMEOUT_MS_MIN = 5000;
12
+ /** Largest delay Node schedules without overflowing to an immediate timer. */
13
+ export declare const QA_INTEGRATION_TIMEOUT_MS_MAX = 2147483647;
14
14
  export declare const QA_INTEGRATION_CONCURRENCY_MIN = 1;
15
15
  export declare const QA_INTEGRATION_CONCURRENCY_MAX = 16;
16
16
  export declare const QA_INTEGRATION_RATE_MIN = 1;
@@ -2,5 +2,14 @@ import type { QaSurfaceConfig, ResolvedQaSurfaceConfig } from "../types.js";
2
2
  type ToolsSlice = ResolvedQaSurfaceConfig["tools"];
3
3
  /** Resolve the QA tool delivery domain: activation trigger, policy, and scope. */
4
4
  export declare function resolveTools(input: QaSurfaceConfig): ToolsSlice;
5
+ /**
6
+ * The version one search runs under, or `""` when the deployment's default is
7
+ * off, unset, or not what the call is about.
8
+ *
9
+ * The readers take a single fallback, and a switch that is off must not fall
10
+ * back at all — the value stays configured for the day it is turned on, and an
11
+ * enabled-but-empty pair is simply no default.
12
+ */
13
+ export declare function docsDefaultVersionOf(tools: ResolvedQaSurfaceConfig["tools"]): string;
5
14
  export {};
6
15
  //# sourceMappingURL=tools.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, QaDocumentPreview, QaIssuedServiceToken, QaLockdownProof, QaOwnershipEntry, QaPasswordResetRequest, QaPendingApproval, QaPendingQuestion, QaQuestionAnswerItem, QaServiceTokenCreateInput, QaServiceTokenSummary, QaSurfaceConfig, ResolvedQaSurfaceConfig, QaAdminSkillScope, QaAdminSkillsView, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillSummary, QaSkillToolDescriptor, QaSkillValidation, QaSourceFilePreview, QaWhoamiResult, QaWorkspaceFile, QaWorkspaceListing, 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";
4
+ import type { QaAccountProfileInput, QaAccountSession, QaAccountStartersInput, QaAccountUserPublic, QaApprovalDecision, QaClaimResult, QaDocumentPreview, QaIssuedServiceToken, QaLockdownProof, QaOwnershipEntry, QaPasswordResetRequest, QaPendingApproval, QaPendingQuestion, QaQuestionAnswerItem, QaServiceTokenCreateInput, QaServiceTokenSummary, QaSurfaceConfig, ResolvedQaSurfaceConfig, QaAdminSkillScope, QaAdminSkillsView, QaSkillDocument, QaSkillDraftInput, QaSkillRemoval, QaSkillSummary, QaSkillToolDescriptor, QaSkillValidation, QaSourceFilePreview, QaWhoamiResult, QaWorkspaceFile, QaWorkspaceListing, QaPrincipal, QaAccessAdminSnapshot, QaAdminAuditEvent, QaAdminOverview, QaAdminPage, QaAdminUserDetail, QaAdminUserRow, QaAdminUserUpdate, QaAuditQuery, QaCapabilitySelection, QaConversationDetail, QaConversationDeletion, QaConversationQuery, QaConversationReview, QaConversationReviewInput, QaConversationSummary, QaCurrentAccess, QaFeedbackHarvestEntry, QaFeedbackHarvestResult, 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";
@@ -232,15 +232,22 @@ export declare class QaSurface extends TypertRemoteService {
232
232
  adminSkillTools(token: string, scope: QaAdminSkillScope): Promise<{
233
233
  readonly tools: readonly QaSkillToolDescriptor[];
234
234
  }>;
235
- /** Counters, attention lines and the newest quality signals. */
236
- adminOverview(token: string): Promise<QaAdminOverview>;
235
+ /**
236
+ * Counters, attention lines and the newest quality signals.
237
+ *
238
+ * The Host hands the last parameter its cancellation: this call reads the
239
+ * newest conversation logs of the deployment, which on a real store is minutes
240
+ * of work, and a reviewer who leaves the page while it runs must not leave it
241
+ * running — every other administrative call waits behind it.
242
+ */
243
+ adminOverview(token: string, signal: AbortSignal): Promise<QaAdminOverview>;
237
244
  adminUsers(token: string, query: QaUserQuery, cursor: string | null, limit: number | null): Promise<QaAdminPage<QaAdminUserRow>>;
238
245
  adminUser(token: string, userId: string): Promise<QaAdminUserDetail>;
239
246
  /** Authorization role, status and subrole assignment of one account. */
240
247
  adminUpdateUser(token: string, userId: string, update: QaAdminUserUpdate): Promise<QaAdminUserDetail>;
241
248
  adminPasswordResetRequests(token: string): Promise<readonly QaPasswordResetRequest[]>;
242
249
  adminResetPassword(token: string, userId: string, password: string): Promise<QaAdminUserDetail>;
243
- adminConversations(token: string, query: QaConversationQuery, cursor: string | null, limit: number | null): Promise<QaAdminPage<QaConversationSummary>>;
250
+ adminConversations(token: string, query: QaConversationQuery, cursor: string | null, limit: number | null, signal: AbortSignal): Promise<QaAdminPage<QaConversationSummary>>;
244
251
  /** One conversation with its messages, tool calls and review state. */
245
252
  adminConversation(token: string, conversationId: string): Promise<QaConversationDetail>;
246
253
  /**
@@ -255,11 +262,16 @@ export declare class QaSurface extends TypertRemoteService {
255
262
  * ratable conversation, exactly as the self-service profile write does.
256
263
  */
257
264
  adminRateMessage(token: string, conversationId: string, messageId: string, input: QaMessageFeedbackInput): QaMessageFeedback;
258
- adminReviewQueue(token: string, cursor: string | null, limit: number | null): Promise<QaAdminPage<QaReviewQueueRow>>;
265
+ /**
266
+ * Replay the ratings a browser still holds from before a thumbs reached the
267
+ * Host. Insert-only: what the deployment already records is never rewritten.
268
+ */
269
+ adminHarvestFeedback(token: string, entries: readonly QaFeedbackHarvestEntry[]): QaFeedbackHarvestResult;
270
+ adminReviewQueue(token: string, cursor: string | null, limit: number | null, signal: AbortSignal): Promise<QaAdminPage<QaReviewQueueRow>>;
259
271
  /** Park a conversation for review without rating it. */
260
272
  adminQueueConversation(token: string, conversationId: string, messageId: string | null): QaReviewQueueItem;
261
273
  adminSaveReview(token: string, input: QaConversationReviewInput): QaConversationReview;
262
- adminMetrics(token: string): Promise<QaQualityMetrics>;
274
+ adminMetrics(token: string, signal: AbortSignal): Promise<QaQualityMetrics>;
263
275
  adminAudit(token: string, query: QaAuditQuery, cursor: string | null, limit: number | null): Promise<QaAdminPage<QaAdminAuditEvent>>;
264
276
  /** Pin and attest the effective policy. The browser supplies identity only. */
265
277
  secureSession(token: string, sessionId: string): Promise<QaLockdownProof>;
@@ -23,7 +23,12 @@ export interface QaToolsSelfcheckDeps {
23
23
  * Documentation root the two readers use, or `""` for the `docs/` directory
24
24
  * inside each chat's own workspace.
25
25
  */
26
- readonly docsRoot?: string;
26
+ readonly docsRoot?: string | (() => string);
27
+ /**
28
+ * Version a search without `version` and without `path` stays inside, or `""`
29
+ * for a search across every edition the corpus carries.
30
+ */
31
+ readonly docsDefaultVersion?: string | (() => string);
27
32
  }
28
33
  /**
29
34
  * Build the shipped QA tool catalog.
@@ -19,6 +19,14 @@ import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
19
19
  * names are accepted as filters. A path the convention does not describe is
20
20
  * still searched and still read — it simply carries no identity.
21
21
  *
22
+ * A real corpus also carries its own working material: the stand publishes an
23
+ * asset tree, inventories and triage notes beside the documents, under leading
24
+ * underscores. None of that is what a question is asked about, and an inventory
25
+ * names every module and version there is, so a walk that met it first spent the
26
+ * whole answer on it. The walk therefore visits the documented modules first,
27
+ * loose files next, and the underscored service material last — searched, but
28
+ * never at the expense of the documents.
29
+ *
22
30
  * Nothing here is destructive and building it registers nothing: like the rest
23
31
  * of the catalog these are side-effect-free definitions, and the workspace root
24
32
  * is read from `exec.agent` at call time.
@@ -67,6 +75,13 @@ interface QaDocsExecution {
67
75
  export interface QaDocsOptions {
68
76
  /** Absolute documentation root, or `""` for `<chat workspace>/docs`. */
69
77
  readonly root?: string;
78
+ /**
79
+ * Documentation version a search stays inside when the caller named neither
80
+ * `version` nor `path`; `""` (the default) leaves every edition in scope. The
81
+ * deployment sets it so a chat's questions do not have to carry the stand's
82
+ * edition, and an explicit `version` or `path` still decides for itself.
83
+ */
84
+ readonly defaultVersion?: string;
70
85
  }
71
86
  /** The resolved documentation root of one call. */
72
87
  export interface QaDocsRoot {
@@ -146,8 +161,8 @@ export interface QaDocsSearchRequest {
146
161
  /**
147
162
  * Search the documentation tree of one chat workspace.
148
163
  *
149
- * Matching is a case-insensitive substring test, line by line: a hit is a
150
- * line, not a file, so the caller can cite it. The result is bounded twice —
164
+ * Matching uses one case-insensitive, grep-style regular expression, line by
165
+ * line: a hit is a line, not a file, so the caller can cite it. The result is bounded twice —
151
166
  * by `limit` hits and by the byte budget of the rendered report — and reports
152
167
  * which bound cut it short instead of silently dropping matches.
153
168
  * @throws QaDocsError when the tree is missing, the scope path is wrong, or the query is empty.
@@ -9,7 +9,12 @@ export interface QaToolsOptions {
9
9
  readonly activationMode: string;
10
10
  readonly activationPresets: readonly string[];
11
11
  /** Documentation root the readers use; `""` reads each chat's own `docs/`. */
12
- readonly docsRoot: string;
12
+ readonly docsRoot: string | (() => string);
13
+ /**
14
+ * Version a search falls back to when the model named neither `version` nor
15
+ * `path`; `""` searches every edition the corpus carries.
16
+ */
17
+ readonly docsDefaultVersion: string | (() => string);
13
18
  }
14
19
  /**
15
20
  * The QA tool subsystem: one catalog, one per-agent activation manager, and the
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The documentation version grammar, shared by the Host and the browser half.
3
+ *
4
+ * This module must stay dependency-free: the settings card bundle inlines the
5
+ * resolver that validates a configured default version, and a browser build
6
+ * must not pull the QA tool runtime — or the Node builtins it reads files with
7
+ * — into the page just to learn what a version looks like.
8
+ */
9
+ /**
10
+ * Whether a path segment — or a configured default version — is a documentation
11
+ * version. A default the corpus can never match is refused where it is written
12
+ * rather than answered with an empty search.
13
+ */
14
+ export declare function isDocumentationVersion(value: string): boolean;
15
+ //# sourceMappingURL=docs-version.d.ts.map
@@ -721,6 +721,21 @@ export interface QaSurfaceConfig {
721
721
  * per-user directory, which is otherwise unreadable to those tools.
722
722
  */
723
723
  readonly docsRoot?: string;
724
+ /**
725
+ * Documentation version `docs_search` stays inside when the model named
726
+ * neither `version` nor `path`. A corpus carries several editions of the
727
+ * same module, and a chat asked about "the product" answers out of whichever
728
+ * edition it happened to read first; naming the stand's edition here lets a
729
+ * search default to it, and an explicit `version` or `path` still decides
730
+ * for itself.
731
+ */
732
+ readonly docsDefaultVersion?: string;
733
+ /**
734
+ * Whether `docsDefaultVersion` is applied. The value stays configured while
735
+ * the switch is off, so a stand can turn the default back on without
736
+ * retyping it.
737
+ */
738
+ readonly docsDefaultVersionEnabled?: boolean;
724
739
  };
725
740
  readonly lockdown?: {
726
741
  readonly enabled?: boolean;
@@ -1002,6 +1017,13 @@ export interface ResolvedQaSurfaceConfig {
1002
1017
  * `docs/` directory inside each chat's own workspace.
1003
1018
  */
1004
1019
  readonly docsRoot: string;
1020
+ /**
1021
+ * Version a search without `version` and without `path` stays inside, or
1022
+ * `""` for a search across every edition the corpus carries.
1023
+ */
1024
+ readonly docsDefaultVersion: string;
1025
+ /** Whether the deployment's default version is in force. */
1026
+ readonly docsDefaultVersionEnabled: boolean;
1005
1027
  };
1006
1028
  readonly sources: {
1007
1029
  readonly enabled: boolean;
@@ -1568,6 +1590,26 @@ export interface QaMessageFeedbackInput {
1568
1590
  readonly reasons?: readonly QaFeedbackReason[];
1569
1591
  readonly comment?: string;
1570
1592
  }
1593
+ /**
1594
+ * One rating a browser still holds locally: a verdict given while the surface
1595
+ * had no way to file it, replayed by the harvest. Reasons never ride along —
1596
+ * the browser's map only ever stored the thumbs.
1597
+ */
1598
+ export interface QaFeedbackHarvestEntry {
1599
+ readonly conversationId: string;
1600
+ /** The durable log position, exactly as a live rating carries it. */
1601
+ readonly messageId: string;
1602
+ readonly rating: QaFeedbackRating;
1603
+ }
1604
+ /** What one harvest write did; the counts answer for the whole batch. */
1605
+ export interface QaFeedbackHarvestResult {
1606
+ /** Ratings the Host stored now. Replaying the same batch stores none. */
1607
+ readonly recorded: number;
1608
+ /** Entries the Host already held a rating for, left as they were. */
1609
+ readonly present: number;
1610
+ /** Entries refused: not the caller's conversation, or unusable. */
1611
+ readonly rejected: number;
1612
+ }
1571
1613
  /** Review workflow state of a conversation or one of its messages. */
1572
1614
  export type QaReviewStatus = "unreviewed" | "in_review" | "reviewed" | "needs_followup";
1573
1615
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yadsh/dsh-qa-surface",
3
- "version": "0.11.2",
3
+ "version": "0.13.0",
4
4
  "description": "A focused end-user QA surface backed by native DeepSeek Harness sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -131,13 +131,14 @@
131
131
  "react-dom": "^18.2.0"
132
132
  },
133
133
  "dependencies": {
134
+ "mermaid": "^11.12.2",
134
135
  "yaml": "^2.9.0",
135
136
  "zod": "^4.4.3",
136
137
  "@yadsh/dsh-audit-core": "^0.1.0",
137
- "@yadsh/dsh-documents": "^0.5.1",
138
- "@yadsh/dsh-plugin-log": "^0.4.0",
139
138
  "@yadsh/dsh-audit-ui": "^0.1.1",
140
- "@yadsh/dsh-plugin-kit": "^0.3.0"
139
+ "@yadsh/dsh-documents": "^0.5.1",
140
+ "@yadsh/dsh-plugin-kit": "^0.4.0",
141
+ "@yadsh/dsh-plugin-log": "^0.4.0"
141
142
  },
142
143
  "devDependencies": {
143
144
  "@deepseek-ai/cordis": "4.0.2",