@m8t-stack/cli 0.2.16 → 0.2.18
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 +1650 -604
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -814,6 +814,7 @@ async function createCoderVersion(args) {
|
|
|
814
814
|
environment_variables: args.env
|
|
815
815
|
};
|
|
816
816
|
const metadata = {
|
|
817
|
+
...Object.fromEntries(Object.entries(args.metadata).filter(([, v2]) => v2 !== null)),
|
|
817
818
|
source: args.metadata.source,
|
|
818
819
|
kind: args.metadata.kind,
|
|
819
820
|
persona: args.metadata.persona
|
|
@@ -886,6 +887,7 @@ async function createPromptVersion(args) {
|
|
|
886
887
|
persona: args.metadata.persona
|
|
887
888
|
};
|
|
888
889
|
if (args.metadata.personaVersion !== null) metadata.personaVersion = args.metadata.personaVersion;
|
|
890
|
+
if (args.metadata.fillableFieldValues !== void 0) metadata.fillableFieldValues = args.metadata.fillableFieldValues;
|
|
889
891
|
const version = await project.agents.createVersion(args.name, definition, { metadata });
|
|
890
892
|
const v = version.version;
|
|
891
893
|
if (v === void 0) {
|
|
@@ -940,6 +942,24 @@ async function grantFoundryUser(args) {
|
|
|
940
942
|
// RoleAssignmentExists — already granted
|
|
941
943
|
});
|
|
942
944
|
}
|
|
945
|
+
async function grantStorageTableDataContributor(args) {
|
|
946
|
+
const roleAssignmentId = randomUUID();
|
|
947
|
+
const url = `${ARM2}${args.scope}/providers/Microsoft.Authorization/roleAssignments/${roleAssignmentId}?api-version=${RA_API}`;
|
|
948
|
+
await authedJsonResult({
|
|
949
|
+
credential: args.credential,
|
|
950
|
+
scope: ARM_SCOPE3,
|
|
951
|
+
method: "PUT",
|
|
952
|
+
url,
|
|
953
|
+
body: {
|
|
954
|
+
properties: {
|
|
955
|
+
roleDefinitionId: `/subscriptions/${args.subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${STORAGE_TABLE_DATA_CONTRIBUTOR_ROLE_ID}`,
|
|
956
|
+
principalId: args.principalId,
|
|
957
|
+
principalType: args.principalType ?? "User"
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
okStatuses: [409]
|
|
961
|
+
});
|
|
962
|
+
}
|
|
943
963
|
async function listRoleAssignments(credential2, scope, filter) {
|
|
944
964
|
const q = `api-version=${RA_API}${filter ? `&$filter=${encodeURIComponent(filter)}` : ""}`;
|
|
945
965
|
const url = `${ARM2}${scope}/providers/Microsoft.Authorization/roleAssignments?${q}`;
|
|
@@ -1057,7 +1077,7 @@ async function resolveKeyVaultResourceId(args) {
|
|
|
1057
1077
|
}
|
|
1058
1078
|
return id;
|
|
1059
1079
|
}
|
|
1060
|
-
var ARM2, ARM_SCOPE3, RA_API, FOUNDRY_USER_ROLE_ID, ACR_PULL_ROLE_ID, OWNER_ROLE_ID, USER_ACCESS_ADMIN_ROLE_ID, RBAC_ADMIN_ROLE_ID, ASSIGNING_ROLE_IDS, KV_SECRETS_USER_ROLE_ID, CONTRIBUTOR_ROLE_ID;
|
|
1080
|
+
var ARM2, ARM_SCOPE3, RA_API, FOUNDRY_USER_ROLE_ID, ACR_PULL_ROLE_ID, OWNER_ROLE_ID, USER_ACCESS_ADMIN_ROLE_ID, RBAC_ADMIN_ROLE_ID, STORAGE_TABLE_DATA_CONTRIBUTOR_ROLE_ID, ASSIGNING_ROLE_IDS, KV_SECRETS_USER_ROLE_ID, CONTRIBUTOR_ROLE_ID;
|
|
1061
1081
|
var init_rbac = __esm({
|
|
1062
1082
|
"src/lib/rbac.ts"() {
|
|
1063
1083
|
"use strict";
|
|
@@ -1071,6 +1091,7 @@ var init_rbac = __esm({
|
|
|
1071
1091
|
OWNER_ROLE_ID = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635";
|
|
1072
1092
|
USER_ACCESS_ADMIN_ROLE_ID = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9";
|
|
1073
1093
|
RBAC_ADMIN_ROLE_ID = "f58310d9-a9f6-439a-9e8d-f62e7b41a168";
|
|
1094
|
+
STORAGE_TABLE_DATA_CONTRIBUTOR_ROLE_ID = "0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3";
|
|
1074
1095
|
ASSIGNING_ROLE_IDS = [OWNER_ROLE_ID, USER_ACCESS_ADMIN_ROLE_ID, RBAC_ADMIN_ROLE_ID];
|
|
1075
1096
|
KV_SECRETS_USER_ROLE_ID = "4633458b-17de-408a-b874-0445c86b69e6";
|
|
1076
1097
|
CONTRIBUTOR_ROLE_ID = "b24988ac-6180-42a0-ab88-20f7382dd24c";
|
|
@@ -1206,7 +1227,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1206
1227
|
import { Builtins, Cli } from "clipanion";
|
|
1207
1228
|
|
|
1208
1229
|
// src/lib/package-version.ts
|
|
1209
|
-
var CLI_VERSION = "0.2.
|
|
1230
|
+
var CLI_VERSION = "0.2.18";
|
|
1210
1231
|
|
|
1211
1232
|
// src/lib/render-error.ts
|
|
1212
1233
|
init_errors();
|
|
@@ -1520,7 +1541,8 @@ var colors = {
|
|
|
1520
1541
|
hint: (s) => pc.dim(s),
|
|
1521
1542
|
success: (s) => pc.green(s),
|
|
1522
1543
|
field: (s) => pc.bold(s),
|
|
1523
|
-
dim: (s) => pc.dim(s)
|
|
1544
|
+
dim: (s) => pc.dim(s),
|
|
1545
|
+
warn: (s) => pc.yellow(s)
|
|
1524
1546
|
};
|
|
1525
1547
|
|
|
1526
1548
|
// ../../node_modules/.pnpm/openai@6.44.0_ws@8.21.0_zod@4.4.3/node_modules/openai/internal/tslib.mjs
|
|
@@ -3304,12 +3326,12 @@ function encodeURIPath(str4) {
|
|
|
3304
3326
|
return str4.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
|
|
3305
3327
|
}
|
|
3306
3328
|
var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3307
|
-
var createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
3329
|
+
var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(statics, ...params) {
|
|
3308
3330
|
if (statics.length === 1)
|
|
3309
3331
|
return statics[0];
|
|
3310
3332
|
let postPath = false;
|
|
3311
3333
|
const invalidSegments = [];
|
|
3312
|
-
const
|
|
3334
|
+
const path33 = statics.reduce((previousValue, currentValue, index) => {
|
|
3313
3335
|
if (/[?#]/.test(currentValue)) {
|
|
3314
3336
|
postPath = true;
|
|
3315
3337
|
}
|
|
@@ -3326,7 +3348,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path29(sta
|
|
|
3326
3348
|
}
|
|
3327
3349
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
3328
3350
|
}, "");
|
|
3329
|
-
const pathOnly =
|
|
3351
|
+
const pathOnly = path33.split(/[?#]/, 1)[0];
|
|
3330
3352
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
3331
3353
|
let match;
|
|
3332
3354
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -3347,10 +3369,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path29(sta
|
|
|
3347
3369
|
}, "");
|
|
3348
3370
|
throw new OpenAIError(`Path parameters result in path with invalid segments:
|
|
3349
3371
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
3350
|
-
${
|
|
3372
|
+
${path33}
|
|
3351
3373
|
${underline}`);
|
|
3352
3374
|
}
|
|
3353
|
-
return
|
|
3375
|
+
return path33;
|
|
3354
3376
|
};
|
|
3355
3377
|
var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
3356
3378
|
|
|
@@ -10867,9 +10889,9 @@ var OpenAI = class {
|
|
|
10867
10889
|
this.apiKey = token;
|
|
10868
10890
|
return true;
|
|
10869
10891
|
}
|
|
10870
|
-
buildURL(
|
|
10892
|
+
buildURL(path32, query, defaultBaseURL) {
|
|
10871
10893
|
const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
10872
|
-
const url = isAbsoluteURL(
|
|
10894
|
+
const url = isAbsoluteURL(path32) ? new URL(path32) : new URL(baseURL + (baseURL.endsWith("/") && path32.startsWith("/") ? path32.slice(1) : path32));
|
|
10873
10895
|
const defaultQuery = this.defaultQuery();
|
|
10874
10896
|
const pathQuery = Object.fromEntries(url.searchParams);
|
|
10875
10897
|
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
|
|
@@ -10897,24 +10919,24 @@ var OpenAI = class {
|
|
|
10897
10919
|
*/
|
|
10898
10920
|
async prepareRequest(request, { url, options }) {
|
|
10899
10921
|
}
|
|
10900
|
-
get(
|
|
10901
|
-
return this.methodRequest("get",
|
|
10922
|
+
get(path32, opts) {
|
|
10923
|
+
return this.methodRequest("get", path32, opts);
|
|
10902
10924
|
}
|
|
10903
|
-
post(
|
|
10904
|
-
return this.methodRequest("post",
|
|
10925
|
+
post(path32, opts) {
|
|
10926
|
+
return this.methodRequest("post", path32, opts);
|
|
10905
10927
|
}
|
|
10906
|
-
patch(
|
|
10907
|
-
return this.methodRequest("patch",
|
|
10928
|
+
patch(path32, opts) {
|
|
10929
|
+
return this.methodRequest("patch", path32, opts);
|
|
10908
10930
|
}
|
|
10909
|
-
put(
|
|
10910
|
-
return this.methodRequest("put",
|
|
10931
|
+
put(path32, opts) {
|
|
10932
|
+
return this.methodRequest("put", path32, opts);
|
|
10911
10933
|
}
|
|
10912
|
-
delete(
|
|
10913
|
-
return this.methodRequest("delete",
|
|
10934
|
+
delete(path32, opts) {
|
|
10935
|
+
return this.methodRequest("delete", path32, opts);
|
|
10914
10936
|
}
|
|
10915
|
-
methodRequest(method,
|
|
10937
|
+
methodRequest(method, path32, opts) {
|
|
10916
10938
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
10917
|
-
return { method, path:
|
|
10939
|
+
return { method, path: path32, ...opts2 };
|
|
10918
10940
|
}));
|
|
10919
10941
|
}
|
|
10920
10942
|
request(options, remainingRetries = null) {
|
|
@@ -11036,8 +11058,8 @@ var OpenAI = class {
|
|
|
11036
11058
|
}));
|
|
11037
11059
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
11038
11060
|
}
|
|
11039
|
-
getAPIList(
|
|
11040
|
-
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path:
|
|
11061
|
+
getAPIList(path32, Page2, opts) {
|
|
11062
|
+
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path32, ...opts2 })) : { method: "get", path: path32, ...opts });
|
|
11041
11063
|
}
|
|
11042
11064
|
requestAPIList(Page2, options) {
|
|
11043
11065
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -11131,8 +11153,8 @@ var OpenAI = class {
|
|
|
11131
11153
|
}
|
|
11132
11154
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
11133
11155
|
const options = { ...inputOptions };
|
|
11134
|
-
const { method, path:
|
|
11135
|
-
const url = this.buildURL(
|
|
11156
|
+
const { method, path: path32, query, defaultBaseURL } = options;
|
|
11157
|
+
const url = this.buildURL(path32, query, defaultBaseURL);
|
|
11136
11158
|
if ("timeout" in options)
|
|
11137
11159
|
validatePositiveInteger("timeout", options.timeout);
|
|
11138
11160
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -12134,10 +12156,10 @@ var BindListCommand = class extends M8tCommand {
|
|
|
12134
12156
|
const qs = new URLSearchParams();
|
|
12135
12157
|
if (this.channel) qs.set("channel", this.channel);
|
|
12136
12158
|
if (this.status) qs.set("status", this.status);
|
|
12137
|
-
const
|
|
12159
|
+
const path32 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
|
|
12138
12160
|
const data = await apiCall(
|
|
12139
12161
|
{ gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
|
|
12140
|
-
{ method: "GET", path:
|
|
12162
|
+
{ method: "GET", path: path32 },
|
|
12141
12163
|
(msg) => this.context.stderr.write(msg + "\n")
|
|
12142
12164
|
);
|
|
12143
12165
|
if (mode === "json") {
|
|
@@ -13338,8 +13360,8 @@ ${r.stderr}`;
|
|
|
13338
13360
|
message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
|
|
13339
13361
|
});
|
|
13340
13362
|
},
|
|
13341
|
-
getFile: async (
|
|
13342
|
-
const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${
|
|
13363
|
+
getFile: async (path32) => {
|
|
13364
|
+
const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path32}`]);
|
|
13343
13365
|
return r.exitCode === 0 ? r.stdout : null;
|
|
13344
13366
|
},
|
|
13345
13367
|
isEmpty: async () => {
|
|
@@ -13788,12 +13810,12 @@ function stripBrainLoader(instructions) {
|
|
|
13788
13810
|
if (h !== -1) return instructions.slice(0, h).replace(/\s+$/, "");
|
|
13789
13811
|
return instructions.replace(/\s+$/, "");
|
|
13790
13812
|
}
|
|
13791
|
-
function appendBrainLoader(base,
|
|
13813
|
+
function appendBrainLoader(base, loaderText2) {
|
|
13792
13814
|
const clean2 = stripBrainLoader(base);
|
|
13793
13815
|
return `${clean2}
|
|
13794
13816
|
|
|
13795
13817
|
${LOADER_START}
|
|
13796
|
-
${
|
|
13818
|
+
${loaderText2.replace(/\s+$/, "")}
|
|
13797
13819
|
${LOADER_END}
|
|
13798
13820
|
`;
|
|
13799
13821
|
}
|
|
@@ -13834,9 +13856,32 @@ function hasA2aSnippet(instructions) {
|
|
|
13834
13856
|
|
|
13835
13857
|
// src/lib/brain-link.ts
|
|
13836
13858
|
init_foundry_agent_get();
|
|
13859
|
+
|
|
13860
|
+
// src/lib/foundry-agent-version.ts
|
|
13861
|
+
init_errors();
|
|
13862
|
+
var FOUNDRY_DATA_SCOPE = "https://ai.azure.com/.default";
|
|
13863
|
+
async function createAgentVersion(args, fetchImpl = fetch) {
|
|
13864
|
+
const token = await args.credential.getToken(FOUNDRY_DATA_SCOPE);
|
|
13865
|
+
if (!token?.token) throw new LocalCliError({ code: "FOUNDRY_AUTH", message: "Could not acquire Foundry data-plane token" });
|
|
13866
|
+
const url = `${args.projectEndpoint}/agents/${args.agentName}/versions?api-version=v1`;
|
|
13867
|
+
const res = await fetchImpl(url, {
|
|
13868
|
+
method: "POST",
|
|
13869
|
+
headers: { Authorization: `Bearer ${token.token}`, "Content-Type": "application/json", ...args.extraHeaders ?? {} },
|
|
13870
|
+
body: JSON.stringify({ definition: args.definition, metadata: args.metadata })
|
|
13871
|
+
});
|
|
13872
|
+
if (!res.ok) {
|
|
13873
|
+
const text = await res.text();
|
|
13874
|
+
throw new LocalCliError({ code: "AGENT_CREATE_VERSION_FAILED", message: `POST ${url}: HTTP ${res.status.toString()}
|
|
13875
|
+
${text.slice(0, 500)}` });
|
|
13876
|
+
}
|
|
13877
|
+
const data = await res.json();
|
|
13878
|
+
if (!data.version) throw new LocalCliError({ code: "AGENT_CREATE_VERSION_NO_VERSION", message: "createVersion returned no version" });
|
|
13879
|
+
return data.version;
|
|
13880
|
+
}
|
|
13881
|
+
|
|
13882
|
+
// src/lib/brain-link.ts
|
|
13837
13883
|
init_brain_yaml_mirror();
|
|
13838
13884
|
var FOUNDRY_ARM_API = "2025-04-01-preview";
|
|
13839
|
-
var FOUNDRY_DATA_SCOPE = "https://ai.azure.com/.default";
|
|
13840
13885
|
var ARM_SCOPE4 = "https://management.azure.com/.default";
|
|
13841
13886
|
var MCP_TOOL_NAMES = [
|
|
13842
13887
|
"get_file_contents",
|
|
@@ -14043,10 +14088,6 @@ ${text.slice(0, 300)}`
|
|
|
14043
14088
|
}
|
|
14044
14089
|
}
|
|
14045
14090
|
async function createBrainEnabledVersion(args) {
|
|
14046
|
-
const fndToken = await args.credential.getToken(FOUNDRY_DATA_SCOPE);
|
|
14047
|
-
if (!fndToken?.token) {
|
|
14048
|
-
throw new LocalCliError({ code: "FOUNDRY_AUTH", message: "Could not acquire Foundry data-plane token" });
|
|
14049
|
-
}
|
|
14050
14091
|
const otherTools = (args.currentDefinition.tools ?? []).filter(
|
|
14051
14092
|
(t) => !(t.type === "mcp" && t.server_label === "brain")
|
|
14052
14093
|
);
|
|
@@ -14067,25 +14108,13 @@ async function createBrainEnabledVersion(args) {
|
|
|
14067
14108
|
...args.currentMetadata,
|
|
14068
14109
|
brain: args.brainLinkJson
|
|
14069
14110
|
};
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14111
|
+
return createAgentVersion({
|
|
14112
|
+
credential: args.credential,
|
|
14113
|
+
projectEndpoint: args.projectEndpoint,
|
|
14114
|
+
agentName: args.agentName,
|
|
14115
|
+
definition,
|
|
14116
|
+
metadata
|
|
14075
14117
|
});
|
|
14076
|
-
if (!res.ok) {
|
|
14077
|
-
const text = await res.text();
|
|
14078
|
-
throw new LocalCliError({
|
|
14079
|
-
code: "AGENT_CREATE_VERSION_FAILED",
|
|
14080
|
-
message: `POST ${url}: HTTP ${res.status.toString()}
|
|
14081
|
-
${text.slice(0, 500)}`
|
|
14082
|
-
});
|
|
14083
|
-
}
|
|
14084
|
-
const data = await res.json();
|
|
14085
|
-
if (!data.version) {
|
|
14086
|
-
throw new LocalCliError({ code: "AGENT_CREATE_VERSION_NO_VERSION", message: `createVersion returned no version` });
|
|
14087
|
-
}
|
|
14088
|
-
return data.version;
|
|
14089
14118
|
}
|
|
14090
14119
|
|
|
14091
14120
|
// src/lib/brain-seed.ts
|
|
@@ -14125,8 +14154,8 @@ function isM8tBrainMarker(yamlText) {
|
|
|
14125
14154
|
}
|
|
14126
14155
|
if (!parsed || typeof parsed !== "object") return false;
|
|
14127
14156
|
const root = parsed;
|
|
14128
|
-
const
|
|
14129
|
-
return
|
|
14157
|
+
const isObj3 = (v) => typeof v === "object" && v !== null;
|
|
14158
|
+
return isObj3(root.engine) || isObj3(root.processes) || isObj3(root.link);
|
|
14130
14159
|
}
|
|
14131
14160
|
async function createBlankRepo(args) {
|
|
14132
14161
|
const res = await fetch(`https://api.github.com/orgs/${args.org}/repos`, {
|
|
@@ -14280,7 +14309,7 @@ function appRepoProbe(args) {
|
|
|
14280
14309
|
const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
14281
14310
|
return res.status;
|
|
14282
14311
|
},
|
|
14283
|
-
getFile: (
|
|
14312
|
+
getFile: (path32) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path32, fetchImpl: args.fetchImpl }),
|
|
14284
14313
|
isEmpty: async () => {
|
|
14285
14314
|
const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
14286
14315
|
return res.status === 404;
|
|
@@ -14599,7 +14628,9 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14599
14628
|
repoRoot,
|
|
14600
14629
|
installationId: resolvedInstallationId,
|
|
14601
14630
|
skipIfLinked: true,
|
|
14602
|
-
onProgress: progress
|
|
14631
|
+
onProgress: progress,
|
|
14632
|
+
subscriptionId,
|
|
14633
|
+
project
|
|
14603
14634
|
});
|
|
14604
14635
|
} finally {
|
|
14605
14636
|
fs10.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -15290,7 +15321,7 @@ async function deployPromptAdvisor(args) {
|
|
|
15290
15321
|
model,
|
|
15291
15322
|
instructions,
|
|
15292
15323
|
reasoningEffort,
|
|
15293
|
-
metadata: { persona: args.persona, personaVersion }
|
|
15324
|
+
metadata: { persona: args.persona, personaVersion, fillableFieldValues: JSON.stringify(valuesMap) }
|
|
15294
15325
|
});
|
|
15295
15326
|
}
|
|
15296
15327
|
|
|
@@ -15416,7 +15447,7 @@ async function awaitDataPlaneReady(opts) {
|
|
|
15416
15447
|
const consecutive = opts.consecutive ?? 3;
|
|
15417
15448
|
const attempts = opts.attempts ?? 60;
|
|
15418
15449
|
const intervalMs = opts.intervalMs ?? 5e3;
|
|
15419
|
-
const
|
|
15450
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15420
15451
|
let streak = 0;
|
|
15421
15452
|
for (let i = 1; i <= attempts; i++) {
|
|
15422
15453
|
const r = await opts.probe();
|
|
@@ -15438,7 +15469,7 @@ async function awaitDataPlaneReady(opts) {
|
|
|
15438
15469
|
streak = 0;
|
|
15439
15470
|
opts.onProgress?.(`data-plane not ready (${forcedRetryable ? `status ${(r.status ?? 0).toString()}` : cls.category}), waiting\u2026`);
|
|
15440
15471
|
}
|
|
15441
|
-
if (i < attempts) await
|
|
15472
|
+
if (i < attempts) await sleep3(intervalMs);
|
|
15442
15473
|
}
|
|
15443
15474
|
return { ready: false, attempts };
|
|
15444
15475
|
}
|
|
@@ -15559,7 +15590,6 @@ function str2(v) {
|
|
|
15559
15590
|
|
|
15560
15591
|
// src/lib/a2a-enable.ts
|
|
15561
15592
|
var FOUNDRY_ARM_API3 = "2025-04-01-preview";
|
|
15562
|
-
var FOUNDRY_DATA_SCOPE3 = "https://ai.azure.com/.default";
|
|
15563
15593
|
var ARM_SCOPE6 = "https://management.azure.com/.default";
|
|
15564
15594
|
var A2A_TOOLS = ["discover_workers", "invoke_worker", "check_delegation"];
|
|
15565
15595
|
async function enableA2a(args) {
|
|
@@ -15580,7 +15610,7 @@ async function enableA2a(args) {
|
|
|
15580
15610
|
if (current.definition.kind === "hosted") {
|
|
15581
15611
|
progress("Registering the hosted target (metadata only)\u2026");
|
|
15582
15612
|
const metadata2 = { ...current.metadata ?? {}, a2a: "true", a2aCard: a2aCardJson };
|
|
15583
|
-
const foundryVersion2 = await
|
|
15613
|
+
const foundryVersion2 = await createAgentVersion({ credential: args.credential, projectEndpoint: args.projectEndpoint, agentName: args.agentName, definition: current.definition, metadata: metadata2, extraHeaders: { "Foundry-Features": "HostedAgents=V1Preview" } });
|
|
15584
15614
|
return { mode: "target", foundryVersion: foundryVersion2 };
|
|
15585
15615
|
}
|
|
15586
15616
|
if (current.definition.kind !== "prompt") {
|
|
@@ -15605,7 +15635,7 @@ async function enableA2a(args) {
|
|
|
15605
15635
|
a2aCard: a2aCardJson,
|
|
15606
15636
|
a2aBearerHash: bearerHash
|
|
15607
15637
|
};
|
|
15608
|
-
const foundryVersion = await
|
|
15638
|
+
const foundryVersion = await createAgentVersion({ credential: args.credential, projectEndpoint: args.projectEndpoint, agentName: args.agentName, definition, metadata });
|
|
15609
15639
|
return { mode: "caller", connectionName, foundryVersion };
|
|
15610
15640
|
}
|
|
15611
15641
|
async function disableA2a(args) {
|
|
@@ -15622,7 +15652,7 @@ async function disableA2a(args) {
|
|
|
15622
15652
|
delete metadata.a2a;
|
|
15623
15653
|
delete metadata.a2aCard;
|
|
15624
15654
|
delete metadata.a2aBearerHash;
|
|
15625
|
-
const foundryVersion = await
|
|
15655
|
+
const foundryVersion = await createAgentVersion({ credential: args.credential, projectEndpoint: args.projectEndpoint, agentName: args.agentName, definition, metadata });
|
|
15626
15656
|
progress("Deleting the A2A connection\u2026");
|
|
15627
15657
|
await deleteConnection2({ credential: args.credential, projectArmId: args.projectArmId, connectionName });
|
|
15628
15658
|
return { connectionName, foundryVersion };
|
|
@@ -15664,20 +15694,6 @@ async function deleteConnection2(args) {
|
|
|
15664
15694
|
await fetch(url, { method: "DELETE", headers: { Authorization: `Bearer ${token.token}` } }).catch(() => {
|
|
15665
15695
|
});
|
|
15666
15696
|
}
|
|
15667
|
-
async function createVersion(args) {
|
|
15668
|
-
const token = await args.credential.getToken(FOUNDRY_DATA_SCOPE3);
|
|
15669
|
-
if (!token?.token) throw new LocalCliError({ code: "FOUNDRY_AUTH", message: "Could not acquire Foundry data-plane token" });
|
|
15670
|
-
const url = `${args.projectEndpoint}/agents/${args.agentName}/versions?api-version=v1`;
|
|
15671
|
-
const res = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${token.token}`, "Content-Type": "application/json", ...args.extraHeaders ?? {} }, body: JSON.stringify({ definition: args.definition, metadata: args.metadata }) });
|
|
15672
|
-
if (!res.ok) {
|
|
15673
|
-
const text = await res.text();
|
|
15674
|
-
throw new LocalCliError({ code: "A2A_CREATE_VERSION_FAILED", message: `POST ${url}: HTTP ${res.status.toString()}
|
|
15675
|
-
${text.slice(0, 500)}` });
|
|
15676
|
-
}
|
|
15677
|
-
const data = await res.json();
|
|
15678
|
-
if (!data.version) throw new LocalCliError({ code: "A2A_CREATE_VERSION_NO_VERSION", message: "createVersion returned no version" });
|
|
15679
|
-
return data.version;
|
|
15680
|
-
}
|
|
15681
15697
|
|
|
15682
15698
|
// src/lib/agent-remove.ts
|
|
15683
15699
|
init_foundry_agents();
|
|
@@ -16275,6 +16291,7 @@ import * as path16 from "path";
|
|
|
16275
16291
|
import { Command as Command28, Option as Option26 } from "clipanion";
|
|
16276
16292
|
import { DefaultAzureCredential as DefaultAzureCredential13 } from "@azure/identity";
|
|
16277
16293
|
init_errors();
|
|
16294
|
+
init_foundry_agent_get();
|
|
16278
16295
|
|
|
16279
16296
|
// src/lib/preconditions.ts
|
|
16280
16297
|
init_errors();
|
|
@@ -16480,7 +16497,7 @@ init_errors();
|
|
|
16480
16497
|
async function deployHostedWorker(args) {
|
|
16481
16498
|
const onProgress = args.onProgress ?? ((_m) => {
|
|
16482
16499
|
});
|
|
16483
|
-
const
|
|
16500
|
+
const sleep3 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
16484
16501
|
const now = args.now ?? (() => Date.now());
|
|
16485
16502
|
const timeout = args.pollTimeoutMs ?? 8 * 60 * 1e3;
|
|
16486
16503
|
const interval = args.pollIntervalMs ?? 1e4;
|
|
@@ -16532,7 +16549,7 @@ async function deployHostedWorker(args) {
|
|
|
16532
16549
|
hint: "Provisioning can take 2\u20135 min; re-run with the same name to resume, or check the version status in the Foundry portal."
|
|
16533
16550
|
});
|
|
16534
16551
|
}
|
|
16535
|
-
await
|
|
16552
|
+
await sleep3(interval);
|
|
16536
16553
|
}
|
|
16537
16554
|
}
|
|
16538
16555
|
|
|
@@ -16726,20 +16743,6 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
16726
16743
|
hint: "small=0.5/1Gi \xB7 medium=1/2Gi \xB7 large=2/4Gi"
|
|
16727
16744
|
});
|
|
16728
16745
|
}
|
|
16729
|
-
const env = {
|
|
16730
|
-
MODEL_DEPLOYMENT_NAME: this.modelDeployment ?? (typeof this.brain === "string" ? "gpt-5-mini" : DEFAULT_MODEL)
|
|
16731
|
-
};
|
|
16732
|
-
for (const pair of this.env ?? []) {
|
|
16733
|
-
const eq = pair.indexOf("=");
|
|
16734
|
-
if (eq <= 0) {
|
|
16735
|
-
throw new LocalCliError({
|
|
16736
|
-
code: "USAGE",
|
|
16737
|
-
message: `Invalid --env '${pair}'. Expected KEY=VALUE.`,
|
|
16738
|
-
hint: "Example: --env M8T_CODER_MAX_ITERATIONS=20"
|
|
16739
|
-
});
|
|
16740
|
-
}
|
|
16741
|
-
env[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
16742
|
-
}
|
|
16743
16746
|
const persona = this.persona ?? "coding-agent";
|
|
16744
16747
|
const imageInput = this.image ?? DEFAULT_IMAGE;
|
|
16745
16748
|
const repoRef = imageInput.includes("/") ? imageInput : `${DEFAULT_REGISTRY}/${imageInput}`;
|
|
@@ -16760,6 +16763,30 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
16760
16763
|
interactive,
|
|
16761
16764
|
endpoint: this.endpoint
|
|
16762
16765
|
});
|
|
16766
|
+
let currentEnv = {};
|
|
16767
|
+
let currentMetadata = {};
|
|
16768
|
+
try {
|
|
16769
|
+
const current = await getAgentVersion({ credential: credential2, projectEndpoint: project.endpoint, agentName: this.name });
|
|
16770
|
+
const def = current.definition;
|
|
16771
|
+
currentEnv = { ...def.environment_variables ?? {} };
|
|
16772
|
+
currentMetadata = { ...current.metadata ?? {} };
|
|
16773
|
+
} catch {
|
|
16774
|
+
}
|
|
16775
|
+
const env = {
|
|
16776
|
+
...currentEnv,
|
|
16777
|
+
MODEL_DEPLOYMENT_NAME: this.modelDeployment ?? currentEnv.MODEL_DEPLOYMENT_NAME ?? (typeof this.brain === "string" ? "gpt-5-mini" : DEFAULT_MODEL)
|
|
16778
|
+
};
|
|
16779
|
+
for (const pair of this.env ?? []) {
|
|
16780
|
+
const eq = pair.indexOf("=");
|
|
16781
|
+
if (eq <= 0) {
|
|
16782
|
+
throw new LocalCliError({
|
|
16783
|
+
code: "USAGE",
|
|
16784
|
+
message: `Invalid --env '${pair}'. Expected KEY=VALUE.`,
|
|
16785
|
+
hint: "Example: --env M8T_CODER_MAX_ITERATIONS=20"
|
|
16786
|
+
});
|
|
16787
|
+
}
|
|
16788
|
+
env[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
16789
|
+
}
|
|
16763
16790
|
const staged = await stagePublicImageIfNeeded({ image: requestedImage, project });
|
|
16764
16791
|
for (const n of staged.notes) {
|
|
16765
16792
|
this.context.stderr.write(`${colors.hint("note:")} ${n}
|
|
@@ -16804,7 +16831,7 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
16804
16831
|
cpu: preset.cpu,
|
|
16805
16832
|
memory: preset.memory,
|
|
16806
16833
|
env,
|
|
16807
|
-
metadata: { source: "m8t-stack-POC", kind: "hosted", persona: personaName, personaVersion },
|
|
16834
|
+
metadata: { ...currentMetadata, source: "m8t-stack-POC", kind: "hosted", persona: personaName, personaVersion },
|
|
16808
16835
|
onProgress
|
|
16809
16836
|
});
|
|
16810
16837
|
if (typeof this.brain === "string") {
|
|
@@ -17336,6 +17363,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
17336
17363
|
|
|
17337
17364
|
// src/commands/platform/status.ts
|
|
17338
17365
|
import { Command as Command31, Option as Option29 } from "clipanion";
|
|
17366
|
+
import { DefaultAzureCredential as DefaultAzureCredential16 } from "@azure/identity";
|
|
17339
17367
|
|
|
17340
17368
|
// src/lib/platform-update.ts
|
|
17341
17369
|
init_errors();
|
|
@@ -17411,20 +17439,6 @@ function planUpdate(opts) {
|
|
|
17411
17439
|
}
|
|
17412
17440
|
return { kind: "roll", current: currentTag, available, target: available, repo: opts.imageRepo };
|
|
17413
17441
|
}
|
|
17414
|
-
function summarizePlan(plan) {
|
|
17415
|
-
switch (plan.kind) {
|
|
17416
|
-
case "noop":
|
|
17417
|
-
return { current: plan.current, available: plan.available, verdict: "up to date" };
|
|
17418
|
-
case "roll":
|
|
17419
|
-
return { current: plan.current, available: plan.available, verdict: "update available" };
|
|
17420
|
-
case "no-versions":
|
|
17421
|
-
return { current: plan.current, available: "-", verdict: "no published versions found" };
|
|
17422
|
-
case "refuse-byoc":
|
|
17423
|
-
return { current: plan.current, available: "-", verdict: `private-ACR image (${plan.currentRepo}) \u2014 not tracked` };
|
|
17424
|
-
case "tag-not-found":
|
|
17425
|
-
return { current: plan.current, available: plan.requested, verdict: `tag ${plan.requested} not published` };
|
|
17426
|
-
}
|
|
17427
|
-
}
|
|
17428
17442
|
async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
17429
17443
|
if (!repo.startsWith("ghcr.io/")) {
|
|
17430
17444
|
throw new LocalCliError({
|
|
@@ -17433,8 +17447,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
17433
17447
|
hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
|
|
17434
17448
|
});
|
|
17435
17449
|
}
|
|
17436
|
-
const
|
|
17437
|
-
const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${
|
|
17450
|
+
const path32 = repo.replace(/^ghcr\.io\//, "");
|
|
17451
|
+
const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path32}:pull`);
|
|
17438
17452
|
if (!tokenRes.ok) {
|
|
17439
17453
|
throw new LocalCliError({
|
|
17440
17454
|
code: "PLATFORM_GHCR_TOKEN_FAILED",
|
|
@@ -17450,7 +17464,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
17450
17464
|
hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
|
|
17451
17465
|
});
|
|
17452
17466
|
}
|
|
17453
|
-
const tagsRes = await fetchImpl(`https://ghcr.io/v2/${
|
|
17467
|
+
const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path32}/tags/list`, {
|
|
17454
17468
|
headers: { Authorization: `Bearer ${token}` }
|
|
17455
17469
|
});
|
|
17456
17470
|
if (!tagsRes.ok) {
|
|
@@ -17463,204 +17477,1381 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
17463
17477
|
return body.tags ?? [];
|
|
17464
17478
|
}
|
|
17465
17479
|
|
|
17466
|
-
// src/
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
);
|
|
17484
|
-
|
|
17485
|
-
|
|
17486
|
-
|
|
17487
|
-
|
|
17488
|
-
});
|
|
17489
|
-
const { resourceGroup, name } = parseContainerAppResourceId(ctx.containerAppResourceId);
|
|
17490
|
-
const currentImage = (await runAz([
|
|
17491
|
-
"containerapp",
|
|
17492
|
-
"show",
|
|
17493
|
-
"-g",
|
|
17494
|
-
resourceGroup,
|
|
17495
|
-
"-n",
|
|
17496
|
-
name,
|
|
17497
|
-
"--query",
|
|
17498
|
-
"properties.template.containers[0].image",
|
|
17499
|
-
"-o",
|
|
17500
|
-
"tsv"
|
|
17501
|
-
])).trim();
|
|
17502
|
-
const tags = await fetchPublicTags(this.imageRepo);
|
|
17503
|
-
const plan = planUpdate({ currentImage, availableTags: tags, imageRepo: this.imageRepo });
|
|
17504
|
-
const s = summarizePlan(plan);
|
|
17505
|
-
if (mode === "json") {
|
|
17506
|
-
this.context.stdout.write(renderJson({ gateway: name, resourceGroup, ...s, kind: plan.kind }) + "\n");
|
|
17507
|
-
return 0;
|
|
17480
|
+
// src/lib/release-channel.ts
|
|
17481
|
+
import * as fs18 from "fs";
|
|
17482
|
+
|
|
17483
|
+
// ../../packages/platform-release/dist/esm/manifest.js
|
|
17484
|
+
var SEVERITIES = ["critical", "recommended", "optional"];
|
|
17485
|
+
var IMAGE_KEYS = ["gateway", "codingAgent", "azureExecutor", "installer"];
|
|
17486
|
+
function isObj2(v) {
|
|
17487
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17488
|
+
}
|
|
17489
|
+
function validateManifest(m) {
|
|
17490
|
+
const errors = [];
|
|
17491
|
+
if (!isObj2(m))
|
|
17492
|
+
return ["manifest is not an object"];
|
|
17493
|
+
if (m.schemaVersion !== 1)
|
|
17494
|
+
errors.push("schemaVersion must be the integer 1");
|
|
17495
|
+
const p = m.platform;
|
|
17496
|
+
if (!isObj2(p)) {
|
|
17497
|
+
errors.push("platform is required");
|
|
17498
|
+
} else {
|
|
17499
|
+
for (const f of ["version", "tag", "releasedAt", "commit", "notes", "notesUrl"]) {
|
|
17500
|
+
if (typeof p[f] !== "string" || p[f].length === 0)
|
|
17501
|
+
errors.push(`platform.${f} is required`);
|
|
17508
17502
|
}
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
{ key: "available", value: s.available },
|
|
17514
|
-
{ key: "status", value: s.verdict }
|
|
17515
|
-
]) + "\n"
|
|
17516
|
-
);
|
|
17517
|
-
return 0;
|
|
17518
|
-
}
|
|
17519
|
-
};
|
|
17520
|
-
|
|
17521
|
-
// src/commands/platform/update.ts
|
|
17522
|
-
import { Command as Command32, Option as Option30 } from "clipanion";
|
|
17523
|
-
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
17524
|
-
init_errors();
|
|
17525
|
-
var PlatformUpdateCommand = class extends M8tCommand {
|
|
17526
|
-
static paths = [["platform", "update"]];
|
|
17527
|
-
static usage = Command32.Usage({
|
|
17528
|
-
description: "Roll the gateway to the newest published platform image.",
|
|
17529
|
-
details: "Discovers the live gateway, compares its image tag to the newest vX.Y.Z on the public GHCR repo, and (if newer) rolls a new Container Apps revision via 'az containerapp update --image'. A fresh tag is required to roll in single-revision mode. Use --check to preview, --to to pin a version, --yes to skip the prompt."
|
|
17530
|
-
});
|
|
17531
|
-
subscription = Option30.String("--subscription");
|
|
17532
|
-
imageRepo = Option30.String("--image-repo", DEFAULT_IMAGE_REPO);
|
|
17533
|
-
to = Option30.String("--to");
|
|
17534
|
-
resourceGroup = Option30.String("--resource-group", {
|
|
17535
|
-
description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
17536
|
-
});
|
|
17537
|
-
check = Option30.Boolean("--check", false);
|
|
17538
|
-
yes = Option30.Boolean("--yes", false);
|
|
17539
|
-
output = Option30.String("--output");
|
|
17540
|
-
async executeCommand() {
|
|
17541
|
-
const mode = resolveOutputMode(
|
|
17542
|
-
this.output,
|
|
17543
|
-
this.context.stdout
|
|
17544
|
-
);
|
|
17545
|
-
const interactive = this.context.stdout.isTTY === true;
|
|
17546
|
-
const log = (msg) => {
|
|
17547
|
-
if (mode !== "json") this.context.stdout.write(msg + "\n");
|
|
17548
|
-
};
|
|
17549
|
-
const ctx = await resolveGatewayContext({
|
|
17550
|
-
interactive: mode !== "json",
|
|
17551
|
-
subscriptionId: this.subscription,
|
|
17552
|
-
resourceGroup: this.resourceGroup
|
|
17553
|
-
});
|
|
17554
|
-
const { resourceGroup, name } = parseContainerAppResourceId(ctx.containerAppResourceId);
|
|
17555
|
-
const currentImage = (await runAz([
|
|
17556
|
-
"containerapp",
|
|
17557
|
-
"show",
|
|
17558
|
-
"-g",
|
|
17559
|
-
resourceGroup,
|
|
17560
|
-
"-n",
|
|
17561
|
-
name,
|
|
17562
|
-
"--query",
|
|
17563
|
-
"properties.template.containers[0].image",
|
|
17564
|
-
"-o",
|
|
17565
|
-
"tsv"
|
|
17566
|
-
])).trim();
|
|
17567
|
-
const tags = await fetchPublicTags(this.imageRepo);
|
|
17568
|
-
const plan = planUpdate({
|
|
17569
|
-
currentImage,
|
|
17570
|
-
availableTags: tags,
|
|
17571
|
-
imageRepo: this.imageRepo,
|
|
17572
|
-
to: this.to
|
|
17573
|
-
});
|
|
17574
|
-
const s = summarizePlan(plan);
|
|
17575
|
-
if (plan.kind === "refuse-byoc") {
|
|
17576
|
-
throw new LocalCliError({
|
|
17577
|
-
code: "PLATFORM_BYOC_NOT_TRACKED",
|
|
17578
|
-
message: `This gateway runs a private-ACR image (${plan.currentRepo}), not the tracked public repo (${plan.trackedRepo}).`,
|
|
17579
|
-
hint: "BYOC instances update by rebuilding + pushing your own image, then 'm8t deploy --image-ref <your-acr>'. Use --image-repo to track a different public repo."
|
|
17580
|
-
});
|
|
17503
|
+
if (!SEVERITIES.includes(p.severity))
|
|
17504
|
+
errors.push(`platform.severity must be one of ${SEVERITIES.join("|")}`);
|
|
17505
|
+
if (!(typeof p.previousVersion === "string" || p.previousVersion === null)) {
|
|
17506
|
+
errors.push("platform.previousVersion must be a string or null");
|
|
17581
17507
|
}
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17587
|
-
|
|
17508
|
+
}
|
|
17509
|
+
const c = m.components;
|
|
17510
|
+
if (!isObj2(c)) {
|
|
17511
|
+
errors.push("components is required");
|
|
17512
|
+
return errors;
|
|
17513
|
+
}
|
|
17514
|
+
for (const key2 of IMAGE_KEYS) {
|
|
17515
|
+
const img = c[key2];
|
|
17516
|
+
if (!isObj2(img)) {
|
|
17517
|
+
errors.push(`components.${key2} is required`);
|
|
17518
|
+
continue;
|
|
17588
17519
|
}
|
|
17589
|
-
if (
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
});
|
|
17520
|
+
if (img.kind !== "image")
|
|
17521
|
+
errors.push(`components.${key2}.kind must be "image"`);
|
|
17522
|
+
for (const f of ["ref", "tag", "version"]) {
|
|
17523
|
+
if (typeof img[f] !== "string" || img[f].length === 0)
|
|
17524
|
+
errors.push(`components.${key2}.${f} is required`);
|
|
17595
17525
|
}
|
|
17596
|
-
if (
|
|
17597
|
-
|
|
17598
|
-
renderKeyValueBlock([
|
|
17599
|
-
{ key: "gateway", value: name },
|
|
17600
|
-
{ key: "current", value: s.current },
|
|
17601
|
-
{ key: "available", value: s.available },
|
|
17602
|
-
{ key: "status", value: s.verdict }
|
|
17603
|
-
]) + "\n"
|
|
17604
|
-
);
|
|
17526
|
+
if (typeof img.digest !== "string" || !/^sha256:[0-9a-f]+$/.test(img.digest)) {
|
|
17527
|
+
errors.push(`components.${key2}.digest must be a sha256:\u2026 digest`);
|
|
17605
17528
|
}
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17529
|
+
}
|
|
17530
|
+
const cli2 = c.cli;
|
|
17531
|
+
if (!isObj2(cli2) || cli2.kind !== "npm") {
|
|
17532
|
+
errors.push("components.cli (kind: npm) is required");
|
|
17533
|
+
} else {
|
|
17534
|
+
for (const f of ["package", "version", "min", "recommended"]) {
|
|
17535
|
+
if (typeof cli2[f] !== "string" || cli2[f].length === 0)
|
|
17536
|
+
errors.push(`components.cli.${f} is required`);
|
|
17610
17537
|
}
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17538
|
+
}
|
|
17539
|
+
const personas = c.personas;
|
|
17540
|
+
if (!isObj2(personas) || !isObj2(personas.items)) {
|
|
17541
|
+
errors.push("components.personas.items is required");
|
|
17542
|
+
} else {
|
|
17543
|
+
for (const [name, item] of Object.entries(personas.items)) {
|
|
17544
|
+
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
17545
|
+
errors.push(`components.personas.items.${name}.path is required`);
|
|
17546
|
+
}
|
|
17547
|
+
if (!isObj2(item) || typeof item.treeSha !== "string" || item.treeSha.length < 4) {
|
|
17548
|
+
errors.push(`components.personas.items.${name}.treeSha is required`);
|
|
17549
|
+
}
|
|
17615
17550
|
}
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17623
|
-
|
|
17551
|
+
}
|
|
17552
|
+
const seeds = c.brainSeeds;
|
|
17553
|
+
if (!isObj2(seeds) || !isObj2(seeds.items)) {
|
|
17554
|
+
errors.push("components.brainSeeds.items is required");
|
|
17555
|
+
} else {
|
|
17556
|
+
for (const [name, item] of Object.entries(seeds.items)) {
|
|
17557
|
+
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
17558
|
+
errors.push(`components.brainSeeds.items.${name}.path is required`);
|
|
17559
|
+
}
|
|
17560
|
+
if (!isObj2(item) || !isObj2(item.subtrees)) {
|
|
17561
|
+
errors.push(`components.brainSeeds.items.${name}.subtrees is required`);
|
|
17624
17562
|
}
|
|
17625
17563
|
}
|
|
17626
|
-
log(`rolling to ${plan.target}\u2026`);
|
|
17627
|
-
await runAz([
|
|
17628
|
-
"containerapp",
|
|
17629
|
-
"update",
|
|
17630
|
-
"-n",
|
|
17631
|
-
name,
|
|
17632
|
-
"-g",
|
|
17633
|
-
resourceGroup,
|
|
17634
|
-
"--image",
|
|
17635
|
-
`${plan.repo}:${plan.target}`
|
|
17636
|
-
]);
|
|
17637
|
-
if (mode === "json") this.context.stdout.write(renderJson({ ...s, kind: plan.kind, rolled: true, rolledTo: plan.target }) + "\n");
|
|
17638
|
-
else log(colors.success(`rolled ${plan.current} \u2192 ${plan.target} \u2713`));
|
|
17639
|
-
return 0;
|
|
17640
17564
|
}
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17565
|
+
const infra = c.infra;
|
|
17566
|
+
if (infra !== void 0) {
|
|
17567
|
+
if (!isObj2(infra) || infra.kind !== "infra") {
|
|
17568
|
+
errors.push('components.infra.kind must be "infra"');
|
|
17569
|
+
}
|
|
17570
|
+
if (!isObj2(infra) || typeof infra.treeSha !== "string" || infra.treeSha.length < 4) {
|
|
17571
|
+
errors.push("components.infra.treeSha is required");
|
|
17572
|
+
}
|
|
17573
|
+
}
|
|
17574
|
+
return errors;
|
|
17575
|
+
}
|
|
17645
17576
|
|
|
17646
|
-
//
|
|
17647
|
-
|
|
17648
|
-
|
|
17577
|
+
// ../../packages/platform-release/dist/esm/stamp.js
|
|
17578
|
+
var TABLE = "Metadata";
|
|
17579
|
+
var PK = "system";
|
|
17580
|
+
var RK = "platform";
|
|
17581
|
+
function stampToEntity(s) {
|
|
17582
|
+
return {
|
|
17583
|
+
partitionKey: PK,
|
|
17584
|
+
rowKey: RK,
|
|
17585
|
+
platformVersion: s.platformVersion,
|
|
17586
|
+
updatedAt: s.updatedAt,
|
|
17587
|
+
lastResult: s.lastResult,
|
|
17588
|
+
value: JSON.stringify(s)
|
|
17589
|
+
};
|
|
17590
|
+
}
|
|
17591
|
+
function entityToStamp(e) {
|
|
17592
|
+
const raw = e.value;
|
|
17593
|
+
if (typeof raw !== "string")
|
|
17594
|
+
return null;
|
|
17649
17595
|
try {
|
|
17650
|
-
|
|
17651
|
-
|
|
17652
|
-
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17596
|
+
return JSON.parse(raw);
|
|
17597
|
+
} catch {
|
|
17598
|
+
return null;
|
|
17599
|
+
}
|
|
17600
|
+
}
|
|
17601
|
+
|
|
17602
|
+
// ../../packages/platform-release/dist/esm/channel-url.js
|
|
17603
|
+
var CHANNEL_LATEST_URL = "https://github.com/m8t-run/m8t/releases/latest/download/manifest.json";
|
|
17604
|
+
function channelUrlForVersion(tag) {
|
|
17605
|
+
return `https://github.com/m8t-run/m8t/releases/download/${tag}/manifest.json`;
|
|
17606
|
+
}
|
|
17607
|
+
|
|
17608
|
+
// src/lib/release-channel.ts
|
|
17609
|
+
init_errors();
|
|
17610
|
+
async function fetchManifest(source, deps = {}) {
|
|
17611
|
+
const readFile8 = deps.readFile ?? ((p) => fs18.readFileSync(p, "utf8"));
|
|
17612
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17613
|
+
const ghToken = deps.ghToken ?? getGhToken;
|
|
17614
|
+
let raw;
|
|
17615
|
+
if ("file" in source) {
|
|
17616
|
+
try {
|
|
17617
|
+
raw = readFile8(source.file);
|
|
17618
|
+
} catch (e) {
|
|
17619
|
+
throw new LocalCliError({ code: "PLATFORM_MANIFEST_READ_FAILED", message: `Could not read manifest file '${source.file}': ${e.message}` });
|
|
17620
|
+
}
|
|
17621
|
+
} else {
|
|
17622
|
+
const url = "url" in source ? source.url : source.version ? channelUrlForVersion(source.version) : CHANNEL_LATEST_URL;
|
|
17623
|
+
let token;
|
|
17624
|
+
try {
|
|
17625
|
+
token = await ghToken();
|
|
17626
|
+
} catch {
|
|
17627
|
+
token = void 0;
|
|
17628
|
+
}
|
|
17629
|
+
const res = await fetchImpl(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
17630
|
+
if (!res.ok) {
|
|
17631
|
+
throw new LocalCliError({
|
|
17632
|
+
code: "PLATFORM_MANIFEST_FETCH_FAILED",
|
|
17633
|
+
message: `GET ${url} returned HTTP ${res.status.toString()}.`,
|
|
17634
|
+
hint: "The release channel is auth-gated until the repo is public. Run 'gh auth login', or pass --manifest-file / --manifest-url."
|
|
17635
|
+
});
|
|
17636
|
+
}
|
|
17637
|
+
raw = JSON.stringify(await res.json());
|
|
17638
|
+
}
|
|
17639
|
+
let parsed;
|
|
17640
|
+
try {
|
|
17641
|
+
parsed = JSON.parse(raw);
|
|
17642
|
+
} catch (e) {
|
|
17643
|
+
throw new LocalCliError({ code: "PLATFORM_MANIFEST_PARSE_FAILED", message: `Manifest is not valid JSON: ${e.message}` });
|
|
17644
|
+
}
|
|
17645
|
+
const errors = validateManifest(parsed);
|
|
17646
|
+
if (errors.length > 0) {
|
|
17647
|
+
throw new LocalCliError({
|
|
17648
|
+
code: "PLATFORM_MANIFEST_INVALID",
|
|
17649
|
+
message: `Fetched manifest failed validation:
|
|
17650
|
+
- ${errors.join("\n - ")}`
|
|
17651
|
+
});
|
|
17652
|
+
}
|
|
17653
|
+
return parsed;
|
|
17654
|
+
}
|
|
17655
|
+
|
|
17656
|
+
// src/lib/release-content.ts
|
|
17657
|
+
import * as fs19 from "fs";
|
|
17658
|
+
import * as os8 from "os";
|
|
17659
|
+
import * as path18 from "path";
|
|
17660
|
+
import { execFileSync } from "child_process";
|
|
17661
|
+
init_errors();
|
|
17662
|
+
var OWNER_REPO = "m8t-run/m8t";
|
|
17663
|
+
function parseTreeResponse(body) {
|
|
17664
|
+
const map = /* @__PURE__ */ new Map();
|
|
17665
|
+
for (const e of body.tree ?? []) if (e.type === "tree") map.set(e.path, e.sha);
|
|
17666
|
+
return { treeShaOf: (p) => map.get(p.replace(/\/$/, "")) };
|
|
17667
|
+
}
|
|
17668
|
+
async function fetchRepoTree(commit, deps = {}) {
|
|
17669
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17670
|
+
const ghToken = deps.ghToken ?? getGhToken;
|
|
17671
|
+
let token;
|
|
17672
|
+
try {
|
|
17673
|
+
token = await ghToken();
|
|
17674
|
+
} catch {
|
|
17675
|
+
token = void 0;
|
|
17676
|
+
}
|
|
17677
|
+
const url = `https://api.github.com/repos/${OWNER_REPO}/git/trees/${commit}?recursive=1`;
|
|
17678
|
+
const res = await fetchImpl(url, {
|
|
17679
|
+
headers: { Accept: "application/vnd.github+json", ...token ? { Authorization: `Bearer ${token}` } : {} }
|
|
17680
|
+
});
|
|
17681
|
+
if (!res.ok) {
|
|
17682
|
+
throw new LocalCliError({ code: "PLATFORM_TREE_FETCH_FAILED", message: `GET ${url} \u2192 HTTP ${res.status.toString()}.` });
|
|
17683
|
+
}
|
|
17684
|
+
return parseTreeResponse(await res.json());
|
|
17685
|
+
}
|
|
17686
|
+
function verifyPersonaTrees(manifest, tree) {
|
|
17687
|
+
const out = [];
|
|
17688
|
+
for (const [name, item] of Object.entries(manifest.components.personas.items)) {
|
|
17689
|
+
const actual = tree.treeShaOf(item.path);
|
|
17690
|
+
if (actual !== void 0 && actual !== item.treeSha) {
|
|
17691
|
+
out.push(`persona '${name}' (${item.path}): manifest pins ${item.treeSha} but commit tree has ${actual}`);
|
|
17692
|
+
}
|
|
17693
|
+
}
|
|
17694
|
+
return out;
|
|
17695
|
+
}
|
|
17696
|
+
async function resolveReleaseContent(manifest, opts = {}) {
|
|
17697
|
+
if (opts.contentDir) {
|
|
17698
|
+
if (!fs19.existsSync(path18.join(opts.contentDir, "personas"))) {
|
|
17699
|
+
throw new LocalCliError({ code: "PLATFORM_CONTENT_DIR_INVALID", message: `--content-dir '${opts.contentDir}' has no personas/ \u2014 not a repo checkout.` });
|
|
17700
|
+
}
|
|
17701
|
+
return opts.contentDir;
|
|
17702
|
+
}
|
|
17703
|
+
const commit = manifest.platform.commit;
|
|
17704
|
+
const cacheRoot = opts.cacheRoot ?? path18.join(os8.homedir(), ".m8t-stack", "cache");
|
|
17705
|
+
const dest = path18.join(cacheRoot, `release-${commit}`);
|
|
17706
|
+
if (fs19.existsSync(path18.join(dest, "personas"))) return dest;
|
|
17707
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
17708
|
+
const ghToken = opts.ghToken ?? getGhToken;
|
|
17709
|
+
let token;
|
|
17710
|
+
try {
|
|
17711
|
+
token = await ghToken();
|
|
17712
|
+
} catch {
|
|
17713
|
+
token = void 0;
|
|
17714
|
+
}
|
|
17715
|
+
const url = `https://api.github.com/repos/${OWNER_REPO}/tarball/${commit}`;
|
|
17716
|
+
const res = await fetchImpl(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
17717
|
+
if (!res.ok) {
|
|
17718
|
+
throw new LocalCliError({ code: "PLATFORM_TARBALL_FETCH_FAILED", message: `GET ${url} \u2192 HTTP ${res.status.toString()}.` });
|
|
17719
|
+
}
|
|
17720
|
+
fs19.mkdirSync(cacheRoot, { recursive: true });
|
|
17721
|
+
const tmp = fs19.mkdtempSync(path18.join(cacheRoot, `.dl-${commit.slice(0, 8)}-`));
|
|
17722
|
+
try {
|
|
17723
|
+
const tgz = path18.join(tmp, "src.tar.gz");
|
|
17724
|
+
fs19.writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
|
|
17725
|
+
const unpacked = path18.join(tmp, "unpacked");
|
|
17726
|
+
fs19.mkdirSync(unpacked);
|
|
17727
|
+
extractTarGz(tgz, unpacked);
|
|
17728
|
+
try {
|
|
17729
|
+
fs19.renameSync(unpacked, dest);
|
|
17730
|
+
} catch (e) {
|
|
17731
|
+
const err = e;
|
|
17732
|
+
const isBenignRace = (err.code === "EEXIST" || err.code === "ENOTEMPTY") && fs19.existsSync(path18.join(dest, "personas"));
|
|
17733
|
+
if (!isBenignRace) {
|
|
17734
|
+
throw new LocalCliError({
|
|
17735
|
+
code: "PLATFORM_CACHE_PUBLISH_FAILED",
|
|
17736
|
+
message: `Could not publish release cache to ${dest}: ${err.message}`
|
|
17737
|
+
});
|
|
17738
|
+
}
|
|
17739
|
+
}
|
|
17740
|
+
} finally {
|
|
17741
|
+
fs19.rmSync(tmp, { recursive: true, force: true });
|
|
17742
|
+
}
|
|
17743
|
+
return dest;
|
|
17744
|
+
}
|
|
17745
|
+
function extractTarGz(tgz, into) {
|
|
17746
|
+
execFileSync("tar", ["-xzf", tgz, "-C", into, "--strip-components=1"]);
|
|
17747
|
+
}
|
|
17748
|
+
|
|
17749
|
+
// src/lib/platform-stamp.ts
|
|
17750
|
+
import { TableClient, AzureNamedKeyCredential } from "@azure/data-tables";
|
|
17751
|
+
init_errors();
|
|
17752
|
+
init_rbac();
|
|
17753
|
+
|
|
17754
|
+
// src/lib/platform-storage-discovery.ts
|
|
17755
|
+
init_http();
|
|
17756
|
+
init_errors();
|
|
17757
|
+
var ARM3 = "https://management.azure.com";
|
|
17758
|
+
var ARM_SCOPE7 = "https://management.azure.com/.default";
|
|
17759
|
+
var STORAGE_API = "2023-05-01";
|
|
17760
|
+
async function discoverStampStorage(opts) {
|
|
17761
|
+
const list = await authedJson({
|
|
17762
|
+
credential: opts.credential,
|
|
17763
|
+
scope: ARM_SCOPE7,
|
|
17764
|
+
method: "GET",
|
|
17765
|
+
url: `${ARM3}/subscriptions/${opts.subscriptionId}/resourceGroups/${opts.resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API}`
|
|
17766
|
+
}) ?? {};
|
|
17767
|
+
const accounts = (list.value ?? []).filter((a) => a.properties?.primaryEndpoints?.table);
|
|
17768
|
+
const chosen = accounts.find((a) => a.tags?.["m8t-stack"] === "storage") ?? accounts.find((a) => a.tags?.["m8t-stack:role"] === "gateway" || a.tags?.["m8t-stack:role"] === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
|
|
17769
|
+
const tableEndpoint = chosen?.properties?.primaryEndpoints?.table?.replace(/\/$/, "");
|
|
17770
|
+
if (!tableEndpoint || !chosen?.id || !chosen.name) {
|
|
17771
|
+
throw new LocalCliError({
|
|
17772
|
+
code: "PLATFORM_STAMP_STORAGE_NOT_FOUND",
|
|
17773
|
+
message: `No table-capable storage account found in resource group ${opts.resourceGroup}.`,
|
|
17774
|
+
hint: `Found: ${accounts.map((a) => a.name).join(", ") || "(none)"}.`
|
|
17775
|
+
});
|
|
17776
|
+
}
|
|
17777
|
+
return { tableEndpoint, accountResourceId: chosen.id, accountName: chosen.name };
|
|
17778
|
+
}
|
|
17779
|
+
|
|
17780
|
+
// src/lib/platform-stamp.ts
|
|
17781
|
+
async function makeSharedKeyClient(opts) {
|
|
17782
|
+
const rg = /\/resourceGroups\/([^/]+)\//i.exec(opts.accountResourceId)?.[1];
|
|
17783
|
+
if (!rg) return null;
|
|
17784
|
+
const key2 = (await runAz(["storage", "account", "keys", "list", "--account-name", opts.accountName, "-g", rg, "--query", "[0].value", "-o", "tsv"])).trim();
|
|
17785
|
+
if (!key2) return null;
|
|
17786
|
+
return new TableClient(opts.tableEndpoint, TABLE, new AzureNamedKeyCredential(opts.accountName, key2));
|
|
17787
|
+
}
|
|
17788
|
+
async function writeStamp(opts) {
|
|
17789
|
+
const { tableEndpoint, accountResourceId, accountName } = await discoverStampStorage(opts);
|
|
17790
|
+
const entity = stampToEntity(opts.stamp);
|
|
17791
|
+
const aad = new TableClient(tableEndpoint, TABLE, opts.credential);
|
|
17792
|
+
await aad.createTable().catch(() => void 0);
|
|
17793
|
+
try {
|
|
17794
|
+
await aad.upsertEntity(entity, "Replace");
|
|
17795
|
+
return;
|
|
17796
|
+
} catch (e) {
|
|
17797
|
+
if (!is403(e)) throw e;
|
|
17798
|
+
}
|
|
17799
|
+
opts.onProgress?.("writing the platform stamp with the account key \u2014 your identity lacks the Storage Table data role.");
|
|
17800
|
+
const shared = await makeSharedKeyClient({ tableEndpoint, accountResourceId, accountName });
|
|
17801
|
+
if (shared) {
|
|
17802
|
+
await shared.createTable().catch(() => void 0);
|
|
17803
|
+
await shared.upsertEntity(entity, "Replace");
|
|
17804
|
+
return;
|
|
17805
|
+
}
|
|
17806
|
+
opts.onProgress?.("shared-key access is disabled \u2014 granting Storage Table Data Contributor to your identity (one-time)\u2026");
|
|
17807
|
+
try {
|
|
17808
|
+
const oid = await getCallerObjectId();
|
|
17809
|
+
await grantStorageTableDataContributor({
|
|
17810
|
+
credential: opts.credential,
|
|
17811
|
+
subscriptionId: opts.subscriptionId,
|
|
17812
|
+
scope: accountResourceId,
|
|
17813
|
+
principalId: oid
|
|
17814
|
+
});
|
|
17815
|
+
} catch (e) {
|
|
17816
|
+
throw new LocalCliError({
|
|
17817
|
+
code: "PLATFORM_STAMP_NO_ACCESS",
|
|
17818
|
+
message: `Cannot write the platform stamp: AAD lacks the Storage Table data role for ${accountName}, shared-key access is disabled, and self-granting the role failed.`,
|
|
17819
|
+
hint: "Grant yourself 'Storage Table Data Contributor' on the storage account and retry.",
|
|
17820
|
+
cause: e
|
|
17821
|
+
});
|
|
17822
|
+
}
|
|
17823
|
+
for (let i = 0; i < 6; i++) {
|
|
17824
|
+
await sleep2(1e4);
|
|
17825
|
+
try {
|
|
17826
|
+
await aad.upsertEntity(entity, "Replace");
|
|
17827
|
+
return;
|
|
17828
|
+
} catch (e) {
|
|
17829
|
+
if (!is403(e)) throw e;
|
|
17830
|
+
}
|
|
17831
|
+
}
|
|
17832
|
+
throw new LocalCliError({
|
|
17833
|
+
code: "PLATFORM_STAMP_NO_ACCESS",
|
|
17834
|
+
message: `Cannot write the platform stamp: granted 'Storage Table Data Contributor' on ${accountName}, but the data-plane role has not propagated yet.`,
|
|
17835
|
+
hint: "The role assignment persists \u2014 retry 'm8t platform update' in a few minutes."
|
|
17836
|
+
});
|
|
17837
|
+
}
|
|
17838
|
+
async function readStamp(opts) {
|
|
17839
|
+
const { tableEndpoint, accountResourceId, accountName } = await discoverStampStorage(opts);
|
|
17840
|
+
const aad = new TableClient(tableEndpoint, TABLE, opts.credential);
|
|
17841
|
+
try {
|
|
17842
|
+
const row = await aad.getEntity(PK, RK);
|
|
17843
|
+
return entityToStamp(row);
|
|
17844
|
+
} catch (e) {
|
|
17845
|
+
if (is404(e)) return null;
|
|
17846
|
+
if (!is403(e)) return null;
|
|
17847
|
+
}
|
|
17848
|
+
try {
|
|
17849
|
+
const shared = await makeSharedKeyClient({ tableEndpoint, accountResourceId, accountName });
|
|
17850
|
+
if (!shared) return null;
|
|
17851
|
+
const row = await shared.getEntity(PK, RK);
|
|
17852
|
+
return entityToStamp(row);
|
|
17853
|
+
} catch {
|
|
17854
|
+
return null;
|
|
17855
|
+
}
|
|
17856
|
+
}
|
|
17857
|
+
function is403(e) {
|
|
17858
|
+
return e.statusCode === 403;
|
|
17859
|
+
}
|
|
17860
|
+
function is404(e) {
|
|
17861
|
+
return e.statusCode === 404;
|
|
17862
|
+
}
|
|
17863
|
+
function sleep2(ms) {
|
|
17864
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
17865
|
+
}
|
|
17866
|
+
|
|
17867
|
+
// src/lib/platform-converge.ts
|
|
17868
|
+
import * as fs21 from "fs";
|
|
17869
|
+
import * as path20 from "path";
|
|
17870
|
+
import { parse as parseYaml10 } from "yaml";
|
|
17871
|
+
|
|
17872
|
+
// src/lib/deploy.ts
|
|
17873
|
+
import * as fs20 from "fs/promises";
|
|
17874
|
+
import * as os9 from "os";
|
|
17875
|
+
import * as path19 from "path";
|
|
17876
|
+
init_errors();
|
|
17877
|
+
var FOUNDRY_TRACING_MODES = ["project", "account", "skip"];
|
|
17878
|
+
function parseFoundryTracingMode(v) {
|
|
17879
|
+
if (v === void 0) return void 0;
|
|
17880
|
+
if (!FOUNDRY_TRACING_MODES.includes(v)) {
|
|
17881
|
+
throw new LocalCliError({
|
|
17882
|
+
code: "DEPLOY_INVALID_TRACING_MODE",
|
|
17883
|
+
message: `Invalid --foundry-tracing value: '${v}'.`,
|
|
17884
|
+
hint: `Use one of: ${FOUNDRY_TRACING_MODES.join(", ")}.`
|
|
17885
|
+
});
|
|
17886
|
+
}
|
|
17887
|
+
return v;
|
|
17888
|
+
}
|
|
17889
|
+
function buildBicepParams(p) {
|
|
17890
|
+
const params = [
|
|
17891
|
+
`location=${p.location}`,
|
|
17892
|
+
`suffix=${p.suffix}`,
|
|
17893
|
+
`imageRef=${p.imageRef}`,
|
|
17894
|
+
`tenantId=${p.tenantId}`,
|
|
17895
|
+
`clientId=${p.clientId}`,
|
|
17896
|
+
`foundryResourceId=${p.foundryResourceId}`,
|
|
17897
|
+
`foundryProjectEndpoint=${p.foundryProjectEndpoint}`,
|
|
17898
|
+
`acrPullIdentityResourceId=${p.acrPullIdentityResourceId}`,
|
|
17899
|
+
`acrResourceId=${p.acrResourceId}`
|
|
17900
|
+
];
|
|
17901
|
+
if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
|
|
17902
|
+
return params;
|
|
17903
|
+
}
|
|
17904
|
+
async function resolveRepoRoot2(home = os9.homedir()) {
|
|
17905
|
+
const marker = path19.join(home, ".m8t-stack", "repo-root");
|
|
17906
|
+
try {
|
|
17907
|
+
const root = (await fs20.readFile(marker, "utf8")).trim();
|
|
17908
|
+
if (!root) throw new Error("empty");
|
|
17909
|
+
return root;
|
|
17910
|
+
} catch {
|
|
17911
|
+
throw new LocalCliError({
|
|
17912
|
+
code: "DEPLOY_NO_REPO_ROOT",
|
|
17913
|
+
message: "~/.m8t-stack/repo-root marker is missing or empty.",
|
|
17914
|
+
hint: "Run 'm8t install' from an m8t-stack checkout (it writes the marker), or run 'm8t deploy' from the repo."
|
|
17915
|
+
});
|
|
17916
|
+
}
|
|
17917
|
+
}
|
|
17918
|
+
async function resolveFoundryResourceId(endpoint, explicit) {
|
|
17919
|
+
if (explicit) return explicit;
|
|
17920
|
+
const m = /^https:\/\/([^.]+)\.services\.ai\.azure\.com/.exec(endpoint);
|
|
17921
|
+
if (!m) {
|
|
17922
|
+
throw new LocalCliError({
|
|
17923
|
+
code: "DEPLOY_FOUNDRY_ENDPOINT_BAD",
|
|
17924
|
+
message: `Cannot parse the Foundry account name from endpoint: ${endpoint}`,
|
|
17925
|
+
hint: "Pass --foundry-resource-id <ARM-id> explicitly."
|
|
17926
|
+
});
|
|
17927
|
+
}
|
|
17928
|
+
const accounts = JSON.parse(
|
|
17929
|
+
await runAz([
|
|
17930
|
+
"cognitiveservices",
|
|
17931
|
+
"account",
|
|
17932
|
+
"list",
|
|
17933
|
+
"--query",
|
|
17934
|
+
`[?name=='${m[1]}'].id`,
|
|
17935
|
+
"--output",
|
|
17936
|
+
"json"
|
|
17937
|
+
])
|
|
17938
|
+
);
|
|
17939
|
+
if (accounts.length === 0) {
|
|
17940
|
+
throw new LocalCliError({
|
|
17941
|
+
code: "DEPLOY_FOUNDRY_NOT_FOUND",
|
|
17942
|
+
message: `No Foundry account named '${m[1]}' in the active subscription.`,
|
|
17943
|
+
hint: "Check the endpoint, or pass --foundry-resource-id explicitly."
|
|
17944
|
+
});
|
|
17945
|
+
}
|
|
17946
|
+
if (accounts.length > 1) {
|
|
17947
|
+
throw new LocalCliError({
|
|
17948
|
+
code: "DEPLOY_FOUNDRY_AMBIGUOUS",
|
|
17949
|
+
message: `Multiple Foundry accounts named '${m[1]}' \u2014 ambiguous.`,
|
|
17950
|
+
hint: "Pass --foundry-resource-id <ARM-id> to disambiguate."
|
|
17951
|
+
});
|
|
17952
|
+
}
|
|
17953
|
+
return accounts[0];
|
|
17954
|
+
}
|
|
17955
|
+
async function resolveAcrResourceId(opts) {
|
|
17956
|
+
if (opts.explicit) return opts.explicit;
|
|
17957
|
+
const host = opts.imageRef.split("/")[0];
|
|
17958
|
+
if (!host.endsWith(".azurecr.io")) return "";
|
|
17959
|
+
const acrName = host.split(".")[0];
|
|
17960
|
+
try {
|
|
17961
|
+
const id = (await runAz(["acr", "show", "-n", acrName, "--query", "id", "-o", "tsv"])).trim();
|
|
17962
|
+
if (!id) throw new Error("empty id");
|
|
17963
|
+
return id;
|
|
17964
|
+
} catch (e) {
|
|
17965
|
+
if (e instanceof LocalCliError) throw e;
|
|
17966
|
+
throw new LocalCliError({
|
|
17967
|
+
code: "DEPLOY_ACR_NOT_FOUND",
|
|
17968
|
+
message: `Cannot resolve the ACR '${acrName}' for image ${opts.imageRef}.`,
|
|
17969
|
+
hint: "Create it (az acr create), sign in (az acr login), and push the web image (docker buildx \u2026 --push); or pass --acr-resource-id <ARM-id>."
|
|
17970
|
+
});
|
|
17971
|
+
}
|
|
17972
|
+
}
|
|
17973
|
+
async function ensureResourceGroup(name, location) {
|
|
17974
|
+
const existing = JSON.parse(
|
|
17975
|
+
await runAz(["group", "show", "--name", name, "--output", "json"]).catch(() => "") || "null"
|
|
17976
|
+
);
|
|
17977
|
+
if (existing) return;
|
|
17978
|
+
await runAz(["group", "create", "--name", name, "--location", location, "--tags", "m8t-stack=deployment"]);
|
|
17979
|
+
}
|
|
17980
|
+
function resolveAcrPullIdentity(opts) {
|
|
17981
|
+
return opts.explicit ?? "";
|
|
17982
|
+
}
|
|
17983
|
+
async function runBicepDeployment(opts) {
|
|
17984
|
+
const bicepPath = path19.join(opts.repoRoot, "deploy", "main.bicep");
|
|
17985
|
+
const result = JSON.parse(
|
|
17986
|
+
await runAz([
|
|
17987
|
+
"deployment",
|
|
17988
|
+
"group",
|
|
17989
|
+
"create",
|
|
17990
|
+
"--name",
|
|
17991
|
+
opts.deploymentName,
|
|
17992
|
+
"--resource-group",
|
|
17993
|
+
opts.resourceGroup,
|
|
17994
|
+
"--template-file",
|
|
17995
|
+
bicepPath,
|
|
17996
|
+
"--parameters",
|
|
17997
|
+
...opts.params,
|
|
17998
|
+
"--output",
|
|
17999
|
+
"json"
|
|
18000
|
+
])
|
|
18001
|
+
);
|
|
18002
|
+
const outputs = result.properties?.outputs ?? {};
|
|
18003
|
+
const fqdn = outputs.containerAppFqdn?.value;
|
|
18004
|
+
if (typeof fqdn !== "string" || !fqdn) {
|
|
18005
|
+
throw new LocalCliError({
|
|
18006
|
+
code: "DEPLOY_NO_FQDN",
|
|
18007
|
+
message: "Bicep deployment did not return containerAppFqdn.",
|
|
18008
|
+
hint: "Check deploy/main.bicep outputs and the deployment log."
|
|
18009
|
+
});
|
|
18010
|
+
}
|
|
18011
|
+
return {
|
|
18012
|
+
containerAppFqdn: fqdn,
|
|
18013
|
+
resourceNames: outputs.resourceNames?.value ?? {}
|
|
18014
|
+
};
|
|
18015
|
+
}
|
|
18016
|
+
|
|
18017
|
+
// src/lib/platform-converge.ts
|
|
18018
|
+
init_foundry_agent_get();
|
|
18019
|
+
init_errors();
|
|
18020
|
+
|
|
18021
|
+
// src/lib/persona-compose.ts
|
|
18022
|
+
function composeInstructions(args) {
|
|
18023
|
+
const live = args.current.definition.instructions ?? "";
|
|
18024
|
+
let out = args.baseBody;
|
|
18025
|
+
const hadLoader = stripBrainLoader(live) !== live;
|
|
18026
|
+
if (hadLoader && args.loaderText) {
|
|
18027
|
+
const templated = args.brainRepo ? args.loaderText.replaceAll("{{brain_repo}}", args.brainRepo) : args.loaderText;
|
|
18028
|
+
out = appendBrainLoader(out, templated);
|
|
18029
|
+
}
|
|
18030
|
+
if (hasA2aSnippet(live)) out = appendSnippet(out);
|
|
18031
|
+
return out;
|
|
18032
|
+
}
|
|
18033
|
+
function resolveFillableValues(current, yamlValues) {
|
|
18034
|
+
const raw = current.metadata?.fillableFieldValues;
|
|
18035
|
+
if (typeof raw === "string") {
|
|
18036
|
+
try {
|
|
18037
|
+
return JSON.parse(raw);
|
|
18038
|
+
} catch {
|
|
18039
|
+
}
|
|
18040
|
+
}
|
|
18041
|
+
return yamlValues;
|
|
18042
|
+
}
|
|
18043
|
+
function isNoop(candidate2, current) {
|
|
18044
|
+
const a = candidate2.definition.instructions ?? "";
|
|
18045
|
+
const b = current.definition.instructions ?? "";
|
|
18046
|
+
if (a !== b) return false;
|
|
18047
|
+
if (JSON.stringify(candidate2.definition.tools ?? []) !== JSON.stringify(current.definition.tools ?? [])) return false;
|
|
18048
|
+
return JSON.stringify(sortObj(candidate2.metadata)) === JSON.stringify(sortObj(current.metadata ?? {}));
|
|
18049
|
+
}
|
|
18050
|
+
function sortObj(o) {
|
|
18051
|
+
return Object.fromEntries(Object.entries(o).sort(([x], [y]) => x.localeCompare(y)));
|
|
18052
|
+
}
|
|
18053
|
+
|
|
18054
|
+
// src/lib/platform-converge.ts
|
|
18055
|
+
var ORDER = ["infra", "gateway", "codingAgent", "azureExecutor", "personas"];
|
|
18056
|
+
function infraTargetSha(manifest, tree) {
|
|
18057
|
+
return manifest.components.infra?.treeSha ?? tree.treeShaOf("deploy") ?? "";
|
|
18058
|
+
}
|
|
18059
|
+
function diffPlan(manifest, stamp, tree, opts = {}) {
|
|
18060
|
+
const actions = [];
|
|
18061
|
+
const skipped = [];
|
|
18062
|
+
const adopt = stamp === null;
|
|
18063
|
+
const want = (k) => opts.only === void 0 || opts.only === k;
|
|
18064
|
+
for (const component of ORDER) {
|
|
18065
|
+
if (!want(component)) {
|
|
18066
|
+
skipped.push({ component, reason: "only-filter" });
|
|
18067
|
+
continue;
|
|
18068
|
+
}
|
|
18069
|
+
if (component === "infra") {
|
|
18070
|
+
if (opts.skipInfra) {
|
|
18071
|
+
skipped.push({ component, reason: "up-to-date" });
|
|
18072
|
+
continue;
|
|
18073
|
+
}
|
|
18074
|
+
const to = infraTargetSha(manifest, tree);
|
|
18075
|
+
const from2 = stamp?.components.infra.treeSha ?? null;
|
|
18076
|
+
if (opts.forceInfra) actions.push({ component, reason: "forced", from: from2, to });
|
|
18077
|
+
else if (adopt) actions.push({ component, reason: "adopt", from: from2, to });
|
|
18078
|
+
else if (from2 !== to) skipped.push({ component, reason: "deferred" });
|
|
18079
|
+
else skipped.push({ component, reason: "up-to-date" });
|
|
18080
|
+
continue;
|
|
18081
|
+
}
|
|
18082
|
+
if (component === "personas") {
|
|
18083
|
+
for (const [name, item] of Object.entries(manifest.components.personas.items)) {
|
|
18084
|
+
const from2 = stamp?.components.personas[name]?.treeSha ?? null;
|
|
18085
|
+
if (adopt) actions.push({ component, personaName: name, reason: "adopt", from: from2, to: item.treeSha });
|
|
18086
|
+
else if (from2 !== item.treeSha) actions.push({ component, personaName: name, reason: "changed", from: from2, to: item.treeSha });
|
|
18087
|
+
else skipped.push({ component, personaName: name, reason: "up-to-date" });
|
|
18088
|
+
}
|
|
18089
|
+
continue;
|
|
18090
|
+
}
|
|
18091
|
+
const img = manifest.components[component];
|
|
18092
|
+
const cur = stamp?.components[component];
|
|
18093
|
+
if (cur?.state === "external") {
|
|
18094
|
+
skipped.push({ component, reason: "external" });
|
|
18095
|
+
continue;
|
|
18096
|
+
}
|
|
18097
|
+
const from = cur?.tag ?? null;
|
|
18098
|
+
if (adopt) actions.push({ component, reason: "adopt", from, to: img.tag });
|
|
18099
|
+
else if (from !== img.tag) actions.push({ component, reason: "changed", from, to: img.tag });
|
|
18100
|
+
else skipped.push({ component, reason: "up-to-date" });
|
|
18101
|
+
}
|
|
18102
|
+
return { targetVersion: manifest.platform.tag, previousVersion: manifest.platform.previousVersion, actions, skipped };
|
|
18103
|
+
}
|
|
18104
|
+
function statusRows(manifest, stamp, tree, opts = {}) {
|
|
18105
|
+
const plan = diffPlan(manifest, stamp, tree, opts);
|
|
18106
|
+
const rows = [];
|
|
18107
|
+
for (const a of plan.actions) {
|
|
18108
|
+
rows.push({
|
|
18109
|
+
component: a.component,
|
|
18110
|
+
...a.personaName ? { personaName: a.personaName } : {},
|
|
18111
|
+
installed: a.from ?? "(none)",
|
|
18112
|
+
target: a.to,
|
|
18113
|
+
status: a.reason === "adopt" ? "needs-adopt" : "drift"
|
|
18114
|
+
});
|
|
18115
|
+
}
|
|
18116
|
+
for (const s of plan.skipped) {
|
|
18117
|
+
rows.push({
|
|
18118
|
+
component: s.component,
|
|
18119
|
+
...s.personaName ? { personaName: s.personaName } : {},
|
|
18120
|
+
installed: installedValueOf(stamp, s.component, s.personaName),
|
|
18121
|
+
target: targetValueOf(manifest, tree, s.component, s.personaName),
|
|
18122
|
+
status: s.reason
|
|
18123
|
+
});
|
|
18124
|
+
}
|
|
18125
|
+
return rows;
|
|
18126
|
+
}
|
|
18127
|
+
function installedValueOf(stamp, component, personaName) {
|
|
18128
|
+
if (!stamp) return "-";
|
|
18129
|
+
if (component === "infra") return stamp.components.infra.treeSha || "-";
|
|
18130
|
+
if (component === "personas") {
|
|
18131
|
+
if (!personaName || !Object.hasOwn(stamp.components.personas, personaName)) return "-";
|
|
18132
|
+
return stamp.components.personas[personaName].treeSha;
|
|
18133
|
+
}
|
|
18134
|
+
return stamp.components[component].tag;
|
|
18135
|
+
}
|
|
18136
|
+
function targetValueOf(manifest, tree, component, personaName) {
|
|
18137
|
+
if (component === "infra") return infraTargetSha(manifest, tree) || "-";
|
|
18138
|
+
if (component === "personas") {
|
|
18139
|
+
if (!personaName || !Object.hasOwn(manifest.components.personas.items, personaName)) return "-";
|
|
18140
|
+
return manifest.components.personas.items[personaName].treeSha;
|
|
18141
|
+
}
|
|
18142
|
+
return manifest.components[component].tag;
|
|
18143
|
+
}
|
|
18144
|
+
async function applyPlan(plan, deps, ctx) {
|
|
18145
|
+
const prior = await deps.readStamp();
|
|
18146
|
+
const stamp = seedStamp(prior, ctx.manifest, plan);
|
|
18147
|
+
delete stamp.lastError;
|
|
18148
|
+
const applied = [];
|
|
18149
|
+
try {
|
|
18150
|
+
for (const a of plan.actions) {
|
|
18151
|
+
if (a.component === "infra") {
|
|
18152
|
+
stamp.components.infra = await deps.applyInfra(a, ctx);
|
|
18153
|
+
} else if (a.component === "gateway") {
|
|
18154
|
+
stamp.components.gateway = await deps.applyGateway(a, ctx);
|
|
18155
|
+
} else if (a.component === "codingAgent" || a.component === "azureExecutor") {
|
|
18156
|
+
const r = await deps.applyAgent(a, ctx);
|
|
18157
|
+
if (r !== "absent") {
|
|
18158
|
+
stamp.components[a.component] = { ...r, state: "managed" };
|
|
18159
|
+
} else {
|
|
18160
|
+
ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 stamp left unchanged.`);
|
|
18161
|
+
}
|
|
18162
|
+
} else {
|
|
18163
|
+
if (!a.personaName) {
|
|
18164
|
+
throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
|
|
18165
|
+
}
|
|
18166
|
+
const r = await deps.applyPersona(a, ctx);
|
|
18167
|
+
if (r === "absent") {
|
|
18168
|
+
ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 stamp left unchanged.`);
|
|
18169
|
+
} else {
|
|
18170
|
+
const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
|
|
18171
|
+
stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
|
|
18172
|
+
}
|
|
18173
|
+
}
|
|
18174
|
+
applied.push(a);
|
|
18175
|
+
stamp.lastResult = "partial";
|
|
18176
|
+
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18177
|
+
await deps.writeStamp(stamp);
|
|
18178
|
+
}
|
|
18179
|
+
await deps.healthGate(ctx, applied);
|
|
18180
|
+
stamp.lastResult = "success";
|
|
18181
|
+
delete stamp.lastError;
|
|
18182
|
+
const isTargeted = plan.skipped.some((s) => s.reason === "only-filter");
|
|
18183
|
+
if (!isTargeted && plan.targetVersion !== stamp.platformVersion) {
|
|
18184
|
+
stamp.previousPlatformVersion = stamp.platformVersion;
|
|
18185
|
+
stamp.platformVersion = plan.targetVersion;
|
|
18186
|
+
}
|
|
18187
|
+
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18188
|
+
await deps.writeStamp(stamp);
|
|
18189
|
+
return { stamp, applied };
|
|
18190
|
+
} catch (e) {
|
|
18191
|
+
stamp.lastResult = "failed";
|
|
18192
|
+
stamp.lastError = {
|
|
18193
|
+
component: applied.length < plan.actions.length ? plan.actions[applied.length].component : "healthGate",
|
|
18194
|
+
message: e.message
|
|
18195
|
+
};
|
|
18196
|
+
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18197
|
+
await deps.writeStamp(stamp).catch(() => {
|
|
18198
|
+
});
|
|
18199
|
+
throw e;
|
|
18200
|
+
}
|
|
18201
|
+
}
|
|
18202
|
+
function seedStamp(prior, manifest, plan) {
|
|
18203
|
+
const blankImg = () => ({ tag: "", digest: "", state: "managed" });
|
|
18204
|
+
return prior ? { ...prior } : {
|
|
18205
|
+
schemaVersion: 1,
|
|
18206
|
+
platformVersion: plan.targetVersion,
|
|
18207
|
+
previousPlatformVersion: plan.previousVersion,
|
|
18208
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18209
|
+
lastResult: "partial",
|
|
18210
|
+
cli: "",
|
|
18211
|
+
components: { gateway: blankImg(), codingAgent: blankImg(), azureExecutor: blankImg(), personas: {}, infra: { treeSha: "" } }
|
|
18212
|
+
};
|
|
18213
|
+
}
|
|
18214
|
+
async function applyGatewayImage(a, ctx, gatewayResourceId) {
|
|
18215
|
+
const { resourceGroup, name } = parseContainerAppResourceId(gatewayResourceId);
|
|
18216
|
+
const current = (await runAz(["containerapp", "show", "-g", resourceGroup, "-n", name, "--query", "properties.template.containers[0].image", "-o", "tsv"])).trim();
|
|
18217
|
+
const tags = await fetchPublicTags(DEFAULT_IMAGE_REPO);
|
|
18218
|
+
const plan = planUpdate({ currentImage: current, availableTags: tags, imageRepo: DEFAULT_IMAGE_REPO, to: a.to });
|
|
18219
|
+
const digest = ctx.manifest.components.gateway.digest;
|
|
18220
|
+
switch (plan.kind) {
|
|
18221
|
+
case "refuse-byoc":
|
|
18222
|
+
ctx.onProgress?.(`gateway is BYOC (${plan.currentRepo}) \u2014 marking externally managed.`);
|
|
18223
|
+
return { tag: a.to, digest, state: "external" };
|
|
18224
|
+
case "roll":
|
|
18225
|
+
await runAz(["containerapp", "update", "-n", name, "-g", resourceGroup, "--image", `${plan.repo}:${plan.target}`]);
|
|
18226
|
+
return { tag: a.to, digest, state: "managed" };
|
|
18227
|
+
case "noop":
|
|
18228
|
+
return { tag: a.to, digest, state: "managed" };
|
|
18229
|
+
case "tag-not-found":
|
|
18230
|
+
throw new LocalCliError({
|
|
18231
|
+
code: "PLATFORM_GATEWAY_TAG_UNAVAILABLE",
|
|
18232
|
+
message: `Gateway image tag '${a.to}' is not published on the tracked repo \u2014 cannot converge the gateway.`,
|
|
18233
|
+
hint: "The manifest may be ahead of the registry (propagation lag), or the repo is wrong. Retry shortly or check --image-repo."
|
|
18234
|
+
});
|
|
18235
|
+
case "no-versions":
|
|
18236
|
+
throw new LocalCliError({
|
|
18237
|
+
code: "PLATFORM_GATEWAY_NO_VERSIONS",
|
|
18238
|
+
message: "No published gateway image versions found on the tracked repo."
|
|
18239
|
+
});
|
|
18240
|
+
}
|
|
18241
|
+
}
|
|
18242
|
+
async function applyInfraDeploy(a, ctx, target, opts = {}) {
|
|
18243
|
+
if (a.reason !== "forced") {
|
|
18244
|
+
ctx.onProgress?.(`infra baseline recorded at ${target} (not re-provisioned; apply with --force-infra or the update job).`);
|
|
18245
|
+
return { treeSha: target };
|
|
18246
|
+
}
|
|
18247
|
+
ctx.onProgress?.("converging infra (idempotent bicep deploy)\u2026");
|
|
18248
|
+
const params = await resolveBicepParamsForConverge(ctx, opts);
|
|
18249
|
+
await runBicepDeployment({
|
|
18250
|
+
resourceGroup: ctx.resourceGroup,
|
|
18251
|
+
repoRoot: ctx.contentDir,
|
|
18252
|
+
params: buildBicepParams(params),
|
|
18253
|
+
deploymentName: `m8t-converge-${target.slice(0, 12)}`
|
|
18254
|
+
});
|
|
18255
|
+
return { treeSha: target };
|
|
18256
|
+
}
|
|
18257
|
+
async function resolveBicepParamsForConverge(ctx, opts = {}) {
|
|
18258
|
+
if (!opts.suffix) {
|
|
18259
|
+
throw new LocalCliError({
|
|
18260
|
+
code: "PLATFORM_INFRA_SUFFIX_REQUIRED",
|
|
18261
|
+
message: "A --force-infra converge deploy requires an explicit --suffix.",
|
|
18262
|
+
hint: "Pass --suffix <existing-suffix> (from the deployed resource names) to avoid provisioning duplicate resources."
|
|
18263
|
+
});
|
|
18264
|
+
}
|
|
18265
|
+
const location = (await runAz(["resource", "list", "-g", ctx.resourceGroup, "--query", "[0].location", "-o", "tsv"])).trim();
|
|
18266
|
+
const foundryResourceId = await resolveFoundryResourceId(opts.foundryEndpoint ?? "", opts.foundryResourceId);
|
|
18267
|
+
const acrPullIdentityResourceId = resolveAcrPullIdentity({ explicit: opts.acrPullIdentity });
|
|
18268
|
+
const imageRef = `${ctx.manifest.components.gateway.ref}:${ctx.manifest.components.gateway.tag}`;
|
|
18269
|
+
const acrResourceId = await resolveAcrResourceId({ imageRef, explicit: opts.acrResourceId });
|
|
18270
|
+
return {
|
|
18271
|
+
location,
|
|
18272
|
+
suffix: opts.suffix,
|
|
18273
|
+
imageRef,
|
|
18274
|
+
tenantId: opts.tenantId ?? "",
|
|
18275
|
+
clientId: opts.clientId ?? "",
|
|
18276
|
+
foundryResourceId,
|
|
18277
|
+
foundryProjectEndpoint: opts.foundryEndpoint ?? "",
|
|
18278
|
+
acrPullIdentityResourceId,
|
|
18279
|
+
acrResourceId,
|
|
18280
|
+
foundryTracingMode: opts.foundryTracingMode
|
|
18281
|
+
};
|
|
18282
|
+
}
|
|
18283
|
+
function swapHostedImage(def, newImage) {
|
|
18284
|
+
return { ...def, image: newImage };
|
|
18285
|
+
}
|
|
18286
|
+
async function applyAgentImage(a, ctx, opts) {
|
|
18287
|
+
const current = await getAgentVersion({ credential: ctx.credential, projectEndpoint: opts.project.endpoint, agentName: opts.agentName });
|
|
18288
|
+
const staged = await stagePublicImageIfNeeded({ image: opts.ghcrImage, project: opts.project, onProgress: ctx.onProgress });
|
|
18289
|
+
const definition = swapHostedImage(current.definition, staged.image);
|
|
18290
|
+
await createAgentVersion({
|
|
18291
|
+
credential: ctx.credential,
|
|
18292
|
+
projectEndpoint: opts.project.endpoint,
|
|
18293
|
+
agentName: opts.agentName,
|
|
18294
|
+
definition,
|
|
18295
|
+
metadata: current.metadata ?? {},
|
|
18296
|
+
extraHeaders: { "Foundry-Features": "HostedAgents=V1Preview" }
|
|
18297
|
+
});
|
|
18298
|
+
const digest = a.component === "codingAgent" ? ctx.manifest.components.codingAgent.digest : ctx.manifest.components.azureExecutor.digest;
|
|
18299
|
+
return { tag: a.to, digest };
|
|
18300
|
+
}
|
|
18301
|
+
function declaredFillableFields(personaPath) {
|
|
18302
|
+
let raw;
|
|
18303
|
+
try {
|
|
18304
|
+
raw = fs21.readFileSync(personaPath, "utf8");
|
|
18305
|
+
} catch {
|
|
18306
|
+
return [];
|
|
18307
|
+
}
|
|
18308
|
+
const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
18309
|
+
if (!fmMatch) return [];
|
|
18310
|
+
try {
|
|
18311
|
+
const fm = parseYaml10(fmMatch[1]);
|
|
18312
|
+
return fm["fillable-fields"] ?? [];
|
|
18313
|
+
} catch {
|
|
18314
|
+
return [];
|
|
18315
|
+
}
|
|
18316
|
+
}
|
|
18317
|
+
function defaultsOf(declared) {
|
|
18318
|
+
const out = {};
|
|
18319
|
+
for (const f of declared) {
|
|
18320
|
+
if (f.default !== void 0) out[f.name] = f.default;
|
|
18321
|
+
}
|
|
18322
|
+
return out;
|
|
18323
|
+
}
|
|
18324
|
+
function injectAndRender(personaPath, values, agentName) {
|
|
18325
|
+
return renderPersonaBody(personaPath, values, agentName);
|
|
18326
|
+
}
|
|
18327
|
+
function loaderText(loaderPath) {
|
|
18328
|
+
if (!loaderPath) return null;
|
|
18329
|
+
try {
|
|
18330
|
+
return fs21.readFileSync(loaderPath, "utf8");
|
|
18331
|
+
} catch {
|
|
18332
|
+
return null;
|
|
18333
|
+
}
|
|
18334
|
+
}
|
|
18335
|
+
function brainRepoOf(current) {
|
|
18336
|
+
const raw = current.metadata?.brain;
|
|
18337
|
+
if (typeof raw !== "string") return null;
|
|
18338
|
+
try {
|
|
18339
|
+
const parsed = JSON.parse(raw);
|
|
18340
|
+
return parsed.repo || null;
|
|
18341
|
+
} catch {
|
|
18342
|
+
return null;
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
async function applyPersona(a, ctx, opts) {
|
|
18346
|
+
const current = await getAgentVersion({ credential: ctx.credential, projectEndpoint: opts.project.endpoint, agentName: opts.agentName });
|
|
18347
|
+
const personaPath = path20.join(opts.personaDir, "persona.md");
|
|
18348
|
+
const yamlValues = readAgentYaml(opts.agentName)?.fillableFieldValues ?? null;
|
|
18349
|
+
const values = resolveFillableValues(current, yamlValues);
|
|
18350
|
+
const declared = declaredFillableFields(personaPath);
|
|
18351
|
+
if (declared.length > 0 && values === null) {
|
|
18352
|
+
const defaultBody = injectAndRender(personaPath, defaultsOf(declared), opts.agentName);
|
|
18353
|
+
const candidateInstructions = composeInstructions({
|
|
18354
|
+
baseBody: defaultBody,
|
|
18355
|
+
current,
|
|
18356
|
+
loaderText: loaderText(opts.loaderPath),
|
|
18357
|
+
brainRepo: brainRepoOf(current)
|
|
18358
|
+
});
|
|
18359
|
+
if (candidateInstructions !== (current.definition.instructions ?? "")) {
|
|
18360
|
+
throw new LocalCliError({
|
|
18361
|
+
code: "PLATFORM_PERSONA_UNRECOVERABLE",
|
|
18362
|
+
message: `Cannot re-version '${opts.agentName}' \u2014 its instance field values are not recoverable (would revert to defaults). Re-deploy the advisor, or pass --content-dir with the correct values.`
|
|
18363
|
+
});
|
|
18364
|
+
}
|
|
18365
|
+
}
|
|
18366
|
+
const missing = declared.filter((f) => !values?.[f.name] && f.default === void 0);
|
|
18367
|
+
if (missing.length > 0) {
|
|
18368
|
+
throw new LocalCliError({
|
|
18369
|
+
code: "PLATFORM_PERSONA_FIELD_MISSING",
|
|
18370
|
+
message: `Persona '${a.personaName ?? opts.agentName}' needs values for: ${missing.map((f) => f.name).join(", ")}.`
|
|
18371
|
+
});
|
|
18372
|
+
}
|
|
18373
|
+
const baseBody = injectAndRender(personaPath, values ?? defaultsOf(declared), opts.agentName);
|
|
18374
|
+
const instructions = composeInstructions({
|
|
18375
|
+
baseBody,
|
|
18376
|
+
current,
|
|
18377
|
+
loaderText: loaderText(opts.loaderPath),
|
|
18378
|
+
brainRepo: brainRepoOf(current)
|
|
18379
|
+
});
|
|
18380
|
+
const metadata = {
|
|
18381
|
+
...current.metadata ?? {},
|
|
18382
|
+
persona: a.personaName ?? opts.agentName,
|
|
18383
|
+
...values ? { fillableFieldValues: JSON.stringify(values) } : {}
|
|
18384
|
+
};
|
|
18385
|
+
const candidate2 = { definition: { ...current.definition, instructions }, metadata };
|
|
18386
|
+
if (isNoop(candidate2, current)) return "noop";
|
|
18387
|
+
const foundryVersion = await createAgentVersion({
|
|
18388
|
+
credential: ctx.credential,
|
|
18389
|
+
projectEndpoint: opts.project.endpoint,
|
|
18390
|
+
agentName: opts.agentName,
|
|
18391
|
+
definition: candidate2.definition,
|
|
18392
|
+
metadata
|
|
18393
|
+
});
|
|
18394
|
+
return { treeSha: a.to, foundryVersion };
|
|
18395
|
+
}
|
|
18396
|
+
|
|
18397
|
+
// src/commands/platform/status.ts
|
|
18398
|
+
var PlatformStatusCommand = class extends M8tCommand {
|
|
18399
|
+
static paths = [["platform", "status"]];
|
|
18400
|
+
static usage = Command31.Usage({
|
|
18401
|
+
description: "Show per-component platform drift against a release manifest.",
|
|
18402
|
+
details: "Fetches a release manifest (the current channel by default), reads the installed-state stamp, and reports each component's installed vs target version and drift status. Read-only \u2014 never writes the stamp, never converges anything (use 'm8t platform update' for that). --verify additionally probes the live gateway (image tag + /api/version) best-effort."
|
|
18403
|
+
});
|
|
18404
|
+
subscription = Option29.String("--subscription");
|
|
18405
|
+
resourceGroup = Option29.String("--resource-group", {
|
|
18406
|
+
description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
18407
|
+
});
|
|
18408
|
+
to = Option29.String("--to", {
|
|
18409
|
+
description: "Check status against a pinned platform version (defaults to the current release channel)."
|
|
18410
|
+
});
|
|
18411
|
+
contentDir = Option29.String("--content-dir", {
|
|
18412
|
+
description: "Path to a local m8t-stack repo checkout (skips the tarball tree-SHA fetch)."
|
|
18413
|
+
});
|
|
18414
|
+
manifestUrl = Option29.String("--manifest-url", {
|
|
18415
|
+
description: "Fetch the release manifest from this URL instead of the release channel."
|
|
18416
|
+
});
|
|
18417
|
+
manifestFile = Option29.String("--manifest-file", {
|
|
18418
|
+
description: "Read the release manifest from this local file instead of the release channel."
|
|
18419
|
+
});
|
|
18420
|
+
verify = Option29.Boolean("--verify", false, {
|
|
18421
|
+
description: "Additionally probe the live gateway (image tag + /api/version) and report stamp-vs-live."
|
|
18422
|
+
});
|
|
18423
|
+
output = Option29.String("--output");
|
|
18424
|
+
async executeCommand() {
|
|
18425
|
+
const mode = resolveOutputMode(
|
|
18426
|
+
this.output,
|
|
18427
|
+
this.context.stdout
|
|
18428
|
+
);
|
|
18429
|
+
const log = (msg) => {
|
|
18430
|
+
if (mode !== "json") this.context.stdout.write(msg + "\n");
|
|
18431
|
+
};
|
|
18432
|
+
const to = typeof this.to === "string" ? this.to : void 0;
|
|
18433
|
+
const contentDirFlag = typeof this.contentDir === "string" ? this.contentDir : void 0;
|
|
18434
|
+
const manifestUrl = typeof this.manifestUrl === "string" ? this.manifestUrl : void 0;
|
|
18435
|
+
const manifestFile = typeof this.manifestFile === "string" ? this.manifestFile : void 0;
|
|
18436
|
+
const gw = await resolveGatewayContext({
|
|
18437
|
+
interactive: mode !== "json",
|
|
18438
|
+
subscriptionId: this.subscription,
|
|
18439
|
+
resourceGroup: this.resourceGroup
|
|
18440
|
+
});
|
|
18441
|
+
const { resourceGroup } = parseContainerAppResourceId(gw.containerAppResourceId);
|
|
18442
|
+
const credential2 = new DefaultAzureCredential16();
|
|
18443
|
+
const account = await getAzAccount();
|
|
18444
|
+
const subscriptionId = this.subscription ?? account.subscriptionId;
|
|
18445
|
+
const source = manifestFile ? { file: manifestFile } : manifestUrl ? { url: manifestUrl } : { channel: true, version: to };
|
|
18446
|
+
const manifest = await fetchManifest(source);
|
|
18447
|
+
const stamp = await readStamp({ credential: credential2, subscriptionId, resourceGroup });
|
|
18448
|
+
const tree = contentDirFlag ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
|
|
18449
|
+
const rows = statusRows(manifest, stamp, tree);
|
|
18450
|
+
let verify;
|
|
18451
|
+
if (this.verify) {
|
|
18452
|
+
verify = await this.probeLive(gw.containerAppResourceId, gw.gatewayUrl, stamp?.components.gateway.tag);
|
|
18453
|
+
}
|
|
18454
|
+
if (mode === "json") {
|
|
18455
|
+
this.context.stdout.write(
|
|
18456
|
+
renderJson({
|
|
18457
|
+
kind: "platform-status",
|
|
18458
|
+
gateway: resourceGroup,
|
|
18459
|
+
targetVersion: manifest.platform.tag,
|
|
18460
|
+
managed: stamp !== null,
|
|
18461
|
+
rows,
|
|
18462
|
+
...verify ? { verify } : {}
|
|
18463
|
+
}) + "\n"
|
|
18464
|
+
);
|
|
18465
|
+
return 0;
|
|
18466
|
+
}
|
|
18467
|
+
log(colors.field(`target: ${manifest.platform.tag}${stamp === null ? " (unmanaged \u2014 no stamp found)" : ""}`));
|
|
18468
|
+
this.context.stdout.write(this.renderRows(rows) + "\n");
|
|
18469
|
+
if (verify) {
|
|
18470
|
+
log("");
|
|
18471
|
+
log(colors.field("live verify:"));
|
|
18472
|
+
log(` stamp gateway tag: ${verify.stampGatewayTag}`);
|
|
18473
|
+
log(` live gateway tag: ${verify.liveGatewayTag}`);
|
|
18474
|
+
log(
|
|
18475
|
+
` match: ${verify.gatewayTagMatch === "unknown" ? colors.warn("unknown") : verify.gatewayTagMatch ? colors.success("yes") : colors.warn("no")}`
|
|
18476
|
+
);
|
|
18477
|
+
log(` /api/version: ${verify.apiVersion}`);
|
|
18478
|
+
}
|
|
18479
|
+
return 0;
|
|
18480
|
+
}
|
|
18481
|
+
renderRows(rows) {
|
|
18482
|
+
const tableRows = rows.map((r) => ({
|
|
18483
|
+
component: r.personaName ? `personas:${r.personaName}` : r.component,
|
|
18484
|
+
installed: r.installed,
|
|
18485
|
+
target: r.target,
|
|
18486
|
+
status: r.status
|
|
18487
|
+
}));
|
|
18488
|
+
return renderTable(tableRows, [
|
|
18489
|
+
{ key: "component", label: "COMPONENT" },
|
|
18490
|
+
{ key: "installed", label: "INSTALLED" },
|
|
18491
|
+
{ key: "target", label: "TARGET" },
|
|
18492
|
+
{ key: "status", label: "STATUS" }
|
|
18493
|
+
]);
|
|
18494
|
+
}
|
|
18495
|
+
/**
|
|
18496
|
+
* Best-effort live probe for --verify: the live gateway image tag (az) and
|
|
18497
|
+
* GET /api/version. Any failure (auth-gated endpoint, transient network
|
|
18498
|
+
* error, etc.) degrades the affected field to "unknown" rather than
|
|
18499
|
+
* throwing — a verify probe must never crash the read-only status command.
|
|
18500
|
+
*/
|
|
18501
|
+
async probeLive(gatewayResourceId, gatewayUrl, stampGatewayTag) {
|
|
18502
|
+
let liveGatewayTag = "unknown";
|
|
18503
|
+
try {
|
|
18504
|
+
const { resourceGroup, name } = parseContainerAppResourceId(gatewayResourceId);
|
|
18505
|
+
const image = (await runAz([
|
|
18506
|
+
"containerapp",
|
|
18507
|
+
"show",
|
|
18508
|
+
"-g",
|
|
18509
|
+
resourceGroup,
|
|
18510
|
+
"-n",
|
|
18511
|
+
name,
|
|
18512
|
+
"--query",
|
|
18513
|
+
"properties.template.containers[0].image",
|
|
18514
|
+
"-o",
|
|
18515
|
+
"tsv"
|
|
18516
|
+
])).trim();
|
|
18517
|
+
const tag = image.split(":").pop();
|
|
18518
|
+
if (tag) liveGatewayTag = tag;
|
|
18519
|
+
} catch {
|
|
18520
|
+
liveGatewayTag = "unknown";
|
|
18521
|
+
}
|
|
18522
|
+
let apiVersion = "unknown";
|
|
18523
|
+
try {
|
|
18524
|
+
const res = await fetch(`${gatewayUrl}/api/version`, { method: "GET" });
|
|
18525
|
+
if (res.ok) {
|
|
18526
|
+
const body = await res.json().catch(() => ({}));
|
|
18527
|
+
apiVersion = body.version ?? "unknown";
|
|
18528
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
18529
|
+
apiVersion = "unknown (auth-gated)";
|
|
18530
|
+
}
|
|
18531
|
+
} catch {
|
|
18532
|
+
apiVersion = "unknown";
|
|
18533
|
+
}
|
|
18534
|
+
const stampTag = stampGatewayTag ?? "unknown";
|
|
18535
|
+
const gatewayTagMatch = liveGatewayTag === "unknown" || stampTag === "unknown" ? "unknown" : liveGatewayTag === stampTag;
|
|
18536
|
+
return { liveGatewayTag, stampGatewayTag: stampTag, gatewayTagMatch, apiVersion };
|
|
18537
|
+
}
|
|
18538
|
+
};
|
|
18539
|
+
|
|
18540
|
+
// src/commands/platform/update.ts
|
|
18541
|
+
import { Command as Command32, Option as Option30 } from "clipanion";
|
|
18542
|
+
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
18543
|
+
import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/identity";
|
|
18544
|
+
|
|
18545
|
+
// src/lib/platform-converge-cli.ts
|
|
18546
|
+
import * as path21 from "path";
|
|
18547
|
+
init_errors();
|
|
18548
|
+
var HOSTED_AGENT_PERSONA_DIR = {
|
|
18549
|
+
codingAgent: "coding-agent",
|
|
18550
|
+
azureExecutor: "azure-executor"
|
|
18551
|
+
};
|
|
18552
|
+
async function buildConvergeDeps(args) {
|
|
18553
|
+
const warn = args.onWarn ?? (() => {
|
|
18554
|
+
});
|
|
18555
|
+
const discoveredAgents = await listM8tAgents({ credential: args.credential, projectEndpoint: args.project.endpoint });
|
|
18556
|
+
const discovered = {};
|
|
18557
|
+
for (const a of discoveredAgents) {
|
|
18558
|
+
const persona = a.metadata.persona;
|
|
18559
|
+
if (persona) discovered[persona] = a.name;
|
|
18560
|
+
}
|
|
18561
|
+
const loaderPath = path21.join(args.contentDir, "targets/foundry/brain-loader.md");
|
|
18562
|
+
return {
|
|
18563
|
+
async applyPersona(a, ctx) {
|
|
18564
|
+
const personaName = a.personaName;
|
|
18565
|
+
if (!personaName) {
|
|
18566
|
+
throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
|
|
18567
|
+
}
|
|
18568
|
+
const agentName = discovered[personaName];
|
|
18569
|
+
if (!agentName) {
|
|
18570
|
+
warn(`persona '${personaName}' is not deployed on this install \u2014 skipping.`);
|
|
18571
|
+
return "absent";
|
|
18572
|
+
}
|
|
18573
|
+
const personaItem = ctx.manifest.components.personas.items[personaName];
|
|
18574
|
+
return applyPersona(a, ctx, {
|
|
18575
|
+
agentName,
|
|
18576
|
+
project: args.project,
|
|
18577
|
+
personaDir: path21.join(ctx.contentDir, personaItem.path),
|
|
18578
|
+
loaderPath
|
|
18579
|
+
});
|
|
18580
|
+
},
|
|
18581
|
+
async applyAgent(a, ctx) {
|
|
18582
|
+
if (a.component !== "codingAgent" && a.component !== "azureExecutor") {
|
|
18583
|
+
throw new LocalCliError({
|
|
18584
|
+
code: "PLATFORM_AGENT_BAD_COMPONENT",
|
|
18585
|
+
message: `applyAgent called with non-hosted component '${a.component}'`
|
|
18586
|
+
});
|
|
18587
|
+
}
|
|
18588
|
+
const dir = HOSTED_AGENT_PERSONA_DIR[a.component];
|
|
18589
|
+
const agentName = dir ? discovered[dir] : void 0;
|
|
18590
|
+
if (!agentName) {
|
|
18591
|
+
warn(`hosted agent for component '${a.component}' is not deployed on this install \u2014 skipping.`);
|
|
18592
|
+
return "absent";
|
|
18593
|
+
}
|
|
18594
|
+
const componentRef = ctx.manifest.components[a.component];
|
|
18595
|
+
return applyAgentImage(a, ctx, {
|
|
18596
|
+
agentName,
|
|
18597
|
+
project: args.project,
|
|
18598
|
+
ghcrImage: `${componentRef.ref}:${a.to}`
|
|
18599
|
+
});
|
|
18600
|
+
},
|
|
18601
|
+
async applyGateway(a, ctx) {
|
|
18602
|
+
return applyGatewayImage(a, ctx, args.gatewayResourceId);
|
|
18603
|
+
},
|
|
18604
|
+
async applyInfra(a, ctx) {
|
|
18605
|
+
const target = infraTargetSha(ctx.manifest, args.tree);
|
|
18606
|
+
return applyInfraDeploy(a, ctx, target, { suffix: args.suffix });
|
|
18607
|
+
},
|
|
18608
|
+
async healthGate(_ctx, applied) {
|
|
18609
|
+
for (const a of applied) {
|
|
18610
|
+
let agentName;
|
|
18611
|
+
if (a.component === "personas" && a.personaName) {
|
|
18612
|
+
agentName = discovered[a.personaName];
|
|
18613
|
+
} else if (a.component === "codingAgent" || a.component === "azureExecutor") {
|
|
18614
|
+
const dir = HOSTED_AGENT_PERSONA_DIR[a.component];
|
|
18615
|
+
agentName = dir ? discovered[dir] : void 0;
|
|
18616
|
+
}
|
|
18617
|
+
if (!agentName) continue;
|
|
18618
|
+
await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.project.endpoint, agentName, onProgress: args.onProgress });
|
|
18619
|
+
}
|
|
18620
|
+
if (args.gatewayUrl) {
|
|
18621
|
+
try {
|
|
18622
|
+
const res = await fetch(`${args.gatewayUrl}/api/version`, { method: "GET" });
|
|
18623
|
+
if (res.ok) {
|
|
18624
|
+
const body = await res.json().catch(() => ({}));
|
|
18625
|
+
args.onProgress?.(`gateway reachable (version: ${body.version ?? "unknown"}).`);
|
|
18626
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
18627
|
+
args.onProgress?.(`gateway reachable (auth-gated, HTTP ${res.status.toString()}).`);
|
|
18628
|
+
} else {
|
|
18629
|
+
warn(`gateway health probe returned HTTP ${res.status.toString()} \u2014 not treated as fatal.`);
|
|
18630
|
+
}
|
|
18631
|
+
} catch (e) {
|
|
18632
|
+
warn(`gateway health probe failed: ${e.message} \u2014 not treated as fatal.`);
|
|
18633
|
+
}
|
|
18634
|
+
}
|
|
18635
|
+
},
|
|
18636
|
+
async readStamp() {
|
|
18637
|
+
return readStamp({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
|
|
18638
|
+
},
|
|
18639
|
+
async writeStamp(s) {
|
|
18640
|
+
await writeStamp({
|
|
18641
|
+
credential: args.credential,
|
|
18642
|
+
subscriptionId: args.subscriptionId,
|
|
18643
|
+
resourceGroup: args.resourceGroup,
|
|
18644
|
+
stamp: { ...s, cli: CLI_VERSION },
|
|
18645
|
+
onProgress: args.onProgress
|
|
18646
|
+
});
|
|
18647
|
+
}
|
|
18648
|
+
};
|
|
18649
|
+
}
|
|
18650
|
+
function toVTag(v) {
|
|
18651
|
+
return v.startsWith("v") ? v : `v${v}`;
|
|
18652
|
+
}
|
|
18653
|
+
function assertCliVersionOk(manifest, runningVersion, onWarn = () => {
|
|
18654
|
+
}) {
|
|
18655
|
+
const cli2 = manifest.components.cli;
|
|
18656
|
+
const running = toVTag(runningVersion);
|
|
18657
|
+
const min = toVTag(cli2.min);
|
|
18658
|
+
const recommended = toVTag(cli2.recommended);
|
|
18659
|
+
if (compareSemver(running, min) < 0) {
|
|
18660
|
+
throw new LocalCliError({
|
|
18661
|
+
code: "PLATFORM_CLI_TOO_OLD",
|
|
18662
|
+
message: `This CLI (${runningVersion}) is older than the minimum supported version (${cli2.min}) for platform ${manifest.platform.tag}.`,
|
|
18663
|
+
hint: "Upgrade: 'npm install -g @m8t-stack/cli@latest' (or your package manager's equivalent)."
|
|
18664
|
+
});
|
|
18665
|
+
}
|
|
18666
|
+
if (compareSemver(running, recommended) < 0) {
|
|
18667
|
+
onWarn(`This CLI (${runningVersion}) is older than the recommended version (${cli2.recommended}) for platform ${manifest.platform.tag}. Consider upgrading.`);
|
|
18668
|
+
}
|
|
18669
|
+
}
|
|
18670
|
+
|
|
18671
|
+
// src/commands/platform/update.ts
|
|
18672
|
+
var PlatformUpdateCommand = class extends M8tCommand {
|
|
18673
|
+
static paths = [["platform", "update"]];
|
|
18674
|
+
static usage = Command32.Usage({
|
|
18675
|
+
description: "Converge the whole platform (gateway, hosted agents, personas, infra) to a release manifest.",
|
|
18676
|
+
details: "Fetches a release manifest (the current channel by default), diffs it against the installed-state stamp, and converges each changed component via its own executor: rolls the gateway/hosted-agent images, re-versions prompt personas (compose-preserving), and records infra state. A deploy/ tree drift is reported but NOT applied unless --force-infra is passed. Use --check to preview, --only to converge a single component, --yes to skip the confirmation prompt."
|
|
18677
|
+
});
|
|
18678
|
+
subscription = Option30.String("--subscription");
|
|
18679
|
+
resourceGroup = Option30.String("--resource-group", {
|
|
18680
|
+
description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
18681
|
+
});
|
|
18682
|
+
to = Option30.String("--to", {
|
|
18683
|
+
description: "Pin the platform version to converge to (defaults to the current release channel)."
|
|
18684
|
+
});
|
|
18685
|
+
only = Option30.String("--only", {
|
|
18686
|
+
description: "Converge only one component: infra | gateway | codingAgent | azureExecutor | personas."
|
|
18687
|
+
});
|
|
18688
|
+
check = Option30.Boolean("--check", false);
|
|
18689
|
+
yes = Option30.Boolean("--yes", false);
|
|
18690
|
+
skipInfra = Option30.Boolean("--skip-infra", false);
|
|
18691
|
+
forceInfra = Option30.Boolean("--force-infra", false);
|
|
18692
|
+
suffix = Option30.String("--suffix", {
|
|
18693
|
+
description: "Required with --force-infra \u2014 the existing deployment's resource-name suffix (avoids provisioning duplicates)."
|
|
18694
|
+
});
|
|
18695
|
+
contentDir = Option30.String("--content-dir", {
|
|
18696
|
+
description: "Path to a local m8t-stack repo checkout (skips the tarball fetch + tree-drift check)."
|
|
18697
|
+
});
|
|
18698
|
+
manifestUrl = Option30.String("--manifest-url", {
|
|
18699
|
+
description: "Fetch the release manifest from this URL instead of the release channel."
|
|
18700
|
+
});
|
|
18701
|
+
manifestFile = Option30.String("--manifest-file", {
|
|
18702
|
+
description: "Read the release manifest from this local file instead of the release channel."
|
|
18703
|
+
});
|
|
18704
|
+
endpoint = Option30.String("--endpoint", {
|
|
18705
|
+
description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
|
|
18706
|
+
});
|
|
18707
|
+
output = Option30.String("--output");
|
|
18708
|
+
async executeCommand() {
|
|
18709
|
+
const mode = resolveOutputMode(
|
|
18710
|
+
this.output,
|
|
18711
|
+
this.context.stdout
|
|
18712
|
+
);
|
|
18713
|
+
const interactive = this.context.stdout.isTTY === true;
|
|
18714
|
+
const log = (msg) => {
|
|
18715
|
+
if (mode !== "json") this.context.stdout.write(msg + "\n");
|
|
18716
|
+
};
|
|
18717
|
+
const to = typeof this.to === "string" ? this.to : void 0;
|
|
18718
|
+
const only = typeof this.only === "string" ? this.only : void 0;
|
|
18719
|
+
const suffix = typeof this.suffix === "string" ? this.suffix : void 0;
|
|
18720
|
+
const contentDirFlag = typeof this.contentDir === "string" ? this.contentDir : void 0;
|
|
18721
|
+
const manifestUrl = typeof this.manifestUrl === "string" ? this.manifestUrl : void 0;
|
|
18722
|
+
const manifestFile = typeof this.manifestFile === "string" ? this.manifestFile : void 0;
|
|
18723
|
+
const gw = await resolveGatewayContext({
|
|
18724
|
+
interactive: mode !== "json",
|
|
18725
|
+
subscriptionId: this.subscription,
|
|
18726
|
+
resourceGroup: this.resourceGroup
|
|
18727
|
+
});
|
|
18728
|
+
const { resourceGroup } = parseContainerAppResourceId(gw.containerAppResourceId);
|
|
18729
|
+
const credential2 = new DefaultAzureCredential17();
|
|
18730
|
+
const account = await getAzAccount();
|
|
18731
|
+
const subscriptionId = this.subscription ?? account.subscriptionId;
|
|
18732
|
+
const onProgress = (m) => {
|
|
18733
|
+
log(colors.dim(m));
|
|
18734
|
+
};
|
|
18735
|
+
const onWarn = (w) => {
|
|
18736
|
+
log(colors.warn(w));
|
|
18737
|
+
};
|
|
18738
|
+
onProgress("resolving Foundry project\u2026");
|
|
18739
|
+
const project = await resolveFoundryProject({
|
|
18740
|
+
credential: credential2,
|
|
18741
|
+
subscriptionId,
|
|
18742
|
+
interactive,
|
|
18743
|
+
endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
|
|
18744
|
+
});
|
|
18745
|
+
const source = manifestFile ? { file: manifestFile } : manifestUrl ? { url: manifestUrl } : { channel: true, version: to };
|
|
18746
|
+
onProgress("fetching release manifest\u2026");
|
|
18747
|
+
const manifest = await fetchManifest(source);
|
|
18748
|
+
assertCliVersionOk(manifest, CLI_VERSION, onWarn);
|
|
18749
|
+
const stamp = await readStamp({ credential: credential2, subscriptionId, resourceGroup });
|
|
18750
|
+
onProgress("resolving release content\u2026");
|
|
18751
|
+
const contentDir = await resolveReleaseContent(manifest, { contentDir: contentDirFlag });
|
|
18752
|
+
const tree = contentDirFlag ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
|
|
18753
|
+
if (!contentDirFlag) {
|
|
18754
|
+
const mismatches = verifyPersonaTrees(manifest, tree);
|
|
18755
|
+
if (mismatches.length > 0) {
|
|
18756
|
+
log(colors.warn(`warning: ${mismatches.length.toString()} persona tree mismatch(es) vs the manifest.`));
|
|
18757
|
+
}
|
|
18758
|
+
}
|
|
18759
|
+
const plan = diffPlan(manifest, stamp, tree, { only, forceInfra: this.forceInfra, skipInfra: this.skipInfra });
|
|
18760
|
+
this.renderPlan(plan, mode);
|
|
18761
|
+
const deferredInfra = plan.skipped.find((s) => s.component === "infra" && s.reason === "deferred");
|
|
18762
|
+
if (deferredInfra) {
|
|
18763
|
+
log(
|
|
18764
|
+
colors.warn(
|
|
18765
|
+
`infra changed in ${plan.targetVersion} (deploy/ tree drift) \u2014 NOT applied by the CLI. Run 'm8t platform update --force-infra --suffix <n>' or let the update job converge it.`
|
|
18766
|
+
)
|
|
18767
|
+
);
|
|
18768
|
+
}
|
|
18769
|
+
if (this.check) {
|
|
18770
|
+
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, checked: true, applied: false }) + "\n");
|
|
18771
|
+
return 0;
|
|
18772
|
+
}
|
|
18773
|
+
if (plan.actions.length === 0) {
|
|
18774
|
+
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: false }) + "\n");
|
|
18775
|
+
else log(colors.success("already up to date \u2713"));
|
|
18776
|
+
return 0;
|
|
18777
|
+
}
|
|
18778
|
+
if (!this.yes && interactive) {
|
|
18779
|
+
const ok = await confirm5({
|
|
18780
|
+
message: `Converge ${plan.actions.length.toString()} component(s) to ${plan.targetVersion}?`,
|
|
18781
|
+
default: true
|
|
18782
|
+
});
|
|
18783
|
+
if (!ok) {
|
|
18784
|
+
log(colors.dim("aborted \u2014 no change."));
|
|
18785
|
+
return 0;
|
|
18786
|
+
}
|
|
18787
|
+
}
|
|
18788
|
+
const deps = await buildConvergeDeps({
|
|
18789
|
+
credential: credential2,
|
|
18790
|
+
subscriptionId,
|
|
18791
|
+
resourceGroup,
|
|
18792
|
+
contentDir,
|
|
18793
|
+
manifest,
|
|
18794
|
+
tree,
|
|
18795
|
+
project,
|
|
18796
|
+
gatewayResourceId: gw.containerAppResourceId,
|
|
18797
|
+
gatewayUrl: gw.gatewayUrl,
|
|
18798
|
+
suffix,
|
|
18799
|
+
onProgress,
|
|
18800
|
+
onWarn
|
|
18801
|
+
});
|
|
18802
|
+
await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress });
|
|
18803
|
+
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: true }) + "\n");
|
|
18804
|
+
else log(colors.success(`converged \u2192 ${plan.targetVersion} \u2713`));
|
|
18805
|
+
return 0;
|
|
18806
|
+
}
|
|
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
|
+
);
|
|
18831
|
+
}
|
|
18832
|
+
};
|
|
18833
|
+
|
|
18834
|
+
// src/commands/platform/enable-cost-report.ts
|
|
18835
|
+
import { Command as Command33, Option as Option31 } from "clipanion";
|
|
18836
|
+
|
|
18837
|
+
// src/lib/wire-gateway-acs.ts
|
|
18838
|
+
init_rbac();
|
|
18839
|
+
async function wireGatewayForAcs(args) {
|
|
18840
|
+
try {
|
|
18841
|
+
await args.az([
|
|
18842
|
+
"role",
|
|
18843
|
+
"assignment",
|
|
18844
|
+
"create",
|
|
18845
|
+
"--assignee-object-id",
|
|
18846
|
+
args.gatewayPrincipalId,
|
|
18847
|
+
"--assignee-principal-type",
|
|
18848
|
+
"ServicePrincipal",
|
|
18849
|
+
"--role",
|
|
18850
|
+
CONTRIBUTOR_ROLE_ID,
|
|
18851
|
+
"--scope",
|
|
18852
|
+
args.acsResourceId
|
|
18853
|
+
]);
|
|
18854
|
+
} catch (e) {
|
|
17664
18855
|
if (!(e instanceof Error && /RoleAssignmentExists/i.test(e.message))) throw e;
|
|
17665
18856
|
}
|
|
17666
18857
|
await args.az([
|
|
@@ -18023,156 +19214,11 @@ async function patchRedirectUris(appObjectId, fqdn) {
|
|
|
18023
19214
|
]);
|
|
18024
19215
|
}
|
|
18025
19216
|
|
|
18026
|
-
// src/lib/deploy.ts
|
|
18027
|
-
import * as fs18 from "fs/promises";
|
|
18028
|
-
import * as os8 from "os";
|
|
18029
|
-
import * as path18 from "path";
|
|
18030
|
-
init_errors();
|
|
18031
|
-
var FOUNDRY_TRACING_MODES = ["project", "account", "skip"];
|
|
18032
|
-
function parseFoundryTracingMode(v) {
|
|
18033
|
-
if (v === void 0) return void 0;
|
|
18034
|
-
if (!FOUNDRY_TRACING_MODES.includes(v)) {
|
|
18035
|
-
throw new LocalCliError({
|
|
18036
|
-
code: "DEPLOY_INVALID_TRACING_MODE",
|
|
18037
|
-
message: `Invalid --foundry-tracing value: '${v}'.`,
|
|
18038
|
-
hint: `Use one of: ${FOUNDRY_TRACING_MODES.join(", ")}.`
|
|
18039
|
-
});
|
|
18040
|
-
}
|
|
18041
|
-
return v;
|
|
18042
|
-
}
|
|
18043
|
-
function buildBicepParams(p) {
|
|
18044
|
-
const params = [
|
|
18045
|
-
`location=${p.location}`,
|
|
18046
|
-
`suffix=${p.suffix}`,
|
|
18047
|
-
`imageRef=${p.imageRef}`,
|
|
18048
|
-
`tenantId=${p.tenantId}`,
|
|
18049
|
-
`clientId=${p.clientId}`,
|
|
18050
|
-
`foundryResourceId=${p.foundryResourceId}`,
|
|
18051
|
-
`foundryProjectEndpoint=${p.foundryProjectEndpoint}`,
|
|
18052
|
-
`acrPullIdentityResourceId=${p.acrPullIdentityResourceId}`,
|
|
18053
|
-
`acrResourceId=${p.acrResourceId}`
|
|
18054
|
-
];
|
|
18055
|
-
if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
|
|
18056
|
-
return params;
|
|
18057
|
-
}
|
|
18058
|
-
async function resolveRepoRoot2(home = os8.homedir()) {
|
|
18059
|
-
const marker = path18.join(home, ".m8t-stack", "repo-root");
|
|
18060
|
-
try {
|
|
18061
|
-
const root = (await fs18.readFile(marker, "utf8")).trim();
|
|
18062
|
-
if (!root) throw new Error("empty");
|
|
18063
|
-
return root;
|
|
18064
|
-
} catch {
|
|
18065
|
-
throw new LocalCliError({
|
|
18066
|
-
code: "DEPLOY_NO_REPO_ROOT",
|
|
18067
|
-
message: "~/.m8t-stack/repo-root marker is missing or empty.",
|
|
18068
|
-
hint: "Run 'm8t install' from an m8t-stack checkout (it writes the marker), or run 'm8t deploy' from the repo."
|
|
18069
|
-
});
|
|
18070
|
-
}
|
|
18071
|
-
}
|
|
18072
|
-
async function resolveFoundryResourceId(endpoint, explicit) {
|
|
18073
|
-
if (explicit) return explicit;
|
|
18074
|
-
const m = /^https:\/\/([^.]+)\.services\.ai\.azure\.com/.exec(endpoint);
|
|
18075
|
-
if (!m) {
|
|
18076
|
-
throw new LocalCliError({
|
|
18077
|
-
code: "DEPLOY_FOUNDRY_ENDPOINT_BAD",
|
|
18078
|
-
message: `Cannot parse the Foundry account name from endpoint: ${endpoint}`,
|
|
18079
|
-
hint: "Pass --foundry-resource-id <ARM-id> explicitly."
|
|
18080
|
-
});
|
|
18081
|
-
}
|
|
18082
|
-
const accounts = JSON.parse(
|
|
18083
|
-
await runAz([
|
|
18084
|
-
"cognitiveservices",
|
|
18085
|
-
"account",
|
|
18086
|
-
"list",
|
|
18087
|
-
"--query",
|
|
18088
|
-
`[?name=='${m[1]}'].id`,
|
|
18089
|
-
"--output",
|
|
18090
|
-
"json"
|
|
18091
|
-
])
|
|
18092
|
-
);
|
|
18093
|
-
if (accounts.length === 0) {
|
|
18094
|
-
throw new LocalCliError({
|
|
18095
|
-
code: "DEPLOY_FOUNDRY_NOT_FOUND",
|
|
18096
|
-
message: `No Foundry account named '${m[1]}' in the active subscription.`,
|
|
18097
|
-
hint: "Check the endpoint, or pass --foundry-resource-id explicitly."
|
|
18098
|
-
});
|
|
18099
|
-
}
|
|
18100
|
-
if (accounts.length > 1) {
|
|
18101
|
-
throw new LocalCliError({
|
|
18102
|
-
code: "DEPLOY_FOUNDRY_AMBIGUOUS",
|
|
18103
|
-
message: `Multiple Foundry accounts named '${m[1]}' \u2014 ambiguous.`,
|
|
18104
|
-
hint: "Pass --foundry-resource-id <ARM-id> to disambiguate."
|
|
18105
|
-
});
|
|
18106
|
-
}
|
|
18107
|
-
return accounts[0];
|
|
18108
|
-
}
|
|
18109
|
-
async function resolveAcrResourceId(opts) {
|
|
18110
|
-
if (opts.explicit) return opts.explicit;
|
|
18111
|
-
const host = opts.imageRef.split("/")[0];
|
|
18112
|
-
if (!host.endsWith(".azurecr.io")) return "";
|
|
18113
|
-
const acrName = host.split(".")[0];
|
|
18114
|
-
try {
|
|
18115
|
-
const id = (await runAz(["acr", "show", "-n", acrName, "--query", "id", "-o", "tsv"])).trim();
|
|
18116
|
-
if (!id) throw new Error("empty id");
|
|
18117
|
-
return id;
|
|
18118
|
-
} catch (e) {
|
|
18119
|
-
if (e instanceof LocalCliError) throw e;
|
|
18120
|
-
throw new LocalCliError({
|
|
18121
|
-
code: "DEPLOY_ACR_NOT_FOUND",
|
|
18122
|
-
message: `Cannot resolve the ACR '${acrName}' for image ${opts.imageRef}.`,
|
|
18123
|
-
hint: "Create it (az acr create), sign in (az acr login), and push the web image (docker buildx \u2026 --push); or pass --acr-resource-id <ARM-id>."
|
|
18124
|
-
});
|
|
18125
|
-
}
|
|
18126
|
-
}
|
|
18127
|
-
async function ensureResourceGroup(name, location) {
|
|
18128
|
-
const existing = JSON.parse(
|
|
18129
|
-
await runAz(["group", "show", "--name", name, "--output", "json"]).catch(() => "") || "null"
|
|
18130
|
-
);
|
|
18131
|
-
if (existing) return;
|
|
18132
|
-
await runAz(["group", "create", "--name", name, "--location", location, "--tags", "m8t-stack=deployment"]);
|
|
18133
|
-
}
|
|
18134
|
-
function resolveAcrPullIdentity(opts) {
|
|
18135
|
-
return opts.explicit ?? "";
|
|
18136
|
-
}
|
|
18137
|
-
async function runBicepDeployment(opts) {
|
|
18138
|
-
const bicepPath = path18.join(opts.repoRoot, "deploy", "main.bicep");
|
|
18139
|
-
const result = JSON.parse(
|
|
18140
|
-
await runAz([
|
|
18141
|
-
"deployment",
|
|
18142
|
-
"group",
|
|
18143
|
-
"create",
|
|
18144
|
-
"--name",
|
|
18145
|
-
opts.deploymentName,
|
|
18146
|
-
"--resource-group",
|
|
18147
|
-
opts.resourceGroup,
|
|
18148
|
-
"--template-file",
|
|
18149
|
-
bicepPath,
|
|
18150
|
-
"--parameters",
|
|
18151
|
-
...opts.params,
|
|
18152
|
-
"--output",
|
|
18153
|
-
"json"
|
|
18154
|
-
])
|
|
18155
|
-
);
|
|
18156
|
-
const outputs = result.properties?.outputs ?? {};
|
|
18157
|
-
const fqdn = outputs.containerAppFqdn?.value;
|
|
18158
|
-
if (typeof fqdn !== "string" || !fqdn) {
|
|
18159
|
-
throw new LocalCliError({
|
|
18160
|
-
code: "DEPLOY_NO_FQDN",
|
|
18161
|
-
message: "Bicep deployment did not return containerAppFqdn.",
|
|
18162
|
-
hint: "Check deploy/main.bicep outputs and the deployment log."
|
|
18163
|
-
});
|
|
18164
|
-
}
|
|
18165
|
-
return {
|
|
18166
|
-
containerAppFqdn: fqdn,
|
|
18167
|
-
resourceNames: outputs.resourceNames?.value ?? {}
|
|
18168
|
-
};
|
|
18169
|
-
}
|
|
18170
|
-
|
|
18171
19217
|
// src/commands/deploy.ts
|
|
18172
19218
|
init_errors();
|
|
18173
19219
|
|
|
18174
19220
|
// src/lib/whatif.ts
|
|
18175
|
-
import * as
|
|
19221
|
+
import * as path22 from "path";
|
|
18176
19222
|
function deriveResourceType(resourceId) {
|
|
18177
19223
|
if (resourceId.startsWith("[")) return "";
|
|
18178
19224
|
const afterProviders = resourceId.split("/providers/")[1];
|
|
@@ -18183,7 +19229,7 @@ function deriveResourceType(resourceId) {
|
|
|
18183
19229
|
return parts.join("/");
|
|
18184
19230
|
}
|
|
18185
19231
|
async function runWhatIf(opts) {
|
|
18186
|
-
const bicepPath =
|
|
19232
|
+
const bicepPath = path22.join(opts.repoRoot, "deploy", "main.bicep");
|
|
18187
19233
|
const out = await runAz([
|
|
18188
19234
|
"deployment",
|
|
18189
19235
|
"group",
|
|
@@ -18248,9 +19294,9 @@ function flattenDelta(delta, prefix = "") {
|
|
|
18248
19294
|
const out = [];
|
|
18249
19295
|
for (const d of delta) {
|
|
18250
19296
|
const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
|
|
18251
|
-
const
|
|
18252
|
-
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children,
|
|
18253
|
-
else out.push({ ...d, path:
|
|
19297
|
+
const path32 = prefix ? `${prefix}${segment}` : d.path;
|
|
19298
|
+
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path32));
|
|
19299
|
+
else out.push({ ...d, path: path32 });
|
|
18254
19300
|
}
|
|
18255
19301
|
return out;
|
|
18256
19302
|
}
|
|
@@ -18520,8 +19566,8 @@ var EvalSkillCommand = class extends M8tCommand {
|
|
|
18520
19566
|
|
|
18521
19567
|
// src/commands/eval/exam.ts
|
|
18522
19568
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
18523
|
-
import { writeFileSync as
|
|
18524
|
-
import { join as
|
|
19569
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync15, existsSync as existsSync13, readdirSync } from "fs";
|
|
19570
|
+
import { join as join22 } from "path";
|
|
18525
19571
|
import { Command as Command36, Option as Option34 } from "clipanion";
|
|
18526
19572
|
init_errors();
|
|
18527
19573
|
init_esm();
|
|
@@ -18687,10 +19733,10 @@ function resolveRefereeHome(refereeHomeOut, env) {
|
|
|
18687
19733
|
return null;
|
|
18688
19734
|
}
|
|
18689
19735
|
function resolveTaskSetDir(base, worker, version) {
|
|
18690
|
-
if (
|
|
19736
|
+
if (existsSync13(join22(base, version))) return version;
|
|
18691
19737
|
const prefixed = `${worker}-${version}`;
|
|
18692
|
-
if (
|
|
18693
|
-
if (version === "latest" &&
|
|
19738
|
+
if (existsSync13(join22(base, prefixed))) return prefixed;
|
|
19739
|
+
if (version === "latest" && existsSync13(base)) {
|
|
18694
19740
|
const dirs = readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(`${worker}-`)).map((d) => d.name).sort();
|
|
18695
19741
|
if (dirs.length > 0) return dirs[dirs.length - 1];
|
|
18696
19742
|
}
|
|
@@ -18705,11 +19751,11 @@ function readSealedTaskIds(taskSetVersion, refereeHomeOut, env = process.env) {
|
|
|
18705
19751
|
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)"
|
|
18706
19752
|
});
|
|
18707
19753
|
}
|
|
18708
|
-
const versionDir = resolveTaskSetDir(
|
|
18709
|
-
const manifestPath =
|
|
19754
|
+
const versionDir = resolveTaskSetDir(join22(home, "tasksets", worker), worker, version);
|
|
19755
|
+
const manifestPath = join22(home, "tasksets", worker, versionDir, "manifest.yaml");
|
|
18710
19756
|
let text;
|
|
18711
19757
|
try {
|
|
18712
|
-
text =
|
|
19758
|
+
text = readFileSync15(manifestPath, "utf-8");
|
|
18713
19759
|
} catch (e) {
|
|
18714
19760
|
throw new LocalCliError({
|
|
18715
19761
|
code: "EXAM_REDACTION_UNSAFE",
|
|
@@ -18827,10 +19873,10 @@ var EvalExamCommand = class extends M8tCommand {
|
|
|
18827
19873
|
const verdict = parseExamVerdict(res.stdout);
|
|
18828
19874
|
if (out !== void 0) {
|
|
18829
19875
|
const sealedTaskIds = taskSetVersion ? readSealedTaskIds(taskSetVersion, out) : /* @__PURE__ */ new Set();
|
|
18830
|
-
|
|
18831
|
-
|
|
19876
|
+
mkdirSync4(out, { recursive: true });
|
|
19877
|
+
writeFileSync4(join22(out, "verdict.json"), JSON.stringify(verdict, null, 2));
|
|
18832
19878
|
const feed = redactForFeed(verdict, sealedTaskIds);
|
|
18833
|
-
|
|
19879
|
+
writeFileSync4(join22(out, "feed.json"), JSON.stringify(feed, null, 2));
|
|
18834
19880
|
}
|
|
18835
19881
|
if (mode === "json") {
|
|
18836
19882
|
this.context.stdout.write(renderJson(verdict) + "\n");
|
|
@@ -19092,10 +20138,10 @@ var StatusCommand = class extends M8tCommand {
|
|
|
19092
20138
|
|
|
19093
20139
|
// src/commands/doctor.ts
|
|
19094
20140
|
import { Command as Command40, Option as Option38 } from "clipanion";
|
|
19095
|
-
import { DefaultAzureCredential as
|
|
19096
|
-
import * as
|
|
19097
|
-
import * as
|
|
19098
|
-
import * as
|
|
20141
|
+
import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
|
|
20142
|
+
import * as fs22 from "fs";
|
|
20143
|
+
import * as os10 from "os";
|
|
20144
|
+
import * as path23 from "path";
|
|
19099
20145
|
|
|
19100
20146
|
// src/lib/doctor-checks.ts
|
|
19101
20147
|
function checkAzSignedIn(az) {
|
|
@@ -19320,24 +20366,24 @@ async function resolveAccountLocation(accountId) {
|
|
|
19320
20366
|
}
|
|
19321
20367
|
}
|
|
19322
20368
|
function probeRepoRoot() {
|
|
19323
|
-
const marker =
|
|
19324
|
-
if (!
|
|
20369
|
+
const marker = path23.join(os10.homedir(), ".m8t-stack", "repo-root");
|
|
20370
|
+
if (!fs22.existsSync(marker)) {
|
|
19325
20371
|
return { markerExists: false, path: null, isDir: false, isGitCheckout: false };
|
|
19326
20372
|
}
|
|
19327
20373
|
let repoPath;
|
|
19328
20374
|
try {
|
|
19329
|
-
repoPath =
|
|
20375
|
+
repoPath = fs22.readFileSync(marker, "utf8").trim();
|
|
19330
20376
|
} catch {
|
|
19331
20377
|
return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
|
|
19332
20378
|
}
|
|
19333
20379
|
if (!repoPath) return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
|
|
19334
20380
|
let isDir = false;
|
|
19335
20381
|
try {
|
|
19336
|
-
isDir =
|
|
20382
|
+
isDir = fs22.statSync(repoPath).isDirectory();
|
|
19337
20383
|
} catch {
|
|
19338
20384
|
isDir = false;
|
|
19339
20385
|
}
|
|
19340
|
-
const isGitCheckout = isDir &&
|
|
20386
|
+
const isGitCheckout = isDir && fs22.existsSync(path23.join(repoPath, ".git"));
|
|
19341
20387
|
return { markerExists: true, path: repoPath, isDir, isGitCheckout };
|
|
19342
20388
|
}
|
|
19343
20389
|
var DoctorCommand = class extends M8tCommand {
|
|
@@ -19420,7 +20466,7 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
19420
20466
|
if (typeof this.agent === "string" && this.agent) {
|
|
19421
20467
|
checking(`delivery grant for ${this.agent}`);
|
|
19422
20468
|
try {
|
|
19423
|
-
const credential2 = new
|
|
20469
|
+
const credential2 = new DefaultAzureCredential18();
|
|
19424
20470
|
const cur = await getAgentVersion({
|
|
19425
20471
|
credential: credential2,
|
|
19426
20472
|
projectEndpoint: foundry.projectEndpoint,
|
|
@@ -19466,15 +20512,15 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
19466
20512
|
import { Command as Command41, Option as Option39 } from "clipanion";
|
|
19467
20513
|
|
|
19468
20514
|
// src/lib/profiles.ts
|
|
19469
|
-
import * as
|
|
19470
|
-
import * as
|
|
19471
|
-
import { parse as
|
|
20515
|
+
import * as fs23 from "fs/promises";
|
|
20516
|
+
import * as path24 from "path";
|
|
20517
|
+
import { parse as parseYaml11, stringify as stringifyYaml4 } from "yaml";
|
|
19472
20518
|
function profilesDir() {
|
|
19473
20519
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
19474
|
-
return
|
|
20520
|
+
return path24.join(home, ".m8t-stack", "profiles");
|
|
19475
20521
|
}
|
|
19476
20522
|
function getProfilePath(name) {
|
|
19477
|
-
return
|
|
20523
|
+
return path24.join(profilesDir(), `${path24.basename(name)}.yaml`);
|
|
19478
20524
|
}
|
|
19479
20525
|
function slugifyTenant(domainOrId) {
|
|
19480
20526
|
const base = domainOrId.split(".")[0].toLowerCase();
|
|
@@ -19483,7 +20529,7 @@ function slugifyTenant(domainOrId) {
|
|
|
19483
20529
|
}
|
|
19484
20530
|
async function listProfiles() {
|
|
19485
20531
|
try {
|
|
19486
|
-
const entries = await
|
|
20532
|
+
const entries = await fs23.readdir(profilesDir());
|
|
19487
20533
|
return entries.filter((e) => e.endsWith(".yaml")).map((e) => e.replace(/\.yaml$/, "")).sort();
|
|
19488
20534
|
} catch (e) {
|
|
19489
20535
|
if (e.code === "ENOENT") return [];
|
|
@@ -19492,8 +20538,8 @@ async function listProfiles() {
|
|
|
19492
20538
|
}
|
|
19493
20539
|
async function readProfile(name) {
|
|
19494
20540
|
try {
|
|
19495
|
-
const raw = await
|
|
19496
|
-
const parsed =
|
|
20541
|
+
const raw = await fs23.readFile(getProfilePath(name), "utf8");
|
|
20542
|
+
const parsed = parseYaml11(raw);
|
|
19497
20543
|
if (!parsed || typeof parsed !== "object") return null;
|
|
19498
20544
|
return parsed;
|
|
19499
20545
|
} catch (e) {
|
|
@@ -19503,14 +20549,14 @@ async function readProfile(name) {
|
|
|
19503
20549
|
}
|
|
19504
20550
|
async function exists(p) {
|
|
19505
20551
|
try {
|
|
19506
|
-
await
|
|
20552
|
+
await fs23.stat(p);
|
|
19507
20553
|
return true;
|
|
19508
20554
|
} catch {
|
|
19509
20555
|
return false;
|
|
19510
20556
|
}
|
|
19511
20557
|
}
|
|
19512
20558
|
async function saveProfile(p) {
|
|
19513
|
-
await
|
|
20559
|
+
await fs23.mkdir(profilesDir(), { recursive: true });
|
|
19514
20560
|
let name = p.name;
|
|
19515
20561
|
let n = 2;
|
|
19516
20562
|
while (await exists(getProfilePath(name))) {
|
|
@@ -19519,8 +20565,8 @@ async function saveProfile(p) {
|
|
|
19519
20565
|
}
|
|
19520
20566
|
const target = getProfilePath(name);
|
|
19521
20567
|
const tmp = `${target}.tmp`;
|
|
19522
|
-
await
|
|
19523
|
-
await
|
|
20568
|
+
await fs23.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
|
|
20569
|
+
await fs23.rename(tmp, target);
|
|
19524
20570
|
return name;
|
|
19525
20571
|
}
|
|
19526
20572
|
|
|
@@ -19766,7 +20812,7 @@ var OpenCommand = class extends M8tCommand {
|
|
|
19766
20812
|
// src/commands/dream/run.ts
|
|
19767
20813
|
import { Command as Command43, Option as Option41 } from "clipanion";
|
|
19768
20814
|
import { AzureCliCredential } from "@azure/identity";
|
|
19769
|
-
import { TableClient as
|
|
20815
|
+
import { TableClient as TableClient3 } from "@azure/data-tables";
|
|
19770
20816
|
import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
|
|
19771
20817
|
import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
|
|
19772
20818
|
|
|
@@ -20073,7 +21119,7 @@ var ConvFetchError = class extends Error {
|
|
|
20073
21119
|
};
|
|
20074
21120
|
|
|
20075
21121
|
// ../../packages/brain/engine/dist/esm/cursor.js
|
|
20076
|
-
import { TableClient } from "@azure/data-tables";
|
|
21122
|
+
import { TableClient as TableClient2 } from "@azure/data-tables";
|
|
20077
21123
|
|
|
20078
21124
|
// ../../packages/agent-ledger/dist/esm/row-key.js
|
|
20079
21125
|
var MAX_MS = 864e13;
|
|
@@ -20195,7 +21241,7 @@ async function commitCursorAdvance(plan, cursor) {
|
|
|
20195
21241
|
}
|
|
20196
21242
|
var CURSOR_TABLE_NAME = "BrainDreamCursor";
|
|
20197
21243
|
function makeTableCursor(credential2, tableEndpoint) {
|
|
20198
|
-
const client = new
|
|
21244
|
+
const client = new TableClient2(tableEndpoint, CURSOR_TABLE_NAME, credential2);
|
|
20199
21245
|
return new TableCursor(client);
|
|
20200
21246
|
}
|
|
20201
21247
|
function formatCursorPreview(cursor) {
|
|
@@ -20272,7 +21318,7 @@ function mapItems(raw) {
|
|
|
20272
21318
|
return acc.reverse();
|
|
20273
21319
|
}
|
|
20274
21320
|
function makeConversationSource(client, opts = {}) {
|
|
20275
|
-
const
|
|
21321
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
20276
21322
|
return {
|
|
20277
21323
|
async items(conversationId) {
|
|
20278
21324
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
@@ -20290,7 +21336,7 @@ function makeConversationSource(client, opts = {}) {
|
|
|
20290
21336
|
throw new ConvFetchError("auth", `auth failed reading ${conversationId}`);
|
|
20291
21337
|
const retryable = status === void 0 || status === TRANSIENT;
|
|
20292
21338
|
if (retryable && attempt < MAX_ATTEMPTS - 1) {
|
|
20293
|
-
await
|
|
21339
|
+
await sleep3(attempt);
|
|
20294
21340
|
continue;
|
|
20295
21341
|
}
|
|
20296
21342
|
throw new ConvFetchError("terminal", `exhausted ${String(MAX_ATTEMPTS)} retries reading ${conversationId}: ${e?.message ?? String(e)}`);
|
|
@@ -20689,7 +21735,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
|
|
|
20689
21735
|
// ../../packages/brain/engine/dist/esm/dream/writer.js
|
|
20690
21736
|
var API2 = "https://api.github.com";
|
|
20691
21737
|
var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
|
|
20692
|
-
var isAllowedWritePath = (
|
|
21738
|
+
var isAllowedWritePath = (path32) => WRITER_ALLOWED_PREFIX.test(path32) && !path32.split("/").includes("..");
|
|
20693
21739
|
var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
20694
21740
|
function parseFrontmatter(content) {
|
|
20695
21741
|
const m = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
@@ -20728,8 +21774,8 @@ function parseFrontmatter(content) {
|
|
|
20728
21774
|
function makeGitDataWriter(args) {
|
|
20729
21775
|
const doFetch = args.fetchImpl ?? fetch;
|
|
20730
21776
|
const repoPath = args.repository;
|
|
20731
|
-
async function gh(token, method,
|
|
20732
|
-
const res = await doFetch(`${API2}/repos/${repoPath}${
|
|
21777
|
+
async function gh(token, method, path32, body) {
|
|
21778
|
+
const res = await doFetch(`${API2}/repos/${repoPath}${path32}`, {
|
|
20733
21779
|
method,
|
|
20734
21780
|
headers: {
|
|
20735
21781
|
Authorization: `Bearer ${token}`,
|
|
@@ -20742,8 +21788,8 @@ function makeGitDataWriter(args) {
|
|
|
20742
21788
|
const text = await res.text();
|
|
20743
21789
|
return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
|
|
20744
21790
|
}
|
|
20745
|
-
async function readFileWith(token,
|
|
20746
|
-
const encodedPath =
|
|
21791
|
+
async function readFileWith(token, path32) {
|
|
21792
|
+
const encodedPath = path32.split("/").map(encodeURIComponent).join("/");
|
|
20747
21793
|
const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
|
|
20748
21794
|
if (r.status === 404)
|
|
20749
21795
|
return null;
|
|
@@ -20759,9 +21805,9 @@ function makeGitDataWriter(args) {
|
|
|
20759
21805
|
throw new Error(`fetchTip: HTTP ${String(r.status)}`);
|
|
20760
21806
|
return r.json.object.sha;
|
|
20761
21807
|
},
|
|
20762
|
-
async readFile(
|
|
21808
|
+
async readFile(path32) {
|
|
20763
21809
|
const token = await args.mintToken();
|
|
20764
|
-
return readFileWith(token,
|
|
21810
|
+
return readFileWith(token, path32);
|
|
20765
21811
|
},
|
|
20766
21812
|
async listMemory() {
|
|
20767
21813
|
const token = await args.mintToken();
|
|
@@ -20770,9 +21816,9 @@ function makeGitDataWriter(args) {
|
|
|
20770
21816
|
return [];
|
|
20771
21817
|
const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
|
|
20772
21818
|
const out = [];
|
|
20773
|
-
for (const
|
|
20774
|
-
const content = await readFileWith(token,
|
|
20775
|
-
out.push({ path:
|
|
21819
|
+
for (const path32 of paths) {
|
|
21820
|
+
const content = await readFileWith(token, path32);
|
|
21821
|
+
out.push({ path: path32, frontmatter: content ? parseFrontmatter(content) : {} });
|
|
20776
21822
|
}
|
|
20777
21823
|
return out;
|
|
20778
21824
|
},
|
|
@@ -21074,7 +22120,7 @@ function extractJson(text) {
|
|
|
21074
22120
|
return JSON.parse(candidate2.slice(start, end + 1));
|
|
21075
22121
|
}
|
|
21076
22122
|
async function propose(model, system, user, opts = {}) {
|
|
21077
|
-
const
|
|
22123
|
+
const sleep3 = opts.sleep ?? defaultSleep2;
|
|
21078
22124
|
const modelName = opts.model ?? MODEL_NAME_FALLBACK;
|
|
21079
22125
|
let inputTokens = 0;
|
|
21080
22126
|
let outputTokens = 0;
|
|
@@ -21090,7 +22136,7 @@ async function propose(model, system, user, opts = {}) {
|
|
|
21090
22136
|
} catch (e) {
|
|
21091
22137
|
lastErr = `parse: ${e.message}`;
|
|
21092
22138
|
if (attempt < MAX_MODEL_ATTEMPTS - 1) {
|
|
21093
|
-
await
|
|
22139
|
+
await sleep3(attempt);
|
|
21094
22140
|
continue;
|
|
21095
22141
|
}
|
|
21096
22142
|
break;
|
|
@@ -21099,7 +22145,7 @@ async function propose(model, system, user, opts = {}) {
|
|
|
21099
22145
|
if (!Array.isArray(rawDeltas)) {
|
|
21100
22146
|
lastErr = "schema: top-level { deltas: [] } missing";
|
|
21101
22147
|
if (attempt < MAX_MODEL_ATTEMPTS - 1) {
|
|
21102
|
-
await
|
|
22148
|
+
await sleep3(attempt);
|
|
21103
22149
|
continue;
|
|
21104
22150
|
}
|
|
21105
22151
|
break;
|
|
@@ -21209,7 +22255,7 @@ async function loadMemoryContext(brain) {
|
|
|
21209
22255
|
return {
|
|
21210
22256
|
files,
|
|
21211
22257
|
indexEntries,
|
|
21212
|
-
originOf: (
|
|
22258
|
+
originOf: (path32) => originByPath.get(path32) ?? "worker"
|
|
21213
22259
|
};
|
|
21214
22260
|
}
|
|
21215
22261
|
|
|
@@ -21230,14 +22276,14 @@ function checkEvidenceMembership(delta, harvested) {
|
|
|
21230
22276
|
}
|
|
21231
22277
|
var BatchAbort = class extends Error {
|
|
21232
22278
|
path;
|
|
21233
|
-
constructor(
|
|
22279
|
+
constructor(path32, message) {
|
|
21234
22280
|
super(message);
|
|
21235
|
-
this.path =
|
|
22281
|
+
this.path = path32;
|
|
21236
22282
|
this.name = "BatchAbort";
|
|
21237
22283
|
}
|
|
21238
22284
|
};
|
|
21239
|
-
var isIndexFile = (
|
|
21240
|
-
var isMemoryIndex = (
|
|
22285
|
+
var isIndexFile = (path32) => /(^|\/)[^/]*_index\.md$/.test(path32);
|
|
22286
|
+
var isMemoryIndex = (path32) => path32 === "memory/MEMORY.md";
|
|
21241
22287
|
function targetPath(delta) {
|
|
21242
22288
|
switch (delta.verb) {
|
|
21243
22289
|
case "new":
|
|
@@ -21254,29 +22300,29 @@ function targetPath(delta) {
|
|
|
21254
22300
|
}
|
|
21255
22301
|
async function checkOriginTier(delta, lookup) {
|
|
21256
22302
|
const evidence = evidenceOf(delta) ?? [];
|
|
21257
|
-
const
|
|
21258
|
-
if (
|
|
22303
|
+
const path32 = targetPath(delta);
|
|
22304
|
+
if (path32 === null)
|
|
21259
22305
|
return { ok: true };
|
|
21260
|
-
if (isIndexFile(
|
|
21261
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
22306
|
+
if (isIndexFile(path32) && !isMemoryIndex(path32)) {
|
|
22307
|
+
return { ok: false, flagged: { kind: "flagged", path: path32, tension: `refused: ${path32} is an index file (only memory/MEMORY.md is editable)`, evidence } };
|
|
21262
22308
|
}
|
|
21263
|
-
const target = await lookup(
|
|
21264
|
-
const isStructural =
|
|
22309
|
+
const target = await lookup(path32);
|
|
22310
|
+
const isStructural = path32.startsWith("references/");
|
|
21265
22311
|
const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
|
|
21266
22312
|
const editable = origin === "worker" || origin === "dream";
|
|
21267
22313
|
if (editable)
|
|
21268
22314
|
return { ok: true };
|
|
21269
22315
|
const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
|
|
21270
22316
|
if (removesOperatorTarget) {
|
|
21271
|
-
throw new BatchAbort(
|
|
22317
|
+
throw new BatchAbort(path32, `supersede/retract OLD target ${path32} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
|
|
21272
22318
|
}
|
|
21273
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
22319
|
+
return { ok: false, flagged: { kind: "flagged", path: path32, tension: `operator-origin (${origin}); flag-only`, evidence } };
|
|
21274
22320
|
}
|
|
21275
22321
|
var ALLOWED_MEMORY = /^memory\/.+\.md$/;
|
|
21276
22322
|
var ALLOWED_INBOX = /^inbox\//;
|
|
21277
22323
|
var ALLOWED_QUARANTINE = /^quarantine\//;
|
|
21278
22324
|
var ALLOWED_FLAG = /^memory\//;
|
|
21279
|
-
var hasTraversal = (
|
|
22325
|
+
var hasTraversal = (path32) => path32.split("/").includes("..");
|
|
21280
22326
|
function checkPathAllowed(delta) {
|
|
21281
22327
|
const evidence = evidenceOf(delta) ?? [];
|
|
21282
22328
|
const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
|
|
@@ -21383,7 +22429,7 @@ ${inject}
|
|
|
21383
22429
|
---
|
|
21384
22430
|
`);
|
|
21385
22431
|
}
|
|
21386
|
-
var indexLine = (
|
|
22432
|
+
var indexLine = (path32, title, date, tags) => `- \`${path32}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
|
|
21387
22433
|
function splitIndex(index) {
|
|
21388
22434
|
const all = index.split("\n");
|
|
21389
22435
|
const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
|
|
@@ -21556,16 +22602,16 @@ function defaultDreamSeams(opts = {}) {
|
|
|
21556
22602
|
const mem = await memory(deps.brain);
|
|
21557
22603
|
const harvested = harvestedIds(input);
|
|
21558
22604
|
const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
|
|
21559
|
-
const lookup = (
|
|
22605
|
+
const lookup = (path32) => Promise.resolve(mem.files.find((f) => f.path === path32) ?? null);
|
|
21560
22606
|
const digest = [...pendingRejected];
|
|
21561
22607
|
let changes = [];
|
|
21562
22608
|
let indexEdit;
|
|
21563
22609
|
try {
|
|
21564
22610
|
for (const raw of deltas) {
|
|
21565
22611
|
const delta = forcePolicyQuarantine(raw);
|
|
21566
|
-
const
|
|
21567
|
-
if (!
|
|
21568
|
-
digest.push(
|
|
22612
|
+
const path32 = checkPathAllowed(delta);
|
|
22613
|
+
if (!path32.ok) {
|
|
22614
|
+
digest.push(path32.rejected);
|
|
21569
22615
|
continue;
|
|
21570
22616
|
}
|
|
21571
22617
|
const ev = checkEvidenceMembership(delta, harvested);
|
|
@@ -21760,7 +22806,7 @@ async function emitDigest(input, deps, digest) {
|
|
|
21760
22806
|
}
|
|
21761
22807
|
|
|
21762
22808
|
// ../../packages/brain/engine/dist/esm/dream/config.js
|
|
21763
|
-
import { parse as
|
|
22809
|
+
import { parse as parseYaml12 } from "yaml";
|
|
21764
22810
|
var DEFAULT_DREAM_MODEL = "gpt-5.4";
|
|
21765
22811
|
function asCadence(v) {
|
|
21766
22812
|
return v === "nightly" ? "nightly" : "off";
|
|
@@ -21768,7 +22814,7 @@ function asCadence(v) {
|
|
|
21768
22814
|
function parseDreamerConfig(rawYaml, env) {
|
|
21769
22815
|
let parsed;
|
|
21770
22816
|
try {
|
|
21771
|
-
parsed =
|
|
22817
|
+
parsed = parseYaml12(rawYaml);
|
|
21772
22818
|
} catch {
|
|
21773
22819
|
return { enabled: false, cadence: "off", model: env.M8T_DREAM_MODEL ?? DEFAULT_DREAM_MODEL };
|
|
21774
22820
|
}
|
|
@@ -21782,31 +22828,31 @@ function parseDreamerConfig(rawYaml, env) {
|
|
|
21782
22828
|
}
|
|
21783
22829
|
|
|
21784
22830
|
// ../../packages/brain/engine/dist/esm/cost-report/config.js
|
|
21785
|
-
import { parse as
|
|
22831
|
+
import { parse as parseYaml13 } from "yaml";
|
|
21786
22832
|
|
|
21787
22833
|
// ../../packages/brain/engine/dist/esm/librarian/codify/persistence-guard.js
|
|
21788
22834
|
var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
|
|
21789
22835
|
|
|
21790
22836
|
// ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
|
|
21791
|
-
import { parse as
|
|
22837
|
+
import { parse as parseYaml14, stringify as stringifyYaml5 } from "yaml";
|
|
21792
22838
|
|
|
21793
22839
|
// ../../packages/brain/engine/dist/esm/librarian/config.js
|
|
21794
|
-
import { parse as
|
|
22840
|
+
import { parse as parseYaml15 } from "yaml";
|
|
21795
22841
|
|
|
21796
22842
|
// src/lib/dream-discovery.ts
|
|
21797
22843
|
init_http();
|
|
21798
22844
|
init_errors();
|
|
21799
|
-
var
|
|
21800
|
-
var
|
|
21801
|
-
var
|
|
22845
|
+
var ARM4 = "https://management.azure.com";
|
|
22846
|
+
var ARM_SCOPE8 = "https://management.azure.com/.default";
|
|
22847
|
+
var STORAGE_API2 = "2023-05-01";
|
|
21802
22848
|
var LA_API = "2023-09-01";
|
|
21803
22849
|
async function discoverLedgerResources(opts) {
|
|
21804
22850
|
const { credential: credential2, subscriptionId, resourceGroup } = opts;
|
|
21805
22851
|
const storage = await authedJson({
|
|
21806
22852
|
credential: credential2,
|
|
21807
|
-
scope:
|
|
22853
|
+
scope: ARM_SCOPE8,
|
|
21808
22854
|
method: "GET",
|
|
21809
|
-
url: `${
|
|
22855
|
+
url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API2}`
|
|
21810
22856
|
}) ?? {};
|
|
21811
22857
|
const accounts = (storage.value ?? []).filter((a) => a.properties?.primaryEndpoints?.table);
|
|
21812
22858
|
const ledgerAcct = accounts.find((a) => (a.tags?.["m8t-stack:role"] ?? a.properties?.tags?.["m8t-stack:role"]) === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
|
|
@@ -21820,9 +22866,9 @@ async function discoverLedgerResources(opts) {
|
|
|
21820
22866
|
}
|
|
21821
22867
|
const laList = await authedJson({
|
|
21822
22868
|
credential: credential2,
|
|
21823
|
-
scope:
|
|
22869
|
+
scope: ARM_SCOPE8,
|
|
21824
22870
|
method: "GET",
|
|
21825
|
-
url: `${
|
|
22871
|
+
url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.OperationalInsights/workspaces?api-version=${LA_API}`
|
|
21826
22872
|
}) ?? {};
|
|
21827
22873
|
const ws = laList.value ?? [];
|
|
21828
22874
|
const workspaceId = (ws.length === 1 ? ws[0] : ws.find((w) => w.properties?.customerId))?.properties?.customerId;
|
|
@@ -22116,7 +23162,7 @@ AppDependencies
|
|
|
22116
23162
|
};
|
|
22117
23163
|
}
|
|
22118
23164
|
function buildLiveSources(context, credential2) {
|
|
22119
|
-
const ledgerClient = new
|
|
23165
|
+
const ledgerClient = new TableClient3(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
|
|
22120
23166
|
const ledger = makeLedgerSource(
|
|
22121
23167
|
async (pk, sinceTs) => fetchLedgerRows(
|
|
22122
23168
|
{ workers: [pk], source: "all", from: sinceTs, to: "9999-12-31T23:59:59.999Z" },
|
|
@@ -22785,9 +23831,9 @@ ${colors.error(" " + why)}
|
|
|
22785
23831
|
}
|
|
22786
23832
|
|
|
22787
23833
|
// src/commands/bootstrap/launch.ts
|
|
22788
|
-
import * as
|
|
22789
|
-
import * as
|
|
22790
|
-
import * as
|
|
23834
|
+
import * as fs26 from "fs";
|
|
23835
|
+
import * as os13 from "os";
|
|
23836
|
+
import * as path27 from "path";
|
|
22791
23837
|
import { Command as Command47, Option as Option45 } from "clipanion";
|
|
22792
23838
|
init_errors();
|
|
22793
23839
|
|
|
@@ -22933,9 +23979,9 @@ async function kickInstaller(s) {
|
|
|
22933
23979
|
|
|
22934
23980
|
// src/lib/bootstrap-status.ts
|
|
22935
23981
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
22936
|
-
import * as
|
|
22937
|
-
import * as
|
|
22938
|
-
import * as
|
|
23982
|
+
import * as fs24 from "fs/promises";
|
|
23983
|
+
import * as os11 from "os";
|
|
23984
|
+
import * as path25 from "path";
|
|
22939
23985
|
init_errors();
|
|
22940
23986
|
var STATUS_CONTAINER = "status";
|
|
22941
23987
|
var STATUS_BLOB = "status.json";
|
|
@@ -22974,7 +24020,7 @@ async function readStatusBlob(opts) {
|
|
|
22974
24020
|
cause: e
|
|
22975
24021
|
});
|
|
22976
24022
|
}
|
|
22977
|
-
const tmpFile =
|
|
24023
|
+
const tmpFile = path25.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
|
|
22978
24024
|
try {
|
|
22979
24025
|
await runAz([
|
|
22980
24026
|
"storage",
|
|
@@ -22993,7 +24039,7 @@ async function readStatusBlob(opts) {
|
|
|
22993
24039
|
"--no-progress",
|
|
22994
24040
|
"--only-show-errors"
|
|
22995
24041
|
]);
|
|
22996
|
-
const raw = await
|
|
24042
|
+
const raw = await fs24.readFile(tmpFile, "utf8");
|
|
22997
24043
|
return JSON.parse(raw.trim());
|
|
22998
24044
|
} catch (e) {
|
|
22999
24045
|
throw new LocalCliError({
|
|
@@ -23003,34 +24049,34 @@ async function readStatusBlob(opts) {
|
|
|
23003
24049
|
cause: e
|
|
23004
24050
|
});
|
|
23005
24051
|
} finally {
|
|
23006
|
-
await
|
|
24052
|
+
await fs24.rm(tmpFile, { force: true });
|
|
23007
24053
|
}
|
|
23008
24054
|
}
|
|
23009
24055
|
|
|
23010
24056
|
// src/lib/bootstrap-state.ts
|
|
23011
|
-
import * as
|
|
23012
|
-
import * as
|
|
23013
|
-
import * as
|
|
24057
|
+
import * as fs25 from "fs/promises";
|
|
24058
|
+
import * as os12 from "os";
|
|
24059
|
+
import * as path26 from "path";
|
|
23014
24060
|
function stateDir(home) {
|
|
23015
|
-
return
|
|
24061
|
+
return path26.join(home, ".m8t-stack");
|
|
23016
24062
|
}
|
|
23017
24063
|
function statePath(home) {
|
|
23018
|
-
return
|
|
24064
|
+
return path26.join(stateDir(home), "bootstrap.json");
|
|
23019
24065
|
}
|
|
23020
|
-
async function readBootstrapState(home =
|
|
24066
|
+
async function readBootstrapState(home = os12.homedir()) {
|
|
23021
24067
|
try {
|
|
23022
|
-
const raw = await
|
|
24068
|
+
const raw = await fs25.readFile(statePath(home), "utf8");
|
|
23023
24069
|
return JSON.parse(raw);
|
|
23024
24070
|
} catch (e) {
|
|
23025
24071
|
if (e.code === "ENOENT") return null;
|
|
23026
24072
|
throw e;
|
|
23027
24073
|
}
|
|
23028
24074
|
}
|
|
23029
|
-
async function writeBootstrapState(state, home =
|
|
23030
|
-
await
|
|
24075
|
+
async function writeBootstrapState(state, home = os12.homedir()) {
|
|
24076
|
+
await fs25.mkdir(stateDir(home), { recursive: true });
|
|
23031
24077
|
const tmp = `${statePath(home)}.tmp`;
|
|
23032
|
-
await
|
|
23033
|
-
await
|
|
24078
|
+
await fs25.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
24079
|
+
await fs25.rename(tmp, statePath(home));
|
|
23034
24080
|
}
|
|
23035
24081
|
|
|
23036
24082
|
// src/commands/bootstrap/launch.ts
|
|
@@ -23070,12 +24116,12 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
23070
24116
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
23071
24117
|
const out = (m) => this.context.stderr.write(` ${colors.dim(m)}
|
|
23072
24118
|
`);
|
|
23073
|
-
const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds :
|
|
24119
|
+
const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path27.join(os13.homedir(), ".m8t-stack", "github-app.json");
|
|
23074
24120
|
let githubApp;
|
|
23075
|
-
if (
|
|
24121
|
+
if (fs26.existsSync(credsPath)) {
|
|
23076
24122
|
let parsed;
|
|
23077
24123
|
try {
|
|
23078
|
-
parsed = JSON.parse(
|
|
24124
|
+
parsed = JSON.parse(fs26.readFileSync(credsPath, "utf8"));
|
|
23079
24125
|
} catch {
|
|
23080
24126
|
throw new LocalCliError({
|
|
23081
24127
|
code: "GITHUB_APP_CREDS_INVALID",
|
|
@@ -23085,7 +24131,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
23085
24131
|
}
|
|
23086
24132
|
let pem;
|
|
23087
24133
|
try {
|
|
23088
|
-
pem =
|
|
24134
|
+
pem = fs26.readFileSync(parsed.pemPath, "utf8");
|
|
23089
24135
|
} catch {
|
|
23090
24136
|
throw new LocalCliError({
|
|
23091
24137
|
code: "GITHUB_APP_PEM_MISSING",
|
|
@@ -23205,7 +24251,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
23205
24251
|
typeof this.output === "string" ? this.output : void 0,
|
|
23206
24252
|
this.context.stdout
|
|
23207
24253
|
);
|
|
23208
|
-
const
|
|
24254
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23209
24255
|
const watch = this.watch === true;
|
|
23210
24256
|
for (; ; ) {
|
|
23211
24257
|
let doc;
|
|
@@ -23215,7 +24261,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
23215
24261
|
if (watch && e instanceof LocalCliError && e.code === "BOOTSTRAP_STATUS_UNREADABLE") {
|
|
23216
24262
|
if (mode === "pretty") this.context.stderr.write(` ${colors.dim("waiting for the installer to start\u2026")}
|
|
23217
24263
|
`);
|
|
23218
|
-
await
|
|
24264
|
+
await sleep3(1e4);
|
|
23219
24265
|
continue;
|
|
23220
24266
|
}
|
|
23221
24267
|
throw e;
|
|
@@ -23240,7 +24286,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
23240
24286
|
);
|
|
23241
24287
|
return 1;
|
|
23242
24288
|
}
|
|
23243
|
-
await
|
|
24289
|
+
await sleep3(1e4);
|
|
23244
24290
|
}
|
|
23245
24291
|
}
|
|
23246
24292
|
};
|
|
@@ -23410,17 +24456,17 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
|
|
|
23410
24456
|
};
|
|
23411
24457
|
|
|
23412
24458
|
// src/commands/bootstrap/finish.ts
|
|
23413
|
-
import * as
|
|
23414
|
-
import * as
|
|
23415
|
-
import * as
|
|
24459
|
+
import * as fs27 from "fs/promises";
|
|
24460
|
+
import * as os15 from "os";
|
|
24461
|
+
import * as path29 from "path";
|
|
23416
24462
|
import { Command as Command50, Option as Option48 } from "clipanion";
|
|
23417
24463
|
init_errors();
|
|
23418
24464
|
|
|
23419
24465
|
// src/lib/company-profile-seed.ts
|
|
23420
24466
|
import { spawn as spawn6 } from "child_process";
|
|
23421
|
-
import { closeSync, openSync, readFileSync as
|
|
23422
|
-
import * as
|
|
23423
|
-
import * as
|
|
24467
|
+
import { closeSync, openSync, readFileSync as readFileSync18 } from "fs";
|
|
24468
|
+
import * as os14 from "os";
|
|
24469
|
+
import * as path28 from "path";
|
|
23424
24470
|
init_errors();
|
|
23425
24471
|
|
|
23426
24472
|
// src/lib/onboarding-profile.ts
|
|
@@ -23601,9 +24647,9 @@ function composeFounderIdentityNote(id) {
|
|
|
23601
24647
|
|
|
23602
24648
|
// src/lib/company-profile-seed.ts
|
|
23603
24649
|
function readGithubAppCreds(credsPath) {
|
|
23604
|
-
const p = credsPath ??
|
|
24650
|
+
const p = credsPath ?? path28.join(os14.homedir(), ".m8t-stack", "github-app.json");
|
|
23605
24651
|
try {
|
|
23606
|
-
return JSON.parse(
|
|
24652
|
+
return JSON.parse(readFileSync18(p, "utf8"));
|
|
23607
24653
|
} catch {
|
|
23608
24654
|
return null;
|
|
23609
24655
|
}
|
|
@@ -23631,7 +24677,7 @@ async function resolveSeedContext(opts) {
|
|
|
23631
24677
|
async function applyProfileToBrain(args) {
|
|
23632
24678
|
const token = await mintInstallationTokenFromPem({
|
|
23633
24679
|
appId: args.appCreds.appId,
|
|
23634
|
-
privateKeyPem:
|
|
24680
|
+
privateKeyPem: readFileSync18(args.appCreds.pemPath, "utf8"),
|
|
23635
24681
|
installationId: args.appCreds.installationId,
|
|
23636
24682
|
fetchImpl: args.fetchImpl
|
|
23637
24683
|
});
|
|
@@ -23666,7 +24712,7 @@ async function applyProfileToBrain(args) {
|
|
|
23666
24712
|
});
|
|
23667
24713
|
}
|
|
23668
24714
|
function spawnDetachedSeedWatch() {
|
|
23669
|
-
const logPath =
|
|
24715
|
+
const logPath = path28.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
|
|
23670
24716
|
const fd = openSync(logPath, "a");
|
|
23671
24717
|
try {
|
|
23672
24718
|
const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
|
|
@@ -23765,9 +24811,9 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23765
24811
|
});
|
|
23766
24812
|
}
|
|
23767
24813
|
const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
|
|
23768
|
-
const markerDir =
|
|
23769
|
-
await
|
|
23770
|
-
await
|
|
24814
|
+
const markerDir = path29.join(os15.homedir(), ".m8t-stack");
|
|
24815
|
+
await fs27.mkdir(markerDir, { recursive: true });
|
|
24816
|
+
await fs27.writeFile(path29.join(markerDir, "repo-root"), `${repoRoot}
|
|
23771
24817
|
`, "utf8");
|
|
23772
24818
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
|
|
23773
24819
|
const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
|
|
@@ -23803,7 +24849,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23803
24849
|
}
|
|
23804
24850
|
let brainOrg = null;
|
|
23805
24851
|
try {
|
|
23806
|
-
const credsRaw = await
|
|
24852
|
+
const credsRaw = await fs27.readFile(path29.join(markerDir, "github-app.json"), "utf8");
|
|
23807
24853
|
const creds = JSON.parse(credsRaw);
|
|
23808
24854
|
brainOrg = typeof creds.org === "string" ? creds.org : null;
|
|
23809
24855
|
} catch {
|
|
@@ -23834,18 +24880,18 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
|
|
|
23834
24880
|
};
|
|
23835
24881
|
|
|
23836
24882
|
// src/commands/bootstrap/ui.ts
|
|
23837
|
-
import * as
|
|
23838
|
-
import * as
|
|
23839
|
-
import * as
|
|
24883
|
+
import * as fs29 from "fs";
|
|
24884
|
+
import * as os17 from "os";
|
|
24885
|
+
import * as path31 from "path";
|
|
23840
24886
|
import { Command as Command51, Option as Option49 } from "clipanion";
|
|
23841
|
-
import { DefaultAzureCredential as
|
|
24887
|
+
import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
|
|
23842
24888
|
init_errors();
|
|
23843
24889
|
|
|
23844
24890
|
// src/lib/bootstrap-ui.ts
|
|
23845
|
-
import * as
|
|
24891
|
+
import * as fs28 from "fs";
|
|
23846
24892
|
import * as net from "net";
|
|
23847
|
-
import * as
|
|
23848
|
-
import * as
|
|
24893
|
+
import * as os16 from "os";
|
|
24894
|
+
import * as path30 from "path";
|
|
23849
24895
|
import { spawn as spawn7 } from "child_process";
|
|
23850
24896
|
init_errors();
|
|
23851
24897
|
init_rbac();
|
|
@@ -23853,7 +24899,7 @@ async function resolveFoundryEndpointWithWait(args, opts = {}) {
|
|
|
23853
24899
|
const pollMs = opts.pollMs ?? 1e4;
|
|
23854
24900
|
const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
|
|
23855
24901
|
const deadline = Date.now() + timeoutMs;
|
|
23856
|
-
const
|
|
24902
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23857
24903
|
for (; ; ) {
|
|
23858
24904
|
try {
|
|
23859
24905
|
const p = await resolveFoundryProject({
|
|
@@ -23887,7 +24933,7 @@ async function resolveFoundryEndpointWithWait(args, opts = {}) {
|
|
|
23887
24933
|
});
|
|
23888
24934
|
}
|
|
23889
24935
|
opts.onWait?.("waiting for Foundry-create to finish\u2026");
|
|
23890
|
-
await
|
|
24936
|
+
await sleep3(pollMs);
|
|
23891
24937
|
}
|
|
23892
24938
|
}
|
|
23893
24939
|
}
|
|
@@ -23912,9 +24958,9 @@ async function ensureFounderFoundryRole(args) {
|
|
|
23912
24958
|
});
|
|
23913
24959
|
}
|
|
23914
24960
|
function writeWebEnvLocal(args) {
|
|
23915
|
-
const envPath =
|
|
23916
|
-
if (
|
|
23917
|
-
|
|
24961
|
+
const envPath = path30.join(args.repoRoot, "apps", "web", ".env.local");
|
|
24962
|
+
if (fs28.existsSync(envPath)) {
|
|
24963
|
+
fs28.copyFileSync(envPath, envPath + ".bak");
|
|
23918
24964
|
}
|
|
23919
24965
|
const body = [
|
|
23920
24966
|
"# Written by `m8t bootstrap ui` \u2014 onboarding chat-only run (Simple Stacey).",
|
|
@@ -23928,7 +24974,7 @@ function writeWebEnvLocal(args) {
|
|
|
23928
24974
|
"NEXT_PUBLIC_M8T_ONBOARDING=1",
|
|
23929
24975
|
""
|
|
23930
24976
|
].join("\n");
|
|
23931
|
-
|
|
24977
|
+
fs28.writeFileSync(envPath, body, "utf8");
|
|
23932
24978
|
return envPath;
|
|
23933
24979
|
}
|
|
23934
24980
|
function assertNodeVersion(versionString = process.version) {
|
|
@@ -23969,11 +25015,11 @@ async function installWebDeps(repoRoot) {
|
|
|
23969
25015
|
});
|
|
23970
25016
|
await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
|
|
23971
25017
|
}
|
|
23972
|
-
function onboardingUiPaths(home =
|
|
23973
|
-
const dir =
|
|
25018
|
+
function onboardingUiPaths(home = os16.homedir()) {
|
|
25019
|
+
const dir = path30.join(home, ".m8t-stack");
|
|
23974
25020
|
return {
|
|
23975
|
-
logPath:
|
|
23976
|
-
pidPath:
|
|
25021
|
+
logPath: path30.join(dir, "onboarding-ui.log"),
|
|
25022
|
+
pidPath: path30.join(dir, "onboarding-ui.pid")
|
|
23977
25023
|
};
|
|
23978
25024
|
}
|
|
23979
25025
|
async function serveOnboardingUiDetached(args) {
|
|
@@ -23997,8 +25043,8 @@ async function serveOnboardingUiDetached(args) {
|
|
|
23997
25043
|
if (await isPortOpen()) {
|
|
23998
25044
|
return { alreadyRunning: true, logPath };
|
|
23999
25045
|
}
|
|
24000
|
-
|
|
24001
|
-
const fd =
|
|
25046
|
+
fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
|
|
25047
|
+
const fd = fs28.openSync(logPath, "a");
|
|
24002
25048
|
const child = spawn7("pnpm", ["--filter", "web", "dev"], {
|
|
24003
25049
|
cwd: args.repoRoot,
|
|
24004
25050
|
detached: true,
|
|
@@ -24006,7 +25052,7 @@ async function serveOnboardingUiDetached(args) {
|
|
|
24006
25052
|
env: { ...process.env, PORT: args.port },
|
|
24007
25053
|
shell: false
|
|
24008
25054
|
});
|
|
24009
|
-
|
|
25055
|
+
fs28.closeSync(fd);
|
|
24010
25056
|
if (child.pid == null) {
|
|
24011
25057
|
throw new LocalCliError({
|
|
24012
25058
|
code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
|
|
@@ -24015,11 +25061,11 @@ async function serveOnboardingUiDetached(args) {
|
|
|
24015
25061
|
});
|
|
24016
25062
|
}
|
|
24017
25063
|
child.unref();
|
|
24018
|
-
|
|
25064
|
+
fs28.writeFileSync(pidPath, String(child.pid), "utf8");
|
|
24019
25065
|
const deadline = Date.now() + 45e3;
|
|
24020
|
-
const
|
|
25066
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24021
25067
|
while (Date.now() < deadline) {
|
|
24022
|
-
await
|
|
25068
|
+
await sleep3(500);
|
|
24023
25069
|
if (await isPortOpen()) break;
|
|
24024
25070
|
}
|
|
24025
25071
|
return { alreadyRunning: false, logPath };
|
|
@@ -24034,11 +25080,11 @@ function autoOpenOnboardingUi(url, open = tryOpenUrl) {
|
|
|
24034
25080
|
} catch {
|
|
24035
25081
|
}
|
|
24036
25082
|
}
|
|
24037
|
-
function stopOnboardingUi(home =
|
|
25083
|
+
function stopOnboardingUi(home = os16.homedir()) {
|
|
24038
25084
|
const { pidPath } = onboardingUiPaths(home);
|
|
24039
25085
|
let pidStr;
|
|
24040
25086
|
try {
|
|
24041
|
-
pidStr =
|
|
25087
|
+
pidStr = fs28.readFileSync(pidPath, "utf8").trim();
|
|
24042
25088
|
} catch {
|
|
24043
25089
|
return false;
|
|
24044
25090
|
}
|
|
@@ -24051,7 +25097,7 @@ function stopOnboardingUi(home = os15.homedir()) {
|
|
|
24051
25097
|
}
|
|
24052
25098
|
}
|
|
24053
25099
|
try {
|
|
24054
|
-
|
|
25100
|
+
fs28.unlinkSync(pidPath);
|
|
24055
25101
|
} catch {
|
|
24056
25102
|
}
|
|
24057
25103
|
return true;
|
|
@@ -24167,10 +25213,10 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
24167
25213
|
this.context.stderr.write(` ${colors.dim(m)}
|
|
24168
25214
|
`);
|
|
24169
25215
|
};
|
|
24170
|
-
if (
|
|
25216
|
+
if (fs29.existsSync(path31.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
|
|
24171
25217
|
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."));
|
|
24172
25218
|
}
|
|
24173
|
-
const credential2 = new
|
|
25219
|
+
const credential2 = new DefaultAzureCredential19();
|
|
24174
25220
|
const account = await getAzAccount();
|
|
24175
25221
|
assertNodeVersion();
|
|
24176
25222
|
out("waiting for Foundry (the installer's foundry-create phase)\u2026");
|
|
@@ -24273,7 +25319,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
24273
25319
|
const parsedTimeout = Number(rawTimeout);
|
|
24274
25320
|
const timeoutMin = rawTimeout !== "" && Number.isFinite(parsedTimeout) ? parsedTimeout : 20;
|
|
24275
25321
|
const deadline = Date.now() + timeoutMin * 6e4;
|
|
24276
|
-
const
|
|
25322
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24277
25323
|
for (; ; ) {
|
|
24278
25324
|
const token = await getFoundryToken();
|
|
24279
25325
|
const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
|
|
@@ -24296,7 +25342,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
24296
25342
|
}
|
|
24297
25343
|
this.context.stderr.write(` ${colors.dim("waiting for the questionnaire to complete\u2026")}
|
|
24298
25344
|
`);
|
|
24299
|
-
await
|
|
25345
|
+
await sleep3(2e4);
|
|
24300
25346
|
}
|
|
24301
25347
|
}
|
|
24302
25348
|
};
|