@loomweaver/mcp 0.11.0 → 0.12.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.
Files changed (2) hide show
  1. package/dist/main.mjs +65 -12
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -31458,7 +31458,7 @@ function pause(): Promise<void> {
31458
31458
  }
31459
31459
 
31460
31460
  // ../devkit/src/recipes/angular-weaver/agent-files.ts
31461
- var AG_UI_ADAPTER_VERSION = "0.11.0";
31461
+ var AG_UI_ADAPTER_VERSION = "0.12.0";
31462
31462
  var AG_UI_PROTOCOL_VERSION = "0.0.x";
31463
31463
  function connectionFile(w) {
31464
31464
  return `import { signal } from '@angular/core';
@@ -31470,11 +31470,6 @@ import {
31470
31470
  } from '@loomweaver/ag-ui';
31471
31471
  import type { PluginContext } from '@loomweaver/plugin-sdk';
31472
31472
 
31473
- // Commands an agent may not run on its own word. Each one asks the person at the keyboard first, and
31474
- // declining stops it: the workbench never sees the call. This weaver's own command is listed as an
31475
- // example \u2014 replace it with the ones that actually cost something.
31476
- const CONSEQUENTIAL = new Set(['${w.id}.hello']);
31477
-
31478
31473
  // A factory, not a module-level connection: everything a run needs lives in the closure, so a second
31479
31474
  // one never shares state with the first.
31480
31475
  export function ${w.propertyName}Connection(ctx: PluginContext): CommandTools {
@@ -31485,11 +31480,21 @@ export function ${w.propertyName}Connection(ctx: PluginContext): CommandTools {
31485
31480
  // deactivate(), so the panel renders an honest empty state either side of that.
31486
31481
  export const ${w.propertyName}Agent = signal<CommandTools | null>(null);
31487
31482
 
31483
+ // What an agent's word is enough for is the command's own statement, declared where the command is
31484
+ // registered and read off the call here. No list of ids lives beside the commands: a list drifts from
31485
+ // what it describes, and it cannot speak for a command another plugin registered. Acting on the
31486
+ // statement is this weaver's half: the workbench states it and enforces nothing.
31488
31487
  async function decide(
31489
31488
  ctx: PluginContext,
31490
31489
  call: PendingToolCall,
31491
31490
  ): Promise<ToolDecision> {
31492
- if (!CONSEQUENTIAL.has(call.commandId)) {
31491
+ if (call.agentConsent === 'never') {
31492
+ return {
31493
+ decision: 'decline',
31494
+ reason: 'an agent may not run this one on its own word.',
31495
+ };
31496
+ }
31497
+ if (call.agentConsent !== 'ask' && call.agentConsent !== 'ask-always') {
31493
31498
  return { decision: 'run' };
31494
31499
  }
31495
31500
  const yes = await ctx.ui.confirm({
@@ -31520,7 +31525,8 @@ interface Asked {
31520
31525
  function contextThat(confirms: boolean, ran: Asked[]): PluginContext {
31521
31526
  return {
31522
31527
  invocableCommands: () => [
31523
- { id: '${w.id}.hello', title: '${w.name} action', description: 'Shows a short message.' },
31528
+ // agentConsent travels with the command, which is what the connection reads off the call.
31529
+ { id: '${w.id}.hello', title: '${w.name} action', description: 'Shows a short message.', agentConsent: 'ask' },
31524
31530
  ],
31525
31531
  invokeCommand: (id: string, args?: CommandArguments) => {
31526
31532
  ran.push({ id, args });
@@ -31806,6 +31812,11 @@ function commandBlock(w) {
31806
31812
  ` answers: '${w.id}.actionAnswers',`,
31807
31813
  ` shortcut: '${w.features.shortcut}',`,
31808
31814
  " callable: true,",
31815
+ ...w.features.agent ? [
31816
+ " // What an agent's word is enough for. The platform states it and enforces nothing:",
31817
+ " // the asking is the connection's half, which reads this off the call.",
31818
+ " agentConsent: 'ask',"
31819
+ ] : [],
31809
31820
  " run: (_context, args) => {",
31810
31821
  " const tone = toneOf(args?.['tone']);",
31811
31822
  ` ctx.ui.toast({ message: '${w.id}.action', kind: tone });`,
@@ -32453,7 +32464,7 @@ var framePlugin = {
32453
32464
  };
32454
32465
 
32455
32466
  // ../devkit/src/recipes/platform-version.ts
32456
- var PLATFORM_VERSION = "0.11.0";
32467
+ var PLATFORM_VERSION = "0.12.0";
32457
32468
 
32458
32469
  // ../devkit/src/recipes/angular-distribution/amendments.ts
32459
32470
  var DISTRIBUTION_INITIAL_BUDGET = {
@@ -32712,6 +32723,10 @@ function readme2(d) {
32712
32723
  "",
32713
32724
  "A switch takes the **affordance and the gesture**: turning `splitRight` off removes the toolbar",
32714
32725
  "button, the drop edges *and* `mod+\\`, so the capability cannot come back through a second door.",
32726
+ "Two switches are finer, and named for it: `splitRightButton` and `splitDownButton` leave out the",
32727
+ "pane toolbar's split button alone, for a product that offers splitting by dragging a tab to a",
32728
+ "pane's edge. The command and the tab menu's entry are removed by naming them in",
32729
+ "`provideShell({ omit })` instead, so each decision keeps one place.",
32715
32730
  "Fields merge group by group, so name only what you turn off. The groups are `content`,",
32716
32731
  "`sidebar`, `rail`, `workspaces`, `windows` and `commands`; everything is on by default except",
32717
32732
  "`content.escalate`, the unlabelled double-click cycle on a tab.",
@@ -34193,6 +34208,7 @@ function read(ts, file2, call) {
34193
34208
  return { ...empty, unreadable: "its id is not a string literal" };
34194
34209
  }
34195
34210
  const callable = properties.get("callable");
34211
+ const consent = properties.get("agentConsent");
34196
34212
  return {
34197
34213
  at: at2,
34198
34214
  id: id.text,
@@ -34200,9 +34216,40 @@ function read(ts, file2, call) {
34200
34216
  described: properties.has("description"),
34201
34217
  answers: properties.has("answers"),
34202
34218
  returnsValue: returnsValue(ts, properties.get("run")),
34203
- arguments: readArguments(ts, properties.get("arguments"))
34219
+ arguments: readArguments(ts, properties.get("arguments")),
34220
+ ...consentOf(ts, consent)
34204
34221
  };
34205
34222
  }
34223
+ function consentOf(ts, declared) {
34224
+ const value = unwrapped(ts, declared);
34225
+ return value && ts.isStringLiteral(value) ? { agentConsent: value.text } : {};
34226
+ }
34227
+ function unwrapped(ts, value) {
34228
+ if (!value) {
34229
+ return void 0;
34230
+ }
34231
+ return ts.isAsExpression(value) || ts.isSatisfiesExpression(value) || ts.isParenthesizedExpression(value) ? unwrapped(ts, value.expression) : value;
34232
+ }
34233
+ var STATED = ["allow", "ask", "ask-always", "never"];
34234
+ function consentLine(consent) {
34235
+ switch (consent) {
34236
+ case "allow": {
34237
+ return "says an agent's word is enough to run it.";
34238
+ }
34239
+ case "ask": {
34240
+ return "says the person is asked first; whoever runs it on an agent's behalf does the asking.";
34241
+ }
34242
+ case "ask-always": {
34243
+ return "says the person is asked every time; whoever runs it on an agent's behalf does the asking.";
34244
+ }
34245
+ case "never": {
34246
+ return "says it is not to be run on an agent's word; closing it to other callers is what enforces that.";
34247
+ }
34248
+ default: {
34249
+ return consent === void 0 ? "says nothing about whether an agent's word is enough to run it." : `declares "${consent}" about an agent's word, which is none of ${STATED.join(", ")}, so nothing reads it.`;
34250
+ }
34251
+ }
34252
+ }
34206
34253
  function readArguments(ts, value) {
34207
34254
  if (!value || !ts.isArrayLiteralExpression(value)) {
34208
34255
  return [];
@@ -34306,6 +34353,12 @@ function judge(registration) {
34306
34353
  message: `${id}: offered to an agent, with ${count} described argument${count === 1 ? "" : "s"}${registration.answers ? " and a declared answer" : ""}.`
34307
34354
  });
34308
34355
  }
34356
+ findings.push({
34357
+ level: "info",
34358
+ code: "command.consent",
34359
+ path: at2,
34360
+ message: `${id}: ${consentLine(registration.agentConsent)}`
34361
+ });
34309
34362
  return findings;
34310
34363
  }
34311
34364
 
@@ -34417,7 +34470,7 @@ function inputSchema(descriptor) {
34417
34470
  function createMcpServer() {
34418
34471
  const server = new McpServer({
34419
34472
  name: "loomweaver-devkit",
34420
- version: "0.11.0"
34473
+ version: "0.12.0"
34421
34474
  });
34422
34475
  server.registerTool(
34423
34476
  "list_generators",
@@ -34462,7 +34515,7 @@ function createMcpServer() {
34462
34515
  server.registerTool(
34463
34516
  "validate_commands",
34464
34517
  {
34465
- description: "Say, per command a plugin registers, whether an agent is offered it and what would leave the agent guessing. Input: files = { <path>: <TypeScript source> }. Only a callable command without a description is a warning; the rest is information.",
34518
+ description: "Say, per command a plugin registers, whether an agent is offered it, what would leave the agent guessing, and what the command states about an agent's word being enough to run it. Input: files = { <path>: <TypeScript source> }. Only a callable command without a description is a warning; the rest is information, and a command that states nothing about an agent's word is reported as stating nothing rather than faulted.",
34466
34519
  inputSchema: {
34467
34520
  files: external_exports.record(external_exports.string(), external_exports.string())
34468
34521
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/mcp",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "LoomWeaver: open-source plugin platform for Angular workbenches. The MCP server: scaffolding and validation as tools, so an AI assistant in any product repo can build weavers without a checkout.",
5
5
  "keywords": [
6
6
  "loomweaver",