dsh-wsr-execution 0.2.1 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-wsr-execution",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "DeepSeek Harness adapter bundle for WSR Intake, Delivery resources, Session state, Actions, and final results.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -39,10 +39,10 @@
39
39
  "displayName": "WSR",
40
40
  "role": "execution-adapter",
41
41
  "foundationOnly": false,
42
- "ownerRevision": "ffe7cc27f89f6555265f438a51f490355c68e7da",
42
+ "ownerRevision": "b4b3b487af7f163c5934aa758d86b183d64115ec",
43
43
  "ownerAsset": {
44
- "url": "https://github.com/firestige/wsr-execution/releases/download/0.2.1/wsr-execution-0.2.1.tgz",
45
- "sha256": "9375714297ea0af221ad9634f08c1b205d985649ae51db2964d46e6a9e4accf4"
44
+ "url": "https://github.com/firestige/wsr-execution/releases/download/0.2.2/wsr-execution-0.2.2.tgz",
45
+ "sha256": "d07eb0aaa4e0498e9e3f5f9bbf3ae4c6a1a9a7ea6c648d15cdfdcccbf53bb41e"
46
46
  }
47
47
  },
48
48
  "dependencies": {
@@ -15,7 +15,7 @@ const KINDS = new Set([
15
15
  "terminal-result",
16
16
  "error",
17
17
  ]);
18
- const ACTION_STATES = new Set(["running", "completed", "failed", "cancelled", "waiting", "recovering"]);
18
+ const ACTION_STATES = new Set(["running", "completed", "failed", "cancelled", "waiting", "recovering", "uncertain", "unresolved"]);
19
19
  const TERMINAL_OUTCOMES = new Set(["SUCCEEDED", "FAILED", "CANCELLED"]);
20
20
  const DELIVERY_STATES = new Set([
21
21
  "BOUND", "START_UNCERTAIN", "RUNNING_CORRELATED", "START_FAILED", "RESULT_UNRESOLVED", "TERMINAL_HANDLING",
@@ -61,14 +61,22 @@ function hasValidTypedData(value) {
61
61
  && (channel === "action" || channel === "tool");
62
62
  }
63
63
  if (value.kind === "terminal-result") {
64
- const outputValid = Object.hasOwn(value.data, "finalOutput")
64
+ const hasFinalOutput = Object.hasOwn(value.data, "finalOutput");
65
+ const hasSummary = Object.hasOwn(value.data, "summary");
66
+ const outputValid = hasFinalOutput
65
67
  ? typeof value.data.finalOutput === "string" && value.data.finalOutput.length > 0
66
- : typeof value.data.summary === "string" && value.data.summary.length > 0;
68
+ : hasSummary ? typeof value.data.summary === "string" && value.data.summary.length > 0
69
+ : value.data.outcome === "SUCCEEDED";
67
70
  return typeof value.data.outcome === "string" && TERMINAL_OUTCOMES.has(value.data.outcome) && outputValid;
68
71
  }
69
72
  if (value.kind === "delivery-running" || value.kind === "delivery-status") {
70
- return value.data.state === undefined
71
- || (typeof value.data.state === "string" && DELIVERY_STATES.has(value.data.state));
73
+ const diagnostic = value.data.diagnostic;
74
+ const diagnosticValid = diagnostic === undefined || (diagnostic !== null && typeof diagnostic === "object" && !Array.isArray(diagnostic)
75
+ && Object.keys(diagnostic).sort().join(",") === "causeCode,stage"
76
+ && typeof diagnostic.stage === "string" && /^[A-Z][A-Z0-9_]{0,63}$/u.test(diagnostic.stage)
77
+ && typeof diagnostic.causeCode === "string" && /^[A-Z][A-Z0-9_]{0,127}$/u.test(diagnostic.causeCode));
78
+ return diagnosticValid && (value.data.state === undefined
79
+ || (typeof value.data.state === "string" && DELIVERY_STATES.has(value.data.state)));
72
80
  }
73
81
  return true;
74
82
  }
@@ -117,8 +125,10 @@ function normalizedLifecycle(value, fallback) {
117
125
  if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
118
126
  if (["waiting", "awaiting-input"].includes(normalized)) return "waiting";
119
127
  if (["start-failed"].includes(normalized)) return "failed";
120
- if (["recovering", "recovery", "running-correlated", "result-unresolved", "terminal-handling"].includes(normalized)) return "recovering";
121
- if (["running", "accepted", "start-uncertain", "bound"].includes(normalized)) return "running";
128
+ if (normalized === "start-uncertain") return "uncertain";
129
+ if (normalized === "result-unresolved") return "unresolved";
130
+ if (["recovering", "recovery", "terminal-handling"].includes(normalized)) return "recovering";
131
+ if (["running", "accepted", "running-correlated", "bound"].includes(normalized)) return "running";
122
132
  return fallback;
123
133
  }
124
134
 
@@ -129,6 +139,8 @@ const STATE_LABELS = Object.freeze({
129
139
  cancelled: "Cancelled",
130
140
  waiting: "Waiting for input",
131
141
  recovering: "Recovering",
142
+ uncertain: "Start uncertain",
143
+ unresolved: "Result unresolved",
132
144
  });
133
145
 
134
146
  function model(input) {
@@ -145,7 +157,7 @@ export function projectExecutionPresentation(event) {
145
157
  return model({
146
158
  correlation, layer: "final", state, title: "Final result",
147
159
  summary: data.outcome[0] + data.outcome.slice(1).toLowerCase(), body,
148
- defaultOpen: true, focusPolicy: "none", role: "article",
160
+ defaultOpen: false, focusPolicy: "none", role: "article",
149
161
  compatibility: typeof data.finalOutput === "string" ? "current" : "legacy-summary",
150
162
  });
151
163
  }
@@ -163,7 +175,7 @@ export function projectExecutionPresentation(event) {
163
175
  correlation, layer: data.channel === "tool" ? "tool" : "action", state,
164
176
  title: typeof data.label === "string" ? data.label : "Workflow Action",
165
177
  summary: STATE_LABELS[state], body: text(data.content) ?? "WSR content unavailable",
166
- defaultOpen: state !== "completed", focusPolicy: "none", role: "status", compatibility: "current",
178
+ defaultOpen: false, focusPolicy: "none", role: "status", compatibility: "current",
167
179
  });
168
180
  }
169
181
  if (event.kind === "error") {
@@ -171,7 +183,7 @@ export function projectExecutionPresentation(event) {
171
183
  correlation, layer: "progress", state: "failed", title: "Workflow presentation",
172
184
  summary: typeof data.code === "string" ? data.code : "WSR_ERROR",
173
185
  body: typeof data.message === "string" ? data.message : "WSR presentation unavailable",
174
- defaultOpen: true, focusPolicy: "none", role: "alert", compatibility: "current",
186
+ defaultOpen: false, focusPolicy: "none", role: "alert", compatibility: "current",
175
187
  });
176
188
  }
177
189
 
@@ -179,10 +191,12 @@ export function projectExecutionPresentation(event) {
179
191
  ? normalizedLifecycle(data.state, "running")
180
192
  : normalizedLifecycle(data.state, event.kind === "command-accepted" ? "running" : "running");
181
193
  const deliveryId = typeof data.deliveryId === "string" ? data.deliveryId : undefined;
194
+ const diagnostic = data.diagnostic;
182
195
  return model({
183
196
  correlation, layer: "progress", state, title: "Workflow delivery",
184
197
  summary: `${STATE_LABELS[state]}${deliveryId === undefined ? "" : ` · ${deliveryId}`}`,
185
- body: undefined, defaultOpen: state !== "completed", focusPolicy: "none", role: "status", compatibility: "current",
198
+ body: diagnostic === undefined ? undefined : `${diagnostic.stage} · ${diagnostic.causeCode}`,
199
+ defaultOpen: false, focusPolicy: "none", role: "status", compatibility: "current",
186
200
  });
187
201
  }
188
202
 
@@ -190,7 +204,6 @@ export function projectExecutionPresentation(event) {
190
204
  export function resolveDisclosureOpen({ current, previousState, nextState, containsFocus }) {
191
205
  if (nextState === "waiting") return true;
192
206
  if (nextState === "completed" && previousState !== "completed") return containsFocus ? true : false;
193
- if (["running", "recovering", "failed", "cancelled"].includes(nextState) && nextState !== previousState) return true;
194
207
  return current;
195
208
  }
196
209
 
@@ -217,6 +230,10 @@ export function createExecutionPresentationDefinition() {
217
230
  if (event.kind === "delivery-list") {
218
231
  return Object.freeze({ ...context.state, presentation: undefined });
219
232
  }
233
+ if (event.kind === "terminal-result" && event.data.outcome === "SUCCEEDED"
234
+ && !Object.hasOwn(event.data, "finalOutput") && !Object.hasOwn(event.data, "summary")) {
235
+ return Object.freeze({ ...context.state, presentation: undefined });
236
+ }
220
237
  return Object.freeze({ ...context.state, presentation: projectExecutionPresentation(event) });
221
238
  },
222
239
  buildViewNode(context) {
@@ -1,29 +1,64 @@
1
- import {
2
- createExecutionPresentationDefinition,
3
- resolveDisclosureOpen,
4
- } from "./model.js";
1
+ import { parseExecutionPresentation, projectExecutionPresentation, resolveDisclosureOpen } from "./model.js";
5
2
 
6
3
  const DOT_STATE = Object.freeze({
7
4
  running: "ongoing",
8
5
  recovering: "ongoing",
6
+ uncertain: "warning",
7
+ unresolved: "warning",
9
8
  completed: "done",
10
9
  waiting: "warning",
11
10
  failed: "error",
12
11
  cancelled: "error",
13
12
  });
14
13
 
14
+ const ACTIONS_STYLE_ID = "dsh-wsr-execution-final-actions";
15
+ const ACTIONS_CSS = ".wsr-answer-actions{align-items:center;gap:10px;height:28px;margin-top:16px;margin-left:-6px;display:flex}.wsr-answer-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:transparent;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.wsr-answer-action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}";
16
+
17
+ export function installActionPresentationStyle() {
18
+ if (typeof document === "undefined" || document.getElementById(ACTIONS_STYLE_ID) !== null) return;
19
+ const tag = document.createElement("style");
20
+ tag.id = ACTIONS_STYLE_ID;
21
+ tag.dataset.plugin = "dsh-wsr-execution";
22
+ tag.textContent = ACTIONS_CSS;
23
+ document.head.append(tag);
24
+ }
25
+
15
26
  /**
16
27
  * Build the WSR renderer from Harness-owned, public UI primitives. Dependency
17
28
  * injection keeps the projection testable without copying any DSH component.
18
29
  */
19
- export function createActionPresentationView({ React, DisclosureRow, MessageText, StateDot, observe = () => undefined }) {
30
+ export function createActionPresentationView({
31
+ React,
32
+ DisclosureRow,
33
+ MessageText,
34
+ StateDot,
35
+ JsonTree,
36
+ Tooltip,
37
+ IconCopyOutline16,
38
+ IconCheckOutline16,
39
+ writeClipboard,
40
+ observe = () => undefined,
41
+ }) {
20
42
  if (typeof DisclosureRow !== "function") throw new TypeError("DSH_DISCLOSURE_ROW_REQUIRED");
43
+ installActionPresentationStyle();
21
44
 
22
- return function WsrExecutionPresentationView({ node }) {
45
+ return function WsrExecutionPresentationView({ node, technicalDetails }) {
23
46
  const presentation = node.data;
47
+ const presentationKind = typeof technicalDetails?.kind === "string"
48
+ ? technicalDetails.kind
49
+ : presentation.layer === "final" ? "terminal-result"
50
+ : presentation.state === "waiting" ? "action-input-request"
51
+ : ["action", "tool"].includes(presentation.layer) ? "action-output"
52
+ : presentation.state === "failed" && presentation.title === "Workflow presentation" ? "error"
53
+ : presentation.layer === "progress" && presentation.state === "running" ? "command-accepted"
54
+ : "delivery-status";
24
55
  const [open, setOpen] = React.useState(presentation.defaultOpen);
56
+ const [copyState, setCopyState] = React.useState("idle");
25
57
  const bodyRef = React.useRef(null);
26
58
  const previousState = React.useRef(presentation.state);
59
+ const copyPending = React.useRef(false);
60
+ const copyEpoch = React.useRef(0);
61
+ const copyTimer = React.useRef(null);
27
62
 
28
63
  React.useEffect(() => {
29
64
  setOpen((current) => resolveDisclosureOpen({
@@ -37,25 +72,67 @@ export function createActionPresentationView({ React, DisclosureRow, MessageText
37
72
  previousState.current = presentation.state;
38
73
  }, [presentation.state]);
39
74
 
75
+ React.useEffect(() => {
76
+ copyEpoch.current += 1;
77
+ copyPending.current = false;
78
+ if (copyTimer.current !== null) clearTimeout(copyTimer.current);
79
+ copyTimer.current = null;
80
+ setCopyState("idle");
81
+ return () => {
82
+ copyEpoch.current += 1;
83
+ copyPending.current = false;
84
+ if (copyTimer.current !== null) clearTimeout(copyTimer.current);
85
+ };
86
+ }, [presentation.body, presentation.correlation]);
87
+
40
88
  observe(presentation);
41
89
 
42
- if (presentation.layer === "final") {
90
+ if (presentation.layer === "final" && presentation.state === "completed") {
91
+ const label = copyState === "copied" ? "Copied" : copyState === "failed" ? "Copy failed" : "Copy";
92
+ const onCopy = async () => {
93
+ if (copyState === "copied" || copyPending.current) return;
94
+ const epoch = copyEpoch.current;
95
+ copyPending.current = true;
96
+ let accepted = false;
97
+ try { accepted = await writeClipboard(presentation.body); }
98
+ catch { accepted = false; }
99
+ if (epoch !== copyEpoch.current) return;
100
+ copyPending.current = false;
101
+ setCopyState(accepted ? "copied" : "failed");
102
+ copyTimer.current = globalThis.setTimeout(() => {
103
+ copyTimer.current = null;
104
+ setCopyState("idle");
105
+ }, 1_000);
106
+ };
43
107
  return React.createElement("article", {
44
108
  "data-wsr-presentation": "true",
109
+ "data-wsr-kind": presentationKind,
110
+ "data-wsr-surface": "chat",
45
111
  "data-wsr-layer": "final",
46
112
  "data-wsr-state": presentation.state,
47
113
  "data-wsr-correlation": presentation.correlation,
48
114
  "data-wsr-chat-role": "assistant",
49
115
  "data-wsr-compatibility": presentation.compatibility,
50
116
  "aria-label": presentation.title,
51
- }, React.createElement(MessageText, { text: presentation.body }));
117
+ },
118
+ React.createElement(MessageText, { text: presentation.body }),
119
+ React.createElement("div", {
120
+ className: "wsr-answer-actions",
121
+ "data-wsr-answer-actions": "true",
122
+ }, React.createElement(Tooltip, { label, side: "bottom" }, React.createElement("button", {
123
+ type: "button",
124
+ className: "wsr-answer-action",
125
+ "aria-label": label,
126
+ "data-copy-state": copyState,
127
+ onClick: onCopy,
128
+ }, React.createElement(copyState === "copied" ? IconCheckOutline16 : IconCopyOutline16, null)))));
52
129
  }
53
130
 
54
131
  const waiting = presentation.state === "waiting";
55
- const expandable = presentation.body !== undefined && !waiting;
56
- const body = presentation.body === undefined ? undefined : React.createElement("div", {
132
+ const expandable = (presentation.body !== undefined || technicalDetails !== undefined) && !waiting;
133
+ const body = presentation.body === undefined && technicalDetails === undefined ? undefined : React.createElement("div", {
57
134
  ref: bodyRef,
58
- "data-wsr-presentation": "true",
135
+ "data-wsr-presentation-body": "true",
59
136
  "data-wsr-layer": presentation.layer,
60
137
  "data-wsr-state": presentation.state,
61
138
  "data-wsr-correlation": presentation.correlation,
@@ -64,9 +141,13 @@ export function createActionPresentationView({ React, DisclosureRow, MessageText
64
141
  tabIndex: waiting ? 0 : undefined,
65
142
  "aria-label": waiting ? presentation.summary : undefined,
66
143
  "aria-live": waiting ? "polite" : undefined,
67
- }, React.createElement("pre", {
144
+ }, presentation.body === undefined ? null : React.createElement("pre", {
68
145
  style: { margin: 0, maxHeight: "20rem", overflow: "auto", whiteSpace: "pre-wrap", wordBreak: "break-word" },
69
- }, presentation.body));
146
+ }, presentation.body), technicalDetails === undefined ? null : React.createElement("details", null,
147
+ React.createElement("summary", null, "Technical details"),
148
+ JsonTree === undefined
149
+ ? React.createElement("pre", null, JSON.stringify(technicalDetails, null, 2))
150
+ : React.createElement(JsonTree, { data: technicalDetails, label: "WSR presentation", copyable: true, expandTopLevel: true })));
70
151
 
71
152
  return React.createElement(DisclosureRow, {
72
153
  icon: React.createElement(StateDot, { state: DOT_STATE[presentation.state], size: 10 }),
@@ -82,17 +163,94 @@ export function createActionPresentationView({ React, DisclosureRow, MessageText
82
163
  keepContentWhenOpen: true,
83
164
  collapsedContent: React.createElement("span", {
84
165
  role: presentation.role,
85
- "aria-live": ["running", "recovering", "waiting"].includes(presentation.state) ? "polite" : undefined,
166
+ "data-wsr-presentation": "true",
167
+ "data-wsr-kind": presentationKind,
168
+ "data-wsr-surface": "chat",
169
+ "data-wsr-chat-role": "assistant",
170
+ "data-wsr-correlation": presentation.correlation,
171
+ "data-wsr-state": presentation.state,
172
+ "aria-live": ["running", "recovering", "waiting"].includes(presentation.state) ? "polite" : undefined,
86
173
  }, presentation.summary),
87
174
  }, body);
88
175
  };
89
176
  }
90
177
 
91
- /** Register the feature-owned event projection and its one keyed chat node. */
178
+ const TERMINAL_PRESENTATION = Object.freeze({
179
+ SUCCEEDED: Object.freeze({ state: "completed", label: "Succeeded" }),
180
+ FAILED: Object.freeze({ state: "failed", label: "Failed" }),
181
+ CANCELLED: Object.freeze({ state: "cancelled", label: "Cancelled" }),
182
+ });
183
+
184
+ function reconcileDeliveryPresentation(presentation, admitted, inventoryState) {
185
+ const deliveryId = admitted?.kind === "delivery-running" && typeof admitted.data.deliveryId === "string"
186
+ ? admitted.data.deliveryId
187
+ : undefined;
188
+ const deliveries = ["ready", "reconnecting"].includes(inventoryState?.kind)
189
+ && Array.isArray(inventoryState.snapshot?.deliveries)
190
+ ? inventoryState.snapshot.deliveries
191
+ : [];
192
+ const matches = deliveryId === undefined ? [] : deliveries.filter((delivery) => delivery?.deliveryId === deliveryId);
193
+ const exact = matches.length === 1 ? matches[0] : undefined;
194
+ const terminal = exact?.lifecycle === "TERMINAL"
195
+ ? TERMINAL_PRESENTATION[exact?.terminal?.outcome]
196
+ : undefined;
197
+ if (terminal !== undefined) return Object.freeze({
198
+ ...presentation,
199
+ state: terminal.state,
200
+ summary: `${terminal.label} · ${deliveryId}`,
201
+ defaultOpen: false,
202
+ });
203
+ if (exact === undefined || typeof exact.lifecycle !== "string") return presentation;
204
+ const lifecycle = new Set(["BOUND", "START_UNCERTAIN", "RUNNING_CORRELATED", "START_FAILED", "RESULT_UNRESOLVED", "TERMINAL_HANDLING"]);
205
+ return lifecycle.has(exact.lifecycle)
206
+ ? projectExecutionPresentation({
207
+ correlation: presentation.correlation,
208
+ kind: "delivery-status",
209
+ data: {
210
+ deliveryId,
211
+ state: exact.lifecycle,
212
+ ...(admitted?.data?.diagnostic === undefined ? {} : { diagnostic: admitted.data.diagnostic }),
213
+ },
214
+ })
215
+ : presentation;
216
+ }
217
+
218
+ function commandPresentation(node, admitted, inventoryState) {
219
+ if (node.outcome === null) return Object.freeze({
220
+ correlation: String(node.commandId), layer: "progress", state: "running",
221
+ title: "Workflow delivery", summary: "Running", body: undefined,
222
+ defaultOpen: false, focusPolicy: "none", role: "status", compatibility: "current",
223
+ });
224
+ const event = admitted ?? parseExecutionPresentation(node.outcome?.text);
225
+ if (event.kind === "delivery-list") {
226
+ const count = Array.isArray(event.data.items) ? event.data.items.length : 0;
227
+ return Object.freeze({
228
+ correlation: event.correlation, layer: "progress", state: "completed",
229
+ title: "Delivery list", summary: `${count} ${count === 1 ? "delivery" : "deliveries"}`,
230
+ body: count === 0 ? "No deliveries." : JSON.stringify(event.data.items, null, 2),
231
+ defaultOpen: false, focusPolicy: "none", role: "status", compatibility: "current",
232
+ });
233
+ }
234
+ return reconcileDeliveryPresentation(projectExecutionPresentation(event), event, inventoryState);
235
+ }
236
+
237
+ /** Replace the generic command card so one-line durable JSON remains inspectable. */
238
+ export function createWsrCommandView(options) {
239
+ const View = createActionPresentationView(options);
240
+ const { React, inventory } = options;
241
+ return function WsrCommandView({ node }) {
242
+ const admitted = node.outcome === null ? undefined : parseExecutionPresentation(node.outcome?.text);
243
+ const inventoryState = inventory === undefined
244
+ ? undefined
245
+ : React.useSyncExternalStore(inventory.subscribe, inventory.getSnapshot, inventory.getSnapshot);
246
+ return View({ node: { data: commandPresentation(node, admitted, inventoryState) }, technicalDetails: admitted });
247
+ };
248
+ }
249
+
250
+ /** Hide the earlier native command row and render the ordered presentation row. */
92
251
  export function registerActionPresentation(ctx, View) {
93
- ctx.conversationEvents.register(createExecutionPresentationDefinition());
94
- ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
95
- name: "conversation.chat.node",
96
- key: "wsr-execution-presentation",
97
- }, View));
252
+ ctx.slots.inject("conversation.chat.commandview", () => {
253
+ ctx.slots.register({ name: "conversation.chat.commandview", key: "wsr" }, () => null);
254
+ ctx.slots.register({ name: "conversation.chat.commandview", key: "wsr-presentation" }, View);
255
+ });
98
256
  }
@@ -1,15 +1,15 @@
1
1
  import React from "react";
2
- import { DisclosureRow, MessageText, StateDot } from "@deepseek-ai/dsh-client-ui-primitives";
2
+ import { Button, DisclosureRow, IconCheckOutline16, IconCopyOutline16, JsonTree, MessageText, Pill, StateDot, Tooltip, writeClipboard } from "@deepseek-ai/dsh-client-ui-primitives";
3
3
  import * as workspaceUi from "@deepseek-ai/dsh-client-ui-workspace";
4
4
 
5
- import { createActionPresentationView, registerActionPresentation } from "../action-presentation/view.js";
5
+ import { createWsrCommandView, registerActionPresentation } from "../action-presentation/view.js";
6
6
  import { createDeliveryControlPlaneClient } from "./delivery/control-plane-port.js";
7
7
  import { registerSessionDeliveryView } from "./delivery/session-delivery-view.js";
8
8
  import { applyDeliverySidebar } from "./delivery-inventory/sidebar.js";
9
9
 
10
10
  export const name = "wsr-execution-client";
11
11
  export const inject = Object.freeze([
12
- "connection", "conversationEvents", "sessions", "slots", "workspaces", "locale",
12
+ "connection", "sessions", "slots", "workspaces", "locale",
13
13
  ]);
14
14
 
15
15
  export function apply(ctx) {
@@ -22,11 +22,30 @@ export function apply(ctx) {
22
22
  applyDeliverySidebar(ctx, { React, workspaceUi, inventory: controlPlane.inventory });
23
23
  registerSessionDeliveryView(ctx, {
24
24
  React,
25
+ Button,
26
+ DisclosureRow,
27
+ IconCheckOutline16,
28
+ IconCopyOutline16,
29
+ Pill,
30
+ StateDot,
31
+ Tooltip,
32
+ writeClipboard,
25
33
  bindProjection(sessionId) {
26
34
  const source = controlPlane.bindSession(String(sessionId));
27
35
  void source.refresh();
28
36
  return source;
29
37
  },
30
38
  });
31
- registerActionPresentation(ctx, createActionPresentationView({ React, DisclosureRow, MessageText, StateDot }));
39
+ registerActionPresentation(ctx, createWsrCommandView({
40
+ React,
41
+ DisclosureRow,
42
+ IconCheckOutline16,
43
+ IconCopyOutline16,
44
+ JsonTree,
45
+ MessageText,
46
+ StateDot,
47
+ Tooltip,
48
+ writeClipboard,
49
+ inventory: controlPlane.inventory,
50
+ }));
32
51
  }
@@ -1,4 +1,10 @@
1
1
  const CHANNEL = "/wsr-execution";
2
+ const ERROR_CODES = new Set([
3
+ "DELIVERY_PROJECTION_CORRUPT",
4
+ "DELIVERY_PROJECTION_STALE_BINDING",
5
+ "DELIVERY_PROJECTION_RECOVERY_MISMATCH",
6
+ "DELIVERY_PROJECTION_UNAVAILABLE",
7
+ ]);
2
8
 
3
9
  function createStore(initial) {
4
10
  let snapshot = initial;
@@ -23,7 +29,9 @@ export function createDeliveryControlPlaneClient(rpc) {
23
29
  const sessions = new Map();
24
30
  const read = async (endpoint, payload) => {
25
31
  const result = await rpc.call(CHANNEL, endpoint, payload);
26
- if (result?.ok !== true) throw new Error(message(result?.error));
32
+ if (result?.ok !== true) throw Object.assign(new Error(message(result?.error)), {
33
+ code: ERROR_CODES.has(result?.error?.code) ? result.error.code : "DELIVERY_PROJECTION_UNAVAILABLE",
34
+ });
27
35
  return result.value;
28
36
  };
29
37
  const client = {
@@ -37,6 +45,7 @@ export function createDeliveryControlPlaneClient(rpc) {
37
45
  const previous = inventory.getSnapshot();
38
46
  inventory.publish({
39
47
  kind: previous.kind === "ready" ? "reconnecting" : "error",
48
+ code: typeof error?.code === "string" ? error.code : "DELIVERY_PROJECTION_UNAVAILABLE",
40
49
  message: message(error),
41
50
  ...(previous.kind === "ready" ? { snapshot: previous.snapshot } : {}),
42
51
  });
@@ -53,7 +62,7 @@ export function createDeliveryControlPlaneClient(rpc) {
53
62
  subscribe: store.subscribe,
54
63
  async refresh() {
55
64
  try { store.publish({ kind: "ready", view: await read("session/read", { sessionCorrelation }) }); }
56
- catch (error) { store.publish({ kind: "error", code: "DELIVERY_PROJECTION_UNAVAILABLE", message: message(error) }); }
65
+ catch (error) { store.publish({ kind: "error", code: typeof error?.code === "string" ? error.code : "DELIVERY_PROJECTION_UNAVAILABLE", message: message(error) }); }
57
66
  },
58
67
  });
59
68
  sessions.set(sessionCorrelation, source);