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/README.md +1 -1
- package/lib/client.js +415 -112
- package/package.json +4 -4
- package/src/action-presentation/model.js +29 -12
- package/src/action-presentation/view.js +178 -20
- package/src/client/browser-entry.js +23 -4
- package/src/client/delivery/control-plane-port.js +11 -2
- package/src/client/delivery/session-delivery-view.js +148 -37
- package/src/client/delivery-inventory/model.js +17 -3
- package/src/host/delivery-control-plane.js +39 -12
- package/src/intake/binding-repository.js +118 -41
- package/src/intake/command.js +17 -1
- package/src/intake/plugin.js +140 -42
package/src/intake/command.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
const USAGE = "Usage: /wsr list | create <selector> | recover [delivery-id] | status [delivery-id] | action finish. Usage: /wsr abandon [delivery-id]";
|
|
2
|
+
|
|
1
3
|
function invalid() {
|
|
2
|
-
throw new TypeError("WSR_COMMAND_INVALID");
|
|
4
|
+
throw Object.assign(new TypeError(`WSR_COMMAND_INVALID. ${USAGE}`), { code: "WSR_COMMAND_INVALID" });
|
|
3
5
|
}
|
|
4
6
|
|
|
5
7
|
function split(value) {
|
|
@@ -30,6 +32,10 @@ export function parseWsrCommand(value) {
|
|
|
30
32
|
return Object.freeze({ operation: "status", deliveryId });
|
|
31
33
|
}
|
|
32
34
|
if (line === "action finish") return Object.freeze({ operation: "action-finish", ...(remainder === undefined ? {} : { remainder }) });
|
|
35
|
+
if (line === "abandon") {
|
|
36
|
+
if (remainder !== undefined) invalid();
|
|
37
|
+
return Object.freeze({ operation: "abandon" });
|
|
38
|
+
}
|
|
33
39
|
if (line.startsWith("abandon ")) {
|
|
34
40
|
const deliveryId = line.slice(8);
|
|
35
41
|
if (deliveryId.length === 0 || deliveryId.includes(" ") || remainder !== undefined) invalid();
|
|
@@ -42,3 +48,13 @@ export function parseWsrCommand(value) {
|
|
|
42
48
|
}
|
|
43
49
|
return invalid();
|
|
44
50
|
}
|
|
51
|
+
|
|
52
|
+
export function promptDiagnostic(operation, attachments = []) {
|
|
53
|
+
if (operation?.operation !== "create") return undefined;
|
|
54
|
+
const hasPrompt = typeof operation.remainder === "string" && operation.remainder.trim().length > 0;
|
|
55
|
+
if (hasPrompt || (Array.isArray(attachments) && attachments.length > 0)) return undefined;
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
code: "TASK_PROMPT_REQUIRED",
|
|
58
|
+
message: "Add a Task instruction after the Workflow selector or attach a file.",
|
|
59
|
+
});
|
|
60
|
+
}
|
package/src/intake/plugin.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { lstat, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
5
6
|
import { IntakeSessionBindingRepository } from "./binding-repository.js";
|
|
6
|
-
import { parseWsrCommand } from "./command.js";
|
|
7
|
+
import { parseWsrCommand, promptDiagnostic } from "./command.js";
|
|
7
8
|
import {
|
|
8
9
|
createDshSessionControlPlaneReadModel,
|
|
9
10
|
registerDeliveryControlPlaneGateway,
|
|
@@ -61,6 +62,72 @@ function error(code) {
|
|
|
61
62
|
return Object.freeze({ kind: "ERROR", code, message: code });
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function gitInitFailure(cause) {
|
|
66
|
+
return Object.assign(new Error("GIT_INIT_FAILED", { cause }), { code: "GIT_INIT_FAILED" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function defaultGitInit(workspace) {
|
|
70
|
+
await runGit(workspace, ["init", "--quiet"]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runGit(workspace, arguments_) {
|
|
74
|
+
return new Promise((accept, reject) => {
|
|
75
|
+
execFile("git", arguments_, { cwd: workspace }, (cause, stdout) => cause === null ? accept(stdout) : reject(cause));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function hasGitHead(workspace) {
|
|
80
|
+
try {
|
|
81
|
+
await runGit(workspace, ["rev-parse", "--verify", "HEAD"]);
|
|
82
|
+
return true;
|
|
83
|
+
} catch (cause) {
|
|
84
|
+
if (cause?.code === 128) return false;
|
|
85
|
+
throw cause;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function ensureGitHead(workspace) {
|
|
90
|
+
if (!await hasGitHead(workspace)) {
|
|
91
|
+
await runGit(workspace, ["add", "-A", "--", "."]);
|
|
92
|
+
await runGit(workspace, [
|
|
93
|
+
"-c", "user.name=WSR Workspace Initializer",
|
|
94
|
+
"-c", "user.email=wsr@localhost",
|
|
95
|
+
"-c", "commit.gpgSign=false",
|
|
96
|
+
"commit", "--quiet", "--allow-empty", "--no-verify", "-m", "Initialize WSR workspace",
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
await runGit(workspace, ["rev-parse", "--verify", "HEAD^{tree}"]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function hasGitMarker(workspace) {
|
|
103
|
+
try {
|
|
104
|
+
const marker = await lstat(path.join(workspace, ".git"));
|
|
105
|
+
return marker.isDirectory() || marker.isFile();
|
|
106
|
+
} catch (cause) {
|
|
107
|
+
if (cause?.code === "ENOENT") return false;
|
|
108
|
+
throw cause;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Establish the Git boundary only for an already authorized exact workspace. */
|
|
113
|
+
export async function ensureGitWorktree(workspace, initialize = defaultGitInit) {
|
|
114
|
+
try {
|
|
115
|
+
if (typeof workspace !== "string" || !path.isAbsolute(workspace) || typeof initialize !== "function") {
|
|
116
|
+
throw new TypeError("workspace must be an absolute path");
|
|
117
|
+
}
|
|
118
|
+
const canonical = await realpath(workspace);
|
|
119
|
+
if (canonical !== workspace || !(await stat(canonical)).isDirectory()) throw new TypeError("workspace is not canonical");
|
|
120
|
+
const initialized = !await hasGitMarker(canonical);
|
|
121
|
+
if (initialized) await initialize(canonical);
|
|
122
|
+
if (!await hasGitMarker(canonical)) throw new TypeError("git marker was not created");
|
|
123
|
+
await ensureGitHead(canonical);
|
|
124
|
+
return Object.freeze({ path: canonical, initialized });
|
|
125
|
+
} catch (cause) {
|
|
126
|
+
if (cause?.code === "GIT_INIT_FAILED") throw cause;
|
|
127
|
+
throw gitInitFailure(cause);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
64
131
|
function textOf(content) {
|
|
65
132
|
if (!Array.isArray(content)) return "";
|
|
66
133
|
return content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
|
|
@@ -143,7 +210,7 @@ export function presentToDshSession(agent, presentation, createId = () => `cmd-w
|
|
|
143
210
|
const commandId = createId();
|
|
144
211
|
agent.session.append("command/run", {
|
|
145
212
|
commandId,
|
|
146
|
-
name: "wsr",
|
|
213
|
+
name: "wsr-presentation",
|
|
147
214
|
source: { kind: "plugin", plugin: "workflow-execution" },
|
|
148
215
|
});
|
|
149
216
|
agent.session.append("command/done", { commandId, kind: presentation?.kind === "error" ? "error" : "success", text });
|
|
@@ -201,16 +268,36 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
201
268
|
const factory = options.factory ?? new api.DefaultExecutionApplicationFactory();
|
|
202
269
|
const application = await factory.create(admitted.configFile, dependencies);
|
|
203
270
|
const control = options.control ?? api.getExecutionApplicationControl(application);
|
|
271
|
+
const ownerProjection = options.ownerProjection ?? api.getExecutionControlPlaneProjection(application);
|
|
204
272
|
const bindingInventory = () => typeof control.bindingInventory === "function" ? control.bindingInventory() : control.list();
|
|
273
|
+
const archiveTerminal = async (sessionKey, correlation, deliveryId) => {
|
|
274
|
+
const snapshot = await ownerProjection.snapshot();
|
|
275
|
+
const matches = snapshot.deliveries.filter((delivery) => delivery.lifecycle === "TERMINAL"
|
|
276
|
+
&& delivery.navigation?.sessionCorrelation === correlation
|
|
277
|
+
&& (deliveryId === undefined || delivery.deliveryId === deliveryId));
|
|
278
|
+
if (matches.length !== 1) {
|
|
279
|
+
throw Object.assign(new Error("INTAKE_BINDING_INVARIANT_VIOLATION"), { code: "INTAKE_BINDING_INVARIANT_VIOLATION" });
|
|
280
|
+
}
|
|
281
|
+
await bindings.archiveTerminal(sessionKey, matches[0]);
|
|
282
|
+
};
|
|
205
283
|
try {
|
|
206
284
|
await application.start();
|
|
207
285
|
const inventory = await bindingInventory();
|
|
208
286
|
await bindings.start(inventory);
|
|
287
|
+
const snapshot = await ownerProjection.snapshot();
|
|
209
288
|
for (const binding of await bindings.list()) {
|
|
210
289
|
const sameDelivery = inventory.filter((item) => item.deliveryId === binding.deliveryId || item.worktree === binding.worktree);
|
|
211
290
|
const matches = sameDelivery.filter((item) => item.deliveryId === binding.deliveryId && item.worktree === binding.worktree
|
|
212
291
|
&& item.deliveryBindingIdentity === binding.deliveryBindingIdentity);
|
|
213
292
|
if (matches.length === 0 && sameDelivery.length === 0) {
|
|
293
|
+
const terminal = snapshot.deliveries.filter((delivery) => delivery.lifecycle === "TERMINAL"
|
|
294
|
+
&& delivery.deliveryId === binding.deliveryId && delivery.worktree === binding.worktree
|
|
295
|
+
&& delivery.deliveryBindingIdentity === binding.deliveryBindingIdentity
|
|
296
|
+
&& delivery.navigation?.sessionCorrelation === binding.correlation);
|
|
297
|
+
if (terminal.length === 1) {
|
|
298
|
+
await bindings.archiveTerminal(binding.sessionKey, terminal[0]);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
214
301
|
await bindings.detach(binding.deliveryId);
|
|
215
302
|
continue;
|
|
216
303
|
}
|
|
@@ -258,6 +345,19 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
258
345
|
return promise;
|
|
259
346
|
}
|
|
260
347
|
|
|
348
|
+
async function awaitRegistrationOrResult(execution, correlation) {
|
|
349
|
+
const result = execution.then((value) => Object.freeze({ kind: "result", result: value }));
|
|
350
|
+
while (true) {
|
|
351
|
+
const first = await Promise.race([
|
|
352
|
+
result,
|
|
353
|
+
control.waitForDelivery(correlation, options.deliveryRegistrationTimeoutMs ?? 10_000)
|
|
354
|
+
.then((delivery) => Object.freeze({ kind: "delivery", delivery })),
|
|
355
|
+
]);
|
|
356
|
+
if (first.kind === "result" || first.delivery !== undefined) return first;
|
|
357
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
261
361
|
async function invokeForSession(input) {
|
|
262
362
|
if (!accepting) return error("APPLICATION_CLOSING");
|
|
263
363
|
const candidateBinding = await bindings.bySession(input.sessionKey);
|
|
@@ -288,24 +388,19 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
288
388
|
if (candidateBinding !== undefined) return error("SESSION_INTAKE_BOUND");
|
|
289
389
|
const authorization = await conversationAuthorization();
|
|
290
390
|
if (authorization === undefined) return error("DSH_INTAKE_WORKSPACE_UNAUTHORIZED");
|
|
391
|
+
try { await (options.ensureGitWorktree ?? ensureGitWorktree)(authorization.path); }
|
|
392
|
+
catch { return error("GIT_INIT_FAILED"); }
|
|
291
393
|
sessionByCorrelation.set(correlation, input.sessionKey);
|
|
292
394
|
const execution = track(service.invoke(Object.freeze({ operation: "create", selector: operation.selector, worktree: authorization.path, directive: operation.directive, turn, correlation }), authorization));
|
|
293
|
-
const first = await
|
|
294
|
-
execution.then((result) => Object.freeze({ kind: "result", result })),
|
|
295
|
-
control.waitForDelivery(correlation, options.deliveryRegistrationTimeoutMs ?? 10_000)
|
|
296
|
-
.then((delivery) => Object.freeze({ kind: "delivery", delivery })),
|
|
297
|
-
]);
|
|
395
|
+
const first = await awaitRegistrationOrResult(execution, correlation);
|
|
298
396
|
if (first.kind === "result") {
|
|
397
|
+
if (first.result.kind === "TERMINAL") await archiveTerminal(input.sessionKey, correlation, first.result.deliveryId);
|
|
299
398
|
if (first.result.kind === "ERROR" || first.result.kind === "TERMINAL" || first.result.kind === "RECOVERY") sessionByCorrelation.delete(correlation);
|
|
300
399
|
return first.result;
|
|
301
400
|
}
|
|
302
401
|
const delivery = first.delivery;
|
|
303
|
-
if (delivery === undefined) {
|
|
304
|
-
const result = await execution;
|
|
305
|
-
if (result.kind === "ERROR" || result.kind === "TERMINAL") sessionByCorrelation.delete(correlation);
|
|
306
|
-
return result;
|
|
307
|
-
}
|
|
308
402
|
await bindings.claim(Object.freeze({ sessionKey: input.sessionKey, correlation, deliveryId: delivery.deliveryId, worktree: delivery.worktree, deliveryBindingIdentity: delivery.deliveryBindingIdentity }));
|
|
403
|
+
control.attach(delivery.deliveryId, correlation);
|
|
309
404
|
void track(execution.then(async (result) => {
|
|
310
405
|
try { await options.present?.(Object.freeze({
|
|
311
406
|
sessionKey: input.sessionKey,
|
|
@@ -313,7 +408,8 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
313
408
|
})); }
|
|
314
409
|
catch { /* presentation is not Delivery control */ }
|
|
315
410
|
if (result.kind === "TERMINAL" || result.kind === "ERROR") {
|
|
316
|
-
await
|
|
411
|
+
if (result.kind === "TERMINAL") await archiveTerminal(input.sessionKey, correlation, delivery.deliveryId);
|
|
412
|
+
else await bindings.detach(delivery.deliveryId);
|
|
317
413
|
sessionByCorrelation.delete(correlation);
|
|
318
414
|
}
|
|
319
415
|
})).catch(() => undefined);
|
|
@@ -339,6 +435,7 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
339
435
|
const recovered = (await bindingInventory()).filter((item) => item.deliveryId === result.deliveryId && item.worktree === result.worktree);
|
|
340
436
|
if (recovered.length !== 1) return error("INTAKE_BINDING_INVARIANT_VIOLATION");
|
|
341
437
|
await bindings.claim(Object.freeze({ sessionKey: input.sessionKey, correlation, deliveryId: result.deliveryId, worktree: result.worktree, deliveryBindingIdentity: recovered[0].deliveryBindingIdentity }));
|
|
438
|
+
control.attach(result.deliveryId, correlation);
|
|
342
439
|
sessionByCorrelation.set(correlation, input.sessionKey);
|
|
343
440
|
}
|
|
344
441
|
return result;
|
|
@@ -354,10 +451,12 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
354
451
|
if (existing === undefined) return error("DELIVERY_UNKNOWN");
|
|
355
452
|
return service.invoke(Object.freeze({ operation: "action-finish", ...(operation.remainder === undefined && attachments.length === 0 ? {} : { turn: Object.freeze({ text: operation.remainder ?? "", attachments }) }), correlation: existing.correlation }));
|
|
356
453
|
}
|
|
357
|
-
const
|
|
454
|
+
const deliveryId = operation.deliveryId ?? existing?.deliveryId;
|
|
455
|
+
if (deliveryId === undefined) return error("WSR_COMMAND_INVALID");
|
|
456
|
+
const result = await service.invoke(Object.freeze({ operation: "abandon", deliveryId, correlation }));
|
|
358
457
|
if (result.kind === "TERMINAL") {
|
|
359
|
-
const detached = await bindings.byDelivery(
|
|
360
|
-
await
|
|
458
|
+
const detached = await bindings.byDelivery(deliveryId);
|
|
459
|
+
if (detached !== undefined) await archiveTerminal(detached.sessionKey, detached.correlation, deliveryId);
|
|
361
460
|
if (detached !== undefined) sessionByCorrelation.delete(detached.correlation);
|
|
362
461
|
}
|
|
363
462
|
return result;
|
|
@@ -385,7 +484,7 @@ export async function createPluginRuntime(config, options = {}) {
|
|
|
385
484
|
return closePromise;
|
|
386
485
|
}
|
|
387
486
|
|
|
388
|
-
return Object.freeze({ application, service, control, bindings, invokeForSession, answerForSession, close });
|
|
487
|
+
return Object.freeze({ application, service, control, ownerProjection, bindings, invokeForSession, answerForSession, close });
|
|
389
488
|
}
|
|
390
489
|
|
|
391
490
|
function commandTurn(rawInput) {
|
|
@@ -394,7 +493,7 @@ function commandTurn(rawInput) {
|
|
|
394
493
|
}
|
|
395
494
|
|
|
396
495
|
export async function recordWsrCommandInput(agent, rawInput, attachments = [], createId = () => `message-workflow-execution-${randomUUID()}`) {
|
|
397
|
-
if (agent === null || typeof agent !== "object" || typeof agent.
|
|
496
|
+
if (agent === null || typeof agent !== "object" || typeof agent.session?.append !== "function"
|
|
398
497
|
|| typeof rawInput !== "string" || !Array.isArray(attachments) || typeof createId !== "function") {
|
|
399
498
|
throw new TypeError("DSH_INTAKE_USER_INPUT_INVALID");
|
|
400
499
|
}
|
|
@@ -407,8 +506,7 @@ export async function recordWsrCommandInput(agent, rawInput, attachments = [], c
|
|
|
407
506
|
...attachments,
|
|
408
507
|
]),
|
|
409
508
|
});
|
|
410
|
-
agent.
|
|
411
|
-
await agent.whenIdle();
|
|
509
|
+
agent.session.append("user/message", message, { surfaceOp: "append" });
|
|
412
510
|
return message;
|
|
413
511
|
}
|
|
414
512
|
|
|
@@ -418,7 +516,8 @@ export function mapIntakeToolOperation(args) {
|
|
|
418
516
|
|| Object.keys(args).some((key) => !["operation", "selector", "deliveryId"].includes(key))
|
|
419
517
|
|| !operationNames.includes(args.operation)
|
|
420
518
|
|| (args.operation === "create") !== (typeof args.selector === "string" && args.selector.length > 0)
|
|
421
|
-
|| (args.operation === "abandon" &&
|
|
519
|
+
|| (args.operation === "abandon" && args.deliveryId !== undefined
|
|
520
|
+
&& (typeof args.deliveryId !== "string" || args.deliveryId.length === 0))
|
|
422
521
|
|| (!["recover", "status", "abandon"].includes(args.operation) && args.deliveryId !== undefined)) {
|
|
423
522
|
throw new TypeError("INTAKE_OPERATION_INVALID");
|
|
424
523
|
}
|
|
@@ -430,11 +529,9 @@ export async function apply(ctx, config) {
|
|
|
430
529
|
const runtime = await createPluginRuntime(config, { present: (value) => presentationRouter.present(value),
|
|
431
530
|
sessionAvailable: (sessionKey) => ctx.agents.get(sessionKey) !== undefined,
|
|
432
531
|
resolveConversationWorkspace: async (agent) => resolveConversationWorkspace(ctx, agent) });
|
|
433
|
-
const executionApi = await import("wsr-execution");
|
|
434
|
-
const ownerProjection = executionApi.getExecutionControlPlaneProjection(runtime.application);
|
|
435
532
|
await registerDeliveryControlPlaneGateway(
|
|
436
533
|
ctx,
|
|
437
|
-
createDshSessionControlPlaneReadModel(ownerProjection, runtime.bindings),
|
|
534
|
+
createDshSessionControlPlaneReadModel(runtime.ownerProjection, runtime.bindings),
|
|
438
535
|
);
|
|
439
536
|
const active = new Set();
|
|
440
537
|
const attachmentStore = ctx.attachments;
|
|
@@ -442,43 +539,44 @@ export async function apply(ctx, config) {
|
|
|
442
539
|
const command = ctx.commands.register({
|
|
443
540
|
name: "wsr",
|
|
444
541
|
description: "Create, list, recover, inspect, finish, or abandon a Workflow Delivery",
|
|
445
|
-
input: { hint: "list | create <selector> | recover [delivery-id] | status [delivery-id] | action finish | abandon
|
|
542
|
+
input: { hint: "list | create <selector> | recover [delivery-id] | status [delivery-id] | action finish | abandon [delivery-id]", images: true },
|
|
446
543
|
recordInput: true,
|
|
447
544
|
async handler(invocation) {
|
|
448
545
|
return run((async () => {
|
|
449
|
-
let query = false;
|
|
450
546
|
try {
|
|
547
|
+
await recordWsrCommandInput(invocation.agent, invocation.rawInput, invocation.attachments);
|
|
451
548
|
const operation = parseWsrCommand(invocation.rawInput);
|
|
452
|
-
if (["create", "recover"].includes(operation.operation)) {
|
|
453
|
-
presentationRouter.retain(String(invocation.agent.id), invocation.agent);
|
|
454
|
-
}
|
|
455
|
-
query = operation.operation === "list" || operation.operation === "status";
|
|
456
549
|
const { createIntakePresentation, presentationForIntakeResult, serializeIntakePresentation } = await import("wsr-execution");
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
550
|
+
const complete = (presentation, kind) => {
|
|
551
|
+
presentToDshSession(invocation.agent, presentation);
|
|
552
|
+
return { kind, text: serializeIntakePresentation(presentation, 4096) };
|
|
553
|
+
};
|
|
554
|
+
const diagnostic = promptDiagnostic(operation, invocation.attachments);
|
|
555
|
+
if (diagnostic !== undefined) {
|
|
556
|
+
const presentation = createIntakePresentation(`presentation-${randomUUID()}`, "error", diagnostic);
|
|
557
|
+
return complete(presentation, "error");
|
|
462
558
|
}
|
|
463
559
|
if (invocation.attachments.length > 0 && !["create", "action-finish"].includes(operation.operation)) {
|
|
464
560
|
const presentation = createIntakePresentation(
|
|
465
561
|
`presentation-${randomUUID()}`, "error", { code: "WSR_COMMAND_INVALID", message: "WSR_COMMAND_INVALID" },
|
|
466
562
|
);
|
|
467
|
-
|
|
468
|
-
|
|
563
|
+
return complete(presentation, "error");
|
|
564
|
+
}
|
|
565
|
+
if (["create", "recover"].includes(operation.operation)) {
|
|
566
|
+
presentationRouter.retain(String(invocation.agent.id), invocation.agent);
|
|
469
567
|
}
|
|
470
568
|
const result = await runtime.invokeForSession({ sessionKey: String(invocation.agent.id), agent: invocation.agent, operation, turnText: commandTurn(invocation.rawInput), images: invocation.attachments, attachmentStore, signal: invocation.signal });
|
|
471
569
|
if (["create", "recover"].includes(operation.operation) && !["START_UNCERTAIN", "RECOVERY"].includes(result.kind)) {
|
|
472
570
|
presentationRouter.release(String(invocation.agent.id));
|
|
473
571
|
}
|
|
474
572
|
const presentation = presentationForDshOperation({ createIntakePresentation, presentationForIntakeResult }, `presentation-${randomUUID()}`, operation, result, 4096);
|
|
475
|
-
|
|
476
|
-
return { kind: result.kind === "ERROR" ? "error" : "success", text: serializeIntakePresentation(presentation, 4096) };
|
|
573
|
+
return complete(presentation, result.kind === "ERROR" ? "error" : "success");
|
|
477
574
|
} catch (cause) {
|
|
478
575
|
const { createIntakePresentation, serializeIntakePresentation } = await import("wsr-execution");
|
|
479
576
|
const code = typeof cause?.code === "string" ? cause.code : "DSH_INTAKE_FAILED";
|
|
480
|
-
const
|
|
481
|
-
|
|
577
|
+
const message = code === "WSR_COMMAND_INVALID" && typeof cause?.message === "string" ? cause.message : code;
|
|
578
|
+
const presentation = createIntakePresentation(`presentation-${randomUUID()}`, "error", { code, message });
|
|
579
|
+
presentToDshSession(invocation.agent, presentation);
|
|
482
580
|
return { kind: "error", text: serializeIntakePresentation(presentation, 4096) };
|
|
483
581
|
}
|
|
484
582
|
})());
|