@loomweaver/cli 0.14.3 → 0.14.4
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/main.mjs +66 -84
- 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
|
|
642
|
-
import {
|
|
643
|
-
import {
|
|
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}
|
|
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}
|
|
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:
|
|
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',
|
|
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',
|
|
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
|
|
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
|
|
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<
|
|
849
|
+
): AsyncGenerator<AGUIEvent> {
|
|
855
850
|
const picked = request.tools.find((tool) =>
|
|
856
851
|
request.prompt.includes(tool.name),
|
|
857
852
|
);
|
|
858
853
|
|
|
859
|
-
yield
|
|
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
|
|
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
|
|
874
|
+
yield { type: EventType.TOOL_CALL_ARGS, toolCallId, delta: piece };
|
|
882
875
|
await pause();
|
|
883
876
|
}
|
|
884
|
-
yield
|
|
877
|
+
yield { type: EventType.TOOL_CALL_END, toolCallId };
|
|
885
878
|
}
|
|
886
879
|
|
|
887
|
-
yield
|
|
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<
|
|
897
|
-
yield
|
|
890
|
+
): AsyncGenerator<AGUIEvent> {
|
|
891
|
+
yield { type: EventType.TEXT_MESSAGE_START, messageId, role: 'assistant' };
|
|
898
892
|
for (const piece of chunks(text, 10)) {
|
|
899
|
-
yield
|
|
893
|
+
yield { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: piece };
|
|
900
894
|
await pause();
|
|
901
895
|
}
|
|
902
|
-
yield
|
|
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.
|
|
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}
|
|
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
|
|
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.
|
|
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
|
|
1014
|
-
return
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1034
|
+
const tools = connect${weaver.className}(contextThat(true, ran));
|
|
1026
1035
|
|
|
1027
|
-
|
|
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.
|
|
1045
|
+
const tools = connect${weaver.className}(contextThat(false, ran));
|
|
1053
1046
|
|
|
1054
|
-
await tools
|
|
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}-
|
|
1088
|
-
[`src/lib/agent/${weaver.id}-agent
|
|
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}-
|
|
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.
|
|
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}
|
|
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}
|
|
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.
|
|
1601
|
+
var PLATFORM_VERSION = "0.14.4";
|
|
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}-
|
|
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
|
|
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.
|
|
5202
|
+
var VERSION = "0.14.4";
|
|
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
|
+
"version": "0.14.4",
|
|
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",
|