@m8t-stack/cli 0.2.18 → 0.2.20

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 (3) hide show
  1. package/dist/cli.js +1934 -461
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1227,7 +1227,7 @@ var init_enable_hosted_brain = __esm({
1227
1227
  import { Builtins, Cli } from "clipanion";
1228
1228
 
1229
1229
  // src/lib/package-version.ts
1230
- var CLI_VERSION = "0.2.18";
1230
+ var CLI_VERSION = "0.2.20";
1231
1231
 
1232
1232
  // src/lib/render-error.ts
1233
1233
  init_errors();
@@ -3326,12 +3326,12 @@ function encodeURIPath(str4) {
3326
3326
  return str4.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
3327
3327
  }
3328
3328
  var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
3329
- var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(statics, ...params) {
3329
+ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path37(statics, ...params) {
3330
3330
  if (statics.length === 1)
3331
3331
  return statics[0];
3332
3332
  let postPath = false;
3333
3333
  const invalidSegments = [];
3334
- const path33 = statics.reduce((previousValue, currentValue, index) => {
3334
+ const path38 = statics.reduce((previousValue, currentValue, index) => {
3335
3335
  if (/[?#]/.test(currentValue)) {
3336
3336
  postPath = true;
3337
3337
  }
@@ -3348,7 +3348,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(sta
3348
3348
  }
3349
3349
  return previousValue + currentValue + (index === params.length ? "" : encoded);
3350
3350
  }, "");
3351
- const pathOnly = path33.split(/[?#]/, 1)[0];
3351
+ const pathOnly = path38.split(/[?#]/, 1)[0];
3352
3352
  const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
3353
3353
  let match;
3354
3354
  while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
@@ -3369,10 +3369,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(sta
3369
3369
  }, "");
3370
3370
  throw new OpenAIError(`Path parameters result in path with invalid segments:
3371
3371
  ${invalidSegments.map((e) => e.error).join("\n")}
3372
- ${path33}
3372
+ ${path38}
3373
3373
  ${underline}`);
3374
3374
  }
3375
- return path33;
3375
+ return path38;
3376
3376
  };
3377
3377
  var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
3378
3378
 
@@ -10889,9 +10889,9 @@ var OpenAI = class {
10889
10889
  this.apiKey = token;
10890
10890
  return true;
10891
10891
  }
10892
- buildURL(path32, query, defaultBaseURL) {
10892
+ buildURL(path37, query, defaultBaseURL) {
10893
10893
  const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
10894
- const url = isAbsoluteURL(path32) ? new URL(path32) : new URL(baseURL + (baseURL.endsWith("/") && path32.startsWith("/") ? path32.slice(1) : path32));
10894
+ const url = isAbsoluteURL(path37) ? new URL(path37) : new URL(baseURL + (baseURL.endsWith("/") && path37.startsWith("/") ? path37.slice(1) : path37));
10895
10895
  const defaultQuery = this.defaultQuery();
10896
10896
  const pathQuery = Object.fromEntries(url.searchParams);
10897
10897
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
@@ -10919,24 +10919,24 @@ var OpenAI = class {
10919
10919
  */
10920
10920
  async prepareRequest(request, { url, options }) {
10921
10921
  }
10922
- get(path32, opts) {
10923
- return this.methodRequest("get", path32, opts);
10922
+ get(path37, opts) {
10923
+ return this.methodRequest("get", path37, opts);
10924
10924
  }
10925
- post(path32, opts) {
10926
- return this.methodRequest("post", path32, opts);
10925
+ post(path37, opts) {
10926
+ return this.methodRequest("post", path37, opts);
10927
10927
  }
10928
- patch(path32, opts) {
10929
- return this.methodRequest("patch", path32, opts);
10928
+ patch(path37, opts) {
10929
+ return this.methodRequest("patch", path37, opts);
10930
10930
  }
10931
- put(path32, opts) {
10932
- return this.methodRequest("put", path32, opts);
10931
+ put(path37, opts) {
10932
+ return this.methodRequest("put", path37, opts);
10933
10933
  }
10934
- delete(path32, opts) {
10935
- return this.methodRequest("delete", path32, opts);
10934
+ delete(path37, opts) {
10935
+ return this.methodRequest("delete", path37, opts);
10936
10936
  }
10937
- methodRequest(method, path32, opts) {
10937
+ methodRequest(method, path37, opts) {
10938
10938
  return this.request(Promise.resolve(opts).then((opts2) => {
10939
- return { method, path: path32, ...opts2 };
10939
+ return { method, path: path37, ...opts2 };
10940
10940
  }));
10941
10941
  }
10942
10942
  request(options, remainingRetries = null) {
@@ -11058,8 +11058,8 @@ var OpenAI = class {
11058
11058
  }));
11059
11059
  return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
11060
11060
  }
11061
- getAPIList(path32, Page2, opts) {
11062
- return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path32, ...opts2 })) : { method: "get", path: path32, ...opts });
11061
+ getAPIList(path37, Page2, opts) {
11062
+ return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path37, ...opts2 })) : { method: "get", path: path37, ...opts });
11063
11063
  }
11064
11064
  requestAPIList(Page2, options) {
11065
11065
  const request = this.makeRequest(options, null, void 0);
@@ -11153,8 +11153,8 @@ var OpenAI = class {
11153
11153
  }
11154
11154
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
11155
11155
  const options = { ...inputOptions };
11156
- const { method, path: path32, query, defaultBaseURL } = options;
11157
- const url = this.buildURL(path32, query, defaultBaseURL);
11156
+ const { method, path: path37, query, defaultBaseURL } = options;
11157
+ const url = this.buildURL(path37, query, defaultBaseURL);
11158
11158
  if ("timeout" in options)
11159
11159
  validatePositiveInteger("timeout", options.timeout);
11160
11160
  options.timeout = options.timeout ?? this.timeout;
@@ -12156,10 +12156,10 @@ var BindListCommand = class extends M8tCommand {
12156
12156
  const qs = new URLSearchParams();
12157
12157
  if (this.channel) qs.set("channel", this.channel);
12158
12158
  if (this.status) qs.set("status", this.status);
12159
- const path32 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
12159
+ const path37 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
12160
12160
  const data = await apiCall(
12161
12161
  { gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
12162
- { method: "GET", path: path32 },
12162
+ { method: "GET", path: path37 },
12163
12163
  (msg) => this.context.stderr.write(msg + "\n")
12164
12164
  );
12165
12165
  if (mode === "json") {
@@ -13197,6 +13197,13 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
13197
13197
  const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
13198
13198
  let stdout = "";
13199
13199
  let stderr = "";
13200
+ let settled = false;
13201
+ const done = (r) => {
13202
+ if (!settled) {
13203
+ settled = true;
13204
+ resolve3(r);
13205
+ }
13206
+ };
13200
13207
  child.stdout.on("data", (d) => {
13201
13208
  stdout += d.toString();
13202
13209
  });
@@ -13204,7 +13211,10 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
13204
13211
  stderr += d.toString();
13205
13212
  });
13206
13213
  child.on("close", (code) => {
13207
- resolve3({ stdout, stderr, exitCode: code ?? -1 });
13214
+ done({ stdout, stderr, exitCode: code ?? -1 });
13215
+ });
13216
+ child.on("error", (err) => {
13217
+ done({ stdout, stderr: stderr || err.message, exitCode: -1 });
13208
13218
  });
13209
13219
  });
13210
13220
  async function isGhAuthed(exec = defaultGhExec) {
@@ -13360,8 +13370,8 @@ ${r.stderr}`;
13360
13370
  message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
13361
13371
  });
13362
13372
  },
13363
- getFile: async (path32) => {
13364
- const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path32}`]);
13373
+ getFile: async (path37) => {
13374
+ const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path37}`]);
13365
13375
  return r.exitCode === 0 ? r.stdout : null;
13366
13376
  },
13367
13377
  isEmpty: async () => {
@@ -13535,15 +13545,21 @@ function discoverKvUri(env, override) {
13535
13545
  hint: "Pass --kv-uri or set AZURE_KEYVAULT_URI / KEYVAULT_URI."
13536
13546
  });
13537
13547
  }
13538
- function resolveRepoRoot() {
13548
+ function tryResolveRepoRoot() {
13539
13549
  const marker = path5.join(os2.homedir(), ".m8t-stack", "repo-root");
13540
- if (!fs5.existsSync(marker)) {
13550
+ if (!fs5.existsSync(marker)) return null;
13551
+ const root = fs5.readFileSync(marker, "utf8").trim();
13552
+ return root.length > 0 ? root : null;
13553
+ }
13554
+ function resolveRepoRoot() {
13555
+ const root = tryResolveRepoRoot();
13556
+ if (root === null) {
13541
13557
  throw new LocalCliError({
13542
13558
  code: "M8T_REPO_ROOT_MISSING",
13543
13559
  message: "~/.m8t-stack/repo-root marker missing \u2014 m8t install never completed."
13544
13560
  });
13545
13561
  }
13546
- return fs5.readFileSync(marker, "utf8").trim();
13562
+ return root;
13547
13563
  }
13548
13564
 
13549
13565
  // src/commands/brain/check-app.ts
@@ -13599,11 +13615,11 @@ var BrainCheckAppCommand = class extends M8tCommand {
13599
13615
  // src/commands/brain/create.ts
13600
13616
  init_esm2();
13601
13617
  import { Command as Command18, Option as Option17 } from "clipanion";
13602
- import { spawnSync } from "child_process";
13618
+ import { execFileSync, spawnSync } from "child_process";
13603
13619
  import { readFileSync as readFileSync6 } from "fs";
13604
- import * as fs10 from "fs";
13620
+ import * as fs11 from "fs";
13605
13621
  import * as os4 from "os";
13606
- import * as path9 from "path";
13622
+ import * as path10 from "path";
13607
13623
  import * as readline2 from "readline/promises";
13608
13624
  import { DefaultAzureCredential as DefaultAzureCredential4 } from "@azure/identity";
13609
13625
  init_errors();
@@ -14119,11 +14135,66 @@ async function createBrainEnabledVersion(args) {
14119
14135
 
14120
14136
  // src/lib/brain-seed.ts
14121
14137
  init_errors();
14138
+ import * as fs10 from "fs";
14139
+ import * as path9 from "path";
14140
+
14141
+ // src/lib/seed-manifest.ts
14122
14142
  import * as fs9 from "fs";
14123
14143
  import * as path8 from "path";
14144
+ var SEED_MANIFEST_PATH = ".m8t/seed-manifest.json";
14145
+ var SEED_PATHS = ["skills", "references", "targets"];
14146
+ function isLineage(v) {
14147
+ if (!v || typeof v !== "object") return false;
14148
+ const l = v;
14149
+ return typeof l.name === "string" && typeof l.refreshedToCommit === "string" && typeof l.refreshedToVersion === "string" && Array.isArray(l.ownedPaths) && l.ownedPaths.every((p) => typeof p === "string");
14150
+ }
14151
+ function parseSeedManifest(text) {
14152
+ if (!text?.trim()) return null;
14153
+ let parsed;
14154
+ try {
14155
+ parsed = JSON.parse(text);
14156
+ } catch {
14157
+ return null;
14158
+ }
14159
+ if (!parsed || typeof parsed !== "object") return null;
14160
+ const m = parsed;
14161
+ if (m.schemaVersion !== 1 || !Array.isArray(m.seeds) || !m.seeds.every(isLineage)) return null;
14162
+ return { schemaVersion: 1, seeds: m.seeds };
14163
+ }
14164
+ function buildSeedManifest(seeds) {
14165
+ return { schemaVersion: 1, seeds };
14166
+ }
14167
+ function upsertLineage(manifest, lineage) {
14168
+ const prior = manifest?.seeds ?? [];
14169
+ const others = prior.filter((s) => s.name !== lineage.name);
14170
+ return { schemaVersion: 1, seeds: [...others, lineage].sort((a, b) => a.name.localeCompare(b.name)) };
14171
+ }
14172
+ function serializeSeedManifest(m) {
14173
+ return JSON.stringify(m, null, 2) + "\n";
14174
+ }
14175
+ function walkFiles(root, rel = "") {
14176
+ const abs = path8.join(root, rel);
14177
+ if (!fs9.existsSync(abs)) return [];
14178
+ const out = [];
14179
+ for (const entry of fs9.readdirSync(abs, { withFileTypes: true })) {
14180
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
14181
+ if (entry.isDirectory()) out.push(...walkFiles(root, childRel));
14182
+ else if (entry.isFile()) out.push(childRel);
14183
+ }
14184
+ return out;
14185
+ }
14186
+ function deriveOwnedPaths(seedDir) {
14187
+ const owned = /* @__PURE__ */ new Set([SEED_MANIFEST_PATH]);
14188
+ for (const sub of SEED_PATHS) {
14189
+ for (const rel of walkFiles(path8.join(seedDir, sub))) owned.add(`${sub}/${rel}`);
14190
+ }
14191
+ return [...owned].sort((a, b) => a.localeCompare(b));
14192
+ }
14193
+
14194
+ // src/lib/brain-seed.ts
14124
14195
  function resolveSeed(name) {
14125
- const dir = path8.join(resolveRepoRoot(), "brain-seeds", name);
14126
- if (!fs9.existsSync(dir)) {
14196
+ const dir = path9.join(resolveRepoRoot(), "brain-seeds", name);
14197
+ if (!fs10.existsSync(dir)) {
14127
14198
  throw new LocalCliError({
14128
14199
  code: "SEED_NOT_FOUND",
14129
14200
  message: `--seed "${name}": no brain-seeds/${name}/ directory found.`,
@@ -14133,11 +14204,22 @@ function resolveSeed(name) {
14133
14204
  return dir;
14134
14205
  }
14135
14206
  function materializeBrainTree(opts) {
14136
- fs9.cpSync(opts.templateSrc, opts.destDir, { recursive: true });
14207
+ fs10.cpSync(opts.templateSrc, opts.destDir, { recursive: true });
14137
14208
  if (opts.seedDir) {
14138
- fs9.cpSync(opts.seedDir, opts.destDir, { recursive: true });
14209
+ fs10.cpSync(opts.seedDir, opts.destDir, { recursive: true });
14139
14210
  }
14140
14211
  }
14212
+ function writeSeedMarker(args) {
14213
+ const manifest = buildSeedManifest([{
14214
+ name: args.seedName,
14215
+ refreshedToCommit: args.refreshedToCommit,
14216
+ refreshedToVersion: args.version,
14217
+ ownedPaths: deriveOwnedPaths(args.seedDir)
14218
+ }]);
14219
+ const dest = path9.join(args.destDir, SEED_MANIFEST_PATH);
14220
+ fs10.mkdirSync(path9.dirname(dest), { recursive: true });
14221
+ fs10.writeFileSync(dest, serializeSeedManifest(manifest));
14222
+ }
14141
14223
 
14142
14224
  // src/lib/github-app-repo.ts
14143
14225
  init_errors();
@@ -14244,18 +14326,32 @@ async function mintInstallationTokenFromPem(args) {
14244
14326
  }
14245
14327
  async function readRepoFileViaApp(args) {
14246
14328
  const doFetch = args.fetchImpl ?? fetch;
14247
- const res = await doFetch(`${GH_API}/repos/${args.repo}/contents/${args.path}`, { headers: ghHeaders(args.token), signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14329
+ const q = args.ref ? `?ref=${encodeURIComponent(args.ref)}` : "";
14330
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/contents/${args.path}${q}`, { headers: ghHeaders(args.token), signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14248
14331
  if (res.status === 404) return null;
14249
14332
  const text = await res.text();
14250
14333
  if (!res.ok) {
14251
14334
  throw new LocalCliError({
14252
14335
  code: "GH_APP_FILE_READ_FAILED",
14253
- message: `GET /repos/${args.repo}/contents/${args.path} HTTP ${res.status.toString()}: ${text.slice(0, 300)}`
14336
+ message: `GET /repos/${args.repo}/contents/${args.path}${q} HTTP ${res.status.toString()}: ${text.slice(0, 300)}`
14254
14337
  });
14255
14338
  }
14256
14339
  const data = JSON.parse(text);
14257
14340
  return Buffer.from(data.content ?? "", "base64").toString("utf8");
14258
14341
  }
14342
+ async function getInitialSeedCommitSha(args) {
14343
+ const doFetch = args.fetchImpl ?? fetch;
14344
+ const H = ghHeaders(args.token);
14345
+ const first = await doFetch(`${GH_API}/repos/${args.repo}/commits?per_page=1`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14346
+ if (!first.ok) return null;
14347
+ const link = first.headers.get("link");
14348
+ const lastUrl = /<([^>]+)>;\s*rel="last"/.exec(link ?? "")?.[1];
14349
+ const url = lastUrl ?? `${GH_API}/repos/${args.repo}/commits?per_page=1`;
14350
+ const res = await doFetch(url, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14351
+ if (!res.ok) return null;
14352
+ const commits = JSON.parse(await res.text());
14353
+ return commits.length > 0 ? commits[commits.length - 1].sha ?? null : null;
14354
+ }
14259
14355
  async function commitFilesViaApp(args) {
14260
14356
  const doFetch = args.fetchImpl ?? fetch;
14261
14357
  const H = ghHeaders(args.token);
@@ -14273,19 +14369,68 @@ async function commitFilesViaApp(args) {
14273
14369
  const ref = await api(`/git/ref/heads/${args.branch}`);
14274
14370
  const baseCommitSha = ref.object.sha;
14275
14371
  const baseCommit = await api(`/git/commits/${baseCommitSha}`);
14276
- const blobs = await Promise.all(args.files.map((f) => api(`/git/blobs`, { method: "POST", body: JSON.stringify({ content: f.content, encoding: "utf-8" }) })));
14277
- const tree = await api(`/git/trees`, {
14372
+ const upserts = args.files.filter((f) => !("delete" in f));
14373
+ const blobs = await Promise.all(upserts.map((f) => api(`/git/blobs`, { method: "POST", body: JSON.stringify({ content: f.content, encoding: "utf-8" }) })));
14374
+ const tree = args.files.map((f) => "delete" in f ? { path: f.path, mode: "100644", type: "blob", sha: null } : { path: f.path, mode: "100644", type: "blob", sha: blobs[upserts.indexOf(f)].sha });
14375
+ const newTree = await api(`/git/trees`, { method: "POST", body: JSON.stringify({ base_tree: baseCommit.tree.sha, tree }) });
14376
+ const commit = await api(`/git/commits`, {
14278
14377
  method: "POST",
14279
- body: JSON.stringify({
14280
- base_tree: baseCommit.tree.sha,
14281
- tree: args.files.map((f, i) => ({ path: f.path, mode: "100644", type: "blob", sha: blobs[i].sha }))
14282
- })
14378
+ body: JSON.stringify({ message: args.message, tree: newTree.sha, parents: [baseCommitSha] })
14379
+ });
14380
+ await api(`/git/refs/heads/${args.branch}`, { method: "PATCH", body: JSON.stringify({ sha: commit.sha }) });
14381
+ }
14382
+ async function createBranchViaApp(args) {
14383
+ const doFetch = args.fetchImpl ?? fetch;
14384
+ const H = ghHeaders(args.token);
14385
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/git/refs`, {
14386
+ method: "POST",
14387
+ headers: H,
14388
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS),
14389
+ body: JSON.stringify({ ref: `refs/heads/${args.branch}`, sha: args.fromSha })
14390
+ });
14391
+ if (res.ok) return;
14392
+ if (res.status === 422) {
14393
+ const patch = await doFetch(`${GH_API}/repos/${args.repo}/git/refs/heads/${args.branch}`, {
14394
+ method: "PATCH",
14395
+ headers: H,
14396
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS),
14397
+ body: JSON.stringify({ sha: args.fromSha, force: true })
14398
+ });
14399
+ if (patch.ok) return;
14400
+ throw new LocalCliError({
14401
+ code: "GH_APP_BRANCH_FAILED",
14402
+ message: `PATCH refs/heads/${args.branch} HTTP ${patch.status.toString()}: ${(await patch.text()).slice(0, 300)}`
14403
+ });
14404
+ }
14405
+ throw new LocalCliError({
14406
+ code: "GH_APP_BRANCH_FAILED",
14407
+ message: `POST /git/refs HTTP ${res.status.toString()}: ${(await res.text()).slice(0, 300)}`
14283
14408
  });
14409
+ }
14410
+ async function commitFilesFromBaseViaApp(args) {
14411
+ const doFetch = args.fetchImpl ?? fetch;
14412
+ const H = ghHeaders(args.token);
14413
+ const api = async (p, init) => {
14414
+ const res = await doFetch(`${GH_API}/repos/${args.repo}${p}`, init ? { ...init, headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) } : { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14415
+ const text = await res.text();
14416
+ if (!res.ok) {
14417
+ throw new LocalCliError({
14418
+ code: "GH_APP_COMMIT_FAILED",
14419
+ message: `${init?.method ?? "GET"} ${p} HTTP ${res.status.toString()}: ${text.slice(0, 300)}`
14420
+ });
14421
+ }
14422
+ return text ? JSON.parse(text) : {};
14423
+ };
14424
+ const baseCommit = await api(`/git/commits/${args.baseSha}`);
14425
+ const upserts = args.files.filter((f) => !("delete" in f));
14426
+ const blobs = await Promise.all(upserts.map((f) => api(`/git/blobs`, { method: "POST", body: JSON.stringify({ content: f.content, encoding: "utf-8" }) })));
14427
+ const tree = args.files.map((f) => "delete" in f ? { path: f.path, mode: "100644", type: "blob", sha: null } : { path: f.path, mode: "100644", type: "blob", sha: blobs[upserts.indexOf(f)].sha });
14428
+ const newTree = await api(`/git/trees`, { method: "POST", body: JSON.stringify({ base_tree: baseCommit.tree.sha, tree }) });
14284
14429
  const commit = await api(`/git/commits`, {
14285
14430
  method: "POST",
14286
- body: JSON.stringify({ message: args.message, tree: tree.sha, parents: [baseCommitSha] })
14431
+ body: JSON.stringify({ message: args.message, tree: newTree.sha, parents: [args.baseSha] })
14287
14432
  });
14288
- await api(`/git/refs/heads/${args.branch}`, { method: "PATCH", body: JSON.stringify({ sha: commit.sha }) });
14433
+ await createBranchViaApp({ token: args.token, repo: args.repo, branch: args.branch, fromSha: commit.sha, fetchImpl: args.fetchImpl });
14289
14434
  }
14290
14435
  async function probeRepoState(probe) {
14291
14436
  const status = await probe.repoStatus();
@@ -14309,13 +14454,66 @@ function appRepoProbe(args) {
14309
14454
  const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14310
14455
  return res.status;
14311
14456
  },
14312
- getFile: (path32) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path32, fetchImpl: args.fetchImpl }),
14457
+ getFile: (path37) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path37, fetchImpl: args.fetchImpl }),
14313
14458
  isEmpty: async () => {
14314
14459
  const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14315
14460
  return res.status === 404;
14316
14461
  }
14317
14462
  };
14318
14463
  }
14464
+ async function openPullRequestViaApp(args) {
14465
+ const doFetch = args.fetchImpl ?? fetch;
14466
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/pulls`, {
14467
+ method: "POST",
14468
+ headers: ghHeaders(args.token),
14469
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS),
14470
+ body: JSON.stringify({ base: args.base, head: args.head, title: args.title, body: args.body })
14471
+ });
14472
+ const text = await res.text();
14473
+ if (!res.ok) throw new LocalCliError({ code: "GH_APP_PR_OPEN_FAILED", message: `POST /pulls HTTP ${res.status.toString()}: ${text.slice(0, 300)}` });
14474
+ const j = JSON.parse(text);
14475
+ return { number: j.number, htmlUrl: j.html_url };
14476
+ }
14477
+ async function findOpenPr(args) {
14478
+ const doFetch = args.fetchImpl ?? fetch;
14479
+ const owner = args.repo.split("/")[0];
14480
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/pulls?state=open&head=${owner}:${args.head}`, { headers: ghHeaders(args.token), signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14481
+ if (!res.ok) return null;
14482
+ const list = JSON.parse(await res.text());
14483
+ return list.length > 0 ? { number: list[0].number, htmlUrl: list[0].html_url } : null;
14484
+ }
14485
+ async function updatePullRequestViaApp(args) {
14486
+ const doFetch = args.fetchImpl ?? fetch;
14487
+ const patch = {};
14488
+ if (args.title !== void 0) patch.title = args.title;
14489
+ if (args.body !== void 0) patch.body = args.body;
14490
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/pulls/${args.number.toString()}`, {
14491
+ method: "PATCH",
14492
+ headers: ghHeaders(args.token),
14493
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS),
14494
+ body: JSON.stringify(patch)
14495
+ });
14496
+ if (!res.ok) throw new LocalCliError({ code: "GH_APP_PR_UPDATE_FAILED", message: `PATCH /pulls/${args.number.toString()} HTTP ${res.status.toString()}: ${(await res.text()).slice(0, 300)}` });
14497
+ }
14498
+ async function closePullRequestViaApp(args) {
14499
+ const doFetch = args.fetchImpl ?? fetch;
14500
+ const H = ghHeaders(args.token);
14501
+ if (args.comment) {
14502
+ await doFetch(`${GH_API}/repos/${args.repo}/issues/${args.number.toString()}/comments`, {
14503
+ method: "POST",
14504
+ headers: H,
14505
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS),
14506
+ body: JSON.stringify({ body: args.comment })
14507
+ });
14508
+ }
14509
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/pulls/${args.number.toString()}`, {
14510
+ method: "PATCH",
14511
+ headers: H,
14512
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS),
14513
+ body: JSON.stringify({ state: "closed" })
14514
+ });
14515
+ if (!res.ok) throw new LocalCliError({ code: "GH_APP_PR_CLOSE_FAILED", message: `PATCH /pulls/${args.number.toString()} HTTP ${res.status.toString()}: ${(await res.text()).slice(0, 300)}` });
14516
+ }
14319
14517
 
14320
14518
  // src/lib/brain-repo-collision.ts
14321
14519
  init_errors();
@@ -14497,15 +14695,15 @@ var BrainCreateCommand = class extends M8tCommand {
14497
14695
  agentEndpoint
14498
14696
  });
14499
14697
  const repoRoot = resolveRepoRoot();
14500
- const templateSrc = path9.join(repoRoot, "brain-template");
14501
- if (!fs10.existsSync(templateSrc)) {
14698
+ const templateSrc = path10.join(repoRoot, "brain-template");
14699
+ if (!fs11.existsSync(templateSrc)) {
14502
14700
  throw new LocalCliError({
14503
14701
  code: "TEMPLATE_MISSING",
14504
14702
  message: `brain-template/ missing at ${templateSrc}`,
14505
14703
  hint: "Ensure brain-template/ exists at the repo root (same level as apps/)."
14506
14704
  });
14507
14705
  }
14508
- const tmpDir = fs10.mkdtempSync(path9.join(os4.tmpdir(), `m8t-brain-create-${worker}-`));
14706
+ const tmpDir = fs11.mkdtempSync(path10.join(os4.tmpdir(), `m8t-brain-create-${worker}-`));
14509
14707
  let result;
14510
14708
  let resolvedInstallationId = null;
14511
14709
  const force = this.reuse === true ? "reuse" : this.newName === true ? "new" : void 0;
@@ -14542,6 +14740,10 @@ var BrainCreateCommand = class extends M8tCommand {
14542
14740
  try {
14543
14741
  if (needsPush) {
14544
14742
  materializeBrainTree({ templateSrc, destDir: tmpDir, seedDir });
14743
+ if (seedName && seedDir) {
14744
+ const headSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf8" }).trim();
14745
+ writeSeedMarker({ destDir: tmpDir, seedName, seedDir, refreshedToCommit: headSha, version: "(scaffold)" });
14746
+ }
14545
14747
  this.context.stdout.write(
14546
14748
  seedName ? `${colors.dim("\u2192")} copied brain-template/ + overlaid seed ${colors.field(seedName)} to ${tmpDir}
14547
14749
  ` : `${colors.dim("\u2192")} copied brain-template/ to ${tmpDir}
@@ -14633,7 +14835,7 @@ var BrainCreateCommand = class extends M8tCommand {
14633
14835
  project
14634
14836
  });
14635
14837
  } finally {
14636
- fs10.rmSync(tmpDir, { recursive: true, force: true });
14838
+ fs11.rmSync(tmpDir, { recursive: true, force: true });
14637
14839
  }
14638
14840
  if (mode === "json") {
14639
14841
  this.context.stdout.write(
@@ -14970,9 +15172,9 @@ init_errors();
14970
15172
  // src/lib/brain-unlink.ts
14971
15173
  init_esm2();
14972
15174
  init_errors();
14973
- import * as fs11 from "fs";
15175
+ import * as fs12 from "fs";
14974
15176
  import * as os5 from "os";
14975
- import * as path10 from "path";
15177
+ import * as path11 from "path";
14976
15178
  init_foundry_agent_get();
14977
15179
  var FOUNDRY_ARM_API2 = "2025-04-01-preview";
14978
15180
  var FOUNDRY_DATA_SCOPE2 = "https://ai.azure.com/.default";
@@ -15003,15 +15205,15 @@ async function unlinkBrain(args) {
15003
15205
  if (yaml?.personaPath) {
15004
15206
  let personaPath = yaml.personaPath;
15005
15207
  if (!personaPath.startsWith("/")) {
15006
- const marker = path10.join(args.home ?? os5.homedir(), ".m8t-stack", "repo-root");
15007
- if (!fs11.existsSync(marker)) {
15208
+ const marker = path11.join(args.home ?? os5.homedir(), ".m8t-stack", "repo-root");
15209
+ if (!fs12.existsSync(marker)) {
15008
15210
  throw new LocalCliError({
15009
15211
  code: "PERSONA_PATH_NOT_ABSOLUTE",
15010
15212
  message: `personaPath '${personaPath}' is relative and the repo-root marker is missing.`,
15011
15213
  hint: "Re-run 'm8t install' from a fresh m8t-stack checkout (writes ~/.m8t-stack/repo-root) \u2014 or edit the personaPath to absolute."
15012
15214
  });
15013
15215
  }
15014
- personaPath = path10.join(fs11.readFileSync(marker, "utf8").trim(), personaPath);
15216
+ personaPath = path11.join(fs12.readFileSync(marker, "utf8").trim(), personaPath);
15015
15217
  }
15016
15218
  strippedInstructions = renderPersonaBody(personaPath, yaml.fillableFieldValues ?? {}, args.agentName);
15017
15219
  } else {
@@ -15251,27 +15453,27 @@ import { DefaultAzureCredential as DefaultAzureCredential9 } from "@azure/identi
15251
15453
  init_errors();
15252
15454
 
15253
15455
  // src/lib/deploy-prompt-advisor.ts
15254
- import * as fs12 from "fs";
15255
- import * as path11 from "path";
15456
+ import * as fs13 from "fs";
15457
+ import * as path12 from "path";
15256
15458
  import { parse as parseYaml6 } from "yaml";
15257
15459
  init_foundry_agents();
15258
15460
  init_errors();
15259
15461
  function findPersonasRoot(hint) {
15260
- let dir = path11.resolve(hint);
15261
- const fsRoot = path11.parse(dir).root;
15462
+ let dir = path12.resolve(hint);
15463
+ const fsRoot = path12.parse(dir).root;
15262
15464
  for (; ; ) {
15263
- if (fs12.existsSync(path11.join(dir, "personas"))) return dir;
15264
- const parent = path11.dirname(dir);
15465
+ if (fs13.existsSync(path12.join(dir, "personas"))) return dir;
15466
+ const parent = path12.dirname(dir);
15265
15467
  if (parent === dir || dir === fsRoot) return null;
15266
15468
  dir = parent;
15267
15469
  }
15268
15470
  }
15269
15471
  async function deployPromptAdvisor(args) {
15270
15472
  const resolvedRoot = findPersonasRoot(args.repoRoot);
15271
- const personaPath = resolvedRoot ? path11.join(resolvedRoot, "personas", args.persona, "persona.md") : path11.join(args.repoRoot, "personas", args.persona, "persona.md");
15473
+ const personaPath = resolvedRoot ? path12.join(resolvedRoot, "personas", args.persona, "persona.md") : path12.join(args.repoRoot, "personas", args.persona, "persona.md");
15272
15474
  let raw;
15273
15475
  try {
15274
- raw = fs12.readFileSync(personaPath, "utf8");
15476
+ raw = fs13.readFileSync(personaPath, "utf8");
15275
15477
  } catch (e) {
15276
15478
  throw new LocalCliError({
15277
15479
  code: "ADVISOR_PERSONA_MISSING",
@@ -15433,7 +15635,7 @@ init_errors();
15433
15635
  // src/lib/agent-remove.ts
15434
15636
  init_errors();
15435
15637
  init_foundry_agent_get();
15436
- import * as fs14 from "fs";
15638
+ import * as fs15 from "fs";
15437
15639
  import { spawnSync as spawnSync2 } from "child_process";
15438
15640
 
15439
15641
  // src/lib/a2a-enable.ts
@@ -15547,12 +15749,12 @@ async function awaitAgentQueryable(args) {
15547
15749
  // src/lib/persona-a2a.ts
15548
15750
  init_errors();
15549
15751
  init_esm();
15550
- import * as fs13 from "fs";
15752
+ import * as fs14 from "fs";
15551
15753
  import { parse as parseYaml7 } from "yaml";
15552
15754
  function readPersonaA2aCard(personaPath) {
15553
15755
  let raw;
15554
15756
  try {
15555
- raw = fs13.readFileSync(personaPath, "utf8");
15757
+ raw = fs14.readFileSync(personaPath, "utf8");
15556
15758
  } catch (e) {
15557
15759
  throw new LocalCliError({ code: "PERSONA_READ_FAILED", message: `Could not read persona '${personaPath}': ${e.message}` });
15558
15760
  }
@@ -15877,7 +16079,7 @@ function defaultRemoveAgentDeps(args) {
15877
16079
  },
15878
16080
  removeLocalYaml(agentName) {
15879
16081
  const p = agentYamlPath(agentName, home);
15880
- if (fs14.existsSync(p)) fs14.rmSync(p);
16082
+ if (fs15.existsSync(p)) fs15.rmSync(p);
15881
16083
  }
15882
16084
  };
15883
16085
  }
@@ -16019,16 +16221,16 @@ ${colors.success("\u2713")} removed ${colors.field(name)}.
16019
16221
  };
16020
16222
 
16021
16223
  // src/commands/a2a/enable.ts
16022
- import * as path13 from "path";
16224
+ import * as path14 from "path";
16023
16225
  import { Command as Command25, Option as Option24 } from "clipanion";
16024
16226
  import { DefaultAzureCredential as DefaultAzureCredential11 } from "@azure/identity";
16025
16227
  init_errors();
16026
16228
 
16027
16229
  // src/lib/bridge-url.ts
16028
16230
  init_errors();
16029
- import * as fs15 from "fs";
16231
+ import * as fs16 from "fs";
16030
16232
  import * as os6 from "os";
16031
- import * as path12 from "path";
16233
+ import * as path13 from "path";
16032
16234
  import { parse as parseYaml8 } from "yaml";
16033
16235
  var A2A_PATH = "/api/a2a/mcp";
16034
16236
  function resolveBridgeUrl(opts) {
@@ -16050,10 +16252,10 @@ function clean(v) {
16050
16252
  return t.length ? t : void 0;
16051
16253
  }
16052
16254
  function readConfigGatewayUrl(home) {
16053
- const cfg = path12.join(home ?? os6.homedir(), ".m8t-stack", "config.yaml");
16054
- if (!fs15.existsSync(cfg)) return void 0;
16255
+ const cfg = path13.join(home ?? os6.homedir(), ".m8t-stack", "config.yaml");
16256
+ if (!fs16.existsSync(cfg)) return void 0;
16055
16257
  try {
16056
- const o = parseYaml8(fs15.readFileSync(cfg, "utf8"));
16258
+ const o = parseYaml8(fs16.readFileSync(cfg, "utf8"));
16057
16259
  return typeof o.gatewayUrl === "string" ? o.gatewayUrl : void 0;
16058
16260
  } catch {
16059
16261
  return void 0;
@@ -16122,11 +16324,11 @@ var A2aEnableCommand = class extends M8tCommand {
16122
16324
  resolvePersonaPath() {
16123
16325
  if (typeof this.persona === "string" && this.persona.length > 0) {
16124
16326
  if (this.persona.includes("/") || this.persona.endsWith(".md")) return this.persona;
16125
- return path13.join(resolveRepoRoot(), "personas", this.persona, "persona.md");
16327
+ return path14.join(resolveRepoRoot(), "personas", this.persona, "persona.md");
16126
16328
  }
16127
16329
  const yaml = readAgentYaml(this.worker);
16128
16330
  if (yaml?.personaPath) {
16129
- return yaml.personaPath.startsWith("/") ? yaml.personaPath : path13.join(resolveRepoRoot(), yaml.personaPath);
16331
+ return yaml.personaPath.startsWith("/") ? yaml.personaPath : path14.join(resolveRepoRoot(), yaml.personaPath);
16130
16332
  }
16131
16333
  throw new LocalCliError({ code: "A2A_NO_PERSONA", message: `Could not resolve a persona for '${this.worker}'. Pass --persona <name>.` });
16132
16334
  }
@@ -16182,12 +16384,12 @@ import { Command as Command27 } from "clipanion";
16182
16384
 
16183
16385
  // src/lib/architect-version.ts
16184
16386
  import { createHash as createHash2 } from "crypto";
16185
- import * as fs16 from "fs";
16387
+ import * as fs17 from "fs";
16186
16388
  import * as os7 from "os";
16187
- import * as path14 from "path";
16389
+ import * as path15 from "path";
16188
16390
  function readVersionFrontmatter(file) {
16189
- if (!fs16.existsSync(file)) return "";
16190
- const text = fs16.readFileSync(file, "utf8");
16391
+ if (!fs17.existsSync(file)) return "";
16392
+ const text = fs17.readFileSync(file, "utf8");
16191
16393
  if (!text.startsWith("---")) return "";
16192
16394
  const closeIdx = text.indexOf("\n---", 3);
16193
16395
  if (closeIdx < 0) return "";
@@ -16196,23 +16398,23 @@ function readVersionFrontmatter(file) {
16196
16398
  return m ? m[1].trim() : "";
16197
16399
  }
16198
16400
  function sha256File(filePath) {
16199
- return createHash2("sha256").update(fs16.readFileSync(filePath)).digest("hex");
16401
+ return createHash2("sha256").update(fs17.readFileSync(filePath)).digest("hex");
16200
16402
  }
16201
16403
  function checkArchitectDrift() {
16202
- const sourcePath = path14.join(
16404
+ const sourcePath = path15.join(
16203
16405
  resolveRepoRoot(),
16204
16406
  "personas",
16205
16407
  "m8t-architect",
16206
16408
  "persona.md"
16207
16409
  );
16208
- const sidecarPath = path14.join(
16410
+ const sidecarPath = path15.join(
16209
16411
  os7.homedir(),
16210
16412
  ".claude",
16211
16413
  "skills",
16212
16414
  "m8t-architect",
16213
16415
  ".m8t-skill.json"
16214
16416
  );
16215
- if (!fs16.existsSync(sourcePath)) {
16417
+ if (!fs17.existsSync(sourcePath)) {
16216
16418
  return {
16217
16419
  match: false,
16218
16420
  sourceVersion: "",
@@ -16222,9 +16424,9 @@ function checkArchitectDrift() {
16222
16424
  }
16223
16425
  const sourceVersion = readVersionFrontmatter(sourcePath);
16224
16426
  let sidecar = null;
16225
- if (fs16.existsSync(sidecarPath)) {
16427
+ if (fs17.existsSync(sidecarPath)) {
16226
16428
  try {
16227
- const raw = JSON.parse(fs16.readFileSync(sidecarPath, "utf8"));
16429
+ const raw = JSON.parse(fs17.readFileSync(sidecarPath, "utf8"));
16228
16430
  if (raw !== null && typeof raw === "object") {
16229
16431
  const r = raw;
16230
16432
  sidecar = {
@@ -16287,7 +16489,7 @@ var ArchitectCheckCommand = class extends M8tCommand {
16287
16489
 
16288
16490
  // src/commands/coder/deploy.ts
16289
16491
  init_esm2();
16290
- import * as path16 from "path";
16492
+ import * as path17 from "path";
16291
16493
  import { Command as Command28, Option as Option26 } from "clipanion";
16292
16494
  import { DefaultAzureCredential as DefaultAzureCredential13 } from "@azure/identity";
16293
16495
  init_errors();
@@ -16554,14 +16756,14 @@ async function deployHostedWorker(args) {
16554
16756
  }
16555
16757
 
16556
16758
  // src/lib/persona.ts
16557
- import * as fs17 from "fs";
16558
- import * as path15 from "path";
16759
+ import * as fs18 from "fs";
16760
+ import * as path16 from "path";
16559
16761
  import { parse as parseYaml9 } from "yaml";
16560
16762
  function findRepoRoot(startDir) {
16561
- let dir = path15.resolve(startDir);
16763
+ let dir = path16.resolve(startDir);
16562
16764
  for (; ; ) {
16563
- if (fs17.existsSync(path15.join(dir, "personas"))) return dir;
16564
- const parent = path15.dirname(dir);
16765
+ if (fs18.existsSync(path16.join(dir, "personas"))) return dir;
16766
+ const parent = path16.dirname(dir);
16565
16767
  if (parent === dir) return null;
16566
16768
  dir = parent;
16567
16769
  }
@@ -16569,10 +16771,10 @@ function findRepoRoot(startDir) {
16569
16771
  function resolvePersona(persona, cwd = process.cwd()) {
16570
16772
  const root = findRepoRoot(cwd);
16571
16773
  if (!root) return { persona, personaVersion: null };
16572
- const file = path15.join(root, "personas", persona, "persona.md");
16774
+ const file = path16.join(root, "personas", persona, "persona.md");
16573
16775
  let raw;
16574
16776
  try {
16575
- raw = fs17.readFileSync(file, "utf-8");
16777
+ raw = fs18.readFileSync(file, "utf-8");
16576
16778
  } catch {
16577
16779
  return { persona, personaVersion: null };
16578
16780
  }
@@ -16909,7 +17111,7 @@ var CoderDeployCommand = class extends M8tCommand {
16909
17111
  projectEndpoint: project.endpoint,
16910
17112
  projectArmId: `${project.accountScope}/projects/${project.projectName}`,
16911
17113
  agentName: this.name,
16912
- personaPath: path16.join(resolveRepoRoot(), "personas", personaName, "persona.md"),
17114
+ personaPath: path17.join(resolveRepoRoot(), "personas", personaName, "persona.md"),
16913
17115
  bridgeUrl,
16914
17116
  onProgress
16915
17117
  });
@@ -17035,7 +17237,7 @@ var CoderTeardownCommand = class extends M8tCommand {
17035
17237
 
17036
17238
  // src/commands/azure-exec/deploy.ts
17037
17239
  init_esm2();
17038
- import * as path17 from "path";
17240
+ import * as path18 from "path";
17039
17241
  import { Command as Command30, Option as Option28 } from "clipanion";
17040
17242
  import { DefaultAzureCredential as DefaultAzureCredential15 } from "@azure/identity";
17041
17243
  init_errors();
@@ -17305,7 +17507,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
17305
17507
  kvUri
17306
17508
  });
17307
17509
  onProgress?.("a2a-enabling as a target\u2026");
17308
- const personaPath = path17.join(resolveRepoRoot(), "personas", "azure-executor", "persona.md");
17510
+ const personaPath = path18.join(resolveRepoRoot(), "personas", "azure-executor", "persona.md");
17309
17511
  const bridgeUrl = resolveBridgeUrl({
17310
17512
  flag: typeof this.gatewayUrl === "string" ? this.gatewayUrl : void 0,
17311
17513
  env: processEnv
@@ -17447,8 +17649,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17447
17649
  hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
17448
17650
  });
17449
17651
  }
17450
- const path32 = repo.replace(/^ghcr\.io\//, "");
17451
- const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path32}:pull`);
17652
+ const path37 = repo.replace(/^ghcr\.io\//, "");
17653
+ const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path37}:pull`);
17452
17654
  if (!tokenRes.ok) {
17453
17655
  throw new LocalCliError({
17454
17656
  code: "PLATFORM_GHCR_TOKEN_FAILED",
@@ -17464,7 +17666,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17464
17666
  hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
17465
17667
  });
17466
17668
  }
17467
- const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path32}/tags/list`, {
17669
+ const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path37}/tags/list`, {
17468
17670
  headers: { Authorization: `Bearer ${token}` }
17469
17671
  });
17470
17672
  if (!tagsRes.ok) {
@@ -17478,7 +17680,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17478
17680
  }
17479
17681
 
17480
17682
  // src/lib/release-channel.ts
17481
- import * as fs18 from "fs";
17683
+ import * as fs19 from "fs";
17482
17684
 
17483
17685
  // ../../packages/platform-release/dist/esm/manifest.js
17484
17686
  var SEVERITIES = ["critical", "recommended", "optional"];
@@ -17601,14 +17803,51 @@ function entityToStamp(e) {
17601
17803
 
17602
17804
  // ../../packages/platform-release/dist/esm/channel-url.js
17603
17805
  var CHANNEL_LATEST_URL = "https://github.com/m8t-run/m8t/releases/latest/download/manifest.json";
17604
- function channelUrlForVersion(tag) {
17605
- return `https://github.com/m8t-run/m8t/releases/download/${tag}/manifest.json`;
17806
+ function platformTag(version) {
17807
+ const bare = version.trim().replace(/^platform-/, "").replace(/^v/, "");
17808
+ return `platform-v${bare}`;
17809
+ }
17810
+ function channelUrlForVersion(version) {
17811
+ return `https://github.com/m8t-run/m8t/releases/download/${platformTag(version)}/manifest.json`;
17812
+ }
17813
+
17814
+ // ../../packages/platform-release/dist/esm/apply-request.js
17815
+ var APPLY_REQUEST_PK = "system";
17816
+ var APPLY_REQUEST_RK = "apply-request";
17817
+ var IN_FLIGHT = ["claimed", "applying"];
17818
+ function isInFlight(status) {
17819
+ return IN_FLIGHT.includes(status);
17820
+ }
17821
+ function applyRequestToEntity(r) {
17822
+ return { partitionKey: APPLY_REQUEST_PK, rowKey: APPLY_REQUEST_RK, value: JSON.stringify(r), updatedAt: r.updatedAt };
17823
+ }
17824
+ function entityToApplyRequest(e) {
17825
+ return JSON.parse(e.value);
17826
+ }
17827
+
17828
+ // ../../packages/platform-release/dist/esm/infra-params.js
17829
+ var INFRA_PARAMS_PK = "system";
17830
+ var INFRA_PARAMS_RK = "infra-params";
17831
+ function infraParamsToEntity(p) {
17832
+ return { partitionKey: INFRA_PARAMS_PK, rowKey: INFRA_PARAMS_RK, value: JSON.stringify(p) };
17833
+ }
17834
+ function entityToInfraParams(e) {
17835
+ return JSON.parse(e.value);
17606
17836
  }
17607
17837
 
17608
17838
  // src/lib/release-channel.ts
17609
17839
  init_errors();
17840
+ function manifestSourceForVersionTag(channelUrl, version) {
17841
+ const tag = platformTag(version);
17842
+ const custom = channelUrl.trim();
17843
+ if (!custom || custom === CHANNEL_LATEST_URL) return { channel: true, version: tag };
17844
+ if (custom.includes("/releases/latest/download/")) {
17845
+ return { url: custom.replace("/releases/latest/download/", `/releases/download/${tag}/`) };
17846
+ }
17847
+ return { url: custom };
17848
+ }
17610
17849
  async function fetchManifest(source, deps = {}) {
17611
- const readFile8 = deps.readFile ?? ((p) => fs18.readFileSync(p, "utf8"));
17850
+ const readFile8 = deps.readFile ?? ((p) => fs19.readFileSync(p, "utf8"));
17612
17851
  const fetchImpl = deps.fetchImpl ?? fetch;
17613
17852
  const ghToken = deps.ghToken ?? getGhToken;
17614
17853
  let raw;
@@ -17654,10 +17893,10 @@ async function fetchManifest(source, deps = {}) {
17654
17893
  }
17655
17894
 
17656
17895
  // src/lib/release-content.ts
17657
- import * as fs19 from "fs";
17896
+ import * as fs20 from "fs";
17658
17897
  import * as os8 from "os";
17659
- import * as path18 from "path";
17660
- import { execFileSync } from "child_process";
17898
+ import * as path19 from "path";
17899
+ import { execFileSync as execFileSync2 } from "child_process";
17661
17900
  init_errors();
17662
17901
  var OWNER_REPO = "m8t-run/m8t";
17663
17902
  function parseTreeResponse(body) {
@@ -17695,15 +17934,15 @@ function verifyPersonaTrees(manifest, tree) {
17695
17934
  }
17696
17935
  async function resolveReleaseContent(manifest, opts = {}) {
17697
17936
  if (opts.contentDir) {
17698
- if (!fs19.existsSync(path18.join(opts.contentDir, "personas"))) {
17937
+ if (!fs20.existsSync(path19.join(opts.contentDir, "personas"))) {
17699
17938
  throw new LocalCliError({ code: "PLATFORM_CONTENT_DIR_INVALID", message: `--content-dir '${opts.contentDir}' has no personas/ \u2014 not a repo checkout.` });
17700
17939
  }
17701
17940
  return opts.contentDir;
17702
17941
  }
17703
17942
  const commit = manifest.platform.commit;
17704
- const cacheRoot = opts.cacheRoot ?? path18.join(os8.homedir(), ".m8t-stack", "cache");
17705
- const dest = path18.join(cacheRoot, `release-${commit}`);
17706
- if (fs19.existsSync(path18.join(dest, "personas"))) return dest;
17943
+ const cacheRoot = opts.cacheRoot ?? path19.join(os8.homedir(), ".m8t-stack", "cache");
17944
+ const dest = path19.join(cacheRoot, `release-${commit}`);
17945
+ if (fs20.existsSync(path19.join(dest, "personas"))) return dest;
17707
17946
  const fetchImpl = opts.fetchImpl ?? fetch;
17708
17947
  const ghToken = opts.ghToken ?? getGhToken;
17709
17948
  let token;
@@ -17717,19 +17956,19 @@ async function resolveReleaseContent(manifest, opts = {}) {
17717
17956
  if (!res.ok) {
17718
17957
  throw new LocalCliError({ code: "PLATFORM_TARBALL_FETCH_FAILED", message: `GET ${url} \u2192 HTTP ${res.status.toString()}.` });
17719
17958
  }
17720
- fs19.mkdirSync(cacheRoot, { recursive: true });
17721
- const tmp = fs19.mkdtempSync(path18.join(cacheRoot, `.dl-${commit.slice(0, 8)}-`));
17959
+ fs20.mkdirSync(cacheRoot, { recursive: true });
17960
+ const tmp = fs20.mkdtempSync(path19.join(cacheRoot, `.dl-${commit.slice(0, 8)}-`));
17722
17961
  try {
17723
- const tgz = path18.join(tmp, "src.tar.gz");
17724
- fs19.writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
17725
- const unpacked = path18.join(tmp, "unpacked");
17726
- fs19.mkdirSync(unpacked);
17962
+ const tgz = path19.join(tmp, "src.tar.gz");
17963
+ fs20.writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
17964
+ const unpacked = path19.join(tmp, "unpacked");
17965
+ fs20.mkdirSync(unpacked);
17727
17966
  extractTarGz(tgz, unpacked);
17728
17967
  try {
17729
- fs19.renameSync(unpacked, dest);
17968
+ fs20.renameSync(unpacked, dest);
17730
17969
  } catch (e) {
17731
17970
  const err = e;
17732
- const isBenignRace = (err.code === "EEXIST" || err.code === "ENOTEMPTY") && fs19.existsSync(path18.join(dest, "personas"));
17971
+ const isBenignRace = (err.code === "EEXIST" || err.code === "ENOTEMPTY") && fs20.existsSync(path19.join(dest, "personas"));
17733
17972
  if (!isBenignRace) {
17734
17973
  throw new LocalCliError({
17735
17974
  code: "PLATFORM_CACHE_PUBLISH_FAILED",
@@ -17738,12 +17977,12 @@ async function resolveReleaseContent(manifest, opts = {}) {
17738
17977
  }
17739
17978
  }
17740
17979
  } finally {
17741
- fs19.rmSync(tmp, { recursive: true, force: true });
17980
+ fs20.rmSync(tmp, { recursive: true, force: true });
17742
17981
  }
17743
17982
  return dest;
17744
17983
  }
17745
17984
  function extractTarGz(tgz, into) {
17746
- execFileSync("tar", ["-xzf", tgz, "-C", into, "--strip-components=1"]);
17985
+ execFileSync2("tar", ["-xzf", tgz, "-C", into, "--strip-components=1"]);
17747
17986
  }
17748
17987
 
17749
17988
  // src/lib/platform-stamp.ts
@@ -17865,14 +18104,14 @@ function sleep2(ms) {
17865
18104
  }
17866
18105
 
17867
18106
  // src/lib/platform-converge.ts
17868
- import * as fs21 from "fs";
17869
- import * as path20 from "path";
18107
+ import * as fs22 from "fs";
18108
+ import * as path21 from "path";
17870
18109
  import { parse as parseYaml10 } from "yaml";
17871
18110
 
17872
18111
  // src/lib/deploy.ts
17873
- import * as fs20 from "fs/promises";
18112
+ import * as fs21 from "fs/promises";
17874
18113
  import * as os9 from "os";
17875
- import * as path19 from "path";
18114
+ import * as path20 from "path";
17876
18115
  init_errors();
17877
18116
  var FOUNDRY_TRACING_MODES = ["project", "account", "skip"];
17878
18117
  function parseFoundryTracingMode(v) {
@@ -17899,12 +18138,30 @@ function buildBicepParams(p) {
17899
18138
  `acrResourceId=${p.acrResourceId}`
17900
18139
  ];
17901
18140
  if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
18141
+ if (p.assignSubscriptionRoles !== void 0) params.push(`assignSubscriptionRoles=${String(p.assignSubscriptionRoles)}`);
18142
+ if (p.provisionUpdater !== void 0) params.push(`provisionUpdater=${String(p.provisionUpdater)}`);
18143
+ if (p.installerImage !== void 0) params.push(`installerImage=${p.installerImage}`);
18144
+ if (p.updateCron !== void 0) params.push(`updateCron=${p.updateCron}`);
18145
+ if (p.channelUrl !== void 0) params.push(`channelUrl=${p.channelUrl}`);
18146
+ if (p.gatewayCpu) params.push(`gatewayCpu=${p.gatewayCpu}`);
18147
+ if (p.gatewayMemory) params.push(`gatewayMemory=${p.gatewayMemory}`);
18148
+ if (p.refereeEnabled) params.push(`refereeEnabled=${p.refereeEnabled}`);
18149
+ if (p.refereeBrainRepos) params.push(`refereeBrainRepos=${p.refereeBrainRepos}`);
18150
+ if (p.refereeFeedRepo) params.push(`refereeFeedRepo=${p.refereeFeedRepo}`);
18151
+ if (p.refereeInstallationId) params.push(`refereeInstallationId=${p.refereeInstallationId}`);
18152
+ if (p.refereeWebhookHmacKvUri) params.push(`refereeWebhookHmacKvUri=${p.refereeWebhookHmacKvUri}`);
18153
+ if (p.examKvUri) params.push(`examKvUri=${p.examKvUri}`);
18154
+ if (p.examLaWorkspaceId) params.push(`examLaWorkspaceId=${p.examLaWorkspaceId}`);
18155
+ if (p.brainEvalDeployment) params.push(`brainEvalDeployment=${p.brainEvalDeployment}`);
18156
+ if (p.brainAppLogin) params.push(`brainAppLogin=${p.brainAppLogin}`);
18157
+ if (p.refereeCheckpointDir) params.push(`refereeCheckpointDir=${p.refereeCheckpointDir}`);
18158
+ if (p.examApiBase) params.push(`examApiBase=${p.examApiBase}`);
17902
18159
  return params;
17903
18160
  }
17904
18161
  async function resolveRepoRoot2(home = os9.homedir()) {
17905
- const marker = path19.join(home, ".m8t-stack", "repo-root");
18162
+ const marker = path20.join(home, ".m8t-stack", "repo-root");
17906
18163
  try {
17907
- const root = (await fs20.readFile(marker, "utf8")).trim();
18164
+ const root = (await fs21.readFile(marker, "utf8")).trim();
17908
18165
  if (!root) throw new Error("empty");
17909
18166
  return root;
17910
18167
  } catch {
@@ -17981,7 +18238,7 @@ function resolveAcrPullIdentity(opts) {
17981
18238
  return opts.explicit ?? "";
17982
18239
  }
17983
18240
  async function runBicepDeployment(opts) {
17984
- const bicepPath = path19.join(opts.repoRoot, "deploy", "main.bicep");
18241
+ const bicepPath = path20.join(opts.repoRoot, "deploy", "main.bicep");
17985
18242
  const result = JSON.parse(
17986
18243
  await runAz([
17987
18244
  "deployment",
@@ -18052,7 +18309,16 @@ function sortObj(o) {
18052
18309
  }
18053
18310
 
18054
18311
  // src/lib/platform-converge.ts
18055
- var ORDER = ["infra", "gateway", "codingAgent", "azureExecutor", "personas"];
18312
+ var ORDER = ["infra", "gateway", "codingAgent", "azureExecutor", "personas", "brainSeeds"];
18313
+ function seedSetRev(manifest) {
18314
+ const items = manifest.components.brainSeeds.items;
18315
+ const parts = [];
18316
+ for (const name of Object.keys(items).sort()) {
18317
+ const subs = items[name].subtrees;
18318
+ for (const sub of Object.keys(subs).sort()) parts.push(`${name}:${sub}:${subs[sub] ?? "-"}`);
18319
+ }
18320
+ return parts.join("|");
18321
+ }
18056
18322
  function infraTargetSha(manifest, tree) {
18057
18323
  return manifest.components.infra?.treeSha ?? tree.treeShaOf("deploy") ?? "";
18058
18324
  }
@@ -18088,6 +18354,16 @@ function diffPlan(manifest, stamp, tree, opts = {}) {
18088
18354
  }
18089
18355
  continue;
18090
18356
  }
18357
+ if (component === "brainSeeds") {
18358
+ const to = seedSetRev(manifest);
18359
+ const from2 = stamp?.components.brainSeeds?.seedRev ?? null;
18360
+ const targeted = opts.only === "brainSeeds";
18361
+ if (adopt) actions.push({ component, reason: "adopt", from: from2, to });
18362
+ else if (targeted) actions.push({ component, reason: from2 === to ? "forced" : "changed", from: from2, to });
18363
+ else if (from2 !== to) actions.push({ component, reason: "changed", from: from2, to });
18364
+ else skipped.push({ component, reason: "up-to-date" });
18365
+ continue;
18366
+ }
18091
18367
  const img = manifest.components[component];
18092
18368
  const cur = stamp?.components[component];
18093
18369
  if (cur?.state === "external") {
@@ -18131,6 +18407,7 @@ function installedValueOf(stamp, component, personaName) {
18131
18407
  if (!personaName || !Object.hasOwn(stamp.components.personas, personaName)) return "-";
18132
18408
  return stamp.components.personas[personaName].treeSha;
18133
18409
  }
18410
+ if (component === "brainSeeds") return stamp.components.brainSeeds?.seedRev ?? "-";
18134
18411
  return stamp.components[component].tag;
18135
18412
  }
18136
18413
  function targetValueOf(manifest, tree, component, personaName) {
@@ -18139,6 +18416,7 @@ function targetValueOf(manifest, tree, component, personaName) {
18139
18416
  if (!personaName || !Object.hasOwn(manifest.components.personas.items, personaName)) return "-";
18140
18417
  return manifest.components.personas.items[personaName].treeSha;
18141
18418
  }
18419
+ if (component === "brainSeeds") return seedSetRev(manifest) || "-";
18142
18420
  return manifest.components[component].tag;
18143
18421
  }
18144
18422
  async function applyPlan(plan, deps, ctx) {
@@ -18154,22 +18432,34 @@ async function applyPlan(plan, deps, ctx) {
18154
18432
  stamp.components.gateway = await deps.applyGateway(a, ctx);
18155
18433
  } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
18156
18434
  const r = await deps.applyAgent(a, ctx);
18157
- if (r !== "absent") {
18158
- stamp.components[a.component] = { ...r, state: "managed" };
18159
- } else {
18160
- ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 stamp left unchanged.`);
18435
+ if (r === "absent") {
18436
+ ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 skipped (not gated).`);
18437
+ continue;
18438
+ }
18439
+ stamp.components[a.component] = { ...r, state: "managed" };
18440
+ } else if (a.component === "brainSeeds") {
18441
+ if (!deps.applySeedRefresh) {
18442
+ ctx.onProgress?.("brainSeeds: seed refresh is not supported by this driver (CLI-only in v1) \u2014 skipped.");
18443
+ continue;
18161
18444
  }
18445
+ const r = await deps.applySeedRefresh(a, ctx);
18446
+ ctx.onProgress?.(`brainSeeds: delivered to ${r.delivered.toString()} repo(s), ${r.noop.toString()} already current.`);
18447
+ if (!r.converged) {
18448
+ ctx.onProgress?.("brainSeeds: not fully delivered (no Key Vault, or some repos failed) \u2014 stamp NOT advanced; will retry next converge.");
18449
+ continue;
18450
+ }
18451
+ stamp.components.brainSeeds = { seedRev: a.to };
18162
18452
  } else {
18163
18453
  if (!a.personaName) {
18164
18454
  throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
18165
18455
  }
18166
18456
  const r = await deps.applyPersona(a, ctx);
18167
18457
  if (r === "absent") {
18168
- ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 stamp left unchanged.`);
18169
- } else {
18170
- const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
18171
- stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
18458
+ ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 skipped (not gated).`);
18459
+ continue;
18172
18460
  }
18461
+ const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
18462
+ stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
18173
18463
  }
18174
18464
  applied.push(a);
18175
18465
  stamp.lastResult = "partial";
@@ -18186,6 +18476,9 @@ async function applyPlan(plan, deps, ctx) {
18186
18476
  }
18187
18477
  stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
18188
18478
  await deps.writeStamp(stamp);
18479
+ if (!isTargeted && ctx.suffix && deps.writeInfraParams) {
18480
+ await deps.writeInfraParams(ctx, ctx.suffix);
18481
+ }
18189
18482
  return { stamp, applied };
18190
18483
  } catch (e) {
18191
18484
  stamp.lastResult = "failed";
@@ -18208,7 +18501,14 @@ function seedStamp(prior, manifest, plan) {
18208
18501
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
18209
18502
  lastResult: "partial",
18210
18503
  cli: "",
18211
- components: { gateway: blankImg(), codingAgent: blankImg(), azureExecutor: blankImg(), personas: {}, infra: { treeSha: "" } }
18504
+ components: {
18505
+ gateway: blankImg(),
18506
+ codingAgent: blankImg(),
18507
+ azureExecutor: blankImg(),
18508
+ personas: {},
18509
+ infra: { treeSha: "" },
18510
+ brainSeeds: { seedRev: "" }
18511
+ }
18212
18512
  };
18213
18513
  }
18214
18514
  async function applyGatewayImage(a, ctx, gatewayResourceId) {
@@ -18277,7 +18577,8 @@ async function resolveBicepParamsForConverge(ctx, opts = {}) {
18277
18577
  foundryProjectEndpoint: opts.foundryEndpoint ?? "",
18278
18578
  acrPullIdentityResourceId,
18279
18579
  acrResourceId,
18280
- foundryTracingMode: opts.foundryTracingMode
18580
+ foundryTracingMode: opts.foundryTracingMode,
18581
+ ...opts.assignSubscriptionRoles !== void 0 ? { assignSubscriptionRoles: opts.assignSubscriptionRoles } : {}
18281
18582
  };
18282
18583
  }
18283
18584
  function swapHostedImage(def, newImage) {
@@ -18301,7 +18602,7 @@ async function applyAgentImage(a, ctx, opts) {
18301
18602
  function declaredFillableFields(personaPath) {
18302
18603
  let raw;
18303
18604
  try {
18304
- raw = fs21.readFileSync(personaPath, "utf8");
18605
+ raw = fs22.readFileSync(personaPath, "utf8");
18305
18606
  } catch {
18306
18607
  return [];
18307
18608
  }
@@ -18327,7 +18628,7 @@ function injectAndRender(personaPath, values, agentName) {
18327
18628
  function loaderText(loaderPath) {
18328
18629
  if (!loaderPath) return null;
18329
18630
  try {
18330
- return fs21.readFileSync(loaderPath, "utf8");
18631
+ return fs22.readFileSync(loaderPath, "utf8");
18331
18632
  } catch {
18332
18633
  return null;
18333
18634
  }
@@ -18344,7 +18645,7 @@ function brainRepoOf(current) {
18344
18645
  }
18345
18646
  async function applyPersona(a, ctx, opts) {
18346
18647
  const current = await getAgentVersion({ credential: ctx.credential, projectEndpoint: opts.project.endpoint, agentName: opts.agentName });
18347
- const personaPath = path20.join(opts.personaDir, "persona.md");
18648
+ const personaPath = path21.join(opts.personaDir, "persona.md");
18348
18649
  const yamlValues = readAgentYaml(opts.agentName)?.fillableFieldValues ?? null;
18349
18650
  const values = resolveFillableValues(current, yamlValues);
18350
18651
  const declared = declaredFillableFields(personaPath);
@@ -18543,8 +18844,411 @@ import { confirm as confirm5 } from "@inquirer/prompts";
18543
18844
  import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/identity";
18544
18845
 
18545
18846
  // src/lib/platform-converge-cli.ts
18546
- import * as path21 from "path";
18847
+ import * as path25 from "path";
18848
+
18849
+ // src/lib/platform-infra-params.ts
18850
+ import { TableClient as TableClient2 } from "@azure/data-tables";
18851
+ async function openInfraParamsTable(opts) {
18852
+ const { tableEndpoint } = await discoverStampStorage(opts);
18853
+ return new TableClient2(tableEndpoint, "Metadata", opts.credential);
18854
+ }
18855
+ async function readInfraParams(client) {
18856
+ try {
18857
+ return entityToInfraParams(await client.getEntity(INFRA_PARAMS_PK, INFRA_PARAMS_RK));
18858
+ } catch {
18859
+ return null;
18860
+ }
18861
+ }
18862
+ async function writeInfraParams(client, p) {
18863
+ await client.upsertEntity(infraParamsToEntity(p), "Replace");
18864
+ }
18865
+ async function deriveSuffixVerified(opts) {
18866
+ const list = JSON.parse(
18867
+ await runAz([
18868
+ "containerapp",
18869
+ "list",
18870
+ "-g",
18871
+ opts.resourceGroup,
18872
+ "--subscription",
18873
+ opts.subscriptionId,
18874
+ "--query",
18875
+ "[?starts_with(name, 'm8t-gateway-')].name",
18876
+ "-o",
18877
+ "json"
18878
+ ])
18879
+ );
18880
+ if (list.length !== 1) return null;
18881
+ const suffix = list[0].replace(/^m8t-gateway-/, "");
18882
+ const cae = JSON.parse(
18883
+ await runAz([
18884
+ "containerapp",
18885
+ "env",
18886
+ "list",
18887
+ "-g",
18888
+ opts.resourceGroup,
18889
+ "--subscription",
18890
+ opts.subscriptionId,
18891
+ "--query",
18892
+ `[?name=='m8t-cae-${suffix}'].name`,
18893
+ "-o",
18894
+ "json"
18895
+ ])
18896
+ );
18897
+ return cae.length === 1 ? suffix : null;
18898
+ }
18899
+
18900
+ // src/lib/platform-converge-cli.ts
18901
+ init_errors();
18902
+
18903
+ // src/lib/seed-refresh-driver.ts
18904
+ init_esm2();
18905
+ import * as path24 from "path";
18906
+
18907
+ // src/lib/seed-refresh.ts
18547
18908
  init_errors();
18909
+ import * as fs24 from "fs";
18910
+ import * as path23 from "path";
18911
+
18912
+ // src/lib/seed-merge.ts
18913
+ import * as fs23 from "fs";
18914
+ import * as os10 from "os";
18915
+ import * as path22 from "path";
18916
+ import { spawnSync as spawnSync3 } from "child_process";
18917
+ function isProbablyText(s) {
18918
+ return !s.includes("\0");
18919
+ }
18920
+ function mergeThreeWay(ours, base, theirs) {
18921
+ if (![ours, base, theirs].every(isProbablyText)) {
18922
+ return { ok: false, merged: "" };
18923
+ }
18924
+ const lf = (s) => s.replace(/\r\n/g, "\n");
18925
+ const dir = fs23.mkdtempSync(path22.join(os10.tmpdir(), "seed-merge-"));
18926
+ try {
18927
+ const o = path22.join(dir, "ours");
18928
+ const b = path22.join(dir, "base");
18929
+ const t = path22.join(dir, "theirs");
18930
+ fs23.writeFileSync(o, lf(ours));
18931
+ fs23.writeFileSync(b, lf(base));
18932
+ fs23.writeFileSync(t, lf(theirs));
18933
+ const res = spawnSync3(
18934
+ "git",
18935
+ ["merge-file", "-p", "--diff3", o, b, t],
18936
+ {
18937
+ encoding: "utf8",
18938
+ maxBuffer: 32 * 1024 * 1024
18939
+ }
18940
+ );
18941
+ if (res.status === 0) {
18942
+ return { ok: true, merged: res.stdout };
18943
+ }
18944
+ return { ok: false, merged: "" };
18945
+ } finally {
18946
+ fs23.rmSync(dir, { recursive: true, force: true });
18947
+ }
18948
+ }
18949
+ var skip = (path37, skipReason) => ({ path: path37, kind: "skip", skipReason });
18950
+ function classifyCandidate(c) {
18951
+ const { path: path37, base, ours, theirs } = c;
18952
+ if (theirs === base) return { path: path37, kind: "noop" };
18953
+ if (base === null) {
18954
+ if (theirs === null) return { path: path37, kind: "noop" };
18955
+ if (ours === null) return { path: path37, kind: "add", content: theirs };
18956
+ if (ours === theirs) return { path: path37, kind: "noop" };
18957
+ return skip(path37, "you already have a file at this path");
18958
+ }
18959
+ if (ours === null) {
18960
+ return theirs === null ? { path: path37, kind: "noop" } : skip(path37, "you removed this file; upstream changed it");
18961
+ }
18962
+ if (theirs === null) {
18963
+ return ours === base ? { path: path37, kind: "delete" } : skip(path37, "you edited this file; upstream removed it");
18964
+ }
18965
+ if (ours === base) return { path: path37, kind: "update", content: theirs };
18966
+ const m = mergeThreeWay(ours, base, theirs);
18967
+ return m.ok ? { path: path37, kind: "update", content: m.merged } : skip(path37, "your edits conflict with the update");
18968
+ }
18969
+
18970
+ // src/lib/seed-pr-body.ts
18971
+ function composeSeedPrTitle(s) {
18972
+ return `Update your ${s.advisorLabel}'s built-in skills & guides (${s.version})`;
18973
+ }
18974
+ var basename = (p) => p.split("/").filter(Boolean).slice(-2).join("/");
18975
+ function composeSeedPrBody(s) {
18976
+ const lines = [];
18977
+ lines.push(`This refreshes the skills and reference guides that came built into your ${s.advisorLabel}, bringing them up to date with platform release **${s.version}**.`);
18978
+ lines.push("");
18979
+ if (s.updated.length || s.added.length || s.deleted.length) {
18980
+ lines.push("**What changed**");
18981
+ if (s.updated.length) lines.push(`- \u270F\uFE0F Updated ${s.updated.length.toString()}: ${s.updated.map(basename).join(", ")}`);
18982
+ if (s.added.length) lines.push(`- \u2795 Added ${s.added.length.toString()}: ${s.added.map(basename).join(", ")}`);
18983
+ if (s.deleted.length) lines.push(`- \u{1F5D1}\uFE0F Removed ${s.deleted.length.toString()}: ${s.deleted.map(basename).join(", ")}`);
18984
+ lines.push("");
18985
+ }
18986
+ lines.push("**Your own content is untouched.** Only built-in files change here \u2014 nothing you or your advisor wrote (your memory, your inbox, skills your advisor learned on its own) is modified.");
18987
+ if (s.skipped.length) {
18988
+ lines.push("");
18989
+ lines.push(`**${s.skipped.length.toString()} file(s) were left as-is** because you've customized them and we won't overwrite your edits. To take an update anyway, open that file in the latest release and copy the changes over by hand:`);
18990
+ for (const sk of s.skipped) lines.push(`- \`${sk.path}\` \u2014 ${sk.skipReason}`);
18991
+ }
18992
+ lines.push("");
18993
+ lines.push("Your advisor's review workflow will check this PR automatically. Merge whenever you're ready.");
18994
+ lines.push("");
18995
+ lines.push(`\u2014 Opened by the m8t platform \xB7 release ${s.version}${s.notesUrl ? ` \xB7 [release notes](${s.notesUrl})` : ""}`);
18996
+ return lines.join("\n");
18997
+ }
18998
+
18999
+ // src/lib/seed-refresh.ts
19000
+ function readIf(dir, rel) {
19001
+ if (!dir) return null;
19002
+ const abs = path23.join(dir, rel);
19003
+ return fs24.existsSync(abs) ? fs24.readFileSync(abs, "utf8") : null;
19004
+ }
19005
+ async function assembleCandidates(args) {
19006
+ const paths = /* @__PURE__ */ new Set([...args.lineage?.ownedPaths ?? [], ...args.trees.ownedAtTheirs]);
19007
+ const out = [];
19008
+ for (const p of [...paths].sort((a, b) => a.localeCompare(b))) {
19009
+ out.push({ path: p, base: readIf(args.trees.baseDir, p), theirs: readIf(args.trees.theirsDir, p), ours: await args.source.readOurs(p) });
19010
+ }
19011
+ return out;
19012
+ }
19013
+ function assertNoDanglingIndexRefs(finalPaths, repoHas, indexBodies) {
19014
+ const refRe = /skills\/[^)\s]+\/SKILL\.md/g;
19015
+ for (const idx of indexBodies) {
19016
+ for (const m of idx.body.matchAll(refRe)) {
19017
+ const ref = m[0];
19018
+ if (!finalPaths.has(ref) && !repoHas(ref)) {
19019
+ throw new LocalCliError({
19020
+ code: "PLATFORM_SEED_INDEX_DANGLING",
19021
+ message: `Refusing to open a PR: ${idx.path} would reference a missing skill '${ref}'. The brain loader only opens indexed paths.`
19022
+ });
19023
+ }
19024
+ }
19025
+ }
19026
+ }
19027
+ async function refreshBrainRepo(args) {
19028
+ const cands = await assembleCandidates({ lineage: args.lineage, trees: args.trees, source: args.source, seedName: args.seedName });
19029
+ const outcomes = cands.map(classifyCandidate);
19030
+ const files = [];
19031
+ const updated = [];
19032
+ const added = [];
19033
+ const deleted = [];
19034
+ const skipped = [];
19035
+ const indexBodies = [];
19036
+ const finalPaths = /* @__PURE__ */ new Set();
19037
+ for (const o of outcomes) {
19038
+ if (o.path === SEED_MANIFEST_PATH) continue;
19039
+ if (o.kind === "update") {
19040
+ files.push({ path: o.path, content: o.content ?? "" });
19041
+ updated.push(o.path);
19042
+ finalPaths.add(o.path);
19043
+ } else if (o.kind === "add") {
19044
+ files.push({ path: o.path, content: o.content ?? "" });
19045
+ added.push(o.path);
19046
+ finalPaths.add(o.path);
19047
+ } else if (o.kind === "delete") {
19048
+ files.push({ path: o.path, delete: true });
19049
+ deleted.push(o.path);
19050
+ } else if (o.kind === "skip") {
19051
+ skipped.push({ path: o.path, skipReason: o.skipReason ?? "left as-is" });
19052
+ }
19053
+ if (o.path.endsWith("/_index.md") && (o.kind === "update" || o.kind === "add")) {
19054
+ indexBodies.push({ path: o.path, body: o.content ?? "" });
19055
+ }
19056
+ }
19057
+ const contentChanged = updated.length + added.length + deleted.length > 0;
19058
+ if (!contentChanged) {
19059
+ return { repo: args.repo, prUrl: null, action: "noop", updated, added, deleted, skipped };
19060
+ }
19061
+ const ownedNow = new Set(args.trees.ownedAtTheirs);
19062
+ assertNoDanglingIndexRefs(finalPaths, (p) => ownedNow.has(p), indexBodies);
19063
+ const newOwned = deriveOwnedPaths(args.trees.theirsDir);
19064
+ const nextManifest = upsertLineage(
19065
+ args.priorManifest ?? (args.lineage ? { schemaVersion: 1, seeds: [args.lineage] } : null),
19066
+ {
19067
+ name: args.seedName,
19068
+ refreshedToCommit: args.targetCommit ?? "",
19069
+ refreshedToVersion: args.version,
19070
+ ownedPaths: newOwned
19071
+ }
19072
+ );
19073
+ files.push({ path: SEED_MANIFEST_PATH, content: serializeSeedManifest(nextManifest) });
19074
+ for (const sb of args.supersedeBranches ?? []) {
19075
+ if (sb === args.branch) continue;
19076
+ const stale = await args.api.findOpenPr(sb);
19077
+ if (stale) await args.api.closePr(stale.number, `superseded by the newer update on \`${args.branch}\`.`);
19078
+ }
19079
+ const opened = await args.api.findOpenPr(args.branch);
19080
+ const headSha = await args.api.getHeadSha(args.base);
19081
+ await args.api.commitFromBase(files, args.branch, headSha, `chore(seed): refresh built-in skills & guides (${args.version})`);
19082
+ const summary = {
19083
+ advisorLabel: args.advisorLabel,
19084
+ version: args.version,
19085
+ notesUrl: args.notesUrl,
19086
+ repo: args.repo,
19087
+ updated,
19088
+ added,
19089
+ deleted,
19090
+ skipped
19091
+ };
19092
+ const title = composeSeedPrTitle(summary);
19093
+ const body = composeSeedPrBody(summary);
19094
+ if (opened) {
19095
+ await args.api.updatePr(opened.number, title, body);
19096
+ return { repo: args.repo, prUrl: opened.htmlUrl, action: "updated", updated, added, deleted, skipped };
19097
+ }
19098
+ const pr = await args.api.openPr(args.branch, args.base, title, body);
19099
+ return { repo: args.repo, prUrl: pr.htmlUrl, action: args.lineage ? "opened" : "bootstrapped", updated, added, deleted, skipped };
19100
+ }
19101
+
19102
+ // src/lib/seed-refresh-driver.ts
19103
+ var PERSONA_SEED_MAP = {
19104
+ "startup-advisor": "startup-advisor",
19105
+ "azure-advisor": "azure-advisor"
19106
+ };
19107
+ var ADVISOR_LABEL = {
19108
+ "startup-advisor": "Startup Advisor",
19109
+ "azure-advisor": "Azure Advisor"
19110
+ };
19111
+ function dedupeBrainRepos(agents) {
19112
+ const byRepo = /* @__PURE__ */ new Map();
19113
+ for (const a of agents) {
19114
+ const raw = a.metadata.brain;
19115
+ if (!raw) continue;
19116
+ let repo;
19117
+ let installationId;
19118
+ try {
19119
+ const parsed = JSON.parse(raw);
19120
+ repo = parsed.repo;
19121
+ installationId = parsed.installationId;
19122
+ } catch {
19123
+ repo = void 0;
19124
+ installationId = void 0;
19125
+ }
19126
+ if (!repo || !installationId) continue;
19127
+ const seedName = a.metadata.persona ? PERSONA_SEED_MAP[a.metadata.persona] ?? null : null;
19128
+ if (!byRepo.has(repo)) byRepo.set(repo, { repo, seedName, installationId });
19129
+ }
19130
+ return [...byRepo.values()];
19131
+ }
19132
+ function seedContentDir(contentRoot, manifest, seedName) {
19133
+ const items = manifest.components.brainSeeds.items;
19134
+ if (!Object.hasOwn(items, seedName)) return null;
19135
+ return path24.join(contentRoot, items[seedName].path);
19136
+ }
19137
+ function bindRepoApi(token, repo) {
19138
+ return {
19139
+ getDefaultBranch: () => Promise.resolve("main"),
19140
+ getHeadSha: async (branch) => {
19141
+ const res = await fetch(`https://api.github.com/repos/${repo}/git/ref/heads/${branch}`, {
19142
+ headers: { Authorization: `token ${token}`, Accept: "application/vnd.github+json", "User-Agent": "m8t" }
19143
+ });
19144
+ const j = await res.json();
19145
+ return j.object?.sha ?? "";
19146
+ },
19147
+ findOpenPr: (head) => findOpenPr({ token, repo, head }),
19148
+ commitFromBase: (files, branch, baseSha, message) => commitFilesFromBaseViaApp({ token, repo, branch, baseSha, files, message }),
19149
+ openPr: (head, base, title, body) => openPullRequestViaApp({ token, repo, base, head, title, body }),
19150
+ updatePr: (n, title, body) => updatePullRequestViaApp({ token, repo, number: n, title, body }),
19151
+ closePr: (n, comment) => closePullRequestViaApp({ token, repo, number: n, comment }),
19152
+ readFileAtRef: (path37, ref) => readRepoFileViaApp({ token, repo, path: path37, ref })
19153
+ };
19154
+ }
19155
+ async function runSeedRefresh(args) {
19156
+ const targetCommit = args.manifest.platform.commit;
19157
+ const theirsRoot = await resolveReleaseContent(args.manifest, {});
19158
+ const repos = dedupeBrainRepos(args.agents);
19159
+ const results = [];
19160
+ let delivered = 0;
19161
+ let noop2 = 0;
19162
+ let failed = 0;
19163
+ for (const ref of repos) {
19164
+ try {
19165
+ const minted = await mintInstallationToken({
19166
+ credential: args.credential,
19167
+ kvUri: args.kvUri,
19168
+ installationId: ref.installationId,
19169
+ repository: ref.repo,
19170
+ permissions: { contents: "write", pull_requests: "write" }
19171
+ });
19172
+ const token = minted.token;
19173
+ const probe = appRepoProbe({ token, repo: ref.repo });
19174
+ const state = await probeRepoState(probe);
19175
+ if (state !== "m8t-brain") {
19176
+ args.onWarn?.(`${ref.repo}: not a reachable m8t brain (${state}) \u2014 skipped.`);
19177
+ continue;
19178
+ }
19179
+ const markerText = await readRepoFileViaApp({ token, repo: ref.repo, path: SEED_MANIFEST_PATH });
19180
+ const marker = parseSeedManifest(markerText);
19181
+ const seedName = ref.seedName ?? marker?.seeds[0]?.name ?? null;
19182
+ if (!seedName) {
19183
+ args.onWarn?.(`${ref.repo}: cannot determine seed lineage \u2014 skipped.`);
19184
+ continue;
19185
+ }
19186
+ const theirsDir = seedContentDir(theirsRoot, args.manifest, seedName);
19187
+ if (!theirsDir) {
19188
+ args.onWarn?.(`${ref.repo}: seed '${seedName}' is not in this release \u2014 skipped.`);
19189
+ continue;
19190
+ }
19191
+ const advisorLabel = ADVISOR_LABEL[seedName] ?? seedName;
19192
+ let lineage = marker?.seeds.find((s) => s.name === seedName) ?? null;
19193
+ let baseDir = null;
19194
+ if (lineage) {
19195
+ try {
19196
+ const baseRoot = await resolveReleaseContent(
19197
+ { ...args.manifest, platform: { ...args.manifest.platform, commit: lineage.refreshedToCommit } },
19198
+ {}
19199
+ );
19200
+ baseDir = seedContentDir(baseRoot, args.manifest, seedName);
19201
+ } catch {
19202
+ baseDir = null;
19203
+ args.onWarn?.(`${ref.repo}: base rev ${lineage.refreshedToCommit} unfetchable \u2014 falling back to marker-only re-adopt.`);
19204
+ lineage = null;
19205
+ }
19206
+ } else {
19207
+ const initSha = await getInitialSeedCommitSha({ token, repo: ref.repo });
19208
+ if (initSha) {
19209
+ try {
19210
+ const baseRoot = await resolveReleaseContent(
19211
+ { ...args.manifest, platform: { ...args.manifest.platform, commit: initSha } },
19212
+ {}
19213
+ );
19214
+ baseDir = seedContentDir(baseRoot, args.manifest, seedName);
19215
+ lineage = baseDir ? { name: seedName, refreshedToCommit: initSha, refreshedToVersion: "(bootstrap)", ownedPaths: deriveOwnedPaths(baseDir) } : null;
19216
+ } catch {
19217
+ baseDir = null;
19218
+ }
19219
+ }
19220
+ }
19221
+ const trees = { baseDir, theirsDir, ownedAtTheirs: deriveOwnedPaths(theirsDir) };
19222
+ const source = { readOurs: (p) => readRepoFileViaApp({ token, repo: ref.repo, path: p }) };
19223
+ const res = await refreshBrainRepo({
19224
+ repo: ref.repo,
19225
+ branch: `m8t/seed-update-${args.version}`,
19226
+ base: "main",
19227
+ version: args.version,
19228
+ notesUrl: args.manifest.platform.notesUrl,
19229
+ advisorLabel,
19230
+ seedName,
19231
+ lineage,
19232
+ trees,
19233
+ source,
19234
+ api: bindRepoApi(token, ref.repo),
19235
+ targetCommit,
19236
+ priorManifest: marker
19237
+ });
19238
+ results.push(res);
19239
+ if (res.action === "noop") noop2++;
19240
+ else delivered++;
19241
+ args.onProgress?.(`${ref.repo}: ${res.action}${res.prUrl ? ` \u2192 ${res.prUrl}` : ""}`);
19242
+ } catch (e) {
19243
+ failed++;
19244
+ args.onWarn?.(`${ref.repo}: seed refresh failed \u2014 ${e.message}; skipped this repo.`);
19245
+ continue;
19246
+ }
19247
+ }
19248
+ return { delivered, noop: noop2, failed, results };
19249
+ }
19250
+
19251
+ // src/lib/platform-converge-cli.ts
18548
19252
  var HOSTED_AGENT_PERSONA_DIR = {
18549
19253
  codingAgent: "coding-agent",
18550
19254
  azureExecutor: "azure-executor"
@@ -18558,7 +19262,7 @@ async function buildConvergeDeps(args) {
18558
19262
  const persona = a.metadata.persona;
18559
19263
  if (persona) discovered[persona] = a.name;
18560
19264
  }
18561
- const loaderPath = path21.join(args.contentDir, "targets/foundry/brain-loader.md");
19265
+ const loaderPath = path25.join(args.contentDir, "targets/foundry/brain-loader.md");
18562
19266
  return {
18563
19267
  async applyPersona(a, ctx) {
18564
19268
  const personaName = a.personaName;
@@ -18574,7 +19278,7 @@ async function buildConvergeDeps(args) {
18574
19278
  return applyPersona(a, ctx, {
18575
19279
  agentName,
18576
19280
  project: args.project,
18577
- personaDir: path21.join(ctx.contentDir, personaItem.path),
19281
+ personaDir: path25.join(ctx.contentDir, personaItem.path),
18578
19282
  loaderPath
18579
19283
  });
18580
19284
  },
@@ -18603,7 +19307,23 @@ async function buildConvergeDeps(args) {
18603
19307
  },
18604
19308
  async applyInfra(a, ctx) {
18605
19309
  const target = infraTargetSha(ctx.manifest, args.tree);
18606
- return applyInfraDeploy(a, ctx, target, { suffix: args.suffix });
19310
+ const infraTable = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
19311
+ const stamped = (await readInfraParams(infraTable))?.bicep;
19312
+ args.onProgress?.("subscription-scoped modules skipped: updater is RG-scoped; sub-level changes require a founder-run CLI converge.");
19313
+ const opts = {
19314
+ suffix: args.suffix,
19315
+ assignSubscriptionRoles: false,
19316
+ ...stamped ? {
19317
+ tenantId: stamped.tenantId,
19318
+ clientId: stamped.clientId,
19319
+ foundryEndpoint: stamped.foundryProjectEndpoint,
19320
+ foundryResourceId: stamped.foundryResourceId,
19321
+ acrPullIdentity: stamped.acrPullIdentityResourceId,
19322
+ acrResourceId: stamped.acrResourceId,
19323
+ ...stamped.foundryTracingMode ? { foundryTracingMode: stamped.foundryTracingMode } : {}
19324
+ } : {}
19325
+ };
19326
+ return applyInfraDeploy(a, ctx, target, opts);
18607
19327
  },
18608
19328
  async healthGate(_ctx, applied) {
18609
19329
  for (const a of applied) {
@@ -18644,6 +19364,47 @@ async function buildConvergeDeps(args) {
18644
19364
  stamp: { ...s, cli: CLI_VERSION },
18645
19365
  onProgress: args.onProgress
18646
19366
  });
19367
+ },
19368
+ async writeInfraParams(ctx, _suffix) {
19369
+ const table = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
19370
+ const existing = await readInfraParams(table);
19371
+ if (!existing) {
19372
+ warn("infra-params re-stamp skipped: no existing row to refresh (deploy/enable-auto-update writes the authoritative row).");
19373
+ return;
19374
+ }
19375
+ await writeInfraParams(table, {
19376
+ ...existing,
19377
+ bicep: {
19378
+ ...existing.bicep,
19379
+ imageRef: `${ctx.manifest.components.gateway.ref}:${ctx.manifest.components.gateway.tag}`
19380
+ },
19381
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
19382
+ });
19383
+ },
19384
+ async applySeedRefresh(_a3, ctx) {
19385
+ let kvUri;
19386
+ try {
19387
+ kvUri = discoverKvUri(process.env);
19388
+ } catch {
19389
+ kvUri = void 0;
19390
+ }
19391
+ if (!kvUri) {
19392
+ warn("brainSeeds: no Key Vault URI (set AZURE_KEYVAULT_URI) \u2014 skipping seed refresh.");
19393
+ return { delivered: 0, noop: 0, converged: false };
19394
+ }
19395
+ const { delivered, noop: noop2, failed } = await runSeedRefresh({
19396
+ credential: args.credential,
19397
+ kvUri,
19398
+ manifest: ctx.manifest,
19399
+ agents: discoveredAgents,
19400
+ version: ctx.manifest.platform.version,
19401
+ onProgress: args.onProgress,
19402
+ onWarn: args.onWarn
19403
+ });
19404
+ if (failed > 0) {
19405
+ warn(`brainSeeds: ${failed.toString()} repo(s) failed to receive a seed update \u2014 re-run 'm8t platform update --only brainSeeds' to retry.`);
19406
+ }
19407
+ return { delivered, noop: noop2, converged: failed === 0 };
18647
19408
  }
18648
19409
  };
18649
19410
  }
@@ -18683,7 +19444,7 @@ var PlatformUpdateCommand = class extends M8tCommand {
18683
19444
  description: "Pin the platform version to converge to (defaults to the current release channel)."
18684
19445
  });
18685
19446
  only = Option30.String("--only", {
18686
- description: "Converge only one component: infra | gateway | codingAgent | azureExecutor | personas."
19447
+ description: "Converge only one component: infra | gateway | codingAgent | azureExecutor | personas | brainSeeds."
18687
19448
  });
18688
19449
  check = Option30.Boolean("--check", false);
18689
19450
  yes = Option30.Boolean("--yes", false);
@@ -18799,40 +19560,529 @@ var PlatformUpdateCommand = class extends M8tCommand {
18799
19560
  onProgress,
18800
19561
  onWarn
18801
19562
  });
18802
- await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress });
19563
+ await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress, suffix });
18803
19564
  if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: true }) + "\n");
18804
19565
  else log(colors.success(`converged \u2192 ${plan.targetVersion} \u2713`));
18805
19566
  return 0;
18806
19567
  }
18807
- renderPlan(plan, mode) {
18808
- if (mode === "json") return;
18809
- const rows = [
18810
- ...plan.actions.map((a) => ({
18811
- component: a.personaName ? `personas:${a.personaName}` : a.component,
18812
- from: a.from ?? "(none)",
18813
- to: a.to,
18814
- reason: a.reason
18815
- })),
18816
- ...plan.skipped.map((s) => ({
18817
- component: s.personaName ? `personas:${s.personaName}` : s.component,
18818
- from: "-",
18819
- to: "-",
18820
- reason: s.reason
18821
- }))
18822
- ];
18823
- this.context.stdout.write(
18824
- renderTable(rows, [
18825
- { key: "component", label: "COMPONENT" },
18826
- { key: "from", label: "FROM" },
18827
- { key: "to", label: "TO" },
18828
- { key: "reason", label: "REASON" }
18829
- ]) + "\n"
18830
- );
19568
+ renderPlan(plan, mode) {
19569
+ if (mode === "json") return;
19570
+ const rows = [
19571
+ ...plan.actions.map((a) => ({
19572
+ component: a.personaName ? `personas:${a.personaName}` : a.component,
19573
+ from: a.from ?? "(none)",
19574
+ to: a.to,
19575
+ reason: a.reason
19576
+ })),
19577
+ ...plan.skipped.map((s) => ({
19578
+ component: s.personaName ? `personas:${s.personaName}` : s.component,
19579
+ from: "-",
19580
+ to: "-",
19581
+ reason: s.reason
19582
+ }))
19583
+ ];
19584
+ this.context.stdout.write(
19585
+ renderTable(rows, [
19586
+ { key: "component", label: "COMPONENT" },
19587
+ { key: "from", label: "FROM" },
19588
+ { key: "to", label: "TO" },
19589
+ { key: "reason", label: "REASON" }
19590
+ ]) + "\n"
19591
+ );
19592
+ }
19593
+ };
19594
+
19595
+ // src/commands/platform/converge.ts
19596
+ import { Command as Command33 } from "clipanion";
19597
+
19598
+ // src/lib/platform-converge-mi.ts
19599
+ import { ManagedIdentityCredential } from "@azure/identity";
19600
+ init_errors();
19601
+ function resolveHeadlessContextFromEnv(env) {
19602
+ const need = (k) => {
19603
+ const v = (env[k] ?? "").trim();
19604
+ if (!v) throw new LocalCliError({ code: "HEADLESS_ENV_MISSING", message: `converge: required env ${k} is unset` });
19605
+ return v;
19606
+ };
19607
+ const miClientId = need("MI_CLIENT_ID");
19608
+ return {
19609
+ credential: new ManagedIdentityCredential({ clientId: miClientId }),
19610
+ miClientId,
19611
+ subscriptionId: need("SUBSCRIPTION_ID"),
19612
+ resourceGroup: need("RESOURCE_GROUP"),
19613
+ endpoint: need("FOUNDRY_ENDPOINT"),
19614
+ channelUrl: (env.M8T_UPDATE_CHANNEL_URL ?? "").trim() || CHANNEL_LATEST_URL
19615
+ };
19616
+ }
19617
+
19618
+ // src/lib/apply-request-store.ts
19619
+ import { TableClient as TableClient3 } from "@azure/data-tables";
19620
+ async function openApplyTable(opts) {
19621
+ const { tableEndpoint } = await discoverStampStorage(opts);
19622
+ return new TableClient3(tableEndpoint, "Metadata", opts.credential);
19623
+ }
19624
+ function hasStatus(e, n) {
19625
+ return e.statusCode === n;
19626
+ }
19627
+ async function readApplyRequest(client) {
19628
+ try {
19629
+ const row = await client.getEntity(APPLY_REQUEST_PK, APPLY_REQUEST_RK);
19630
+ return { ...entityToApplyRequest(row), etag: row.etag };
19631
+ } catch (e) {
19632
+ if (hasStatus(e, 404)) return null;
19633
+ throw e;
19634
+ }
19635
+ }
19636
+ async function claimApplyRequest(client, executionId, nowIso, leaseMs) {
19637
+ const cur = await readApplyRequest(client);
19638
+ if (!cur) return null;
19639
+ const leaseExpired = cur.status === "claimed" && cur.leaseUntil !== null && Date.parse(cur.leaseUntil) < Date.parse(nowIso);
19640
+ const claimable = cur.status === "pending" || cur.status === "awaiting-engine-update" || leaseExpired;
19641
+ if (!claimable) return null;
19642
+ const nowMs = Date.parse(nowIso);
19643
+ const leaseUntil = new Date((Number.isNaN(nowMs) ? Date.now() : nowMs) + leaseMs).toISOString();
19644
+ const claimed = { ...cur, status: "claimed", claimedBy: executionId, leaseUntil, updatedAt: nowIso };
19645
+ try {
19646
+ await client.updateEntity({ ...applyRequestToEntity(claimed), etag: cur.etag }, "Merge", { etag: cur.etag });
19647
+ return claimed;
19648
+ } catch (e) {
19649
+ if (hasStatus(e, 412)) return null;
19650
+ throw e;
19651
+ }
19652
+ }
19653
+ async function patchApplyRequest(client, mutate) {
19654
+ const cur = await readApplyRequest(client);
19655
+ if (!cur) return;
19656
+ const next = mutate(cur);
19657
+ await client.updateEntity({ ...applyRequestToEntity(next), etag: cur.etag }, "Merge", { etag: cur.etag });
19658
+ }
19659
+
19660
+ // src/lib/platform-self-update.ts
19661
+ import * as fs25 from "fs";
19662
+ import * as path26 from "path";
19663
+ function toVTag2(v) {
19664
+ return v.startsWith("v") ? v : `v${v}`;
19665
+ }
19666
+ function readBakedReleaseVersion(repoRoot) {
19667
+ if (!repoRoot) return null;
19668
+ try {
19669
+ const raw = fs25.readFileSync(path26.join(repoRoot, "installer", "version.json"), "utf8");
19670
+ const v = JSON.parse(raw).version;
19671
+ return typeof v === "string" && v.length > 0 ? v : null;
19672
+ } catch {
19673
+ return null;
19674
+ }
19675
+ }
19676
+ function needsEngineUpdate(manifest, runningVersion, bakedRelease) {
19677
+ if (runningVersion === "0.0.0-dev") return false;
19678
+ if (bakedRelease != null && bakedRelease.length > 0 && bakedRelease !== manifest.components.installer.version) return true;
19679
+ const running = toVTag2(runningVersion);
19680
+ const min = toVTag2(manifest.components.cli.min);
19681
+ return compareSemver(running, min) < 0;
19682
+ }
19683
+ async function updateOwnJobImage(opts) {
19684
+ const inst = opts.manifest.components.installer;
19685
+ const image = `${inst.ref}:${inst.tag}`;
19686
+ const job = opts.jobName ?? process.env.UPDATER_JOB_NAME;
19687
+ if (!job) throw new Error("UPDATER_JOB_NAME unset \u2014 cannot self-update the updater job image");
19688
+ await runAz([
19689
+ "containerapp",
19690
+ "job",
19691
+ "update",
19692
+ "-n",
19693
+ job,
19694
+ "-g",
19695
+ opts.resourceGroup,
19696
+ "--subscription",
19697
+ opts.subscriptionId,
19698
+ "--image",
19699
+ image,
19700
+ "--only-show-errors"
19701
+ ]);
19702
+ }
19703
+
19704
+ // src/lib/platform-health-gate.ts
19705
+ function nextBreaker(cur) {
19706
+ return cur === "none" ? "rolled-back" : "held";
19707
+ }
19708
+ function gateTargetsFor(applied) {
19709
+ const targets = [];
19710
+ for (const a of applied) {
19711
+ if (a.component === "gateway") {
19712
+ targets.push({ kind: "gateway-version", expected: a.to });
19713
+ } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
19714
+ targets.push({ kind: "agent-queryable", component: a.component });
19715
+ } else if (a.component === "personas" && a.personaName) {
19716
+ targets.push({ kind: "agent-queryable", persona: a.personaName });
19717
+ }
19718
+ }
19719
+ return targets;
19720
+ }
19721
+ function gatePassed(probes) {
19722
+ return probes.every((p) => p.ok);
19723
+ }
19724
+ async function runHealthGateWithRollback(args) {
19725
+ const notify = args.notify ?? (async () => {
19726
+ });
19727
+ const budgetMs = args.gateBudgetMs ?? 5 * 6e4;
19728
+ const target = args.plan.targetVersion;
19729
+ let applied = [];
19730
+ try {
19731
+ const result = await applyPlan(args.plan, args.deps, args.ctx);
19732
+ applied = result.applied;
19733
+ const outcomes = await runGate(args, applied, budgetMs);
19734
+ if (!gatePassed(outcomes)) {
19735
+ const detail = outcomes.find((o) => !o.ok)?.detail ?? "a post-apply health probe failed";
19736
+ return await rollbackOrHold(args, notify, detail);
19737
+ }
19738
+ await patchApplyRequest(args.client, (r) => ({
19739
+ ...r,
19740
+ status: "success",
19741
+ breaker: r.breaker,
19742
+ // unchanged — a clean apply never trips the breaker
19743
+ result: { appliedVersion: target, error: null },
19744
+ updatedAt: args.now().toISOString()
19745
+ }));
19746
+ await notify({ outcome: "success", target });
19747
+ return "success";
19748
+ } catch (e) {
19749
+ return await rollbackOrHold(args, notify, e.message);
19750
+ }
19751
+ }
19752
+ async function runGate(args, applied, budgetMs) {
19753
+ const deadline = args.now().getTime() + budgetMs;
19754
+ const probeGateway = args.probeGatewayVersion ?? defaultGatewayProbe;
19755
+ const probeAgent = args.probeAgentQueryable ?? defaultAgentProbe;
19756
+ const outcomes = [];
19757
+ for (const t of gateTargetsFor(applied)) {
19758
+ if (args.now().getTime() > deadline) {
19759
+ outcomes.push({ ok: false, detail: "health gate exceeded its time budget" });
19760
+ break;
19761
+ }
19762
+ if (t.kind === "gateway-version") {
19763
+ if (!args.gatewayUrl) continue;
19764
+ outcomes.push(await probeGateway(args.gatewayUrl, t.expected));
19765
+ } else {
19766
+ const agentName = "persona" in t ? t.persona : hostedAgentName(t.component);
19767
+ outcomes.push(await probeAgent({ credential: args.credential, endpoint: args.endpoint, agentName }));
19768
+ }
19769
+ }
19770
+ return outcomes;
19771
+ }
19772
+ async function rollbackOrHold(args, notify, error) {
19773
+ const target = args.plan.targetVersion;
19774
+ const rollbackTarget = args.stamp.previousPlatformVersion;
19775
+ const current = await readBreaker(args.client);
19776
+ const next = nextBreaker(current);
19777
+ if (next === "held") {
19778
+ await patchApplyRequest(args.client, (r) => ({
19779
+ ...r,
19780
+ status: "held",
19781
+ breaker: "held",
19782
+ result: { appliedVersion: r.result?.appliedVersion ?? null, error },
19783
+ updatedAt: args.now().toISOString()
19784
+ }));
19785
+ await notify({ outcome: "held", target, error });
19786
+ return "held";
19787
+ }
19788
+ if (rollbackTarget) {
19789
+ await reconvergeToPrevious(args, rollbackTarget);
19790
+ }
19791
+ await patchApplyRequest(args.client, (r) => ({
19792
+ ...r,
19793
+ status: "rolled-back",
19794
+ breaker: "rolled-back",
19795
+ result: { appliedVersion: rollbackTarget, error },
19796
+ updatedAt: args.now().toISOString()
19797
+ }));
19798
+ await notify({ outcome: "rolled-back", target, error });
19799
+ return "rolled-back";
19800
+ }
19801
+ async function reconvergeToPrevious(args, previousVersion) {
19802
+ const fetchManifestImpl = args.fetchManifestImpl ?? fetchManifest;
19803
+ const resolveContentImpl = args.resolveReleaseContentImpl ?? resolveReleaseContent;
19804
+ const fetchTreeImpl = args.fetchRepoTreeImpl ?? fetchRepoTree;
19805
+ const prevManifest = await fetchManifestImpl(manifestSourceForVersionTag(args.channelUrl ?? "", previousVersion));
19806
+ const contentDir = await resolveContentImpl(prevManifest, args.repoRoot ? { contentDir: args.repoRoot } : {});
19807
+ const tree = prevManifest.components.infra?.treeSha ? { treeShaOf: () => void 0 } : await fetchTreeImpl(prevManifest.platform.commit);
19808
+ const liveStamp = await args.deps.readStamp();
19809
+ const rollbackPlan = diffPlan(prevManifest, liveStamp, tree);
19810
+ const rollbackCtx = { ...args.ctx, manifest: prevManifest, contentDir };
19811
+ await applyPlan(rollbackPlan, args.deps, rollbackCtx);
19812
+ }
19813
+ async function defaultGatewayProbe(gatewayUrl, expected) {
19814
+ try {
19815
+ const res = await fetch(`${gatewayUrl}/api/version`, { method: "GET" });
19816
+ if (res.status === 401 || res.status === 403) return { ok: true };
19817
+ if (!res.ok) return { ok: false, detail: `gateway /api/version returned HTTP ${res.status.toString()}` };
19818
+ const body = await res.json().catch(() => ({}));
19819
+ if (body.version && !versionMatches(body.version, expected)) {
19820
+ return { ok: false, detail: `gateway reports version '${body.version}', expected '${expected}'` };
19821
+ }
19822
+ return { ok: true };
19823
+ } catch (e) {
19824
+ return { ok: false, detail: `gateway unreachable: ${e.message}` };
19825
+ }
19826
+ }
19827
+ function versionMatches(running, expected) {
19828
+ const norm = (s) => s.replace(/^v/, "").trim();
19829
+ const r = norm(running);
19830
+ const e = norm(expected);
19831
+ return r === e || r.startsWith(e) || e.startsWith(r);
19832
+ }
19833
+ async function defaultAgentProbe(args) {
19834
+ try {
19835
+ await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.endpoint, agentName: args.agentName });
19836
+ return { ok: true };
19837
+ } catch (e) {
19838
+ return { ok: false, detail: `agent '${args.agentName}' not queryable: ${e.message}` };
19839
+ }
19840
+ }
19841
+ function hostedAgentName(component) {
19842
+ return component === "codingAgent" ? "coder" : "azure-executor";
19843
+ }
19844
+ async function readBreaker(client) {
19845
+ const row = await readApplyRequest(client);
19846
+ return row?.breaker ?? "none";
19847
+ }
19848
+
19849
+ // src/commands/platform/converge.ts
19850
+ init_errors();
19851
+ function canonicalVersion(v) {
19852
+ const bare = v.replace(/^platform-/, "");
19853
+ return bare.startsWith("v") ? bare : `v${bare}`;
19854
+ }
19855
+ function isForwardTarget(target, installed) {
19856
+ if (!installed) return true;
19857
+ return compareSemver(canonicalVersion(target), canonicalVersion(installed)) >= 0;
19858
+ }
19859
+ async function failIfStillInFlight(client, now, error) {
19860
+ const cur = await readApplyRequest(client);
19861
+ if (!cur || !isInFlight(cur.status)) return;
19862
+ await patchApplyRequest(client, (r) => ({
19863
+ ...r,
19864
+ status: "failed",
19865
+ result: { appliedVersion: null, error },
19866
+ updatedAt: now()
19867
+ }));
19868
+ }
19869
+ var PlatformConvergeCommand = class extends M8tCommand {
19870
+ static paths = [["platform", "converge"]];
19871
+ static usage = Command33.Usage({
19872
+ category: "Platform",
19873
+ description: "Headless updater-job driver: claim a pending apply-request and converge the platform to it.",
19874
+ details: "The Managed-Identity-authenticated entry point run by the updater Container Apps job. One invocation is one tick: it claims the pending apply-request row (if any), refuses a downgrade against the installed stamp, self-fetches and validates the target manifest, self-updates its own engine image when the manifest requires a newer CLI, then drives the F02 converge engine behind the T15 health-gate + auto-rollback. Reads its subscription/RG/endpoint/channel from env (MI_CLIENT_ID, SUBSCRIPTION_ID, RESOURCE_GROUP, FOUNDRY_ENDPOINT, M8T_UPDATE_CHANNEL_URL). Not intended for interactive use \u2014 the founder-facing path is 'm8t platform update'."
19875
+ });
19876
+ async executeCommand() {
19877
+ const ctx = resolveHeadlessContextFromEnv(process.env);
19878
+ const executionId = process.env.CONTAINER_APP_JOB_EXECUTION_NAME ?? `exec-${process.pid.toString()}`;
19879
+ const now = () => (/* @__PURE__ */ new Date()).toISOString();
19880
+ const log = (m) => {
19881
+ this.context.stdout.write(m + "\n");
19882
+ };
19883
+ const client = await openApplyTable({
19884
+ credential: ctx.credential,
19885
+ subscriptionId: ctx.subscriptionId,
19886
+ resourceGroup: ctx.resourceGroup
19887
+ });
19888
+ const claimed = await claimApplyRequest(client, executionId, now(), 30 * 60 * 1e3);
19889
+ if (!claimed) {
19890
+ log("no pending apply-request \u2014 nothing to converge.");
19891
+ return 0;
19892
+ }
19893
+ log(`claimed apply-request \u2192 target ${claimed.target} (execution ${executionId}).`);
19894
+ const stamp = await readStamp({
19895
+ credential: ctx.credential,
19896
+ subscriptionId: ctx.subscriptionId,
19897
+ resourceGroup: ctx.resourceGroup
19898
+ });
19899
+ const installed = stamp ? stamp.platformVersion : null;
19900
+ if (!isForwardTarget(claimed.target, installed)) {
19901
+ await patchApplyRequest(client, (r) => ({
19902
+ ...r,
19903
+ status: "failed",
19904
+ result: { appliedVersion: null, error: `refused downgrade ${installed ?? "(none)"} \u2192 ${claimed.target}` },
19905
+ updatedAt: now()
19906
+ }));
19907
+ log(`refused: target ${claimed.target} is a downgrade from installed ${installed ?? "(none)"}.`);
19908
+ return 1;
19909
+ }
19910
+ const manifest = await fetchManifest(targetManifestSource(ctx, claimed.target));
19911
+ if (manifest.platform.version !== claimed.target) {
19912
+ await patchApplyRequest(client, (r) => ({
19913
+ ...r,
19914
+ status: "failed",
19915
+ result: { appliedVersion: null, error: `channel manifest is version ${manifest.platform.version}, expected the claimed target ${claimed.target}` },
19916
+ updatedAt: now()
19917
+ }));
19918
+ log(`refused: fetched manifest version ${manifest.platform.version} \u2260 claimed target ${claimed.target}.`);
19919
+ return 1;
19920
+ }
19921
+ const repoRoot = tryResolveRepoRoot();
19922
+ const bakedRelease = readBakedReleaseVersion(repoRoot);
19923
+ if (needsEngineUpdate(manifest, CLI_VERSION, bakedRelease)) {
19924
+ log(
19925
+ `self-updating the updater image (baked release ${bakedRelease ?? "(none)"} / engine ${CLI_VERSION} \u2192 target installer ${manifest.components.installer.version}, cli.min ${manifest.components.cli.min}).`
19926
+ );
19927
+ await updateOwnJobImage({
19928
+ subscriptionId: ctx.subscriptionId,
19929
+ resourceGroup: ctx.resourceGroup,
19930
+ manifest
19931
+ });
19932
+ await patchApplyRequest(client, (r) => ({
19933
+ ...r,
19934
+ status: "awaiting-engine-update",
19935
+ phase: "updating the updater engine image",
19936
+ updatedAt: now()
19937
+ }));
19938
+ log("updater image bumped \u2014 awaiting-engine-update. The next tick re-claims with the new engine.");
19939
+ return 0;
19940
+ }
19941
+ assertCliVersionOk(manifest, CLI_VERSION, (w) => this.context.stderr.write(w + "\n"));
19942
+ await patchApplyRequest(client, (r) => ({ ...r, status: "applying", phase: "starting", updatedAt: now() }));
19943
+ let plan;
19944
+ let applyCtx;
19945
+ let deps;
19946
+ try {
19947
+ const project = await resolveFoundryProject({
19948
+ credential: ctx.credential,
19949
+ subscriptionId: ctx.subscriptionId,
19950
+ interactive: false,
19951
+ endpoint: ctx.endpoint
19952
+ });
19953
+ const contentDir = await resolveReleaseContent(manifest, repoRoot ? { contentDir: repoRoot } : {});
19954
+ const tree = manifest.components.infra?.treeSha ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
19955
+ const suffix = await this.recoverSuffix(ctx);
19956
+ const gatewayResourceId = await this.resolveGatewayResourceId(ctx, suffix);
19957
+ deps = await buildConvergeDeps({
19958
+ credential: ctx.credential,
19959
+ subscriptionId: ctx.subscriptionId,
19960
+ resourceGroup: ctx.resourceGroup,
19961
+ contentDir,
19962
+ manifest,
19963
+ tree,
19964
+ project,
19965
+ gatewayResourceId,
19966
+ suffix,
19967
+ onProgress: (m) => {
19968
+ void patchApplyRequest(client, (r) => ({ ...r, phase: m, updatedAt: now() })).catch(() => void 0);
19969
+ log(m);
19970
+ },
19971
+ onWarn: (m) => this.context.stderr.write(m + "\n")
19972
+ });
19973
+ plan = diffPlan(manifest, stamp, tree, { forceInfra: true });
19974
+ applyCtx = {
19975
+ credential: ctx.credential,
19976
+ subscriptionId: ctx.subscriptionId,
19977
+ resourceGroup: ctx.resourceGroup,
19978
+ contentDir,
19979
+ manifest,
19980
+ suffix,
19981
+ onProgress: (m) => {
19982
+ log(m);
19983
+ }
19984
+ };
19985
+ } catch (e) {
19986
+ await failIfStillInFlight(client, now, e.message);
19987
+ throw e;
19988
+ }
19989
+ const outcome = await runHealthGateWithRollback({
19990
+ client,
19991
+ deps,
19992
+ plan,
19993
+ ctx: applyCtx,
19994
+ stamp: stamp ?? seedStampFor(plan),
19995
+ manifest,
19996
+ credential: ctx.credential,
19997
+ endpoint: ctx.endpoint,
19998
+ // The rollback re-converge needs the SAME zero-repo-auth inputs as the
19999
+ // forward path: baked content (repoRoot) + the channel to resolve the
20000
+ // PREVIOUS version's manifest (never GitHub repo auth). Without these the
20001
+ // rollback would 404 on the private pre-OSS repo just like the forward
20002
+ // path did before this fix.
20003
+ channelUrl: ctx.channelUrl,
20004
+ repoRoot,
20005
+ now: () => /* @__PURE__ */ new Date()
20006
+ });
20007
+ log(`converge outcome: ${outcome}.`);
20008
+ return outcome === "success" ? 0 : 1;
20009
+ }
20010
+ /**
20011
+ * Recover the real bicep suffix WITHOUT guessing: prefer the stamped
20012
+ * `system/infra-params` row (written by deploy + every full converge), and
20013
+ * fall back to deriving it from the live gateway container-app name — but only
20014
+ * when that derivation VERIFIES (exactly one gateway + a matching CAE). If
20015
+ * both fail, throw rather than provision a duplicate stack.
20016
+ */
20017
+ async recoverSuffix(ctx) {
20018
+ const infraTable = await openInfraParamsTable({
20019
+ credential: ctx.credential,
20020
+ subscriptionId: ctx.subscriptionId,
20021
+ resourceGroup: ctx.resourceGroup
20022
+ });
20023
+ const stamped = (await readInfraParams(infraTable))?.suffix;
20024
+ const suffix = stamped ?? await deriveSuffixVerified({
20025
+ credential: ctx.credential,
20026
+ subscriptionId: ctx.subscriptionId,
20027
+ resourceGroup: ctx.resourceGroup
20028
+ });
20029
+ if (!suffix) {
20030
+ throw new LocalCliError({
20031
+ code: "PLATFORM_INFRA_SUFFIX_UNRECOVERABLE",
20032
+ message: "Could not recover the deployment's resource-name suffix: no system/infra-params row is stamped, and deriving it from the live gateway did not verify (0 or >1 gateway, or no matching CAE).",
20033
+ hint: "Run a full 'm8t platform update --force-infra --suffix <suffix>' once to stamp system/infra-params, then re-run the updater."
20034
+ });
20035
+ }
20036
+ return suffix;
20037
+ }
20038
+ /** Resolve the gateway container-app ARM resource id for the recovered suffix. */
20039
+ async resolveGatewayResourceId(ctx, suffix) {
20040
+ const id = (await runAz([
20041
+ "containerapp",
20042
+ "show",
20043
+ "-n",
20044
+ `m8t-gateway-${suffix}`,
20045
+ "-g",
20046
+ ctx.resourceGroup,
20047
+ "--subscription",
20048
+ ctx.subscriptionId,
20049
+ "--query",
20050
+ "id",
20051
+ "-o",
20052
+ "tsv"
20053
+ ])).trim();
20054
+ if (!id) {
20055
+ throw new LocalCliError({
20056
+ code: "PLATFORM_GATEWAY_NOT_FOUND",
20057
+ message: `Could not resolve the gateway container app 'm8t-gateway-${suffix}' in resource group ${ctx.resourceGroup}.`
20058
+ });
20059
+ }
20060
+ return id;
18831
20061
  }
18832
20062
  };
20063
+ function seedStampFor(plan) {
20064
+ return {
20065
+ schemaVersion: 1,
20066
+ platformVersion: plan.targetVersion,
20067
+ previousPlatformVersion: null,
20068
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
20069
+ lastResult: "success",
20070
+ cli: CLI_VERSION,
20071
+ components: {
20072
+ gateway: { tag: "", digest: "", state: "managed" },
20073
+ codingAgent: { tag: "", digest: "", state: "managed" },
20074
+ azureExecutor: { tag: "", digest: "", state: "managed" },
20075
+ personas: {},
20076
+ infra: { treeSha: "" }
20077
+ }
20078
+ };
20079
+ }
20080
+ function targetManifestSource(ctx, target) {
20081
+ return manifestSourceForVersionTag(ctx.channelUrl, target);
20082
+ }
18833
20083
 
18834
20084
  // src/commands/platform/enable-cost-report.ts
18835
- import { Command as Command33, Option as Option31 } from "clipanion";
20085
+ import { Command as Command34, Option as Option31 } from "clipanion";
18836
20086
 
18837
20087
  // src/lib/wire-gateway-acs.ts
18838
20088
  init_rbac();
@@ -18872,7 +20122,7 @@ async function wireGatewayForAcs(args) {
18872
20122
  init_errors();
18873
20123
  var PlatformEnableCostReportCommand = class extends M8tCommand {
18874
20124
  static paths = [["platform", "enable-cost-report"]];
18875
- static usage = Command33.Usage({
20125
+ static usage = Command34.Usage({
18876
20126
  description: "Wire the deployed gateway to send the bi-weekly cost report via ACS Email.",
18877
20127
  details: "Discovers the live gateway Container App, grants its managed identity Contributor at the ACS resource scope (authorising the ACS Email send), and sets M8T_ACS_ENDPOINT / M8T_ACS_SENDER / M8T_ENABLE_COST_REPORTER=1 on the gateway. Idempotent \u2014 safely re-runnable. ACS is created during the executor deploy; pass its endpoint, sender, and resource id (from that deploy or 'az communication list')."
18878
20128
  });
@@ -18956,8 +20206,161 @@ var PlatformEnableCostReportCommand = class extends M8tCommand {
18956
20206
  }
18957
20207
  };
18958
20208
 
20209
+ // src/commands/platform/enable-auto-update.ts
20210
+ import { Command as Command35, Option as Option32 } from "clipanion";
20211
+ import { confirm as confirm6 } from "@inquirer/prompts";
20212
+ import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
20213
+ init_errors();
20214
+ var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
20215
+ static paths = [["platform", "enable-auto-update"]];
20216
+ static usage = Command35.Usage({
20217
+ category: "Platform",
20218
+ description: "Retrofit the auto-updater (MI + cron job + role assignments) onto an existing install.",
20219
+ details: "For an install that predates the Platform Update Framework: recovers the deployment's resource-name suffix (from the stamped system/infra-params row, or by verified derivation from the live gateway), recovers the other bicep params from live state (WITHOUT changing the deployed gateway image), and re-runs deploy/main.bicep with provisionUpdater=true \u2014 which provisions the updater managed identity, the cron Container Apps Job, and every role assignment (Owner@RG + Foundry User@account + Storage Table + Storage Blob + AcrPull), all scoped to the resource group. On success, backfills system/infra-params so future converges never have to re-derive the suffix. Idempotent \u2014 safe to re-run."
20220
+ });
20221
+ subscription = Option32.String("--subscription");
20222
+ resourceGroup = Option32.String("--resource-group", {
20223
+ description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20224
+ });
20225
+ suffix = Option32.String("--suffix", {
20226
+ description: "The existing deployment's resource-name suffix. Required when it cannot be recovered from system/infra-params or the live gateway."
20227
+ });
20228
+ installerImage = Option32.String("--installer-image", {
20229
+ required: true,
20230
+ description: "Updater CA-Job image ref (the converge engine at the current release). Required \u2014 an empty image would skip provisioning."
20231
+ });
20232
+ updateCron = Option32.String("--update-cron", {
20233
+ description: "Cron schedule for the updater job (bicep default applies when omitted)."
20234
+ });
20235
+ channelUrl = Option32.String("--channel-url", {
20236
+ description: "Release-channel URL the updater job polls (bicep default applies when omitted)."
20237
+ });
20238
+ endpoint = Option32.String("--endpoint", {
20239
+ description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
20240
+ });
20241
+ yes = Option32.Boolean("--yes", false);
20242
+ output = Option32.String("--output");
20243
+ async executeCommand() {
20244
+ const mode = resolveOutputMode(
20245
+ this.output,
20246
+ this.context.stdout
20247
+ );
20248
+ const interactive = this.context.stdout.isTTY === true;
20249
+ const log = (msg) => {
20250
+ if (mode !== "json") this.context.stdout.write(msg + "\n");
20251
+ };
20252
+ const onProgress = (m) => {
20253
+ log(colors.dim(m));
20254
+ };
20255
+ const gw = await resolveGatewayContext({
20256
+ interactive: mode !== "json",
20257
+ subscriptionId: this.subscription,
20258
+ resourceGroup: this.resourceGroup
20259
+ });
20260
+ const { resourceGroup, name: gatewayName } = parseContainerAppResourceId(gw.containerAppResourceId);
20261
+ const credential2 = new DefaultAzureCredential18();
20262
+ const account = await getAzAccount();
20263
+ const subscriptionId = this.subscription ?? account.subscriptionId;
20264
+ const explicitSuffix = typeof this.suffix === "string" && this.suffix.length > 0 ? this.suffix : void 0;
20265
+ onProgress("recovering the deployment's resource-name suffix\u2026");
20266
+ const infraTable = await openInfraParamsTable({ credential: credential2, subscriptionId, resourceGroup });
20267
+ const stampedParams = await readInfraParams(infraTable);
20268
+ const derivedSuffix = explicitSuffix ? void 0 : stampedParams?.suffix ?? await deriveSuffixVerified({ credential: credential2, subscriptionId, resourceGroup }) ?? void 0;
20269
+ const suffix = explicitSuffix ?? derivedSuffix;
20270
+ if (!suffix) {
20271
+ throw new LocalCliError({
20272
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_SUFFIX_UNRECOVERABLE",
20273
+ message: "Could not recover the deployment's resource-name suffix: no --suffix was given, no system/infra-params row is stamped, and deriving it from the live gateway did not verify (0 or >1 gateway, or no matching CAE).",
20274
+ hint: "Pass --suffix <existing-suffix> explicitly (from the deployed resource names, e.g. m8t-gateway-<suffix>)."
20275
+ });
20276
+ }
20277
+ if (!explicitSuffix && !stampedParams && !this.yes && interactive) {
20278
+ const ok = await confirm6({
20279
+ message: `Derived suffix '${suffix}' from the live gateway \u2014 proceed?`,
20280
+ default: true
20281
+ });
20282
+ if (!ok) {
20283
+ log(colors.dim("aborted \u2014 no change. Re-run with --suffix <suffix> to specify it explicitly."));
20284
+ return 0;
20285
+ }
20286
+ }
20287
+ onProgress("resolving Foundry project\u2026");
20288
+ const project = await resolveFoundryProject({
20289
+ credential: credential2,
20290
+ subscriptionId,
20291
+ interactive,
20292
+ endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
20293
+ });
20294
+ onProgress("reading the live gateway image (left unchanged)\u2026");
20295
+ const imageRef = (await runAz(["containerapp", "show", "-n", gatewayName, "-g", resourceGroup, "--query", "properties.template.containers[0].image", "-o", "tsv"])).trim();
20296
+ if (!imageRef) {
20297
+ throw new LocalCliError({
20298
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_NO_IMAGE",
20299
+ message: `Could not read the live image for gateway '${gatewayName}' in resource group ${resourceGroup}.`
20300
+ });
20301
+ }
20302
+ const location = (await runAz(["resource", "list", "-g", resourceGroup, "--subscription", subscriptionId, "--query", "[0].location", "-o", "tsv"])).trim();
20303
+ if (!location) {
20304
+ throw new LocalCliError({
20305
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_NO_LOCATION",
20306
+ message: `Could not resolve a location for resource group ${resourceGroup} \u2014 it may be empty.`
20307
+ });
20308
+ }
20309
+ const foundryResourceId = await resolveFoundryResourceId(project.endpoint);
20310
+ const acrPullIdentityResourceId = resolveAcrPullIdentity({});
20311
+ const acrResourceId = acrPullIdentityResourceId ? "" : await resolveAcrResourceId({ imageRef });
20312
+ const recoveredParams = {
20313
+ location,
20314
+ suffix,
20315
+ imageRef,
20316
+ tenantId: gw.gatewayTenantId,
20317
+ clientId: gw.gatewayClientId,
20318
+ foundryResourceId,
20319
+ foundryProjectEndpoint: project.endpoint,
20320
+ acrPullIdentityResourceId,
20321
+ acrResourceId
20322
+ };
20323
+ onProgress("running Bicep deployment to provision the updater (~2-4 min)\u2026");
20324
+ const repoRoot = await resolveRepoRoot2();
20325
+ await runBicepDeployment({
20326
+ resourceGroup,
20327
+ repoRoot,
20328
+ params: buildBicepParams({
20329
+ ...recoveredParams,
20330
+ provisionUpdater: true,
20331
+ installerImage: this.installerImage,
20332
+ ...typeof this.updateCron === "string" ? { updateCron: this.updateCron } : {},
20333
+ ...typeof this.channelUrl === "string" ? { channelUrl: this.channelUrl } : {}
20334
+ }),
20335
+ deploymentName: `m8t-enable-auto-update-${suffix.slice(0, 12)}`
20336
+ });
20337
+ onProgress("backfilling system/infra-params\u2026");
20338
+ await writeInfraParams(infraTable, {
20339
+ schemaVersion: 1,
20340
+ suffix,
20341
+ bicep: recoveredParams,
20342
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
20343
+ });
20344
+ if (mode === "json") {
20345
+ this.context.stdout.write(renderJson({ suffix, resourceGroup, installerImage: this.installerImage, enabled: true }) + "\n");
20346
+ return 0;
20347
+ }
20348
+ this.context.stdout.write(
20349
+ renderKeyValueBlock([
20350
+ { key: "suffix", value: suffix },
20351
+ { key: "resource group", value: resourceGroup },
20352
+ { key: "installer image", value: this.installerImage },
20353
+ { key: "infra-params", value: "backfilled" }
20354
+ ]) + "\n"
20355
+ );
20356
+ log(colors.success("auto-update enabled \u2014 the updater job will start converging on its next cron tick \u2713"));
20357
+ log(colors.dim("next: 'm8t platform status' to check drift, or 'm8t platform update' to converge interactively at any time."));
20358
+ return 0;
20359
+ }
20360
+ };
20361
+
18959
20362
  // src/commands/deploy.ts
18960
- import { Command as Command34, Option as Option32 } from "clipanion";
20363
+ import { Command as Command36, Option as Option33 } from "clipanion";
18961
20364
 
18962
20365
  // src/lib/app-reg.ts
18963
20366
  init_errors();
@@ -19218,7 +20621,7 @@ async function patchRedirectUris(appObjectId, fqdn) {
19218
20621
  init_errors();
19219
20622
 
19220
20623
  // src/lib/whatif.ts
19221
- import * as path22 from "path";
20624
+ import * as path27 from "path";
19222
20625
  function deriveResourceType(resourceId) {
19223
20626
  if (resourceId.startsWith("[")) return "";
19224
20627
  const afterProviders = resourceId.split("/providers/")[1];
@@ -19229,7 +20632,7 @@ function deriveResourceType(resourceId) {
19229
20632
  return parts.join("/");
19230
20633
  }
19231
20634
  async function runWhatIf(opts) {
19232
- const bicepPath = path22.join(opts.repoRoot, "deploy", "main.bicep");
20635
+ const bicepPath = path27.join(opts.repoRoot, "deploy", "main.bicep");
19233
20636
  const out = await runAz([
19234
20637
  "deployment",
19235
20638
  "group",
@@ -19294,9 +20697,9 @@ function flattenDelta(delta, prefix = "") {
19294
20697
  const out = [];
19295
20698
  for (const d of delta) {
19296
20699
  const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
19297
- const path32 = prefix ? `${prefix}${segment}` : d.path;
19298
- if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path32));
19299
- else out.push({ ...d, path: path32 });
20700
+ const path37 = prefix ? `${prefix}${segment}` : d.path;
20701
+ if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path37));
20702
+ else out.push({ ...d, path: path37 });
19300
20703
  }
19301
20704
  return out;
19302
20705
  }
@@ -19346,24 +20749,40 @@ function classifyWhatIf(changes) {
19346
20749
  var DEFAULT_IMAGE_REF = "ghcr.io/m8t-run/m8t:latest";
19347
20750
  var DeployCommand = class extends M8tCommand {
19348
20751
  static paths = [["deploy"]];
19349
- static usage = Command34.Usage({
20752
+ static usage = Command36.Usage({
19350
20753
  description: "Deploy (or update) the m8t gateway/webapp stack via Bicep.",
19351
20754
  details: "Ensures the Entra app reg (or pass --client-id to reuse an existing one \u2014 required if you can't create app regs), writes ~/.m8t-stack/config.yaml, ensures the resource group, and runs deploy/main.bicep. The repo is located via ~/.m8t-stack/repo-root."
19352
20755
  });
19353
- subscription = Option32.String("--subscription");
19354
- resourceGroup = Option32.String("--resource-group", "rg-m8t-stack");
19355
- location = Option32.String("--location", "eastus");
19356
- suffix = Option32.String("--suffix", "");
19357
- imageRef = Option32.String("--image-ref", DEFAULT_IMAGE_REF);
19358
- acrPullIdentity = Option32.String("--acrpull-identity");
19359
- acrResourceId = Option32.String("--acr-resource-id");
19360
- foundryEndpoint = Option32.String("--foundry-endpoint");
19361
- foundryResourceId = Option32.String("--foundry-resource-id");
19362
- foundryTracing = Option32.String("--foundry-tracing");
20756
+ subscription = Option33.String("--subscription");
20757
+ resourceGroup = Option33.String("--resource-group", "rg-m8t-stack");
20758
+ location = Option33.String("--location", "eastus");
20759
+ suffix = Option33.String("--suffix", "");
20760
+ imageRef = Option33.String("--image-ref", DEFAULT_IMAGE_REF);
20761
+ acrPullIdentity = Option33.String("--acrpull-identity");
20762
+ acrResourceId = Option33.String("--acr-resource-id");
20763
+ foundryEndpoint = Option33.String("--foundry-endpoint");
20764
+ foundryResourceId = Option33.String("--foundry-resource-id");
20765
+ foundryTracing = Option33.String("--foundry-tracing");
19363
20766
  // project | account | skip (bicep default: project)
19364
- clientId = Option32.String("--client-id");
19365
- whatIf = Option32.Boolean("--what-if", false);
19366
- output = Option32.String("--output");
20767
+ clientId = Option33.String("--client-id");
20768
+ whatIf = Option33.Boolean("--what-if", false);
20769
+ output = Option33.String("--output");
20770
+ // Referee (E2-F5) — all optional, undefined by default ⇒ the bicep defaults
20771
+ // apply (dormant: gatewayCpu=0.25, gatewayMemory=0.5Gi, every referee/exam var
20772
+ // empty). Only pass these when explicitly enabling the referee exam stack.
20773
+ gatewayCpu = Option33.String("--gateway-cpu");
20774
+ gatewayMemory = Option33.String("--gateway-memory");
20775
+ refereeEnabled = Option33.String("--referee-enabled");
20776
+ refereeBrainRepos = Option33.String("--referee-brain-repos");
20777
+ refereeFeedRepo = Option33.String("--referee-feed-repo");
20778
+ refereeInstallationId = Option33.String("--referee-installation-id");
20779
+ refereeWebhookHmacKvUri = Option33.String("--referee-webhook-hmac-kv-uri");
20780
+ examKvUri = Option33.String("--exam-kv-uri");
20781
+ examLaWorkspaceId = Option33.String("--exam-la-workspace-id");
20782
+ brainEvalDeployment = Option33.String("--brain-eval-deployment");
20783
+ brainAppLogin = Option33.String("--brain-app-login");
20784
+ refereeCheckpointDir = Option33.String("--referee-checkpoint-dir");
20785
+ examApiBase = Option33.String("--exam-api-base");
19367
20786
  async executeCommand() {
19368
20787
  const mode = resolveOutputMode(
19369
20788
  this.output,
@@ -19407,7 +20826,20 @@ var DeployCommand = class extends M8tCommand {
19407
20826
  foundryProjectEndpoint: foundryEndpoint,
19408
20827
  acrPullIdentityResourceId: acrPullIdentityResourceId2,
19409
20828
  acrResourceId: acrResourceId2,
19410
- foundryTracingMode: parseFoundryTracingMode(this.foundryTracing)
20829
+ foundryTracingMode: parseFoundryTracingMode(this.foundryTracing),
20830
+ gatewayCpu: this.gatewayCpu,
20831
+ gatewayMemory: this.gatewayMemory,
20832
+ refereeEnabled: this.refereeEnabled,
20833
+ refereeBrainRepos: this.refereeBrainRepos,
20834
+ refereeFeedRepo: this.refereeFeedRepo,
20835
+ refereeInstallationId: this.refereeInstallationId,
20836
+ refereeWebhookHmacKvUri: this.refereeWebhookHmacKvUri,
20837
+ examKvUri: this.examKvUri,
20838
+ examLaWorkspaceId: this.examLaWorkspaceId,
20839
+ brainEvalDeployment: this.brainEvalDeployment,
20840
+ brainAppLogin: this.brainAppLogin,
20841
+ refereeCheckpointDir: this.refereeCheckpointDir,
20842
+ examApiBase: this.examApiBase
19411
20843
  });
19412
20844
  const changes = await runWhatIf({ resourceGroup: this.resourceGroup, repoRoot: repoRoot2, params: params2 });
19413
20845
  const classified = classifyWhatIf(changes);
@@ -19442,7 +20874,20 @@ var DeployCommand = class extends M8tCommand {
19442
20874
  foundryProjectEndpoint: foundryEndpoint,
19443
20875
  acrPullIdentityResourceId,
19444
20876
  acrResourceId,
19445
- foundryTracingMode: parseFoundryTracingMode(this.foundryTracing)
20877
+ foundryTracingMode: parseFoundryTracingMode(this.foundryTracing),
20878
+ gatewayCpu: this.gatewayCpu,
20879
+ gatewayMemory: this.gatewayMemory,
20880
+ refereeEnabled: this.refereeEnabled,
20881
+ refereeBrainRepos: this.refereeBrainRepos,
20882
+ refereeFeedRepo: this.refereeFeedRepo,
20883
+ refereeInstallationId: this.refereeInstallationId,
20884
+ refereeWebhookHmacKvUri: this.refereeWebhookHmacKvUri,
20885
+ examKvUri: this.examKvUri,
20886
+ examLaWorkspaceId: this.examLaWorkspaceId,
20887
+ brainEvalDeployment: this.brainEvalDeployment,
20888
+ brainAppLogin: this.brainAppLogin,
20889
+ refereeCheckpointDir: this.refereeCheckpointDir,
20890
+ examApiBase: this.examApiBase
19446
20891
  });
19447
20892
  log("running Bicep deployment (~3-5 min)\u2026");
19448
20893
  const outputs = await runBicepDeployment({
@@ -19476,8 +20921,8 @@ var DeployCommand = class extends M8tCommand {
19476
20921
  };
19477
20922
 
19478
20923
  // src/commands/eval/skill.ts
19479
- import { spawnSync as spawnSync3 } from "child_process";
19480
- import { Command as Command35, Option as Option33 } from "clipanion";
20924
+ import { spawnSync as spawnSync4 } from "child_process";
20925
+ import { Command as Command37, Option as Option34 } from "clipanion";
19481
20926
  init_errors();
19482
20927
  var DECISIONS = /* @__PURE__ */ new Set(["promote", "reject", "needs_review"]);
19483
20928
  var JUDGE_STATUSES = /* @__PURE__ */ new Set(["ok", "skipped", "unavailable"]);
@@ -19502,14 +20947,14 @@ function parseVerdict(stdout) {
19502
20947
  }
19503
20948
  var EvalSkillCommand = class extends M8tCommand {
19504
20949
  static paths = [["eval", "skill"]];
19505
- static usage = Command35.Usage({
20950
+ static usage = Command37.Usage({
19506
20951
  description: "Vet one inbox skill candidate (Brain Faculties Eval F1): promote / reject / needs_review. Shells out to the Python `brain-eval` core (override its path with $BRAIN_EVAL_BIN)."
19507
20952
  });
19508
- candidate = Option33.String();
19509
- skillsDir = Option33.String("--skills-dir");
19510
- noJudge = Option33.Boolean("--no-judge", false);
19511
- deployment = Option33.String("--deployment");
19512
- output = Option33.String("--output");
20953
+ candidate = Option34.String();
20954
+ skillsDir = Option34.String("--skills-dir");
20955
+ noJudge = Option34.Boolean("--no-judge", false);
20956
+ deployment = Option34.String("--deployment");
20957
+ output = Option34.String("--output");
19513
20958
  executeCommand() {
19514
20959
  return Promise.resolve(this._runCommand());
19515
20960
  }
@@ -19528,7 +20973,7 @@ var EvalSkillCommand = class extends M8tCommand {
19528
20973
  const args = ["skill", candidate2, "--skills-dir", skillsDir, "--json"];
19529
20974
  if (this.noJudge === true) args.push("--no-judge");
19530
20975
  if (typeof this.deployment === "string") args.push("--deployment", this.deployment);
19531
- const res = spawnSync3(bin, args, { encoding: "utf-8" });
20976
+ const res = spawnSync4(bin, args, { encoding: "utf-8" });
19532
20977
  if (res.error) {
19533
20978
  throw new LocalCliError({
19534
20979
  code: "BRAIN_EVAL_SPAWN",
@@ -19565,10 +21010,10 @@ var EvalSkillCommand = class extends M8tCommand {
19565
21010
  };
19566
21011
 
19567
21012
  // src/commands/eval/exam.ts
19568
- import { spawnSync as spawnSync4 } from "child_process";
19569
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync15, existsSync as existsSync13, readdirSync } from "fs";
19570
- import { join as join22 } from "path";
19571
- import { Command as Command36, Option as Option34 } from "clipanion";
21013
+ import { spawnSync as spawnSync5 } from "child_process";
21014
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync17, existsSync as existsSync15, readdirSync as readdirSync2 } from "fs";
21015
+ import { join as join27 } from "path";
21016
+ import { Command as Command38, Option as Option35 } from "clipanion";
19572
21017
  init_errors();
19573
21018
  init_esm();
19574
21019
  function parseArmToken(tok, opts) {
@@ -19656,7 +21101,7 @@ function parseExamVerdict(stdout) {
19656
21101
  }
19657
21102
  return v;
19658
21103
  }
19659
- var DEFAULT_REPS = { impact: 3, dream_delta: 5 };
21104
+ var DEFAULT_REPS = { impact: 3, dream_delta: 5, skill_abstraction: 5 };
19660
21105
  function inferExamType(arms, explicit) {
19661
21106
  if (explicit !== void 0) {
19662
21107
  if (!EXAM_TYPES.has(explicit)) {
@@ -19677,6 +21122,22 @@ function inferExamType(arms, explicit) {
19677
21122
  message: "could not infer --exam-type from arms \u2014 pass --exam-type impact|dream_delta|tournament"
19678
21123
  });
19679
21124
  }
21125
+ function assertAbstractionArmsExplicit(examType, arms, skill) {
21126
+ if (examType !== "skill_abstraction") return;
21127
+ if (typeof skill !== "string" || skill.length === 0) {
21128
+ throw new LocalCliError({
21129
+ code: "USAGE",
21130
+ message: "--exam-type skill_abstraction requires --skill <slug> (the promoted skill's slug)"
21131
+ });
21132
+ }
21133
+ const bothPinned = arms.length === 2 && arms.every((a) => typeof a.state === "object" && "pinned" in a.state);
21134
+ if (!bothPinned) {
21135
+ throw new LocalCliError({
21136
+ code: "USAGE",
21137
+ message: "--exam-type skill_abstraction requires exactly 2 explicit pinned arms (the promotion event's base/merge shas), e.g. --arms 'pinned:<base>+pinned:<merge>'"
21138
+ });
21139
+ }
21140
+ }
19680
21141
  function resolveJudgeDeployment(flag, env) {
19681
21142
  if (typeof flag === "string" && flag.length > 0) return { deployment: flag };
19682
21143
  const fromEnv = env.EXAM_JUDGE_DEPLOYMENT;
@@ -19733,11 +21194,11 @@ function resolveRefereeHome(refereeHomeOut, env) {
19733
21194
  return null;
19734
21195
  }
19735
21196
  function resolveTaskSetDir(base, worker, version) {
19736
- if (existsSync13(join22(base, version))) return version;
21197
+ if (existsSync15(join27(base, version))) return version;
19737
21198
  const prefixed = `${worker}-${version}`;
19738
- if (existsSync13(join22(base, prefixed))) return prefixed;
19739
- if (version === "latest" && existsSync13(base)) {
19740
- const dirs = readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(`${worker}-`)).map((d) => d.name).sort();
21199
+ if (existsSync15(join27(base, prefixed))) return prefixed;
21200
+ if (version === "latest" && existsSync15(base)) {
21201
+ const dirs = readdirSync2(base, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(`${worker}-`)).map((d) => d.name).sort();
19741
21202
  if (dirs.length > 0) return dirs[dirs.length - 1];
19742
21203
  }
19743
21204
  return version;
@@ -19751,11 +21212,11 @@ function readSealedTaskIds(taskSetVersion, refereeHomeOut, env = process.env) {
19751
21212
  message: "cannot resolve the referee home to read the sealed-task manifest \u2014 set $REFEREE_HOME (mirroring the Python runner) so the feed can be redacted; refusing to emit a possibly-unredacted feed (Law-1)"
19752
21213
  });
19753
21214
  }
19754
- const versionDir = resolveTaskSetDir(join22(home, "tasksets", worker), worker, version);
19755
- const manifestPath = join22(home, "tasksets", worker, versionDir, "manifest.yaml");
21215
+ const versionDir = resolveTaskSetDir(join27(home, "tasksets", worker), worker, version);
21216
+ const manifestPath = join27(home, "tasksets", worker, versionDir, "manifest.yaml");
19756
21217
  let text;
19757
21218
  try {
19758
- text = readFileSync15(manifestPath, "utf-8");
21219
+ text = readFileSync17(manifestPath, "utf-8");
19759
21220
  } catch (e) {
19760
21221
  throw new LocalCliError({
19761
21222
  code: "EXAM_REDACTION_UNSAFE",
@@ -19782,23 +21243,24 @@ function buildPlan(args) {
19782
21243
  }
19783
21244
  var EvalExamCommand = class extends M8tCommand {
19784
21245
  static paths = [["eval", "exam"]];
19785
- static usage = Command36.Usage({
21246
+ static usage = Command38.Usage({
19786
21247
  description: "Run a brain exam (Brain Referee E2-F2): impact A/B + dream-delta. Resolves the plan, then shells out to the Python `brain-exam` orchestrator (override its path with $BRAIN_EXAM_BIN). Renders an ExamVerdict: three-valued verdict + power note + per-task flips."
19787
21248
  });
19788
- worker = Option34.String();
19789
- arms = Option34.String("--arms");
19790
- taskSet = Option34.String("--task-set");
19791
- examType = Option34.String("--exam-type");
19792
- reps = Option34.String("-n,--reps");
19793
- probes = Option34.String("--probes");
19794
- pool = Option34.String("--pool");
19795
- out = Option34.String("--out");
19796
- dryRun = Option34.Boolean("--dry-run", false);
19797
- keepArms = Option34.Boolean("--keep-arms", false);
19798
- allowStub = Option34.Boolean("--allow-stub", false);
19799
- deployment = Option34.String("--deployment");
19800
- output = Option34.String("--output");
19801
- observeWaitS = Option34.String("--observe-wait-s");
21249
+ worker = Option35.String();
21250
+ arms = Option35.String("--arms");
21251
+ taskSet = Option35.String("--task-set");
21252
+ examType = Option35.String("--exam-type");
21253
+ skill = Option35.String("--skill");
21254
+ reps = Option35.String("-n,--reps");
21255
+ probes = Option35.String("--probes");
21256
+ pool = Option35.String("--pool");
21257
+ out = Option35.String("--out");
21258
+ dryRun = Option35.Boolean("--dry-run", false);
21259
+ keepArms = Option35.Boolean("--keep-arms", false);
21260
+ allowStub = Option35.Boolean("--allow-stub", false);
21261
+ deployment = Option35.String("--deployment");
21262
+ output = Option35.String("--output");
21263
+ observeWaitS = Option35.String("--observe-wait-s");
19802
21264
  async executeCommand() {
19803
21265
  await Promise.resolve();
19804
21266
  const worker = typeof this.worker === "string" ? this.worker : void 0;
@@ -19807,6 +21269,7 @@ var EvalExamCommand = class extends M8tCommand {
19807
21269
  if (!armsRaw) throw new LocalCliError({ code: "USAGE", message: "--arms <spec> is required" });
19808
21270
  const arms = parseArmSpec(armsRaw, "git", { allowStub: this.allowStub === true });
19809
21271
  const examType = inferExamType(arms, typeof this.examType === "string" ? this.examType : void 0);
21272
+ assertAbstractionArmsExplicit(examType, arms, typeof this.skill === "string" ? this.skill : void 0);
19810
21273
  let reps = DEFAULT_REPS[examType] ?? 3;
19811
21274
  if (typeof this.reps === "string") {
19812
21275
  const parsed = Number.parseInt(this.reps, 10);
@@ -19860,7 +21323,7 @@ var EvalExamCommand = class extends M8tCommand {
19860
21323
  return 0;
19861
21324
  }
19862
21325
  const bin = process.env.BRAIN_EXAM_BIN ?? "brain-exam";
19863
- const res = spawnSync4(bin, ["run", "--plan", "-", "--json"], { encoding: "utf-8", input: renderJson(plan) });
21326
+ const res = spawnSync5(bin, ["run", "--plan", "-", "--json"], { encoding: "utf-8", input: renderJson(plan) });
19864
21327
  if (res.error) {
19865
21328
  throw new LocalCliError({
19866
21329
  code: "EXAM_SPAWN",
@@ -19873,10 +21336,10 @@ var EvalExamCommand = class extends M8tCommand {
19873
21336
  const verdict = parseExamVerdict(res.stdout);
19874
21337
  if (out !== void 0) {
19875
21338
  const sealedTaskIds = taskSetVersion ? readSealedTaskIds(taskSetVersion, out) : /* @__PURE__ */ new Set();
19876
- mkdirSync4(out, { recursive: true });
19877
- writeFileSync4(join22(out, "verdict.json"), JSON.stringify(verdict, null, 2));
21339
+ mkdirSync5(out, { recursive: true });
21340
+ writeFileSync6(join27(out, "verdict.json"), JSON.stringify(verdict, null, 2));
19878
21341
  const feed = redactForFeed(verdict, sealedTaskIds);
19879
- writeFileSync4(join22(out, "feed.json"), JSON.stringify(feed, null, 2));
21342
+ writeFileSync6(join27(out, "feed.json"), JSON.stringify(feed, null, 2));
19880
21343
  }
19881
21344
  if (mode === "json") {
19882
21345
  this.context.stdout.write(renderJson(verdict) + "\n");
@@ -19912,10 +21375,10 @@ var EvalExamCommand = class extends M8tCommand {
19912
21375
  };
19913
21376
 
19914
21377
  // src/commands/version.ts
19915
- import { Command as Command37, Option as Option35 } from "clipanion";
21378
+ import { Command as Command39, Option as Option36 } from "clipanion";
19916
21379
  var VersionCommand = class extends M8tCommand {
19917
21380
  static paths = [["version"], ["--version"], ["-v"]];
19918
- static usage = Command37.Usage({
21381
+ static usage = Command39.Usage({
19919
21382
  description: "Print the CLI version.",
19920
21383
  details: "Prints the m8t CLI version. With --verbose, also prints Node version and platform.",
19921
21384
  examples: [
@@ -19923,8 +21386,8 @@ var VersionCommand = class extends M8tCommand {
19923
21386
  ["Print as JSON", "$0 version --output json"]
19924
21387
  ]
19925
21388
  });
19926
- output = Option35.String("--output", { description: "pretty | json | auto (default)" });
19927
- verbose = Option35.Boolean("--verbose", false);
21389
+ output = Option36.String("--output", { description: "pretty | json | auto (default)" });
21390
+ verbose = Option36.Boolean("--verbose", false);
19928
21391
  executeCommand() {
19929
21392
  const mode = resolveOutputMode(
19930
21393
  this.output ?? "auto",
@@ -19955,18 +21418,18 @@ var VersionCommand = class extends M8tCommand {
19955
21418
  };
19956
21419
 
19957
21420
  // src/commands/whoami.ts
19958
- import { Command as Command38, Option as Option36 } from "clipanion";
21421
+ import { Command as Command40, Option as Option37 } from "clipanion";
19959
21422
  var WhoamiCommand = class extends M8tCommand {
19960
21423
  static paths = [["whoami"]];
19961
- static usage = Command38.Usage({
21424
+ static usage = Command40.Usage({
19962
21425
  description: "Show your identity + the gateway you'll talk to. Probes the backend."
19963
21426
  });
19964
- output = Option36.String("--output");
19965
- verbose = Option36.Boolean("--verbose", false);
19966
- subscription = Option36.String("--subscription", {
21427
+ output = Option37.String("--output");
21428
+ verbose = Option37.Boolean("--verbose", false);
21429
+ subscription = Option37.String("--subscription", {
19967
21430
  description: "Azure subscription ID to discover gateway in (defaults to active az subscription)."
19968
21431
  });
19969
- resourceGroup = Option36.String("--resource-group", {
21432
+ resourceGroup = Option37.String("--resource-group", {
19970
21433
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19971
21434
  });
19972
21435
  async executeCommand() {
@@ -20031,7 +21494,7 @@ var WhoamiCommand = class extends M8tCommand {
20031
21494
  };
20032
21495
 
20033
21496
  // src/commands/status.ts
20034
- import { Command as Command39, Option as Option37 } from "clipanion";
21497
+ import { Command as Command41, Option as Option38 } from "clipanion";
20035
21498
 
20036
21499
  // src/lib/azd.ts
20037
21500
  init_errors();
@@ -20096,10 +21559,10 @@ async function resolveLocalContext() {
20096
21559
  // src/commands/status.ts
20097
21560
  var StatusCommand = class extends M8tCommand {
20098
21561
  static paths = [["status"]];
20099
- static usage = Command39.Usage({
21562
+ static usage = Command41.Usage({
20100
21563
  description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
20101
21564
  });
20102
- output = Option37.String("--output");
21565
+ output = Option38.String("--output");
20103
21566
  async executeCommand() {
20104
21567
  const mode = resolveOutputMode(
20105
21568
  this.output,
@@ -20137,11 +21600,11 @@ var StatusCommand = class extends M8tCommand {
20137
21600
  };
20138
21601
 
20139
21602
  // src/commands/doctor.ts
20140
- import { Command as Command40, Option as Option38 } from "clipanion";
20141
- import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
20142
- import * as fs22 from "fs";
20143
- import * as os10 from "os";
20144
- import * as path23 from "path";
21603
+ import { Command as Command42, Option as Option39 } from "clipanion";
21604
+ import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
21605
+ import * as fs26 from "fs";
21606
+ import * as os11 from "os";
21607
+ import * as path28 from "path";
20145
21608
 
20146
21609
  // src/lib/doctor-checks.ts
20147
21610
  function checkAzSignedIn(az) {
@@ -20366,34 +21829,34 @@ async function resolveAccountLocation(accountId) {
20366
21829
  }
20367
21830
  }
20368
21831
  function probeRepoRoot() {
20369
- const marker = path23.join(os10.homedir(), ".m8t-stack", "repo-root");
20370
- if (!fs22.existsSync(marker)) {
21832
+ const marker = path28.join(os11.homedir(), ".m8t-stack", "repo-root");
21833
+ if (!fs26.existsSync(marker)) {
20371
21834
  return { markerExists: false, path: null, isDir: false, isGitCheckout: false };
20372
21835
  }
20373
21836
  let repoPath;
20374
21837
  try {
20375
- repoPath = fs22.readFileSync(marker, "utf8").trim();
21838
+ repoPath = fs26.readFileSync(marker, "utf8").trim();
20376
21839
  } catch {
20377
21840
  return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
20378
21841
  }
20379
21842
  if (!repoPath) return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
20380
21843
  let isDir = false;
20381
21844
  try {
20382
- isDir = fs22.statSync(repoPath).isDirectory();
21845
+ isDir = fs26.statSync(repoPath).isDirectory();
20383
21846
  } catch {
20384
21847
  isDir = false;
20385
21848
  }
20386
- const isGitCheckout = isDir && fs22.existsSync(path23.join(repoPath, ".git"));
21849
+ const isGitCheckout = isDir && fs26.existsSync(path28.join(repoPath, ".git"));
20387
21850
  return { markerExists: true, path: repoPath, isDir, isGitCheckout };
20388
21851
  }
20389
21852
  var DoctorCommand = class extends M8tCommand {
20390
21853
  static paths = [["doctor"]];
20391
- static usage = Command40.Usage({
21854
+ static usage = Command42.Usage({
20392
21855
  description: "Diagnose the local m8t setup: az login, config.yaml, gateway, Foundry data-plane."
20393
21856
  });
20394
- output = Option38.String("--output");
20395
- agent = Option38.String("--agent");
20396
- resourceGroup = Option38.String("--resource-group", {
21857
+ output = Option39.String("--output");
21858
+ agent = Option39.String("--agent");
21859
+ resourceGroup = Option39.String("--resource-group", {
20397
21860
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20398
21861
  });
20399
21862
  async executeCommand() {
@@ -20466,7 +21929,7 @@ var DoctorCommand = class extends M8tCommand {
20466
21929
  if (typeof this.agent === "string" && this.agent) {
20467
21930
  checking(`delivery grant for ${this.agent}`);
20468
21931
  try {
20469
- const credential2 = new DefaultAzureCredential18();
21932
+ const credential2 = new DefaultAzureCredential19();
20470
21933
  const cur = await getAgentVersion({
20471
21934
  credential: credential2,
20472
21935
  projectEndpoint: foundry.projectEndpoint,
@@ -20509,18 +21972,18 @@ var DoctorCommand = class extends M8tCommand {
20509
21972
  };
20510
21973
 
20511
21974
  // src/commands/switch.ts
20512
- import { Command as Command41, Option as Option39 } from "clipanion";
21975
+ import { Command as Command43, Option as Option40 } from "clipanion";
20513
21976
 
20514
21977
  // src/lib/profiles.ts
20515
- import * as fs23 from "fs/promises";
20516
- import * as path24 from "path";
21978
+ import * as fs27 from "fs/promises";
21979
+ import * as path29 from "path";
20517
21980
  import { parse as parseYaml11, stringify as stringifyYaml4 } from "yaml";
20518
21981
  function profilesDir() {
20519
21982
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20520
- return path24.join(home, ".m8t-stack", "profiles");
21983
+ return path29.join(home, ".m8t-stack", "profiles");
20521
21984
  }
20522
21985
  function getProfilePath(name) {
20523
- return path24.join(profilesDir(), `${path24.basename(name)}.yaml`);
21986
+ return path29.join(profilesDir(), `${path29.basename(name)}.yaml`);
20524
21987
  }
20525
21988
  function slugifyTenant(domainOrId) {
20526
21989
  const base = domainOrId.split(".")[0].toLowerCase();
@@ -20529,7 +21992,7 @@ function slugifyTenant(domainOrId) {
20529
21992
  }
20530
21993
  async function listProfiles() {
20531
21994
  try {
20532
- const entries = await fs23.readdir(profilesDir());
21995
+ const entries = await fs27.readdir(profilesDir());
20533
21996
  return entries.filter((e) => e.endsWith(".yaml")).map((e) => e.replace(/\.yaml$/, "")).sort();
20534
21997
  } catch (e) {
20535
21998
  if (e.code === "ENOENT") return [];
@@ -20538,7 +22001,7 @@ async function listProfiles() {
20538
22001
  }
20539
22002
  async function readProfile(name) {
20540
22003
  try {
20541
- const raw = await fs23.readFile(getProfilePath(name), "utf8");
22004
+ const raw = await fs27.readFile(getProfilePath(name), "utf8");
20542
22005
  const parsed = parseYaml11(raw);
20543
22006
  if (!parsed || typeof parsed !== "object") return null;
20544
22007
  return parsed;
@@ -20549,14 +22012,14 @@ async function readProfile(name) {
20549
22012
  }
20550
22013
  async function exists(p) {
20551
22014
  try {
20552
- await fs23.stat(p);
22015
+ await fs27.stat(p);
20553
22016
  return true;
20554
22017
  } catch {
20555
22018
  return false;
20556
22019
  }
20557
22020
  }
20558
22021
  async function saveProfile(p) {
20559
- await fs23.mkdir(profilesDir(), { recursive: true });
22022
+ await fs27.mkdir(profilesDir(), { recursive: true });
20560
22023
  let name = p.name;
20561
22024
  let n = 2;
20562
22025
  while (await exists(getProfilePath(name))) {
@@ -20565,8 +22028,8 @@ async function saveProfile(p) {
20565
22028
  }
20566
22029
  const target = getProfilePath(name);
20567
22030
  const tmp = `${target}.tmp`;
20568
- await fs23.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
20569
- await fs23.rename(tmp, target);
22031
+ await fs27.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
22032
+ await fs27.rename(tmp, target);
20570
22033
  return name;
20571
22034
  }
20572
22035
 
@@ -20649,14 +22112,14 @@ async function profileSwitch(name, asName) {
20649
22112
  init_errors();
20650
22113
  var SwitchCommand = class extends M8tCommand {
20651
22114
  static paths = [["switch"]];
20652
- static usage = Command41.Usage({
22115
+ static usage = Command43.Usage({
20653
22116
  description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
20654
22117
  });
20655
- profile = Option39.String({ required: false });
20656
- subscription = Option39.String("--subscription");
20657
- list = Option39.Boolean("--list", false);
20658
- as = Option39.String("--as");
20659
- output = Option39.String("--output");
22118
+ profile = Option40.String({ required: false });
22119
+ subscription = Option40.String("--subscription");
22120
+ list = Option40.Boolean("--list", false);
22121
+ as = Option40.String("--as");
22122
+ output = Option40.String("--output");
20660
22123
  async executeCommand() {
20661
22124
  const mode = resolveOutputMode(
20662
22125
  this.output,
@@ -20713,7 +22176,7 @@ var SwitchCommand = class extends M8tCommand {
20713
22176
 
20714
22177
  // src/commands/open.ts
20715
22178
  import { spawn as spawn5 } from "child_process";
20716
- import { Command as Command42, Option as Option40 } from "clipanion";
22179
+ import { Command as Command44, Option as Option41 } from "clipanion";
20717
22180
 
20718
22181
  // src/lib/open-targets.ts
20719
22182
  init_errors();
@@ -20759,14 +22222,14 @@ function openUrl(url) {
20759
22222
  }
20760
22223
  var OpenCommand = class extends M8tCommand {
20761
22224
  static paths = [["open"]];
20762
- static usage = Command42.Usage({
22225
+ static usage = Command44.Usage({
20763
22226
  description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
20764
22227
  details: "Targets: webapp (deployed app, default) | foundry (ai.azure.com) | portal (resource group in the Azure portal). Pass --print to emit the URL instead of launching a browser (also the default when stdout isn't a TTY)."
20765
22228
  });
20766
- target = Option40.String({ required: false });
20767
- print = Option40.Boolean("--print", false);
20768
- output = Option40.String("--output");
20769
- resourceGroup = Option40.String("--resource-group", {
22229
+ target = Option41.String({ required: false });
22230
+ print = Option41.Boolean("--print", false);
22231
+ output = Option41.String("--output");
22232
+ resourceGroup = Option41.String("--resource-group", {
20770
22233
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20771
22234
  });
20772
22235
  async executeCommand() {
@@ -20810,9 +22273,9 @@ var OpenCommand = class extends M8tCommand {
20810
22273
  };
20811
22274
 
20812
22275
  // src/commands/dream/run.ts
20813
- import { Command as Command43, Option as Option41 } from "clipanion";
22276
+ import { Command as Command45, Option as Option42 } from "clipanion";
20814
22277
  import { AzureCliCredential } from "@azure/identity";
20815
- import { TableClient as TableClient3 } from "@azure/data-tables";
22278
+ import { TableClient as TableClient5 } from "@azure/data-tables";
20816
22279
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
20817
22280
  import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
20818
22281
 
@@ -20952,6 +22415,8 @@ function odataFilter(worker, f) {
20952
22415
  parts.unshift(`PartitionKey eq '${esc2(worker)}'`);
20953
22416
  if (f.source !== "all")
20954
22417
  parts.push(`source eq '${esc2(f.source)}'`);
22418
+ if (f.foundryConversationId)
22419
+ parts.push(`foundryConversationId eq '${esc2(f.foundryConversationId)}'`);
20955
22420
  return parts.join(" and ");
20956
22421
  }
20957
22422
  async function fetchLedgerRows(filters, client) {
@@ -21119,7 +22584,7 @@ var ConvFetchError = class extends Error {
21119
22584
  };
21120
22585
 
21121
22586
  // ../../packages/brain/engine/dist/esm/cursor.js
21122
- import { TableClient as TableClient2 } from "@azure/data-tables";
22587
+ import { TableClient as TableClient4 } from "@azure/data-tables";
21123
22588
 
21124
22589
  // ../../packages/agent-ledger/dist/esm/row-key.js
21125
22590
  var MAX_MS = 864e13;
@@ -21241,7 +22706,7 @@ async function commitCursorAdvance(plan, cursor) {
21241
22706
  }
21242
22707
  var CURSOR_TABLE_NAME = "BrainDreamCursor";
21243
22708
  function makeTableCursor(credential2, tableEndpoint) {
21244
- const client = new TableClient2(tableEndpoint, CURSOR_TABLE_NAME, credential2);
22709
+ const client = new TableClient4(tableEndpoint, CURSOR_TABLE_NAME, credential2);
21245
22710
  return new TableCursor(client);
21246
22711
  }
21247
22712
  function formatCursorPreview(cursor) {
@@ -21735,7 +23200,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
21735
23200
  // ../../packages/brain/engine/dist/esm/dream/writer.js
21736
23201
  var API2 = "https://api.github.com";
21737
23202
  var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
21738
- var isAllowedWritePath = (path32) => WRITER_ALLOWED_PREFIX.test(path32) && !path32.split("/").includes("..");
23203
+ var isAllowedWritePath = (path37, allowedPrefix) => allowedPrefix.test(path37) && !path37.split("/").includes("..");
21739
23204
  var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
21740
23205
  function parseFrontmatter(content) {
21741
23206
  const m = /^---\n([\s\S]*?)\n---/.exec(content);
@@ -21774,8 +23239,12 @@ function parseFrontmatter(content) {
21774
23239
  function makeGitDataWriter(args) {
21775
23240
  const doFetch = args.fetchImpl ?? fetch;
21776
23241
  const repoPath = args.repository;
21777
- async function gh(token, method, path32, body) {
21778
- const res = await doFetch(`${API2}/repos/${repoPath}${path32}`, {
23242
+ const allowedPathPrefix = args.allowedPathPrefix ?? WRITER_ALLOWED_PREFIX;
23243
+ if (args.allowedPathPrefix && !args.allowedPathPrefix.source.startsWith("^")) {
23244
+ throw new Error(`makeGitDataWriter: allowedPathPrefix must be anchored with "^" (got /${args.allowedPathPrefix.source}/) \u2014 an unanchored regex would widen the B1 write-path allowlist`);
23245
+ }
23246
+ async function gh(token, method, path37, body) {
23247
+ const res = await doFetch(`${API2}/repos/${repoPath}${path37}`, {
21779
23248
  method,
21780
23249
  headers: {
21781
23250
  Authorization: `Bearer ${token}`,
@@ -21788,8 +23257,8 @@ function makeGitDataWriter(args) {
21788
23257
  const text = await res.text();
21789
23258
  return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
21790
23259
  }
21791
- async function readFileWith(token, path32) {
21792
- const encodedPath = path32.split("/").map(encodeURIComponent).join("/");
23260
+ async function readFileWith(token, path37) {
23261
+ const encodedPath = path37.split("/").map(encodeURIComponent).join("/");
21793
23262
  const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
21794
23263
  if (r.status === 404)
21795
23264
  return null;
@@ -21805,9 +23274,9 @@ function makeGitDataWriter(args) {
21805
23274
  throw new Error(`fetchTip: HTTP ${String(r.status)}`);
21806
23275
  return r.json.object.sha;
21807
23276
  },
21808
- async readFile(path32) {
23277
+ async readFile(path37) {
21809
23278
  const token = await args.mintToken();
21810
- return readFileWith(token, path32);
23279
+ return readFileWith(token, path37);
21811
23280
  },
21812
23281
  async listMemory() {
21813
23282
  const token = await args.mintToken();
@@ -21816,15 +23285,15 @@ function makeGitDataWriter(args) {
21816
23285
  return [];
21817
23286
  const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
21818
23287
  const out = [];
21819
- for (const path32 of paths) {
21820
- const content = await readFileWith(token, path32);
21821
- out.push({ path: path32, frontmatter: content ? parseFrontmatter(content) : {} });
23288
+ for (const path37 of paths) {
23289
+ const content = await readFileWith(token, path37);
23290
+ out.push({ path: path37, frontmatter: content ? parseFrontmatter(content) : {} });
21822
23291
  }
21823
23292
  return out;
21824
23293
  },
21825
23294
  async commitBatch(a) {
21826
23295
  try {
21827
- const escaping = a.changes.filter((c) => !isAllowedWritePath(c.path));
23296
+ const escaping = a.changes.filter((c) => !isAllowedWritePath(c.path, allowedPathPrefix));
21828
23297
  if (escaping.length > 0) {
21829
23298
  return {
21830
23299
  ok: false,
@@ -21876,9 +23345,11 @@ function makeFoundryModelClient(openai, model) {
21876
23345
  const create = openai.responses.create;
21877
23346
  const stream = await create({
21878
23347
  model,
23348
+ // Explicit `type: "message"` — the Responses API rejects untyped input
23349
+ // items (400 `Invalid value: ''`).
21879
23350
  input: [
21880
- { role: "system", content: system },
21881
- { role: "user", content: user }
23351
+ { type: "message", role: "system", content: system },
23352
+ { type: "message", role: "user", content: user }
21882
23353
  ],
21883
23354
  stream: true
21884
23355
  }, signal ? { signal } : void 0);
@@ -22255,7 +23726,7 @@ async function loadMemoryContext(brain) {
22255
23726
  return {
22256
23727
  files,
22257
23728
  indexEntries,
22258
- originOf: (path32) => originByPath.get(path32) ?? "worker"
23729
+ originOf: (path37) => originByPath.get(path37) ?? "worker"
22259
23730
  };
22260
23731
  }
22261
23732
 
@@ -22276,14 +23747,14 @@ function checkEvidenceMembership(delta, harvested) {
22276
23747
  }
22277
23748
  var BatchAbort = class extends Error {
22278
23749
  path;
22279
- constructor(path32, message) {
23750
+ constructor(path37, message) {
22280
23751
  super(message);
22281
- this.path = path32;
23752
+ this.path = path37;
22282
23753
  this.name = "BatchAbort";
22283
23754
  }
22284
23755
  };
22285
- var isIndexFile = (path32) => /(^|\/)[^/]*_index\.md$/.test(path32);
22286
- var isMemoryIndex = (path32) => path32 === "memory/MEMORY.md";
23756
+ var isIndexFile = (path37) => /(^|\/)[^/]*_index\.md$/.test(path37);
23757
+ var isMemoryIndex = (path37) => path37 === "memory/MEMORY.md";
22287
23758
  function targetPath(delta) {
22288
23759
  switch (delta.verb) {
22289
23760
  case "new":
@@ -22300,29 +23771,29 @@ function targetPath(delta) {
22300
23771
  }
22301
23772
  async function checkOriginTier(delta, lookup) {
22302
23773
  const evidence = evidenceOf(delta) ?? [];
22303
- const path32 = targetPath(delta);
22304
- if (path32 === null)
23774
+ const path37 = targetPath(delta);
23775
+ if (path37 === null)
22305
23776
  return { ok: true };
22306
- if (isIndexFile(path32) && !isMemoryIndex(path32)) {
22307
- return { ok: false, flagged: { kind: "flagged", path: path32, tension: `refused: ${path32} is an index file (only memory/MEMORY.md is editable)`, evidence } };
23777
+ if (isIndexFile(path37) && !isMemoryIndex(path37)) {
23778
+ return { ok: false, flagged: { kind: "flagged", path: path37, tension: `refused: ${path37} is an index file (only memory/MEMORY.md is editable)`, evidence } };
22308
23779
  }
22309
- const target = await lookup(path32);
22310
- const isStructural = path32.startsWith("references/");
23780
+ const target = await lookup(path37);
23781
+ const isStructural = path37.startsWith("references/");
22311
23782
  const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
22312
23783
  const editable = origin === "worker" || origin === "dream";
22313
23784
  if (editable)
22314
23785
  return { ok: true };
22315
23786
  const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
22316
23787
  if (removesOperatorTarget) {
22317
- throw new BatchAbort(path32, `supersede/retract OLD target ${path32} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
23788
+ throw new BatchAbort(path37, `supersede/retract OLD target ${path37} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
22318
23789
  }
22319
- return { ok: false, flagged: { kind: "flagged", path: path32, tension: `operator-origin (${origin}); flag-only`, evidence } };
23790
+ return { ok: false, flagged: { kind: "flagged", path: path37, tension: `operator-origin (${origin}); flag-only`, evidence } };
22320
23791
  }
22321
23792
  var ALLOWED_MEMORY = /^memory\/.+\.md$/;
22322
23793
  var ALLOWED_INBOX = /^inbox\//;
22323
23794
  var ALLOWED_QUARANTINE = /^quarantine\//;
22324
23795
  var ALLOWED_FLAG = /^memory\//;
22325
- var hasTraversal = (path32) => path32.split("/").includes("..");
23796
+ var hasTraversal = (path37) => path37.split("/").includes("..");
22326
23797
  function checkPathAllowed(delta) {
22327
23798
  const evidence = evidenceOf(delta) ?? [];
22328
23799
  const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
@@ -22429,7 +23900,7 @@ ${inject}
22429
23900
  ---
22430
23901
  `);
22431
23902
  }
22432
- var indexLine = (path32, title, date, tags) => `- \`${path32}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
23903
+ var indexLine = (path37, title, date, tags) => `- \`${path37}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
22433
23904
  function splitIndex(index) {
22434
23905
  const all = index.split("\n");
22435
23906
  const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
@@ -22602,16 +24073,16 @@ function defaultDreamSeams(opts = {}) {
22602
24073
  const mem = await memory(deps.brain);
22603
24074
  const harvested = harvestedIds(input);
22604
24075
  const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
22605
- const lookup = (path32) => Promise.resolve(mem.files.find((f) => f.path === path32) ?? null);
24076
+ const lookup = (path37) => Promise.resolve(mem.files.find((f) => f.path === path37) ?? null);
22606
24077
  const digest = [...pendingRejected];
22607
24078
  let changes = [];
22608
24079
  let indexEdit;
22609
24080
  try {
22610
24081
  for (const raw of deltas) {
22611
24082
  const delta = forcePolicyQuarantine(raw);
22612
- const path32 = checkPathAllowed(delta);
22613
- if (!path32.ok) {
22614
- digest.push(path32.rejected);
24083
+ const path37 = checkPathAllowed(delta);
24084
+ if (!path37.ok) {
24085
+ digest.push(path37.rejected);
22615
24086
  continue;
22616
24087
  }
22617
24088
  const ev = checkEvidenceMembership(delta, harvested);
@@ -22954,20 +24425,20 @@ function redactTranscripts(input) {
22954
24425
  }
22955
24426
  var DreamRunCommand = class extends M8tCommand {
22956
24427
  static paths = [["dream", "run"]];
22957
- static usage = Command43.Usage({
24428
+ static usage = Command45.Usage({
22958
24429
  description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes).",
22959
24430
  details: "Builds AzureCliCredential + resolves the Foundry project, ledger table, and Log Analytics workspace, runs the consumption pipeline, and prints the skip-ledger, the partition invariant, and harvest stats. Transcripts are metadata-only unless --show-transcripts is passed."
22960
24431
  });
22961
- worker = Option41.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
22962
- dryRun = Option41.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
22963
- since = Option41.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
22964
- reset = Option41.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
22965
- showTranscripts = Option41.Boolean("--show-transcripts", false, {
24432
+ worker = Option42.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
24433
+ dryRun = Option42.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
24434
+ since = Option42.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
24435
+ reset = Option42.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
24436
+ showTranscripts = Option42.Boolean("--show-transcripts", false, {
22966
24437
  description: "Print transcript bodies (default: metadata only)."
22967
24438
  });
22968
- subscription = Option41.String("--subscription");
22969
- endpoint = Option41.String("--endpoint");
22970
- output = Option41.String("--output");
24439
+ subscription = Option42.String("--subscription");
24440
+ endpoint = Option42.String("--endpoint");
24441
+ output = Option42.String("--output");
22971
24442
  // Resolution seam — overridden by tests; built lazily at runtime otherwise.
22972
24443
  deps;
22973
24444
  async executeCommand() {
@@ -23162,7 +24633,7 @@ AppDependencies
23162
24633
  };
23163
24634
  }
23164
24635
  function buildLiveSources(context, credential2) {
23165
- const ledgerClient = new TableClient3(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
24636
+ const ledgerClient = new TableClient5(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
23166
24637
  const ledger = makeLedgerSource(
23167
24638
  async (pk, sinceTs) => fetchLedgerRows(
23168
24639
  { workers: [pk], source: "all", from: sinceTs, to: "9999-12-31T23:59:59.999Z" },
@@ -23315,7 +24786,7 @@ function defaultDeps(overrides) {
23315
24786
  }
23316
24787
 
23317
24788
  // src/commands/foundry/create.ts
23318
- import { Command as Command44, Option as Option42 } from "clipanion";
24789
+ import { Command as Command46, Option as Option43 } from "clipanion";
23319
24790
 
23320
24791
  // src/lib/foundry-create.ts
23321
24792
  init_errors();
@@ -23553,7 +25024,7 @@ async function createFoundryProject(args) {
23553
25024
  init_errors();
23554
25025
  var FoundryCreateCommand = class extends M8tCommand {
23555
25026
  static paths = [["foundry", "create"]];
23556
- static usage = Command44.Usage({
25027
+ static usage = Command46.Usage({
23557
25028
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
23558
25029
  details: "Non-interactive and idempotent. Creates the AIServices account (custom subdomain + project management), a project, and a model deployment (default gpt-4.1-mini @ capacity 50). Region must be hosted-agent-eligible. Emits the project endpoint as structured output. Re-run is a clean no-op (account/project skipped if present; deployment capacity converges UP, never down).",
23559
25030
  examples: [
@@ -23562,16 +25033,16 @@ var FoundryCreateCommand = class extends M8tCommand {
23562
25033
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
23563
25034
  ]
23564
25035
  });
23565
- resourceGroup = Option42.String("--resource-group");
23566
- location = Option42.String("--location");
23567
- account = Option42.String("--account");
23568
- project = Option42.String("--project", "m8t");
23569
- model = Option42.String("--model", "gpt-4.1-mini");
23570
- modelVersion = Option42.String("--model-version", "2025-04-14");
23571
- capacity = Option42.String("--capacity", "50");
23572
- subscription = Option42.String("--subscription");
23573
- skipQuotaCheck = Option42.Boolean("--skip-quota-check", false);
23574
- output = Option42.String("--output");
25036
+ resourceGroup = Option43.String("--resource-group");
25037
+ location = Option43.String("--location");
25038
+ account = Option43.String("--account");
25039
+ project = Option43.String("--project", "m8t");
25040
+ model = Option43.String("--model", "gpt-4.1-mini");
25041
+ modelVersion = Option43.String("--model-version", "2025-04-14");
25042
+ capacity = Option43.String("--capacity", "50");
25043
+ subscription = Option43.String("--subscription");
25044
+ skipQuotaCheck = Option43.Boolean("--skip-quota-check", false);
25045
+ output = Option43.String("--output");
23575
25046
  async executeCommand() {
23576
25047
  const mode = resolveOutputMode(
23577
25048
  this.output,
@@ -23643,22 +25114,22 @@ var FoundryCreateCommand = class extends M8tCommand {
23643
25114
  };
23644
25115
 
23645
25116
  // src/commands/foundry/await-ready.ts
23646
- import { Command as Command45, Option as Option43 } from "clipanion";
25117
+ import { Command as Command47, Option as Option44 } from "clipanion";
23647
25118
  import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
23648
25119
  init_errors();
23649
25120
  var FoundryAwaitReadyCommand = class extends M8tCommand {
23650
25121
  static paths = [["foundry", "await-ready"]];
23651
- static usage = Command45.Usage({
25122
+ static usage = Command47.Usage({
23652
25123
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
23653
25124
  details: "Probes the project (GET /agents) until it returns 200 on a few consecutive tries, or fails clearly after a bounded budget. A newly-created account can serve intermittent 404 'Project not found' for minutes; run this after 'foundry create' and before deploying agents so the worker phase doesn't catch the unstable window.",
23654
25125
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
23655
25126
  });
23656
- endpoint = Option43.String("--endpoint");
23657
- consecutive = Option43.String("--consecutive", "3");
23658
- attempts = Option43.String("--attempts", "60");
23659
- interval = Option43.String("--interval", "5");
23660
- subscription = Option43.String("--subscription");
23661
- output = Option43.String("--output");
25127
+ endpoint = Option44.String("--endpoint");
25128
+ consecutive = Option44.String("--consecutive", "3");
25129
+ attempts = Option44.String("--attempts", "60");
25130
+ interval = Option44.String("--interval", "5");
25131
+ subscription = Option44.String("--subscription");
25132
+ output = Option44.String("--output");
23662
25133
  async executeCommand() {
23663
25134
  const mode = resolveOutputMode(this.output, this.context.stdout);
23664
25135
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -23692,7 +25163,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
23692
25163
  };
23693
25164
 
23694
25165
  // src/commands/bootstrap/preflight.ts
23695
- import { Command as Command46, Option as Option44 } from "clipanion";
25166
+ import { Command as Command48, Option as Option45 } from "clipanion";
23696
25167
  init_errors();
23697
25168
 
23698
25169
  // src/lib/bootstrap-preflight.ts
@@ -23766,7 +25237,7 @@ function buildPreflightBanner(who) {
23766
25237
  // src/commands/bootstrap/preflight.ts
23767
25238
  var BootstrapPreflightCommand = class extends M8tCommand {
23768
25239
  static paths = [["bootstrap", "preflight"]];
23769
- static usage = Command46.Usage({
25240
+ static usage = Command48.Usage({
23770
25241
  description: "Loudly verify you can install m8t-stack (Owner/UAA + directory admin) and hard-stop if not.",
23771
25242
  details: "Step 1 of `m8t bootstrap`. Prints an unmissable admin-credentials notice, then checks: Owner or User Access Administrator at subscription scope (hard requirement), directory-admin capability to register the app (or pass --client-id), and registers Microsoft.ContainerInstance. Exits non-zero with the exact failing check + remedy.",
23772
25243
  examples: [
@@ -23774,8 +25245,8 @@ var BootstrapPreflightCommand = class extends M8tCommand {
23774
25245
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
23775
25246
  ]
23776
25247
  });
23777
- clientId = Option44.String("--client-id");
23778
- subscription = Option44.String("--subscription");
25248
+ clientId = Option45.String("--client-id");
25249
+ subscription = Option45.String("--subscription");
23779
25250
  async executeCommand() {
23780
25251
  const clientId = typeof this.clientId === "string" ? this.clientId : void 0;
23781
25252
  const account = await getAzAccount();
@@ -23831,10 +25302,10 @@ ${colors.error(" " + why)}
23831
25302
  }
23832
25303
 
23833
25304
  // src/commands/bootstrap/launch.ts
23834
- import * as fs26 from "fs";
23835
- import * as os13 from "os";
23836
- import * as path27 from "path";
23837
- import { Command as Command47, Option as Option45 } from "clipanion";
25305
+ import * as fs30 from "fs";
25306
+ import * as os14 from "os";
25307
+ import * as path32 from "path";
25308
+ import { Command as Command49, Option as Option46 } from "clipanion";
23838
25309
  init_errors();
23839
25310
 
23840
25311
  // src/lib/bootstrap-mi.ts
@@ -23979,9 +25450,9 @@ async function kickInstaller(s) {
23979
25450
 
23980
25451
  // src/lib/bootstrap-status.ts
23981
25452
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
23982
- import * as fs24 from "fs/promises";
23983
- import * as os11 from "os";
23984
- import * as path25 from "path";
25453
+ import * as fs28 from "fs/promises";
25454
+ import * as os12 from "os";
25455
+ import * as path30 from "path";
23985
25456
  init_errors();
23986
25457
  var STATUS_CONTAINER = "status";
23987
25458
  var STATUS_BLOB = "status.json";
@@ -24020,7 +25491,7 @@ async function readStatusBlob(opts) {
24020
25491
  cause: e
24021
25492
  });
24022
25493
  }
24023
- const tmpFile = path25.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
25494
+ const tmpFile = path30.join(os12.tmpdir(), `m8t-status-${randomUUID2()}.json`);
24024
25495
  try {
24025
25496
  await runAz([
24026
25497
  "storage",
@@ -24039,7 +25510,7 @@ async function readStatusBlob(opts) {
24039
25510
  "--no-progress",
24040
25511
  "--only-show-errors"
24041
25512
  ]);
24042
- const raw = await fs24.readFile(tmpFile, "utf8");
25513
+ const raw = await fs28.readFile(tmpFile, "utf8");
24043
25514
  return JSON.parse(raw.trim());
24044
25515
  } catch (e) {
24045
25516
  throw new LocalCliError({
@@ -24049,34 +25520,34 @@ async function readStatusBlob(opts) {
24049
25520
  cause: e
24050
25521
  });
24051
25522
  } finally {
24052
- await fs24.rm(tmpFile, { force: true });
25523
+ await fs28.rm(tmpFile, { force: true });
24053
25524
  }
24054
25525
  }
24055
25526
 
24056
25527
  // src/lib/bootstrap-state.ts
24057
- import * as fs25 from "fs/promises";
24058
- import * as os12 from "os";
24059
- import * as path26 from "path";
25528
+ import * as fs29 from "fs/promises";
25529
+ import * as os13 from "os";
25530
+ import * as path31 from "path";
24060
25531
  function stateDir(home) {
24061
- return path26.join(home, ".m8t-stack");
25532
+ return path31.join(home, ".m8t-stack");
24062
25533
  }
24063
25534
  function statePath(home) {
24064
- return path26.join(stateDir(home), "bootstrap.json");
25535
+ return path31.join(stateDir(home), "bootstrap.json");
24065
25536
  }
24066
- async function readBootstrapState(home = os12.homedir()) {
25537
+ async function readBootstrapState(home = os13.homedir()) {
24067
25538
  try {
24068
- const raw = await fs25.readFile(statePath(home), "utf8");
25539
+ const raw = await fs29.readFile(statePath(home), "utf8");
24069
25540
  return JSON.parse(raw);
24070
25541
  } catch (e) {
24071
25542
  if (e.code === "ENOENT") return null;
24072
25543
  throw e;
24073
25544
  }
24074
25545
  }
24075
- async function writeBootstrapState(state, home = os12.homedir()) {
24076
- await fs25.mkdir(stateDir(home), { recursive: true });
25546
+ async function writeBootstrapState(state, home = os13.homedir()) {
25547
+ await fs29.mkdir(stateDir(home), { recursive: true });
24077
25548
  const tmp = `${statePath(home)}.tmp`;
24078
- await fs25.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
24079
- await fs25.rename(tmp, statePath(home));
25549
+ await fs29.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
25550
+ await fs29.rename(tmp, statePath(home));
24080
25551
  }
24081
25552
 
24082
25553
  // src/commands/bootstrap/launch.ts
@@ -24087,7 +25558,7 @@ var ACI_NAME = "m8t-installer";
24087
25558
  var MI_NAME = "m8t-installer-mi";
24088
25559
  var BootstrapLaunchCommand = class extends M8tCommand {
24089
25560
  static paths = [["bootstrap", "launch"]];
24090
- static usage = Command47.Usage({
25561
+ static usage = Command49.Usage({
24091
25562
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
24092
25563
  details: "Step 2 of `m8t bootstrap` (run after `preflight`). Ensures the resource group, creates the m8t-stack app registration (or uses --client-id), creates a user-assigned managed identity granted Owner at subscription scope, and launches the published m8t-installer image as an ACI run-to-completion job under that identity. Writes ~/.m8t-stack/bootstrap.json for `status`/`reap`/`finish`.",
24093
25564
  examples: [
@@ -24096,13 +25567,13 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24096
25567
  ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.0"]
24097
25568
  ]
24098
25569
  });
24099
- location = Option45.String("--location");
24100
- resourceGroup = Option45.String("--resource-group");
24101
- clientId = Option45.String("--client-id");
24102
- subscription = Option45.String("--subscription");
24103
- installerTag = Option45.String("--installer-tag");
24104
- gatewayImageRef = Option45.String("--gateway-image-ref");
24105
- githubAppCreds = Option45.String("--github-app-creds");
25570
+ location = Option46.String("--location");
25571
+ resourceGroup = Option46.String("--resource-group");
25572
+ clientId = Option46.String("--client-id");
25573
+ subscription = Option46.String("--subscription");
25574
+ installerTag = Option46.String("--installer-tag");
25575
+ gatewayImageRef = Option46.String("--gateway-image-ref");
25576
+ githubAppCreds = Option46.String("--github-app-creds");
24106
25577
  async executeCommand() {
24107
25578
  const location = typeof this.location === "string" ? this.location : void 0;
24108
25579
  if (!location) {
@@ -24116,12 +25587,12 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24116
25587
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
24117
25588
  const out = (m) => this.context.stderr.write(` ${colors.dim(m)}
24118
25589
  `);
24119
- const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path27.join(os13.homedir(), ".m8t-stack", "github-app.json");
25590
+ const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path32.join(os14.homedir(), ".m8t-stack", "github-app.json");
24120
25591
  let githubApp;
24121
- if (fs26.existsSync(credsPath)) {
25592
+ if (fs30.existsSync(credsPath)) {
24122
25593
  let parsed;
24123
25594
  try {
24124
- parsed = JSON.parse(fs26.readFileSync(credsPath, "utf8"));
25595
+ parsed = JSON.parse(fs30.readFileSync(credsPath, "utf8"));
24125
25596
  } catch {
24126
25597
  throw new LocalCliError({
24127
25598
  code: "GITHUB_APP_CREDS_INVALID",
@@ -24131,7 +25602,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24131
25602
  }
24132
25603
  let pem;
24133
25604
  try {
24134
- pem = fs26.readFileSync(parsed.pemPath, "utf8");
25605
+ pem = fs30.readFileSync(parsed.pemPath, "utf8");
24135
25606
  } catch {
24136
25607
  throw new LocalCliError({
24137
25608
  code: "GITHUB_APP_PEM_MISSING",
@@ -24203,7 +25674,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24203
25674
  };
24204
25675
 
24205
25676
  // src/commands/bootstrap/status.ts
24206
- import { Command as Command48, Option as Option46 } from "clipanion";
25677
+ import { Command as Command50, Option as Option47 } from "clipanion";
24207
25678
  init_errors();
24208
25679
 
24209
25680
  // src/lib/bootstrap-aci-state.ts
@@ -24231,13 +25702,13 @@ async function getAciState(opts) {
24231
25702
  // src/commands/bootstrap/status.ts
24232
25703
  var BootstrapStatusCommand = class extends M8tCommand {
24233
25704
  static paths = [["bootstrap", "status"]];
24234
- static usage = Command48.Usage({
25705
+ static usage = Command50.Usage({
24235
25706
  description: "Show the cloud installer's live status (phase, progress, result).",
24236
25707
  details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.",
24237
25708
  examples: [["One read", "$0 bootstrap status"], ["Watch to completion", "$0 bootstrap status --watch"]]
24238
25709
  });
24239
- watch = Option46.Boolean("--watch", false);
24240
- output = Option46.String("--output");
25710
+ watch = Option47.Boolean("--watch", false);
25711
+ output = Option47.String("--output");
24241
25712
  async executeCommand() {
24242
25713
  const state = await readBootstrapState();
24243
25714
  if (!state) {
@@ -24308,7 +25779,7 @@ function formatStatus(d) {
24308
25779
  }
24309
25780
 
24310
25781
  // src/commands/bootstrap/reap.ts
24311
- import { Command as Command49, Option as Option47 } from "clipanion";
25782
+ import { Command as Command51, Option as Option48 } from "clipanion";
24312
25783
  init_errors();
24313
25784
 
24314
25785
  // src/lib/bootstrap-reap.ts
@@ -24384,14 +25855,14 @@ async function reapInstaller(opts) {
24384
25855
  // src/commands/bootstrap/reap.ts
24385
25856
  var BootstrapReapCommand = class extends M8tCommand {
24386
25857
  static paths = [["bootstrap", "reap"]];
24387
- static usage = Command49.Usage({
25858
+ static usage = Command51.Usage({
24388
25859
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
24389
25860
  details: "Runs locally on the 'done' signal (the installer can't delete its own identity). The platform RG and the gateway's own assignments persist. A failed install is left intact for diagnosis unless --force.",
24390
25861
  examples: [["Reap after done", "$0 bootstrap reap"]]
24391
25862
  });
24392
- force = Option47.Boolean("--force", false);
24393
- sweepOrphans = Option47.Boolean("--sweep-orphans", false);
24394
- yes = Option47.Boolean("--yes", false);
25863
+ force = Option48.Boolean("--force", false);
25864
+ sweepOrphans = Option48.Boolean("--sweep-orphans", false);
25865
+ yes = Option48.Boolean("--yes", false);
24395
25866
  async executeCommand() {
24396
25867
  if (this.sweepOrphans === true) {
24397
25868
  const { subscriptionId: sub } = await getAzAccount();
@@ -24456,17 +25927,17 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
24456
25927
  };
24457
25928
 
24458
25929
  // src/commands/bootstrap/finish.ts
24459
- import * as fs27 from "fs/promises";
24460
- import * as os15 from "os";
24461
- import * as path29 from "path";
24462
- import { Command as Command50, Option as Option48 } from "clipanion";
25930
+ import * as fs31 from "fs/promises";
25931
+ import * as os16 from "os";
25932
+ import * as path34 from "path";
25933
+ import { Command as Command52, Option as Option49 } from "clipanion";
24463
25934
  init_errors();
24464
25935
 
24465
25936
  // src/lib/company-profile-seed.ts
24466
25937
  import { spawn as spawn6 } from "child_process";
24467
- import { closeSync, openSync, readFileSync as readFileSync18 } from "fs";
24468
- import * as os14 from "os";
24469
- import * as path28 from "path";
25938
+ import { closeSync, openSync, readFileSync as readFileSync20 } from "fs";
25939
+ import * as os15 from "os";
25940
+ import * as path33 from "path";
24470
25941
  init_errors();
24471
25942
 
24472
25943
  // src/lib/onboarding-profile.ts
@@ -24647,9 +26118,9 @@ function composeFounderIdentityNote(id) {
24647
26118
 
24648
26119
  // src/lib/company-profile-seed.ts
24649
26120
  function readGithubAppCreds(credsPath) {
24650
- const p = credsPath ?? path28.join(os14.homedir(), ".m8t-stack", "github-app.json");
26121
+ const p = credsPath ?? path33.join(os15.homedir(), ".m8t-stack", "github-app.json");
24651
26122
  try {
24652
- return JSON.parse(readFileSync18(p, "utf8"));
26123
+ return JSON.parse(readFileSync20(p, "utf8"));
24653
26124
  } catch {
24654
26125
  return null;
24655
26126
  }
@@ -24677,7 +26148,7 @@ async function resolveSeedContext(opts) {
24677
26148
  async function applyProfileToBrain(args) {
24678
26149
  const token = await mintInstallationTokenFromPem({
24679
26150
  appId: args.appCreds.appId,
24680
- privateKeyPem: readFileSync18(args.appCreds.pemPath, "utf8"),
26151
+ privateKeyPem: readFileSync20(args.appCreds.pemPath, "utf8"),
24681
26152
  installationId: args.appCreds.installationId,
24682
26153
  fetchImpl: args.fetchImpl
24683
26154
  });
@@ -24712,7 +26183,7 @@ async function applyProfileToBrain(args) {
24712
26183
  });
24713
26184
  }
24714
26185
  function spawnDetachedSeedWatch() {
24715
- const logPath = path28.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
26186
+ const logPath = path33.join(os15.homedir(), ".m8t-stack", "seed-profile.log");
24716
26187
  const fd = openSync(logPath, "a");
24717
26188
  try {
24718
26189
  const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
@@ -24789,14 +26260,14 @@ function renderInstallSummary(args) {
24789
26260
  // src/commands/bootstrap/finish.ts
24790
26261
  var BootstrapFinishCommand = class extends M8tCommand {
24791
26262
  static paths = [["bootstrap", "finish"]];
24792
- static usage = Command50.Usage({
26263
+ static usage = Command52.Usage({
24793
26264
  description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
24794
26265
  details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t-stack/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding questionnaire \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
24795
26266
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
24796
26267
  });
24797
- repoRoot = Option48.String("--repo-root");
24798
- subscription = Option48.String("--subscription");
24799
- resourceGroup = Option48.String("--resource-group");
26268
+ repoRoot = Option49.String("--repo-root");
26269
+ subscription = Option49.String("--subscription");
26270
+ resourceGroup = Option49.String("--resource-group");
24800
26271
  async executeCommand() {
24801
26272
  const state = await readBootstrapState();
24802
26273
  if (!state) {
@@ -24811,9 +26282,9 @@ var BootstrapFinishCommand = class extends M8tCommand {
24811
26282
  });
24812
26283
  }
24813
26284
  const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
24814
- const markerDir = path29.join(os15.homedir(), ".m8t-stack");
24815
- await fs27.mkdir(markerDir, { recursive: true });
24816
- await fs27.writeFile(path29.join(markerDir, "repo-root"), `${repoRoot}
26285
+ const markerDir = path34.join(os16.homedir(), ".m8t-stack");
26286
+ await fs31.mkdir(markerDir, { recursive: true });
26287
+ await fs31.writeFile(path34.join(markerDir, "repo-root"), `${repoRoot}
24817
26288
  `, "utf8");
24818
26289
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
24819
26290
  const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
@@ -24849,7 +26320,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
24849
26320
  }
24850
26321
  let brainOrg = null;
24851
26322
  try {
24852
- const credsRaw = await fs27.readFile(path29.join(markerDir, "github-app.json"), "utf8");
26323
+ const credsRaw = await fs31.readFile(path34.join(markerDir, "github-app.json"), "utf8");
24853
26324
  const creds = JSON.parse(credsRaw);
24854
26325
  brainOrg = typeof creds.org === "string" ? creds.org : null;
24855
26326
  } catch {
@@ -24880,18 +26351,18 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
24880
26351
  };
24881
26352
 
24882
26353
  // src/commands/bootstrap/ui.ts
24883
- import * as fs29 from "fs";
24884
- import * as os17 from "os";
24885
- import * as path31 from "path";
24886
- import { Command as Command51, Option as Option49 } from "clipanion";
24887
- import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
26354
+ import * as fs33 from "fs";
26355
+ import * as os18 from "os";
26356
+ import * as path36 from "path";
26357
+ import { Command as Command53, Option as Option50 } from "clipanion";
26358
+ import { DefaultAzureCredential as DefaultAzureCredential20 } from "@azure/identity";
24888
26359
  init_errors();
24889
26360
 
24890
26361
  // src/lib/bootstrap-ui.ts
24891
- import * as fs28 from "fs";
26362
+ import * as fs32 from "fs";
24892
26363
  import * as net from "net";
24893
- import * as os16 from "os";
24894
- import * as path30 from "path";
26364
+ import * as os17 from "os";
26365
+ import * as path35 from "path";
24895
26366
  import { spawn as spawn7 } from "child_process";
24896
26367
  init_errors();
24897
26368
  init_rbac();
@@ -24958,9 +26429,9 @@ async function ensureFounderFoundryRole(args) {
24958
26429
  });
24959
26430
  }
24960
26431
  function writeWebEnvLocal(args) {
24961
- const envPath = path30.join(args.repoRoot, "apps", "web", ".env.local");
24962
- if (fs28.existsSync(envPath)) {
24963
- fs28.copyFileSync(envPath, envPath + ".bak");
26432
+ const envPath = path35.join(args.repoRoot, "apps", "web", ".env.local");
26433
+ if (fs32.existsSync(envPath)) {
26434
+ fs32.copyFileSync(envPath, envPath + ".bak");
24964
26435
  }
24965
26436
  const body = [
24966
26437
  "# Written by `m8t bootstrap ui` \u2014 onboarding chat-only run (Simple Stacey).",
@@ -24974,7 +26445,7 @@ function writeWebEnvLocal(args) {
24974
26445
  "NEXT_PUBLIC_M8T_ONBOARDING=1",
24975
26446
  ""
24976
26447
  ].join("\n");
24977
- fs28.writeFileSync(envPath, body, "utf8");
26448
+ fs32.writeFileSync(envPath, body, "utf8");
24978
26449
  return envPath;
24979
26450
  }
24980
26451
  function assertNodeVersion(versionString = process.version) {
@@ -25015,11 +26486,11 @@ async function installWebDeps(repoRoot) {
25015
26486
  });
25016
26487
  await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
25017
26488
  }
25018
- function onboardingUiPaths(home = os16.homedir()) {
25019
- const dir = path30.join(home, ".m8t-stack");
26489
+ function onboardingUiPaths(home = os17.homedir()) {
26490
+ const dir = path35.join(home, ".m8t-stack");
25020
26491
  return {
25021
- logPath: path30.join(dir, "onboarding-ui.log"),
25022
- pidPath: path30.join(dir, "onboarding-ui.pid")
26492
+ logPath: path35.join(dir, "onboarding-ui.log"),
26493
+ pidPath: path35.join(dir, "onboarding-ui.pid")
25023
26494
  };
25024
26495
  }
25025
26496
  async function serveOnboardingUiDetached(args) {
@@ -25043,8 +26514,8 @@ async function serveOnboardingUiDetached(args) {
25043
26514
  if (await isPortOpen()) {
25044
26515
  return { alreadyRunning: true, logPath };
25045
26516
  }
25046
- fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
25047
- const fd = fs28.openSync(logPath, "a");
26517
+ fs32.mkdirSync(path35.dirname(logPath), { recursive: true });
26518
+ const fd = fs32.openSync(logPath, "a");
25048
26519
  const child = spawn7("pnpm", ["--filter", "web", "dev"], {
25049
26520
  cwd: args.repoRoot,
25050
26521
  detached: true,
@@ -25052,7 +26523,7 @@ async function serveOnboardingUiDetached(args) {
25052
26523
  env: { ...process.env, PORT: args.port },
25053
26524
  shell: false
25054
26525
  });
25055
- fs28.closeSync(fd);
26526
+ fs32.closeSync(fd);
25056
26527
  if (child.pid == null) {
25057
26528
  throw new LocalCliError({
25058
26529
  code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
@@ -25061,7 +26532,7 @@ async function serveOnboardingUiDetached(args) {
25061
26532
  });
25062
26533
  }
25063
26534
  child.unref();
25064
- fs28.writeFileSync(pidPath, String(child.pid), "utf8");
26535
+ fs32.writeFileSync(pidPath, String(child.pid), "utf8");
25065
26536
  const deadline = Date.now() + 45e3;
25066
26537
  const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
25067
26538
  while (Date.now() < deadline) {
@@ -25080,11 +26551,11 @@ function autoOpenOnboardingUi(url, open = tryOpenUrl) {
25080
26551
  } catch {
25081
26552
  }
25082
26553
  }
25083
- function stopOnboardingUi(home = os16.homedir()) {
26554
+ function stopOnboardingUi(home = os17.homedir()) {
25084
26555
  const { pidPath } = onboardingUiPaths(home);
25085
26556
  let pidStr;
25086
26557
  try {
25087
- pidStr = fs28.readFileSync(pidPath, "utf8").trim();
26558
+ pidStr = fs32.readFileSync(pidPath, "utf8").trim();
25088
26559
  } catch {
25089
26560
  return false;
25090
26561
  }
@@ -25097,7 +26568,7 @@ function stopOnboardingUi(home = os16.homedir()) {
25097
26568
  }
25098
26569
  }
25099
26570
  try {
25100
- fs28.unlinkSync(pidPath);
26571
+ fs32.unlinkSync(pidPath);
25101
26572
  } catch {
25102
26573
  }
25103
26574
  return true;
@@ -25154,7 +26625,7 @@ async function deploySimpleStacey(args) {
25154
26625
  // src/commands/bootstrap/ui.ts
25155
26626
  var BootstrapUiCommand = class extends M8tCommand {
25156
26627
  static paths = [["bootstrap", "ui"]];
25157
- static usage = Command51.Usage({
26628
+ static usage = Command53.Usage({
25158
26629
  description: "Deploy Simple Stacey + start the local onboarding chat UI in the background (returns immediately).",
25159
26630
  details: [
25160
26631
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -25172,15 +26643,15 @@ var BootstrapUiCommand = class extends M8tCommand {
25172
26643
  ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"]
25173
26644
  ]
25174
26645
  });
25175
- repoRoot = Option49.String("--repo-root");
25176
- port = Option49.String("--port", "3000");
25177
- endpoint = Option49.String("--endpoint", {
26646
+ repoRoot = Option50.String("--repo-root");
26647
+ port = Option50.String("--port", "3000");
26648
+ endpoint = Option50.String("--endpoint", {
25178
26649
  description: "Foundry project endpoint to target \u2014 disambiguates when the subscription has multiple projects."
25179
26650
  });
25180
- prepOnly = Option49.Boolean("--prep-only", false);
25181
- skipInstall = Option49.Boolean("--skip-install", false);
25182
- stop = Option49.Boolean("--stop", false);
25183
- foreground = Option49.Boolean("--foreground", false);
26651
+ prepOnly = Option50.Boolean("--prep-only", false);
26652
+ skipInstall = Option50.Boolean("--skip-install", false);
26653
+ stop = Option50.Boolean("--stop", false);
26654
+ foreground = Option50.Boolean("--foreground", false);
25184
26655
  async executeCommand() {
25185
26656
  if (this.stop === true) {
25186
26657
  const stopped = stopOnboardingUi();
@@ -25213,10 +26684,10 @@ var BootstrapUiCommand = class extends M8tCommand {
25213
26684
  this.context.stderr.write(` ${colors.dim(m)}
25214
26685
  `);
25215
26686
  };
25216
- if (fs29.existsSync(path31.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
26687
+ if (fs33.existsSync(path36.join(os18.homedir(), ".m8t-stack", "config.yaml"))) {
25217
26688
  out(colors.error("\u26A0 an existing ~/.m8t-stack/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
25218
26689
  }
25219
- const credential2 = new DefaultAzureCredential19();
26690
+ const credential2 = new DefaultAzureCredential20();
25220
26691
  const account = await getAzAccount();
25221
26692
  assertNodeVersion();
25222
26693
  out("waiting for Foundry (the installer's foundry-create phase)\u2026");
@@ -25287,10 +26758,10 @@ var BootstrapUiCommand = class extends M8tCommand {
25287
26758
  };
25288
26759
 
25289
26760
  // src/commands/bootstrap/seed-profile.ts
25290
- import { Command as Command52, Option as Option50 } from "clipanion";
26761
+ import { Command as Command54, Option as Option51 } from "clipanion";
25291
26762
  var BootstrapSeedProfileCommand = class extends M8tCommand {
25292
26763
  static paths = [["bootstrap", "seed-profile"]];
25293
- static usage = Command52.Usage({
26764
+ static usage = Command54.Usage({
25294
26765
  description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
25295
26766
  details: "Reads the latest stacey-intake conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
25296
26767
  examples: [
@@ -25298,11 +26769,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
25298
26769
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
25299
26770
  ]
25300
26771
  });
25301
- endpoint = Option50.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
25302
- brain = Option50.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
25303
- watch = Option50.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
25304
- timeout = Option50.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
25305
- githubAppCreds = Option50.String("--github-app-creds");
26772
+ endpoint = Option51.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
26773
+ brain = Option51.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
26774
+ watch = Option51.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
26775
+ timeout = Option51.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
26776
+ githubAppCreds = Option51.String("--github-app-creds");
25306
26777
  async executeCommand() {
25307
26778
  const ctx = await resolveSeedContext({
25308
26779
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -25387,7 +26858,9 @@ cli.register(CoderTeardownCommand);
25387
26858
  cli.register(AzureExecDeployCommand);
25388
26859
  cli.register(PlatformStatusCommand);
25389
26860
  cli.register(PlatformUpdateCommand);
26861
+ cli.register(PlatformConvergeCommand);
25390
26862
  cli.register(PlatformEnableCostReportCommand);
26863
+ cli.register(PlatformEnableAutoUpdateCommand);
25391
26864
  cli.register(DeployCommand);
25392
26865
  cli.register(AgentDeployAdvisorCommand);
25393
26866
  cli.register(AgentRemoveCommand);