@yadsh/dsh-qa-surface 0.1.1 → 0.2.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/CHANGELOG.md +68 -0
- package/README.md +162 -14
- package/SPEC-dsh-qa-surface.md +600 -96
- package/capability-policy.json +5 -0
- package/compatibility.json +7 -1
- package/cordis.patch.yml +11 -0
- package/docs/ARCHITECTURE.md +49 -7
- package/docs/COMPATIBILITY.md +5 -4
- package/docs/CONFIGURATION.md +96 -2
- package/lib/attestation.js +10 -0
- package/lib/attestation.js.map +1 -0
- package/lib/client/QaConfigController.js +111 -4
- package/lib/client/QaConfigController.js.map +1 -1
- package/lib/client/QaSessionController.js +501 -119
- package/lib/client/QaSessionController.js.map +1 -1
- package/lib/client/QaSurface.js +137 -20
- package/lib/client/QaSurface.js.map +1 -1
- package/lib/client/QaTranscriptAdapter.js +534 -45
- package/lib/client/QaTranscriptAdapter.js.map +1 -1
- package/lib/client/attestation.js +60 -0
- package/lib/client/attestation.js.map +1 -0
- package/lib/client/chat-index.js +86 -0
- package/lib/client/chat-index.js.map +1 -0
- package/lib/client/components/Markdown.js +75 -5
- package/lib/client/components/Markdown.js.map +1 -1
- package/lib/client/components/QaAgentsDrawer.js +40 -0
- package/lib/client/components/QaAgentsDrawer.js.map +1 -0
- package/lib/client/components/QaComposer.js +132 -15
- package/lib/client/components/QaComposer.js.map +1 -1
- package/lib/client/components/QaMessage.js +184 -10
- package/lib/client/components/QaMessage.js.map +1 -1
- package/lib/client/components/QaSidebar.js +124 -0
- package/lib/client/components/QaSidebar.js.map +1 -0
- package/lib/client/components/QaSourcesDrawer.js +46 -0
- package/lib/client/components/QaSourcesDrawer.js.map +1 -0
- package/lib/client/components/QaWorkGroup.js +135 -0
- package/lib/client/components/QaWorkGroup.js.map +1 -0
- package/lib/client/components/VariantSwitcher.js +29 -0
- package/lib/client/components/VariantSwitcher.js.map +1 -0
- package/lib/client/components/format.js +48 -0
- package/lib/client/components/format.js.map +1 -0
- package/lib/client/index.js +77 -34
- package/lib/client/index.js.map +1 -1
- package/lib/client/styles.js +308 -43
- package/lib/client/styles.js.map +1 -1
- package/lib/client/types.js +13 -0
- package/lib/client/types.js.map +1 -0
- package/lib/client/wait-for.js +49 -0
- package/lib/client/wait-for.js.map +1 -0
- package/lib/client.js +8228 -383
- package/lib/client.js.map +1 -1
- package/lib/config.js +61 -9
- package/lib/config.js.map +1 -1
- package/lib/index.js +166 -70
- package/lib/index.js.map +1 -1
- package/lib/lockdown-policy.js +20 -0
- package/lib/lockdown-policy.js.map +1 -0
- package/lib/resolve-config.js +122 -10
- package/lib/resolve-config.js.map +1 -1
- package/lib/secure-session.js +170 -0
- package/lib/secure-session.js.map +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +191 -0
- package/lib/typert.remote-client.d.ts +24 -0
- package/lib/typert.remote-client.js +129 -0
- package/lib/types/attestation.d.ts +17 -0
- package/lib/types/client/QaConfigController.d.ts +34 -2
- package/lib/types/client/QaSessionController.d.ts +106 -19
- package/lib/types/client/QaSurface.d.ts +5 -4
- package/lib/types/client/QaTranscriptAdapter.d.ts +17 -2
- package/lib/types/client/attestation.d.ts +25 -0
- package/lib/types/client/chat-index.d.ts +28 -0
- package/lib/types/client/components/Markdown.d.ts +7 -3
- package/lib/types/client/components/QaAgentsDrawer.d.ts +25 -0
- package/lib/types/client/components/QaComposer.d.ts +12 -2
- package/lib/types/client/components/QaMessage.d.ts +14 -1
- package/lib/types/client/components/QaSidebar.d.ts +41 -0
- package/lib/types/client/components/QaSourcesDrawer.d.ts +8 -0
- package/lib/types/client/components/QaWorkGroup.d.ts +19 -0
- package/lib/types/client/components/VariantSwitcher.d.ts +19 -0
- package/lib/types/client/components/format.d.ts +9 -0
- package/lib/types/client/index.d.ts +1 -5
- package/lib/types/client/types.d.ts +29 -0
- package/lib/types/client/wait-for.d.ts +11 -0
- package/lib/types/index.d.ts +22 -6
- package/lib/types/lockdown-policy.d.ts +18 -0
- package/lib/types/secure-session.d.ts +22 -0
- package/lib/types/types.d.ts +148 -1
- package/package.json +29 -3
package/compatibility.json
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
"connection.api.sessions",
|
|
9
9
|
"sessions.binding",
|
|
10
10
|
"settingsScope",
|
|
11
|
-
"webServer.register"
|
|
11
|
+
"webServer.register",
|
|
12
|
+
"typert.remote",
|
|
13
|
+
"agentPresets.composedPreset",
|
|
14
|
+
"permissionPresets.set",
|
|
15
|
+
"tools.restrict",
|
|
16
|
+
"tools.guard",
|
|
17
|
+
"workspaceRegistry.get"
|
|
12
18
|
]
|
|
13
19
|
},
|
|
14
20
|
"node": "^22.19.0 || >=24.0.0"
|
package/cordis.patch.yml
CHANGED
|
@@ -7,3 +7,14 @@
|
|
|
7
7
|
route:
|
|
8
8
|
path: /qa
|
|
9
9
|
matchChildren: true
|
|
10
|
+
ui:
|
|
11
|
+
showReset: false
|
|
12
|
+
lockdown:
|
|
13
|
+
enabled: true
|
|
14
|
+
sandboxMode: read-only
|
|
15
|
+
approvalPolicy: never
|
|
16
|
+
permissionPreset: qa-read-only
|
|
17
|
+
allowSessionReset: false
|
|
18
|
+
toolPolicy:
|
|
19
|
+
mode: allow-list
|
|
20
|
+
allow: []
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -4,20 +4,62 @@ The Host half validates the Loader configuration, registers the `qa-surface`
|
|
|
4
4
|
settings namespace and owns one narrow GET/HEAD route for the configured QA
|
|
5
5
|
path. Published DSH `0.1.1-rc.2` returns 404 for unknown frontend paths, so the
|
|
6
6
|
route redirects navigation through the canonical `/` index with an encoded
|
|
7
|
-
same-origin path marker. It
|
|
8
|
-
|
|
7
|
+
same-origin path marker. It also exposes one typed
|
|
8
|
+
`secureSession(sessionId)` operation whose policy is read only from Host
|
|
9
|
+
settings. The entry delegates that operation to `QaPolicyAdmission`
|
|
10
|
+
(`secure-session.ts`), the Host-side admission boundary. It does not serve
|
|
11
|
+
HTML, create another server, replace the frontend fallback, or touch
|
|
12
|
+
API/plugin routes. The browser removes the
|
|
9
13
|
marker and restores the requested path with `history.replaceState` before
|
|
10
|
-
route matching. The Host creates no sessions.
|
|
14
|
+
route matching. The Host creates no sessions itself.
|
|
11
15
|
|
|
12
16
|
The browser half registers `QaSurface` in the root-scoped `shell.overlay` list.
|
|
13
17
|
`QaRouteController` keeps that entry dormant outside the configured path and
|
|
14
18
|
reversibly observes `pushState`, `replaceState` and `popstate`.
|
|
15
19
|
|
|
16
|
-
`QaSessionController` is the only DSH session-coupled module. It uses
|
|
20
|
+
`QaSessionController` is the only browser-side DSH session-coupled module. It uses
|
|
17
21
|
`connection.api.sessions.create/selectModel` for creation policy and the public
|
|
18
|
-
`SessionFace` for prompt, cancel and snapshots
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
`SessionFace` for prompt, cancel and snapshots; the shared client contracts and
|
|
23
|
+
the idle state live in `client/types.ts`. Its supporting modules are pure
|
|
24
|
+
helpers: `client/wait-for.ts` waits on observable stores, `client/chat-index.ts`
|
|
25
|
+
(`QaChatIndex`) keeps the browser-local chat index and persisted active
|
|
26
|
+
session in storage, and `client/attestation.ts` parses Host reason markers and
|
|
27
|
+
validates proofs against the deployed lockdown config. `QaTranscriptAdapter`
|
|
28
|
+
projects session snapshots to plugin-local messages. "New chat" enters a
|
|
29
|
+
draft that materializes an attested session only when the first prompt is
|
|
30
|
+
sent, and the chat list orders by the host's `updatedAt`. The log is
|
|
31
|
+
append-only and there is no truncation seam, so regeneration prompts a
|
|
32
|
+
hidden instruction and projects the follow-up turns as variants of one
|
|
33
|
+
question; the same projection flattens tool activity into the sources
|
|
34
|
+
drawer (web targets, searches, files read). Subagent delegation, when the
|
|
35
|
+
deployment opts it in, renders launches and settlement notices inline, and
|
|
36
|
+
the agents drawer opens a child session as a read-only live transcript: the
|
|
37
|
+
binding skips attestation and every send path stays closed. By default it excludes
|
|
38
|
+
reasoning, tool arguments and tool results. When explicitly enabled, it
|
|
39
|
+
correlates assistant blocks, paired tool results, running calls and turn
|
|
40
|
+
timings into one plugin-local work group per turn; `QaWorkGroup` owns
|
|
41
|
+
disclosure state and collapses the group when the turn completes. System
|
|
42
|
+
context and raw failures remain excluded.
|
|
43
|
+
|
|
44
|
+
In locked mode the Host operation verifies immutable session composition,
|
|
45
|
+
refuses to adopt a non-QA session with user history, validates the named
|
|
46
|
+
permission preset, installs `ToolRestriction.allow`, installs an agent-scoped
|
|
47
|
+
monotonic execution guard, selects the permission preset, and returns only a
|
|
48
|
+
sanitized proof. The controller remains non-writable until every proof field
|
|
49
|
+
matches its Host-delivered config and repeats the check before prompt
|
|
50
|
+
admission. No private RPC handler, slash command, direct provider API, or DSH
|
|
51
|
+
core patch is used.
|
|
52
|
+
|
|
53
|
+
This narrows a QA agent's effective session/tool policy, but a route overlay on
|
|
54
|
+
a shared privileged Host is not an authorization boundary for other DSH
|
|
55
|
+
clients. Strong isolation requires a dedicated process/profile, restricted
|
|
56
|
+
identity and network boundary.
|
|
57
|
+
|
|
58
|
+
All interaction colors come from the company interaction palette
|
|
59
|
+
("Цвета взаимодействия"), defined once as the `QA_BRAND_PALETTE` object at the
|
|
60
|
+
top of `styles.ts` and emitted as `--dsh-qa-*` custom properties on the
|
|
61
|
+
overlay root; surfaces and typography keep using the themed `--dsw-alias-*`
|
|
62
|
+
tokens so light and dark hosts stay coherent.
|
|
21
63
|
|
|
22
64
|
The overlay locks document scrolling and contains keyboard focus while active.
|
|
23
65
|
It does not locate AppFrame through generated CSS classes and does not dispose
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
| Plugin version | Tested DSH release | Required public contracts
|
|
4
|
-
| -------------- | ------------------ |
|
|
5
|
-
| 0.1.x | 0.1.1-rc.2 | `shell.overlay`, `
|
|
3
|
+
| Plugin version | Tested DSH release | Required public contracts |
|
|
4
|
+
| -------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
5
|
+
| 0.1.x | 0.1.1-rc.2 | `shell.overlay`, Typert remote, `agentPresets.composedPreset`, `permissionPresets`, agent-scoped `tools.restrict/guard`, `workspaceRegistry`, session/model APIs, `webServer.register` |
|
|
6
6
|
|
|
7
7
|
DSH is developer-preview software. Compatibility-sensitive calls are isolated
|
|
8
8
|
in `QaSessionController`, `QaTranscriptAdapter`, `QaConfigController` and
|
|
9
|
-
`QaRouteController
|
|
9
|
+
`QaRouteController`; Host lockdown coupling is isolated in
|
|
10
|
+
`QaSurface.secureSession`. The Host navigation adapter also works around this tested
|
|
10
11
|
release's exact-index static serving: `/qa` is redirected through `/`, then the
|
|
11
12
|
client restores the original pathname. Missing settings fall back to default
|
|
12
13
|
browser config; missing required client services prevent the browser plugin
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -10,9 +10,103 @@ rules are:
|
|
|
10
10
|
- `maxContentWidth` is an integer from 480 through 1600;
|
|
11
11
|
- duplicate/blank suggested questions are removed;
|
|
12
12
|
- approval and question policies are fixed to safe blocking behavior;
|
|
13
|
-
- reasoning
|
|
13
|
+
- reasoning and tool details are opt-in through `ui.showReasoning` and
|
|
14
|
+
`ui.showToolActivity`; enable them only where those contents are appropriate
|
|
15
|
+
for the QA audience;
|
|
16
|
+
- lockdown defaults to enabled and requires a non-empty permission preset;
|
|
17
|
+
- the preset must resolve on the Host to exactly `read-only` + `never`;
|
|
18
|
+
- permission, slash-command, settings, rename, delete and arbitrary-open
|
|
19
|
+
capability flags cannot be enabled;
|
|
20
|
+
- `showReset` requires the independent `allowSessionReset` opt-in;
|
|
21
|
+
- the tool policy is an allow-list; unknown configured tool names fail closed.
|
|
22
|
+
A name only resolves while its tool is actually mounted: MCP tools
|
|
23
|
+
(`mcp__<server>__*`) exist only while their server is reachable, so keep
|
|
24
|
+
them out of the allow list on hosts that cannot reach the server. Tools
|
|
25
|
+
mounted by an agent preset live in that preset's ancestor scope; the Host
|
|
26
|
+
validates and restricts the complete agent-scoped view, not just globals.
|
|
27
|
+
|
|
28
|
+
## Pinning chats to a directory
|
|
29
|
+
|
|
30
|
+
By default a QA chat is created against the Host working directory. Two
|
|
31
|
+
mutually exclusive pins anchor every new chat (and its subagents) to the
|
|
32
|
+
deployment's own directory:
|
|
33
|
+
|
|
34
|
+
- `session.cwd: "D:/qa-docs"` - an absolute directory used directly as the
|
|
35
|
+
session cwd; no registration needed. Attestation compares the session's
|
|
36
|
+
recorded cwd case- and separator-insensitively on Windows.
|
|
37
|
+
- `session.workspaceId: "<uuid>"` - a registered DSH workspace (created in
|
|
38
|
+
the web UI or via the workspace API; the id is a generated uuid, not the
|
|
39
|
+
path). Sessions attach to the workspace, so they group under it in the
|
|
40
|
+
host UI. When the id does not resolve, attestation refuses with
|
|
41
|
+
`reason: workspace-unavailable` instead of an opaque mismatch.
|
|
42
|
+
|
|
43
|
+
With `lockdown.enforceFixedWorkspace: true` (the default) a chat created
|
|
44
|
+
anywhere else refuses to attest, so the QA audience can never pull the
|
|
45
|
+
assistant out of the pinned directory.
|
|
14
46
|
|
|
15
47
|
Browser persistence stores only the DSH session id under
|
|
16
|
-
`<storageKey>:v1:<route>:session
|
|
48
|
+
`<storageKey>:v1:<route>:session`, plus — when `ui.showSessionList` is
|
|
49
|
+
enabled — a per-browser chat index under `<storageKey>:v1:<route>:chats`
|
|
50
|
+
(session ids only, capped at 50). Transcript content, credentials and tool
|
|
17
51
|
results remain in the Host-owned DSH Session and are never copied to browser
|
|
18
52
|
storage.
|
|
53
|
+
|
|
54
|
+
When either work-detail flag is enabled, the QA transcript groups reasoning,
|
|
55
|
+
intermediate assistant progress, and tool rows by DSH turn. Running work is
|
|
56
|
+
expanded. Completed work is collapsed behind a duration summary and can be
|
|
57
|
+
reopened; each tool row can separately reveal its formatted input and output.
|
|
58
|
+
These controls change presentation only and never widen the Host allow-list.
|
|
59
|
+
|
|
60
|
+
The browser cannot override lockdown settings. Before binding and immediately
|
|
61
|
+
before every prompt, it requests a Host attestation for the selected session.
|
|
62
|
+
Any unproved agent preset, workspace, model, permission bundle or tool policy
|
|
63
|
+
disables Send with the generic message `Настройки помощника недоступны.`
|
|
64
|
+
Detailed mismatch facts are written only to Host logs; the
|
|
65
|
+
browser console additionally prints one line with a stable coarse reason code
|
|
66
|
+
(`reason: unknown-tools`, `workspace-unavailable`, `composition-mismatch`, `permission-preset`,
|
|
67
|
+
`adoption-refused`, `proof-mismatch` or `attestation-failed`) plus an operator
|
|
68
|
+
hint, so a refused surface can be diagnosed without Host log access.
|
|
69
|
+
|
|
70
|
+
The built-in branding, controls, status messages and accessibility labels are
|
|
71
|
+
Russian. The default quick questions are rendered directly above the composer
|
|
72
|
+
only while the current chat is empty. Set `suggestedQuestions: []` to hide
|
|
73
|
+
them, or provide a deployment-specific list to replace them.
|
|
74
|
+
|
|
75
|
+
When adding tools, update the deployment's reviewed capability inventory as
|
|
76
|
+
part of the same change. The package's
|
|
77
|
+
[default inventory](../capability-policy.json) is intentionally empty, matching
|
|
78
|
+
the default `toolPolicy.allow`.
|
|
79
|
+
|
|
80
|
+
## Configuration channel over the LAN
|
|
81
|
+
|
|
82
|
+
The browser normally reads the effective configuration from the Host-owned
|
|
83
|
+
`qa-surface` settings namespace. DSH pins settings RPCs to loopback, so a
|
|
84
|
+
browser served over the LAN always sees that namespace as unavailable. In
|
|
85
|
+
that case the client calls the plugin's `qaSurface/describe` Host Remote and
|
|
86
|
+
uses the returned effective configuration; a rejected call falls back to the
|
|
87
|
+
client defaults with the same `unavailable` status as before. Only one
|
|
88
|
+
describe request runs per page load, and a settled answer survives scope
|
|
89
|
+
updates — the namespace (when readable) stays the authority and keeps
|
|
90
|
+
delivering live changes.
|
|
91
|
+
|
|
92
|
+
Host-side enforcement never depended on the browser's read path: `secureSession`
|
|
93
|
+
attestation re-derives everything from the Host-owned configuration on every
|
|
94
|
+
bind and every prompt.
|
|
95
|
+
|
|
96
|
+
## Skill catalog scope
|
|
97
|
+
|
|
98
|
+
The deployment's agent preset controls which skills the QA assistant sees.
|
|
99
|
+
The shipped `qa-research` preset mounts the skill filesystem with
|
|
100
|
+
`includeDefaultRoots: false`: project-root skills (for example the harness
|
|
101
|
+
checkout's own `.dsh/skills`) and user-home skills stay out of the catalog,
|
|
102
|
+
and skills enter only through plugin providers or an explicit
|
|
103
|
+
`customSkillDirs` list in the preset. Keep the QA catalog to exactly the
|
|
104
|
+
skills the audience is meant to use.
|
|
105
|
+
|
|
106
|
+
## Deleting chats
|
|
107
|
+
|
|
108
|
+
The sidebar delete control removes a chat from this browser's index only;
|
|
109
|
+
the Host session stays on disk. Deleting the chat that is currently open
|
|
110
|
+
continues in a fresh attested session when `lockdown.allowSessionReset` is
|
|
111
|
+
enabled. There is no Host session-deletion API in DSH 0.1.x for the plugin
|
|
112
|
+
to call.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Host rejection whose wire message carries the coarse reason marker. */
|
|
2
|
+
export class QaAttestationError extends Error {
|
|
3
|
+
reason;
|
|
4
|
+
constructor(reason, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.reason = reason;
|
|
7
|
+
this.name = "QaAttestationError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=attestation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestation.js","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAmBA,0EAA0E;AAC1E,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC,MAAM;IADjB,YACW,MAA2B,EACpC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;sBAHN,MAAM;QAIf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import { DEFAULT_QA_SURFACE_CONFIG, resolveConfig } from "../resolve-config.js";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Browser projection of the Host-owned qa-surface settings namespace.
|
|
4
|
+
*
|
|
5
|
+
* The DSH gateway pins settings RPCs to loopback, so a browser served over the
|
|
6
|
+
* LAN always sees the namespace as `unavailable`. When the caller supplies the
|
|
7
|
+
* `qaSurface/describe` Remote as a fallback, that channel answers instead: the
|
|
8
|
+
* projection waits in `loading` until it settles, then reports the Host-owned
|
|
9
|
+
* configuration as `ready`. A rejected fallback returns the projection to
|
|
10
|
+
* `unavailable`, exactly as when no fallback exists.
|
|
11
|
+
*/
|
|
3
12
|
export class QaConfigController {
|
|
4
13
|
scope;
|
|
5
14
|
listeners = new Set();
|
|
6
15
|
snapshot;
|
|
7
16
|
unsubscribe;
|
|
8
|
-
|
|
17
|
+
fallback;
|
|
18
|
+
fallbackState = "idle";
|
|
19
|
+
fallbackConfig;
|
|
20
|
+
disposed = false;
|
|
21
|
+
constructor(scope, fallback) {
|
|
9
22
|
this.scope = scope;
|
|
23
|
+
this.fallback = fallback;
|
|
10
24
|
this.snapshot = this.project();
|
|
11
25
|
this.unsubscribe = scope.subscribe(() => this.refresh());
|
|
26
|
+
this.refresh();
|
|
12
27
|
}
|
|
13
28
|
getSnapshot = () => this.snapshot;
|
|
14
29
|
subscribe = (listener) => {
|
|
@@ -16,6 +31,7 @@ export class QaConfigController {
|
|
|
16
31
|
return () => this.listeners.delete(listener);
|
|
17
32
|
};
|
|
18
33
|
dispose() {
|
|
34
|
+
this.disposed = true;
|
|
19
35
|
this.unsubscribe();
|
|
20
36
|
this.listeners.clear();
|
|
21
37
|
}
|
|
@@ -29,6 +45,22 @@ export class QaConfigController {
|
|
|
29
45
|
};
|
|
30
46
|
}
|
|
31
47
|
if (settings.status === "unavailable") {
|
|
48
|
+
if ((this.fallbackState === "ready" ||
|
|
49
|
+
this.fallbackState === "refreshing") &&
|
|
50
|
+
this.fallbackConfig !== undefined) {
|
|
51
|
+
return {
|
|
52
|
+
status: "ready",
|
|
53
|
+
config: this.fallbackConfig,
|
|
54
|
+
error: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (this.fallbackState === "pending") {
|
|
58
|
+
return {
|
|
59
|
+
status: "loading",
|
|
60
|
+
config: DEFAULT_QA_SURFACE_CONFIG,
|
|
61
|
+
error: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
32
64
|
return {
|
|
33
65
|
status: "unavailable",
|
|
34
66
|
config: DEFAULT_QA_SURFACE_CONFIG,
|
|
@@ -46,12 +78,16 @@ export class QaConfigController {
|
|
|
46
78
|
return {
|
|
47
79
|
status: "error",
|
|
48
80
|
config: DEFAULT_QA_SURFACE_CONFIG,
|
|
49
|
-
error: "
|
|
81
|
+
error: "Настройки помощника недоступны.",
|
|
50
82
|
};
|
|
51
83
|
}
|
|
52
84
|
}
|
|
53
85
|
refresh() {
|
|
54
|
-
|
|
86
|
+
this.requestFallbackIfNeeded();
|
|
87
|
+
const projected = this.project();
|
|
88
|
+
const next = JSON.stringify(projected.config) === JSON.stringify(this.snapshot.config)
|
|
89
|
+
? { ...projected, config: this.snapshot.config }
|
|
90
|
+
: projected;
|
|
55
91
|
if (next.status === this.snapshot.status &&
|
|
56
92
|
next.config === this.snapshot.config &&
|
|
57
93
|
next.error === this.snapshot.error) {
|
|
@@ -61,5 +97,76 @@ export class QaConfigController {
|
|
|
61
97
|
for (const listener of this.listeners)
|
|
62
98
|
listener();
|
|
63
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Re-read the Host configuration after a Host reconnect. Without this, an
|
|
102
|
+
* open page keeps the configuration it fetched at load time: after the Host
|
|
103
|
+
* restarts with a changed deployment config, the next attestation compares
|
|
104
|
+
* its proof against the stale client copy and refuses. While re-reading,
|
|
105
|
+
* the previous answer stays projected — no loading flicker; the snapshot
|
|
106
|
+
* updates only when the answer actually changed.
|
|
107
|
+
*/
|
|
108
|
+
refreshFallback() {
|
|
109
|
+
if (this.disposed ||
|
|
110
|
+
this.fallback === undefined ||
|
|
111
|
+
this.fallbackState === "idle" ||
|
|
112
|
+
this.fallbackState === "pending" ||
|
|
113
|
+
this.scope.getSnapshot().status === "ready") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.fallbackState = "refreshing";
|
|
117
|
+
void this.fallback().then((config) => {
|
|
118
|
+
if (this.disposed)
|
|
119
|
+
return;
|
|
120
|
+
try {
|
|
121
|
+
this.fallbackConfig = resolveConfig(config);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// A malformed answer must not wipe a working previous one.
|
|
125
|
+
}
|
|
126
|
+
this.fallbackState = "ready";
|
|
127
|
+
// refresh() keeps the old projection when the answer is unchanged.
|
|
128
|
+
this.refresh();
|
|
129
|
+
}, (error) => {
|
|
130
|
+
if (this.disposed)
|
|
131
|
+
return;
|
|
132
|
+
console.warn("dsh-qa-surface: Host configuration refresh failed", error);
|
|
133
|
+
this.fallbackState = "ready";
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The fallback trigger rides every refresh: the scope flips to
|
|
138
|
+
* `unavailable` whenever the browser is served over the LAN, which a
|
|
139
|
+
* constructor-time check can miss. The state guard makes repeats a no-op,
|
|
140
|
+
* so one fetch runs per controller lifetime; the recursive refresh lets the
|
|
141
|
+
* pending state surface before the first notification pass.
|
|
142
|
+
*/
|
|
143
|
+
requestFallbackIfNeeded() {
|
|
144
|
+
if (this.disposed ||
|
|
145
|
+
this.fallback === undefined ||
|
|
146
|
+
this.fallbackState !== "idle" ||
|
|
147
|
+
this.scope.getSnapshot().status !== "unavailable") {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.fallbackState = "pending";
|
|
151
|
+
this.refresh();
|
|
152
|
+
void this.fallback().then((config) => {
|
|
153
|
+
if (this.disposed)
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
this.fallbackConfig = resolveConfig(config);
|
|
157
|
+
this.fallbackState = "ready";
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
this.fallbackState = "failed";
|
|
161
|
+
}
|
|
162
|
+
this.refresh();
|
|
163
|
+
}, (error) => {
|
|
164
|
+
if (this.disposed)
|
|
165
|
+
return;
|
|
166
|
+
console.warn("dsh-qa-surface: Host configuration is unavailable", error);
|
|
167
|
+
this.fallbackState = "failed";
|
|
168
|
+
this.refresh();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
64
171
|
}
|
|
65
172
|
//# sourceMappingURL=QaConfigController.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QaConfigController.js","sourceRoot":"","sources":["../../src/client/QaConfigController.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"QaConfigController.js","sourceRoot":"","sources":["../../src/client/QaConfigController.ts"],"names":[],"mappings":"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"}
|