@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/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 CoreSeam handle; it opens
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 { InvalidModelCatalogQueryError, 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,53 +95,62 @@ class Module {
95
95
  #http;
96
96
  #threads = new Map(); // threadId → envelope
97
97
  #threadWorkers = new Map(); // threadId → conversation workerId
98
- static #CONTROL_ACTIONS = Object.freeze([
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.#moduleActions();
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
- #moduleActions() {
128
- const actions = new Map();
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 (_a.#BUILTIN_ACTIONS.has(name)) {
132
- throw new Error(`module action '${name}' collides with AG-UI built-in action`);
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
- if (actions.has(name))
138
- throw new Error(`module action '${name}' is registered more than once`);
139
- actions.set(name, descriptor);
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
- #isWorkspaceAction(kind) {
144
- return _a.#WORLD_SCOPED.has(kind) || this.#moduleActions().get(kind)?.scope === "workspace";
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.#isWorkspaceAction(action.kind)) {
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
- // The capability manifest a client probes (`discover`) for exact action/event membership.
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 methods = {};
505
- for (const k of [
506
- ..._a.#CONTROL_ACTIONS,
507
- ..._a.#WORLD_SCOPED,
508
- ...this.#moduleActions().keys(),
509
- ]) {
510
- if (methods[k])
511
- throw new Error(`AG-UI action '${k}' is registered more than once`);
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 { methods, notifications, display };
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 p = a.params;
526
- const moduleAction = this.#moduleActions().get(a.kind);
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
- // The control plane — worldless verbs (no bound workspace; #WORLD_SCOPED gates this).
529
- switch (a.kind) {
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
- let query;
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
- if (moduleAction?.scope === "worldless") {
591
- return {
592
- ok: true,
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(env.workspaceId);
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: env.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: env.workspaceId, workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId), prompt: p.prompt });
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(env.workspaceId)) } };
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(env.workspaceId, Object.hasOwn(p, "limit") ? p.limit : undefined),
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(env.workspaceId, p.name) };
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(env.workspaceId, p.effect, p.glob) };
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(env.workspaceId, p.effect, p.glob) };
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(env.workspaceId) } };
665
- case "workspace.derivation": return { ok: true, result: { status: this.#seam.workspaceDerivationStatus(env.workspaceId) } };
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: env.workspaceId,
682
+ workspaceId: world.workspaceId,
676
683
  workerId: typeof p.workerId === "number"
677
684
  ? p.workerId
678
- : conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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
- const [result] = await this.#seam.dispatchClientAction({ workspaceId: env.workspaceId, workerId: env.workerId, statements: [statement] });
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({ workspaceId: env.workspaceId, workerId: env.workerId, statements });
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(env.workspaceId) };
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({ workspaceId: env.workspaceId, workerId: env.workerId, statement }));
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: env.workspaceId, workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId), ...(typeof p.name === "string" ? { name: p.name } : {}) }) };
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: env.workspaceId,
796
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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: env.workspaceId,
806
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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: env.workspaceId,
817
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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: env.workspaceId,
824
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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: env.workspaceId,
833
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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: env.workspaceId,
840
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
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: env.workspaceId,
849
- workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(env.workspaceId),
871
+ workspaceId: world.workspaceId,
872
+ workerId: conversationWorkerId ?? await this.#seam.ensureModelWorker(world.workspaceId),
850
873
  settings: p.settings,
851
874
  }) };
852
875
  }
853
- default: return actionFailure("unknown-action", `Action '${a.kind}' is not registered.`, 404, {
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 '${a.kind}' failed:`, err);
883
+ console.error(`AG-UI action '${kind}' failed:`, err);
864
884
  return actionFailure("action-failed", "The action failed unexpectedly.", 500);
865
885
  }
866
886
  }