@loomweaver/cli 0.14.3 → 0.14.5

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.
Files changed (2) hide show
  1. package/dist/main.mjs +66 -84
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -638,9 +638,9 @@ function renderRegions(indent) {
638
638
  // ../devkit/src/recipes/angular-weaver/agent-panel.ts
639
639
  function panelFile(weaver) {
640
640
  return `import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
641
- import { EventType, type BaseEvent, type Tool, type ToolMessage } from '@ag-ui/core';
642
- import { ${weaver.propertyName}Agent } from './${weaver.id}-agent';
643
- import { askAgent } from './${weaver.id}-agent-source';
641
+ import { EventType, type AGUIEvent, type Tool, type ToolMessage } from '@ag-ui/core';
642
+ import { askAgent } from './${weaver.id}-agent';
643
+ import { ${weaver.propertyName}Tools } from './${weaver.id}-connection';
644
644
 
645
645
  interface Line {
646
646
  readonly kind: 'you' | 'agent' | 'call' | 'result' | 'note';
@@ -670,11 +670,11 @@ export class ${weaver.className}AgentPanel {
670
670
  // The list is read again whenever the panel gains focus, because the weaver's own translations
671
671
  // and other plugins' commands may arrive after this component was built.
672
672
  protected refreshOffered(): void {
673
- this.offered.set(${weaver.propertyName}Agent()?.list() ?? []);
673
+ this.offered.set(${weaver.propertyName}Tools()?.list() ?? []);
674
674
  }
675
675
 
676
676
  protected async ask(name: string): Promise<void> {
677
- const tools = ${weaver.propertyName}Agent();
677
+ const tools = ${weaver.propertyName}Tools();
678
678
  if (!tools || this.busy()) {
679
679
  return;
680
680
  }
@@ -717,24 +717,19 @@ export class ${weaver.className}AgentPanel {
717
717
  }
718
718
  }
719
719
 
720
- private draw(event: BaseEvent): void {
721
- const raw = event as unknown as Record<string, unknown>;
720
+ private draw(event: AGUIEvent): void {
722
721
  switch (event.type) {
723
722
  case EventType.TEXT_MESSAGE_START:
724
723
  this.push({ kind: 'agent', text: '' });
725
724
  break;
726
725
  case EventType.TEXT_MESSAGE_CONTENT:
727
- this.grow('text', String(raw['delta'] ?? ''));
726
+ this.grow('text', event.delta);
728
727
  break;
729
728
  case EventType.TOOL_CALL_START:
730
- this.push({
731
- kind: 'call',
732
- text: String(raw['toolCallName'] ?? ''),
733
- args: '',
734
- });
729
+ this.push({ kind: 'call', text: event.toolCallName, args: '' });
735
730
  break;
736
731
  case EventType.TOOL_CALL_ARGS:
737
- this.grow('args', String(raw['delta'] ?? ''));
732
+ this.grow('args', event.delta);
738
733
  break;
739
734
  default:
740
735
  break;
@@ -763,7 +758,7 @@ function panelTemplateFile(weaver) {
763
758
  class="shrink-0 rounded-md border border-border bg-surface-raised px-3 py-2 text-xs text-content-muted"
764
759
  >
765
760
  This is a stand-in, not an assistant. It speaks the protocol so you can watch the whole path;
766
- replace <code class="text-content">${weaver.id}-agent-source.ts</code> with your own transport.
761
+ replace <code class="text-content">${weaver.id}-agent.ts</code> with your own transport.
767
762
  </p>
768
763
 
769
764
  <div class="min-h-0 flex-1 overflow-auto">
@@ -831,7 +826,7 @@ function panelTemplateFile(weaver) {
831
826
 
832
827
  // ../devkit/src/recipes/angular-weaver/agent-stand-in.ts
833
828
  function standInFile(weaver) {
834
- return `import { EventType, type BaseEvent, type Tool } from '@ag-ui/core';
829
+ return `import { EventType, type AGUIEvent, type Tool } from '@ag-ui/core';
835
830
 
836
831
  // WHAT THIS IS: a stand-in for an agent, and not an agent. It speaks the AG-UI protocol and nothing
837
832
  // else \u2014 no model, no network, no judgement \u2014 so that the whole path from the offered tools through
@@ -851,15 +846,16 @@ export interface AgentRequest {
851
846
 
852
847
  export async function* askAgent(
853
848
  request: AgentRequest,
854
- ): AsyncGenerator<BaseEvent> {
849
+ ): AsyncGenerator<AGUIEvent> {
855
850
  const picked = request.tools.find((tool) =>
856
851
  request.prompt.includes(tool.name),
857
852
  );
858
853
 
859
- yield event(EventType.RUN_STARTED, {
854
+ yield {
855
+ type: EventType.RUN_STARTED,
860
856
  threadId: '${weaver.id}-stand-in',
861
857
  runId: request.runId,
862
- });
858
+ };
863
859
  yield* speak(
864
860
  \`\${request.runId}.says\`,
865
861
  picked
@@ -869,37 +865,35 @@ export async function* askAgent(
869
865
 
870
866
  if (picked) {
871
867
  const toolCallId = \`\${request.runId}.call\`;
872
- yield event(EventType.TOOL_CALL_START, {
873
- toolCallId,
874
- toolCallName: picked.name,
875
- });
868
+ yield { type: EventType.TOOL_CALL_START, toolCallId, toolCallName: picked.name };
876
869
  // Arguments arrive in pieces, exactly as they do from a real model. The adapter assembles them;
877
870
  // nothing downstream ever sees a half-written call. The values come from the JSON Schema the
878
871
  // workbench described in picked.parameters: the first declared choice of the first argument that
879
872
  // has choices, and nothing for the rest, so the command applies its own defaults there.
880
873
  for (const piece of chunks(JSON.stringify(sampleArguments(picked)), 4)) {
881
- yield event(EventType.TOOL_CALL_ARGS, { toolCallId, delta: piece });
874
+ yield { type: EventType.TOOL_CALL_ARGS, toolCallId, delta: piece };
882
875
  await pause();
883
876
  }
884
- yield event(EventType.TOOL_CALL_END, { toolCallId });
877
+ yield { type: EventType.TOOL_CALL_END, toolCallId };
885
878
  }
886
879
 
887
- yield event(EventType.RUN_FINISHED, {
880
+ yield {
881
+ type: EventType.RUN_FINISHED,
888
882
  threadId: '${weaver.id}-stand-in',
889
883
  runId: request.runId,
890
- });
884
+ };
891
885
  }
892
886
 
893
887
  async function* speak(
894
888
  messageId: string,
895
889
  text: string,
896
- ): AsyncGenerator<BaseEvent> {
897
- yield event(EventType.TEXT_MESSAGE_START, { messageId, role: 'assistant' });
890
+ ): AsyncGenerator<AGUIEvent> {
891
+ yield { type: EventType.TEXT_MESSAGE_START, messageId, role: 'assistant' };
898
892
  for (const piece of chunks(text, 10)) {
899
- yield event(EventType.TEXT_MESSAGE_CONTENT, { messageId, delta: piece });
893
+ yield { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: piece };
900
894
  await pause();
901
895
  }
902
- yield event(EventType.TEXT_MESSAGE_END, { messageId });
896
+ yield { type: EventType.TEXT_MESSAGE_END, messageId };
903
897
  }
904
898
 
905
899
  function sampleArguments(tool: Tool): Record<string, unknown> {
@@ -922,10 +916,6 @@ function chunks(text: string, size: number): readonly string[] {
922
916
  return pieces;
923
917
  }
924
918
 
925
- function event(type: EventType, fields: Record<string, unknown>): BaseEvent {
926
- return { type, ...fields } as unknown as BaseEvent;
927
- }
928
-
929
919
  function pause(): Promise<void> {
930
920
  return new Promise((done) => setTimeout(done, PAUSE_MS));
931
921
  }
@@ -944,15 +934,18 @@ import {
944
934
  } from '@loomweaver/ag-ui';
945
935
  import type { PluginContext } from '@loomweaver/plugin-sdk';
946
936
 
937
+ // The connection is the workbench's half: the commands offered as tools, and each call put to the
938
+ // workbench and answered. The agent is yours and lives beside it, in ${weaver.id}-agent.ts.
939
+ //
947
940
  // A factory, not a module-level connection: everything a run needs lives in the closure, so a second
948
941
  // one never shares state with the first.
949
- export function ${weaver.propertyName}Connection(ctx: PluginContext): CommandTools {
942
+ export function connect${weaver.className}(ctx: PluginContext): CommandTools {
950
943
  return commandTools(ctx, { before: (call) => decide(ctx, call) });
951
944
  }
952
945
 
953
946
  // The one connection this plugin activates, published for its panel. Set in activate(), cleared in
954
947
  // deactivate(), so the panel renders an honest empty state either side of that.
955
- export const ${weaver.propertyName}Agent = signal<CommandTools | null>(null);
948
+ export const ${weaver.propertyName}Tools = signal<CommandTools | null>(null);
956
949
 
957
950
  // What an agent's word is enough for is the command's own statement, declared where the command is
958
951
  // registered and read off the call here. No list of ids lives beside the commands: a list drifts from
@@ -987,9 +980,10 @@ async function decide(
987
980
  `;
988
981
  }
989
982
  function specFile(weaver) {
990
- return `import { EventType, type BaseEvent } from '@ag-ui/core';
983
+ return `import { EventType, type AGUIEvent, type ToolMessage } from '@ag-ui/core';
984
+ import type { CommandTools } from '@loomweaver/ag-ui';
991
985
  import type { CommandArguments, PluginContext } from '@loomweaver/plugin-sdk';
992
- import { ${weaver.propertyName}Connection } from './${weaver.id}-agent';
986
+ import { connect${weaver.className} } from './${weaver.id}-connection';
993
987
 
994
988
  interface Asked {
995
989
  readonly id: string;
@@ -1010,37 +1004,36 @@ function contextThat(confirms: boolean, ran: Asked[]): PluginContext {
1010
1004
  } as unknown as PluginContext;
1011
1005
  }
1012
1006
 
1013
- function event(type: EventType, fields: Record<string, unknown>): BaseEvent {
1014
- return { type, ...fields } as unknown as BaseEvent;
1007
+ function streamedCall(toolCallId: string, ...deltas: string[]): AGUIEvent[] {
1008
+ return [
1009
+ { type: EventType.TOOL_CALL_START, toolCallId, toolCallName: '${weaver.id}.hello' },
1010
+ ...deltas.map((delta): AGUIEvent => ({ type: EventType.TOOL_CALL_ARGS, toolCallId, delta })),
1011
+ { type: EventType.TOOL_CALL_END, toolCallId },
1012
+ ];
1015
1013
  }
1016
1014
 
1017
- describe('${weaver.propertyName}Connection', () => {
1015
+ async function answersTo(tools: CommandTools, events: AGUIEvent[]): Promise<ToolMessage[]> {
1016
+ const answers: ToolMessage[] = [];
1017
+ for (const event of events) {
1018
+ const answer = await tools.receive(event);
1019
+ if (answer) {
1020
+ answers.push(answer);
1021
+ }
1022
+ }
1023
+ return answers;
1024
+ }
1025
+
1026
+ describe('connect${weaver.className}', () => {
1018
1027
  it('offers what the workbench offers', () => {
1019
- const tools = ${weaver.propertyName}Connection(contextThat(true, []));
1028
+ const tools = connect${weaver.className}(contextThat(true, []));
1020
1029
  expect(tools.list().map((tool) => tool.name)).toEqual(['${weaver.id}.hello']);
1021
1030
  });
1022
1031
 
1023
1032
  it('assembles a call from its events and answers with the outcome', async () => {
1024
1033
  const ran: Asked[] = [];
1025
- const tools = ${weaver.propertyName}Connection(contextThat(true, ran));
1034
+ const tools = connect${weaver.className}(contextThat(true, ran));
1026
1035
 
1027
- expect(
1028
- await tools.receive(
1029
- event(EventType.TOOL_CALL_START, {
1030
- toolCallId: 'c1',
1031
- toolCallName: '${weaver.id}.hello',
1032
- }),
1033
- ),
1034
- ).toBeNull();
1035
- await tools.receive(
1036
- event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: '{"tone"' }),
1037
- );
1038
- await tools.receive(
1039
- event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: ':"success"}' }),
1040
- );
1041
- const answer = await tools.receive(
1042
- event(EventType.TOOL_CALL_END, { toolCallId: 'c1' }),
1043
- );
1036
+ const [answer] = await answersTo(tools, streamedCall('c1', '{"tone"', ':"success"}'));
1044
1037
 
1045
1038
  expect(ran).toEqual([{ id: '${weaver.id}.hello', args: { tone: 'success' } }]);
1046
1039
  expect(JSON.parse(answer?.content ?? '{}').tone).toBe('success');
@@ -1049,20 +1042,9 @@ describe('${weaver.propertyName}Connection', () => {
1049
1042
 
1050
1043
  it('never reaches the workbench when a consequential call is declined', async () => {
1051
1044
  const ran: Asked[] = [];
1052
- const tools = ${weaver.propertyName}Connection(contextThat(false, ran));
1045
+ const tools = connect${weaver.className}(contextThat(false, ran));
1053
1046
 
1054
- await tools.receive(
1055
- event(EventType.TOOL_CALL_START, {
1056
- toolCallId: 'c2',
1057
- toolCallName: '${weaver.id}.hello',
1058
- }),
1059
- );
1060
- await tools.receive(
1061
- event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c2', delta: '{}' }),
1062
- );
1063
- const answer = await tools.receive(
1064
- event(EventType.TOOL_CALL_END, { toolCallId: 'c2' }),
1065
- );
1047
+ const [answer] = await answersTo(tools, streamedCall('c2', '{}'));
1066
1048
 
1067
1049
  expect(ran).toEqual([]);
1068
1050
  expect(answer?.error).toContain('did not run');
@@ -1084,13 +1066,13 @@ function agentSurfaceBlock(weaver) {
1084
1066
  }
1085
1067
  function agentFiles(weaver) {
1086
1068
  const files = {
1087
- [`src/lib/agent/${weaver.id}-agent.ts`]: connectionFile(weaver),
1088
- [`src/lib/agent/${weaver.id}-agent-source.ts`]: standInFile(weaver),
1069
+ [`src/lib/agent/${weaver.id}-connection.ts`]: connectionFile(weaver),
1070
+ [`src/lib/agent/${weaver.id}-agent.ts`]: standInFile(weaver),
1089
1071
  [`src/lib/agent/${weaver.id}-agent-panel.ts`]: panelFile(weaver),
1090
1072
  [`src/lib/agent/${weaver.id}-agent-panel.html`]: panelTemplateFile(weaver)
1091
1073
  };
1092
1074
  if (weaver.features.spec) {
1093
- files[`src/lib/agent/${weaver.id}-agent.spec.ts`] = specFile(weaver);
1075
+ files[`src/lib/agent/${weaver.id}-connection.spec.ts`] = specFile(weaver);
1094
1076
  }
1095
1077
  return files;
1096
1078
  }
@@ -1551,7 +1533,7 @@ function pluginImports(weaver) {
1551
1533
  }
1552
1534
  if (weaver.features.agent) {
1553
1535
  imports.push(
1554
- `import { ${weaver.propertyName}Agent, ${weaver.propertyName}Connection } from '../agent/${weaver.id}-agent';`,
1536
+ `import { connect${weaver.className}, ${weaver.propertyName}Tools } from '../agent/${weaver.id}-connection';`,
1555
1537
  `import { ${weaver.className}AgentPanel } from '../agent/${weaver.id}-agent-panel';`
1556
1538
  );
1557
1539
  }
@@ -1576,7 +1558,7 @@ function pluginBody(weaver) {
1576
1558
  const body = [` ctx.contributeIcons({ '${weaver.id}': icon });`];
1577
1559
  if (weaver.features.agent) {
1578
1560
  body.push(
1579
- ` ${weaver.propertyName}Agent.set(${weaver.propertyName}Connection(ctx));`
1561
+ ` ${weaver.propertyName}Tools.set(connect${weaver.className}(ctx));`
1580
1562
  );
1581
1563
  }
1582
1564
  if (weaver.features.command) body.push(commandBlock(weaver));
@@ -1593,7 +1575,7 @@ function pluginFile(weaver) {
1593
1575
  const body = pluginBody(weaver);
1594
1576
  const deactivate = weaver.features.agent ? `
1595
1577
  deactivate() {
1596
- ${weaver.propertyName}Agent.set(null);
1578
+ ${weaver.propertyName}Tools.set(null);
1597
1579
  },` : "";
1598
1580
  return `${pluginImports(weaver).join("\n")}
1599
1581
 
@@ -1616,7 +1598,7 @@ function accessLine(weaver) {
1616
1598
  }
1617
1599
 
1618
1600
  // ../devkit/src/recipes/platform-version.ts
1619
- var PLATFORM_VERSION = "0.14.3";
1601
+ var PLATFORM_VERSION = "0.14.5";
1620
1602
 
1621
1603
  // ../devkit/src/recipes/readme-notes.ts
1622
1604
  var UNTAGGED_NOTE = [
@@ -1690,11 +1672,11 @@ function agentNotes(weaver) {
1690
1672
  "",
1691
1673
  `\`src/lib/agent/\` holds three files and one of them is meant to be thrown away.`,
1692
1674
  "",
1693
- `- \`${weaver.id}-agent.ts\` is the connection: the workbench's own commands offered as tools, and a`,
1675
+ `- \`${weaver.id}-connection.ts\` is the connection: the workbench's own commands offered as tools, and a`,
1694
1676
  " seam where this weaver decides about a call before it runs. Nothing is registered twice \u2014 the",
1695
1677
  " list comes from the workbench, already narrowed by everything that would refuse the call.",
1696
1678
  `- \`${weaver.id}-agent-panel.ts\` shows what is offered, the call as it streams, and the outcome.`,
1697
- `- \`${weaver.id}-agent-source.ts\` is a **stand-in**, not an assistant: it produces the protocol's own`,
1679
+ `- \`${weaver.id}-agent.ts\` is a **stand-in** for the agent, not an assistant: it produces the protocol's own`,
1698
1680
  " events so the whole path runs before you have connected anything. Replace that one file with",
1699
1681
  " your transport and nothing else changes. No transport, credential or model is generated for",
1700
1682
  " you, because none of them can be guessed.",
@@ -5217,7 +5199,7 @@ function validateCatalogCommand(args, io) {
5217
5199
  }
5218
5200
 
5219
5201
  // src/lib/run.ts
5220
- var VERSION = "0.14.3";
5202
+ var VERSION = "0.14.5";
5221
5203
  var COMMANDS = /* @__PURE__ */ new Map([
5222
5204
  ["init", init],
5223
5205
  ["list", list],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/cli",
3
- "version": "0.14.3",
3
+ "version": "0.14.5",
4
4
  "description": "LoomWeaver: open-source plugin platform for Angular workbenches. The scaffolding CLI: generates weavers, distributions and integrations into any project, with no Nx workspace and no checkout required.",
5
5
  "keywords": [
6
6
  "loomweaver",