@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.
- package/dist/cli.js +1934 -461
- package/dist/cli.js.map +1 -1
- 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.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
${
|
|
3372
|
+
${path38}
|
|
3373
3373
|
${underline}`);
|
|
3374
3374
|
}
|
|
3375
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
10923
|
-
return this.methodRequest("get",
|
|
10922
|
+
get(path37, opts) {
|
|
10923
|
+
return this.methodRequest("get", path37, opts);
|
|
10924
10924
|
}
|
|
10925
|
-
post(
|
|
10926
|
-
return this.methodRequest("post",
|
|
10925
|
+
post(path37, opts) {
|
|
10926
|
+
return this.methodRequest("post", path37, opts);
|
|
10927
10927
|
}
|
|
10928
|
-
patch(
|
|
10929
|
-
return this.methodRequest("patch",
|
|
10928
|
+
patch(path37, opts) {
|
|
10929
|
+
return this.methodRequest("patch", path37, opts);
|
|
10930
10930
|
}
|
|
10931
|
-
put(
|
|
10932
|
-
return this.methodRequest("put",
|
|
10931
|
+
put(path37, opts) {
|
|
10932
|
+
return this.methodRequest("put", path37, opts);
|
|
10933
10933
|
}
|
|
10934
|
-
delete(
|
|
10935
|
-
return this.methodRequest("delete",
|
|
10934
|
+
delete(path37, opts) {
|
|
10935
|
+
return this.methodRequest("delete", path37, opts);
|
|
10936
10936
|
}
|
|
10937
|
-
methodRequest(method,
|
|
10937
|
+
methodRequest(method, path37, opts) {
|
|
10938
10938
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
10939
|
-
return { method, path:
|
|
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(
|
|
11062
|
-
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path:
|
|
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:
|
|
11157
|
-
const url = this.buildURL(
|
|
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
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
13364
|
-
const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${
|
|
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
|
|
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
|
|
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
|
|
13620
|
+
import * as fs11 from "fs";
|
|
13605
13621
|
import * as os4 from "os";
|
|
13606
|
-
import * as
|
|
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 =
|
|
14126
|
-
if (!
|
|
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
|
-
|
|
14207
|
+
fs10.cpSync(opts.templateSrc, opts.destDir, { recursive: true });
|
|
14137
14208
|
if (opts.seedDir) {
|
|
14138
|
-
|
|
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
|
|
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
|
|
14277
|
-
const
|
|
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
|
-
|
|
14281
|
-
|
|
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:
|
|
14431
|
+
body: JSON.stringify({ message: args.message, tree: newTree.sha, parents: [args.baseSha] })
|
|
14287
14432
|
});
|
|
14288
|
-
await
|
|
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: (
|
|
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 =
|
|
14501
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
15175
|
+
import * as fs12 from "fs";
|
|
14974
15176
|
import * as os5 from "os";
|
|
14975
|
-
import * as
|
|
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 =
|
|
15007
|
-
if (!
|
|
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 =
|
|
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
|
|
15255
|
-
import * as
|
|
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 =
|
|
15261
|
-
const fsRoot =
|
|
15462
|
+
let dir = path12.resolve(hint);
|
|
15463
|
+
const fsRoot = path12.parse(dir).root;
|
|
15262
15464
|
for (; ; ) {
|
|
15263
|
-
if (
|
|
15264
|
-
const parent =
|
|
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 ?
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
|
16231
|
+
import * as fs16 from "fs";
|
|
16030
16232
|
import * as os6 from "os";
|
|
16031
|
-
import * as
|
|
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 =
|
|
16054
|
-
if (!
|
|
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(
|
|
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
|
|
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 :
|
|
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
|
|
16387
|
+
import * as fs17 from "fs";
|
|
16186
16388
|
import * as os7 from "os";
|
|
16187
|
-
import * as
|
|
16389
|
+
import * as path15 from "path";
|
|
16188
16390
|
function readVersionFrontmatter(file) {
|
|
16189
|
-
if (!
|
|
16190
|
-
const text =
|
|
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(
|
|
16401
|
+
return createHash2("sha256").update(fs17.readFileSync(filePath)).digest("hex");
|
|
16200
16402
|
}
|
|
16201
16403
|
function checkArchitectDrift() {
|
|
16202
|
-
const sourcePath =
|
|
16404
|
+
const sourcePath = path15.join(
|
|
16203
16405
|
resolveRepoRoot(),
|
|
16204
16406
|
"personas",
|
|
16205
16407
|
"m8t-architect",
|
|
16206
16408
|
"persona.md"
|
|
16207
16409
|
);
|
|
16208
|
-
const sidecarPath =
|
|
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 (!
|
|
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 (
|
|
16427
|
+
if (fs17.existsSync(sidecarPath)) {
|
|
16226
16428
|
try {
|
|
16227
|
-
const raw = JSON.parse(
|
|
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
|
|
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
|
|
16558
|
-
import * as
|
|
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 =
|
|
16763
|
+
let dir = path16.resolve(startDir);
|
|
16562
16764
|
for (; ; ) {
|
|
16563
|
-
if (
|
|
16564
|
-
const parent =
|
|
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 =
|
|
16774
|
+
const file = path16.join(root, "personas", persona, "persona.md");
|
|
16573
16775
|
let raw;
|
|
16574
16776
|
try {
|
|
16575
|
-
raw =
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
17451
|
-
const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${
|
|
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/${
|
|
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
|
|
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
|
|
17605
|
-
|
|
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) =>
|
|
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
|
|
17896
|
+
import * as fs20 from "fs";
|
|
17658
17897
|
import * as os8 from "os";
|
|
17659
|
-
import * as
|
|
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 (!
|
|
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 ??
|
|
17705
|
-
const dest =
|
|
17706
|
-
if (
|
|
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
|
-
|
|
17721
|
-
const tmp =
|
|
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 =
|
|
17724
|
-
|
|
17725
|
-
const unpacked =
|
|
17726
|
-
|
|
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
|
-
|
|
17968
|
+
fs20.renameSync(unpacked, dest);
|
|
17730
17969
|
} catch (e) {
|
|
17731
17970
|
const err = e;
|
|
17732
|
-
const isBenignRace = (err.code === "EEXIST" || err.code === "ENOTEMPTY") &&
|
|
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
|
-
|
|
17980
|
+
fs20.rmSync(tmp, { recursive: true, force: true });
|
|
17742
17981
|
}
|
|
17743
17982
|
return dest;
|
|
17744
17983
|
}
|
|
17745
17984
|
function extractTarGz(tgz, into) {
|
|
17746
|
-
|
|
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
|
|
17869
|
-
import * as
|
|
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
|
|
18112
|
+
import * as fs21 from "fs/promises";
|
|
17874
18113
|
import * as os9 from "os";
|
|
17875
|
-
import * as
|
|
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 =
|
|
18162
|
+
const marker = path20.join(home, ".m8t-stack", "repo-root");
|
|
17906
18163
|
try {
|
|
17907
|
-
const root = (await
|
|
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 =
|
|
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
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
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
|
|
18169
|
-
|
|
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: {
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
19298
|
-
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children,
|
|
19299
|
-
else out.push({ ...d, path:
|
|
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 =
|
|
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 =
|
|
19354
|
-
resourceGroup =
|
|
19355
|
-
location =
|
|
19356
|
-
suffix =
|
|
19357
|
-
imageRef =
|
|
19358
|
-
acrPullIdentity =
|
|
19359
|
-
acrResourceId =
|
|
19360
|
-
foundryEndpoint =
|
|
19361
|
-
foundryResourceId =
|
|
19362
|
-
foundryTracing =
|
|
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 =
|
|
19365
|
-
whatIf =
|
|
19366
|
-
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
|
|
19480
|
-
import { Command as
|
|
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 =
|
|
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 =
|
|
19509
|
-
skillsDir =
|
|
19510
|
-
noJudge =
|
|
19511
|
-
deployment =
|
|
19512
|
-
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 =
|
|
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
|
|
19569
|
-
import { writeFileSync as
|
|
19570
|
-
import { join as
|
|
19571
|
-
import { Command as
|
|
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 (
|
|
21197
|
+
if (existsSync15(join27(base, version))) return version;
|
|
19737
21198
|
const prefixed = `${worker}-${version}`;
|
|
19738
|
-
if (
|
|
19739
|
-
if (version === "latest" &&
|
|
19740
|
-
const dirs =
|
|
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(
|
|
19755
|
-
const manifestPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
19789
|
-
arms =
|
|
19790
|
-
taskSet =
|
|
19791
|
-
examType =
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
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 =
|
|
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
|
-
|
|
19877
|
-
|
|
21339
|
+
mkdirSync5(out, { recursive: true });
|
|
21340
|
+
writeFileSync6(join27(out, "verdict.json"), JSON.stringify(verdict, null, 2));
|
|
19878
21341
|
const feed = redactForFeed(verdict, sealedTaskIds);
|
|
19879
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
19927
|
-
verbose =
|
|
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
|
|
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 =
|
|
21424
|
+
static usage = Command40.Usage({
|
|
19962
21425
|
description: "Show your identity + the gateway you'll talk to. Probes the backend."
|
|
19963
21426
|
});
|
|
19964
|
-
output =
|
|
19965
|
-
verbose =
|
|
19966
|
-
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 =
|
|
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
|
|
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 =
|
|
21562
|
+
static usage = Command41.Usage({
|
|
20100
21563
|
description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
|
|
20101
21564
|
});
|
|
20102
|
-
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
|
|
20141
|
-
import { DefaultAzureCredential as
|
|
20142
|
-
import * as
|
|
20143
|
-
import * as
|
|
20144
|
-
import * as
|
|
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 =
|
|
20370
|
-
if (!
|
|
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 =
|
|
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 =
|
|
21845
|
+
isDir = fs26.statSync(repoPath).isDirectory();
|
|
20383
21846
|
} catch {
|
|
20384
21847
|
isDir = false;
|
|
20385
21848
|
}
|
|
20386
|
-
const isGitCheckout = isDir &&
|
|
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 =
|
|
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 =
|
|
20395
|
-
agent =
|
|
20396
|
-
resourceGroup =
|
|
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
|
|
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
|
|
21975
|
+
import { Command as Command43, Option as Option40 } from "clipanion";
|
|
20513
21976
|
|
|
20514
21977
|
// src/lib/profiles.ts
|
|
20515
|
-
import * as
|
|
20516
|
-
import * as
|
|
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
|
|
21983
|
+
return path29.join(home, ".m8t-stack", "profiles");
|
|
20521
21984
|
}
|
|
20522
21985
|
function getProfilePath(name) {
|
|
20523
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
20569
|
-
await
|
|
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 =
|
|
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 =
|
|
20656
|
-
subscription =
|
|
20657
|
-
list =
|
|
20658
|
-
as =
|
|
20659
|
-
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
|
|
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 =
|
|
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 =
|
|
20767
|
-
print =
|
|
20768
|
-
output =
|
|
20769
|
-
resourceGroup =
|
|
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
|
|
22276
|
+
import { Command as Command45, Option as Option42 } from "clipanion";
|
|
20814
22277
|
import { AzureCliCredential } from "@azure/identity";
|
|
20815
|
-
import { TableClient as
|
|
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
|
|
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
|
|
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 = (
|
|
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
|
-
|
|
21778
|
-
|
|
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,
|
|
21792
|
-
const encodedPath =
|
|
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(
|
|
23277
|
+
async readFile(path37) {
|
|
21809
23278
|
const token = await args.mintToken();
|
|
21810
|
-
return readFileWith(token,
|
|
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
|
|
21820
|
-
const content = await readFileWith(token,
|
|
21821
|
-
out.push({ path:
|
|
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: (
|
|
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(
|
|
23750
|
+
constructor(path37, message) {
|
|
22280
23751
|
super(message);
|
|
22281
|
-
this.path =
|
|
23752
|
+
this.path = path37;
|
|
22282
23753
|
this.name = "BatchAbort";
|
|
22283
23754
|
}
|
|
22284
23755
|
};
|
|
22285
|
-
var isIndexFile = (
|
|
22286
|
-
var isMemoryIndex = (
|
|
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
|
|
22304
|
-
if (
|
|
23774
|
+
const path37 = targetPath(delta);
|
|
23775
|
+
if (path37 === null)
|
|
22305
23776
|
return { ok: true };
|
|
22306
|
-
if (isIndexFile(
|
|
22307
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
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(
|
|
22310
|
-
const isStructural =
|
|
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(
|
|
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:
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
|
22613
|
-
if (!
|
|
22614
|
-
digest.push(
|
|
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 =
|
|
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 =
|
|
22962
|
-
dryRun =
|
|
22963
|
-
since =
|
|
22964
|
-
reset =
|
|
22965
|
-
showTranscripts =
|
|
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 =
|
|
22969
|
-
endpoint =
|
|
22970
|
-
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
|
|
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
|
|
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 =
|
|
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 =
|
|
23566
|
-
location =
|
|
23567
|
-
account =
|
|
23568
|
-
project =
|
|
23569
|
-
model =
|
|
23570
|
-
modelVersion =
|
|
23571
|
-
capacity =
|
|
23572
|
-
subscription =
|
|
23573
|
-
skipQuotaCheck =
|
|
23574
|
-
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
|
|
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 =
|
|
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 =
|
|
23657
|
-
consecutive =
|
|
23658
|
-
attempts =
|
|
23659
|
-
interval =
|
|
23660
|
-
subscription =
|
|
23661
|
-
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
|
|
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 =
|
|
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 =
|
|
23778
|
-
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
|
|
23835
|
-
import * as
|
|
23836
|
-
import * as
|
|
23837
|
-
import { Command as
|
|
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
|
|
23983
|
-
import * as
|
|
23984
|
-
import * as
|
|
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 =
|
|
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
|
|
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
|
|
25523
|
+
await fs28.rm(tmpFile, { force: true });
|
|
24053
25524
|
}
|
|
24054
25525
|
}
|
|
24055
25526
|
|
|
24056
25527
|
// src/lib/bootstrap-state.ts
|
|
24057
|
-
import * as
|
|
24058
|
-
import * as
|
|
24059
|
-
import * as
|
|
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
|
|
25532
|
+
return path31.join(home, ".m8t-stack");
|
|
24062
25533
|
}
|
|
24063
25534
|
function statePath(home) {
|
|
24064
|
-
return
|
|
25535
|
+
return path31.join(stateDir(home), "bootstrap.json");
|
|
24065
25536
|
}
|
|
24066
|
-
async function readBootstrapState(home =
|
|
25537
|
+
async function readBootstrapState(home = os13.homedir()) {
|
|
24067
25538
|
try {
|
|
24068
|
-
const raw = await
|
|
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 =
|
|
24076
|
-
await
|
|
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
|
|
24079
|
-
await
|
|
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 =
|
|
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 =
|
|
24100
|
-
resourceGroup =
|
|
24101
|
-
clientId =
|
|
24102
|
-
subscription =
|
|
24103
|
-
installerTag =
|
|
24104
|
-
gatewayImageRef =
|
|
24105
|
-
githubAppCreds =
|
|
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 :
|
|
25590
|
+
const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path32.join(os14.homedir(), ".m8t-stack", "github-app.json");
|
|
24120
25591
|
let githubApp;
|
|
24121
|
-
if (
|
|
25592
|
+
if (fs30.existsSync(credsPath)) {
|
|
24122
25593
|
let parsed;
|
|
24123
25594
|
try {
|
|
24124
|
-
parsed = JSON.parse(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
24240
|
-
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
|
|
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 =
|
|
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 =
|
|
24393
|
-
sweepOrphans =
|
|
24394
|
-
yes =
|
|
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
|
|
24460
|
-
import * as
|
|
24461
|
-
import * as
|
|
24462
|
-
import { Command as
|
|
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
|
|
24468
|
-
import * as
|
|
24469
|
-
import * as
|
|
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 ??
|
|
26121
|
+
const p = credsPath ?? path33.join(os15.homedir(), ".m8t-stack", "github-app.json");
|
|
24651
26122
|
try {
|
|
24652
|
-
return JSON.parse(
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
24798
|
-
subscription =
|
|
24799
|
-
resourceGroup =
|
|
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 =
|
|
24815
|
-
await
|
|
24816
|
-
await
|
|
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
|
|
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
|
|
24884
|
-
import * as
|
|
24885
|
-
import * as
|
|
24886
|
-
import { Command as
|
|
24887
|
-
import { DefaultAzureCredential as
|
|
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
|
|
26362
|
+
import * as fs32 from "fs";
|
|
24892
26363
|
import * as net from "net";
|
|
24893
|
-
import * as
|
|
24894
|
-
import * as
|
|
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 =
|
|
24962
|
-
if (
|
|
24963
|
-
|
|
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
|
-
|
|
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 =
|
|
25019
|
-
const dir =
|
|
26489
|
+
function onboardingUiPaths(home = os17.homedir()) {
|
|
26490
|
+
const dir = path35.join(home, ".m8t-stack");
|
|
25020
26491
|
return {
|
|
25021
|
-
logPath:
|
|
25022
|
-
pidPath:
|
|
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
|
-
|
|
25047
|
-
const fd =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
26554
|
+
function stopOnboardingUi(home = os17.homedir()) {
|
|
25084
26555
|
const { pidPath } = onboardingUiPaths(home);
|
|
25085
26556
|
let pidStr;
|
|
25086
26557
|
try {
|
|
25087
|
-
pidStr =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
25176
|
-
port =
|
|
25177
|
-
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 =
|
|
25181
|
-
skipInstall =
|
|
25182
|
-
stop =
|
|
25183
|
-
foreground =
|
|
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 (
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
25302
|
-
brain =
|
|
25303
|
-
watch =
|
|
25304
|
-
timeout =
|
|
25305
|
-
githubAppCreds =
|
|
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);
|