dsh-wsr-execution 0.0.1 → 0.1.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/package.json CHANGED
@@ -1,24 +1,77 @@
1
1
  {
2
2
  "name": "dsh-wsr-execution",
3
- "version": "0.0.1",
4
- "description": "Reserved package name for the WSR Execution integration for DeepSeek Harness.",
3
+ "version": "0.1.0",
4
+ "description": "DeepSeek Harness adapter bundle for WSR Intake, Delivery resources, Session state, Actions, and final results.",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./cordis.patch.yml": "./cordis.patch.yml",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "lib/client.js",
16
+ "skills",
17
+ "cordis.patch.yml",
18
+ "NOTICE.md",
19
+ "README.md"
20
+ ],
21
+ "dsh": {
22
+ "bundle": {
23
+ "patch": "./cordis.patch.yml"
24
+ },
25
+ "client": {
26
+ "inject": [
27
+ "@deepseek-ai/dsh-client-connection",
28
+ "@deepseek-ai/dsh-client-runtime",
29
+ "@deepseek-ai/dsh-client-ui-conversation",
30
+ "@deepseek-ai/dsh-client-ui-primitives"
31
+ ],
32
+ "platform": "web"
33
+ },
34
+ "compatibility": {
35
+ "dsh": "0.1.1-rc.2"
36
+ }
37
+ },
38
+ "wsr": {
39
+ "displayName": "WSR",
40
+ "role": "execution-adapter",
41
+ "foundationOnly": false,
42
+ "ownerRevision": "0feb3333afd88e00444f80a7a0d135d2f93582db",
43
+ "ownerAsset": {
44
+ "url": "https://github.com/firestige/wsr-execution/releases/download/0.1.4-rc.1/wsr-execution-0.1.4.tgz",
45
+ "sha256": "4407239534795f528b3ca597583a682636dd539516f567434a128d5437345e4d"
46
+ }
47
+ },
48
+ "dependencies": {
49
+ "@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
50
+ "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
51
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.1-rc.2",
52
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
53
+ "@deepseek-ai/dsh-client-ui-workspace": "0.1.1-rc.2",
54
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2"
55
+ },
56
+ "peerDependencies": {
57
+ "@deepseek-ai/dsh": "0.1.1-rc.2",
58
+ "wsr-execution": "0.1.4"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@deepseek-ai/dsh": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "engines": {
66
+ "node": "24.12.0"
67
+ },
5
68
  "license": "Apache-2.0",
6
- "author": {
7
- "name": "firestige"
69
+ "publishConfig": {
70
+ "access": "public"
8
71
  },
9
72
  "repository": {
10
73
  "type": "git",
11
74
  "url": "git+https://github.com/firestige/wsr-dsh.git",
12
75
  "directory": "packages/execution"
13
- },
14
- "homepage": "https://github.com/firestige/wsr-dsh#readme",
15
- "bugs": {
16
- "url": "https://github.com/firestige/wsr-dsh/issues"
17
- },
18
- "keywords": [
19
- "deepseek-harness",
20
- "dsh",
21
- "workflow",
22
- "wsr"
23
- ]
76
+ }
24
77
  }
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: workflow-execution
3
+ description: Explicitly create, inspect, recover, finish an interaction, or abandon a Workflow Delivery.
4
+ disable-model-invocation: true
5
+ user-invocable: true
6
+ ---
7
+
8
+ Use the operation that matches the user's explicit request:
9
+
10
+ - `list` discovers privacy-safe Delivery and worktree state.
11
+ - `create` starts a new Delivery from the current turn text and attachments.
12
+ - `recover` binds this conversation to an exact detached Delivery, or to this worktree's Delivery when no ID was supplied.
13
+ - `status` inspects the current binding or one exact Delivery ID.
14
+ - `action-finish` corresponds to `/wsr action finish`; it requests closure of the current Action interaction without claiming that the Action completed.
15
+ - `abandon` performs exact authorized abandonment.
16
+
17
+ Call `workflow_execution_intake` exactly once with that closed operation and the current host-neutral turn values. Do not call executable plugin code, Core, M01, Runner, an execution session, or a Provider service directly.
@@ -0,0 +1,236 @@
1
+ export const DSH_ACTION_PRESENTATION_COMPATIBILITY = Object.freeze({
2
+ dsh: "0.1.1-rc.2",
3
+ uiPrimitives: "@deepseek-ai/dsh-client-ui-primitives@0.1.1-rc.2",
4
+ presentation: "wsr.presentation@1.0.0",
5
+ });
6
+
7
+ const ROOT_KEYS = "correlation,data,kind,schemaVersion";
8
+ const KINDS = new Set([
9
+ "command-accepted",
10
+ "delivery-running",
11
+ "delivery-list",
12
+ "delivery-status",
13
+ "action-output",
14
+ "action-input-request",
15
+ "terminal-result",
16
+ "error",
17
+ ]);
18
+ const ACTION_STATES = new Set(["running", "completed", "failed", "cancelled", "waiting", "recovering"]);
19
+ const TERMINAL_OUTCOMES = new Set(["SUCCEEDED", "FAILED", "CANCELLED"]);
20
+ const DELIVERY_STATES = new Set([
21
+ "BOUND", "START_UNCERTAIN", "RUNNING_CORRELATED", "START_FAILED", "RESULT_UNRESOLVED", "TERMINAL_HANDLING",
22
+ "RUNNING", "RECOVERING", "WAITING", "AWAITING_INPUT", "COMPLETED", "SUCCEEDED", "FAILED", "CANCELLED",
23
+ ]);
24
+ const MAX_PRESENTATION_BYTES = 4096;
25
+
26
+ function deepFreeze(value, seen = new Set()) {
27
+ if (value === null || typeof value !== "object" || seen.has(value)) return value;
28
+ seen.add(value);
29
+ for (const child of Object.values(value)) deepFreeze(child, seen);
30
+ return Object.freeze(value);
31
+ }
32
+
33
+ function invalidPresentation() {
34
+ return deepFreeze({
35
+ schemaVersion: DSH_ACTION_PRESENTATION_COMPATIBILITY.presentation,
36
+ correlation: "presentation-invalid",
37
+ kind: "error",
38
+ data: { code: "WSR_PRESENTATION_INVALID", message: "WSR presentation unavailable" },
39
+ });
40
+ }
41
+
42
+ function plainJson(value, seen = new Set()) {
43
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
44
+ if (typeof value === "number") return Number.isFinite(value);
45
+ if (typeof value !== "object" || seen.has(value)) return false;
46
+ seen.add(value);
47
+ if (Array.isArray(value)) return value.every((item) => plainJson(item, seen));
48
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
49
+ const descriptors = Object.getOwnPropertyDescriptors(value);
50
+ return Reflect.ownKeys(value).every((key) => typeof key === "string"
51
+ && descriptors[key] !== undefined
52
+ && "value" in descriptors[key]
53
+ && plainJson(descriptors[key].value, seen));
54
+ }
55
+
56
+ function hasValidTypedData(value) {
57
+ if (value.kind === "action-output") {
58
+ const state = value.data.state === undefined ? "completed" : value.data.state;
59
+ const channel = value.data.channel === undefined ? "action" : value.data.channel;
60
+ return typeof state === "string" && ACTION_STATES.has(state)
61
+ && (channel === "action" || channel === "tool");
62
+ }
63
+ if (value.kind === "terminal-result") {
64
+ const outputValid = Object.hasOwn(value.data, "finalOutput")
65
+ ? typeof value.data.finalOutput === "string" && value.data.finalOutput.length > 0
66
+ : typeof value.data.summary === "string" && value.data.summary.length > 0;
67
+ return typeof value.data.outcome === "string" && TERMINAL_OUTCOMES.has(value.data.outcome) && outputValid;
68
+ }
69
+ 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));
72
+ }
73
+ return true;
74
+ }
75
+
76
+ /**
77
+ * Admit one durable Execution presentation fact. Unsupported revisions and
78
+ * shapes become a bounded generic error; rejected bytes are never echoed.
79
+ */
80
+ export function parseExecutionPresentation(input) {
81
+ let value = input;
82
+ if (typeof input === "string") {
83
+ if (input.length === 0 || new TextEncoder().encode(input).byteLength > MAX_PRESENTATION_BYTES) return invalidPresentation();
84
+ try { value = JSON.parse(input); } catch { return invalidPresentation(); }
85
+ }
86
+ if (value === null || typeof value !== "object" || Array.isArray(value)
87
+ || Object.keys(value).sort().join(",") !== ROOT_KEYS
88
+ || value.schemaVersion !== DSH_ACTION_PRESENTATION_COMPATIBILITY.presentation
89
+ || typeof value.correlation !== "string" || value.correlation.length === 0
90
+ || !KINDS.has(value.kind)
91
+ || value.data === null || typeof value.data !== "object" || Array.isArray(value.data)
92
+ || !plainJson(value.data)
93
+ || !hasValidTypedData(value)) return invalidPresentation();
94
+ return deepFreeze(structuredClone(value));
95
+ }
96
+
97
+ function text(value, keys = ["text", "message", "question", "summary", "result"]) {
98
+ if (typeof value === "string") return value;
99
+ if (Array.isArray(value)) {
100
+ const lines = value.map((item) => text(item, keys)).filter((item) => typeof item === "string" && item.length > 0);
101
+ return lines.length === 0 ? undefined : lines.join("\n\n");
102
+ }
103
+ if (value === null || typeof value !== "object") return undefined;
104
+ for (const key of keys) {
105
+ if (!Object.hasOwn(value, key)) continue;
106
+ const nested = text(value[key], keys);
107
+ if (nested !== undefined) return nested;
108
+ }
109
+ return undefined;
110
+ }
111
+
112
+ function normalizedLifecycle(value, fallback) {
113
+ if (typeof value !== "string") return fallback;
114
+ const normalized = value.toLowerCase().replaceAll("_", "-");
115
+ if (["succeeded", "success", "done", "terminal"].includes(normalized)) return "completed";
116
+ if (["failed", "error"].includes(normalized)) return "failed";
117
+ if (["cancelled", "canceled"].includes(normalized)) return "cancelled";
118
+ if (["waiting", "awaiting-input"].includes(normalized)) return "waiting";
119
+ 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";
122
+ return fallback;
123
+ }
124
+
125
+ const STATE_LABELS = Object.freeze({
126
+ running: "Running",
127
+ completed: "Completed",
128
+ failed: "Failed",
129
+ cancelled: "Cancelled",
130
+ waiting: "Waiting for input",
131
+ recovering: "Recovering",
132
+ });
133
+
134
+ function model(input) {
135
+ return deepFreeze(input);
136
+ }
137
+
138
+ /** Convert a typed owner fact into the browser-only WSR presentation model. */
139
+ export function projectExecutionPresentation(event) {
140
+ const { correlation, data } = event;
141
+ if (event.kind === "delivery-list") throw new TypeError("WSR_PRESENTATION_OUT_OF_SCOPE");
142
+ if (event.kind === "terminal-result") {
143
+ const state = normalizedLifecycle(data.outcome, "failed");
144
+ const body = typeof data.finalOutput === "string" ? data.finalOutput : data.summary;
145
+ return model({
146
+ correlation, layer: "final", state, title: "Final result",
147
+ summary: data.outcome[0] + data.outcome.slice(1).toLowerCase(), body,
148
+ defaultOpen: true, focusPolicy: "none", role: "article",
149
+ compatibility: typeof data.finalOutput === "string" ? "current" : "legacy-summary",
150
+ });
151
+ }
152
+ if (event.kind === "action-input-request") {
153
+ return model({
154
+ correlation, layer: "action", state: "waiting",
155
+ title: typeof data.label === "string" ? data.label : "Workflow Action",
156
+ summary: STATE_LABELS.waiting, body: text(data.prompt) ?? "Input required",
157
+ defaultOpen: true, focusPolicy: "preserve", role: "status", compatibility: "current",
158
+ });
159
+ }
160
+ if (event.kind === "action-output") {
161
+ const state = data.state ?? "completed";
162
+ return model({
163
+ correlation, layer: data.channel === "tool" ? "tool" : "action", state,
164
+ title: typeof data.label === "string" ? data.label : "Workflow Action",
165
+ summary: STATE_LABELS[state], body: text(data.content) ?? "WSR content unavailable",
166
+ defaultOpen: state !== "completed", focusPolicy: "none", role: "status", compatibility: "current",
167
+ });
168
+ }
169
+ if (event.kind === "error") {
170
+ return model({
171
+ correlation, layer: "progress", state: "failed", title: "Workflow presentation",
172
+ summary: typeof data.code === "string" ? data.code : "WSR_ERROR",
173
+ body: typeof data.message === "string" ? data.message : "WSR presentation unavailable",
174
+ defaultOpen: true, focusPolicy: "none", role: "alert", compatibility: "current",
175
+ });
176
+ }
177
+
178
+ const state = event.kind === "delivery-running"
179
+ ? normalizedLifecycle(data.state, "running")
180
+ : normalizedLifecycle(data.state, event.kind === "command-accepted" ? "running" : "running");
181
+ const deliveryId = typeof data.deliveryId === "string" ? data.deliveryId : undefined;
182
+ return model({
183
+ correlation, layer: "progress", state, title: "Workflow delivery",
184
+ summary: `${STATE_LABELS[state]}${deliveryId === undefined ? "" : ` · ${deliveryId}`}`,
185
+ body: undefined, defaultOpen: state !== "completed", focusPolicy: "none", role: "status", compatibility: "current",
186
+ });
187
+ }
188
+
189
+ /** Lifecycle-driven disclosure policy that never destroys the active focus target. */
190
+ export function resolveDisclosureOpen({ current, previousState, nextState, containsFocus }) {
191
+ if (nextState === "waiting") return true;
192
+ if (nextState === "completed" && previousState !== "completed") return containsFocus ? true : false;
193
+ if (["running", "recovering", "failed", "cancelled"].includes(nextState) && nextState !== previousState) return true;
194
+ return current;
195
+ }
196
+
197
+ /** Durable Harness conversation-event adapter. It creates only WSR nodes. */
198
+ export function createExecutionPresentationDefinition() {
199
+ return Object.freeze({
200
+ kind: "wsr-execution-presentation",
201
+ target: "chat",
202
+ match(event) {
203
+ if (event?.type === "command/run" && event.data?.name === "wsr"
204
+ && event.data?.source?.kind === "plugin" && event.data?.source?.plugin === "workflow-execution"
205
+ && typeof event.data?.commandId === "string") {
206
+ return { id: event.data.commandId, role: "start" };
207
+ }
208
+ return event?.type === "command/done" && typeof event.data?.commandId === "string"
209
+ ? { id: event.data.commandId, role: "update" }
210
+ : null;
211
+ },
212
+ start(_context, match) {
213
+ return Object.freeze({ seq: match.event.seq, presentation: undefined });
214
+ },
215
+ update(context, match) {
216
+ const event = parseExecutionPresentation(match.event?.data?.text);
217
+ if (event.kind === "delivery-list") {
218
+ return Object.freeze({ ...context.state, presentation: undefined });
219
+ }
220
+ return Object.freeze({ ...context.state, presentation: projectExecutionPresentation(event) });
221
+ },
222
+ buildViewNode(context) {
223
+ if (context.state?.presentation === undefined) return null;
224
+ return Object.freeze({
225
+ key: context.key,
226
+ kind: "wsr-execution-presentation",
227
+ id: context.id,
228
+ target: "chat",
229
+ anchorSeq: context.state.seq,
230
+ location: context.start?.location ?? { kind: "unresolved" },
231
+ visibility: "visible",
232
+ data: context.state.presentation,
233
+ });
234
+ },
235
+ });
236
+ }
@@ -0,0 +1,98 @@
1
+ import {
2
+ createExecutionPresentationDefinition,
3
+ resolveDisclosureOpen,
4
+ } from "./model.js";
5
+
6
+ const DOT_STATE = Object.freeze({
7
+ running: "ongoing",
8
+ recovering: "ongoing",
9
+ completed: "done",
10
+ waiting: "warning",
11
+ failed: "error",
12
+ cancelled: "error",
13
+ });
14
+
15
+ /**
16
+ * Build the WSR renderer from Harness-owned, public UI primitives. Dependency
17
+ * injection keeps the projection testable without copying any DSH component.
18
+ */
19
+ export function createActionPresentationView({ React, DisclosureRow, MessageText, StateDot, observe = () => undefined }) {
20
+ if (typeof DisclosureRow !== "function") throw new TypeError("DSH_DISCLOSURE_ROW_REQUIRED");
21
+
22
+ return function WsrExecutionPresentationView({ node }) {
23
+ const presentation = node.data;
24
+ const [open, setOpen] = React.useState(presentation.defaultOpen);
25
+ const bodyRef = React.useRef(null);
26
+ const previousState = React.useRef(presentation.state);
27
+
28
+ React.useEffect(() => {
29
+ setOpen((current) => resolveDisclosureOpen({
30
+ current,
31
+ previousState: previousState.current,
32
+ nextState: presentation.state,
33
+ containsFocus: typeof document !== "undefined"
34
+ && bodyRef.current !== null
35
+ && bodyRef.current.contains(document.activeElement),
36
+ }));
37
+ previousState.current = presentation.state;
38
+ }, [presentation.state]);
39
+
40
+ observe(presentation);
41
+
42
+ if (presentation.layer === "final") {
43
+ return React.createElement("article", {
44
+ "data-wsr-presentation": "true",
45
+ "data-wsr-layer": "final",
46
+ "data-wsr-state": presentation.state,
47
+ "data-wsr-correlation": presentation.correlation,
48
+ "data-wsr-chat-role": "assistant",
49
+ "data-wsr-compatibility": presentation.compatibility,
50
+ "aria-label": presentation.title,
51
+ }, React.createElement(MessageText, { text: presentation.body }));
52
+ }
53
+
54
+ const waiting = presentation.state === "waiting";
55
+ const expandable = presentation.body !== undefined && !waiting;
56
+ const body = presentation.body === undefined ? undefined : React.createElement("div", {
57
+ ref: bodyRef,
58
+ "data-wsr-presentation": "true",
59
+ "data-wsr-layer": presentation.layer,
60
+ "data-wsr-state": presentation.state,
61
+ "data-wsr-correlation": presentation.correlation,
62
+ "data-wsr-action-input": waiting ? "true" : undefined,
63
+ role: waiting ? "group" : undefined,
64
+ tabIndex: waiting ? 0 : undefined,
65
+ "aria-label": waiting ? presentation.summary : undefined,
66
+ "aria-live": waiting ? "polite" : undefined,
67
+ }, React.createElement("pre", {
68
+ style: { margin: 0, maxHeight: "20rem", overflow: "auto", whiteSpace: "pre-wrap", wordBreak: "break-word" },
69
+ }, presentation.body));
70
+
71
+ return React.createElement(DisclosureRow, {
72
+ icon: React.createElement(StateDot, { state: DOT_STATE[presentation.state], size: 10 }),
73
+ title: presentation.title,
74
+ open: waiting ? true : open,
75
+ expandable,
76
+ onToggle: waiting ? () => undefined : () => setOpen((current) => !current),
77
+ expandOnRowClick: expandable,
78
+ // The locked primitive animates its hover-preview icon without a
79
+ // reduced-motion branch. Keeping that optional preview off preserves the
80
+ // same keyboard disclosure while making WSR rows motion-free.
81
+ previewChevron: false,
82
+ keepContentWhenOpen: true,
83
+ collapsedContent: React.createElement("span", {
84
+ role: presentation.role,
85
+ "aria-live": ["running", "recovering", "waiting"].includes(presentation.state) ? "polite" : undefined,
86
+ }, presentation.summary),
87
+ }, body);
88
+ };
89
+ }
90
+
91
+ /** Register the feature-owned event projection and its one keyed chat node. */
92
+ 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));
98
+ }
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { DisclosureRow, MessageText, StateDot } from "@deepseek-ai/dsh-client-ui-primitives";
3
+ import * as workspaceUi from "@deepseek-ai/dsh-client-ui-workspace";
4
+
5
+ import { createActionPresentationView, registerActionPresentation } from "../action-presentation/view.js";
6
+ import { createDeliveryControlPlaneClient } from "./delivery/control-plane-port.js";
7
+ import { registerSessionDeliveryView } from "./delivery/session-delivery-view.js";
8
+ import { applyDeliverySidebar } from "./delivery-inventory/sidebar.js";
9
+
10
+ export const name = "wsr-execution-client";
11
+ export const inject = Object.freeze([
12
+ "connection", "conversationEvents", "sessions", "slots", "workspaces", "locale",
13
+ ]);
14
+
15
+ export function apply(ctx) {
16
+ const controlPlane = createDeliveryControlPlaneClient(ctx.connection.rpc);
17
+ const refresh = () => { void controlPlane.refresh(); };
18
+ refresh();
19
+ const timer = setInterval(refresh, 2_000);
20
+ ctx.effect(() => () => clearInterval(timer), "wsr-execution: control-plane refresh");
21
+
22
+ applyDeliverySidebar(ctx, { React, workspaceUi, inventory: controlPlane.inventory });
23
+ registerSessionDeliveryView(ctx, {
24
+ React,
25
+ bindProjection(sessionId) {
26
+ const source = controlPlane.bindSession(String(sessionId));
27
+ void source.refresh();
28
+ return source;
29
+ },
30
+ });
31
+ registerActionPresentation(ctx, createActionPresentationView({ React, DisclosureRow, MessageText, StateDot }));
32
+ }
@@ -0,0 +1,64 @@
1
+ const CHANNEL = "/wsr-execution";
2
+
3
+ function createStore(initial) {
4
+ let snapshot = initial;
5
+ const listeners = new Set();
6
+ return Object.freeze({
7
+ getSnapshot: () => snapshot,
8
+ subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
9
+ publish(value) { snapshot = Object.freeze(value); for (const listener of [...listeners]) listener(); },
10
+ });
11
+ }
12
+
13
+ function message(error) {
14
+ return typeof error?.message === "string" && error.message.length > 0
15
+ ? error.message
16
+ : "Delivery control plane unavailable";
17
+ }
18
+
19
+ /** Browser-side React stores backed only by the loopback Host read gateway. */
20
+ export function createDeliveryControlPlaneClient(rpc) {
21
+ if (typeof rpc?.call !== "function") throw new TypeError("DSH_CONNECTION_RPC_REQUIRED");
22
+ const inventory = createStore(Object.freeze({ kind: "loading" }));
23
+ const sessions = new Map();
24
+ const read = async (endpoint, payload) => {
25
+ const result = await rpc.call(CHANNEL, endpoint, payload);
26
+ if (result?.ok !== true) throw new Error(message(result?.error));
27
+ return result.value;
28
+ };
29
+ const client = {
30
+ inventory,
31
+ async refresh() {
32
+ try {
33
+ const value = await read("inventory/read", {});
34
+ inventory.publish({ kind: "ready", snapshot: value });
35
+ for (const source of sessions.values()) void source.refresh();
36
+ } catch (error) {
37
+ const previous = inventory.getSnapshot();
38
+ inventory.publish({
39
+ kind: previous.kind === "ready" ? "reconnecting" : "error",
40
+ message: message(error),
41
+ ...(previous.kind === "ready" ? { snapshot: previous.snapshot } : {}),
42
+ });
43
+ }
44
+ },
45
+ bindSession(sessionCorrelation) {
46
+ if (typeof sessionCorrelation !== "string" || sessionCorrelation.length === 0 || sessionCorrelation.length > 512) {
47
+ throw new TypeError("SESSION_CORRELATION_INVALID");
48
+ }
49
+ if (sessions.has(sessionCorrelation)) return sessions.get(sessionCorrelation);
50
+ const store = createStore(Object.freeze({ kind: "loading" }));
51
+ const source = Object.freeze({
52
+ getSnapshot: store.getSnapshot,
53
+ subscribe: store.subscribe,
54
+ async refresh() {
55
+ 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) }); }
57
+ },
58
+ });
59
+ sessions.set(sessionCorrelation, source);
60
+ return source;
61
+ },
62
+ };
63
+ return Object.freeze(client);
64
+ }
@@ -0,0 +1,126 @@
1
+ export const DELIVERY_VIEW_ID = "delivery";
2
+ export const DELIVERY_VIEW_ORDER = 20;
3
+
4
+ const SHA256 = /^sha256:[0-9a-f]{64}$/u;
5
+ const LIFECYCLES = new Set([
6
+ "BOUND", "START_UNCERTAIN", "RUNNING_CORRELATED", "START_FAILED",
7
+ "RESULT_UNRESOLVED", "TERMINAL_HANDLING", "TERMINAL",
8
+ ]);
9
+
10
+ function nonEmpty(value) {
11
+ return typeof value === "string" && value.length > 0;
12
+ }
13
+ function validDelivery(delivery, sessionCorrelation) {
14
+ return delivery !== null && typeof delivery === "object" && !Array.isArray(delivery)
15
+ && nonEmpty(delivery.deliveryId) && SHA256.test(delivery.deliveryBindingIdentity)
16
+ && nonEmpty(delivery.task?.identity)
17
+ && nonEmpty(delivery.workflow?.identity)
18
+ && nonEmpty(delivery.workflow?.packageName)
19
+ && nonEmpty(delivery.workflow?.exactPackageVersion)
20
+ && SHA256.test(delivery.workflow?.packageDigest)
21
+ && nonEmpty(delivery.workflow?.snapshotIdentity)
22
+ && SHA256.test(delivery.workflow?.snapshotDigest)
23
+ && LIFECYCLES.has(delivery.lifecycle)
24
+ && delivery.navigation?.sessionCorrelation === sessionCorrelation
25
+ && delivery.detached === false
26
+ && typeof delivery.recoverable === "boolean"
27
+ && Number.isSafeInteger(delivery.timing?.startedAt)
28
+ && Number.isSafeInteger(delivery.timing?.updatedAt)
29
+ && Number.isSafeInteger(delivery.timing?.elapsedMs)
30
+ && delivery.timing.elapsedMs >= 0;
31
+ }
32
+
33
+ function duration(value) {
34
+ const seconds = Math.floor(value / 1000);
35
+ if (seconds < 60) return `${seconds}.${String(value % 1000).padStart(3, "0")}s`;
36
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
37
+ }
38
+
39
+ function safeSnapshot(source) {
40
+ try { return source.getSnapshot(); }
41
+ catch { return { kind: "error", code: "DELIVERY_PROJECTION_UNAVAILABLE", message: "Execution projection unavailable" }; }
42
+ }
43
+
44
+ function safeSubscribe(source, notify) {
45
+ try { const dispose = source.subscribe(notify); return typeof dispose === "function" ? dispose : () => undefined; }
46
+ catch { return () => undefined; }
47
+ }
48
+
49
+ function pair(React, label, value) {
50
+ return [
51
+ React.createElement("dt", { key: `${label}-label` }, label),
52
+ React.createElement("dd", { key: `${label}-value` }, value),
53
+ ];
54
+ }
55
+
56
+ function statePanel(React, role, code, message) {
57
+ return React.createElement("section", {
58
+ "aria-labelledby": "wsr-delivery-view-title", "aria-live": role === "alert" ? "assertive" : "polite",
59
+ "data-wsr-delivery-view": "true", role,
60
+ }, React.createElement("h2", { id: "wsr-delivery-view-title" }, "Delivery"),
61
+ React.createElement("p", null, message), code === undefined ? null : React.createElement("code", null, code));
62
+ }
63
+
64
+ /** Render the exact owner `SessionDeliveryView` without a shadow projection. */
65
+ export function createSessionDeliveryView(React) {
66
+ if (typeof React?.createElement !== "function" || typeof React?.useSyncExternalStore !== "function") {
67
+ throw new TypeError("DELIVERY_VIEW_REACT_INVALID");
68
+ }
69
+ return function SessionDeliveryView({ sessionId, source }) {
70
+ const state = React.useSyncExternalStore(
71
+ (notify) => safeSubscribe(source, notify),
72
+ () => safeSnapshot(source),
73
+ () => safeSnapshot(source),
74
+ );
75
+ if (state.kind === "loading") return statePanel(React, "status", undefined, "Loading Delivery…");
76
+ if (state.kind === "error") return statePanel(React, "alert", state.code ?? "DELIVERY_PROJECTION_UNAVAILABLE", state.message ?? "Execution projection unavailable");
77
+ const view = state.view;
78
+ if (state.kind !== "ready" || view?.sessionCorrelation !== sessionId) {
79
+ return statePanel(React, "alert", "DELIVERY_PROJECTION_CORRUPT", "Delivery projection invalid");
80
+ }
81
+ if (view.kind === "UNBOUND") return statePanel(React, "status", undefined, "No Delivery bound to this Session");
82
+ if (view.kind !== "BOUND" || !validDelivery(view.delivery, sessionId)) {
83
+ return statePanel(React, "alert", "DELIVERY_PROJECTION_CORRUPT", "Delivery projection invalid");
84
+ }
85
+ const delivery = view.delivery;
86
+ const failed = delivery.terminal?.outcome === "FAILED" || delivery.error !== null;
87
+ const identityRows = [
88
+ ...pair(React, "Delivery", delivery.deliveryId),
89
+ ...pair(React, "Task", delivery.task.displayName ?? delivery.task.identity),
90
+ ...pair(React, "Workflow", delivery.workflow.identity),
91
+ ...pair(React, "Package", `${delivery.workflow.packageName}@${delivery.workflow.exactPackageVersion}`),
92
+ ...pair(React, "Package digest", delivery.workflow.packageDigest),
93
+ ...pair(React, "Snapshot", delivery.workflow.snapshotIdentity),
94
+ ...pair(React, "Snapshot digest", delivery.workflow.snapshotDigest),
95
+ ...pair(React, "Binding", delivery.deliveryBindingIdentity),
96
+ ];
97
+ const lifecycleRows = [
98
+ ...pair(React, "Lifecycle", delivery.lifecycle),
99
+ ...pair(React, "Recoverable", delivery.recoverable ? "yes" : "no"),
100
+ ...(delivery.current === null ? [] : pair(React, delivery.current.kind === "ACTION" ? "Current Action" : "Current Intervention", delivery.current.identity)),
101
+ ...pair(React, "Started", new Date(delivery.timing.startedAt).toISOString()),
102
+ ...pair(React, "Elapsed", duration(delivery.timing.elapsedMs)),
103
+ ...(delivery.terminal === null ? [] : [
104
+ ...pair(React, "Ended", new Date(delivery.terminal.finishedAt).toISOString()),
105
+ ...pair(React, "Outcome", delivery.terminal.outcome),
106
+ ]),
107
+ ...(delivery.error === null ? [] : pair(React, "Error", delivery.error.code)),
108
+ ];
109
+ return React.createElement("section", {
110
+ "aria-labelledby": "wsr-delivery-view-title", "aria-live": failed ? "assertive" : "polite",
111
+ "data-wsr-delivery-id": delivery.deliveryId, "data-wsr-delivery-view": "true", role: failed ? "alert" : "region",
112
+ }, React.createElement("h2", { id: "wsr-delivery-view-title" }, "Delivery"),
113
+ React.createElement("dl", { "aria-label": "Delivery identity" }, identityRows),
114
+ React.createElement("dl", { "aria-label": "Delivery lifecycle" }, lifecycleRows));
115
+ };
116
+ }
117
+
118
+ export function registerSessionDeliveryView(ctx, options) {
119
+ if (typeof ctx?.slots?.inject !== "function" || typeof ctx?.slots?.register !== "function"
120
+ || typeof options?.bindProjection !== "function") throw new TypeError("DELIVERY_VIEW_REGISTRATION_INVALID");
121
+ const View = createSessionDeliveryView(options.React);
122
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
123
+ name: "conversation.view", id: DELIVERY_VIEW_ID, order: DELIVERY_VIEW_ORDER, label: "Delivery",
124
+ inject: (sessionId) => ({ source: options.bindProjection(sessionId) }),
125
+ }, View));
126
+ }