@m8t-stack/cli 0.2.10 → 0.2.11

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/cli.js CHANGED
@@ -650,19 +650,47 @@ var init_foundry_agent_get = __esm({
650
650
  });
651
651
 
652
652
  // src/lib/brain-yaml-mirror.ts
653
+ import { parse as parseYaml4 } from "yaml";
653
654
  function linkSection(args) {
654
655
  return [
655
656
  `link:`,
656
657
  ` repo: "${args.repo}"`,
657
658
  ` branch: ${args.branch}`,
658
659
  ` topology: per-worker`,
659
- ` schemaVersion: "1"`,
660
+ ` schemaVersion: "2"`,
660
661
  ` persona: "${args.persona}"`,
661
- ` agent: "${args.agent}"`,
662
+ ` agents:`,
663
+ ...args.agents.map((a) => ` - ${a}`),
662
664
  ``
663
665
  ].join("\n");
664
666
  }
667
+ function mergeAgents(prior, incoming) {
668
+ return prior.includes(incoming) ? prior : [...prior, incoming];
669
+ }
670
+ function readLinkAgents(existing) {
671
+ if (!existing) return [];
672
+ let parsed;
673
+ try {
674
+ parsed = parseYaml4(existing);
675
+ } catch {
676
+ return [];
677
+ }
678
+ if (!parsed || typeof parsed !== "object") return [];
679
+ const root = parsed;
680
+ const link = root.link && typeof root.link === "object" ? root.link : root;
681
+ const raw = Array.isArray(link.agents) ? link.agents : typeof link.agent === "string" ? [link.agent] : [];
682
+ const out = [];
683
+ for (const v of raw) {
684
+ const s = typeof v === "string" ? v.trim() : "";
685
+ if (!s) continue;
686
+ if (/^\{\{.*\}\}$/.test(s)) continue;
687
+ if (!out.includes(s)) out.push(s);
688
+ }
689
+ return out;
690
+ }
665
691
  function buildBrainYaml(existing, args) {
692
+ const agents = mergeAgents(readLinkAgents(existing), args.agent);
693
+ const link = linkSection({ repo: args.repo, branch: args.branch, persona: args.persona, agents });
666
694
  if (existing) {
667
695
  const hasAuthoritative = /^engine:/m.test(existing) || /^processes:/m.test(existing);
668
696
  if (hasAuthoritative) {
@@ -670,10 +698,10 @@ function buildBrainYaml(existing, args) {
670
698
  const linkIdx = existing.search(/^link:/m);
671
699
  const cut = mirrorIdx >= 0 ? mirrorIdx : linkIdx >= 0 ? linkIdx : existing.length;
672
700
  const authoritative = existing.slice(0, cut).replace(/\s*$/, "\n\n");
673
- return authoritative + MIRROR_BANNER + "\n" + linkSection(args);
701
+ return authoritative + MIRROR_BANNER + "\n" + link;
674
702
  }
675
703
  }
676
- return DEFAULT_AUTHORITATIVE + "\n\n" + MIRROR_BANNER + "\n" + linkSection(args);
704
+ return DEFAULT_AUTHORITATIVE + "\n\n" + MIRROR_BANNER + "\n" + link;
677
705
  }
678
706
  async function mirrorBrainYaml(args) {
679
707
  const minted = await mintInstallationToken({
@@ -683,58 +711,56 @@ async function mirrorBrainYaml(args) {
683
711
  repository: args.repo
684
712
  });
685
713
  const [owner, repoName] = args.repo.split("/");
686
- const getRes = await fetch(
687
- `https://api.github.com/repos/${owner}/${repoName}/contents/.m8t/brain.yaml`,
688
- {
689
- headers: {
690
- Authorization: `Bearer ${minted.token}`,
691
- Accept: "application/vnd.github+json",
692
- "X-GitHub-Api-Version": "2022-11-28"
693
- }
694
- }
695
- );
696
- let sha;
697
- let existing;
698
- if (getRes.ok) {
699
- const data = await getRes.json();
700
- sha = data.sha;
701
- if (typeof data.content === "string") {
702
- existing = Buffer.from(data.content, "base64").toString("utf8");
703
- }
704
- }
705
- const content = buildBrainYaml(existing, {
706
- repo: args.repo,
707
- branch: args.branch,
708
- persona: args.persona,
709
- agent: args.agent
710
- });
711
- const body = {
712
- message: "chore(brain): mirror .m8t/brain.yaml (m8t brain link)",
713
- content: Buffer.from(content, "utf8").toString("base64"),
714
- branch: args.branch,
715
- ...sha ? { sha } : {}
714
+ const url = `https://api.github.com/repos/${owner}/${repoName}/contents/.m8t/brain.yaml`;
715
+ const headers = {
716
+ Authorization: `Bearer ${minted.token}`,
717
+ Accept: "application/vnd.github+json",
718
+ "X-GitHub-Api-Version": "2022-11-28"
716
719
  };
717
- const putRes = await fetch(
718
- `https://api.github.com/repos/${owner}/${repoName}/contents/.m8t/brain.yaml`,
719
- {
720
+ const MAX_ATTEMPTS2 = 3;
721
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS2; attempt++) {
722
+ const getRes = await fetch(url, { headers });
723
+ let sha;
724
+ let existing;
725
+ if (getRes.ok) {
726
+ const data = await getRes.json();
727
+ sha = data.sha;
728
+ if (typeof data.content === "string") {
729
+ existing = Buffer.from(data.content, "base64").toString("utf8");
730
+ }
731
+ }
732
+ const content = buildBrainYaml(existing, {
733
+ repo: args.repo,
734
+ branch: args.branch,
735
+ persona: args.persona,
736
+ agent: args.agent
737
+ });
738
+ const body = {
739
+ message: "chore(brain): mirror .m8t/brain.yaml (m8t brain link)",
740
+ content: Buffer.from(content, "utf8").toString("base64"),
741
+ branch: args.branch,
742
+ ...sha ? { sha } : {}
743
+ };
744
+ const putRes = await fetch(url, {
720
745
  method: "PUT",
721
- headers: {
722
- Authorization: `Bearer ${minted.token}`,
723
- Accept: "application/vnd.github+json",
724
- "X-GitHub-Api-Version": "2022-11-28",
725
- "Content-Type": "application/json"
726
- },
746
+ headers: { ...headers, "Content-Type": "application/json" },
727
747
  body: JSON.stringify(body)
728
- }
729
- );
730
- if (!putRes.ok) {
748
+ });
749
+ if (putRes.ok) return;
731
750
  const text = await putRes.text();
751
+ const retryable = putRes.status === 409 || putRes.status === 422;
752
+ if (retryable && attempt < MAX_ATTEMPTS2) continue;
753
+ const suffix = retryable ? ` after ${MAX_ATTEMPTS2.toString()} attempts` : "";
732
754
  throw new LocalCliError({
733
755
  code: "BRAIN_YAML_MIRROR_FAILED",
734
- message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}
756
+ message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}${suffix}
735
757
  ${text.slice(0, 300)}`
736
758
  });
737
759
  }
760
+ throw new LocalCliError({
761
+ code: "BRAIN_YAML_MIRROR_FAILED",
762
+ message: `PUT .m8t/brain.yaml: retry loop exited unexpectedly`
763
+ });
738
764
  }
739
765
  var DEFAULT_AUTHORITATIVE, MIRROR_BANNER;
740
766
  var init_brain_yaml_mirror = __esm({
@@ -1169,7 +1195,7 @@ var init_enable_hosted_brain = __esm({
1169
1195
  import { Builtins, Cli } from "clipanion";
1170
1196
 
1171
1197
  // src/lib/package-version.ts
1172
- var CLI_VERSION = "0.2.10";
1198
+ var CLI_VERSION = "0.2.11";
1173
1199
 
1174
1200
  // src/lib/render-error.ts
1175
1201
  init_errors();
@@ -13720,6 +13746,40 @@ ${LOADER_END}
13720
13746
  `;
13721
13747
  }
13722
13748
 
13749
+ // src/lib/a2a-snippet.ts
13750
+ var SNIPPET_START = "<!-- m8t:a2a:start -->";
13751
+ var SNIPPET_END = "<!-- m8t:a2a:end -->";
13752
+ var DELEGATE_SNIPPET = `${SNIPPET_START}
13753
+ ## Working with other workers
13754
+ You can delegate to other m8t workers. Before you do:
13755
+ 1. Call \`discover_workers\` to see who's available now \u2014 each entry's card says what
13756
+ the worker is for, when to delegate to it, and what it accepts and returns.
13757
+ 2. If one is a clear fit, call \`invoke_worker(target, task)\` \u2014 \`target\` is the name
13758
+ from the directory; \`task\` is a complete, self-contained instruction (the worker
13759
+ can't ask follow-ups mid-task).
13760
+ 3. If invoke_worker returns status "in_progress" with a taskId, the worker is taking
13761
+ a while \u2014 call \`check_delegation(taskId)\` to fetch the result, repeating until the
13762
+ status is no longer "in_progress".
13763
+ 4. Fold the worker's result into your own answer.
13764
+ Delegate only when another worker is genuinely better suited; otherwise answer
13765
+ directly. Don't re-delegate the same request in a loop.
13766
+ ${SNIPPET_END}`;
13767
+ function esc(s) {
13768
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13769
+ }
13770
+ function stripSnippet(instructions) {
13771
+ const re = new RegExp(`\\n*${esc(SNIPPET_START)}[\\s\\S]*?${esc(SNIPPET_END)}\\n*`, "g");
13772
+ return instructions.replace(re, "\n").replace(/\s+$/, "");
13773
+ }
13774
+ function appendSnippet(instructions) {
13775
+ return `${stripSnippet(instructions).replace(/\s+$/, "")}
13776
+
13777
+ ${DELEGATE_SNIPPET}`;
13778
+ }
13779
+ function hasA2aSnippet(instructions) {
13780
+ return instructions.includes(SNIPPET_START);
13781
+ }
13782
+
13723
13783
  // src/lib/brain-link.ts
13724
13784
  init_foundry_agent_get();
13725
13785
  init_brain_yaml_mirror();
@@ -13812,7 +13872,10 @@ async function linkBrain(args) {
13812
13872
  progress("No local persona yaml \u2014 basing instructions on the deployed agent\u2026");
13813
13873
  base = current.definition.instructions ?? "";
13814
13874
  }
13815
- const instructionsWithLoader = appendBrainLoader(base, loader);
13875
+ let instructionsWithLoader = appendBrainLoader(base, loader);
13876
+ if (hasA2aSnippet(current.definition.instructions ?? "")) {
13877
+ instructionsWithLoader = appendSnippet(instructionsWithLoader);
13878
+ }
13816
13879
  const link = {
13817
13880
  repo: args.repo,
13818
13881
  branch: args.branch,
@@ -14944,7 +15007,7 @@ init_errors();
14944
15007
  // src/lib/deploy-prompt-advisor.ts
14945
15008
  import * as fs12 from "fs";
14946
15009
  import * as path11 from "path";
14947
- import { parse as parseYaml4 } from "yaml";
15010
+ import { parse as parseYaml5 } from "yaml";
14948
15011
  init_foundry_agents();
14949
15012
  init_errors();
14950
15013
  function findPersonasRoot(hint) {
@@ -14972,7 +15035,7 @@ async function deployPromptAdvisor(args) {
14972
15035
  });
14973
15036
  }
14974
15037
  const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
14975
- const fm = fmMatch ? parseYaml4(fmMatch[1]) : {};
15038
+ const fm = fmMatch ? parseYaml5(fmMatch[1]) : {};
14976
15039
  const model = args.model ?? fm.targets?.foundry?.model;
14977
15040
  if (!model) {
14978
15041
  throw new LocalCliError({
@@ -15137,7 +15200,7 @@ import { randomBytes as randomBytes2, createHash } from "crypto";
15137
15200
  init_errors();
15138
15201
  init_esm();
15139
15202
  import * as fs13 from "fs";
15140
- import { parse as parseYaml5 } from "yaml";
15203
+ import { parse as parseYaml6 } from "yaml";
15141
15204
  function readPersonaA2aCard(personaPath) {
15142
15205
  let raw;
15143
15206
  try {
@@ -15147,7 +15210,7 @@ function readPersonaA2aCard(personaPath) {
15147
15210
  }
15148
15211
  const fm = /^---\n([\s\S]*?)\n---/.exec(raw);
15149
15212
  if (!fm) throw new LocalCliError({ code: "PERSONA_NO_FRONTMATTER", message: `Persona '${personaPath}' has no frontmatter.` });
15150
- const front = parseYaml5(fm[1]);
15213
+ const front = parseYaml6(fm[1]);
15151
15214
  const role = typeof front.role === "string" ? front.role : "";
15152
15215
  const cardYaml = front.targets?.foundry?.["a2a-card"];
15153
15216
  if (!cardYaml || typeof cardYaml !== "object") {
@@ -15182,23 +15245,6 @@ var FOUNDRY_ARM_API3 = "2025-04-01-preview";
15182
15245
  var FOUNDRY_DATA_SCOPE3 = "https://ai.azure.com/.default";
15183
15246
  var ARM_SCOPE6 = "https://management.azure.com/.default";
15184
15247
  var A2A_TOOLS = ["discover_workers", "invoke_worker", "check_delegation"];
15185
- var SNIPPET_START = "<!-- m8t:a2a:start -->";
15186
- var SNIPPET_END = "<!-- m8t:a2a:end -->";
15187
- var DELEGATE_SNIPPET = `${SNIPPET_START}
15188
- ## Working with other workers
15189
- You can delegate to other m8t workers. Before you do:
15190
- 1. Call \`discover_workers\` to see who's available now \u2014 each entry's card says what
15191
- the worker is for, when to delegate to it, and what it accepts and returns.
15192
- 2. If one is a clear fit, call \`invoke_worker(target, task)\` \u2014 \`target\` is the name
15193
- from the directory; \`task\` is a complete, self-contained instruction (the worker
15194
- can't ask follow-ups mid-task).
15195
- 3. If invoke_worker returns status "in_progress" with a taskId, the worker is taking
15196
- a while \u2014 call \`check_delegation(taskId)\` to fetch the result, repeating until the
15197
- status is no longer "in_progress".
15198
- 4. Fold the worker's result into your own answer.
15199
- Delegate only when another worker is genuinely better suited; otherwise answer
15200
- directly. Don't re-delegate the same request in a loop.
15201
- ${SNIPPET_END}`;
15202
15248
  async function enableA2a(args) {
15203
15249
  const progress = args.onProgress ?? ((_m) => {
15204
15250
  });
@@ -15257,15 +15303,6 @@ async function disableA2a(args) {
15257
15303
  await deleteConnection2({ credential: args.credential, projectArmId: args.projectArmId, connectionName });
15258
15304
  return { connectionName, foundryVersion };
15259
15305
  }
15260
- function appendSnippet(instructions) {
15261
- return `${stripSnippet(instructions).replace(/\s+$/, "")}
15262
-
15263
- ${DELEGATE_SNIPPET}`;
15264
- }
15265
- function stripSnippet(instructions) {
15266
- const re = new RegExp(`\\n*${esc(SNIPPET_START)}[\\s\\S]*?${esc(SNIPPET_END)}\\n*`, "g");
15267
- return instructions.replace(re, "\n").replace(/\s+$/, "");
15268
- }
15269
15306
  function withA2aTool(tools, bridgeUrl, connectionName) {
15270
15307
  const others = tools.filter(
15271
15308
  (t) => !(t.type === "mcp" && t.server_label === "a2a")
@@ -15275,9 +15312,6 @@ function withA2aTool(tools, bridgeUrl, connectionName) {
15275
15312
  { type: "mcp", server_label: "a2a", server_url: bridgeUrl, allowed_tools: A2A_TOOLS, require_approval: "never", project_connection_id: connectionName }
15276
15313
  ];
15277
15314
  }
15278
- function esc(s) {
15279
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15280
- }
15281
15315
  async function putA2aConnection(args) {
15282
15316
  const token = await args.credential.getToken(ARM_SCOPE6);
15283
15317
  if (!token?.token) throw new LocalCliError({ code: "ARM_AUTH", message: "Could not acquire ARM token" });
@@ -15655,7 +15689,7 @@ init_errors();
15655
15689
  import * as fs15 from "fs";
15656
15690
  import * as os6 from "os";
15657
15691
  import * as path12 from "path";
15658
- import { parse as parseYaml6 } from "yaml";
15692
+ import { parse as parseYaml7 } from "yaml";
15659
15693
  var A2A_PATH = "/api/a2a/mcp";
15660
15694
  function resolveBridgeUrl(opts) {
15661
15695
  const env = opts.env ?? process.env;
@@ -15679,7 +15713,7 @@ function readConfigGatewayUrl(home) {
15679
15713
  const cfg = path12.join(home ?? os6.homedir(), ".m8t-stack", "config.yaml");
15680
15714
  if (!fs15.existsSync(cfg)) return void 0;
15681
15715
  try {
15682
- const o = parseYaml6(fs15.readFileSync(cfg, "utf8"));
15716
+ const o = parseYaml7(fs15.readFileSync(cfg, "utf8"));
15683
15717
  return typeof o.gatewayUrl === "string" ? o.gatewayUrl : void 0;
15684
15718
  } catch {
15685
15719
  return void 0;
@@ -16181,7 +16215,7 @@ async function deployHostedWorker(args) {
16181
16215
  // src/lib/persona.ts
16182
16216
  import * as fs17 from "fs";
16183
16217
  import * as path15 from "path";
16184
- import { parse as parseYaml7 } from "yaml";
16218
+ import { parse as parseYaml8 } from "yaml";
16185
16219
  function findRepoRoot(startDir) {
16186
16220
  let dir = path15.resolve(startDir);
16187
16221
  for (; ; ) {
@@ -16205,7 +16239,7 @@ function resolvePersona(persona, cwd = process.cwd()) {
16205
16239
  if (!match) return { persona, personaVersion: null };
16206
16240
  let fm = null;
16207
16241
  try {
16208
- fm = parseYaml7(match[1]);
16242
+ fm = parseYaml8(match[1]);
16209
16243
  } catch {
16210
16244
  return { persona, personaVersion: null };
16211
16245
  }
@@ -18985,7 +19019,7 @@ import { Command as Command40, Option as Option38 } from "clipanion";
18985
19019
  // src/lib/profiles.ts
18986
19020
  import * as fs20 from "fs/promises";
18987
19021
  import * as path21 from "path";
18988
- import { parse as parseYaml8, stringify as stringifyYaml4 } from "yaml";
19022
+ import { parse as parseYaml9, stringify as stringifyYaml4 } from "yaml";
18989
19023
  function profilesDir() {
18990
19024
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
18991
19025
  return path21.join(home, ".m8t-stack", "profiles");
@@ -19010,7 +19044,7 @@ async function listProfiles() {
19010
19044
  async function readProfile(name) {
19011
19045
  try {
19012
19046
  const raw = await fs20.readFile(getProfilePath(name), "utf8");
19013
- const parsed = parseYaml8(raw);
19047
+ const parsed = parseYaml9(raw);
19014
19048
  if (!parsed || typeof parsed !== "object") return null;
19015
19049
  return parsed;
19016
19050
  } catch (e) {
@@ -21277,7 +21311,7 @@ async function emitDigest(input, deps, digest) {
21277
21311
  }
21278
21312
 
21279
21313
  // ../../packages/brain/engine/dist/esm/dream/config.js
21280
- import { parse as parseYaml9 } from "yaml";
21314
+ import { parse as parseYaml10 } from "yaml";
21281
21315
  var DEFAULT_DREAM_MODEL = "gpt-5.4";
21282
21316
  function asCadence(v) {
21283
21317
  return v === "nightly" ? "nightly" : "off";
@@ -21285,7 +21319,7 @@ function asCadence(v) {
21285
21319
  function parseDreamerConfig(rawYaml, env) {
21286
21320
  let parsed;
21287
21321
  try {
21288
- parsed = parseYaml9(rawYaml);
21322
+ parsed = parseYaml10(rawYaml);
21289
21323
  } catch {
21290
21324
  return { enabled: false, cadence: "off", model: env.M8T_DREAM_MODEL ?? DEFAULT_DREAM_MODEL };
21291
21325
  }
@@ -21302,10 +21336,10 @@ function parseDreamerConfig(rawYaml, env) {
21302
21336
  var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
21303
21337
 
21304
21338
  // ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
21305
- import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
21339
+ import { parse as parseYaml11, stringify as stringifyYaml5 } from "yaml";
21306
21340
 
21307
21341
  // ../../packages/brain/engine/dist/esm/librarian/config.js
21308
- import { parse as parseYaml11 } from "yaml";
21342
+ import { parse as parseYaml12 } from "yaml";
21309
21343
 
21310
21344
  // src/lib/dream-discovery.ts
21311
21345
  init_http();