@plurnk/plurnk-agui 1.8.0 → 1.9.1
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 +102 -77
- package/dist/AguiPlus.d.ts +1 -0
- package/dist/AguiPlus.d.ts.map +1 -1
- package/dist/AguiPlus.js +1 -1
- 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 +158 -138
- package/dist/Module.js.map +1 -1
- package/dist/Portal.d.ts +2 -2
- package/dist/Portal.d.ts.map +1 -1
- package/dist/ProposalHitl.d.ts +2 -2
- package/dist/ProposalHitl.d.ts.map +1 -1
- 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 +131 -24
- 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 +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/DaemonSeam.d.ts +0 -226
- 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 {
|
|
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,53 +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", "models.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
|
-
"worker.model.get", "worker.model.set", "worker.child.set",
|
|
109
|
-
"worker.reasoning.get", "worker.reasoning.set",
|
|
110
|
-
"worker.settings.get", "worker.settings.set",
|
|
111
|
-
]));
|
|
112
|
-
static #BUILTIN_ACTIONS = Object.freeze(new Set([
|
|
113
|
-
...this.#CONTROL_ACTIONS,
|
|
114
|
-
...this.#WORLD_SCOPED,
|
|
115
|
-
]));
|
|
116
|
-
static #NOTIFICATIONS = Object.freeze([
|
|
117
|
-
"log/entry", "loop/terminated", "loop/proposal", "loop/interaction", "notice/event",
|
|
118
|
-
"stream/event", "stream/concluded", "workspace/branch-batch",
|
|
119
|
-
]);
|
|
98
|
+
#actions = new Map();
|
|
120
99
|
constructor(seam, opts) {
|
|
121
100
|
this.#seam = seam;
|
|
122
101
|
this.#opts = resolveModuleOptions(opts);
|
|
123
|
-
this.#
|
|
102
|
+
this.#registerActions();
|
|
124
103
|
this.#portal = new Portal(seam);
|
|
125
104
|
this.#http = createServer((req, res) => { void this.#route(req, res); });
|
|
126
105
|
}
|
|
127
|
-
#
|
|
128
|
-
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
|
+
}
|
|
129
113
|
for (const descriptor of this.#seam.listModuleActions()) {
|
|
130
|
-
const { name, scope } = descriptor;
|
|
131
|
-
if (
|
|
132
|
-
throw new Error(`
|
|
133
|
-
|
|
134
|
-
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") {
|
|
135
118
|
throw new Error(`module action '${name}' has invalid scope '${String(scope)}'`);
|
|
136
119
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
});
|
|
140
145
|
}
|
|
141
|
-
return actions;
|
|
142
146
|
}
|
|
143
|
-
#
|
|
144
|
-
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;
|
|
145
154
|
}
|
|
146
155
|
static init(opts) {
|
|
147
156
|
return {
|
|
@@ -309,7 +318,7 @@ class Module {
|
|
|
309
318
|
// unknown kind, which is no worker at all) answers without binding — or forging — a
|
|
310
319
|
// workspace. Only world-scoped actions and conversations reach #envelope below.
|
|
311
320
|
const action = parseAction(input.forwardedProps);
|
|
312
|
-
if (action !== null && !this.#
|
|
321
|
+
if (action !== null && !this.#requiresWorkspace(action.kind)) {
|
|
313
322
|
return await this.#controlRun(action, input, res);
|
|
314
323
|
}
|
|
315
324
|
let prompt = null;
|
|
@@ -498,52 +507,71 @@ class Module {
|
|
|
498
507
|
emit({ type: EventType.RUN_FINISHED, threadId: input.threadId, runId: input.runId, outcome: { type: "success" } });
|
|
499
508
|
res.end();
|
|
500
509
|
}
|
|
501
|
-
//
|
|
502
|
-
// 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.
|
|
503
511
|
async #capabilities() {
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
methods[k] = true;
|
|
513
|
-
}
|
|
514
|
-
const notifications = {};
|
|
515
|
-
for (const n of _a.#NOTIFICATIONS)
|
|
516
|
-
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
|
+
]));
|
|
517
520
|
const display = Validator.assertClientDisplayCapabilities(await this.#seam.listClientDisplayCapabilities());
|
|
518
|
-
return {
|
|
521
|
+
return Validator.assertAguiDiscovery({
|
|
522
|
+
schemaVersion: 1,
|
|
523
|
+
actions,
|
|
524
|
+
notifications: AGUI_NOTIFICATIONS,
|
|
525
|
+
display,
|
|
526
|
+
});
|
|
519
527
|
}
|
|
520
|
-
// The action executor — the verb surface. The control plane runs worldless; everything
|
|
521
|
-
// below the guard operates within a bound workspace. An unknown kind is an honest error,
|
|
522
|
-
// never a silent pass-through. loop.inject rides here too (§4): the seam's unified
|
|
523
|
-
// runLoop folds a prompt into the active drain; the steered effect streams on the SSE.
|
|
524
528
|
async #action(a, env, conversationWorkerId) {
|
|
525
|
-
const
|
|
526
|
-
|
|
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) {
|
|
527
567
|
try {
|
|
528
|
-
//
|
|
529
|
-
switch (
|
|
568
|
+
// Worldless actions never bind or forge a workspace.
|
|
569
|
+
switch (kind) {
|
|
530
570
|
case "ping": return { ok: true, result: {} };
|
|
531
571
|
case "discover": return { ok: true, result: await this.#capabilities() };
|
|
532
572
|
case "providers.list": return { ok: true, result: this.#seam.listProviders() };
|
|
533
573
|
case "models.list": {
|
|
534
|
-
|
|
535
|
-
try {
|
|
536
|
-
query = Validator.assertModelCatalogQuery(p);
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
if (error instanceof InvalidModelCatalogQueryError) {
|
|
540
|
-
return actionFailure("invalid-action-parameters", "models.list received an invalid catalog query.", 400, {
|
|
541
|
-
recovery: "Use provider?, search?, availability?, offset?, and limit? within the advertised bounds.",
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
throw error;
|
|
545
|
-
}
|
|
546
|
-
return { ok: true, result: this.#seam.listModels(query) };
|
|
574
|
+
return { ok: true, result: this.#seam.listModels(Validator.assertModelCatalogQuery(p)) };
|
|
547
575
|
}
|
|
548
576
|
case "workspace.list": return { ok: true, result: { workspaces: await this.#seam.listWorkspaces() } };
|
|
549
577
|
case "workspace.create": {
|
|
@@ -587,35 +615,14 @@ class Module {
|
|
|
587
615
|
return { ok: true, result: { id: att.workspaceId, name: att.workspaceName, workerId: att.workerId } };
|
|
588
616
|
}
|
|
589
617
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
result: await this.#seam.invokeModuleAction(a.kind, p, { scope: "worldless" }),
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
// Below this line lives IN a world. An unknown kind is no worker at all; a
|
|
597
|
-
// world-scoped kind with no bound workspace is a routing bug — both surface plainly.
|
|
598
|
-
if (!_a.#WORLD_SCOPED.has(a.kind) && moduleAction?.scope !== "workspace") {
|
|
599
|
-
return actionFailure("unknown-action", `Action '${a.kind}' is not registered.`, 404, {
|
|
600
|
-
requestedAction: a.kind,
|
|
601
|
-
recovery: "Use an action advertised by discover.",
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
if (env === null)
|
|
605
|
-
throw new Error(`action '${a.kind}' operates within a workspace, but none is bound`);
|
|
606
|
-
if (moduleAction?.scope === "workspace") {
|
|
607
|
-
return {
|
|
608
|
-
ok: true,
|
|
609
|
-
result: await this.#seam.invokeModuleAction(a.kind, p, { scope: "workspace", workspaceId: env.workspaceId }),
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
switch (a.kind) {
|
|
613
|
-
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) } };
|
|
614
621
|
case "log.read": {
|
|
615
622
|
// Default worker: the conversation; p.workerId pins another.
|
|
616
|
-
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);
|
|
617
624
|
const entries = await this.#seam.readLog({
|
|
618
|
-
workspaceId:
|
|
625
|
+
workspaceId: world.workspaceId,
|
|
619
626
|
workerId: readWorkerId,
|
|
620
627
|
...(Object.hasOwn(p, "limit") ? { limit: p.limit } : {}),
|
|
621
628
|
...(Object.hasOwn(p, "sinceId") ? { sinceId: p.sinceId } : {}),
|
|
@@ -631,38 +638,38 @@ class Module {
|
|
|
631
638
|
if (typeof p.prompt !== "string" || p.prompt.length === 0) {
|
|
632
639
|
return actionFailure("invalid-action-parameters", "loop.inject requires a non-empty prompt.", 400, { field: "prompt", recovery: "Provide the prompt to inject." });
|
|
633
640
|
}
|
|
634
|
-
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 });
|
|
635
642
|
return operationOutcome(ack);
|
|
636
643
|
}
|
|
637
644
|
// The stop button (TUI /stop + Ctrl-C, nvim :PlurnkStop): abort the model
|
|
638
645
|
// worker's active drain. Mirrors the SSE-hangup abort, addressable as a verb.
|
|
639
|
-
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) } };
|
|
640
647
|
case "workspace.prompts": return {
|
|
641
648
|
ok: true,
|
|
642
649
|
result: {
|
|
643
|
-
prompts: await this.#seam.listPrompts(
|
|
650
|
+
prompts: await this.#seam.listPrompts(world.workspaceId, Object.hasOwn(p, "limit") ? p.limit : undefined),
|
|
644
651
|
},
|
|
645
652
|
};
|
|
646
653
|
case "workspace.rename": {
|
|
647
654
|
if (typeof p.name !== "string" || p.name.length === 0) {
|
|
648
655
|
return actionFailure("invalid-action-parameters", "workspace.rename requires a non-empty name.", 400, { field: "name", recovery: "Provide the new workspace name." });
|
|
649
656
|
}
|
|
650
|
-
return { ok: true, result: await this.#seam.renameWorkspace(
|
|
657
|
+
return { ok: true, result: await this.#seam.renameWorkspace(world.workspaceId, p.name) };
|
|
651
658
|
}
|
|
652
659
|
case "workspace.constrain": {
|
|
653
660
|
if (typeof p.effect !== "string" || typeof p.glob !== "string") {
|
|
654
661
|
return actionFailure("invalid-action-parameters", "workspace.constrain requires string effect and glob parameters.", 400, { fields: ["effect", "glob"], recovery: "Provide both constraint parameters." });
|
|
655
662
|
}
|
|
656
|
-
return { ok: true, result: await this.#seam.constrain(
|
|
663
|
+
return { ok: true, result: await this.#seam.constrain(world.workspaceId, p.effect, p.glob) };
|
|
657
664
|
}
|
|
658
665
|
case "workspace.unconstrain": {
|
|
659
666
|
if (typeof p.effect !== "string" || typeof p.glob !== "string") {
|
|
660
667
|
return actionFailure("invalid-action-parameters", "workspace.unconstrain requires string effect and glob parameters.", 400, { fields: ["effect", "glob"], recovery: "Provide both constraint parameters." });
|
|
661
668
|
}
|
|
662
|
-
return { ok: true, result: await this.#seam.unconstrain(
|
|
669
|
+
return { ok: true, result: await this.#seam.unconstrain(world.workspaceId, p.effect, p.glob) };
|
|
663
670
|
}
|
|
664
|
-
case "workspace.constraints": return { ok: true, result: { constraints: await this.#seam.listConstraints(
|
|
665
|
-
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) } };
|
|
666
673
|
case "entry.read": {
|
|
667
674
|
if (typeof p.target !== "string") {
|
|
668
675
|
return actionFailure("invalid-action-parameters", "entry.read requires a string target.", 400, { field: "target", recovery: "Provide an entry URI." });
|
|
@@ -672,10 +679,10 @@ class Module {
|
|
|
672
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." });
|
|
673
680
|
}
|
|
674
681
|
const result = Validator.assertEntryReadResult(await this.#seam.readEntry({
|
|
675
|
-
workspaceId:
|
|
682
|
+
workspaceId: world.workspaceId,
|
|
676
683
|
workerId: typeof p.workerId === "number"
|
|
677
684
|
? p.workerId
|
|
678
|
-
: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
685
|
+
: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
679
686
|
target: p.target,
|
|
680
687
|
...(Object.hasOwn(p, "channel") ? { channel: p.channel } : {}),
|
|
681
688
|
...(Object.hasOwn(p, "offset") ? { offset: p.offset } : {}),
|
|
@@ -693,8 +700,14 @@ class Module {
|
|
|
693
700
|
lineMarker: null, body: p.command, position: UNKNOWN_POSITION,
|
|
694
701
|
};
|
|
695
702
|
// Client ops journal as client-origin turns in the client worker (worker split:
|
|
696
|
-
// only LOOPS live in the model worker)
|
|
697
|
-
|
|
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
|
+
});
|
|
698
711
|
if (result === undefined)
|
|
699
712
|
throw new Error("op.exec dispatch returned no operation result");
|
|
700
713
|
return operationOutcome(result);
|
|
@@ -735,7 +748,12 @@ class Module {
|
|
|
735
748
|
}
|
|
736
749
|
const dispatched = statements.length === 0
|
|
737
750
|
? []
|
|
738
|
-
: 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
|
+
});
|
|
739
757
|
let index = 0;
|
|
740
758
|
for (let i = 0; i < results.length; i++) {
|
|
741
759
|
if (results[i] === null)
|
|
@@ -743,7 +761,7 @@ class Module {
|
|
|
743
761
|
}
|
|
744
762
|
return { ok: true, result: { results } };
|
|
745
763
|
}
|
|
746
|
-
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) };
|
|
747
765
|
case "op.look": {
|
|
748
766
|
// {§agui-op-look}
|
|
749
767
|
if (typeof p.text !== "string" || p.text.length === 0) {
|
|
@@ -787,13 +805,18 @@ class Module {
|
|
|
787
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." });
|
|
788
806
|
}
|
|
789
807
|
const statement = { ...item.statement, op: "READ" };
|
|
790
|
-
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
|
+
}));
|
|
791
814
|
}
|
|
792
|
-
case "run.fork": return { ok: true, result: await this.#seam.forkWorker({ workspaceId:
|
|
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 } : {}) }) };
|
|
793
816
|
case "worker.model.get": {
|
|
794
817
|
const { model, spawnModel } = await this.#seam.readWorkerModel({
|
|
795
|
-
workspaceId:
|
|
796
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
818
|
+
workspaceId: world.workspaceId,
|
|
819
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
797
820
|
});
|
|
798
821
|
return { ok: true, result: { model, spawnModel } };
|
|
799
822
|
}
|
|
@@ -802,8 +825,8 @@ class Module {
|
|
|
802
825
|
return actionFailure("invalid-action-parameters", "worker.model.set requires a selector.", 400, { recovery: "Provide a declared alias or provider/model route." });
|
|
803
826
|
}
|
|
804
827
|
return { ok: true, result: await this.#seam.setWorkerModel({
|
|
805
|
-
workspaceId:
|
|
806
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
828
|
+
workspaceId: world.workspaceId,
|
|
829
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
807
830
|
selector: p.selector,
|
|
808
831
|
}) };
|
|
809
832
|
}
|
|
@@ -813,15 +836,15 @@ class Module {
|
|
|
813
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." });
|
|
814
837
|
}
|
|
815
838
|
return { ok: true, result: await this.#seam.setWorkerSpawnModel({
|
|
816
|
-
workspaceId:
|
|
817
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
839
|
+
workspaceId: world.workspaceId,
|
|
840
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
818
841
|
selector: p.selector,
|
|
819
842
|
}) };
|
|
820
843
|
}
|
|
821
844
|
case "worker.reasoning.get": {
|
|
822
845
|
return { ok: true, result: await this.#seam.readWorkerReasoning({
|
|
823
|
-
workspaceId:
|
|
824
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
846
|
+
workspaceId: world.workspaceId,
|
|
847
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
825
848
|
}) };
|
|
826
849
|
}
|
|
827
850
|
case "worker.reasoning.set": {
|
|
@@ -829,15 +852,15 @@ class Module {
|
|
|
829
852
|
return actionFailure("invalid-action-parameters", "worker.reasoning.set requires a policy.", 400, { recovery: "Provide a reasoning policy." });
|
|
830
853
|
}
|
|
831
854
|
return { ok: true, result: await this.#seam.setWorkerReasoning({
|
|
832
|
-
workspaceId:
|
|
833
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
855
|
+
workspaceId: world.workspaceId,
|
|
856
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
834
857
|
policy: p.policy,
|
|
835
858
|
}) };
|
|
836
859
|
}
|
|
837
860
|
case "worker.settings.get": {
|
|
838
861
|
return { ok: true, result: await this.#seam.readWorkerSettings({
|
|
839
|
-
workspaceId:
|
|
840
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
862
|
+
workspaceId: world.workspaceId,
|
|
863
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
841
864
|
}) };
|
|
842
865
|
}
|
|
843
866
|
case "worker.settings.set": {
|
|
@@ -845,22 +868,19 @@ class Module {
|
|
|
845
868
|
return actionFailure("invalid-action-parameters", "worker.settings.set requires a settings object.", 400, { recovery: "Provide a settings object, e.g. { requestUserInput: true }." });
|
|
846
869
|
}
|
|
847
870
|
return { ok: true, result: await this.#seam.setWorkerSettings({
|
|
848
|
-
workspaceId:
|
|
849
|
-
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(
|
|
871
|
+
workspaceId: world.workspaceId,
|
|
872
|
+
workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
|
|
850
873
|
settings: p.settings,
|
|
851
874
|
}) };
|
|
852
875
|
}
|
|
853
|
-
default:
|
|
854
|
-
requestedAction: a.kind,
|
|
855
|
-
recovery: "Use an action advertised by discover.",
|
|
856
|
-
});
|
|
876
|
+
default: throw new Error(`AG-UI built-in '${kind}' has no executor`);
|
|
857
877
|
}
|
|
858
878
|
}
|
|
859
879
|
catch (err) {
|
|
860
880
|
const problem = problemFromError(err);
|
|
861
881
|
if (problem !== null)
|
|
862
882
|
return { ok: false, problem };
|
|
863
|
-
console.error(`AG-UI action '${
|
|
883
|
+
console.error(`AG-UI action '${kind}' failed:`, err);
|
|
864
884
|
return actionFailure("action-failed", "The action failed unexpectedly.", 500);
|
|
865
885
|
}
|
|
866
886
|
}
|