@yadsh/dsh-qa-surface 0.10.0 → 0.11.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 (169) hide show
  1. package/README.md +251 -12
  2. package/lib/access/service.js +49 -10
  3. package/lib/access/service.js.map +1 -1
  4. package/lib/account-remotes.js +41 -0
  5. package/lib/account-remotes.js.map +1 -1
  6. package/lib/accounts/database.js +140 -0
  7. package/lib/accounts/database.js.map +1 -1
  8. package/lib/accounts/errors.js.map +1 -1
  9. package/lib/accounts/service-token.js +103 -0
  10. package/lib/accounts/service-token.js.map +1 -0
  11. package/lib/accounts/store.js +291 -5
  12. package/lib/accounts/store.js.map +1 -1
  13. package/lib/admin/conversation-log.js +14 -8
  14. package/lib/admin/conversation-log.js.map +1 -1
  15. package/lib/admin/permissions.js +5 -0
  16. package/lib/admin/permissions.js.map +1 -1
  17. package/lib/admin/quality-store.js +4 -0
  18. package/lib/admin/quality-store.js.map +1 -1
  19. package/lib/admin/service.js +173 -0
  20. package/lib/admin/service.js.map +1 -1
  21. package/lib/admin/session-log.js +39 -8
  22. package/lib/admin/session-log.js.map +1 -1
  23. package/lib/attachment-rules.js +39 -6
  24. package/lib/attachment-rules.js.map +1 -1
  25. package/lib/cli.js +83 -1
  26. package/lib/cli.js.map +1 -1
  27. package/lib/client/QaAccountsController.js +213 -4
  28. package/lib/client/QaAccountsController.js.map +1 -1
  29. package/lib/client/QaSessionController.js +37 -21
  30. package/lib/client/QaSessionController.js.map +1 -1
  31. package/lib/client/QaSurface.js +28 -4
  32. package/lib/client/QaSurface.js.map +1 -1
  33. package/lib/client/admin/QaAdmin.js +9 -1
  34. package/lib/client/admin/QaAdmin.js.map +1 -1
  35. package/lib/client/admin/pages/SkillFiles.js +78 -0
  36. package/lib/client/admin/pages/SkillFiles.js.map +1 -0
  37. package/lib/client/admin/pages/Users.js +65 -1
  38. package/lib/client/admin/pages/Users.js.map +1 -1
  39. package/lib/client/admin/routes.js +8 -1
  40. package/lib/client/admin/routes.js.map +1 -1
  41. package/lib/client/admin/styles.js +2 -0
  42. package/lib/client/admin/styles.js.map +1 -1
  43. package/lib/client/attachments.js +41 -11
  44. package/lib/client/attachments.js.map +1 -1
  45. package/lib/client/components/QaAuthGate.js +19 -10
  46. package/lib/client/components/QaAuthGate.js.map +1 -1
  47. package/lib/client/components/QaChangelog.js +27 -0
  48. package/lib/client/components/QaChangelog.js.map +1 -1
  49. package/lib/client/index.js +13 -0
  50. package/lib/client/index.js.map +1 -1
  51. package/lib/client/lineage.js +64 -0
  52. package/lib/client/lineage.js.map +1 -1
  53. package/lib/client/settings/sections/attachments.js +6 -5
  54. package/lib/client/settings/sections/attachments.js.map +1 -1
  55. package/lib/client/settings/sections/notes.js +22 -4
  56. package/lib/client/settings/sections/notes.js.map +1 -1
  57. package/lib/client/styles.js +9 -0
  58. package/lib/client/styles.js.map +1 -1
  59. package/lib/client/types.js.map +1 -1
  60. package/lib/client/user-settings/IntegrationTokensPage.js +150 -0
  61. package/lib/client/user-settings/IntegrationTokensPage.js.map +1 -0
  62. package/lib/client/user-settings/PasswordSettingsPage.js +48 -0
  63. package/lib/client/user-settings/PasswordSettingsPage.js.map +1 -0
  64. package/lib/client/user-settings/SkillCatalog.js +2 -2
  65. package/lib/client/user-settings/SkillCatalog.js.map +1 -1
  66. package/lib/client/user-settings/UserSettingsDialog.js +17 -2
  67. package/lib/client/user-settings/UserSettingsDialog.js.map +1 -1
  68. package/lib/client/user-settings/copy.js +20 -0
  69. package/lib/client/user-settings/copy.js.map +1 -1
  70. package/lib/client.js +2466 -137
  71. package/lib/client.js.map +1 -1
  72. package/lib/config-resolvers/attachments.js +6 -5
  73. package/lib/config-resolvers/attachments.js.map +1 -1
  74. package/lib/config-resolvers/defaults.js +23 -2
  75. package/lib/config-resolvers/defaults.js.map +1 -1
  76. package/lib/config-resolvers/integration.js +94 -0
  77. package/lib/config-resolvers/integration.js.map +1 -0
  78. package/lib/config-resolvers/notes.js +2 -0
  79. package/lib/config-resolvers/notes.js.map +1 -1
  80. package/lib/config.js +72 -0
  81. package/lib/config.js.map +1 -1
  82. package/lib/index.js +197 -2
  83. package/lib/index.js.map +1 -1
  84. package/lib/integration/answer.js +100 -0
  85. package/lib/integration/answer.js.map +1 -0
  86. package/lib/integration/attachments.js +231 -0
  87. package/lib/integration/attachments.js.map +1 -0
  88. package/lib/integration/contract.js +53 -0
  89. package/lib/integration/contract.js.map +1 -0
  90. package/lib/integration/host-runner.js +243 -0
  91. package/lib/integration/host-runner.js.map +1 -0
  92. package/lib/integration/http.js +538 -0
  93. package/lib/integration/http.js.map +1 -0
  94. package/lib/integration/service.js +331 -0
  95. package/lib/integration/service.js.map +1 -0
  96. package/lib/integration/transcript-reader.js +202 -0
  97. package/lib/integration/transcript-reader.js.map +1 -0
  98. package/lib/integration/transcript.js +148 -0
  99. package/lib/integration/transcript.js.map +1 -0
  100. package/lib/personal-skills/index.js +2 -2
  101. package/lib/personal-skills/index.js.map +1 -1
  102. package/lib/personal-skills/paths.js +14 -8
  103. package/lib/personal-skills/paths.js.map +1 -1
  104. package/lib/personal-skills/provider.js +16 -4
  105. package/lib/personal-skills/provider.js.map +1 -1
  106. package/lib/personal-skills/service.js +240 -30
  107. package/lib/personal-skills/service.js.map +1 -1
  108. package/lib/prompt-notes.js +58 -0
  109. package/lib/prompt-notes.js.map +1 -1
  110. package/lib/provenance/document-preview.js +10 -1
  111. package/lib/provenance/document-preview.js.map +1 -1
  112. package/lib/qa-tools/catalog.js +12 -1
  113. package/lib/qa-tools/catalog.js.map +1 -1
  114. package/lib/qa-tools/docs-tools.js +563 -0
  115. package/lib/qa-tools/docs-tools.js.map +1 -0
  116. package/lib/resolve-config.js +4 -0
  117. package/lib/resolve-config.js.map +1 -1
  118. package/lib/secure-session.js +83 -3
  119. package/lib/secure-session.js.map +1 -1
  120. package/lib/shared/integration-tokens.js +23 -0
  121. package/lib/shared/integration-tokens.js.map +1 -0
  122. package/lib/typert.host.js +1176 -213
  123. package/lib/typert.remote-client.d.ts +27 -1
  124. package/lib/typert.remote-client.js +911 -69
  125. package/lib/types/access/service.d.ts +33 -2
  126. package/lib/types/account-remotes.d.ts +30 -1
  127. package/lib/types/accounts/database.d.ts +53 -0
  128. package/lib/types/accounts/errors.d.ts +5 -1
  129. package/lib/types/accounts/service-token.d.ts +70 -0
  130. package/lib/types/accounts/store.d.ts +131 -1
  131. package/lib/types/admin/conversation-log.d.ts +12 -0
  132. package/lib/types/admin/service.d.ts +56 -1
  133. package/lib/types/admin/session-log.d.ts +21 -0
  134. package/lib/types/attachment-rules.d.ts +26 -3
  135. package/lib/types/client/QaAccountsController.d.ts +64 -5
  136. package/lib/types/client/QaSessionController.d.ts +24 -2
  137. package/lib/types/client/admin/pages/SkillFiles.d.ts +17 -0
  138. package/lib/types/client/admin/routes.d.ts +2 -0
  139. package/lib/types/client/attachments.d.ts +21 -4
  140. package/lib/types/client/lineage.d.ts +42 -0
  141. package/lib/types/client/settings/sections/notes.d.ts +5 -3
  142. package/lib/types/client/types.d.ts +71 -1
  143. package/lib/types/client/user-settings/IntegrationTokensPage.d.ts +21 -0
  144. package/lib/types/client/user-settings/PasswordSettingsPage.d.ts +19 -0
  145. package/lib/types/client/user-settings/UserSettingsDialog.d.ts +11 -2
  146. package/lib/types/client/user-settings/copy.d.ts +10 -1
  147. package/lib/types/config-resolvers/integration.d.ts +47 -0
  148. package/lib/types/config.d.ts +10 -0
  149. package/lib/types/index.d.ts +73 -1
  150. package/lib/types/integration/answer.d.ts +65 -0
  151. package/lib/types/integration/attachments.d.ts +85 -0
  152. package/lib/types/integration/contract.d.ts +196 -0
  153. package/lib/types/integration/host-runner.d.ts +44 -0
  154. package/lib/types/integration/http.d.ts +79 -0
  155. package/lib/types/integration/service.d.ts +120 -0
  156. package/lib/types/integration/transcript-reader.d.ts +100 -0
  157. package/lib/types/integration/transcript.d.ts +59 -0
  158. package/lib/types/personal-skills/index.d.ts +2 -2
  159. package/lib/types/personal-skills/paths.d.ts +15 -3
  160. package/lib/types/personal-skills/provider.d.ts +14 -2
  161. package/lib/types/personal-skills/service.d.ts +89 -14
  162. package/lib/types/prompt-notes.d.ts +28 -0
  163. package/lib/types/provenance/document-preview.d.ts +4 -0
  164. package/lib/types/qa-tools/catalog.d.ts +1 -1
  165. package/lib/types/qa-tools/docs-tools.d.ts +160 -0
  166. package/lib/types/secure-session.d.ts +52 -1
  167. package/lib/types/shared/integration-tokens.d.ts +24 -0
  168. package/lib/types/types.d.ts +170 -2
  169. package/package.json +4 -4
@@ -0,0 +1,160 @@
1
+ import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
2
+ /**
3
+ * `docs_search` and `docs_read` — the documentation surface of the QA catalog.
4
+ *
5
+ * A QA chat answers questions from the stand's documentation, and until now the
6
+ * model had no tool that said where that documentation is: the path in a
7
+ * reviewer's remark (`docs/platform/3.8/…`) was something the model had to
8
+ * guess its way to with a glob sweep, and a miss read as "the document does
9
+ * not exist" rather than "wrong tree". These two tools make the tree a
10
+ * first-class surface. Both are read-only, both are fenced to the
11
+ * documentation directory inside the calling chat's workspace
12
+ * (`<session cwd>/docs`, the directory the stand publishes its documentation
13
+ * into), and both name the documentation as what they search, so the tool
14
+ * description itself answers "where do I look for documentation?".
15
+ *
16
+ * The tree is read as a shallow layout convention, not a schema: hits carry
17
+ * the `version` and `module` parsed out of the path (`docs/<module>/<version>/…`)
18
+ * so the model can stay inside one edition of one module, and the same two
19
+ * names are accepted as filters. A path the convention does not describe is
20
+ * still searched and still read — it simply carries no identity.
21
+ *
22
+ * Nothing here is destructive and building it registers nothing: like the rest
23
+ * of the catalog these are side-effect-free definitions, and the workspace root
24
+ * is read from `exec.agent` at call time.
25
+ */
26
+ /** The documentation directory inside a chat workspace. */
27
+ export declare const QA_DOCS_DIRECTORY = "docs";
28
+ /** Search defaults and budgets, all fixed: a chat cannot raise them. */
29
+ export declare const QA_DOCS_SEARCH_DEFAULT_LIMIT = 8;
30
+ export declare const QA_DOCS_SEARCH_MAX_LIMIT = 40;
31
+ export declare const QA_DOCS_SEARCH_BYTE_BUDGET = 4000;
32
+ export declare const QA_DOCS_READ_DEFAULT_LINES = 120;
33
+ export declare const QA_DOCS_READ_MAX_LINES = 400;
34
+ /** Why a documentation tool refused a call. */
35
+ export type QaDocsRefusal = "workspace-unavailable" | "docs-unavailable" | "invalid-request" | "outside-docs" | "symlink-escape" | "not-found" | "not-a-file" | "not-text";
36
+ /**
37
+ * A typed refusal of one documentation call.
38
+ *
39
+ * The message is what the model reads. It names the reason in words and never
40
+ * echoes an absolute host path, so a refusal cannot leak the deployment's
41
+ * layout into the conversation.
42
+ */
43
+ export declare class QaDocsError extends Error {
44
+ readonly code: QaDocsRefusal;
45
+ constructor(code: QaDocsRefusal, message: string, options?: ErrorOptions);
46
+ }
47
+ /** The slice of the execution record these tool bodies read. */
48
+ interface QaDocsExecution {
49
+ readonly agent?: {
50
+ readonly session: {
51
+ readonly header: {
52
+ readonly cwd?: string;
53
+ };
54
+ };
55
+ };
56
+ }
57
+ /** The resolved documentation root of one call. */
58
+ export interface QaDocsRoot {
59
+ /** Canonical workspace root of the calling chat. */
60
+ readonly workspace: string;
61
+ /** Canonical `<workspace>/docs`. */
62
+ readonly docs: string;
63
+ }
64
+ /**
65
+ * Module/version identity parsed out of a documentation path.
66
+ *
67
+ * Both are optional and, when a path does not carry one, genuinely absent: the
68
+ * catalog's output schema is read with `exactOptionalPropertyTypes`, so an
69
+ * identity is spread into a report rather than written as `undefined`.
70
+ */
71
+ export interface QaDocsIdentity {
72
+ readonly version?: string;
73
+ readonly module?: string;
74
+ }
75
+ /** One documentation match. */
76
+ export interface QaDocsHit extends QaDocsIdentity {
77
+ /** Workspace-relative path, starting at `docs/`, with forward slashes. */
78
+ readonly path: string;
79
+ /** 1-based line number inside the file. */
80
+ readonly line: number;
81
+ /** The matched line, trimmed and bounded. */
82
+ readonly text: string;
83
+ }
84
+ /** The outcome of one documentation search. */
85
+ export interface QaDocsSearchResult extends QaDocsIdentity {
86
+ readonly query: string;
87
+ /** The directory documentation is read from, relative to the workspace. */
88
+ readonly root: string;
89
+ readonly hits: QaDocsHit[];
90
+ readonly truncated: boolean;
91
+ readonly filesScanned: number;
92
+ readonly skipped: number;
93
+ }
94
+ /** The outcome of one documentation read. */
95
+ export interface QaDocsReadResult extends QaDocsIdentity {
96
+ readonly path: string;
97
+ readonly from: number;
98
+ readonly to: number;
99
+ readonly totalLines: number;
100
+ readonly truncated: boolean;
101
+ readonly text: string;
102
+ }
103
+ /**
104
+ * Identity of one documentation path, read relative to the documentation root.
105
+ *
106
+ * The documented layout is `docs/<module>/<version>/…`: the version is the
107
+ * first directory segment that parses as one (`3.8`, `v2`, `2024.1`), and the
108
+ * module is the directory segment immediately before it. A path with no
109
+ * version segment keeps its first directory segment as the module; a file
110
+ * directly in `docs/` carries neither, and is still searched and read.
111
+ */
112
+ export declare function docsIdentityOf(pathFromDocsRoot: string): QaDocsIdentity;
113
+ /**
114
+ * The canonical documentation root of one call.
115
+ *
116
+ * The root is a real directory inside the canonical workspace: a `docs` entry
117
+ * that is a link or a file is refused rather than followed, because the fence
118
+ * is the point of the tool and a link is how a fence is left.
119
+ */
120
+ export declare function docsRootOf(exec: QaDocsExecution): Promise<QaDocsRoot>;
121
+ export interface QaDocsSearchRequest {
122
+ readonly query: string;
123
+ readonly version?: string | undefined;
124
+ readonly module?: string | undefined;
125
+ readonly path?: string | undefined;
126
+ readonly limit?: number | undefined;
127
+ }
128
+ /**
129
+ * Search the documentation tree of one chat workspace.
130
+ *
131
+ * Matching is a case-insensitive substring test, line by line: a hit is a
132
+ * line, not a file, so the caller can cite it. The result is bounded twice —
133
+ * by `limit` hits and by the byte budget of the rendered report — and reports
134
+ * which bound cut it short instead of silently dropping matches.
135
+ * @throws QaDocsError when the tree is missing, the scope path is wrong, or the query is empty.
136
+ */
137
+ export declare function searchDocumentation(root: QaDocsRoot, request: QaDocsSearchRequest): Promise<QaDocsSearchResult>;
138
+ export interface QaDocsReadRequest {
139
+ readonly path: string;
140
+ readonly from?: number | undefined;
141
+ readonly lines?: number | undefined;
142
+ }
143
+ /**
144
+ * Read one window of one documentation file of a chat workspace.
145
+ * @throws QaDocsError when the path is outside the tree, missing, a directory, or not text.
146
+ */
147
+ export declare function readDocumentation(root: QaDocsRoot, request: QaDocsReadRequest): Promise<QaDocsReadResult>;
148
+ /**
149
+ * `docs_search` — the catalog's documentation search.
150
+ *
151
+ * The description is part of the tool's job: it states that documentation is
152
+ * read here, from `docs/`, and that memory is not a documentation source. The
153
+ * version/module facets exist so a model that has been told "3.8" stops
154
+ * sweeping the whole tree.
155
+ */
156
+ export declare function createDocsSearchTool(): ToolDefinition;
157
+ /** `docs_read` — open one documentation file at a bounded window of lines. */
158
+ export declare function createDocsReadTool(): ToolDefinition;
159
+ export {};
160
+ //# sourceMappingURL=docs-tools.d.ts.map
@@ -14,6 +14,13 @@ export interface QaAccountsGate {
14
14
  readonly id: string;
15
15
  } | undefined;
16
16
  userWorkspace(userId: string, registeredWorkspacePath: string): string;
17
+ /**
18
+ * The account an ownership record names, for callers that authenticated by
19
+ * another credential (the integration API's service token). Absent records
20
+ * and a changed owner both resolve to undefined. Only read by that path: the
21
+ * token-shaped entry above remains the browser's only door.
22
+ */
23
+ ownerIdOf(sessionId: string): string | undefined;
17
24
  }
18
25
  /**
19
26
  * Host-side QA policy admission boundary — the only browser-callable operation
@@ -48,6 +55,11 @@ export declare class QaPolicyAdmission {
48
55
  */
49
56
  private readonly attested;
50
57
  private readonly workspaceAccess;
58
+ /**
59
+ * Removers of the document input roots granted per session, so a grant lives
60
+ * exactly as long as the workspace access that justified it.
61
+ */
62
+ private readonly documentInputRoots;
51
63
  /**
52
64
  * Every name reachable anywhere in one QA conversation, keyed by session id.
53
65
  *
@@ -67,7 +79,9 @@ export declare class QaPolicyAdmission {
67
79
  */
68
80
  dynamicToolNames?: (agent: Agent) => readonly string[],
69
81
  /** Resolve and freeze the session's subrole policy after its agent exists. */
70
- capabilityPolicy?: ((token: string, sessionId: string, agent: Agent) => Promise<QaResolvedSessionPolicy | undefined>) | undefined,
82
+ capabilityPolicy?: ((owner: {
83
+ readonly id: string;
84
+ } | undefined, sessionId: string, agent: Agent) => Promise<QaResolvedSessionPolicy | undefined>) | undefined,
71
85
  /** Scope-local catalog entries may be known before they are activated. */
72
86
  knownDynamicToolNames?: () => readonly string[]);
73
87
  /**
@@ -92,6 +106,20 @@ export declare class QaPolicyAdmission {
92
106
  * Source preview asks for the same root, so one upload is readable by both.
93
107
  */
94
108
  attachmentRoot(): string | undefined;
109
+ /**
110
+ * Carry the fence's attachment exemption into the document pipeline.
111
+ *
112
+ * The guard above lets a single-file read reach the mounted store: an upload
113
+ * is content-addressed, immutable, outside every workspace, and the prompt
114
+ * hands the model exactly that path. The document pipeline keeps its own
115
+ * read scope and knew nothing about the store, so `document_*` refused the
116
+ * very file the model was allowed to read — the panel's own Word preview hit
117
+ * the same wall. The grant is the missing hand-over, made where the access it
118
+ * mirrors is made: per admitted session, and revoked with it.
119
+ */
120
+ private grantDocumentInputRoots;
121
+ /** Drop one session's document grant, if the pipeline still holds it. */
122
+ private revokeDocumentInputRoots;
95
123
  /**
96
124
  * The live agent for one session, resuming a Session the Host has not
97
125
  * materialized in this process.
@@ -127,6 +155,29 @@ export declare class QaPolicyAdmission {
127
155
  */
128
156
  private accessOwner;
129
157
  secureSession(token: string, sessionId: string): Promise<QaLockdownProof>;
158
+ /**
159
+ * The same admission for a caller whose account was already authenticated by
160
+ * another credential — the integration API, which has a service token and no
161
+ * browser session.
162
+ *
163
+ * Everything after identity is shared with {@link secureSession}: the
164
+ * policy pins, the tool allow-list, the QA tool attachment and the
165
+ * attestation record are one code path, because a second copy of admission
166
+ * would be a second place for a QA chat to be admitted without them.
167
+ *
168
+ * @param userId - the account the credential resolved to.
169
+ * @param sessionId - the session to attest.
170
+ * @returns the sanitized proof, as the browser path receives it.
171
+ */
172
+ secureSessionForUser(userId: string, sessionId: string): Promise<QaLockdownProof>;
173
+ /**
174
+ * The account behind one session, taken from the ownership record rather
175
+ * than from a token. The record is the same authority the token path reads:
176
+ * a chat belongs to exactly one account, and a service token may only ever
177
+ * reach the chats of its own.
178
+ */
179
+ private ownerById;
180
+ private secureSessionAs;
130
181
  /**
131
182
  * Mask one agent down to the policy's tool names, for the account-free path
132
183
  * (a capability policy installs its own mask through its grants).
@@ -0,0 +1,24 @@
1
+ import type { QaServiceTokenScope } from "../types.js";
2
+ /**
3
+ * The integration-token vocabulary and its bounds, on the browser side too.
4
+ *
5
+ * Minting lives in `accounts/service-token.ts`, which is Host-only (it hashes a
6
+ * secret before storing it); the profile page has to name the same scopes and
7
+ * enforce the same limits without importing that. Dependency-free like every
8
+ * `src/shared` module, so both bundles inline it and there is exactly one copy
9
+ * of the numbers the two halves have to agree on.
10
+ */
11
+ /**
12
+ * What an integration token may do. Scopes are checked per request rather than
13
+ * per deployment: the same bridge token that asks questions carries no
14
+ * permission to read back a conversation catalog it was not given.
15
+ */
16
+ export declare const QA_SERVICE_TOKEN_SCOPES: readonly QaServiceTokenScope[];
17
+ /** A token that carries no scope is useless; `ask` is what the API is for. */
18
+ export declare const QA_SERVICE_TOKEN_DEFAULT_SCOPES: readonly QaServiceTokenScope[];
19
+ /** Bounds the caller's label; it is a note to the operator, not a document. */
20
+ export declare const QA_SERVICE_TOKEN_LABEL_MAX = 80;
21
+ /** Bounds on a token's life; zero or a negative TTL would mint a dead token. */
22
+ export declare const QA_SERVICE_TOKEN_TTL_DAYS_MIN = 1;
23
+ export declare const QA_SERVICE_TOKEN_TTL_DAYS_MAX = 3650;
24
+ //# sourceMappingURL=integration-tokens.d.ts.map
@@ -42,7 +42,9 @@ export interface QaQuestionAnswerItem {
42
42
  */
43
43
  export type QaAccountRole = "user" | "reviewer" | "admin";
44
44
  /** Administrative permissions a role grants; see `admin/permissions.ts`. */
45
- export type QaPermission = "users.read" | "users.manage" | "roles.read" | "roles.manage" | "conversations.read.all" | "conversations.read.own" | "conversations.delete" | "reviews.read" | "reviews.write" | "analytics.read" | "audit.read" | "settings.manage";
45
+ export type QaPermission = "users.read" | "users.manage" | "roles.read" | "roles.manage" | "conversations.read.all" | "conversations.read.own" | "conversations.delete" | "reviews.read" | "reviews.write" | "analytics.read" | "audit.read" | "settings.manage"
46
+ /** Write access to skill files, personal and deployment-wide. */
47
+ | "skills.manage";
46
48
  /**
47
49
  * Tools split by how they become available. `always` is visible from the first
48
50
  * model step; `skillGrantable` is a ceiling — the tool only appears once an
@@ -342,6 +344,21 @@ export interface QaAccountSession {
342
344
  readonly token: string;
343
345
  readonly user: QaAccountUserPublic;
344
346
  }
347
+ /**
348
+ * One forgotten-password request waiting for an operator. The address and name
349
+ * travel with it so the console can show who is locked out without a second
350
+ * lookup; the count makes repeated taps visible instead of silent.
351
+ */
352
+ export interface QaPasswordResetRequest {
353
+ readonly userId: string;
354
+ readonly email: string;
355
+ readonly displayName: string;
356
+ readonly requestedAt: string;
357
+ readonly lastRequestedAt: string;
358
+ readonly requestCount: number;
359
+ /** True when the account is disabled: a reset will not let it sign in. */
360
+ readonly disabled: boolean;
361
+ }
345
362
  export type QaWhoamiResult = {
346
363
  readonly authenticated: false;
347
364
  } | {
@@ -354,6 +371,45 @@ export interface QaClaimResult {
354
371
  /** Ids already owned by a different user; the browser drops them. */
355
372
  readonly conflicts: readonly string[];
356
373
  }
374
+ /**
375
+ * What an integration token may do. The credential a non-browser application
376
+ * presents to the QA HTTP API carries its own scopes, so a bridge that asks
377
+ * questions cannot read back conversations it was never given.
378
+ */
379
+ export type QaServiceTokenScope = "ask" | "sessions:read";
380
+ /**
381
+ * What an account asks a new integration token to be. The credential is always
382
+ * issued to the caller: nothing a browser sends may name another account, so
383
+ * this shape has no owner field and the administrative path keeps its own.
384
+ */
385
+ export interface QaServiceTokenCreateInput {
386
+ /** A note the operator's list shows; empty becomes a generic label. */
387
+ readonly label?: string;
388
+ /** Requested scopes; unknown ones are dropped, empty falls back to `ask`. */
389
+ readonly scopes?: readonly string[];
390
+ /** How long the token lives, in days; bounded server-side. */
391
+ readonly ttlDays?: number;
392
+ }
393
+ /**
394
+ * One integration token as a list shows it. The plaintext is deliberately not
395
+ * part of this shape: a secret is shown once, when it is minted, and a list
396
+ * that could repeat it would be a list that leaks it.
397
+ */
398
+ export interface QaServiceTokenSummary {
399
+ readonly id: string;
400
+ readonly label: string;
401
+ readonly scopes: readonly QaServiceTokenScope[];
402
+ readonly createdAt: string;
403
+ readonly expiresAt: string;
404
+ readonly lastUsedAt: string | null;
405
+ readonly revokedAt: string | null;
406
+ readonly useCount: number;
407
+ }
408
+ /** One freshly minted token: the plaintext is here and nowhere else, ever. */
409
+ export interface QaIssuedServiceToken extends QaServiceTokenSummary {
410
+ /** The credential to hand to the integration. Never recoverable later. */
411
+ readonly token: string;
412
+ }
357
413
  /**
358
414
  * One chat-ownership entry as the admin views see it: the session, its owner
359
415
  * and the owner's display name resolved at read time (a disabled account
@@ -388,6 +444,44 @@ export type QaSkillJsonValue = string | number | boolean | null | readonly QaSki
388
444
  readonly [key: string]: QaSkillJsonValue;
389
445
  };
390
446
  /** One personal skill as the catalog list renders it. */
447
+ /**
448
+ * One administrator write to a skill somebody else owns.
449
+ *
450
+ * The record exists so a personal skill cannot be changed behind its owner's
451
+ * back: it is written beside the skill and reported on every read, and the
452
+ * owner sees the badge in their own catalog.
453
+ */
454
+ export interface QaSkillAdminEdit {
455
+ /** Administrator account id; the owner is told a role, not a colleague. */
456
+ readonly actorId: string;
457
+ /** ISO timestamp of that write. */
458
+ readonly at: string;
459
+ }
460
+ /**
461
+ * Which skill store an administrator is editing: the deployment-wide shared
462
+ * root every account may read, or one account's own directory.
463
+ */
464
+ export type QaAdminSkillScope = {
465
+ readonly kind: "shared";
466
+ } | {
467
+ readonly kind: "user";
468
+ readonly userId: string;
469
+ };
470
+ /** The account a personal skill scope belongs to, for the console's header. */
471
+ export interface QaAdminSkillOwner {
472
+ readonly userId: string;
473
+ readonly email: string;
474
+ readonly displayName: string;
475
+ }
476
+ /** One scope's catalog plus the facts the console names it by. */
477
+ export interface QaAdminSkillsView {
478
+ readonly scope: QaAdminSkillScope;
479
+ /** The account of a personal scope; null for the shared root. */
480
+ readonly owner: QaAdminSkillOwner | null;
481
+ readonly skills: readonly QaSkillSummary[];
482
+ /** Absolute directory this scope reads and writes, rendered read-only. */
483
+ readonly rootPath: string;
484
+ }
391
485
  export interface QaSkillSummary {
392
486
  readonly name: string;
393
487
  readonly description: string;
@@ -405,6 +499,13 @@ export interface QaSkillSummary {
405
499
  readonly updatedAt: string | null;
406
500
  /** sha256 of the stored bytes; an update must echo the revision it read. */
407
501
  readonly revision: string;
502
+ /**
503
+ * The administrator write that produced the stored revision, or null. A
504
+ * record whose revision no longer matches is stale and reports as null: the
505
+ * badge answers "did an administrator write what I am looking at", not
506
+ * "was this skill ever touched by an administrator".
507
+ */
508
+ readonly adminEdit: QaSkillAdminEdit | null;
408
509
  }
409
510
  /** One personal skill with everything the editor needs. */
410
511
  export interface QaSkillDocument extends QaSkillSummary {
@@ -528,6 +629,16 @@ export interface QaNotesConfig {
528
629
  readonly enabled?: boolean;
529
630
  readonly template?: string;
530
631
  };
632
+ /** Which reader an attached office document belongs to; no placeholders. */
633
+ readonly documents?: {
634
+ readonly enabled?: boolean;
635
+ readonly template?: string;
636
+ };
637
+ /** Where an answer should come from; no placeholders. */
638
+ readonly sourcePriority?: {
639
+ readonly enabled?: boolean;
640
+ readonly template?: string;
641
+ };
531
642
  }
532
643
  export interface QaSurfaceConfig {
533
644
  readonly enabled?: boolean;
@@ -710,6 +821,42 @@ export interface QaSurfaceConfig {
710
821
  readonly sources?: QaSourcesConfig;
711
822
  readonly attachments?: QaAttachmentsConfig;
712
823
  readonly notes?: QaNotesConfig;
824
+ readonly integration?: QaIntegrationConfig;
825
+ }
826
+ /**
827
+ * The HTTP API an external application (the ticket bridge) uses to ask
828
+ * questions and read its own conversations back: `POST {basePath}/ask`,
829
+ * `GET {basePath}/session` and `GET {basePath}/health`, authenticated with an
830
+ * account's integration token. Off unless a deployment asks for it.
831
+ */
832
+ export interface QaIntegrationConfig {
833
+ /** Serve the API at all. Requires accounts; off by default. */
834
+ readonly enabled?: boolean;
835
+ /** Route namespace; the endpoints all hang off it. */
836
+ readonly basePath?: string;
837
+ /** Default lifetime of a minted integration token, in days. */
838
+ readonly tokenTtlDays?: number;
839
+ /**
840
+ * Wall-clock budget for one question. The endpoint answers within it — with
841
+ * an escalation when the turn is still running — because the caller's own
842
+ * timeout is a fixed part of the contract.
843
+ */
844
+ readonly requestTimeoutMs?: number;
845
+ /** Questions answered at the same time, deployment wide. */
846
+ readonly maxConcurrent?: number;
847
+ /** Requests one token may make per rolling minute. */
848
+ readonly requestsPerMinute?: number;
849
+ /** Ceiling on one request body, in bytes. */
850
+ readonly maxRequestBytes?: number;
851
+ /** Ceiling on one inline image attachment, in bytes. */
852
+ readonly maxAttachmentBytes?: number;
853
+ /**
854
+ * Ceiling on the published answer, in characters. The caller posts it into a
855
+ * ticket comment, whose own budget is smaller than a model's answer, and a
856
+ * cut made here at a paragraph boundary is readable where the caller's own
857
+ * silent truncation is not.
858
+ */
859
+ readonly maxAnswerCharacters?: number;
713
860
  }
714
861
  /** What a QA visitor may attach to one message. */
715
862
  export interface QaAttachmentsConfig {
@@ -913,6 +1060,25 @@ export interface ResolvedQaSurfaceConfig {
913
1060
  readonly enabled: boolean;
914
1061
  readonly template: string;
915
1062
  };
1063
+ readonly documents: {
1064
+ readonly enabled: boolean;
1065
+ readonly template: string;
1066
+ };
1067
+ readonly sourcePriority: {
1068
+ readonly enabled: boolean;
1069
+ readonly template: string;
1070
+ };
1071
+ };
1072
+ readonly integration: {
1073
+ readonly enabled: boolean;
1074
+ readonly basePath: string;
1075
+ readonly tokenTtlDays: number;
1076
+ readonly requestTimeoutMs: number;
1077
+ readonly maxConcurrent: number;
1078
+ readonly requestsPerMinute: number;
1079
+ readonly maxRequestBytes: number;
1080
+ readonly maxAttachmentBytes: number;
1081
+ readonly maxAnswerCharacters: number;
916
1082
  };
917
1083
  }
918
1084
  /** Which native registry one unified entry came from. */
@@ -1444,7 +1610,9 @@ export interface QaReviewQueueItem {
1444
1610
  readonly feedbackId?: string;
1445
1611
  }
1446
1612
  /** Administrative actions recorded in the audit trail. */
1447
- export type QaAdminAuditAction = "user.created" | "user.updated" | "user.enabled" | "user.disabled" | "authorization.changed" | "subrole.assignment.changed" | "subrole.created" | "subrole.updated" | "subrole.deleted" | "common_capabilities.updated" | "conversation.reviewed" | "conversation.deleted" | "review.updated" | "review.queued" | "admin.settings.updated";
1613
+ export type QaAdminAuditAction = "user.created" | "user.updated" | "user.enabled" | "user.disabled"
1614
+ /** An operator set a new password for an account that had requested one. */
1615
+ | "user.password-reset" | "authorization.changed" | "subrole.assignment.changed" | "subrole.created" | "subrole.updated" | "subrole.deleted" | "common_capabilities.updated" | "conversation.reviewed" | "conversation.deleted" | "review.updated" | "review.queued" | "admin.settings.updated" | "skill.created" | "skill.updated" | "skill.deleted";
1448
1616
  export interface QaAdminAuditEvent {
1449
1617
  readonly id: string;
1450
1618
  readonly timestamp: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yadsh/dsh-qa-surface",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "A focused end-user QA surface backed by native DeepSeek Harness sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -135,9 +135,9 @@
135
135
  "zod": "^4.4.3",
136
136
  "@yadsh/dsh-audit-core": "^0.1.0",
137
137
  "@yadsh/dsh-audit-ui": "^0.1.0",
138
- "@yadsh/dsh-documents": "^0.4.0",
139
- "@yadsh/dsh-plugin-kit": "^0.3.0",
140
- "@yadsh/dsh-plugin-log": "^0.4.0"
138
+ "@yadsh/dsh-documents": "^0.5.0",
139
+ "@yadsh/dsh-plugin-log": "^0.4.0",
140
+ "@yadsh/dsh-plugin-kit": "^0.3.0"
141
141
  },
142
142
  "devDependencies": {
143
143
  "@deepseek-ai/cordis": "4.0.2",