@yadsh/dsh-qa-surface 0.15.0 → 0.15.2

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 (86) hide show
  1. package/README.md +1 -1
  2. package/lib/attestation.js.map +1 -1
  3. package/lib/client/QaAccountsController.js +3 -1
  4. package/lib/client/QaAccountsController.js.map +1 -1
  5. package/lib/client/QaSessionController.js +66 -18
  6. package/lib/client/QaSessionController.js.map +1 -1
  7. package/lib/client/QaSurface.js +64 -2
  8. package/lib/client/QaSurface.js.map +1 -1
  9. package/lib/client/QaTranscriptAdapter.js +46 -8
  10. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  11. package/lib/client/admin/pages/Users.js +2 -2
  12. package/lib/client/admin/pages/Users.js.map +1 -1
  13. package/lib/client/attestation.js +13 -5
  14. package/lib/client/attestation.js.map +1 -1
  15. package/lib/client/chat-artifacts.js +94 -0
  16. package/lib/client/chat-artifacts.js.map +1 -0
  17. package/lib/client/chat-files.js +24 -14
  18. package/lib/client/chat-files.js.map +1 -1
  19. package/lib/client/clipboard.js +86 -0
  20. package/lib/client/clipboard.js.map +1 -0
  21. package/lib/client/components/QaAuthGate.js +41 -5
  22. package/lib/client/components/QaAuthGate.js.map +1 -1
  23. package/lib/client/components/QaChangelog.js +46 -0
  24. package/lib/client/components/QaChangelog.js.map +1 -1
  25. package/lib/client/components/QaFileAttachment.js +7 -5
  26. package/lib/client/components/QaFileAttachment.js.map +1 -1
  27. package/lib/client/components/QaFilesPanel.js +7 -3
  28. package/lib/client/components/QaFilesPanel.js.map +1 -1
  29. package/lib/client/components/QaMessage.js +27 -24
  30. package/lib/client/components/QaMessage.js.map +1 -1
  31. package/lib/client/components/QaWorkspaceBrowser.js +18 -25
  32. package/lib/client/components/QaWorkspaceBrowser.js.map +1 -1
  33. package/lib/client/components/copy-hint.js +10 -0
  34. package/lib/client/components/copy-hint.js.map +1 -0
  35. package/lib/client/markdown/CodeBlock.js +6 -13
  36. package/lib/client/markdown/CodeBlock.js.map +1 -1
  37. package/lib/client/project-session-state.js +1 -0
  38. package/lib/client/project-session-state.js.map +1 -1
  39. package/lib/client/settings/styles.js +1 -1
  40. package/lib/client/styles.js +11 -4
  41. package/lib/client/styles.js.map +1 -1
  42. package/lib/client/user-settings/IntegrationTokensPage.js +4 -12
  43. package/lib/client/user-settings/IntegrationTokensPage.js.map +1 -1
  44. package/lib/client/user-settings/PasswordSettingsPage.js +6 -1
  45. package/lib/client/user-settings/PasswordSettingsPage.js.map +1 -1
  46. package/lib/client/workspace-download.js +32 -0
  47. package/lib/client/workspace-download.js.map +1 -0
  48. package/lib/client/workspace-paths.js +55 -0
  49. package/lib/client/workspace-paths.js.map +1 -0
  50. package/lib/client.js +727 -253
  51. package/lib/client.js.map +1 -1
  52. package/lib/index.js +15 -1
  53. package/lib/index.js.map +1 -1
  54. package/lib/integration/answer.js +53 -0
  55. package/lib/integration/answer.js.map +1 -1
  56. package/lib/integration/host-runner.js +85 -13
  57. package/lib/integration/host-runner.js.map +1 -1
  58. package/lib/integration/service.js +12 -0
  59. package/lib/integration/service.js.map +1 -1
  60. package/lib/secure-session.js +36 -11
  61. package/lib/secure-session.js.map +1 -1
  62. package/lib/typert.host.js +75 -75
  63. package/lib/typert.remote-client.js +75 -75
  64. package/lib/types/attestation.d.ts +1 -1
  65. package/lib/types/client/QaAccountsController.d.ts +2 -0
  66. package/lib/types/client/QaSessionController.d.ts +13 -0
  67. package/lib/types/client/QaTranscriptAdapter.d.ts +6 -0
  68. package/lib/types/client/attestation.d.ts +7 -3
  69. package/lib/types/client/chat-artifacts.d.ts +10 -0
  70. package/lib/types/client/chat-files.d.ts +13 -8
  71. package/lib/types/client/clipboard.d.ts +37 -0
  72. package/lib/types/client/components/QaAuthGate.d.ts +4 -2
  73. package/lib/types/client/components/QaFileAttachment.d.ts +13 -4
  74. package/lib/types/client/components/QaFilesPanel.d.ts +15 -2
  75. package/lib/types/client/components/QaMessage.d.ts +10 -2
  76. package/lib/types/client/components/QaWorkspaceBrowser.d.ts +7 -1
  77. package/lib/types/client/components/copy-hint.d.ts +8 -0
  78. package/lib/types/client/project-session-state.d.ts +5 -0
  79. package/lib/types/client/workspace-download.d.ts +16 -0
  80. package/lib/types/client/workspace-paths.d.ts +25 -0
  81. package/lib/types/integration/answer.d.ts +17 -0
  82. package/lib/types/integration/contract.d.ts +13 -0
  83. package/lib/types/secure-session.d.ts +4 -0
  84. package/lib/types/types.d.ts +26 -0
  85. package/locale/en.json +6 -0
  86. package/package.json +4 -2
@@ -8,7 +8,7 @@
8
8
  * audience, precise enough for an operator reading the browser console to
9
9
  * know which class of mismatch to look for in the Host logs.
10
10
  */
11
- export type QaAttestationReason = "agent-unavailable" | "workspace-unavailable" | "composition-mismatch" | "permission-preset" | "adoption-refused" | "unknown-tools" | "auth-required" | "session-owned-elsewhere" | "attestation-failed";
11
+ export type QaAttestationReason = "agent-unavailable" | "workspace-unavailable" | "composition-mismatch" | "permission-preset" | "adoption-refused" | "subagent-session" | "unknown-tools" | "auth-required" | "session-owned-elsewhere" | "attestation-failed";
12
12
  /** Host rejection whose wire message carries the coarse reason marker. */
13
13
  export declare class QaAttestationError extends Error {
14
14
  readonly reason: QaAttestationReason;
@@ -38,6 +38,8 @@ export type QaAccountsSnapshot = {
38
38
  /** Bumped whenever ownedIds, ownIds or ownership changes; re-projects lists. */
39
39
  readonly ownedRevision: number;
40
40
  };
41
+ /** The Host's password floor, mirrored so a refusal and its copy name the same number. */
42
+ export declare const QA_MIN_PASSWORD_LENGTH = 8;
41
43
  /**
42
44
  * Audience-safe copy for the coarse account refusal codes; anything unknown
43
45
  * falls back to the generic line.
@@ -307,6 +307,19 @@ export declare class QaSessionController {
307
307
  * so this call gives up rather than taking the chat back from under them.
308
308
  */
309
309
  startDraft(policyChange?: boolean): Promise<void>;
310
+ /**
311
+ * Put a draft on the screen: no session, so no row in the chat history and no
312
+ * record on the Host until the first prompt is sent into it
313
+ * ({@link materializeDraft}). Taking the screen moves the generation the way
314
+ * every other path that retires a binding does, so an adoption still looking
315
+ * for its session measures itself against the new one rather than taking the
316
+ * draft back.
317
+ *
318
+ * A draft that is already on screen — including one whose first send fell
319
+ * short of a claimed session — keeps its chat identity: a new one rebuilds the
320
+ * composer over the question the visitor is still looking at.
321
+ */
322
+ private enterDraft;
310
323
  /**
311
324
  * Turn the current draft into a real attested session. Runs inside the
312
325
  * send flow; concurrent sends share one materialization. A refusal here is
@@ -20,5 +20,11 @@ export declare function projectTranscript(snapshot: ConversationSnapshot | undef
20
20
  readonly subagentNames?: Readonly<Record<string, string>>;
21
21
  /** Sign settlement titles with deterministic codenames. */
22
22
  readonly subagentCodenames?: boolean;
23
+ /**
24
+ * The chat's own workspace directory. An answer that quotes a file inside it
25
+ * is made to name the file instead of locating it, because the directory is
26
+ * the account's own and the transcript is not where that layout belongs.
27
+ */
28
+ readonly workspaceRoot?: string;
23
29
  }): readonly QaMessage[];
24
30
  //# sourceMappingURL=QaTranscriptAdapter.d.ts.map
@@ -10,9 +10,13 @@ export declare class QaPolicyAttestationError extends Error {
10
10
  constructor(reason?: string | null);
11
11
  }
12
12
  /**
13
- * Refusals that classify an existing chat as belonging to an older deployment
14
- * composition. Keeping only its transcript is safe: policyReady stays false,
15
- * so no prompt, cancel, approval or question reaches the Host.
13
+ * Refusals that classify what the browser has on screen rather than faulting
14
+ * the deployment: an existing chat of an older composition, one adopted from
15
+ * outside the QA policy, or an identity that is another conversation's
16
+ * delegated child.
17
+ *
18
+ * Keeping only its transcript is safe: policyReady stays false, so no prompt,
19
+ * cancel, approval or question reaches the Host.
16
20
  */
17
21
  export declare function canOpenAsCompatibilityReadOnly(reason: string | null): boolean;
18
22
  /**
@@ -0,0 +1,10 @@
1
+ import type { QaArtifactView } from "../types.js";
2
+ /**
3
+ * The files one tool result reports, in the order it reported them.
4
+ *
5
+ * `output` is the rendered text of the result, the same string the work group
6
+ * shows. A tool that produced nothing — a read, an inspection, a refusal — is
7
+ * not in the table and contributes no artifact.
8
+ */
9
+ export declare function artifactsOfToolResult(toolName: string, output: string | null): readonly QaArtifactView[];
10
+ //# sourceMappingURL=chat-artifacts.d.ts.map
@@ -2,27 +2,32 @@
2
2
  * Attachment roster of one chat, projected for the rail's files tab: a pure
3
3
  * scan of the durable transcript, newest message first. The browser never
4
4
  * re-reads file bytes (the attachment route serves images), so a row keeps
5
- * the same handle the transcript shows.
5
+ * the same handle the transcript shows. A document the agent produced is on
6
+ * the same roster: it is an attachment of the conversation, sent by the answer
7
+ * rather than by the question.
6
8
  */
7
- import type { QaImageView, QaMessage, QaFileView } from "../types.js";
8
- /** Everything one user message carried, in prompt order. */
9
+ import type { QaArtifactView, QaImageView, QaMessage, QaFileView } from "../types.js";
10
+ /** Everything one message carried, in prompt order. */
9
11
  export interface QaChatFileGroup {
10
- /** The user message id; doubles as the transcript turn anchor. */
12
+ /** The message id; doubles as the transcript turn anchor. */
11
13
  readonly messageId: string;
12
14
  /** Wall-clock send time, when the transcript recorded one. */
13
15
  readonly timestamp?: number;
14
16
  readonly files: readonly QaFileView[];
15
17
  readonly images: readonly QaImageView[];
18
+ /** Files this message's turn produced; empty on a message that made none. */
19
+ readonly artifacts?: readonly QaArtifactView[];
16
20
  }
17
21
  /** Whether the group carries at least one attachment of either kind. */
18
22
  export declare function groupHasAttachments(group: QaChatFileGroup): boolean;
19
23
  /** Total attachment count across groups; the header badge's value. */
20
24
  export declare function countChatAttachments(groups: readonly QaChatFileGroup[]): number;
21
25
  /**
22
- * Group the attachments of a chat by their sending message. Only user
23
- * messages can carry attachments; messages without any are dropped. The
24
- * groups come out newest message first, matching a roster that grows at the
25
- * bottom of the transcript.
26
+ * Group the attachments of a chat by their message. A user message carries what
27
+ * was sent; an assistant message carries what its turn produced, so a document
28
+ * the stand made is listed beside the answer that made it instead of only on
29
+ * disk. Messages with neither are dropped. The groups come out newest first,
30
+ * matching a roster that grows at the bottom of the transcript.
26
31
  * @param messages - the chat's projected transcript.
27
32
  * @returns attachment groups, newest message first.
28
33
  */
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The sentence a copy affordance falls back to. Three surfaces show it, so it
3
+ * is written once.
4
+ */
5
+ export declare const COPY_MANUAL_HINT = "\u0421\u043A\u043E\u043F\u0438\u0440\u0443\u0439\u0442\u0435 \u0432\u0440\u0443\u0447\u043D\u0443\u044E";
6
+ /** The reason behind that sentence, for the tooltip that carries it. */
7
+ export declare const COPY_MANUAL_REASON = "\u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442 \u0431\u0443\u0444\u0435\u0440 \u043E\u0431\u043C\u0435\u043D\u0430 \u2014 \u0432\u044B\u0434\u0435\u043B\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442 \u0438 \u0441\u043A\u043E\u043F\u0438\u0440\u0443\u0439\u0442\u0435 \u0435\u0433\u043E \u0441\u0430\u043C\u0438";
8
+ /**
9
+ * Whether this browser can be asked to move text at all.
10
+ *
11
+ * The stand is served over plain HTTP on a LAN address, and there
12
+ * `window.isSecureContext` is false, so the browser never exposes
13
+ * `navigator.clipboard`. The older copy command still works in an insecure
14
+ * context, which is why the absence of the API alone does not mean copying is
15
+ * impossible — and why asking for it anyway only ends in silence.
16
+ */
17
+ export declare function canCopyToClipboard(): boolean;
18
+ /**
19
+ * Write `text` to the clipboard, resolving false when nothing took it — the
20
+ * caller then says so instead of leaving the click unanswered.
21
+ */
22
+ export declare function copyTextToClipboard(text: string): Promise<boolean>;
23
+ export interface CopyAction {
24
+ /** The flash that says the text left. */
25
+ readonly copied: boolean;
26
+ /** Nothing can copy here: the affordance is a hint, not a button. */
27
+ readonly impossible: boolean;
28
+ /** A copy was attempted and refused: the hint joins a still-live button. */
29
+ readonly refused: boolean;
30
+ readonly copy: () => void;
31
+ }
32
+ /**
33
+ * The copy state one affordance needs: the confirmation window, the refusal
34
+ * that replaces it with the manual hint, and whether either was ever possible.
35
+ */
36
+ export declare function useCopyAction(text: string): CopyAction;
37
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { QaAccountsController, QaAccountsSnapshot } from "../QaAccountsController.js";
1
+ import { type QaAccountsController, type QaAccountsSnapshot } from "../QaAccountsController.js";
2
2
  export interface QaAuthGateProps {
3
3
  readonly accounts: QaAccountsController;
4
4
  readonly snapshot: QaAccountsSnapshot;
@@ -9,7 +9,9 @@ export interface QaAuthGateProps {
9
9
  /**
10
10
  * The full-frame login/registration card shown instead of the chat while the
11
11
  * deployment has accounts enabled and the browser holds no valid identity.
12
- * Errors are the Host's coarse codes rendered as audience-safe copy.
12
+ * Both halves of the error line are the surface's own copy: the card refuses
13
+ * mistyped input itself, and renders the Host's coarse codes in audience-safe
14
+ * words.
13
15
  */
14
16
  export declare function QaAuthGate(props: QaAuthGateProps): import("react/jsx-runtime").JSX.Element;
15
17
  //# sourceMappingURL=QaAuthGate.d.ts.map
@@ -4,11 +4,20 @@ export interface QaFileAttachmentProps {
4
4
  /** Pending draft vs already-sent attachment: pending offers removal. */
5
5
  readonly tone: "draft" | "sent";
6
6
  readonly onRemove?: () => void;
7
+ /**
8
+ * Open the file: shown as a control on a card the reader can act on, and left
9
+ * out of a chip that only records what was sent. A produced document is
10
+ * worthless as a name in a paragraph — this is what makes it reachable.
11
+ */
12
+ readonly onOpen?: () => void;
13
+ /** Save the file. Omitted where the surface cannot read its bytes. */
14
+ readonly onDownload?: () => void;
7
15
  }
8
16
  /**
9
- * One attached file: the extension badge, the name and the size. The browser
10
- * never re-reads these bytes (the Host attachment route serves images), so
11
- * both the composer card and the sent row show the same handle.
17
+ * One attached file: the extension badge, the name and the size. A sent
18
+ * attachment shows the handle the model resolved and the browser never
19
+ * re-reads its bytes; a file the turn produced is the same chip with the two
20
+ * controls that make it usable — open it, or take it away.
12
21
  */
13
- export declare function QaFileAttachment({ name, bytes, tone, onRemove, }: QaFileAttachmentProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function QaFileAttachment({ name, bytes, tone, onRemove, onOpen, onDownload, }: QaFileAttachmentProps): import("react/jsx-runtime").JSX.Element;
14
23
  //# sourceMappingURL=QaFileAttachment.d.ts.map
@@ -1,11 +1,24 @@
1
+ import type { QaArtifactView } from "../../types.js";
1
2
  import type { QaChatFileGroup } from "../chat-files.js";
2
3
  import type { QaBoundSourceApi } from "../types.js";
3
4
  export interface QaFilesPanelProps {
4
5
  readonly groups: readonly QaChatFileGroup[];
5
6
  /** Resolve an image attachment to an object URL; images render inert without it. */
6
7
  readonly resolveImage?: (attachmentId: string) => Promise<string>;
7
- /** Scroll the transcript to the group's user message. */
8
+ /** Scroll the transcript to the group's message. */
8
9
  readonly onJumpToMessage: (messageId: string) => void;
10
+ /** Open a produced file in the workspace viewer below. */
11
+ readonly onArtifactOpen?: (artifact: QaArtifactView) => void;
12
+ /** Save a produced file. */
13
+ readonly onArtifactDownload?: (artifact: QaArtifactView) => void;
14
+ /**
15
+ * A file to open on arrival, with the request's stamp. The stamp is what makes
16
+ * a second click on the same card a fresh arrival rather than a no-op.
17
+ */
18
+ readonly openArtifact?: {
19
+ readonly path: string;
20
+ readonly stamp: number;
21
+ };
9
22
  /**
10
23
  * The chat whose workspace the panel browses. Both this and `api` are absent
11
24
  * in hosts that only render the attachment roster, where the workspace
@@ -23,5 +36,5 @@ export interface QaFilesPanelProps {
23
36
  * back, and the images are the same object URLs the message showed — while the
24
37
  * workspace section reads through the Host's fenced listing and file reads.
25
38
  */
26
- export declare function QaFilesPanel({ groups, resolveImage, onJumpToMessage, sessionId, api, }: QaFilesPanelProps): import("react/jsx-runtime").JSX.Element;
39
+ export declare function QaFilesPanel({ groups, resolveImage, onJumpToMessage, onArtifactOpen, onArtifactDownload, openArtifact, sessionId, api, }: QaFilesPanelProps): import("react/jsx-runtime").JSX.Element;
27
40
  //# sourceMappingURL=QaFilesPanel.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { QaMessage as QaMessageModel, QaSource, QaTurnSources } from "../../types.js";
1
+ import type { QaArtifactView, QaMessage as QaMessageModel, QaSource, QaTurnSources } from "../../types.js";
2
2
  import type { QaFeedbackReason } from "../../types.js";
3
3
  export interface QaMessageProps {
4
4
  readonly message: QaMessageModel;
@@ -17,6 +17,14 @@ export interface QaMessageProps {
17
17
  readonly onOpenSources?: (sources: readonly QaSource[], complete: boolean, incompleteOrigins: QaTurnSources["incompleteOrigins"]) => void;
18
18
  /** Open one path-backed source's detail (an inline footnote click). */
19
19
  readonly onSourceDetail?: (source: QaSource) => void;
20
+ /**
21
+ * Open a file this answer's turn produced, in the surface's own file viewer.
22
+ * Omitted where the surface has no workspace reads: the card then names the
23
+ * file without promising an action it cannot perform.
24
+ */
25
+ readonly onArtifactOpen?: (artifact: QaArtifactView) => void;
26
+ /** Save a produced file. Omitted on the same terms as {@link onArtifactOpen}. */
27
+ readonly onArtifactDownload?: (artifact: QaArtifactView) => void;
20
28
  /** Operator-configured running phrases; omitted reads the built-in list. */
21
29
  readonly thinkingPhrases?: readonly string[];
22
30
  /**
@@ -39,5 +47,5 @@ export interface QaMessageProps {
39
47
  * comparison is by value, which keeps fresh copies cheap to recognize.
40
48
  */
41
49
  export declare function sameMessage(a: QaMessageModel, b: QaMessageModel): boolean;
42
- export declare const QaMessage: import("react").MemoExoticComponent<({ message, renderMarkdown, showTimestamp, stateKey, onRegenerate, resolveImage, onOpenSources, onSourceDetail, thinkingPhrases, onRateFeedback, }: QaMessageProps) => import("react/jsx-runtime").JSX.Element>;
50
+ export declare const QaMessage: import("react").MemoExoticComponent<({ message, renderMarkdown, showTimestamp, stateKey, onRegenerate, resolveImage, onOpenSources, onSourceDetail, onArtifactOpen, onArtifactDownload, thinkingPhrases, onRateFeedback, }: QaMessageProps) => import("react/jsx-runtime").JSX.Element>;
43
51
  //# sourceMappingURL=QaMessage.d.ts.map
@@ -2,6 +2,12 @@ import type { QaBoundSourceApi } from "../types.js";
2
2
  export interface QaWorkspaceBrowserProps {
3
3
  readonly sessionId: string;
4
4
  readonly api: QaBoundSourceApi;
5
+ /**
6
+ * A file to open on arrival, named inside this workspace. The card under an
7
+ * answer points here rather than carrying its own viewer, so a document the
8
+ * turn produced previews through the same fenced read a browsed file uses.
9
+ */
10
+ readonly initialFile?: string;
5
11
  }
6
12
  /**
7
13
  * The chat's own workspace, browsable from the files rail.
@@ -11,5 +17,5 @@ export interface QaWorkspaceBrowserProps {
11
17
  * the visitor out of the chat's tree. Opening a file reads exactly that file —
12
18
  * text inline, anything else as a downloadable handle.
13
19
  */
14
- export declare function QaWorkspaceBrowser({ sessionId, api, }: QaWorkspaceBrowserProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function QaWorkspaceBrowser({ sessionId, api, initialFile, }: QaWorkspaceBrowserProps): import("react/jsx-runtime").JSX.Element;
15
21
  //# sourceMappingURL=QaWorkspaceBrowser.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What a copy affordance shows where copying cannot happen: a sentence naming
3
+ * the dead end, in place of a button that would only look like it worked.
4
+ */
5
+ export declare function CopyHint({ testId }: {
6
+ readonly testId: string;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=copy-hint.d.ts.map
@@ -25,6 +25,11 @@ export interface QaBoundProjectionInput {
25
25
  readonly conversationSnapshot: ConversationSnapshot | undefined;
26
26
  /** Turn bundles with the Host provenance already merged in (Host wins). */
27
27
  readonly sourceBundles: readonly QaTurnSources[];
28
+ /**
29
+ * The chat's own workspace directory, as the Host records it for this session.
30
+ * The transcript masks it out of an answer that quotes a file it made.
31
+ */
32
+ readonly workspaceRoot?: string;
28
33
  /**
29
34
  * Tool calls and questions the Host parked for the operator. They are
30
35
  * host-side state, not part of the session snapshot, so the controller polls
@@ -0,0 +1,16 @@
1
+ import type { QaWorkspaceFile } from "../types.js";
2
+ /**
3
+ * Handing one file of the chat's workspace to the person the turn produced it
4
+ * for.
5
+ *
6
+ * A produced document has no attachment-store handle, so the download is the
7
+ * same read the files panel does: the Host returns the bytes under the chat's
8
+ * own fence and the browser's download machinery takes it from there. Shared by
9
+ * the panel's own viewer and the card under an answer, so the two can never
10
+ * disagree on what "download this file" means.
11
+ */
12
+ /** Last segment of a workspace path, spelled with either separator. */
13
+ export declare function fileNameOf(path: string): string;
14
+ /** Save one workspace file under its own name. */
15
+ export declare function downloadWorkspaceFile(file: QaWorkspaceFile): void;
16
+ //# sourceMappingURL=workspace-download.d.ts.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The answer a person reads names a file; it does not say where the deployment
3
+ * keeps the account that owns it.
4
+ *
5
+ * A model quoting a path it was handed prints it verbatim, and a QA deployment
6
+ * gives every account its own directory under one parent, so a path printed
7
+ * with an account identifier is a sentence about the server's layout — and
8
+ * stand dialogs below the signature line can be read by other accounts of the
9
+ * same server. The document tools report workspace-relative names, which is
10
+ * the fix at the source; this is the repair for what still arrives, because a
11
+ * model can rebuild a path from the working directory it is told, and because
12
+ * an answer written before this change is durable history that replays.
13
+ */
14
+ /**
15
+ * Rewrite an answer so a file is named, not located.
16
+ *
17
+ * The chat's own workspace directory is removed, leaving the spelling the
18
+ * producing tool reports and the workspace reads take. The per-account
19
+ * partition is removed the same way even when the root is unknown or the answer
20
+ * quotes a different chat's file, because the identifier inside it is the part
21
+ * that must not be printed. A path that is neither of those is left alone: a
22
+ * reader told about `/etc/hosts` is not being shown this deployment's layout.
23
+ */
24
+ export declare function toWorkspaceRelativeText(text: string, workspaceRoot: string | undefined): string;
25
+ //# sourceMappingURL=workspace-paths.d.ts.map
@@ -79,6 +79,23 @@ export declare function answerForRequest(events: readonly StoredSessionEvent[],
79
79
  * @returns the answer, or its bounded head.
80
80
  */
81
81
  export declare function boundAnswer(answer: string, maxCharacters: number): string;
82
+ /**
83
+ * Whether the turn one request's prompt opened is still the turn the agent runs.
84
+ *
85
+ * A caller that gives up may only stop its own work. Prompts are admitted with
86
+ * `mode: "queue"`, so a chat can hold several questions at once and the running
87
+ * turn belongs to whoever the harness claimed — not to whoever asked first, and
88
+ * not to whoever is still holding a connection. The two facts the log gives
89
+ * back decide it: this request's row exists only once the harness has claimed
90
+ * the prompt into a turn, and a turn that has committed its closer (or been
91
+ * followed by a newer `turn/start`, since the harness runs one turn at a time)
92
+ * is over whatever the agent is busy with afterwards.
93
+ *
94
+ * @param events - durable events, in any order.
95
+ * @param requestId - the rpc id the prompt was submitted with.
96
+ * @returns true only when a claimed row of this request has no closer yet.
97
+ */
98
+ export declare function ownTurnStillRunning(events: readonly StoredSessionEvent[], requestId: string): boolean;
82
99
  /**
83
100
  * The seq of the newest user-authored message in a log, which is the floor
84
101
  * `answerAfter` reads from when the caller has just submitted a prompt.
@@ -168,7 +168,20 @@ export interface QaIntegrationRunner {
168
168
  readonly message: string;
169
169
  readonly attachments: readonly QaIntegrationAttachment[];
170
170
  readonly ticketKey: string | null;
171
+ /** Ends the wait: the caller's own lifetime merged with the deployment's budget. */
171
172
  readonly signal: AbortSignal;
173
+ /**
174
+ * The caller's lifetime alone, without the budget.
175
+ *
176
+ * The two ends of `signal` mean different things. A question that ran out of
177
+ * `requestTimeoutMs` still has a reader: the escalation hands back the chat
178
+ * id and the bridge picks the answer up from the session read, so the turn
179
+ * has to keep going. A question whose caller disconnected has none, and the
180
+ * turn is stopped. Absent when the caller cannot tell the two apart, which
181
+ * leaves every abandoned turn running — the safe direction, because a turn
182
+ * stopped for the wrong reason cannot be asked again.
183
+ */
184
+ readonly callerSignal?: AbortSignal;
172
185
  /**
173
186
  * Called as soon as the chat exists, before the turn settles. The service
174
187
  * needs the identity of a chat whose answer it may never see: a timed-out
@@ -137,6 +137,10 @@ export declare class QaPolicyAdmission {
137
137
  * composition a stock prompt would produce — so a session composed outside
138
138
  * the QA preset still lands on the mismatch refusals below ("composition
139
139
  * mismatch"), and the adoption and permission checks stay the gate.
140
+ *
141
+ * Not every answer describes this deployment: the Host also names an identity
142
+ * subagent routing owns, which is a fact about another conversation's child
143
+ * rather than a failure of this chat. The two are refused apart below.
140
144
  */
141
145
  private liveAgent;
142
146
  /**
@@ -1480,6 +1480,13 @@ export type QaMessage = QaUserMessage | {
1480
1480
  readonly seq?: number;
1481
1481
  /** Host turn this answer belongs to; groups regenerations into variants. */
1482
1482
  readonly turn?: number;
1483
+ /**
1484
+ * Files this turn produced, in the order the tools reported them. The
1485
+ * answer is the place a produced document is handed over: without these
1486
+ * cards the file exists only as a path in a paragraph, and a reader who
1487
+ * does not know the files tab concludes nothing was made.
1488
+ */
1489
+ readonly artifacts?: readonly QaArtifactView[];
1483
1490
  /** Canonical evidence snapshot shared with this answer's source drawer. */
1484
1491
  readonly sources?: readonly QaSource[];
1485
1492
  readonly sourcesComplete?: boolean;
@@ -1589,6 +1596,25 @@ export interface QaFileView {
1589
1596
  readonly name: string;
1590
1597
  readonly bytes: number;
1591
1598
  }
1599
+ /**
1600
+ * A file the agent produced during the turn an answer belongs to.
1601
+ *
1602
+ * An artifact has no attachment-store handle: it lives in the chat's own
1603
+ * workspace, so the card addresses it by the workspace-relative name the
1604
+ * producing tool reported — the spelling the Host's workspace reads take, and
1605
+ * the only spelling that cannot publish the directory the deployment keeps
1606
+ * this account's workspace in.
1607
+ */
1608
+ export interface QaArtifactView {
1609
+ /** Workspace-relative path; the identifier every workspace read takes. */
1610
+ readonly path: string;
1611
+ /** Last path segment, which is what the card is signed with. */
1612
+ readonly name: string;
1613
+ /** Format the producing tool reported: `docx`, `pdf` or `md`. */
1614
+ readonly format: string;
1615
+ /** Bytes as the tool reported them; 0 when it reported no size. */
1616
+ readonly bytes: number;
1617
+ }
1592
1618
  /** A subagent transcript opened read-only from the agents panel. */
1593
1619
  export interface QaSubagentView {
1594
1620
  readonly id: string;
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "QA Surface",
4
+ "description": "A focused end-user QA surface backed by native DeepSeek Harness sessions"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yadsh/dsh-qa-surface",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "A focused end-user QA surface backed by native DeepSeek Harness sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,6 +48,7 @@
48
48
  "types": "./lib/types/types.d.ts",
49
49
  "default": "./lib/types.js"
50
50
  },
51
+ "./locale/en.json": "./locale/en.json",
51
52
  "./package.json": "./package.json"
52
53
  },
53
54
  "dsh": {
@@ -78,6 +79,7 @@
78
79
  "lib/**/*.d.ts",
79
80
  "scripts/repair-session-events.mjs",
80
81
  "scripts/attach-workspace-sessions.mjs",
82
+ "locale/*.json",
81
83
  "cordis.patch.yml",
82
84
  "compatibility.json",
83
85
  "capability-policy.json",
@@ -135,8 +137,8 @@
135
137
  "zod": "^4.4.3",
136
138
  "@yadsh/dsh-audit-core": "^0.1.1",
137
139
  "@yadsh/dsh-audit-ui": "^0.1.2",
138
- "@yadsh/dsh-documents": "^0.6.1",
139
140
  "@yadsh/dsh-plugin-kit": "^0.5.0",
141
+ "@yadsh/dsh-documents": "^0.6.2",
140
142
  "@yadsh/dsh-plugin-log": "^0.4.1"
141
143
  },
142
144
  "devDependencies": {