@plurnk/plurnk-agui 1.7.0 → 1.9.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/SPEC.md +132 -65
- package/dist/AguiPlus.d.ts +2 -1
- package/dist/AguiPlus.d.ts.map +1 -1
- package/dist/AguiPlus.js +6 -6
- package/dist/AguiPlus.js.map +1 -1
- package/dist/AguiSurface.d.ts +56 -0
- package/dist/AguiSurface.d.ts.map +1 -0
- package/dist/AguiSurface.js +233 -0
- package/dist/AguiSurface.js.map +1 -0
- package/dist/EventRouter.d.ts.map +1 -1
- package/dist/EventRouter.js +8 -0
- package/dist/EventRouter.js.map +1 -1
- package/dist/Module.d.ts +3 -3
- package/dist/Module.d.ts.map +1 -1
- package/dist/Module.js +228 -128
- package/dist/Module.js.map +1 -1
- package/dist/Portal.d.ts +4 -6
- package/dist/Portal.d.ts.map +1 -1
- package/dist/Portal.js +9 -8
- package/dist/Portal.js.map +1 -1
- package/dist/ProposalHitl.d.ts +3 -3
- package/dist/ProposalHitl.d.ts.map +1 -1
- package/dist/ProposalHitl.js +2 -2
- package/dist/ProposalHitl.js.map +1 -1
- package/dist/Translator.d.ts +2 -1
- package/dist/Translator.d.ts.map +1 -1
- package/dist/Translator.js +171 -34
- package/dist/Translator.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +14 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/DaemonSeam.d.ts +0 -174
- package/dist/DaemonSeam.d.ts.map +0 -1
- package/dist/DaemonSeam.js +0 -7
- package/dist/DaemonSeam.js.map +0 -1
package/dist/Module.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// {§agui-daemon-client} The in-process transport module is what the daemon's boot plug-point
|
|
2
|
-
// activates: registerModule(aguiModule(opts)) hands this the
|
|
2
|
+
// activates: registerModule(aguiModule(opts)) hands this the ApplicationPort handle; it opens
|
|
3
3
|
// the AG-UI+ HTTP/SSE listener and owns the client interface from there.
|
|
4
4
|
//
|
|
5
5
|
// This is the single external client interface:
|
|
@@ -15,11 +15,11 @@ var _a;
|
|
|
15
15
|
import { createServer } from "node:http";
|
|
16
16
|
import Portal from "./Portal.js";
|
|
17
17
|
import { stateSnapshot, parseAction, actionResult } from "./AguiPlus.js";
|
|
18
|
-
import { PlurnkParser, UNKNOWN_POSITION } from "@plurnk/plurnk-contracts";
|
|
19
18
|
import { EventType } from "./types.js";
|
|
20
19
|
import { aguiRouteTemplate, observed } from "./observe.js";
|
|
21
20
|
import { RunAgentInputSchema } from "@ag-ui/core";
|
|
22
|
-
import { Problems, Validator } from "@plurnk/plurnk-contracts";
|
|
21
|
+
import { Problems, PlurnkParser, UNKNOWN_POSITION, Validator, } from "@plurnk/plurnk-contracts";
|
|
22
|
+
import { AGUI_BUILTIN_ACTIONS, AGUI_NOTIFICATIONS, } from "./AguiSurface.js";
|
|
23
23
|
import { resolveModuleOptions } from "./config.js";
|
|
24
24
|
const actionFailure = (code, detail, status = 400, extensions = {}) => ({
|
|
25
25
|
ok: false,
|
|
@@ -95,50 +95,62 @@ class Module {
|
|
|
95
95
|
#http;
|
|
96
96
|
#threads = new Map(); // threadId → envelope
|
|
97
97
|
#threadWorkers = new Map(); // threadId → conversation workerId
|
|
98
|
-
|
|
99
|
-
"ping", "discover", "providers.list", "workspace.list", "workspace.create", "workspace.attach",
|
|
100
|
-
]);
|
|
101
|
-
// The control plane vs the world. An AG-UI Run lives in a world (a conversation, or an action
|
|
102
|
-
// that reads/writes a workspace's log); a control-plane action (list/create/attach/discover/
|
|
103
|
-
// auth) does NOT — so it must not bind or forge a workspace. Only these kinds bind a workspace.
|
|
104
|
-
static #WORLD_SCOPED = Object.freeze(new Set([
|
|
105
|
-
"workspace.workers", "log.read", "loop.inject", "loop.cancel", "workspace.prompts", "workspace.rename",
|
|
106
|
-
"workspace.constrain", "workspace.unconstrain", "workspace.constraints", "entry.read",
|
|
107
|
-
"workspace.derivation", "op.exec", "op.parse", "workspace.members", "op.look", "run.fork",
|
|
108
|
-
]));
|
|
109
|
-
static #BUILTIN_ACTIONS = Object.freeze(new Set([
|
|
110
|
-
...this.#CONTROL_ACTIONS,
|
|
111
|
-
...this.#WORLD_SCOPED,
|
|
112
|
-
]));
|
|
113
|
-
static #NOTIFICATIONS = Object.freeze([
|
|
114
|
-
"log/entry", "loop/terminated", "loop/proposal", "loop/interaction", "notice/event",
|
|
115
|
-
"stream/event", "stream/concluded", "workspace/branch-batch",
|
|
116
|
-
]);
|
|
98
|
+
#actions = new Map();
|
|
117
99
|
constructor(seam, opts) {
|
|
118
100
|
this.#seam = seam;
|
|
119
101
|
this.#opts = resolveModuleOptions(opts);
|
|
120
|
-
this.#
|
|
102
|
+
this.#registerActions();
|
|
121
103
|
this.#portal = new Portal(seam);
|
|
122
104
|
this.#http = createServer((req, res) => { void this.#route(req, res); });
|
|
123
105
|
}
|
|
124
|
-
#
|
|
125
|
-
const
|
|
106
|
+
#registerActions() {
|
|
107
|
+
for (const [name, contract] of Object.entries(AGUI_BUILTIN_ACTIONS)) {
|
|
108
|
+
this.#actions.set(name, {
|
|
109
|
+
...contract,
|
|
110
|
+
execute: (params, env, conversationWorkerId) => this.#executeBuiltin(name, params, env, conversationWorkerId),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
126
113
|
for (const descriptor of this.#seam.listModuleActions()) {
|
|
127
|
-
const { name, scope } = descriptor;
|
|
128
|
-
if (
|
|
129
|
-
throw new Error(`
|
|
130
|
-
|
|
131
|
-
if (scope !== "worldless" && scope !== "workspace") {
|
|
114
|
+
const { name, scope, inputSchema, outputSchema } = descriptor;
|
|
115
|
+
if (this.#actions.has(name))
|
|
116
|
+
throw new Error(`AG-UI action '${name}' is registered more than once`);
|
|
117
|
+
if (scope !== "worldless" && scope !== "workspace" && scope !== "worker") {
|
|
132
118
|
throw new Error(`module action '${name}' has invalid scope '${String(scope)}'`);
|
|
133
119
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
120
|
+
this.#actions.set(name, {
|
|
121
|
+
scope,
|
|
122
|
+
inputSchema,
|
|
123
|
+
outputSchema,
|
|
124
|
+
execute: async (params, env, conversationWorkerId) => {
|
|
125
|
+
if (scope === "worldless") {
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
result: await this.#seam.invokeModuleAction(name, params, { scope }),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const { workspaceId } = _a.#requireWorkspace(name, env);
|
|
132
|
+
if (scope === "workspace") {
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
result: await this.#seam.invokeModuleAction(name, params, { scope, workspaceId }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const workerId = conversationWorkerId ?? await this.#seam.ensureModelWorker(workspaceId);
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
result: await this.#seam.invokeModuleAction(name, params, { scope, workspaceId, workerId }),
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
});
|
|
137
145
|
}
|
|
138
|
-
return actions;
|
|
139
146
|
}
|
|
140
|
-
#
|
|
141
|
-
return
|
|
147
|
+
#requiresWorkspace(kind) {
|
|
148
|
+
return this.#actions.get(kind)?.scope !== "worldless";
|
|
149
|
+
}
|
|
150
|
+
static #requireWorkspace(kind, env) {
|
|
151
|
+
if (env === null)
|
|
152
|
+
throw new Error(`action '${kind}' operates within a workspace, but none is bound`);
|
|
153
|
+
return env;
|
|
142
154
|
}
|
|
143
155
|
static init(opts) {
|
|
144
156
|
return {
|
|
@@ -267,15 +279,20 @@ class Module {
|
|
|
267
279
|
return { env, reattached };
|
|
268
280
|
}
|
|
269
281
|
// Resolve the thread's conversation worker within its world. Cached per threadId;
|
|
270
|
-
// worker names are immutable so the binding can't rot.
|
|
271
|
-
|
|
282
|
+
// worker names are immutable so the binding can't rot. {§worker-settings} — the
|
|
283
|
+
// client's per-run declaration (forwardedProps.plurnk.requestUserInput) rides the
|
|
284
|
+
// resolution, so a client can change its mind between loops.
|
|
285
|
+
async #conversationWorker(threadId, env, settings) {
|
|
272
286
|
const cached = this.#threadWorkers.get(threadId);
|
|
273
|
-
if (cached !== undefined)
|
|
287
|
+
if (cached !== undefined) {
|
|
288
|
+
if (settings !== undefined)
|
|
289
|
+
await this.#seam.setWorkerSettings({ workspaceId: env.workspaceId, workerId: cached, settings });
|
|
274
290
|
return cached;
|
|
291
|
+
}
|
|
275
292
|
const workerId = threadId === env.workspaceName
|
|
276
|
-
? await this.#seam.ensureModelWorker(env.workspaceId)
|
|
293
|
+
? await this.#seam.ensureModelWorker(env.workspaceId, settings)
|
|
277
294
|
: (await this.#seam.listWorkers(env.workspaceId)).find((r) => r.name === threadId)?.id
|
|
278
|
-
?? (await this.#seam.createConversationWorker({ workspaceId: env.workspaceId, name: threadId })).workerId;
|
|
295
|
+
?? (await this.#seam.createConversationWorker({ workspaceId: env.workspaceId, name: threadId, ...(settings === undefined ? {} : { settings }) })).workerId;
|
|
279
296
|
this.#threadWorkers.set(threadId, workerId);
|
|
280
297
|
return workerId;
|
|
281
298
|
}
|
|
@@ -301,7 +318,7 @@ class Module {
|
|
|
301
318
|
// unknown kind, which is no worker at all) answers without binding — or forging — a
|
|
302
319
|
// workspace. Only world-scoped actions and conversations reach #envelope below.
|
|
303
320
|
const action = parseAction(input.forwardedProps);
|
|
304
|
-
if (action !== null && !this.#
|
|
321
|
+
if (action !== null && !this.#requiresWorkspace(action.kind)) {
|
|
305
322
|
return await this.#controlRun(action, input, res);
|
|
306
323
|
}
|
|
307
324
|
let prompt = null;
|
|
@@ -322,7 +339,9 @@ class Module {
|
|
|
322
339
|
// world. threadId == workspace name binds the model worker (the default conversation);
|
|
323
340
|
// a distinct threadId names its own worker: found by name, else minted via
|
|
324
341
|
// createConversationWorker. The name is the identity at BOTH levels.
|
|
325
|
-
const
|
|
342
|
+
const plurnk = forwarded?.plurnk;
|
|
343
|
+
const requestUserInput = plurnk?.requestUserInput;
|
|
344
|
+
const workerId = await this.#conversationWorker(input.threadId, env, typeof requestUserInput === "boolean" ? { requestUserInput } : undefined);
|
|
326
345
|
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", "connection": "keep-alive" });
|
|
327
346
|
let finished = false;
|
|
328
347
|
const interrupts = [];
|
|
@@ -434,20 +453,15 @@ class Module {
|
|
|
434
453
|
...(forwarded !== undefined && Object.hasOwn(forwarded, "openPaths")
|
|
435
454
|
? { openPaths: forwarded.openPaths }
|
|
436
455
|
: {}),
|
|
437
|
-
// {§methods-loop-run-model} The client
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
:
|
|
443
|
-
...(forwarded !== undefined && Object.hasOwn(forwarded, "model")
|
|
444
|
-
? { model: forwarded.model }
|
|
456
|
+
// {§methods-loop-run-model} The client may still forward one explicit
|
|
457
|
+
// alias-or-provider/model selector. The daemon
|
|
458
|
+
// persists an explicit selection onto the worker and snapshots it on the
|
|
459
|
+
// loop; an omitted selector continues the worker's durable model.
|
|
460
|
+
...(forwarded !== undefined && Object.hasOwn(forwarded, "selector")
|
|
461
|
+
? { selector: forwarded.selector }
|
|
445
462
|
: {}),
|
|
446
|
-
...(forwarded !== undefined && Object.hasOwn(forwarded, "
|
|
447
|
-
? {
|
|
448
|
-
: {}),
|
|
449
|
-
...(forwarded !== undefined && Object.hasOwn(forwarded, "childModel")
|
|
450
|
-
? { childModel: forwarded.childModel }
|
|
463
|
+
...(forwarded !== undefined && Object.hasOwn(forwarded, "childSelector")
|
|
464
|
+
? { childSelector: forwarded.childSelector }
|
|
451
465
|
: {}),
|
|
452
466
|
});
|
|
453
467
|
// A dropped SSE on a live AG-UI Run cancels the loop (hangup is the abort). A stream we
|
|
@@ -493,38 +507,72 @@ class Module {
|
|
|
493
507
|
emit({ type: EventType.RUN_FINISHED, threadId: input.threadId, runId: input.runId, outcome: { type: "success" } });
|
|
494
508
|
res.end();
|
|
495
509
|
}
|
|
496
|
-
//
|
|
497
|
-
// The built-ins come from the same inventories routing uses; extension names come from core.
|
|
510
|
+
// Discovery is a projection of the executable registry, not a second inventory.
|
|
498
511
|
async #capabilities() {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
methods[k] = true;
|
|
508
|
-
}
|
|
509
|
-
const notifications = {};
|
|
510
|
-
for (const n of _a.#NOTIFICATIONS)
|
|
511
|
-
notifications[n] = true;
|
|
512
|
+
const actions = Object.fromEntries([...this.#actions].map(([name, action]) => [
|
|
513
|
+
name,
|
|
514
|
+
{
|
|
515
|
+
scope: action.scope,
|
|
516
|
+
inputSchema: action.inputSchema,
|
|
517
|
+
outputSchema: action.outputSchema,
|
|
518
|
+
},
|
|
519
|
+
]));
|
|
512
520
|
const display = Validator.assertClientDisplayCapabilities(await this.#seam.listClientDisplayCapabilities());
|
|
513
|
-
return {
|
|
521
|
+
return Validator.assertAguiDiscovery({
|
|
522
|
+
schemaVersion: 1,
|
|
523
|
+
actions,
|
|
524
|
+
notifications: AGUI_NOTIFICATIONS,
|
|
525
|
+
display,
|
|
526
|
+
});
|
|
514
527
|
}
|
|
515
|
-
// The action executor — the verb surface. The control plane runs worldless; everything
|
|
516
|
-
// below the guard operates within a bound workspace. An unknown kind is an honest error,
|
|
517
|
-
// never a silent pass-through. loop.inject rides here too (§4): the seam's unified
|
|
518
|
-
// runLoop folds a prompt into the active drain; the steered effect streams on the SSE.
|
|
519
528
|
async #action(a, env, conversationWorkerId) {
|
|
520
|
-
const
|
|
521
|
-
|
|
529
|
+
const action = this.#actions.get(a.kind);
|
|
530
|
+
if (action === undefined) {
|
|
531
|
+
return actionFailure("unknown-action", `Action '${a.kind}' is not registered.`, 404, {
|
|
532
|
+
requestedAction: a.kind,
|
|
533
|
+
recovery: "Use an action advertised by discover.",
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
const admitted = Validator.validateJsonSchemaInstance(action.inputSchema, a.params);
|
|
537
|
+
if (!admitted.valid) {
|
|
538
|
+
return actionFailure("invalid-action-parameters", `Action '${a.kind}' received parameters outside its advertised input schema.`, 400, {
|
|
539
|
+
issues: admitted.errors,
|
|
540
|
+
recovery: "Conform the action parameters to discover.actions[<name>].inputSchema.",
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
if (action.scope !== "worldless")
|
|
545
|
+
_a.#requireWorkspace(a.kind, env);
|
|
546
|
+
const outcome = await action.execute(a.params, env, conversationWorkerId);
|
|
547
|
+
if (!outcome.ok)
|
|
548
|
+
return outcome;
|
|
549
|
+
const projected = Validator.validateJsonSchemaInstance(action.outputSchema, outcome.result);
|
|
550
|
+
if (!projected.valid) {
|
|
551
|
+
throw new Error(`AG-UI action '${a.kind}' produced output outside its advertised schema: ${JSON.stringify(projected.errors)}`);
|
|
552
|
+
}
|
|
553
|
+
return outcome;
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
const problem = problemFromError(err);
|
|
557
|
+
if (problem !== null)
|
|
558
|
+
return { ok: false, problem };
|
|
559
|
+
console.error(`AG-UI action '${a.kind}' failed:`, err);
|
|
560
|
+
return actionFailure("action-failed", "The action failed unexpectedly.", 500);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// The built-in implementation half of the declarative registry. The registry
|
|
564
|
+
// owns membership, scope, admission, projection, and discovery; this dispatch
|
|
565
|
+
// owns only the corresponding daemon operation.
|
|
566
|
+
async #executeBuiltin(kind, p, env, conversationWorkerId) {
|
|
522
567
|
try {
|
|
523
|
-
//
|
|
524
|
-
switch (
|
|
568
|
+
// Worldless actions never bind or forge a workspace.
|
|
569
|
+
switch (kind) {
|
|
525
570
|
case "ping": return { ok: true, result: {} };
|
|
526
571
|
case "discover": return { ok: true, result: await this.#capabilities() };
|
|
527
572
|
case "providers.list": return { ok: true, result: this.#seam.listProviders() };
|
|
573
|
+
case "models.list": {
|
|
574
|
+
return { ok: true, result: this.#seam.listModels(Validator.assertModelCatalogQuery(p)) };
|
|
575
|
+
}
|
|
528
576
|
case "workspace.list": return { ok: true, result: { workspaces: await this.#seam.listWorkspaces() } };
|
|
529
577
|
case "workspace.create": {
|
|
530
578
|
// The name IS the identity: an explicit name creates/attaches EXACTLY
|
|
@@ -567,35 +615,14 @@ class Module {
|
|
|
567
615
|
return { ok: true, result: { id: att.workspaceId, name: att.workspaceName, workerId: att.workerId } };
|
|
568
616
|
}
|
|
569
617
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
result: await this.#seam.invokeModuleAction(a.kind, p, { scope: "worldless" }),
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
// Below this line lives IN a world. An unknown kind is no worker at all; a
|
|
577
|
-
// world-scoped kind with no bound workspace is a routing bug — both surface plainly.
|
|
578
|
-
if (!_a.#WORLD_SCOPED.has(a.kind) && moduleAction?.scope !== "workspace") {
|
|
579
|
-
return actionFailure("unknown-action", `Action '${a.kind}' is not registered.`, 404, {
|
|
580
|
-
requestedAction: a.kind,
|
|
581
|
-
recovery: "Use an action advertised by discover.",
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
if (env === null)
|
|
585
|
-
throw new Error(`action '${a.kind}' operates within a workspace, but none is bound`);
|
|
586
|
-
if (moduleAction?.scope === "workspace") {
|
|
587
|
-
return {
|
|
588
|
-
ok: true,
|
|
589
|
-
result: await this.#seam.invokeModuleAction(a.kind, p, { scope: "workspace", workspaceId: env.workspaceId }),
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
switch (a.kind) {
|
|
593
|
-
case "workspace.workers": return { ok: true, result: { workers: await this.#seam.listWorkers(typeof p.id === "number" ? p.id : env.workspaceId) } };
|
|
618
|
+
const world = _a.#requireWorkspace(kind, env);
|
|
619
|
+
switch (kind) {
|
|
620
|
+
case "workspace.workers": return { ok: true, result: { workers: await this.#seam.listWorkers(typeof p.id === "number" ? p.id : world.workspaceId) } };
|
|
594
621
|
case "log.read": {
|
|
595
622
|
// Default worker: the conversation; p.workerId pins another.
|
|
596
|
-
const readWorkerId = typeof p.workerId === "number" ? p.workerId : conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
623
|
+
const readWorkerId = typeof p.workerId === "number" ? p.workerId : conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId);
|
|
597
624
|
const entries = await this.#seam.readLog({
|
|
598
|
-
workspaceId:
|
|
625
|
+
workspaceId: world.workspaceId,
|
|
599
626
|
workerId: readWorkerId,
|
|
600
627
|
...(Object.hasOwn(p, "limit") ? { limit: p.limit } : {}),
|
|
601
628
|
...(Object.hasOwn(p, "sinceId") ? { sinceId: p.sinceId } : {}),
|
|
@@ -611,38 +638,38 @@ class Module {
|
|
|
611
638
|
if (typeof p.prompt !== "string" || p.prompt.length === 0) {
|
|
612
639
|
return actionFailure("invalid-action-parameters", "loop.inject requires a non-empty prompt.", 400, { field: "prompt", recovery: "Provide the prompt to inject." });
|
|
613
640
|
}
|
|
614
|
-
const ack = await this.#seam.runLoop({ workspaceId:
|
|
641
|
+
const ack = await this.#seam.runLoop({ workspaceId: world.workspaceId, workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId), prompt: p.prompt });
|
|
615
642
|
return operationOutcome(ack);
|
|
616
643
|
}
|
|
617
644
|
// The stop button (TUI /stop + Ctrl-C, nvim :PlurnkStop): abort the model
|
|
618
645
|
// worker's active drain. Mirrors the SSE-hangup abort, addressable as a verb.
|
|
619
|
-
case "loop.cancel": return { ok: true, result: { cancelled: this.#seam.cancelDrain(conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
646
|
+
case "loop.cancel": return { ok: true, result: { cancelled: this.#seam.cancelDrain(conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId), typeof p.reason === "string" ? p.reason : undefined) } };
|
|
620
647
|
case "workspace.prompts": return {
|
|
621
648
|
ok: true,
|
|
622
649
|
result: {
|
|
623
|
-
prompts: await this.#seam.listPrompts(
|
|
650
|
+
prompts: await this.#seam.listPrompts(world.workspaceId, Object.hasOwn(p, "limit") ? p.limit : undefined),
|
|
624
651
|
},
|
|
625
652
|
};
|
|
626
653
|
case "workspace.rename": {
|
|
627
654
|
if (typeof p.name !== "string" || p.name.length === 0) {
|
|
628
655
|
return actionFailure("invalid-action-parameters", "workspace.rename requires a non-empty name.", 400, { field: "name", recovery: "Provide the new workspace name." });
|
|
629
656
|
}
|
|
630
|
-
return { ok: true, result: await this.#seam.renameWorkspace(
|
|
657
|
+
return { ok: true, result: await this.#seam.renameWorkspace(world.workspaceId, p.name) };
|
|
631
658
|
}
|
|
632
659
|
case "workspace.constrain": {
|
|
633
660
|
if (typeof p.effect !== "string" || typeof p.glob !== "string") {
|
|
634
661
|
return actionFailure("invalid-action-parameters", "workspace.constrain requires string effect and glob parameters.", 400, { fields: ["effect", "glob"], recovery: "Provide both constraint parameters." });
|
|
635
662
|
}
|
|
636
|
-
return { ok: true, result: await this.#seam.constrain(
|
|
663
|
+
return { ok: true, result: await this.#seam.constrain(world.workspaceId, p.effect, p.glob) };
|
|
637
664
|
}
|
|
638
665
|
case "workspace.unconstrain": {
|
|
639
666
|
if (typeof p.effect !== "string" || typeof p.glob !== "string") {
|
|
640
667
|
return actionFailure("invalid-action-parameters", "workspace.unconstrain requires string effect and glob parameters.", 400, { fields: ["effect", "glob"], recovery: "Provide both constraint parameters." });
|
|
641
668
|
}
|
|
642
|
-
return { ok: true, result: await this.#seam.unconstrain(
|
|
669
|
+
return { ok: true, result: await this.#seam.unconstrain(world.workspaceId, p.effect, p.glob) };
|
|
643
670
|
}
|
|
644
|
-
case "workspace.constraints": return { ok: true, result: { constraints: await this.#seam.listConstraints(
|
|
645
|
-
case "workspace.derivation": return { ok: true, result: { status: this.#seam.workspaceDerivationStatus(
|
|
671
|
+
case "workspace.constraints": return { ok: true, result: { constraints: await this.#seam.listConstraints(world.workspaceId) } };
|
|
672
|
+
case "workspace.derivation": return { ok: true, result: { status: this.#seam.workspaceDerivationStatus(world.workspaceId) } };
|
|
646
673
|
case "entry.read": {
|
|
647
674
|
if (typeof p.target !== "string") {
|
|
648
675
|
return actionFailure("invalid-action-parameters", "entry.read requires a string target.", 400, { field: "target", recovery: "Provide an entry URI." });
|
|
@@ -652,10 +679,10 @@ class Module {
|
|
|
652
679
|
return actionFailure("invalid-action-parameters", "entry.read workerId must be a positive integer.", 400, { field: "workerId", recovery: "Use the workerId supplied with the entry notification." });
|
|
653
680
|
}
|
|
654
681
|
const result = Validator.assertEntryReadResult(await this.#seam.readEntry({
|
|
655
|
-
workspaceId:
|
|
682
|
+
workspaceId: world.workspaceId,
|
|
656
683
|
workerId: typeof p.workerId === "number"
|
|
657
684
|
? p.workerId
|
|
658
|
-
: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
685
|
+
: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
659
686
|
target: p.target,
|
|
660
687
|
...(Object.hasOwn(p, "channel") ? { channel: p.channel } : {}),
|
|
661
688
|
...(Object.hasOwn(p, "offset") ? { offset: p.offset } : {}),
|
|
@@ -669,12 +696,18 @@ class Module {
|
|
|
669
696
|
return actionFailure("invalid-action-parameters", "op.exec requires a non-empty command.", 400, { field: "command", recovery: "Provide the command to execute." });
|
|
670
697
|
}
|
|
671
698
|
const statement = {
|
|
672
|
-
op: "EXEC",
|
|
699
|
+
op: "EXEC", delimiter: "", annotation: null, signal: null, target: null,
|
|
673
700
|
lineMarker: null, body: p.command, position: UNKNOWN_POSITION,
|
|
674
701
|
};
|
|
675
702
|
// Client ops journal as client-origin turns in the client worker (worker split:
|
|
676
|
-
// only LOOPS live in the model worker)
|
|
677
|
-
|
|
703
|
+
// only LOOPS live in the model worker) and execute in the attached Worker's
|
|
704
|
+
// Functionality ({§actor-boundary-attached-functionality}).
|
|
705
|
+
const [result] = await this.#seam.dispatchClientAction({
|
|
706
|
+
workspaceId: world.workspaceId,
|
|
707
|
+
workerId: world.workerId,
|
|
708
|
+
functionalityWorkerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
709
|
+
statements: [statement],
|
|
710
|
+
});
|
|
678
711
|
if (result === undefined)
|
|
679
712
|
throw new Error("op.exec dispatch returned no operation result");
|
|
680
713
|
return operationOutcome(result);
|
|
@@ -715,7 +748,12 @@ class Module {
|
|
|
715
748
|
}
|
|
716
749
|
const dispatched = statements.length === 0
|
|
717
750
|
? []
|
|
718
|
-
: await this.#seam.dispatchClientAction({
|
|
751
|
+
: await this.#seam.dispatchClientAction({
|
|
752
|
+
workspaceId: world.workspaceId,
|
|
753
|
+
workerId: world.workerId,
|
|
754
|
+
functionalityWorkerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
755
|
+
statements,
|
|
756
|
+
});
|
|
719
757
|
let index = 0;
|
|
720
758
|
for (let i = 0; i < results.length; i++) {
|
|
721
759
|
if (results[i] === null)
|
|
@@ -723,7 +761,7 @@ class Module {
|
|
|
723
761
|
}
|
|
724
762
|
return { ok: true, result: { results } };
|
|
725
763
|
}
|
|
726
|
-
case "workspace.members": return { ok: true, result: await this.#seam.listMembers(
|
|
764
|
+
case "workspace.members": return { ok: true, result: await this.#seam.listMembers(world.workspaceId) };
|
|
727
765
|
case "op.look": {
|
|
728
766
|
// {§agui-op-look}
|
|
729
767
|
if (typeof p.text !== "string" || p.text.length === 0) {
|
|
@@ -767,20 +805,82 @@ class Module {
|
|
|
767
805
|
return actionFailure("invalid-action-parameters", `op.look parsed ${item.statement.op}; the single statement must be LOOK.`, 400, { field: "text", recovery: "Use LOOK as the observation operation." });
|
|
768
806
|
}
|
|
769
807
|
const statement = { ...item.statement, op: "READ" };
|
|
770
|
-
return operationOutcome(await this.#seam.look({
|
|
808
|
+
return operationOutcome(await this.#seam.look({
|
|
809
|
+
workspaceId: world.workspaceId,
|
|
810
|
+
workerId: world.workerId,
|
|
811
|
+
functionalityWorkerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
812
|
+
statement,
|
|
813
|
+
}));
|
|
771
814
|
}
|
|
772
|
-
case "run.fork": return { ok: true, result: await this.#seam.forkWorker({ workspaceId:
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
815
|
+
case "run.fork": return { ok: true, result: await this.#seam.forkWorker({ workspaceId: world.workspaceId, workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId), ...(typeof p.name === "string" ? { name: p.name } : {}) }) };
|
|
816
|
+
case "worker.model.get": {
|
|
817
|
+
const { model, spawnModel } = await this.#seam.readWorkerModel({
|
|
818
|
+
workspaceId: world.workspaceId,
|
|
819
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
820
|
+
});
|
|
821
|
+
return { ok: true, result: { model, spawnModel } };
|
|
822
|
+
}
|
|
823
|
+
case "worker.model.set": {
|
|
824
|
+
if (typeof p.selector !== "string" || p.selector.length === 0) {
|
|
825
|
+
return actionFailure("invalid-action-parameters", "worker.model.set requires a selector.", 400, { recovery: "Provide a declared alias or provider/model route." });
|
|
826
|
+
}
|
|
827
|
+
return { ok: true, result: await this.#seam.setWorkerModel({
|
|
828
|
+
workspaceId: world.workspaceId,
|
|
829
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
830
|
+
selector: p.selector,
|
|
831
|
+
}) };
|
|
832
|
+
}
|
|
833
|
+
case "worker.child.set": {
|
|
834
|
+
if (!Object.hasOwn(p, "selector")
|
|
835
|
+
|| (p.selector !== null && (typeof p.selector !== "string" || p.selector.length === 0))) {
|
|
836
|
+
return actionFailure("invalid-action-parameters", "worker.child.set requires a selector.", 400, { recovery: "Provide a declared alias or provider/model route; null means inherit." });
|
|
837
|
+
}
|
|
838
|
+
return { ok: true, result: await this.#seam.setWorkerSpawnModel({
|
|
839
|
+
workspaceId: world.workspaceId,
|
|
840
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
841
|
+
selector: p.selector,
|
|
842
|
+
}) };
|
|
843
|
+
}
|
|
844
|
+
case "worker.reasoning.get": {
|
|
845
|
+
return { ok: true, result: await this.#seam.readWorkerReasoning({
|
|
846
|
+
workspaceId: world.workspaceId,
|
|
847
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
848
|
+
}) };
|
|
849
|
+
}
|
|
850
|
+
case "worker.reasoning.set": {
|
|
851
|
+
if (!Object.hasOwn(p, "policy")) {
|
|
852
|
+
return actionFailure("invalid-action-parameters", "worker.reasoning.set requires a policy.", 400, { recovery: "Provide a reasoning policy." });
|
|
853
|
+
}
|
|
854
|
+
return { ok: true, result: await this.#seam.setWorkerReasoning({
|
|
855
|
+
workspaceId: world.workspaceId,
|
|
856
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
857
|
+
policy: p.policy,
|
|
858
|
+
}) };
|
|
859
|
+
}
|
|
860
|
+
case "worker.settings.get": {
|
|
861
|
+
return { ok: true, result: await this.#seam.readWorkerSettings({
|
|
862
|
+
workspaceId: world.workspaceId,
|
|
863
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
864
|
+
}) };
|
|
865
|
+
}
|
|
866
|
+
case "worker.settings.set": {
|
|
867
|
+
if (typeof p.settings !== "object" || p.settings === null || Array.isArray(p.settings)) {
|
|
868
|
+
return actionFailure("invalid-action-parameters", "worker.settings.set requires a settings object.", 400, { recovery: "Provide a settings object, e.g. { requestUserInput: true }." });
|
|
869
|
+
}
|
|
870
|
+
return { ok: true, result: await this.#seam.setWorkerSettings({
|
|
871
|
+
workspaceId: world.workspaceId,
|
|
872
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
873
|
+
settings: p.settings,
|
|
874
|
+
}) };
|
|
875
|
+
}
|
|
876
|
+
default: throw new Error(`AG-UI built-in '${kind}' has no executor`);
|
|
777
877
|
}
|
|
778
878
|
}
|
|
779
879
|
catch (err) {
|
|
780
880
|
const problem = problemFromError(err);
|
|
781
881
|
if (problem !== null)
|
|
782
882
|
return { ok: false, problem };
|
|
783
|
-
console.error(`AG-UI action '${
|
|
883
|
+
console.error(`AG-UI action '${kind}' failed:`, err);
|
|
784
884
|
return actionFailure("action-failed", "The action failed unexpectedly.", 500);
|
|
785
885
|
}
|
|
786
886
|
}
|