@yadsh/dsh-qa-surface 0.2.0 → 0.4.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.
- package/README.md +236 -20
- package/compatibility.json +11 -3
- package/lib/account-remotes.js +100 -0
- package/lib/account-remotes.js.map +1 -0
- package/lib/accounts/credentials.js +27 -0
- package/lib/accounts/credentials.js.map +1 -0
- package/lib/accounts/errors.js +9 -0
- package/lib/accounts/errors.js.map +1 -0
- package/lib/accounts/file.js +101 -0
- package/lib/accounts/file.js.map +1 -0
- package/lib/accounts/index.js +2 -0
- package/lib/accounts/index.js.map +1 -0
- package/lib/accounts/store.js +461 -0
- package/lib/accounts/store.js.map +1 -0
- package/lib/accounts/token.js +61 -0
- package/lib/accounts/token.js.map +1 -0
- package/lib/attachment-rules.js +111 -0
- package/lib/attachment-rules.js.map +1 -0
- package/lib/attestation.js.map +1 -1
- package/lib/cli.js +256 -0
- package/lib/cli.js.map +1 -0
- package/lib/client/QaAccountsController.js +359 -0
- package/lib/client/QaAccountsController.js.map +1 -0
- package/lib/client/QaSessionController.js +238 -180
- package/lib/client/QaSessionController.js.map +1 -1
- package/lib/client/QaSurface.js +281 -105
- package/lib/client/QaSurface.js.map +1 -1
- package/lib/client/QaTranscriptAdapter.js +95 -244
- package/lib/client/QaTranscriptAdapter.js.map +1 -1
- package/lib/client/attachments.js +150 -0
- package/lib/client/attachments.js.map +1 -0
- package/lib/client/attestation.js +7 -1
- package/lib/client/attestation.js.map +1 -1
- package/lib/client/base64.js +16 -0
- package/lib/client/base64.js.map +1 -0
- package/lib/client/chat-files.js +48 -0
- package/lib/client/chat-files.js.map +1 -0
- package/lib/client/components/Markdown.js +54 -13
- package/lib/client/components/Markdown.js.map +1 -1
- package/lib/client/components/QaAuthGate.js +40 -0
- package/lib/client/components/QaAuthGate.js.map +1 -0
- package/lib/client/components/QaChangelog.js +131 -0
- package/lib/client/components/QaChangelog.js.map +1 -0
- package/lib/client/components/QaComposer.js +56 -74
- package/lib/client/components/QaComposer.js.map +1 -1
- package/lib/client/components/QaFileAttachment.js +19 -0
- package/lib/client/components/QaFileAttachment.js.map +1 -0
- package/lib/client/components/QaFilesPanel.js +45 -0
- package/lib/client/components/QaFilesPanel.js.map +1 -0
- package/lib/client/components/QaHeader.js +34 -0
- package/lib/client/components/QaHeader.js.map +1 -0
- package/lib/client/components/QaMessage.js +61 -18
- package/lib/client/components/QaMessage.js.map +1 -1
- package/lib/client/components/QaModal.js +31 -0
- package/lib/client/components/QaModal.js.map +1 -0
- package/lib/client/components/QaProfile.js +64 -0
- package/lib/client/components/QaProfile.js.map +1 -0
- package/lib/client/components/QaRightRail.js +15 -0
- package/lib/client/components/QaRightRail.js.map +1 -0
- package/lib/client/components/QaSidebar.js +68 -20
- package/lib/client/components/QaSidebar.js.map +1 -1
- package/lib/client/components/QaSourcesPanel.js +127 -0
- package/lib/client/components/QaSourcesPanel.js.map +1 -0
- package/lib/client/components/QaTurnRail.js +296 -0
- package/lib/client/components/QaTurnRail.js.map +1 -0
- package/lib/client/components/QaWelcomeNotice.js +125 -0
- package/lib/client/components/QaWelcomeNotice.js.map +1 -0
- package/lib/client/components/QaWidthHandle.js +147 -0
- package/lib/client/components/QaWidthHandle.js.map +1 -0
- package/lib/client/components/QaWorkGroup.js +19 -20
- package/lib/client/components/QaWorkGroup.js.map +1 -1
- package/lib/client/components/source-refs.js +78 -0
- package/lib/client/components/source-refs.js.map +1 -0
- package/lib/client/components/surface-utils.js +28 -0
- package/lib/client/components/surface-utils.js.map +1 -0
- package/lib/client/components/thinking-phrases.js +24 -0
- package/lib/client/components/thinking-phrases.js.map +1 -0
- package/lib/client/create-session.js +26 -0
- package/lib/client/create-session.js.map +1 -0
- package/lib/client/failure-copy.js +63 -0
- package/lib/client/failure-copy.js.map +1 -0
- package/lib/client/index.js +135 -16
- package/lib/client/index.js.map +1 -1
- package/lib/client/project-session-state.js +78 -0
- package/lib/client/project-session-state.js.map +1 -0
- package/lib/client/prompt-content.js +54 -0
- package/lib/client/prompt-content.js.map +1 -0
- package/lib/client/session-admission.js +38 -0
- package/lib/client/session-admission.js.map +1 -0
- package/lib/client/session-assets.js +52 -0
- package/lib/client/session-assets.js.map +1 -0
- package/lib/client/session-sources.js +61 -0
- package/lib/client/session-sources.js.map +1 -0
- package/lib/client/settings/card.js +143 -0
- package/lib/client/settings/card.js.map +1 -0
- package/lib/client/settings/fields.js +145 -0
- package/lib/client/settings/fields.js.map +1 -0
- package/lib/client/settings/format.js +255 -0
- package/lib/client/settings/format.js.map +1 -0
- package/lib/client/settings/sections/access.js +25 -0
- package/lib/client/settings/sections/access.js.map +1 -0
- package/lib/client/settings/sections/accounts.js +47 -0
- package/lib/client/settings/sections/accounts.js.map +1 -0
- package/lib/client/settings/sections/attachments.js +46 -0
- package/lib/client/settings/sections/attachments.js.map +1 -0
- package/lib/client/settings/sections/branding.js +28 -0
- package/lib/client/settings/sections/branding.js.map +1 -0
- package/lib/client/settings/sections/common.js +20 -0
- package/lib/client/settings/sections/common.js.map +1 -0
- package/lib/client/settings/sections/embedding.js +19 -0
- package/lib/client/settings/sections/embedding.js.map +1 -0
- package/lib/client/settings/sections/interface.js +60 -0
- package/lib/client/settings/sections/interface.js.map +1 -0
- package/lib/client/settings/sections/lockdown.js +82 -0
- package/lib/client/settings/sections/lockdown.js.map +1 -0
- package/lib/client/settings/sections/session.js +53 -0
- package/lib/client/settings/sections/session.js.map +1 -0
- package/lib/client/settings/sections/sources.js +72 -0
- package/lib/client/settings/sections/sources.js.map +1 -0
- package/lib/client/settings/sections/status.js +37 -0
- package/lib/client/settings/sections/status.js.map +1 -0
- package/lib/client/settings/sections.js +23 -0
- package/lib/client/settings/sections.js.map +1 -0
- package/lib/client/settings/styles.js +57 -0
- package/lib/client/settings/styles.js.map +1 -0
- package/lib/client/settlement.js +24 -0
- package/lib/client/settlement.js.map +1 -0
- package/lib/client/stream-publisher.js +47 -0
- package/lib/client/stream-publisher.js.map +1 -0
- package/lib/client/styles.js +225 -17
- package/lib/client/styles.js.map +1 -1
- package/lib/client/tool-presentation.js +102 -0
- package/lib/client/tool-presentation.js.map +1 -0
- package/lib/client/turn-sources.js +98 -0
- package/lib/client/turn-sources.js.map +1 -0
- package/lib/client/types.js +2 -0
- package/lib/client/types.js.map +1 -1
- package/lib/client/use-right-rail.js +96 -0
- package/lib/client/use-right-rail.js.map +1 -0
- package/lib/client/use-session-ui-state.js +75 -0
- package/lib/client/use-session-ui-state.js.map +1 -0
- package/lib/client/use-transcript-view.js +44 -0
- package/lib/client/use-transcript-view.js.map +1 -0
- package/lib/client.js +9052 -1345
- package/lib/client.js.map +1 -1
- package/lib/config-resolvers/accounts.js +60 -0
- package/lib/config-resolvers/accounts.js.map +1 -0
- package/lib/config-resolvers/attachments.js +28 -0
- package/lib/config-resolvers/attachments.js.map +1 -0
- package/lib/config-resolvers/basics.js +59 -0
- package/lib/config-resolvers/basics.js.map +1 -0
- package/lib/config-resolvers/defaults.js +132 -0
- package/lib/config-resolvers/defaults.js.map +1 -0
- package/lib/config-resolvers/lockdown.js +79 -0
- package/lib/config-resolvers/lockdown.js.map +1 -0
- package/lib/config-resolvers/session.js +42 -0
- package/lib/config-resolvers/session.js.map +1 -0
- package/lib/config-resolvers/shared.js +42 -0
- package/lib/config-resolvers/shared.js.map +1 -0
- package/lib/config-resolvers/sources.js +81 -0
- package/lib/config-resolvers/sources.js.map +1 -0
- package/lib/config-resolvers/ui.js +20 -0
- package/lib/config-resolvers/ui.js.map +1 -0
- package/lib/config.js +279 -102
- package/lib/config.js.map +1 -1
- package/lib/entry-redirect.js +45 -0
- package/lib/entry-redirect.js.map +1 -0
- package/lib/host-route.js +25 -1
- package/lib/host-route.js.map +1 -1
- package/lib/index.js +258 -23
- package/lib/index.js.map +1 -1
- package/lib/launch-token.js +42 -0
- package/lib/launch-token.js.map +1 -0
- package/lib/profile.js +214 -0
- package/lib/profile.js.map +1 -0
- package/lib/prompt-notes.js +231 -0
- package/lib/prompt-notes.js.map +1 -0
- package/lib/provenance/collector.js +40 -0
- package/lib/provenance/collector.js.map +1 -0
- package/lib/provenance/connectors.js +147 -0
- package/lib/provenance/connectors.js.map +1 -0
- package/lib/provenance/dedupe.js +89 -0
- package/lib/provenance/dedupe.js.map +1 -0
- package/lib/provenance/extractors.js +340 -0
- package/lib/provenance/extractors.js.map +1 -0
- package/lib/provenance/file-preview.js +39 -0
- package/lib/provenance/file-preview.js.map +1 -0
- package/lib/provenance/host-store.js +387 -0
- package/lib/provenance/host-store.js.map +1 -0
- package/lib/provenance/index.js +9 -0
- package/lib/provenance/index.js.map +1 -0
- package/lib/provenance/normalize.js +128 -0
- package/lib/provenance/normalize.js.map +1 -0
- package/lib/provenance/reported.js +53 -0
- package/lib/provenance/reported.js.map +1 -0
- package/lib/provenance/types.js +2 -0
- package/lib/provenance/types.js.map +1 -0
- package/lib/resolve-config.js +35 -249
- package/lib/resolve-config.js.map +1 -1
- package/lib/secure-session.js +122 -14
- package/lib/secure-session.js.map +1 -1
- package/lib/shared/session-key.js +18 -0
- package/lib/shared/session-key.js.map +1 -0
- package/lib/shared/settings.js +10 -0
- package/lib/shared/settings.js.map +1 -0
- package/lib/thinking-phrases.js +68 -0
- package/lib/thinking-phrases.js.map +1 -0
- package/lib/typert.host.js +749 -11
- package/lib/typert.remote-client.d.ts +23 -3
- package/lib/typert.remote-client.js +593 -6
- package/lib/types/account-remotes.d.ts +42 -0
- package/lib/types/accounts/credentials.d.ts +14 -0
- package/lib/types/accounts/errors.d.ts +11 -0
- package/lib/types/accounts/file.d.ts +82 -0
- package/lib/types/accounts/index.d.ts +2 -0
- package/lib/types/accounts/store.d.ts +144 -0
- package/lib/types/accounts/token.d.ts +31 -0
- package/lib/types/attachment-rules.d.ts +44 -0
- package/lib/types/attestation.d.ts +1 -1
- package/lib/types/cli.d.ts +9 -0
- package/lib/types/client/QaAccountsController.d.ts +102 -0
- package/lib/types/client/QaConfigController.d.ts +1 -1
- package/lib/types/client/QaSessionController.d.ts +61 -22
- package/lib/types/client/QaSurface.d.ts +14 -3
- package/lib/types/client/QaTranscriptAdapter.d.ts +5 -10
- package/lib/types/client/attachments.d.ts +54 -0
- package/lib/types/client/base64.d.ts +10 -0
- package/lib/types/client/chat-files.d.ts +30 -0
- package/lib/types/client/components/Markdown.d.ts +11 -1
- package/lib/types/client/components/QaAgentsDrawer.d.ts +1 -1
- package/lib/types/client/components/QaAuthGate.d.ts +15 -0
- package/lib/types/client/components/QaChangelog.d.ts +21 -0
- package/lib/types/client/components/QaComposer.d.ts +8 -4
- package/lib/types/client/components/QaFileAttachment.d.ts +14 -0
- package/lib/types/client/components/QaFilesPanel.d.ts +15 -0
- package/lib/types/client/components/QaHeader.d.ts +41 -0
- package/lib/types/client/components/QaMessage.d.ts +12 -3
- package/lib/types/client/components/QaModal.d.ts +26 -0
- package/lib/types/client/components/QaProfile.d.ts +25 -0
- package/lib/types/client/components/QaRightRail.d.ts +23 -0
- package/lib/types/client/components/QaSidebar.d.ts +44 -2
- package/lib/types/client/components/QaSourcesPanel.d.ts +23 -0
- package/lib/types/client/components/QaTurnRail.d.ts +65 -0
- package/lib/types/client/components/QaWelcomeNotice.d.ts +27 -0
- package/lib/types/client/components/QaWidthHandle.d.ts +43 -0
- package/lib/types/client/components/QaWorkGroup.d.ts +5 -3
- package/lib/types/client/components/source-refs.d.ts +16 -0
- package/lib/types/client/components/surface-utils.d.ts +11 -0
- package/lib/types/client/components/thinking-phrases.d.ts +9 -0
- package/lib/types/client/create-session.d.ts +13 -0
- package/lib/types/client/failure-copy.d.ts +19 -0
- package/lib/types/client/index.d.ts +1 -0
- package/lib/types/client/project-session-state.d.ts +30 -0
- package/lib/types/client/prompt-content.d.ts +30 -0
- package/lib/types/client/session-admission.d.ts +23 -0
- package/lib/types/client/session-assets.d.ts +20 -0
- package/lib/types/client/session-sources.d.ts +28 -0
- package/lib/types/client/settings/card.d.ts +23 -0
- package/lib/types/client/settings/fields.d.ts +139 -0
- package/lib/types/client/settings/format.d.ts +108 -0
- package/lib/types/client/settings/sections/access.d.ts +9 -0
- package/lib/types/client/settings/sections/accounts.d.ts +9 -0
- package/lib/types/client/settings/sections/attachments.d.ts +15 -0
- package/lib/types/client/settings/sections/branding.d.ts +8 -0
- package/lib/types/client/settings/sections/common.d.ts +28 -0
- package/lib/types/client/settings/sections/embedding.d.ts +8 -0
- package/lib/types/client/settings/sections/interface.d.ts +9 -0
- package/lib/types/client/settings/sections/lockdown.d.ts +9 -0
- package/lib/types/client/settings/sections/session.d.ts +9 -0
- package/lib/types/client/settings/sections/sources.d.ts +9 -0
- package/lib/types/client/settings/sections/status.d.ts +22 -0
- package/lib/types/client/settings/sections.d.ts +25 -0
- package/lib/types/client/settings/styles.d.ts +8 -0
- package/lib/types/client/settlement.d.ts +6 -0
- package/lib/types/client/stream-publisher.d.ts +28 -0
- package/lib/types/client/tool-presentation.d.ts +6 -0
- package/lib/types/client/turn-sources.d.ts +14 -0
- package/lib/types/client/types.d.ts +101 -9
- package/lib/types/client/use-right-rail.d.ts +56 -0
- package/lib/types/client/use-session-ui-state.d.ts +41 -0
- package/lib/types/client/use-transcript-view.d.ts +20 -0
- package/lib/types/config-resolvers/accounts.d.ts +15 -0
- package/lib/types/config-resolvers/attachments.d.ts +6 -0
- package/lib/types/config-resolvers/basics.d.ts +8 -0
- package/lib/types/config-resolvers/defaults.d.ts +4 -0
- package/lib/types/config-resolvers/lockdown.d.ts +10 -0
- package/lib/types/config-resolvers/session.d.ts +6 -0
- package/lib/types/config-resolvers/shared.d.ts +16 -0
- package/lib/types/config-resolvers/sources.d.ts +6 -0
- package/lib/types/config-resolvers/ui.d.ts +6 -0
- package/lib/types/entry-redirect.d.ts +20 -0
- package/lib/types/host-route.d.ts +14 -1
- package/lib/types/index.d.ts +54 -3
- package/lib/types/launch-token.d.ts +21 -0
- package/lib/types/profile.d.ts +76 -0
- package/lib/types/prompt-notes.d.ts +110 -0
- package/lib/types/provenance/collector.d.ts +26 -0
- package/lib/types/provenance/connectors.d.ts +11 -0
- package/lib/types/provenance/dedupe.d.ts +6 -0
- package/lib/types/provenance/extractors.d.ts +14 -0
- package/lib/types/provenance/file-preview.d.ts +10 -0
- package/lib/types/provenance/host-store.d.ts +54 -0
- package/lib/types/provenance/index.d.ts +10 -0
- package/lib/types/provenance/normalize.d.ts +13 -0
- package/lib/types/provenance/reported.d.ts +5 -0
- package/lib/types/provenance/types.d.ts +78 -0
- package/lib/types/resolve-config.d.ts +8 -3
- package/lib/types/secure-session.d.ts +36 -2
- package/lib/types/shared/session-key.d.ts +23 -0
- package/lib/types/shared/settings.d.ts +10 -0
- package/lib/types/thinking-phrases.d.ts +17 -0
- package/lib/types/types.d.ts +290 -16
- package/lib/types/user-workspace.d.ts +38 -0
- package/lib/user-workspace.js +231 -0
- package/lib/user-workspace.js.map +1 -0
- package/package.json +71 -47
- package/CHANGELOG.md +0 -93
- package/SPEC-dsh-qa-surface.md +0 -2425
- package/docs/ARCHITECTURE.md +0 -66
- package/docs/COMPATIBILITY.md +0 -14
- package/docs/CONFIGURATION.md +0 -112
- package/docs/EMBEDDING.md +0 -13
- package/lib/client/components/QaSourcesDrawer.js +0 -46
- package/lib/client/components/QaSourcesDrawer.js.map +0 -1
- package/lib/types/client/components/QaSourcesDrawer.d.ts +0 -8
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
binary += String.fromCharCode(...bytes.subarray(index, index + 0x8000));
|
|
5
|
-
}
|
|
6
|
-
return btoa(binary);
|
|
7
|
-
}
|
|
8
|
-
import { QaPolicyAttestationError, attestationHint, attestationReasonOf, proofMatchesConfig, } from "./attestation.js";
|
|
1
|
+
import { bytesToBase64 } from "./base64.js";
|
|
2
|
+
import { qaStorageNamespace } from "../shared/session-key.js";
|
|
3
|
+
import { QaPolicyAttestationError } from "./attestation.js";
|
|
9
4
|
import { QaChatIndex } from "./chat-index.js";
|
|
5
|
+
import { SessionAssetRepository } from "./session-assets.js";
|
|
6
|
+
import { createQaSession } from "./create-session.js";
|
|
7
|
+
import { buildQaPromptContent, stageQaFiles } from "./prompt-content.js";
|
|
8
|
+
import { attestQaPolicy } from "./session-admission.js";
|
|
9
|
+
import { QaHostSourceBridge } from "./session-sources.js";
|
|
10
|
+
import { StreamPublisher } from "./stream-publisher.js";
|
|
10
11
|
import { QA_SESSION_IDLE_STATE } from "./types.js";
|
|
11
12
|
import { waitFor } from "./wait-for.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
13
|
+
import { QA_REGENERATE_MARKER } from "./QaTranscriptAdapter.js";
|
|
14
|
+
import { projectBoundSessionState } from "./project-session-state.js";
|
|
15
|
+
import { projectTurnSources } from "./turn-sources.js";
|
|
14
16
|
const CONFIGURATION_ERROR = "Настройки помощника недоступны.";
|
|
15
17
|
/**
|
|
16
18
|
* The only module that couples QA behavior to DSH Session/client APIs. It owns
|
|
@@ -20,16 +22,26 @@ const CONFIGURATION_ERROR = "Настройки помощника недост
|
|
|
20
22
|
export class QaSessionController {
|
|
21
23
|
listeners = new Set();
|
|
22
24
|
sessions;
|
|
23
|
-
|
|
25
|
+
createSessionRemote;
|
|
26
|
+
conversation;
|
|
24
27
|
connection;
|
|
25
28
|
config;
|
|
26
29
|
secureSessionRemote;
|
|
30
|
+
sourceApi;
|
|
27
31
|
chats;
|
|
32
|
+
accounts;
|
|
33
|
+
/** Resolved per send: a page without the upload plugin has no receipts. */
|
|
34
|
+
fileUpload;
|
|
35
|
+
/** Per-chat attachment URL cache; blob URLs die with the chat binding. */
|
|
36
|
+
assets = new SessionAssetRepository();
|
|
28
37
|
timeoutMs;
|
|
29
|
-
|
|
38
|
+
/** Spacing for a running turn's stream frames; the policy lives there. */
|
|
39
|
+
streamPublisher;
|
|
30
40
|
state = QA_SESSION_IDLE_STATE;
|
|
31
41
|
session;
|
|
42
|
+
conversationBinding;
|
|
32
43
|
unsubscribeSession;
|
|
44
|
+
unsubscribeChat;
|
|
33
45
|
unsubscribeConnection;
|
|
34
46
|
ensuring;
|
|
35
47
|
materializing;
|
|
@@ -37,7 +49,6 @@ export class QaSessionController {
|
|
|
37
49
|
admissionPending = false;
|
|
38
50
|
policyReady = false;
|
|
39
51
|
drafting = false;
|
|
40
|
-
streamFlushTimer;
|
|
41
52
|
/** The chat session to return to when a subagent view closes. */
|
|
42
53
|
chatSessionId = null;
|
|
43
54
|
viewingSubagent = null;
|
|
@@ -45,15 +56,30 @@ export class QaSessionController {
|
|
|
45
56
|
disposed = false;
|
|
46
57
|
generation = 0;
|
|
47
58
|
chatsRevision = 0;
|
|
59
|
+
/** Host-side provenance of the bound chat, merged into the projection. */
|
|
60
|
+
hostSources;
|
|
48
61
|
constructor(options) {
|
|
49
62
|
this.sessions = options.sessions;
|
|
50
|
-
this.
|
|
63
|
+
this.createSessionRemote = options.createSession;
|
|
64
|
+
this.conversation = options.conversation;
|
|
51
65
|
this.connection = options.connection;
|
|
52
66
|
this.config = options.config;
|
|
53
67
|
this.secureSessionRemote = options.secureSession;
|
|
54
|
-
this.
|
|
68
|
+
this.sourceApi =
|
|
69
|
+
options.sourceApi ??
|
|
70
|
+
{
|
|
71
|
+
sources: async () => ({ ok: true, value: [] }),
|
|
72
|
+
readSourceFile: async () => ({
|
|
73
|
+
ok: false,
|
|
74
|
+
error: "unavailable",
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
this.chats = new QaChatIndex(options.storage, qaStorageNamespace(options.config));
|
|
78
|
+
this.accounts = options.accounts;
|
|
79
|
+
this.fileUpload = options.fileUpload ?? (() => undefined);
|
|
80
|
+
this.hostSources = new QaHostSourceBridge(this.sourceApi, () => this.accounts?.token() ?? "");
|
|
55
81
|
this.timeoutMs = options.timeoutMs ?? 15_000;
|
|
56
|
-
this.
|
|
82
|
+
this.streamPublisher = new StreamPublisher(options.streamIntervalMs ?? 66);
|
|
57
83
|
this.connectedOnce = this.connection.getSnapshot() !== undefined;
|
|
58
84
|
this.unsubscribeConnection = this.connection.subscribe(() => {
|
|
59
85
|
if (this.connection.getSnapshot() !== undefined)
|
|
@@ -76,9 +102,12 @@ export class QaSessionController {
|
|
|
76
102
|
this.ensuring = operation;
|
|
77
103
|
return operation;
|
|
78
104
|
}
|
|
79
|
-
async send(text,
|
|
105
|
+
async send(text, attachments = []) {
|
|
80
106
|
const prompt = text.trim();
|
|
81
|
-
|
|
107
|
+
// Images ride the prompt inline; files must be staged first, because the
|
|
108
|
+
// wire carries a receipt rather than bytes.
|
|
109
|
+
const files = attachments.filter((attachment) => attachment.kind === "file");
|
|
110
|
+
if ((prompt === "" && attachments.length === 0) || !this.state.canSend) {
|
|
82
111
|
return false;
|
|
83
112
|
}
|
|
84
113
|
if (prompt.startsWith("/")) {
|
|
@@ -96,25 +125,49 @@ export class QaSessionController {
|
|
|
96
125
|
}
|
|
97
126
|
}
|
|
98
127
|
this.operationError = null;
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
const step = await this.attestPolicy();
|
|
129
|
+
if (step.kind === "stale") {
|
|
130
|
+
// The user moved to another chat while the proof was pending; that
|
|
131
|
+
// binding attests through its own flow and must not receive a prompt
|
|
132
|
+
// meant for the old one.
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
let attestedId = step.kind === "ok" ? step.sessionId : null;
|
|
136
|
+
if (attestedId === null) {
|
|
137
|
+
attestedId = await this.recoverForSend();
|
|
138
|
+
if (attestedId === null)
|
|
103
139
|
return false;
|
|
104
140
|
}
|
|
141
|
+
// Nothing can interleave between the last await and here, so matching the
|
|
142
|
+
// attested id proves the prompt rides exactly the session it proved.
|
|
143
|
+
const target = this.session;
|
|
144
|
+
if (this.disposed ||
|
|
145
|
+
target === undefined ||
|
|
146
|
+
String(target.sessionId) !== attestedId) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
let receipts = new Map();
|
|
150
|
+
if (files.length > 0) {
|
|
151
|
+
const outcome = await stageQaFiles(this.fileUpload(), target, files);
|
|
152
|
+
if (outcome.kind !== "ok") {
|
|
153
|
+
if (this.session === target) {
|
|
154
|
+
this.operationError =
|
|
155
|
+
outcome.kind === "unavailable"
|
|
156
|
+
? "Вложения недоступны на этом сервере."
|
|
157
|
+
: "Не удалось приложить файл.";
|
|
158
|
+
this.publish();
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
receipts = outcome.receipts;
|
|
163
|
+
}
|
|
105
164
|
this.admissionPending = true;
|
|
106
165
|
this.publish();
|
|
107
166
|
try {
|
|
108
|
-
const content =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
mediaType: image.mediaType,
|
|
113
|
-
data: image.data,
|
|
114
|
-
name: image.name,
|
|
115
|
-
})),
|
|
116
|
-
];
|
|
117
|
-
const result = await this.session.prompt(content, "queue");
|
|
167
|
+
const content = buildQaPromptContent(prompt, attachments, receipts);
|
|
168
|
+
const result = await target.prompt(content, "queue");
|
|
169
|
+
if (this.session !== target)
|
|
170
|
+
return false;
|
|
118
171
|
if (!result.ok) {
|
|
119
172
|
this.admissionPending = false;
|
|
120
173
|
this.operationError = "Не удалось отправить сообщение.";
|
|
@@ -127,8 +180,10 @@ export class QaSessionController {
|
|
|
127
180
|
catch (error) {
|
|
128
181
|
this.admissionPending = false;
|
|
129
182
|
console.error("dsh-qa-surface: prompt failed", error);
|
|
130
|
-
this.
|
|
131
|
-
|
|
183
|
+
if (this.session === target) {
|
|
184
|
+
this.operationError = "Не удалось отправить сообщение.";
|
|
185
|
+
this.publish();
|
|
186
|
+
}
|
|
132
187
|
return false;
|
|
133
188
|
}
|
|
134
189
|
}
|
|
@@ -143,15 +198,20 @@ export class QaSessionController {
|
|
|
143
198
|
return this.send(QA_REGENERATE_MARKER);
|
|
144
199
|
}
|
|
145
200
|
async stop() {
|
|
146
|
-
|
|
201
|
+
const target = this.session;
|
|
202
|
+
if (target === undefined || !this.state.canStop)
|
|
147
203
|
return;
|
|
148
204
|
try {
|
|
149
|
-
const result = await
|
|
205
|
+
const result = await target.cancel();
|
|
206
|
+
if (this.session !== target)
|
|
207
|
+
return;
|
|
150
208
|
if (!result.ok)
|
|
151
209
|
this.operationError = "Не удалось остановить ответ.";
|
|
152
210
|
}
|
|
153
211
|
catch (error) {
|
|
154
212
|
console.error("dsh-qa-surface: stop failed", error);
|
|
213
|
+
if (this.session !== target)
|
|
214
|
+
return;
|
|
155
215
|
this.operationError = "Не удалось остановить ответ.";
|
|
156
216
|
}
|
|
157
217
|
this.publish();
|
|
@@ -200,7 +260,10 @@ export class QaSessionController {
|
|
|
200
260
|
const attempt = (async () => {
|
|
201
261
|
try {
|
|
202
262
|
await this.waitForConnection();
|
|
203
|
-
const id = await
|
|
263
|
+
const id = await createQaSession({
|
|
264
|
+
createSession: this.createSessionRemote,
|
|
265
|
+
token: this.accounts?.token() ?? "",
|
|
266
|
+
});
|
|
204
267
|
if (this.disposed || operation !== this.generation)
|
|
205
268
|
return false;
|
|
206
269
|
await this.bind(id);
|
|
@@ -213,6 +276,8 @@ export class QaSessionController {
|
|
|
213
276
|
return true;
|
|
214
277
|
}
|
|
215
278
|
catch (error) {
|
|
279
|
+
if (this.disposed || operation !== this.generation)
|
|
280
|
+
return false;
|
|
216
281
|
this.fail(this.operationError === CONFIGURATION_ERROR
|
|
217
282
|
? this.operationError
|
|
218
283
|
: "Не удалось начать чат.", error);
|
|
@@ -267,6 +332,9 @@ export class QaSessionController {
|
|
|
267
332
|
this.chats.saveActive(sessionId);
|
|
268
333
|
}
|
|
269
334
|
catch (error) {
|
|
335
|
+
// A newer operation superseded this switch: its own outcome governs.
|
|
336
|
+
if (this.disposed || operation !== this.generation)
|
|
337
|
+
return;
|
|
270
338
|
this.fail(this.operationError === CONFIGURATION_ERROR
|
|
271
339
|
? this.operationError
|
|
272
340
|
: "Не удалось открыть этот чат.", error);
|
|
@@ -305,6 +373,9 @@ export class QaSessionController {
|
|
|
305
373
|
await this.bind(id, { attest: false, track: false, report: false });
|
|
306
374
|
}
|
|
307
375
|
catch (error) {
|
|
376
|
+
// A newer operation superseded this view request: leave its state alone.
|
|
377
|
+
if (this.disposed || operation !== this.generation)
|
|
378
|
+
return;
|
|
308
379
|
this.viewingSubagent = null;
|
|
309
380
|
this.fail("Не удалось открыть транскрипт субагента.", error);
|
|
310
381
|
}
|
|
@@ -318,9 +389,14 @@ export class QaSessionController {
|
|
|
318
389
|
return;
|
|
319
390
|
await this.switchTo(chat);
|
|
320
391
|
}
|
|
321
|
-
/** This browser's
|
|
392
|
+
/** This browser's chat ids: the owned list when accounts are on, else the
|
|
393
|
+
* local index. Both intersect the host session list at projection time. */
|
|
322
394
|
chatIds() {
|
|
323
|
-
|
|
395
|
+
const owned = this.accounts?.ownedIds() ?? [];
|
|
396
|
+
if (owned.length === 0)
|
|
397
|
+
return this.chats.chatIds();
|
|
398
|
+
const local = this.chats.chatIds();
|
|
399
|
+
return [...new Set([...owned, ...local])];
|
|
324
400
|
}
|
|
325
401
|
/** The bound session id, or null while no chat is bound. */
|
|
326
402
|
activeSessionId() {
|
|
@@ -328,12 +404,21 @@ export class QaSessionController {
|
|
|
328
404
|
}
|
|
329
405
|
/**
|
|
330
406
|
* Resolve one durable image attachment of the bound session into a
|
|
331
|
-
* browser-usable URL.
|
|
407
|
+
* browser-usable URL. Resolutions are cached per chat in the asset
|
|
408
|
+
* repository (attachment ids repeat across chats) and revoked when the chat
|
|
409
|
+
* is unbound; failures surface as broken views and retry on demand.
|
|
332
410
|
*/
|
|
333
411
|
async readImage(attachmentId) {
|
|
334
412
|
if (this.session === undefined)
|
|
335
413
|
throw new Error("no bound session");
|
|
336
|
-
const
|
|
414
|
+
const sessionId = String(this.session.sessionId);
|
|
415
|
+
return this.assets.resolve(sessionId, attachmentId, (id) => this.readAttachmentUrl(id));
|
|
416
|
+
}
|
|
417
|
+
async readAttachmentUrl(attachmentId) {
|
|
418
|
+
const session = this.session;
|
|
419
|
+
if (session === undefined)
|
|
420
|
+
throw new Error("no bound session");
|
|
421
|
+
const result = await session.readAttachment(attachmentId);
|
|
337
422
|
if (!result.ok) {
|
|
338
423
|
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
339
424
|
}
|
|
@@ -374,9 +459,10 @@ export class QaSessionController {
|
|
|
374
459
|
return;
|
|
375
460
|
this.disposed = true;
|
|
376
461
|
this.generation += 1;
|
|
377
|
-
this.
|
|
462
|
+
this.streamPublisher.clear();
|
|
378
463
|
this.unsubscribeConnection();
|
|
379
464
|
this.unbind();
|
|
465
|
+
this.assets.dispose();
|
|
380
466
|
this.listeners.clear();
|
|
381
467
|
}
|
|
382
468
|
async ensureSessionNow() {
|
|
@@ -410,7 +496,10 @@ export class QaSessionController {
|
|
|
410
496
|
}
|
|
411
497
|
}
|
|
412
498
|
if (id === null) {
|
|
413
|
-
id = await
|
|
499
|
+
id = await createQaSession({
|
|
500
|
+
createSession: this.createSessionRemote,
|
|
501
|
+
token: this.accounts?.token() ?? "",
|
|
502
|
+
});
|
|
414
503
|
}
|
|
415
504
|
if (this.disposed || operation !== this.generation)
|
|
416
505
|
return;
|
|
@@ -421,6 +510,10 @@ export class QaSessionController {
|
|
|
421
510
|
await this.bind(id, { report: !restored });
|
|
422
511
|
}
|
|
423
512
|
catch (error) {
|
|
513
|
+
// A stale restore must not start its recovery ladder against a newer
|
|
514
|
+
// operation; rethrow for the generation-guarded outer catch.
|
|
515
|
+
if (this.disposed || operation !== this.generation)
|
|
516
|
+
throw error;
|
|
424
517
|
if (!restored ||
|
|
425
518
|
this.config.session.policy !== "browser-persistent" ||
|
|
426
519
|
!(error instanceof QaPolicyAttestationError)) {
|
|
@@ -432,7 +525,10 @@ export class QaSessionController {
|
|
|
432
525
|
this.unbind();
|
|
433
526
|
this.operationError = null;
|
|
434
527
|
this.chats.clearActive();
|
|
435
|
-
id = await
|
|
528
|
+
id = await createQaSession({
|
|
529
|
+
createSession: this.createSessionRemote,
|
|
530
|
+
token: this.accounts?.token() ?? "",
|
|
531
|
+
});
|
|
436
532
|
if (this.disposed || operation !== this.generation)
|
|
437
533
|
return;
|
|
438
534
|
await this.bind(id);
|
|
@@ -444,6 +540,9 @@ export class QaSessionController {
|
|
|
444
540
|
}
|
|
445
541
|
}
|
|
446
542
|
catch (error) {
|
|
543
|
+
// A newer operation superseded this bootstrap: its own outcome governs.
|
|
544
|
+
if (this.disposed || operation !== this.generation)
|
|
545
|
+
return;
|
|
447
546
|
this.fail(this.operationError === CONFIGURATION_ERROR
|
|
448
547
|
? this.operationError
|
|
449
548
|
: "Не удалось начать чат.", error);
|
|
@@ -456,14 +555,15 @@ export class QaSessionController {
|
|
|
456
555
|
* re-opening re-materializes it. Step 2 — when that refused session is
|
|
457
556
|
* still blank (it never received the prompt, so there is nothing to lose) —
|
|
458
557
|
* starts a fresh attested session instead. A session with history is left
|
|
459
|
-
* alone: recreating it would silently drop the conversation.
|
|
558
|
+
* alone: recreating it would silently drop the conversation. Returns the
|
|
559
|
+
* attested session id, or null when nothing was attested.
|
|
460
560
|
*/
|
|
461
561
|
async recoverForSend() {
|
|
462
562
|
const id = this.session === undefined ? null : String(this.session.sessionId);
|
|
463
563
|
if (id === null ||
|
|
464
564
|
this.disposed ||
|
|
465
565
|
this.config.session.policy === "fixed") {
|
|
466
|
-
return
|
|
566
|
+
return null;
|
|
467
567
|
}
|
|
468
568
|
const operation = ++this.generation;
|
|
469
569
|
this.unbind();
|
|
@@ -476,72 +576,43 @@ export class QaSessionController {
|
|
|
476
576
|
try {
|
|
477
577
|
await this.bind(id);
|
|
478
578
|
if (this.disposed || operation !== this.generation)
|
|
479
|
-
return
|
|
579
|
+
return null;
|
|
480
580
|
if (this.policyReady)
|
|
481
|
-
return
|
|
581
|
+
return id;
|
|
482
582
|
}
|
|
483
583
|
catch (error) {
|
|
484
584
|
if (!(error instanceof QaPolicyAttestationError))
|
|
485
585
|
throw error;
|
|
486
586
|
if (this.disposed || operation !== this.generation)
|
|
487
|
-
return
|
|
587
|
+
return null;
|
|
488
588
|
}
|
|
489
589
|
if (this.session !== undefined && !this.session.getSnapshot().blank) {
|
|
490
590
|
this.fail(CONFIGURATION_ERROR, new QaPolicyAttestationError());
|
|
491
|
-
return
|
|
591
|
+
return null;
|
|
492
592
|
}
|
|
493
593
|
this.unbind();
|
|
494
594
|
this.chats.clearActive();
|
|
495
|
-
const created = await
|
|
595
|
+
const created = await createQaSession({
|
|
596
|
+
createSession: this.createSessionRemote,
|
|
597
|
+
token: this.accounts?.token() ?? "",
|
|
598
|
+
});
|
|
496
599
|
if (this.disposed || operation !== this.generation)
|
|
497
|
-
return
|
|
600
|
+
return null;
|
|
498
601
|
await this.bind(created);
|
|
499
602
|
if (this.disposed || operation !== this.generation)
|
|
500
|
-
return
|
|
603
|
+
return null;
|
|
501
604
|
this.chats.saveActive(created);
|
|
502
|
-
return this.policyReady;
|
|
605
|
+
return this.policyReady ? created : null;
|
|
503
606
|
}
|
|
504
607
|
catch (error) {
|
|
608
|
+
if (this.disposed || operation !== this.generation)
|
|
609
|
+
return null;
|
|
505
610
|
this.fail(this.operationError === CONFIGURATION_ERROR
|
|
506
611
|
? this.operationError
|
|
507
612
|
: "Не удалось открыть этот чат.", error);
|
|
508
|
-
return
|
|
613
|
+
return null;
|
|
509
614
|
}
|
|
510
615
|
}
|
|
511
|
-
async createSession() {
|
|
512
|
-
const created = await this.sessions.create({
|
|
513
|
-
...(this.config.session.workspaceId !== null
|
|
514
|
-
? { workspaceId: this.config.session.workspaceId }
|
|
515
|
-
: this.config.session.cwd !== null
|
|
516
|
-
? { cwd: this.config.session.cwd }
|
|
517
|
-
: {}),
|
|
518
|
-
});
|
|
519
|
-
const id = String(created);
|
|
520
|
-
if (this.config.session.agentPreset !== null) {
|
|
521
|
-
const selectedPreset = await this.api.selectAgentPreset({
|
|
522
|
-
sessionId: id,
|
|
523
|
-
agentPreset: this.config.session.agentPreset,
|
|
524
|
-
});
|
|
525
|
-
if (!selectedPreset.result.ok) {
|
|
526
|
-
throw new Error(selectedPreset.result.error.code);
|
|
527
|
-
}
|
|
528
|
-
this.sessions.noteAgentPreset(id, this.config.session.agentPreset);
|
|
529
|
-
}
|
|
530
|
-
if (this.config.session.provider !== null &&
|
|
531
|
-
this.config.session.model !== null) {
|
|
532
|
-
const selected = await this.api.selectModel({
|
|
533
|
-
sessionId: id,
|
|
534
|
-
provider: this.config.session.provider,
|
|
535
|
-
model: this.config.session.model,
|
|
536
|
-
...(this.config.session.reasoningEffort === null
|
|
537
|
-
? {}
|
|
538
|
-
: { reasoningEffort: this.config.session.reasoningEffort }),
|
|
539
|
-
});
|
|
540
|
-
if (!selected.result.ok)
|
|
541
|
-
throw new Error(selected.result.error.code);
|
|
542
|
-
}
|
|
543
|
-
return id;
|
|
544
|
-
}
|
|
545
616
|
async bind(id, options = {}) {
|
|
546
617
|
const { report = true, attest = true, track = true } = options;
|
|
547
618
|
this.sessions.open(id);
|
|
@@ -554,65 +625,61 @@ export class QaSessionController {
|
|
|
554
625
|
throw new Error("Session binding is unavailable.");
|
|
555
626
|
this.unbind();
|
|
556
627
|
this.session = binding.session;
|
|
628
|
+
this.conversationBinding = this.conversation.binding(id);
|
|
557
629
|
this.policyReady = false;
|
|
558
630
|
this.unsubscribeSession = binding.session.subscribe(() => {
|
|
559
631
|
this.admissionPending = false;
|
|
560
632
|
this.operationError = null;
|
|
561
633
|
this.publishSessionUpdate();
|
|
562
634
|
});
|
|
635
|
+
// Subscribing the Chat target activates it, so the assembled transcript
|
|
636
|
+
// (nodes, partials, running calls) materializes for the projection.
|
|
637
|
+
this.unsubscribeChat = this.conversationBinding
|
|
638
|
+
.target("chat")
|
|
639
|
+
.subscribe(() => {
|
|
640
|
+
this.publishSessionUpdate();
|
|
641
|
+
});
|
|
563
642
|
await waitFor(binding.session, (snapshot) => snapshot.openState === "open" || snapshot.openState === "error", this.timeoutMs);
|
|
564
643
|
if (binding.session.getSnapshot().openState !== "open") {
|
|
565
644
|
throw new Error("Session binding could not be opened.");
|
|
566
645
|
}
|
|
567
|
-
if (attest
|
|
568
|
-
|
|
646
|
+
if (attest) {
|
|
647
|
+
const step = await this.attestPolicy(report);
|
|
648
|
+
if (step.kind !== "ok")
|
|
649
|
+
throw new QaPolicyAttestationError();
|
|
569
650
|
}
|
|
570
651
|
if (track) {
|
|
571
652
|
this.chatSessionId = String(this.session.sessionId);
|
|
572
653
|
this.viewingSubagent = null;
|
|
573
654
|
this.chats.addChat(String(this.session.sessionId));
|
|
655
|
+
this.accounts?.onSessionCreated(String(this.session.sessionId));
|
|
574
656
|
}
|
|
575
657
|
this.publish();
|
|
576
658
|
}
|
|
577
659
|
unbind() {
|
|
578
|
-
this.
|
|
660
|
+
this.streamPublisher.clear();
|
|
579
661
|
this.unsubscribeSession?.();
|
|
580
662
|
this.unsubscribeSession = undefined;
|
|
663
|
+
this.unsubscribeChat?.();
|
|
664
|
+
this.unsubscribeChat = undefined;
|
|
665
|
+
this.conversationBinding = undefined;
|
|
666
|
+
if (this.session !== undefined) {
|
|
667
|
+
// Leaving the chat retires its attachment URLs; a later re-bind
|
|
668
|
+
// re-resolves them from the durable store.
|
|
669
|
+
this.assets.release(String(this.session.sessionId));
|
|
670
|
+
}
|
|
581
671
|
this.session = undefined;
|
|
672
|
+
this.hostSources.reset();
|
|
582
673
|
this.admissionPending = false;
|
|
583
674
|
this.policyReady = false;
|
|
584
675
|
}
|
|
585
676
|
waitForConnection() {
|
|
586
677
|
return waitFor(this.connection, (description) => description !== undefined, this.timeoutMs);
|
|
587
678
|
}
|
|
588
|
-
/**
|
|
589
|
-
*
|
|
590
|
-
* animation-frame cadence, so re-projections are spaced at least
|
|
591
|
-
* {@link streamIntervalMs} apart: the first frame of a window projects at
|
|
592
|
-
* once and further frames are absorbed (the memoized render path replays
|
|
593
|
-
* nothing, so absorbing a frame only defers it). Everything outside a
|
|
594
|
-
* running turn - phase flips, errors, turn completion - projects
|
|
595
|
-
* immediately.
|
|
596
|
-
*/
|
|
679
|
+
/** Project one session notification; the running-turn spacing policy
|
|
680
|
+
* (first frame at once, further frames absorbed) lives in the publisher. */
|
|
597
681
|
publishSessionUpdate() {
|
|
598
|
-
|
|
599
|
-
this.session?.getSnapshot().running !== true) {
|
|
600
|
-
this.clearStreamFlush();
|
|
601
|
-
this.publish();
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (this.streamFlushTimer !== undefined)
|
|
605
|
-
return;
|
|
606
|
-
this.publish();
|
|
607
|
-
this.streamFlushTimer = setTimeout(() => {
|
|
608
|
-
this.streamFlushTimer = undefined;
|
|
609
|
-
}, this.streamIntervalMs);
|
|
610
|
-
}
|
|
611
|
-
clearStreamFlush() {
|
|
612
|
-
if (this.streamFlushTimer === undefined)
|
|
613
|
-
return;
|
|
614
|
-
clearTimeout(this.streamFlushTimer);
|
|
615
|
-
this.streamFlushTimer = undefined;
|
|
682
|
+
this.streamPublisher.publish(this.session?.getSnapshot().running === true, () => this.publish());
|
|
616
683
|
}
|
|
617
684
|
publish() {
|
|
618
685
|
if (this.disposed)
|
|
@@ -646,38 +713,29 @@ export class QaSessionController {
|
|
|
646
713
|
return;
|
|
647
714
|
}
|
|
648
715
|
const snapshot = this.session.getSnapshot();
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
phase,
|
|
669
|
-
sessionId: String(this.session.sessionId),
|
|
670
|
-
messages: projectTranscript(snapshot, {
|
|
671
|
-
showToolActivity: this.config.ui.showToolActivity,
|
|
672
|
-
showReasoning: this.config.ui.showReasoning,
|
|
673
|
-
}),
|
|
674
|
-
error,
|
|
675
|
-
canSend: connected && phase === "ready" && this.policyReady,
|
|
676
|
-
canStop: connected && snapshot.running && this.config.ui.showStop && !blocked,
|
|
716
|
+
const sessionId = String(this.session.sessionId);
|
|
717
|
+
const conversationSnapshot = this.conversationBinding?.snapshot.getSnapshot();
|
|
718
|
+
const projectedSourceBundles = projectTurnSources(conversationSnapshot, sessionId, this.config.session.cwd ?? undefined);
|
|
719
|
+
const sourceBundles = this.hostSources.merge(projectedSourceBundles);
|
|
720
|
+
if (!snapshot.running) {
|
|
721
|
+
void this.hostSources.refresh(sessionId, () => this.publish());
|
|
722
|
+
}
|
|
723
|
+
this.state = projectBoundSessionState({
|
|
724
|
+
connected,
|
|
725
|
+
sessionId,
|
|
726
|
+
sessionSnapshot: snapshot,
|
|
727
|
+
conversationSnapshot,
|
|
728
|
+
sourceBundles,
|
|
729
|
+
// Ownership is chat-level: every user message of a foreign chat
|
|
730
|
+
// carries its owner's name when an admin reads it.
|
|
731
|
+
author: this.accounts?.messageAuthorOf(sessionId),
|
|
732
|
+
operationError: this.operationError,
|
|
733
|
+
policyReady: this.policyReady,
|
|
734
|
+
admissionPending: this.admissionPending,
|
|
677
735
|
chatsRevision: this.chatsRevision,
|
|
678
|
-
sources: projectSources(snapshot),
|
|
679
736
|
viewingSubagent: this.viewingSubagent,
|
|
680
|
-
|
|
737
|
+
config: this.config,
|
|
738
|
+
});
|
|
681
739
|
this.emit();
|
|
682
740
|
}
|
|
683
741
|
fail(message, error) {
|
|
@@ -697,48 +755,48 @@ export class QaSessionController {
|
|
|
697
755
|
};
|
|
698
756
|
this.emit();
|
|
699
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Attest the currently bound session. `ok` carries the session id the proof
|
|
760
|
+
* was issued for; `stale` means the binding changed while the request was
|
|
761
|
+
* in flight, so nothing was written — the newer binding manages its own
|
|
762
|
+
* attestation; `refused` is a genuine admission refusal of this binding.
|
|
763
|
+
*/
|
|
700
764
|
async attestPolicy(reportFailure = true) {
|
|
701
|
-
|
|
702
|
-
|
|
765
|
+
const session = this.session;
|
|
766
|
+
if (session === undefined)
|
|
767
|
+
return { kind: "refused" };
|
|
768
|
+
const sessionId = String(session.sessionId);
|
|
703
769
|
if (!this.config.lockdown.enabled) {
|
|
704
770
|
this.policyReady = true;
|
|
705
|
-
return
|
|
771
|
+
return { kind: "ok", sessionId };
|
|
706
772
|
}
|
|
707
773
|
this.policyReady = false;
|
|
708
774
|
this.publish();
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
};
|
|
720
|
-
try {
|
|
721
|
-
const result = await this.secureSessionRemote(String(this.session.sessionId));
|
|
722
|
-
if (!result.ok) {
|
|
723
|
-
return reject(attestationReasonOf(result.error));
|
|
724
|
-
}
|
|
725
|
-
if (!proofMatchesConfig(result.value, this.config.lockdown, String(this.session.sessionId))) {
|
|
726
|
-
return reject("proof-mismatch");
|
|
727
|
-
}
|
|
775
|
+
const outcome = await attestQaPolicy({
|
|
776
|
+
secureSession: this.secureSessionRemote,
|
|
777
|
+
token: this.accounts?.token() ?? "",
|
|
778
|
+
sessionId,
|
|
779
|
+
lockdown: this.config.lockdown,
|
|
780
|
+
report: reportFailure,
|
|
781
|
+
});
|
|
782
|
+
if (this.session !== session)
|
|
783
|
+
return { kind: "stale" };
|
|
784
|
+
if (outcome.ok) {
|
|
728
785
|
this.policyReady = true;
|
|
729
786
|
this.operationError = null;
|
|
730
787
|
this.publish();
|
|
731
|
-
return
|
|
788
|
+
return { kind: "ok", sessionId };
|
|
732
789
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
this.operationError = CONFIGURATION_ERROR;
|
|
739
|
-
this.publish();
|
|
740
|
-
return false;
|
|
790
|
+
if (outcome.authRequired) {
|
|
791
|
+
// The identity expired or was rotated: back to the gate instead of
|
|
792
|
+
// the generic configuration error.
|
|
793
|
+
this.accounts?.onAuthRequired();
|
|
794
|
+
return { kind: "refused" };
|
|
741
795
|
}
|
|
796
|
+
this.policyReady = false;
|
|
797
|
+
this.operationError = CONFIGURATION_ERROR;
|
|
798
|
+
this.publish();
|
|
799
|
+
return { kind: "refused" };
|
|
742
800
|
}
|
|
743
801
|
emit() {
|
|
744
802
|
for (const listener of this.listeners)
|