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/LICENSE +201 -0
- package/NOTICE.md +12 -0
- package/README.md +24 -3
- package/cordis.patch.yml +10 -0
- package/lib/client.js +3211 -0
- package/package.json +68 -15
- package/skills/workflow-execution/SKILL.md +17 -0
- package/src/action-presentation/model.js +236 -0
- package/src/action-presentation/view.js +98 -0
- package/src/client/browser-entry.js +32 -0
- package/src/client/delivery/control-plane-port.js +64 -0
- package/src/client/delivery/session-delivery-view.js +126 -0
- package/src/client/delivery-inventory/LICENSE.upstream +21 -0
- package/src/client/delivery-inventory/UPSTREAM.md +20 -0
- package/src/client/delivery-inventory/model.js +116 -0
- package/src/client/delivery-inventory/sidebar.js +85 -0
- package/src/host/delivery-control-plane.js +132 -0
- package/src/index.js +4 -0
- package/src/intake/binding-repository.js +171 -0
- package/src/intake/command.js +44 -0
- package/src/intake/plugin.js +538 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Fixed Workspace UI fork provenance
|
|
2
|
+
|
|
3
|
+
This feature is a composition fork of
|
|
4
|
+
`@deepseek-ai/dsh-client-ui-workspace@0.1.1-rc.2`. It invokes that exact
|
|
5
|
+
browser module while intercepting its single `sidebar.workspaces` registration
|
|
6
|
+
so the WSR shell owns the slot and renders the unmodified `WorkspaceBrowser`
|
|
7
|
+
component as a React child under Workspace. It performs no DOM reparenting.
|
|
8
|
+
|
|
9
|
+
- Upstream repository: `https://github.com/deepseek-ai/deepseek-harness.git`
|
|
10
|
+
- Upstream source directory: `packages/client/ui-workspace`
|
|
11
|
+
- npm package: `@deepseek-ai/dsh-client-ui-workspace@0.1.1-rc.2`
|
|
12
|
+
- npm tarball integrity: `sha512-k/jB5ke2e+oNyNKzu4/PBlriwCHKVg5bY3kn7Co3MtWZdqbJ42hfwZkRNMnn+nmziQXCVXWRzuiHZt0xNTAveA==`
|
|
13
|
+
- Installed upstream `lib/client.js` SHA-256: `75d8a09a43a820e0ff8470e7b9c87b6dced523764ee650a8382317f6ef7a314b`
|
|
14
|
+
- Installed upstream `package.json` SHA-256: `d8f1788dbd53719690bec6f236602a02aa1d213494e241c2aa8d645e4bf05706`
|
|
15
|
+
- Installed upstream `LICENSE` SHA-256: `ebb4f09972aee8608be255debaf78451a68e95c290f55c240dec2ecfa16ea6be`
|
|
16
|
+
- Upstream license: MIT; exact text is retained in `LICENSE.upstream`.
|
|
17
|
+
|
|
18
|
+
The WSR composition and Delivery code are Apache-2.0. The upstream Workspace
|
|
19
|
+
UI implementation remains MIT-licensed and is not copied into this directory.
|
|
20
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const CONTROL_PLANE_SCHEMA = "execution.delivery-control-plane@1.0.0";
|
|
2
|
+
const LIFECYCLES = new Set([
|
|
3
|
+
"BOUND", "START_UNCERTAIN", "RUNNING_CORRELATED", "START_FAILED",
|
|
4
|
+
"RESULT_UNRESOLVED", "TERMINAL_HANDLING", "TERMINAL",
|
|
5
|
+
]);
|
|
6
|
+
const WORKSPACE_KEY = "wsr.sidebar.workspace.expanded.v1";
|
|
7
|
+
const DELIVERY_KEY = "wsr.sidebar.delivery.expanded.v1";
|
|
8
|
+
|
|
9
|
+
function errorView(label = "Delivery inventory unavailable") {
|
|
10
|
+
return Object.freeze({ kind: "error", role: "alert", label, rows: Object.freeze([]) });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function validString(value) {
|
|
14
|
+
return typeof value === "string" && value.length > 0 && value.length <= 512;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function lifecycleLabel(delivery) {
|
|
18
|
+
if (delivery.lifecycle === "TERMINAL") return delivery.terminal?.outcome ?? "Terminal";
|
|
19
|
+
return delivery.lifecycle.toLowerCase().replaceAll("_", " ").replace(/^./u, (value) => value.toUpperCase());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function rowsFrom(deliveries, selectedSessionId) {
|
|
23
|
+
if (!Array.isArray(deliveries)) return undefined;
|
|
24
|
+
const identities = new Set();
|
|
25
|
+
const rows = [];
|
|
26
|
+
for (const delivery of deliveries) {
|
|
27
|
+
const sessionCorrelation = delivery?.navigation?.sessionCorrelation ?? null;
|
|
28
|
+
if (delivery === null || typeof delivery !== "object" || Array.isArray(delivery)
|
|
29
|
+
|| !validString(delivery.deliveryId) || identities.has(delivery.deliveryId)
|
|
30
|
+
|| !LIFECYCLES.has(delivery.lifecycle)
|
|
31
|
+
|| typeof delivery.detached !== "boolean" || typeof delivery.recoverable !== "boolean"
|
|
32
|
+
|| !validString(delivery.task?.identity)
|
|
33
|
+
|| !(delivery.task.displayName === null || validString(delivery.task.displayName))
|
|
34
|
+
|| !(sessionCorrelation === null || validString(sessionCorrelation))) return undefined;
|
|
35
|
+
identities.add(delivery.deliveryId);
|
|
36
|
+
rows.push(Object.freeze({
|
|
37
|
+
deliveryId: delivery.deliveryId,
|
|
38
|
+
label: delivery.task.displayName ?? delivery.task.identity,
|
|
39
|
+
statusLabel: lifecycleLabel(delivery),
|
|
40
|
+
sessionId: sessionCorrelation,
|
|
41
|
+
availability: sessionCorrelation !== null ? "bound" : delivery.recoverable ? "recoverable" : "detached",
|
|
42
|
+
selected: sessionCorrelation !== null && sessionCorrelation === selectedSessionId,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
rows.sort((left, right) => left.deliveryId.localeCompare(right.deliveryId));
|
|
46
|
+
return Object.freeze(rows);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Project only the formal Execution DeliveryControlPlaneSnapshot. */
|
|
50
|
+
export function projectDeliveryInventory(state, { selectedSessionId } = {}) {
|
|
51
|
+
if (state?.kind === "loading") return Object.freeze({ kind: "loading", role: "status", label: "Loading Deliveries", rows: Object.freeze([]) });
|
|
52
|
+
if (state?.kind === "error") return errorView(validString(state.message) ? state.message : undefined);
|
|
53
|
+
if (!new Set(["ready", "reconnecting"]).has(state?.kind)) return errorView();
|
|
54
|
+
const snapshot = state.snapshot;
|
|
55
|
+
if (snapshot === null || typeof snapshot !== "object" || Array.isArray(snapshot)
|
|
56
|
+
|| snapshot.schemaVersion !== CONTROL_PLANE_SCHEMA
|
|
57
|
+
|| !Number.isSafeInteger(snapshot.generation) || snapshot.generation < 1) return errorView();
|
|
58
|
+
const rows = rowsFrom(snapshot.deliveries, selectedSessionId);
|
|
59
|
+
if (rows === undefined) return errorView();
|
|
60
|
+
if (state.kind === "reconnecting") return Object.freeze({ kind: "reconnecting", role: "status", label: "Reconnecting to Delivery inventory", rows });
|
|
61
|
+
if (rows.length === 0) return Object.freeze({ kind: "empty", role: "status", label: "No Deliveries", rows });
|
|
62
|
+
return Object.freeze({ kind: "ready", role: "list", label: "Deliveries", rows });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createDeliveryInventoryController(inventory) {
|
|
66
|
+
if (inventory === null || typeof inventory !== "object"
|
|
67
|
+
|| typeof inventory.getSnapshot !== "function" || typeof inventory.subscribe !== "function") {
|
|
68
|
+
throw new TypeError("Execution inventory projection must be read-only and subscribable");
|
|
69
|
+
}
|
|
70
|
+
const controller = {
|
|
71
|
+
getSnapshot() {
|
|
72
|
+
try { return projectDeliveryInventory(inventory.getSnapshot()); }
|
|
73
|
+
catch { return errorView(); }
|
|
74
|
+
},
|
|
75
|
+
subscribe(listener) {
|
|
76
|
+
if (typeof listener !== "function") throw new TypeError("inventory listener must be a function");
|
|
77
|
+
const unsubscribe = inventory.subscribe(() => listener(controller.getSnapshot()));
|
|
78
|
+
if (typeof unsubscribe !== "function") throw new TypeError("inventory subscription must return an unsubscribe function");
|
|
79
|
+
return unsubscribe;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
return Object.freeze(controller);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function persistedBoolean(value) {
|
|
86
|
+
return value === "false" ? false : true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createMemoryCollapseStore(storage = {}) {
|
|
90
|
+
const listeners = new Set();
|
|
91
|
+
const read = typeof storage.read === "function" ? storage.read : () => undefined;
|
|
92
|
+
const write = typeof storage.write === "function" ? storage.write : () => undefined;
|
|
93
|
+
let snapshot;
|
|
94
|
+
try {
|
|
95
|
+
snapshot = Object.freeze({
|
|
96
|
+
workspaceExpanded: persistedBoolean(read(WORKSPACE_KEY)),
|
|
97
|
+
deliveryExpanded: persistedBoolean(read(DELIVERY_KEY)),
|
|
98
|
+
});
|
|
99
|
+
} catch {
|
|
100
|
+
snapshot = Object.freeze({ workspaceExpanded: true, deliveryExpanded: true });
|
|
101
|
+
}
|
|
102
|
+
const update = (name, key, value) => {
|
|
103
|
+
const next = Boolean(value);
|
|
104
|
+
if (snapshot[name] === next) return;
|
|
105
|
+
snapshot = Object.freeze({ ...snapshot, [name]: next });
|
|
106
|
+
try { write(key, String(next)); } catch { /* persistence denial is non-fatal */ }
|
|
107
|
+
for (const listener of listeners) listener();
|
|
108
|
+
};
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
getSnapshot: () => snapshot,
|
|
111
|
+
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
112
|
+
setWorkspaceExpanded(value) { update("workspaceExpanded", WORKSPACE_KEY, value); },
|
|
113
|
+
setDeliveryExpanded(value) { update("deliveryExpanded", DELIVERY_KEY, value); },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { projectDeliveryInventory } from "./model.js";
|
|
2
|
+
|
|
3
|
+
const STYLE_ID = "dsh-wsr-execution-delivery-inventory";
|
|
4
|
+
const CSS = ".wsr-sidebar-resources{min-height:0;flex:1;display:flex;flex-direction:column;gap:4px}.wsr-sidebar-resource{min-height:0;display:flex;flex-direction:column}.wsr-sidebar-resource:first-child{flex:1}.wsr-sidebar-resource-header{box-sizing:border-box;width:100%;height:36px;cursor:pointer;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;display:flex;align-items:center;gap:6px;padding:0 8px;font-size:13px;text-align:left}.wsr-sidebar-resource-header:hover{background:var(--dsw-alias-interactive-bg-hover)}.wsr-delivery-row{box-sizing:border-box;width:100%;height:32px;cursor:pointer;color:var(--dsw-alias-label-primary);background:transparent;border:0;border-radius:8px;display:flex;align-items:center;gap:6px;padding:0 8px;font-size:14px;line-height:20px;text-align:left}.wsr-delivery-row:hover,.wsr-delivery-row[aria-current=page]{background:var(--dsw-alias-interactive-bg-hover)}.wsr-delivery-row:disabled{cursor:default}.wsr-delivery-row>span:first-child{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden;flex:1}.wsr-delivery-status{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}.wsr-delivery-status-recoverable{color:var(--dsw-alias-state-warning-primary)}";
|
|
5
|
+
|
|
6
|
+
function installStyle() {
|
|
7
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID) !== null) return;
|
|
8
|
+
const tag = document.createElement("style");
|
|
9
|
+
tag.id = STYLE_ID;
|
|
10
|
+
tag.dataset.plugin = "dsh-wsr-execution";
|
|
11
|
+
tag.textContent = CSS;
|
|
12
|
+
document.head.append(tag);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function persisted(key) {
|
|
16
|
+
try { return typeof localStorage === "undefined" || localStorage.getItem(key) !== "false"; }
|
|
17
|
+
catch { return true; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function persist(key, value) {
|
|
21
|
+
try { if (typeof localStorage !== "undefined") localStorage.setItem(key, String(value)); }
|
|
22
|
+
catch { /* browser storage is optional */ }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createSidebarResources(React, WorkspaceBrowser, inventory) {
|
|
26
|
+
return function WsrSidebarResources(props) {
|
|
27
|
+
const [workspaceExpanded, setWorkspaceExpanded] = React.useState(() => persisted("wsr.sidebar.workspace.expanded.v1"));
|
|
28
|
+
const [deliveryExpanded, setDeliveryExpanded] = React.useState(() => persisted("wsr.sidebar.delivery.expanded.v1"));
|
|
29
|
+
const selectedSessionId = props.useSessions((state) => state.current);
|
|
30
|
+
const state = React.useSyncExternalStore(inventory.subscribe, inventory.getSnapshot, inventory.getSnapshot);
|
|
31
|
+
const view = React.useMemo(() => projectDeliveryInventory(state, { selectedSessionId }), [state, selectedSessionId]);
|
|
32
|
+
const toggle = (kind) => {
|
|
33
|
+
if (kind === "workspace") {
|
|
34
|
+
const next = !workspaceExpanded;
|
|
35
|
+
setWorkspaceExpanded(next);
|
|
36
|
+
persist("wsr.sidebar.workspace.expanded.v1", next);
|
|
37
|
+
} else {
|
|
38
|
+
const next = !deliveryExpanded;
|
|
39
|
+
setDeliveryExpanded(next);
|
|
40
|
+
persist("wsr.sidebar.delivery.expanded.v1", next);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const header = (id, label, expanded, kind) => React.createElement("button", {
|
|
44
|
+
type: "button", className: "wsr-sidebar-resource-header",
|
|
45
|
+
"aria-controls": id, "aria-expanded": expanded, onClick: () => toggle(kind),
|
|
46
|
+
onKeyDown: (event) => {
|
|
47
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
toggle(kind);
|
|
50
|
+
},
|
|
51
|
+
}, React.createElement("span", { "aria-hidden": "true" }, expanded ? "▾" : "▸"), label);
|
|
52
|
+
return React.createElement("div", { className: "wsr-sidebar-resources", "data-wsr-sidebar-resources": "true" },
|
|
53
|
+
React.createElement("section", { className: "wsr-sidebar-resource", "aria-label": "Workspace" },
|
|
54
|
+
header("wsr-sidebar-workspace", "Workspace", workspaceExpanded, "workspace"),
|
|
55
|
+
workspaceExpanded && React.createElement("div", { id: "wsr-sidebar-workspace" }, React.createElement(WorkspaceBrowser, props))),
|
|
56
|
+
React.createElement("section", { className: "wsr-sidebar-resource", "aria-label": "Delivery" },
|
|
57
|
+
header("wsr-sidebar-delivery", "Delivery", deliveryExpanded, "delivery"),
|
|
58
|
+
deliveryExpanded && React.createElement("div", {
|
|
59
|
+
id: "wsr-sidebar-delivery", role: view.kind === "error" ? "alert" : "region", "aria-live": "polite",
|
|
60
|
+
}, view.kind === "ready"
|
|
61
|
+
? React.createElement("div", { role: "list", "aria-label": "Deliveries" }, view.rows.map((row) => React.createElement("button", {
|
|
62
|
+
key: row.deliveryId, type: "button", role: "listitem", className: "wsr-delivery-row",
|
|
63
|
+
"aria-current": row.selected ? "page" : undefined,
|
|
64
|
+
"aria-label": `${row.label}, ${row.statusLabel}`,
|
|
65
|
+
disabled: row.sessionId === null,
|
|
66
|
+
onClick: row.sessionId === null ? undefined : () => props.open(row.sessionId),
|
|
67
|
+
}, React.createElement("span", null, row.label), React.createElement("span", {
|
|
68
|
+
className: `wsr-delivery-status wsr-delivery-status-${row.availability}`,
|
|
69
|
+
}, row.statusLabel))))
|
|
70
|
+
: React.createElement("div", { role: view.role }, view.label))));
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fixed-version Workspace UI composition fork; WSR owns the single slot. */
|
|
75
|
+
export function applyDeliverySidebar(ctx, { React, workspaceUi, inventory }) {
|
|
76
|
+
installStyle();
|
|
77
|
+
const originalSlots = ctx.slots;
|
|
78
|
+
const slots = Object.create(originalSlots);
|
|
79
|
+
slots.register = (definition, component) => definition?.name === "sidebar.workspaces"
|
|
80
|
+
? originalSlots.register(definition, createSidebarResources(React, component, inventory))
|
|
81
|
+
: originalSlots.register(definition, component);
|
|
82
|
+
slots.inject = (name, factory) => originalSlots.inject(name, factory);
|
|
83
|
+
const forked = new Proxy(ctx, { get(target, property) { return property === "slots" ? slots : Reflect.get(target, property); } });
|
|
84
|
+
return workspaceUi.apply(forked);
|
|
85
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
]);
|
|
8
|
+
|
|
9
|
+
function assertReadModel(readModel) {
|
|
10
|
+
if (readModel === null || typeof readModel !== "object"
|
|
11
|
+
|| typeof readModel.snapshot !== "function"
|
|
12
|
+
|| typeof readModel.session !== "function"
|
|
13
|
+
|| typeof readModel.subscribe !== "function") {
|
|
14
|
+
throw new TypeError("DELIVERY_CONTROL_PLANE_READ_MODEL_INVALID");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function errorResult(error) {
|
|
19
|
+
const code = typeof error?.code === "string" && ERROR_CODES.has(error.code)
|
|
20
|
+
? error.code
|
|
21
|
+
: "DELIVERY_PROJECTION_UNAVAILABLE";
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
ok: false,
|
|
24
|
+
error: Object.freeze({ code, message: "Delivery control plane unavailable" }),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validSessionPayload(payload) {
|
|
29
|
+
return payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
30
|
+
&& Object.keys(payload).join(",") === "sessionCorrelation"
|
|
31
|
+
&& typeof payload.sessionCorrelation === "string"
|
|
32
|
+
&& payload.sessionCorrelation.length > 0
|
|
33
|
+
&& payload.sessionCorrelation.length <= 512;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Bridge the Execution-owned async read model into a bounded DSH Host channel.
|
|
38
|
+
* The gateway retains owner snapshots verbatim and has no mutation operation.
|
|
39
|
+
*/
|
|
40
|
+
export async function createDeliveryControlPlaneGateway(readModel) {
|
|
41
|
+
assertReadModel(readModel);
|
|
42
|
+
let current;
|
|
43
|
+
let failure;
|
|
44
|
+
let closed = false;
|
|
45
|
+
const dispose = await readModel.subscribe(
|
|
46
|
+
(snapshot) => { if (!closed) { current = snapshot; failure = undefined; } },
|
|
47
|
+
(error) => { if (!closed) failure = errorResult(error); },
|
|
48
|
+
);
|
|
49
|
+
if (typeof dispose !== "function") throw new TypeError("DELIVERY_CONTROL_PLANE_SUBSCRIPTION_INVALID");
|
|
50
|
+
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
async handle(endpoint, payload) {
|
|
53
|
+
if (closed) throw new TypeError("DELIVERY_CONTROL_PLANE_GATEWAY_CLOSED");
|
|
54
|
+
if (endpoint === "inventory/read") {
|
|
55
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload) || Object.keys(payload).length !== 0) {
|
|
56
|
+
throw new TypeError("CONTROL_PLANE_RPC_INVALID");
|
|
57
|
+
}
|
|
58
|
+
if (failure !== undefined) return failure;
|
|
59
|
+
if (current === undefined) {
|
|
60
|
+
try { current = await readModel.snapshot(); }
|
|
61
|
+
catch (error) { return errorResult(error); }
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze({ ok: true, value: current });
|
|
64
|
+
}
|
|
65
|
+
if (endpoint === "session/read") {
|
|
66
|
+
if (!validSessionPayload(payload)) throw new TypeError("CONTROL_PLANE_RPC_INVALID");
|
|
67
|
+
try { return Object.freeze({ ok: true, value: await readModel.session(payload.sessionCorrelation) }); }
|
|
68
|
+
catch (error) { return errorResult(error); }
|
|
69
|
+
}
|
|
70
|
+
throw new TypeError("CONTROL_PLANE_RPC_INVALID");
|
|
71
|
+
},
|
|
72
|
+
async close() {
|
|
73
|
+
if (closed) return;
|
|
74
|
+
closed = true;
|
|
75
|
+
await dispose();
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Map the owner correlation coordinate to the DSH Session id at the Host edge. */
|
|
81
|
+
export function createDshSessionControlPlaneReadModel(readModel, bindings) {
|
|
82
|
+
assertReadModel(readModel);
|
|
83
|
+
if (typeof bindings?.list !== "function" || typeof bindings?.bySession !== "function") {
|
|
84
|
+
throw new TypeError("DSH_SESSION_BINDINGS_REQUIRED");
|
|
85
|
+
}
|
|
86
|
+
const mapSnapshot = async (snapshot) => {
|
|
87
|
+
const rows = await bindings.list();
|
|
88
|
+
const sessions = new Map(rows.map((binding) => [binding.correlation, binding.sessionKey]));
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
...snapshot,
|
|
91
|
+
deliveries: Object.freeze(snapshot.deliveries.map((delivery) => {
|
|
92
|
+
if (delivery.navigation === null) return delivery;
|
|
93
|
+
const sessionCorrelation = sessions.get(delivery.navigation.sessionCorrelation);
|
|
94
|
+
if (sessionCorrelation === undefined) throw Object.assign(new Error("DELIVERY_PROJECTION_STALE_BINDING"), { code: "DELIVERY_PROJECTION_STALE_BINDING" });
|
|
95
|
+
return Object.freeze({ ...delivery, navigation: Object.freeze({ sessionCorrelation }) });
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
async snapshot() { return mapSnapshot(await readModel.snapshot()); },
|
|
101
|
+
async session(sessionCorrelation) {
|
|
102
|
+
const binding = await bindings.bySession(sessionCorrelation);
|
|
103
|
+
if (binding === undefined) return Object.freeze({ kind: "UNBOUND", sessionCorrelation });
|
|
104
|
+
const view = await readModel.session(binding.correlation);
|
|
105
|
+
if (view.kind === "UNBOUND") return Object.freeze({ kind: "UNBOUND", sessionCorrelation });
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
kind: "BOUND",
|
|
108
|
+
sessionCorrelation,
|
|
109
|
+
delivery: Object.freeze({ ...view.delivery, navigation: Object.freeze({ sessionCorrelation }) }),
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
async subscribe(listener, onError) {
|
|
113
|
+
return readModel.subscribe(
|
|
114
|
+
(snapshot) => { void mapSnapshot(snapshot).then(listener, onError); },
|
|
115
|
+
onError,
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function registerDeliveryControlPlaneGateway(ctx, readModel) {
|
|
122
|
+
if (typeof ctx?.connection?.rpc?.handle !== "function") throw new TypeError("DSH_CONNECTION_RPC_REQUIRED");
|
|
123
|
+
const gateway = await createDeliveryControlPlaneGateway(readModel);
|
|
124
|
+
const unregister = ctx.connection.rpc.handle(CHANNEL, gateway.handle, { authority: "loopback" });
|
|
125
|
+
ctx.effect(function* deliveryControlPlaneGatewayLifecycle() {
|
|
126
|
+
yield async () => {
|
|
127
|
+
await unregister?.();
|
|
128
|
+
await gateway.close();
|
|
129
|
+
};
|
|
130
|
+
}, "wsr-execution: Delivery control plane read gateway");
|
|
131
|
+
return gateway;
|
|
132
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
export class IntakeBindingError extends Error {
|
|
6
|
+
constructor(code) {
|
|
7
|
+
super(code);
|
|
8
|
+
this.name = "IntakeBindingError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function exact(value) {
|
|
14
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)
|
|
15
|
+
|| Object.keys(value).sort().join(",") !== "correlation,deliveryBindingIdentity,deliveryId,sessionKey,state,worktree"
|
|
16
|
+
|| typeof value.sessionKey !== "string" || value.sessionKey.length === 0
|
|
17
|
+
|| typeof value.correlation !== "string" || value.correlation.length === 0
|
|
18
|
+
|| typeof value.deliveryId !== "string" || value.deliveryId.length === 0
|
|
19
|
+
|| typeof value.deliveryBindingIdentity !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(value.deliveryBindingIdentity)
|
|
20
|
+
|| typeof value.worktree !== "string" || !isAbsolute(value.worktree)
|
|
21
|
+
|| !["BOUND", "DETACHED"].includes(value.state)) {
|
|
22
|
+
throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
23
|
+
}
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
sessionKey: value.sessionKey,
|
|
26
|
+
correlation: value.correlation,
|
|
27
|
+
deliveryId: value.deliveryId,
|
|
28
|
+
deliveryBindingIdentity: value.deliveryBindingIdentity,
|
|
29
|
+
worktree: resolve(value.worktree),
|
|
30
|
+
state: value.state,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function legacy(value) {
|
|
35
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)
|
|
36
|
+
|| Object.keys(value).sort().join(",") !== "correlation,deliveryId,sessionKey,state,worktree"
|
|
37
|
+
|| typeof value.sessionKey !== "string" || value.sessionKey.length === 0
|
|
38
|
+
|| typeof value.correlation !== "string" || value.correlation.length === 0
|
|
39
|
+
|| typeof value.deliveryId !== "string" || value.deliveryId.length === 0
|
|
40
|
+
|| typeof value.worktree !== "string" || !isAbsolute(value.worktree)
|
|
41
|
+
|| !["BOUND", "DETACHED"].includes(value.state)) {
|
|
42
|
+
throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze({ ...value, worktree: resolve(value.worktree) });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validate(bindings) {
|
|
48
|
+
const accepted = bindings.map(exact);
|
|
49
|
+
if (new Set(accepted.map((entry) => entry.sessionKey)).size !== accepted.length
|
|
50
|
+
|| new Set(accepted.map((entry) => entry.correlation)).size !== accepted.length
|
|
51
|
+
|| new Set(accepted.map((entry) => entry.deliveryId)).size !== accepted.length
|
|
52
|
+
|| new Set(accepted.map((entry) => entry.deliveryBindingIdentity)).size !== accepted.length
|
|
53
|
+
|| new Set(accepted.map((entry) => entry.worktree)).size !== accepted.length) {
|
|
54
|
+
throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
55
|
+
}
|
|
56
|
+
return accepted;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class IntakeSessionBindingRepository {
|
|
60
|
+
#bindings = [];
|
|
61
|
+
#started = false;
|
|
62
|
+
constructor(file) {
|
|
63
|
+
if (typeof file !== "string" || !isAbsolute(file)) throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
64
|
+
this.file = resolve(file);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async start(inventory = []) {
|
|
68
|
+
if (this.#started) return;
|
|
69
|
+
try {
|
|
70
|
+
const document = JSON.parse(await readFile(this.file, "utf8"));
|
|
71
|
+
if (document === null || typeof document !== "object" || Array.isArray(document)
|
|
72
|
+
|| Object.keys(document).sort().join(",") !== "bindings,schemaVersion" || !Array.isArray(document.bindings)
|
|
73
|
+
|| !["execution.intake-bindings@1.0.0", "execution.intake-bindings@2.0.0"].includes(document.schemaVersion)) {
|
|
74
|
+
throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
75
|
+
}
|
|
76
|
+
if (document.schemaVersion === "execution.intake-bindings@2.0.0") {
|
|
77
|
+
this.#bindings = validate(document.bindings);
|
|
78
|
+
} else {
|
|
79
|
+
if (!Array.isArray(inventory)) throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
80
|
+
this.#bindings = validate(document.bindings.map((candidate) => {
|
|
81
|
+
const entry = legacy(candidate);
|
|
82
|
+
const matches = inventory.filter((item) => item !== null && typeof item === "object"
|
|
83
|
+
&& item.deliveryId === entry.deliveryId && typeof item.worktree === "string" && isAbsolute(item.worktree)
|
|
84
|
+
&& resolve(item.worktree) === entry.worktree
|
|
85
|
+
&& typeof item.deliveryBindingIdentity === "string" && /^sha256:[0-9a-f]{64}$/u.test(item.deliveryBindingIdentity));
|
|
86
|
+
if (matches.length !== 1) throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
87
|
+
return { ...entry, deliveryBindingIdentity: matches[0].deliveryBindingIdentity };
|
|
88
|
+
}));
|
|
89
|
+
await this.#persist();
|
|
90
|
+
}
|
|
91
|
+
} catch (cause) {
|
|
92
|
+
if (cause?.code !== "ENOENT") {
|
|
93
|
+
if (cause instanceof IntakeBindingError) throw cause;
|
|
94
|
+
throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
95
|
+
}
|
|
96
|
+
this.#bindings = [];
|
|
97
|
+
}
|
|
98
|
+
this.#started = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async claim(input) {
|
|
102
|
+
this.#ready();
|
|
103
|
+
const candidate = exact({ ...input, state: "BOUND" });
|
|
104
|
+
const session = this.#bindings.find((entry) => entry.sessionKey === candidate.sessionKey);
|
|
105
|
+
if (session !== undefined) {
|
|
106
|
+
if (session.deliveryId === candidate.deliveryId && session.correlation === candidate.correlation
|
|
107
|
+
&& session.worktree === candidate.worktree && session.deliveryBindingIdentity === candidate.deliveryBindingIdentity) return session;
|
|
108
|
+
if (session.state === "DETACHED" && session.deliveryId === candidate.deliveryId
|
|
109
|
+
&& session.worktree === candidate.worktree && session.deliveryBindingIdentity === candidate.deliveryBindingIdentity) {
|
|
110
|
+
this.#bindings.splice(this.#bindings.indexOf(session), 1, candidate);
|
|
111
|
+
await this.#persist();
|
|
112
|
+
return candidate;
|
|
113
|
+
}
|
|
114
|
+
throw new IntakeBindingError("SESSION_INTAKE_BOUND");
|
|
115
|
+
}
|
|
116
|
+
const delivery = this.#bindings.find((entry) => entry.deliveryId === candidate.deliveryId);
|
|
117
|
+
if (delivery?.state === "BOUND") throw new IntakeBindingError("DELIVERY_INTAKE_BOUND");
|
|
118
|
+
if (delivery?.state === "DETACHED") {
|
|
119
|
+
if (delivery.worktree !== candidate.worktree || delivery.deliveryBindingIdentity !== candidate.deliveryBindingIdentity) {
|
|
120
|
+
throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
121
|
+
}
|
|
122
|
+
this.#bindings.splice(this.#bindings.indexOf(delivery), 1, candidate);
|
|
123
|
+
}
|
|
124
|
+
else this.#bindings.push(candidate);
|
|
125
|
+
await this.#persist();
|
|
126
|
+
return candidate;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async bySession(sessionKey) {
|
|
130
|
+
this.#ready();
|
|
131
|
+
return this.#bindings.find((entry) => entry.sessionKey === sessionKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async byDelivery(deliveryId) {
|
|
135
|
+
this.#ready();
|
|
136
|
+
return this.#bindings.find((entry) => entry.deliveryId === deliveryId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async list() {
|
|
140
|
+
this.#ready();
|
|
141
|
+
return Object.freeze([...this.#bindings]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async markDetached(deliveryId) {
|
|
145
|
+
this.#ready();
|
|
146
|
+
const index = this.#bindings.findIndex((entry) => entry.deliveryId === deliveryId);
|
|
147
|
+
if (index === -1 || this.#bindings[index].state === "DETACHED") return;
|
|
148
|
+
this.#bindings.splice(index, 1, Object.freeze({ ...this.#bindings[index], state: "DETACHED" }));
|
|
149
|
+
await this.#persist();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async detach(deliveryId) {
|
|
153
|
+
this.#ready();
|
|
154
|
+
const index = this.#bindings.findIndex((entry) => entry.deliveryId === deliveryId);
|
|
155
|
+
if (index === -1) return;
|
|
156
|
+
this.#bindings.splice(index, 1);
|
|
157
|
+
await this.#persist();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#ready() {
|
|
161
|
+
if (!this.#started) throw new IntakeBindingError("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async #persist() {
|
|
165
|
+
await mkdir(dirname(this.file), { recursive: true, mode: 0o700 });
|
|
166
|
+
const temporary = `${this.file}.${randomUUID()}.tmp`;
|
|
167
|
+
const document = { schemaVersion: "execution.intake-bindings@2.0.0", bindings: this.#bindings };
|
|
168
|
+
await writeFile(temporary, `${JSON.stringify(document)}\n`, { flag: "wx", mode: 0o600 });
|
|
169
|
+
await rename(temporary, this.file);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function invalid() {
|
|
2
|
+
throw new TypeError("WSR_COMMAND_INVALID");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function split(value) {
|
|
6
|
+
if (typeof value !== "string" || value.includes("\0")) invalid();
|
|
7
|
+
const normalized = value.startsWith(" ") ? value.slice(1) : value;
|
|
8
|
+
const lineEnd = normalized.indexOf("\n");
|
|
9
|
+
const line = (lineEnd === -1 ? normalized : normalized.slice(0, lineEnd)).trimEnd();
|
|
10
|
+
const remainder = lineEnd === -1 ? undefined : normalized.slice(lineEnd + 1);
|
|
11
|
+
return { line, remainder };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseWsrCommand(value) {
|
|
15
|
+
const { line, remainder } = split(value);
|
|
16
|
+
if (line === "list") {
|
|
17
|
+
if (remainder !== undefined) invalid();
|
|
18
|
+
return Object.freeze({ operation: "list" });
|
|
19
|
+
}
|
|
20
|
+
if (line === "recover") return Object.freeze({ operation: "recover" });
|
|
21
|
+
if (line.startsWith("recover ")) {
|
|
22
|
+
const deliveryId = line.slice(8);
|
|
23
|
+
if (deliveryId.length === 0 || deliveryId.includes(" ") || remainder !== undefined) invalid();
|
|
24
|
+
return Object.freeze({ operation: "recover", deliveryId });
|
|
25
|
+
}
|
|
26
|
+
if (line === "status") return Object.freeze({ operation: "status" });
|
|
27
|
+
if (line.startsWith("status ")) {
|
|
28
|
+
const deliveryId = line.slice(7);
|
|
29
|
+
if (deliveryId.length === 0 || deliveryId.includes(" ") || remainder !== undefined) invalid();
|
|
30
|
+
return Object.freeze({ operation: "status", deliveryId });
|
|
31
|
+
}
|
|
32
|
+
if (line === "action finish") return Object.freeze({ operation: "action-finish", ...(remainder === undefined ? {} : { remainder }) });
|
|
33
|
+
if (line.startsWith("abandon ")) {
|
|
34
|
+
const deliveryId = line.slice(8);
|
|
35
|
+
if (deliveryId.length === 0 || deliveryId.includes(" ") || remainder !== undefined) invalid();
|
|
36
|
+
return Object.freeze({ operation: "abandon", deliveryId });
|
|
37
|
+
}
|
|
38
|
+
if (line.startsWith("create ")) {
|
|
39
|
+
const selector = line.slice(7);
|
|
40
|
+
if (selector.length === 0 || selector.includes(" ") || selector.startsWith("-") || selector.includes("--intent")) invalid();
|
|
41
|
+
return Object.freeze({ operation: "create", selector, directive: `/wsr create ${selector}`, ...(remainder === undefined ? {} : { remainder }) });
|
|
42
|
+
}
|
|
43
|
+
return invalid();
|
|
44
|
+
}
|