@m8t-stack/cli 0.2.15 → 0.2.17
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 +1649 -607
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -197,6 +197,13 @@ var init_brain_eval = __esm({
|
|
|
197
197
|
}
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
+
// ../../packages/api-contract/dist/esm/founder-benchmark.js
|
|
201
|
+
var init_founder_benchmark = __esm({
|
|
202
|
+
"../../packages/api-contract/dist/esm/founder-benchmark.js"() {
|
|
203
|
+
"use strict";
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
200
207
|
// ../../packages/api-contract/dist/esm/index.js
|
|
201
208
|
var init_esm = __esm({
|
|
202
209
|
"../../packages/api-contract/dist/esm/index.js"() {
|
|
@@ -212,6 +219,7 @@ var init_esm = __esm({
|
|
|
212
219
|
init_brain_link();
|
|
213
220
|
init_a2a_card();
|
|
214
221
|
init_brain_eval();
|
|
222
|
+
init_founder_benchmark();
|
|
215
223
|
}
|
|
216
224
|
});
|
|
217
225
|
|
|
@@ -806,6 +814,7 @@ async function createCoderVersion(args) {
|
|
|
806
814
|
environment_variables: args.env
|
|
807
815
|
};
|
|
808
816
|
const metadata = {
|
|
817
|
+
...Object.fromEntries(Object.entries(args.metadata).filter(([, v2]) => v2 !== null)),
|
|
809
818
|
source: args.metadata.source,
|
|
810
819
|
kind: args.metadata.kind,
|
|
811
820
|
persona: args.metadata.persona
|
|
@@ -878,6 +887,7 @@ async function createPromptVersion(args) {
|
|
|
878
887
|
persona: args.metadata.persona
|
|
879
888
|
};
|
|
880
889
|
if (args.metadata.personaVersion !== null) metadata.personaVersion = args.metadata.personaVersion;
|
|
890
|
+
if (args.metadata.fillableFieldValues !== void 0) metadata.fillableFieldValues = args.metadata.fillableFieldValues;
|
|
881
891
|
const version = await project.agents.createVersion(args.name, definition, { metadata });
|
|
882
892
|
const v = version.version;
|
|
883
893
|
if (v === void 0) {
|
|
@@ -932,6 +942,24 @@ async function grantFoundryUser(args) {
|
|
|
932
942
|
// RoleAssignmentExists — already granted
|
|
933
943
|
});
|
|
934
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
|
+
}
|
|
935
963
|
async function listRoleAssignments(credential2, scope, filter) {
|
|
936
964
|
const q = `api-version=${RA_API}${filter ? `&$filter=${encodeURIComponent(filter)}` : ""}`;
|
|
937
965
|
const url = `${ARM2}${scope}/providers/Microsoft.Authorization/roleAssignments?${q}`;
|
|
@@ -1049,7 +1077,7 @@ async function resolveKeyVaultResourceId(args) {
|
|
|
1049
1077
|
}
|
|
1050
1078
|
return id;
|
|
1051
1079
|
}
|
|
1052
|
-
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;
|
|
1053
1081
|
var init_rbac = __esm({
|
|
1054
1082
|
"src/lib/rbac.ts"() {
|
|
1055
1083
|
"use strict";
|
|
@@ -1063,6 +1091,7 @@ var init_rbac = __esm({
|
|
|
1063
1091
|
OWNER_ROLE_ID = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635";
|
|
1064
1092
|
USER_ACCESS_ADMIN_ROLE_ID = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9";
|
|
1065
1093
|
RBAC_ADMIN_ROLE_ID = "f58310d9-a9f6-439a-9e8d-f62e7b41a168";
|
|
1094
|
+
STORAGE_TABLE_DATA_CONTRIBUTOR_ROLE_ID = "0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3";
|
|
1066
1095
|
ASSIGNING_ROLE_IDS = [OWNER_ROLE_ID, USER_ACCESS_ADMIN_ROLE_ID, RBAC_ADMIN_ROLE_ID];
|
|
1067
1096
|
KV_SECRETS_USER_ROLE_ID = "4633458b-17de-408a-b874-0445c86b69e6";
|
|
1068
1097
|
CONTRIBUTOR_ROLE_ID = "b24988ac-6180-42a0-ab88-20f7382dd24c";
|
|
@@ -1198,7 +1227,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1198
1227
|
import { Builtins, Cli } from "clipanion";
|
|
1199
1228
|
|
|
1200
1229
|
// src/lib/package-version.ts
|
|
1201
|
-
var CLI_VERSION = "0.2.
|
|
1230
|
+
var CLI_VERSION = "0.2.17";
|
|
1202
1231
|
|
|
1203
1232
|
// src/lib/render-error.ts
|
|
1204
1233
|
init_errors();
|
|
@@ -1512,7 +1541,8 @@ var colors = {
|
|
|
1512
1541
|
hint: (s) => pc.dim(s),
|
|
1513
1542
|
success: (s) => pc.green(s),
|
|
1514
1543
|
field: (s) => pc.bold(s),
|
|
1515
|
-
dim: (s) => pc.dim(s)
|
|
1544
|
+
dim: (s) => pc.dim(s),
|
|
1545
|
+
warn: (s) => pc.yellow(s)
|
|
1516
1546
|
};
|
|
1517
1547
|
|
|
1518
1548
|
// ../../node_modules/.pnpm/openai@6.44.0_ws@8.21.0_zod@4.4.3/node_modules/openai/internal/tslib.mjs
|
|
@@ -3296,12 +3326,12 @@ function encodeURIPath(str4) {
|
|
|
3296
3326
|
return str4.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
|
|
3297
3327
|
}
|
|
3298
3328
|
var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3299
|
-
var createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
3329
|
+
var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(statics, ...params) {
|
|
3300
3330
|
if (statics.length === 1)
|
|
3301
3331
|
return statics[0];
|
|
3302
3332
|
let postPath = false;
|
|
3303
3333
|
const invalidSegments = [];
|
|
3304
|
-
const
|
|
3334
|
+
const path33 = statics.reduce((previousValue, currentValue, index) => {
|
|
3305
3335
|
if (/[?#]/.test(currentValue)) {
|
|
3306
3336
|
postPath = true;
|
|
3307
3337
|
}
|
|
@@ -3318,7 +3348,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path29(sta
|
|
|
3318
3348
|
}
|
|
3319
3349
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
3320
3350
|
}, "");
|
|
3321
|
-
const pathOnly =
|
|
3351
|
+
const pathOnly = path33.split(/[?#]/, 1)[0];
|
|
3322
3352
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
3323
3353
|
let match;
|
|
3324
3354
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -3339,10 +3369,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path29(sta
|
|
|
3339
3369
|
}, "");
|
|
3340
3370
|
throw new OpenAIError(`Path parameters result in path with invalid segments:
|
|
3341
3371
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
3342
|
-
${
|
|
3372
|
+
${path33}
|
|
3343
3373
|
${underline}`);
|
|
3344
3374
|
}
|
|
3345
|
-
return
|
|
3375
|
+
return path33;
|
|
3346
3376
|
};
|
|
3347
3377
|
var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
3348
3378
|
|
|
@@ -10859,9 +10889,9 @@ var OpenAI = class {
|
|
|
10859
10889
|
this.apiKey = token;
|
|
10860
10890
|
return true;
|
|
10861
10891
|
}
|
|
10862
|
-
buildURL(
|
|
10892
|
+
buildURL(path32, query, defaultBaseURL) {
|
|
10863
10893
|
const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
10864
|
-
const url = isAbsoluteURL(
|
|
10894
|
+
const url = isAbsoluteURL(path32) ? new URL(path32) : new URL(baseURL + (baseURL.endsWith("/") && path32.startsWith("/") ? path32.slice(1) : path32));
|
|
10865
10895
|
const defaultQuery = this.defaultQuery();
|
|
10866
10896
|
const pathQuery = Object.fromEntries(url.searchParams);
|
|
10867
10897
|
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
|
|
@@ -10889,24 +10919,24 @@ var OpenAI = class {
|
|
|
10889
10919
|
*/
|
|
10890
10920
|
async prepareRequest(request, { url, options }) {
|
|
10891
10921
|
}
|
|
10892
|
-
get(
|
|
10893
|
-
return this.methodRequest("get",
|
|
10922
|
+
get(path32, opts) {
|
|
10923
|
+
return this.methodRequest("get", path32, opts);
|
|
10894
10924
|
}
|
|
10895
|
-
post(
|
|
10896
|
-
return this.methodRequest("post",
|
|
10925
|
+
post(path32, opts) {
|
|
10926
|
+
return this.methodRequest("post", path32, opts);
|
|
10897
10927
|
}
|
|
10898
|
-
patch(
|
|
10899
|
-
return this.methodRequest("patch",
|
|
10928
|
+
patch(path32, opts) {
|
|
10929
|
+
return this.methodRequest("patch", path32, opts);
|
|
10900
10930
|
}
|
|
10901
|
-
put(
|
|
10902
|
-
return this.methodRequest("put",
|
|
10931
|
+
put(path32, opts) {
|
|
10932
|
+
return this.methodRequest("put", path32, opts);
|
|
10903
10933
|
}
|
|
10904
|
-
delete(
|
|
10905
|
-
return this.methodRequest("delete",
|
|
10934
|
+
delete(path32, opts) {
|
|
10935
|
+
return this.methodRequest("delete", path32, opts);
|
|
10906
10936
|
}
|
|
10907
|
-
methodRequest(method,
|
|
10937
|
+
methodRequest(method, path32, opts) {
|
|
10908
10938
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
10909
|
-
return { method, path:
|
|
10939
|
+
return { method, path: path32, ...opts2 };
|
|
10910
10940
|
}));
|
|
10911
10941
|
}
|
|
10912
10942
|
request(options, remainingRetries = null) {
|
|
@@ -11028,8 +11058,8 @@ var OpenAI = class {
|
|
|
11028
11058
|
}));
|
|
11029
11059
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
11030
11060
|
}
|
|
11031
|
-
getAPIList(
|
|
11032
|
-
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 });
|
|
11033
11063
|
}
|
|
11034
11064
|
requestAPIList(Page2, options) {
|
|
11035
11065
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -11123,8 +11153,8 @@ var OpenAI = class {
|
|
|
11123
11153
|
}
|
|
11124
11154
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
11125
11155
|
const options = { ...inputOptions };
|
|
11126
|
-
const { method, path:
|
|
11127
|
-
const url = this.buildURL(
|
|
11156
|
+
const { method, path: path32, query, defaultBaseURL } = options;
|
|
11157
|
+
const url = this.buildURL(path32, query, defaultBaseURL);
|
|
11128
11158
|
if ("timeout" in options)
|
|
11129
11159
|
validatePositiveInteger("timeout", options.timeout);
|
|
11130
11160
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -12126,10 +12156,10 @@ var BindListCommand = class extends M8tCommand {
|
|
|
12126
12156
|
const qs = new URLSearchParams();
|
|
12127
12157
|
if (this.channel) qs.set("channel", this.channel);
|
|
12128
12158
|
if (this.status) qs.set("status", this.status);
|
|
12129
|
-
const
|
|
12159
|
+
const path32 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
|
|
12130
12160
|
const data = await apiCall(
|
|
12131
12161
|
{ gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
|
|
12132
|
-
{ method: "GET", path:
|
|
12162
|
+
{ method: "GET", path: path32 },
|
|
12133
12163
|
(msg) => this.context.stderr.write(msg + "\n")
|
|
12134
12164
|
);
|
|
12135
12165
|
if (mode === "json") {
|
|
@@ -13330,8 +13360,8 @@ ${r.stderr}`;
|
|
|
13330
13360
|
message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
|
|
13331
13361
|
});
|
|
13332
13362
|
},
|
|
13333
|
-
getFile: async (
|
|
13334
|
-
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}`]);
|
|
13335
13365
|
return r.exitCode === 0 ? r.stdout : null;
|
|
13336
13366
|
},
|
|
13337
13367
|
isEmpty: async () => {
|
|
@@ -13780,12 +13810,12 @@ function stripBrainLoader(instructions) {
|
|
|
13780
13810
|
if (h !== -1) return instructions.slice(0, h).replace(/\s+$/, "");
|
|
13781
13811
|
return instructions.replace(/\s+$/, "");
|
|
13782
13812
|
}
|
|
13783
|
-
function appendBrainLoader(base,
|
|
13813
|
+
function appendBrainLoader(base, loaderText2) {
|
|
13784
13814
|
const clean2 = stripBrainLoader(base);
|
|
13785
13815
|
return `${clean2}
|
|
13786
13816
|
|
|
13787
13817
|
${LOADER_START}
|
|
13788
|
-
${
|
|
13818
|
+
${loaderText2.replace(/\s+$/, "")}
|
|
13789
13819
|
${LOADER_END}
|
|
13790
13820
|
`;
|
|
13791
13821
|
}
|
|
@@ -13826,9 +13856,32 @@ function hasA2aSnippet(instructions) {
|
|
|
13826
13856
|
|
|
13827
13857
|
// src/lib/brain-link.ts
|
|
13828
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
|
|
13829
13883
|
init_brain_yaml_mirror();
|
|
13830
13884
|
var FOUNDRY_ARM_API = "2025-04-01-preview";
|
|
13831
|
-
var FOUNDRY_DATA_SCOPE = "https://ai.azure.com/.default";
|
|
13832
13885
|
var ARM_SCOPE4 = "https://management.azure.com/.default";
|
|
13833
13886
|
var MCP_TOOL_NAMES = [
|
|
13834
13887
|
"get_file_contents",
|
|
@@ -14035,10 +14088,6 @@ ${text.slice(0, 300)}`
|
|
|
14035
14088
|
}
|
|
14036
14089
|
}
|
|
14037
14090
|
async function createBrainEnabledVersion(args) {
|
|
14038
|
-
const fndToken = await args.credential.getToken(FOUNDRY_DATA_SCOPE);
|
|
14039
|
-
if (!fndToken?.token) {
|
|
14040
|
-
throw new LocalCliError({ code: "FOUNDRY_AUTH", message: "Could not acquire Foundry data-plane token" });
|
|
14041
|
-
}
|
|
14042
14091
|
const otherTools = (args.currentDefinition.tools ?? []).filter(
|
|
14043
14092
|
(t) => !(t.type === "mcp" && t.server_label === "brain")
|
|
14044
14093
|
);
|
|
@@ -14059,25 +14108,13 @@ async function createBrainEnabledVersion(args) {
|
|
|
14059
14108
|
...args.currentMetadata,
|
|
14060
14109
|
brain: args.brainLinkJson
|
|
14061
14110
|
};
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14111
|
+
return createAgentVersion({
|
|
14112
|
+
credential: args.credential,
|
|
14113
|
+
projectEndpoint: args.projectEndpoint,
|
|
14114
|
+
agentName: args.agentName,
|
|
14115
|
+
definition,
|
|
14116
|
+
metadata
|
|
14067
14117
|
});
|
|
14068
|
-
if (!res.ok) {
|
|
14069
|
-
const text = await res.text();
|
|
14070
|
-
throw new LocalCliError({
|
|
14071
|
-
code: "AGENT_CREATE_VERSION_FAILED",
|
|
14072
|
-
message: `POST ${url}: HTTP ${res.status.toString()}
|
|
14073
|
-
${text.slice(0, 500)}`
|
|
14074
|
-
});
|
|
14075
|
-
}
|
|
14076
|
-
const data = await res.json();
|
|
14077
|
-
if (!data.version) {
|
|
14078
|
-
throw new LocalCliError({ code: "AGENT_CREATE_VERSION_NO_VERSION", message: `createVersion returned no version` });
|
|
14079
|
-
}
|
|
14080
|
-
return data.version;
|
|
14081
14118
|
}
|
|
14082
14119
|
|
|
14083
14120
|
// src/lib/brain-seed.ts
|
|
@@ -14117,8 +14154,8 @@ function isM8tBrainMarker(yamlText) {
|
|
|
14117
14154
|
}
|
|
14118
14155
|
if (!parsed || typeof parsed !== "object") return false;
|
|
14119
14156
|
const root = parsed;
|
|
14120
|
-
const
|
|
14121
|
-
return
|
|
14157
|
+
const isObj3 = (v) => typeof v === "object" && v !== null;
|
|
14158
|
+
return isObj3(root.engine) || isObj3(root.processes) || isObj3(root.link);
|
|
14122
14159
|
}
|
|
14123
14160
|
async function createBlankRepo(args) {
|
|
14124
14161
|
const res = await fetch(`https://api.github.com/orgs/${args.org}/repos`, {
|
|
@@ -14272,7 +14309,7 @@ function appRepoProbe(args) {
|
|
|
14272
14309
|
const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
14273
14310
|
return res.status;
|
|
14274
14311
|
},
|
|
14275
|
-
getFile: (
|
|
14312
|
+
getFile: (path32) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path32, fetchImpl: args.fetchImpl }),
|
|
14276
14313
|
isEmpty: async () => {
|
|
14277
14314
|
const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
14278
14315
|
return res.status === 404;
|
|
@@ -14591,7 +14628,9 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14591
14628
|
repoRoot,
|
|
14592
14629
|
installationId: resolvedInstallationId,
|
|
14593
14630
|
skipIfLinked: true,
|
|
14594
|
-
onProgress: progress
|
|
14631
|
+
onProgress: progress,
|
|
14632
|
+
subscriptionId,
|
|
14633
|
+
project
|
|
14595
14634
|
});
|
|
14596
14635
|
} finally {
|
|
14597
14636
|
fs10.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -15282,7 +15321,7 @@ async function deployPromptAdvisor(args) {
|
|
|
15282
15321
|
model,
|
|
15283
15322
|
instructions,
|
|
15284
15323
|
reasoningEffort,
|
|
15285
|
-
metadata: { persona: args.persona, personaVersion }
|
|
15324
|
+
metadata: { persona: args.persona, personaVersion, fillableFieldValues: JSON.stringify(valuesMap) }
|
|
15286
15325
|
});
|
|
15287
15326
|
}
|
|
15288
15327
|
|
|
@@ -15408,7 +15447,7 @@ async function awaitDataPlaneReady(opts) {
|
|
|
15408
15447
|
const consecutive = opts.consecutive ?? 3;
|
|
15409
15448
|
const attempts = opts.attempts ?? 60;
|
|
15410
15449
|
const intervalMs = opts.intervalMs ?? 5e3;
|
|
15411
|
-
const
|
|
15450
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15412
15451
|
let streak = 0;
|
|
15413
15452
|
for (let i = 1; i <= attempts; i++) {
|
|
15414
15453
|
const r = await opts.probe();
|
|
@@ -15430,7 +15469,7 @@ async function awaitDataPlaneReady(opts) {
|
|
|
15430
15469
|
streak = 0;
|
|
15431
15470
|
opts.onProgress?.(`data-plane not ready (${forcedRetryable ? `status ${(r.status ?? 0).toString()}` : cls.category}), waiting\u2026`);
|
|
15432
15471
|
}
|
|
15433
|
-
if (i < attempts) await
|
|
15472
|
+
if (i < attempts) await sleep3(intervalMs);
|
|
15434
15473
|
}
|
|
15435
15474
|
return { ready: false, attempts };
|
|
15436
15475
|
}
|
|
@@ -15551,7 +15590,6 @@ function str2(v) {
|
|
|
15551
15590
|
|
|
15552
15591
|
// src/lib/a2a-enable.ts
|
|
15553
15592
|
var FOUNDRY_ARM_API3 = "2025-04-01-preview";
|
|
15554
|
-
var FOUNDRY_DATA_SCOPE3 = "https://ai.azure.com/.default";
|
|
15555
15593
|
var ARM_SCOPE6 = "https://management.azure.com/.default";
|
|
15556
15594
|
var A2A_TOOLS = ["discover_workers", "invoke_worker", "check_delegation"];
|
|
15557
15595
|
async function enableA2a(args) {
|
|
@@ -15572,7 +15610,7 @@ async function enableA2a(args) {
|
|
|
15572
15610
|
if (current.definition.kind === "hosted") {
|
|
15573
15611
|
progress("Registering the hosted target (metadata only)\u2026");
|
|
15574
15612
|
const metadata2 = { ...current.metadata ?? {}, a2a: "true", a2aCard: a2aCardJson };
|
|
15575
|
-
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" } });
|
|
15576
15614
|
return { mode: "target", foundryVersion: foundryVersion2 };
|
|
15577
15615
|
}
|
|
15578
15616
|
if (current.definition.kind !== "prompt") {
|
|
@@ -15597,7 +15635,7 @@ async function enableA2a(args) {
|
|
|
15597
15635
|
a2aCard: a2aCardJson,
|
|
15598
15636
|
a2aBearerHash: bearerHash
|
|
15599
15637
|
};
|
|
15600
|
-
const foundryVersion = await
|
|
15638
|
+
const foundryVersion = await createAgentVersion({ credential: args.credential, projectEndpoint: args.projectEndpoint, agentName: args.agentName, definition, metadata });
|
|
15601
15639
|
return { mode: "caller", connectionName, foundryVersion };
|
|
15602
15640
|
}
|
|
15603
15641
|
async function disableA2a(args) {
|
|
@@ -15614,7 +15652,7 @@ async function disableA2a(args) {
|
|
|
15614
15652
|
delete metadata.a2a;
|
|
15615
15653
|
delete metadata.a2aCard;
|
|
15616
15654
|
delete metadata.a2aBearerHash;
|
|
15617
|
-
const foundryVersion = await
|
|
15655
|
+
const foundryVersion = await createAgentVersion({ credential: args.credential, projectEndpoint: args.projectEndpoint, agentName: args.agentName, definition, metadata });
|
|
15618
15656
|
progress("Deleting the A2A connection\u2026");
|
|
15619
15657
|
await deleteConnection2({ credential: args.credential, projectArmId: args.projectArmId, connectionName });
|
|
15620
15658
|
return { connectionName, foundryVersion };
|
|
@@ -15656,20 +15694,6 @@ async function deleteConnection2(args) {
|
|
|
15656
15694
|
await fetch(url, { method: "DELETE", headers: { Authorization: `Bearer ${token.token}` } }).catch(() => {
|
|
15657
15695
|
});
|
|
15658
15696
|
}
|
|
15659
|
-
async function createVersion(args) {
|
|
15660
|
-
const token = await args.credential.getToken(FOUNDRY_DATA_SCOPE3);
|
|
15661
|
-
if (!token?.token) throw new LocalCliError({ code: "FOUNDRY_AUTH", message: "Could not acquire Foundry data-plane token" });
|
|
15662
|
-
const url = `${args.projectEndpoint}/agents/${args.agentName}/versions?api-version=v1`;
|
|
15663
|
-
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 }) });
|
|
15664
|
-
if (!res.ok) {
|
|
15665
|
-
const text = await res.text();
|
|
15666
|
-
throw new LocalCliError({ code: "A2A_CREATE_VERSION_FAILED", message: `POST ${url}: HTTP ${res.status.toString()}
|
|
15667
|
-
${text.slice(0, 500)}` });
|
|
15668
|
-
}
|
|
15669
|
-
const data = await res.json();
|
|
15670
|
-
if (!data.version) throw new LocalCliError({ code: "A2A_CREATE_VERSION_NO_VERSION", message: "createVersion returned no version" });
|
|
15671
|
-
return data.version;
|
|
15672
|
-
}
|
|
15673
15697
|
|
|
15674
15698
|
// src/lib/agent-remove.ts
|
|
15675
15699
|
init_foundry_agents();
|
|
@@ -16267,6 +16291,7 @@ import * as path16 from "path";
|
|
|
16267
16291
|
import { Command as Command28, Option as Option26 } from "clipanion";
|
|
16268
16292
|
import { DefaultAzureCredential as DefaultAzureCredential13 } from "@azure/identity";
|
|
16269
16293
|
init_errors();
|
|
16294
|
+
init_foundry_agent_get();
|
|
16270
16295
|
|
|
16271
16296
|
// src/lib/preconditions.ts
|
|
16272
16297
|
init_errors();
|
|
@@ -16472,7 +16497,7 @@ init_errors();
|
|
|
16472
16497
|
async function deployHostedWorker(args) {
|
|
16473
16498
|
const onProgress = args.onProgress ?? ((_m) => {
|
|
16474
16499
|
});
|
|
16475
|
-
const
|
|
16500
|
+
const sleep3 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
16476
16501
|
const now = args.now ?? (() => Date.now());
|
|
16477
16502
|
const timeout = args.pollTimeoutMs ?? 8 * 60 * 1e3;
|
|
16478
16503
|
const interval = args.pollIntervalMs ?? 1e4;
|
|
@@ -16524,7 +16549,7 @@ async function deployHostedWorker(args) {
|
|
|
16524
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."
|
|
16525
16550
|
});
|
|
16526
16551
|
}
|
|
16527
|
-
await
|
|
16552
|
+
await sleep3(interval);
|
|
16528
16553
|
}
|
|
16529
16554
|
}
|
|
16530
16555
|
|
|
@@ -16718,20 +16743,6 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
16718
16743
|
hint: "small=0.5/1Gi \xB7 medium=1/2Gi \xB7 large=2/4Gi"
|
|
16719
16744
|
});
|
|
16720
16745
|
}
|
|
16721
|
-
const env = {
|
|
16722
|
-
MODEL_DEPLOYMENT_NAME: this.modelDeployment ?? (typeof this.brain === "string" ? "gpt-5-mini" : DEFAULT_MODEL)
|
|
16723
|
-
};
|
|
16724
|
-
for (const pair of this.env ?? []) {
|
|
16725
|
-
const eq = pair.indexOf("=");
|
|
16726
|
-
if (eq <= 0) {
|
|
16727
|
-
throw new LocalCliError({
|
|
16728
|
-
code: "USAGE",
|
|
16729
|
-
message: `Invalid --env '${pair}'. Expected KEY=VALUE.`,
|
|
16730
|
-
hint: "Example: --env M8T_CODER_MAX_ITERATIONS=20"
|
|
16731
|
-
});
|
|
16732
|
-
}
|
|
16733
|
-
env[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
16734
|
-
}
|
|
16735
16746
|
const persona = this.persona ?? "coding-agent";
|
|
16736
16747
|
const imageInput = this.image ?? DEFAULT_IMAGE;
|
|
16737
16748
|
const repoRef = imageInput.includes("/") ? imageInput : `${DEFAULT_REGISTRY}/${imageInput}`;
|
|
@@ -16752,6 +16763,30 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
16752
16763
|
interactive,
|
|
16753
16764
|
endpoint: this.endpoint
|
|
16754
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
|
+
}
|
|
16755
16790
|
const staged = await stagePublicImageIfNeeded({ image: requestedImage, project });
|
|
16756
16791
|
for (const n of staged.notes) {
|
|
16757
16792
|
this.context.stderr.write(`${colors.hint("note:")} ${n}
|
|
@@ -16796,7 +16831,7 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
16796
16831
|
cpu: preset.cpu,
|
|
16797
16832
|
memory: preset.memory,
|
|
16798
16833
|
env,
|
|
16799
|
-
metadata: { source: "m8t-stack-POC", kind: "hosted", persona: personaName, personaVersion },
|
|
16834
|
+
metadata: { ...currentMetadata, source: "m8t-stack-POC", kind: "hosted", persona: personaName, personaVersion },
|
|
16800
16835
|
onProgress
|
|
16801
16836
|
});
|
|
16802
16837
|
if (typeof this.brain === "string") {
|
|
@@ -17328,6 +17363,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
17328
17363
|
|
|
17329
17364
|
// src/commands/platform/status.ts
|
|
17330
17365
|
import { Command as Command31, Option as Option29 } from "clipanion";
|
|
17366
|
+
import { DefaultAzureCredential as DefaultAzureCredential16 } from "@azure/identity";
|
|
17331
17367
|
|
|
17332
17368
|
// src/lib/platform-update.ts
|
|
17333
17369
|
init_errors();
|
|
@@ -17403,20 +17439,6 @@ function planUpdate(opts) {
|
|
|
17403
17439
|
}
|
|
17404
17440
|
return { kind: "roll", current: currentTag, available, target: available, repo: opts.imageRepo };
|
|
17405
17441
|
}
|
|
17406
|
-
function summarizePlan(plan) {
|
|
17407
|
-
switch (plan.kind) {
|
|
17408
|
-
case "noop":
|
|
17409
|
-
return { current: plan.current, available: plan.available, verdict: "up to date" };
|
|
17410
|
-
case "roll":
|
|
17411
|
-
return { current: plan.current, available: plan.available, verdict: "update available" };
|
|
17412
|
-
case "no-versions":
|
|
17413
|
-
return { current: plan.current, available: "-", verdict: "no published versions found" };
|
|
17414
|
-
case "refuse-byoc":
|
|
17415
|
-
return { current: plan.current, available: "-", verdict: `private-ACR image (${plan.currentRepo}) \u2014 not tracked` };
|
|
17416
|
-
case "tag-not-found":
|
|
17417
|
-
return { current: plan.current, available: plan.requested, verdict: `tag ${plan.requested} not published` };
|
|
17418
|
-
}
|
|
17419
|
-
}
|
|
17420
17442
|
async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
17421
17443
|
if (!repo.startsWith("ghcr.io/")) {
|
|
17422
17444
|
throw new LocalCliError({
|
|
@@ -17425,8 +17447,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
17425
17447
|
hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
|
|
17426
17448
|
});
|
|
17427
17449
|
}
|
|
17428
|
-
const
|
|
17429
|
-
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`);
|
|
17430
17452
|
if (!tokenRes.ok) {
|
|
17431
17453
|
throw new LocalCliError({
|
|
17432
17454
|
code: "PLATFORM_GHCR_TOKEN_FAILED",
|
|
@@ -17442,7 +17464,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
17442
17464
|
hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
|
|
17443
17465
|
});
|
|
17444
17466
|
}
|
|
17445
|
-
const tagsRes = await fetchImpl(`https://ghcr.io/v2/${
|
|
17467
|
+
const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path32}/tags/list`, {
|
|
17446
17468
|
headers: { Authorization: `Bearer ${token}` }
|
|
17447
17469
|
});
|
|
17448
17470
|
if (!tagsRes.ok) {
|
|
@@ -17455,205 +17477,1370 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
17455
17477
|
return body.tags ?? [];
|
|
17456
17478
|
}
|
|
17457
17479
|
|
|
17458
|
-
// src/
|
|
17459
|
-
|
|
17460
|
-
static paths = [["platform", "status"]];
|
|
17461
|
-
static usage = Command31.Usage({
|
|
17462
|
-
description: "Show the deployed platform image vs the newest published version.",
|
|
17463
|
-
details: "Discovers the live gateway, reads its current image tag, and compares it to the newest vX.Y.Z published on the public GHCR repo. Read-only \u2014 never rolls a revision."
|
|
17464
|
-
});
|
|
17465
|
-
subscription = Option29.String("--subscription");
|
|
17466
|
-
imageRepo = Option29.String("--image-repo", DEFAULT_IMAGE_REPO);
|
|
17467
|
-
output = Option29.String("--output");
|
|
17468
|
-
resourceGroup = Option29.String("--resource-group", {
|
|
17469
|
-
description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
17470
|
-
});
|
|
17471
|
-
async executeCommand() {
|
|
17472
|
-
const mode = resolveOutputMode(
|
|
17473
|
-
this.output,
|
|
17474
|
-
this.context.stdout
|
|
17475
|
-
);
|
|
17476
|
-
const ctx = await resolveGatewayContext({
|
|
17477
|
-
interactive: mode !== "json",
|
|
17478
|
-
subscriptionId: this.subscription,
|
|
17479
|
-
resourceGroup: this.resourceGroup
|
|
17480
|
-
});
|
|
17481
|
-
const { resourceGroup, name } = parseContainerAppResourceId(ctx.containerAppResourceId);
|
|
17482
|
-
const currentImage = (await runAz([
|
|
17483
|
-
"containerapp",
|
|
17484
|
-
"show",
|
|
17485
|
-
"-g",
|
|
17486
|
-
resourceGroup,
|
|
17487
|
-
"-n",
|
|
17488
|
-
name,
|
|
17489
|
-
"--query",
|
|
17490
|
-
"properties.template.containers[0].image",
|
|
17491
|
-
"-o",
|
|
17492
|
-
"tsv"
|
|
17493
|
-
])).trim();
|
|
17494
|
-
const tags = await fetchPublicTags(this.imageRepo);
|
|
17495
|
-
const plan = planUpdate({ currentImage, availableTags: tags, imageRepo: this.imageRepo });
|
|
17496
|
-
const s = summarizePlan(plan);
|
|
17497
|
-
if (mode === "json") {
|
|
17498
|
-
this.context.stdout.write(renderJson({ gateway: name, resourceGroup, ...s, kind: plan.kind }) + "\n");
|
|
17499
|
-
return 0;
|
|
17500
|
-
}
|
|
17501
|
-
this.context.stdout.write(
|
|
17502
|
-
renderKeyValueBlock([
|
|
17503
|
-
{ key: "gateway", value: name },
|
|
17504
|
-
{ key: "current", value: s.current },
|
|
17505
|
-
{ key: "available", value: s.available },
|
|
17506
|
-
{ key: "status", value: s.verdict }
|
|
17507
|
-
]) + "\n"
|
|
17508
|
-
);
|
|
17509
|
-
return 0;
|
|
17510
|
-
}
|
|
17511
|
-
};
|
|
17480
|
+
// src/lib/release-channel.ts
|
|
17481
|
+
import * as fs18 from "fs";
|
|
17512
17482
|
|
|
17513
|
-
// src/
|
|
17514
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
17517
|
-
|
|
17518
|
-
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
|
|
17529
|
-
check = Option30.Boolean("--check", false);
|
|
17530
|
-
yes = Option30.Boolean("--yes", false);
|
|
17531
|
-
output = Option30.String("--output");
|
|
17532
|
-
async executeCommand() {
|
|
17533
|
-
const mode = resolveOutputMode(
|
|
17534
|
-
this.output,
|
|
17535
|
-
this.context.stdout
|
|
17536
|
-
);
|
|
17537
|
-
const interactive = this.context.stdout.isTTY === true;
|
|
17538
|
-
const log = (msg) => {
|
|
17539
|
-
if (mode !== "json") this.context.stdout.write(msg + "\n");
|
|
17540
|
-
};
|
|
17541
|
-
const ctx = await resolveGatewayContext({
|
|
17542
|
-
interactive: mode !== "json",
|
|
17543
|
-
subscriptionId: this.subscription,
|
|
17544
|
-
resourceGroup: this.resourceGroup
|
|
17545
|
-
});
|
|
17546
|
-
const { resourceGroup, name } = parseContainerAppResourceId(ctx.containerAppResourceId);
|
|
17547
|
-
const currentImage = (await runAz([
|
|
17548
|
-
"containerapp",
|
|
17549
|
-
"show",
|
|
17550
|
-
"-g",
|
|
17551
|
-
resourceGroup,
|
|
17552
|
-
"-n",
|
|
17553
|
-
name,
|
|
17554
|
-
"--query",
|
|
17555
|
-
"properties.template.containers[0].image",
|
|
17556
|
-
"-o",
|
|
17557
|
-
"tsv"
|
|
17558
|
-
])).trim();
|
|
17559
|
-
const tags = await fetchPublicTags(this.imageRepo);
|
|
17560
|
-
const plan = planUpdate({
|
|
17561
|
-
currentImage,
|
|
17562
|
-
availableTags: tags,
|
|
17563
|
-
imageRepo: this.imageRepo,
|
|
17564
|
-
to: this.to
|
|
17565
|
-
});
|
|
17566
|
-
const s = summarizePlan(plan);
|
|
17567
|
-
if (plan.kind === "refuse-byoc") {
|
|
17568
|
-
throw new LocalCliError({
|
|
17569
|
-
code: "PLATFORM_BYOC_NOT_TRACKED",
|
|
17570
|
-
message: `This gateway runs a private-ACR image (${plan.currentRepo}), not the tracked public repo (${plan.trackedRepo}).`,
|
|
17571
|
-
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."
|
|
17572
|
-
});
|
|
17483
|
+
// src/lib/release-manifest.ts
|
|
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)) return ["manifest is not an object"];
|
|
17492
|
+
if (m.schemaVersion !== 1) errors.push("schemaVersion must be the integer 1");
|
|
17493
|
+
const p = m.platform;
|
|
17494
|
+
if (!isObj2(p)) {
|
|
17495
|
+
errors.push("platform is required");
|
|
17496
|
+
} else {
|
|
17497
|
+
for (const f of ["version", "tag", "releasedAt", "commit", "notes", "notesUrl"]) {
|
|
17498
|
+
if (typeof p[f] !== "string" || p[f].length === 0) errors.push(`platform.${f} is required`);
|
|
17573
17499
|
}
|
|
17574
|
-
if (
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
message: `Tag ${plan.requested} is not published on ${this.imageRepo}.`,
|
|
17578
|
-
hint: "Run 'm8t platform status' to see the newest available version, or omit --to."
|
|
17579
|
-
});
|
|
17500
|
+
if (!SEVERITIES.includes(p.severity)) errors.push(`platform.severity must be one of ${SEVERITIES.join("|")}`);
|
|
17501
|
+
if (!(typeof p.previousVersion === "string" || p.previousVersion === null)) {
|
|
17502
|
+
errors.push("platform.previousVersion must be a string or null");
|
|
17580
17503
|
}
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17504
|
+
}
|
|
17505
|
+
const c = m.components;
|
|
17506
|
+
if (!isObj2(c)) {
|
|
17507
|
+
errors.push("components is required");
|
|
17508
|
+
return errors;
|
|
17509
|
+
}
|
|
17510
|
+
for (const key2 of IMAGE_KEYS) {
|
|
17511
|
+
const img = c[key2];
|
|
17512
|
+
if (!isObj2(img)) {
|
|
17513
|
+
errors.push(`components.${key2} is required`);
|
|
17514
|
+
continue;
|
|
17587
17515
|
}
|
|
17588
|
-
if (
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
{ key: "gateway", value: name },
|
|
17592
|
-
{ key: "current", value: s.current },
|
|
17593
|
-
{ key: "available", value: s.available },
|
|
17594
|
-
{ key: "status", value: s.verdict }
|
|
17595
|
-
]) + "\n"
|
|
17596
|
-
);
|
|
17516
|
+
if (img.kind !== "image") errors.push(`components.${key2}.kind must be "image"`);
|
|
17517
|
+
for (const f of ["ref", "tag", "version"]) {
|
|
17518
|
+
if (typeof img[f] !== "string" || img[f].length === 0) errors.push(`components.${key2}.${f} is required`);
|
|
17597
17519
|
}
|
|
17598
|
-
if (
|
|
17599
|
-
|
|
17600
|
-
else log(colors.success("already up to date \u2713"));
|
|
17601
|
-
return 0;
|
|
17520
|
+
if (typeof img.digest !== "string" || !/^sha256:[0-9a-f]+$/.test(img.digest)) {
|
|
17521
|
+
errors.push(`components.${key2}.digest must be a sha256:\u2026 digest`);
|
|
17602
17522
|
}
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17523
|
+
}
|
|
17524
|
+
const cli2 = c.cli;
|
|
17525
|
+
if (!isObj2(cli2) || cli2.kind !== "npm") {
|
|
17526
|
+
errors.push("components.cli (kind: npm) is required");
|
|
17527
|
+
} else {
|
|
17528
|
+
for (const f of ["package", "version", "min", "recommended"]) {
|
|
17529
|
+
if (typeof cli2[f] !== "string" || cli2[f].length === 0) errors.push(`components.cli.${f} is required`);
|
|
17607
17530
|
}
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17531
|
+
}
|
|
17532
|
+
const personas = c.personas;
|
|
17533
|
+
if (!isObj2(personas) || !isObj2(personas.items)) {
|
|
17534
|
+
errors.push("components.personas.items is required");
|
|
17535
|
+
} else {
|
|
17536
|
+
for (const [name, item] of Object.entries(personas.items)) {
|
|
17537
|
+
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
17538
|
+
errors.push(`components.personas.items.${name}.path is required`);
|
|
17539
|
+
}
|
|
17540
|
+
if (!isObj2(item) || typeof item.treeSha !== "string" || item.treeSha.length < 4) {
|
|
17541
|
+
errors.push(`components.personas.items.${name}.treeSha is required`);
|
|
17616
17542
|
}
|
|
17617
17543
|
}
|
|
17618
|
-
log(`rolling to ${plan.target}\u2026`);
|
|
17619
|
-
await runAz([
|
|
17620
|
-
"containerapp",
|
|
17621
|
-
"update",
|
|
17622
|
-
"-n",
|
|
17623
|
-
name,
|
|
17624
|
-
"-g",
|
|
17625
|
-
resourceGroup,
|
|
17626
|
-
"--image",
|
|
17627
|
-
`${plan.repo}:${plan.target}`
|
|
17628
|
-
]);
|
|
17629
|
-
if (mode === "json") this.context.stdout.write(renderJson({ ...s, kind: plan.kind, rolled: true, rolledTo: plan.target }) + "\n");
|
|
17630
|
-
else log(colors.success(`rolled ${plan.current} \u2192 ${plan.target} \u2713`));
|
|
17631
|
-
return 0;
|
|
17632
17544
|
}
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
"
|
|
17650
|
-
|
|
17651
|
-
|
|
17652
|
-
"
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17545
|
+
const seeds = c.brainSeeds;
|
|
17546
|
+
if (!isObj2(seeds) || !isObj2(seeds.items)) {
|
|
17547
|
+
errors.push("components.brainSeeds.items is required");
|
|
17548
|
+
} else {
|
|
17549
|
+
for (const [name, item] of Object.entries(seeds.items)) {
|
|
17550
|
+
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
17551
|
+
errors.push(`components.brainSeeds.items.${name}.path is required`);
|
|
17552
|
+
}
|
|
17553
|
+
if (!isObj2(item) || !isObj2(item.subtrees)) {
|
|
17554
|
+
errors.push(`components.brainSeeds.items.${name}.subtrees is required`);
|
|
17555
|
+
}
|
|
17556
|
+
}
|
|
17557
|
+
}
|
|
17558
|
+
const infra = c.infra;
|
|
17559
|
+
if (infra !== void 0) {
|
|
17560
|
+
if (!isObj2(infra) || infra.kind !== "infra") {
|
|
17561
|
+
errors.push('components.infra.kind must be "infra"');
|
|
17562
|
+
}
|
|
17563
|
+
if (!isObj2(infra) || typeof infra.treeSha !== "string" || infra.treeSha.length < 4) {
|
|
17564
|
+
errors.push("components.infra.treeSha is required");
|
|
17565
|
+
}
|
|
17566
|
+
}
|
|
17567
|
+
return errors;
|
|
17568
|
+
}
|
|
17569
|
+
|
|
17570
|
+
// src/lib/release-channel.ts
|
|
17571
|
+
init_errors();
|
|
17572
|
+
var CHANNEL_LATEST_URL = "https://github.com/m8t-run/m8t/releases/latest/download/manifest.json";
|
|
17573
|
+
function channelUrlForVersion(tag) {
|
|
17574
|
+
return `https://github.com/m8t-run/m8t/releases/download/${tag}/manifest.json`;
|
|
17575
|
+
}
|
|
17576
|
+
async function fetchManifest(source, deps = {}) {
|
|
17577
|
+
const readFile8 = deps.readFile ?? ((p) => fs18.readFileSync(p, "utf8"));
|
|
17578
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17579
|
+
const ghToken = deps.ghToken ?? getGhToken;
|
|
17580
|
+
let raw;
|
|
17581
|
+
if ("file" in source) {
|
|
17582
|
+
try {
|
|
17583
|
+
raw = readFile8(source.file);
|
|
17584
|
+
} catch (e) {
|
|
17585
|
+
throw new LocalCliError({ code: "PLATFORM_MANIFEST_READ_FAILED", message: `Could not read manifest file '${source.file}': ${e.message}` });
|
|
17586
|
+
}
|
|
17587
|
+
} else {
|
|
17588
|
+
const url = "url" in source ? source.url : source.version ? channelUrlForVersion(source.version) : CHANNEL_LATEST_URL;
|
|
17589
|
+
let token;
|
|
17590
|
+
try {
|
|
17591
|
+
token = await ghToken();
|
|
17592
|
+
} catch {
|
|
17593
|
+
token = void 0;
|
|
17594
|
+
}
|
|
17595
|
+
const res = await fetchImpl(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
17596
|
+
if (!res.ok) {
|
|
17597
|
+
throw new LocalCliError({
|
|
17598
|
+
code: "PLATFORM_MANIFEST_FETCH_FAILED",
|
|
17599
|
+
message: `GET ${url} returned HTTP ${res.status.toString()}.`,
|
|
17600
|
+
hint: "The release channel is auth-gated until the repo is public. Run 'gh auth login', or pass --manifest-file / --manifest-url."
|
|
17601
|
+
});
|
|
17602
|
+
}
|
|
17603
|
+
raw = JSON.stringify(await res.json());
|
|
17604
|
+
}
|
|
17605
|
+
let parsed;
|
|
17606
|
+
try {
|
|
17607
|
+
parsed = JSON.parse(raw);
|
|
17608
|
+
} catch (e) {
|
|
17609
|
+
throw new LocalCliError({ code: "PLATFORM_MANIFEST_PARSE_FAILED", message: `Manifest is not valid JSON: ${e.message}` });
|
|
17610
|
+
}
|
|
17611
|
+
const errors = validateManifest(parsed);
|
|
17612
|
+
if (errors.length > 0) {
|
|
17613
|
+
throw new LocalCliError({
|
|
17614
|
+
code: "PLATFORM_MANIFEST_INVALID",
|
|
17615
|
+
message: `Fetched manifest failed validation:
|
|
17616
|
+
- ${errors.join("\n - ")}`
|
|
17617
|
+
});
|
|
17618
|
+
}
|
|
17619
|
+
return parsed;
|
|
17620
|
+
}
|
|
17621
|
+
|
|
17622
|
+
// src/lib/release-content.ts
|
|
17623
|
+
import * as fs19 from "fs";
|
|
17624
|
+
import * as os8 from "os";
|
|
17625
|
+
import * as path18 from "path";
|
|
17626
|
+
import { execFileSync } from "child_process";
|
|
17627
|
+
init_errors();
|
|
17628
|
+
var OWNER_REPO = "m8t-run/m8t";
|
|
17629
|
+
function parseTreeResponse(body) {
|
|
17630
|
+
const map = /* @__PURE__ */ new Map();
|
|
17631
|
+
for (const e of body.tree ?? []) if (e.type === "tree") map.set(e.path, e.sha);
|
|
17632
|
+
return { treeShaOf: (p) => map.get(p.replace(/\/$/, "")) };
|
|
17633
|
+
}
|
|
17634
|
+
async function fetchRepoTree(commit, deps = {}) {
|
|
17635
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17636
|
+
const ghToken = deps.ghToken ?? getGhToken;
|
|
17637
|
+
let token;
|
|
17638
|
+
try {
|
|
17639
|
+
token = await ghToken();
|
|
17640
|
+
} catch {
|
|
17641
|
+
token = void 0;
|
|
17642
|
+
}
|
|
17643
|
+
const url = `https://api.github.com/repos/${OWNER_REPO}/git/trees/${commit}?recursive=1`;
|
|
17644
|
+
const res = await fetchImpl(url, {
|
|
17645
|
+
headers: { Accept: "application/vnd.github+json", ...token ? { Authorization: `Bearer ${token}` } : {} }
|
|
17646
|
+
});
|
|
17647
|
+
if (!res.ok) {
|
|
17648
|
+
throw new LocalCliError({ code: "PLATFORM_TREE_FETCH_FAILED", message: `GET ${url} \u2192 HTTP ${res.status.toString()}.` });
|
|
17649
|
+
}
|
|
17650
|
+
return parseTreeResponse(await res.json());
|
|
17651
|
+
}
|
|
17652
|
+
function verifyPersonaTrees(manifest, tree) {
|
|
17653
|
+
const out = [];
|
|
17654
|
+
for (const [name, item] of Object.entries(manifest.components.personas.items)) {
|
|
17655
|
+
const actual = tree.treeShaOf(item.path);
|
|
17656
|
+
if (actual !== void 0 && actual !== item.treeSha) {
|
|
17657
|
+
out.push(`persona '${name}' (${item.path}): manifest pins ${item.treeSha} but commit tree has ${actual}`);
|
|
17658
|
+
}
|
|
17659
|
+
}
|
|
17660
|
+
return out;
|
|
17661
|
+
}
|
|
17662
|
+
async function resolveReleaseContent(manifest, opts = {}) {
|
|
17663
|
+
if (opts.contentDir) {
|
|
17664
|
+
if (!fs19.existsSync(path18.join(opts.contentDir, "personas"))) {
|
|
17665
|
+
throw new LocalCliError({ code: "PLATFORM_CONTENT_DIR_INVALID", message: `--content-dir '${opts.contentDir}' has no personas/ \u2014 not a repo checkout.` });
|
|
17666
|
+
}
|
|
17667
|
+
return opts.contentDir;
|
|
17668
|
+
}
|
|
17669
|
+
const commit = manifest.platform.commit;
|
|
17670
|
+
const cacheRoot = opts.cacheRoot ?? path18.join(os8.homedir(), ".m8t-stack", "cache");
|
|
17671
|
+
const dest = path18.join(cacheRoot, `release-${commit}`);
|
|
17672
|
+
if (fs19.existsSync(path18.join(dest, "personas"))) return dest;
|
|
17673
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
17674
|
+
const ghToken = opts.ghToken ?? getGhToken;
|
|
17675
|
+
let token;
|
|
17676
|
+
try {
|
|
17677
|
+
token = await ghToken();
|
|
17678
|
+
} catch {
|
|
17679
|
+
token = void 0;
|
|
17680
|
+
}
|
|
17681
|
+
const url = `https://api.github.com/repos/${OWNER_REPO}/tarball/${commit}`;
|
|
17682
|
+
const res = await fetchImpl(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
17683
|
+
if (!res.ok) {
|
|
17684
|
+
throw new LocalCliError({ code: "PLATFORM_TARBALL_FETCH_FAILED", message: `GET ${url} \u2192 HTTP ${res.status.toString()}.` });
|
|
17685
|
+
}
|
|
17686
|
+
fs19.mkdirSync(cacheRoot, { recursive: true });
|
|
17687
|
+
const tmp = fs19.mkdtempSync(path18.join(cacheRoot, `.dl-${commit.slice(0, 8)}-`));
|
|
17688
|
+
try {
|
|
17689
|
+
const tgz = path18.join(tmp, "src.tar.gz");
|
|
17690
|
+
fs19.writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
|
|
17691
|
+
const unpacked = path18.join(tmp, "unpacked");
|
|
17692
|
+
fs19.mkdirSync(unpacked);
|
|
17693
|
+
extractTarGz(tgz, unpacked);
|
|
17694
|
+
try {
|
|
17695
|
+
fs19.renameSync(unpacked, dest);
|
|
17696
|
+
} catch (e) {
|
|
17697
|
+
const err = e;
|
|
17698
|
+
const isBenignRace = (err.code === "EEXIST" || err.code === "ENOTEMPTY") && fs19.existsSync(path18.join(dest, "personas"));
|
|
17699
|
+
if (!isBenignRace) {
|
|
17700
|
+
throw new LocalCliError({
|
|
17701
|
+
code: "PLATFORM_CACHE_PUBLISH_FAILED",
|
|
17702
|
+
message: `Could not publish release cache to ${dest}: ${err.message}`
|
|
17703
|
+
});
|
|
17704
|
+
}
|
|
17705
|
+
}
|
|
17706
|
+
} finally {
|
|
17707
|
+
fs19.rmSync(tmp, { recursive: true, force: true });
|
|
17708
|
+
}
|
|
17709
|
+
return dest;
|
|
17710
|
+
}
|
|
17711
|
+
function extractTarGz(tgz, into) {
|
|
17712
|
+
execFileSync("tar", ["-xzf", tgz, "-C", into, "--strip-components=1"]);
|
|
17713
|
+
}
|
|
17714
|
+
|
|
17715
|
+
// src/lib/platform-stamp.ts
|
|
17716
|
+
import { TableClient, AzureNamedKeyCredential } from "@azure/data-tables";
|
|
17717
|
+
init_errors();
|
|
17718
|
+
init_rbac();
|
|
17719
|
+
|
|
17720
|
+
// src/lib/platform-storage-discovery.ts
|
|
17721
|
+
init_http();
|
|
17722
|
+
init_errors();
|
|
17723
|
+
var ARM3 = "https://management.azure.com";
|
|
17724
|
+
var ARM_SCOPE7 = "https://management.azure.com/.default";
|
|
17725
|
+
var STORAGE_API = "2023-05-01";
|
|
17726
|
+
async function discoverStampStorage(opts) {
|
|
17727
|
+
const list = await authedJson({
|
|
17728
|
+
credential: opts.credential,
|
|
17729
|
+
scope: ARM_SCOPE7,
|
|
17730
|
+
method: "GET",
|
|
17731
|
+
url: `${ARM3}/subscriptions/${opts.subscriptionId}/resourceGroups/${opts.resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API}`
|
|
17732
|
+
}) ?? {};
|
|
17733
|
+
const accounts = (list.value ?? []).filter((a) => a.properties?.primaryEndpoints?.table);
|
|
17734
|
+
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);
|
|
17735
|
+
const tableEndpoint = chosen?.properties?.primaryEndpoints?.table?.replace(/\/$/, "");
|
|
17736
|
+
if (!tableEndpoint || !chosen?.id || !chosen.name) {
|
|
17737
|
+
throw new LocalCliError({
|
|
17738
|
+
code: "PLATFORM_STAMP_STORAGE_NOT_FOUND",
|
|
17739
|
+
message: `No table-capable storage account found in resource group ${opts.resourceGroup}.`,
|
|
17740
|
+
hint: `Found: ${accounts.map((a) => a.name).join(", ") || "(none)"}.`
|
|
17741
|
+
});
|
|
17742
|
+
}
|
|
17743
|
+
return { tableEndpoint, accountResourceId: chosen.id, accountName: chosen.name };
|
|
17744
|
+
}
|
|
17745
|
+
|
|
17746
|
+
// src/lib/platform-stamp.ts
|
|
17747
|
+
var TABLE = "Metadata";
|
|
17748
|
+
var PK = "system";
|
|
17749
|
+
var RK = "platform";
|
|
17750
|
+
function stampToEntity(s) {
|
|
17751
|
+
return {
|
|
17752
|
+
partitionKey: PK,
|
|
17753
|
+
rowKey: RK,
|
|
17754
|
+
platformVersion: s.platformVersion,
|
|
17755
|
+
updatedAt: s.updatedAt,
|
|
17756
|
+
lastResult: s.lastResult,
|
|
17757
|
+
value: JSON.stringify(s)
|
|
17758
|
+
};
|
|
17759
|
+
}
|
|
17760
|
+
function entityToStamp(e) {
|
|
17761
|
+
const raw = e.value;
|
|
17762
|
+
if (typeof raw !== "string") return null;
|
|
17763
|
+
try {
|
|
17764
|
+
return JSON.parse(raw);
|
|
17765
|
+
} catch {
|
|
17766
|
+
return null;
|
|
17767
|
+
}
|
|
17768
|
+
}
|
|
17769
|
+
async function makeSharedKeyClient(opts) {
|
|
17770
|
+
const rg = /\/resourceGroups\/([^/]+)\//i.exec(opts.accountResourceId)?.[1];
|
|
17771
|
+
if (!rg) return null;
|
|
17772
|
+
const key2 = (await runAz(["storage", "account", "keys", "list", "--account-name", opts.accountName, "-g", rg, "--query", "[0].value", "-o", "tsv"])).trim();
|
|
17773
|
+
if (!key2) return null;
|
|
17774
|
+
return new TableClient(opts.tableEndpoint, TABLE, new AzureNamedKeyCredential(opts.accountName, key2));
|
|
17775
|
+
}
|
|
17776
|
+
async function writeStamp(opts) {
|
|
17777
|
+
const { tableEndpoint, accountResourceId, accountName } = await discoverStampStorage(opts);
|
|
17778
|
+
const entity = stampToEntity(opts.stamp);
|
|
17779
|
+
const aad = new TableClient(tableEndpoint, TABLE, opts.credential);
|
|
17780
|
+
await aad.createTable().catch(() => void 0);
|
|
17781
|
+
try {
|
|
17782
|
+
await aad.upsertEntity(entity, "Replace");
|
|
17783
|
+
return;
|
|
17784
|
+
} catch (e) {
|
|
17785
|
+
if (!is403(e)) throw e;
|
|
17786
|
+
}
|
|
17787
|
+
opts.onProgress?.("writing the platform stamp with the account key \u2014 your identity lacks the Storage Table data role.");
|
|
17788
|
+
const shared = await makeSharedKeyClient({ tableEndpoint, accountResourceId, accountName });
|
|
17789
|
+
if (shared) {
|
|
17790
|
+
await shared.createTable().catch(() => void 0);
|
|
17791
|
+
await shared.upsertEntity(entity, "Replace");
|
|
17792
|
+
return;
|
|
17793
|
+
}
|
|
17794
|
+
opts.onProgress?.("shared-key access is disabled \u2014 granting Storage Table Data Contributor to your identity (one-time)\u2026");
|
|
17795
|
+
try {
|
|
17796
|
+
const oid = await getCallerObjectId();
|
|
17797
|
+
await grantStorageTableDataContributor({
|
|
17798
|
+
credential: opts.credential,
|
|
17799
|
+
subscriptionId: opts.subscriptionId,
|
|
17800
|
+
scope: accountResourceId,
|
|
17801
|
+
principalId: oid
|
|
17802
|
+
});
|
|
17803
|
+
} catch (e) {
|
|
17804
|
+
throw new LocalCliError({
|
|
17805
|
+
code: "PLATFORM_STAMP_NO_ACCESS",
|
|
17806
|
+
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.`,
|
|
17807
|
+
hint: "Grant yourself 'Storage Table Data Contributor' on the storage account and retry.",
|
|
17808
|
+
cause: e
|
|
17809
|
+
});
|
|
17810
|
+
}
|
|
17811
|
+
for (let i = 0; i < 6; i++) {
|
|
17812
|
+
await sleep2(1e4);
|
|
17813
|
+
try {
|
|
17814
|
+
await aad.upsertEntity(entity, "Replace");
|
|
17815
|
+
return;
|
|
17816
|
+
} catch (e) {
|
|
17817
|
+
if (!is403(e)) throw e;
|
|
17818
|
+
}
|
|
17819
|
+
}
|
|
17820
|
+
throw new LocalCliError({
|
|
17821
|
+
code: "PLATFORM_STAMP_NO_ACCESS",
|
|
17822
|
+
message: `Cannot write the platform stamp: granted 'Storage Table Data Contributor' on ${accountName}, but the data-plane role has not propagated yet.`,
|
|
17823
|
+
hint: "The role assignment persists \u2014 retry 'm8t platform update' in a few minutes."
|
|
17824
|
+
});
|
|
17825
|
+
}
|
|
17826
|
+
async function readStamp(opts) {
|
|
17827
|
+
const { tableEndpoint, accountResourceId, accountName } = await discoverStampStorage(opts);
|
|
17828
|
+
const aad = new TableClient(tableEndpoint, TABLE, opts.credential);
|
|
17829
|
+
try {
|
|
17830
|
+
const row = await aad.getEntity(PK, RK);
|
|
17831
|
+
return entityToStamp(row);
|
|
17832
|
+
} catch (e) {
|
|
17833
|
+
if (is404(e)) return null;
|
|
17834
|
+
if (!is403(e)) return null;
|
|
17835
|
+
}
|
|
17836
|
+
try {
|
|
17837
|
+
const shared = await makeSharedKeyClient({ tableEndpoint, accountResourceId, accountName });
|
|
17838
|
+
if (!shared) return null;
|
|
17839
|
+
const row = await shared.getEntity(PK, RK);
|
|
17840
|
+
return entityToStamp(row);
|
|
17841
|
+
} catch {
|
|
17842
|
+
return null;
|
|
17843
|
+
}
|
|
17844
|
+
}
|
|
17845
|
+
function is403(e) {
|
|
17846
|
+
return e.statusCode === 403;
|
|
17847
|
+
}
|
|
17848
|
+
function is404(e) {
|
|
17849
|
+
return e.statusCode === 404;
|
|
17850
|
+
}
|
|
17851
|
+
function sleep2(ms) {
|
|
17852
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
17853
|
+
}
|
|
17854
|
+
|
|
17855
|
+
// src/lib/platform-converge.ts
|
|
17856
|
+
import * as fs21 from "fs";
|
|
17857
|
+
import * as path20 from "path";
|
|
17858
|
+
import { parse as parseYaml10 } from "yaml";
|
|
17859
|
+
|
|
17860
|
+
// src/lib/deploy.ts
|
|
17861
|
+
import * as fs20 from "fs/promises";
|
|
17862
|
+
import * as os9 from "os";
|
|
17863
|
+
import * as path19 from "path";
|
|
17864
|
+
init_errors();
|
|
17865
|
+
var FOUNDRY_TRACING_MODES = ["project", "account", "skip"];
|
|
17866
|
+
function parseFoundryTracingMode(v) {
|
|
17867
|
+
if (v === void 0) return void 0;
|
|
17868
|
+
if (!FOUNDRY_TRACING_MODES.includes(v)) {
|
|
17869
|
+
throw new LocalCliError({
|
|
17870
|
+
code: "DEPLOY_INVALID_TRACING_MODE",
|
|
17871
|
+
message: `Invalid --foundry-tracing value: '${v}'.`,
|
|
17872
|
+
hint: `Use one of: ${FOUNDRY_TRACING_MODES.join(", ")}.`
|
|
17873
|
+
});
|
|
17874
|
+
}
|
|
17875
|
+
return v;
|
|
17876
|
+
}
|
|
17877
|
+
function buildBicepParams(p) {
|
|
17878
|
+
const params = [
|
|
17879
|
+
`location=${p.location}`,
|
|
17880
|
+
`suffix=${p.suffix}`,
|
|
17881
|
+
`imageRef=${p.imageRef}`,
|
|
17882
|
+
`tenantId=${p.tenantId}`,
|
|
17883
|
+
`clientId=${p.clientId}`,
|
|
17884
|
+
`foundryResourceId=${p.foundryResourceId}`,
|
|
17885
|
+
`foundryProjectEndpoint=${p.foundryProjectEndpoint}`,
|
|
17886
|
+
`acrPullIdentityResourceId=${p.acrPullIdentityResourceId}`,
|
|
17887
|
+
`acrResourceId=${p.acrResourceId}`
|
|
17888
|
+
];
|
|
17889
|
+
if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
|
|
17890
|
+
return params;
|
|
17891
|
+
}
|
|
17892
|
+
async function resolveRepoRoot2(home = os9.homedir()) {
|
|
17893
|
+
const marker = path19.join(home, ".m8t-stack", "repo-root");
|
|
17894
|
+
try {
|
|
17895
|
+
const root = (await fs20.readFile(marker, "utf8")).trim();
|
|
17896
|
+
if (!root) throw new Error("empty");
|
|
17897
|
+
return root;
|
|
17898
|
+
} catch {
|
|
17899
|
+
throw new LocalCliError({
|
|
17900
|
+
code: "DEPLOY_NO_REPO_ROOT",
|
|
17901
|
+
message: "~/.m8t-stack/repo-root marker is missing or empty.",
|
|
17902
|
+
hint: "Run 'm8t install' from an m8t-stack checkout (it writes the marker), or run 'm8t deploy' from the repo."
|
|
17903
|
+
});
|
|
17904
|
+
}
|
|
17905
|
+
}
|
|
17906
|
+
async function resolveFoundryResourceId(endpoint, explicit) {
|
|
17907
|
+
if (explicit) return explicit;
|
|
17908
|
+
const m = /^https:\/\/([^.]+)\.services\.ai\.azure\.com/.exec(endpoint);
|
|
17909
|
+
if (!m) {
|
|
17910
|
+
throw new LocalCliError({
|
|
17911
|
+
code: "DEPLOY_FOUNDRY_ENDPOINT_BAD",
|
|
17912
|
+
message: `Cannot parse the Foundry account name from endpoint: ${endpoint}`,
|
|
17913
|
+
hint: "Pass --foundry-resource-id <ARM-id> explicitly."
|
|
17914
|
+
});
|
|
17915
|
+
}
|
|
17916
|
+
const accounts = JSON.parse(
|
|
17917
|
+
await runAz([
|
|
17918
|
+
"cognitiveservices",
|
|
17919
|
+
"account",
|
|
17920
|
+
"list",
|
|
17921
|
+
"--query",
|
|
17922
|
+
`[?name=='${m[1]}'].id`,
|
|
17923
|
+
"--output",
|
|
17924
|
+
"json"
|
|
17925
|
+
])
|
|
17926
|
+
);
|
|
17927
|
+
if (accounts.length === 0) {
|
|
17928
|
+
throw new LocalCliError({
|
|
17929
|
+
code: "DEPLOY_FOUNDRY_NOT_FOUND",
|
|
17930
|
+
message: `No Foundry account named '${m[1]}' in the active subscription.`,
|
|
17931
|
+
hint: "Check the endpoint, or pass --foundry-resource-id explicitly."
|
|
17932
|
+
});
|
|
17933
|
+
}
|
|
17934
|
+
if (accounts.length > 1) {
|
|
17935
|
+
throw new LocalCliError({
|
|
17936
|
+
code: "DEPLOY_FOUNDRY_AMBIGUOUS",
|
|
17937
|
+
message: `Multiple Foundry accounts named '${m[1]}' \u2014 ambiguous.`,
|
|
17938
|
+
hint: "Pass --foundry-resource-id <ARM-id> to disambiguate."
|
|
17939
|
+
});
|
|
17940
|
+
}
|
|
17941
|
+
return accounts[0];
|
|
17942
|
+
}
|
|
17943
|
+
async function resolveAcrResourceId(opts) {
|
|
17944
|
+
if (opts.explicit) return opts.explicit;
|
|
17945
|
+
const host = opts.imageRef.split("/")[0];
|
|
17946
|
+
if (!host.endsWith(".azurecr.io")) return "";
|
|
17947
|
+
const acrName = host.split(".")[0];
|
|
17948
|
+
try {
|
|
17949
|
+
const id = (await runAz(["acr", "show", "-n", acrName, "--query", "id", "-o", "tsv"])).trim();
|
|
17950
|
+
if (!id) throw new Error("empty id");
|
|
17951
|
+
return id;
|
|
17952
|
+
} catch (e) {
|
|
17953
|
+
if (e instanceof LocalCliError) throw e;
|
|
17954
|
+
throw new LocalCliError({
|
|
17955
|
+
code: "DEPLOY_ACR_NOT_FOUND",
|
|
17956
|
+
message: `Cannot resolve the ACR '${acrName}' for image ${opts.imageRef}.`,
|
|
17957
|
+
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>."
|
|
17958
|
+
});
|
|
17959
|
+
}
|
|
17960
|
+
}
|
|
17961
|
+
async function ensureResourceGroup(name, location) {
|
|
17962
|
+
const existing = JSON.parse(
|
|
17963
|
+
await runAz(["group", "show", "--name", name, "--output", "json"]).catch(() => "") || "null"
|
|
17964
|
+
);
|
|
17965
|
+
if (existing) return;
|
|
17966
|
+
await runAz(["group", "create", "--name", name, "--location", location, "--tags", "m8t-stack=deployment"]);
|
|
17967
|
+
}
|
|
17968
|
+
function resolveAcrPullIdentity(opts) {
|
|
17969
|
+
return opts.explicit ?? "";
|
|
17970
|
+
}
|
|
17971
|
+
async function runBicepDeployment(opts) {
|
|
17972
|
+
const bicepPath = path19.join(opts.repoRoot, "deploy", "main.bicep");
|
|
17973
|
+
const result = JSON.parse(
|
|
17974
|
+
await runAz([
|
|
17975
|
+
"deployment",
|
|
17976
|
+
"group",
|
|
17977
|
+
"create",
|
|
17978
|
+
"--name",
|
|
17979
|
+
opts.deploymentName,
|
|
17980
|
+
"--resource-group",
|
|
17981
|
+
opts.resourceGroup,
|
|
17982
|
+
"--template-file",
|
|
17983
|
+
bicepPath,
|
|
17984
|
+
"--parameters",
|
|
17985
|
+
...opts.params,
|
|
17986
|
+
"--output",
|
|
17987
|
+
"json"
|
|
17988
|
+
])
|
|
17989
|
+
);
|
|
17990
|
+
const outputs = result.properties?.outputs ?? {};
|
|
17991
|
+
const fqdn = outputs.containerAppFqdn?.value;
|
|
17992
|
+
if (typeof fqdn !== "string" || !fqdn) {
|
|
17993
|
+
throw new LocalCliError({
|
|
17994
|
+
code: "DEPLOY_NO_FQDN",
|
|
17995
|
+
message: "Bicep deployment did not return containerAppFqdn.",
|
|
17996
|
+
hint: "Check deploy/main.bicep outputs and the deployment log."
|
|
17997
|
+
});
|
|
17998
|
+
}
|
|
17999
|
+
return {
|
|
18000
|
+
containerAppFqdn: fqdn,
|
|
18001
|
+
resourceNames: outputs.resourceNames?.value ?? {}
|
|
18002
|
+
};
|
|
18003
|
+
}
|
|
18004
|
+
|
|
18005
|
+
// src/lib/platform-converge.ts
|
|
18006
|
+
init_foundry_agent_get();
|
|
18007
|
+
init_errors();
|
|
18008
|
+
|
|
18009
|
+
// src/lib/persona-compose.ts
|
|
18010
|
+
function composeInstructions(args) {
|
|
18011
|
+
const live = args.current.definition.instructions ?? "";
|
|
18012
|
+
let out = args.baseBody;
|
|
18013
|
+
const hadLoader = stripBrainLoader(live) !== live;
|
|
18014
|
+
if (hadLoader && args.loaderText) {
|
|
18015
|
+
const templated = args.brainRepo ? args.loaderText.replaceAll("{{brain_repo}}", args.brainRepo) : args.loaderText;
|
|
18016
|
+
out = appendBrainLoader(out, templated);
|
|
18017
|
+
}
|
|
18018
|
+
if (hasA2aSnippet(live)) out = appendSnippet(out);
|
|
18019
|
+
return out;
|
|
18020
|
+
}
|
|
18021
|
+
function resolveFillableValues(current, yamlValues) {
|
|
18022
|
+
const raw = current.metadata?.fillableFieldValues;
|
|
18023
|
+
if (typeof raw === "string") {
|
|
18024
|
+
try {
|
|
18025
|
+
return JSON.parse(raw);
|
|
18026
|
+
} catch {
|
|
18027
|
+
}
|
|
18028
|
+
}
|
|
18029
|
+
return yamlValues;
|
|
18030
|
+
}
|
|
18031
|
+
function isNoop(candidate2, current) {
|
|
18032
|
+
const a = candidate2.definition.instructions ?? "";
|
|
18033
|
+
const b = current.definition.instructions ?? "";
|
|
18034
|
+
if (a !== b) return false;
|
|
18035
|
+
if (JSON.stringify(candidate2.definition.tools ?? []) !== JSON.stringify(current.definition.tools ?? [])) return false;
|
|
18036
|
+
return JSON.stringify(sortObj(candidate2.metadata)) === JSON.stringify(sortObj(current.metadata ?? {}));
|
|
18037
|
+
}
|
|
18038
|
+
function sortObj(o) {
|
|
18039
|
+
return Object.fromEntries(Object.entries(o).sort(([x], [y]) => x.localeCompare(y)));
|
|
18040
|
+
}
|
|
18041
|
+
|
|
18042
|
+
// src/lib/platform-converge.ts
|
|
18043
|
+
var ORDER = ["infra", "gateway", "codingAgent", "azureExecutor", "personas"];
|
|
18044
|
+
function infraTargetSha(manifest, tree) {
|
|
18045
|
+
return manifest.components.infra?.treeSha ?? tree.treeShaOf("deploy") ?? "";
|
|
18046
|
+
}
|
|
18047
|
+
function diffPlan(manifest, stamp, tree, opts = {}) {
|
|
18048
|
+
const actions = [];
|
|
18049
|
+
const skipped = [];
|
|
18050
|
+
const adopt = stamp === null;
|
|
18051
|
+
const want = (k) => opts.only === void 0 || opts.only === k;
|
|
18052
|
+
for (const component of ORDER) {
|
|
18053
|
+
if (!want(component)) {
|
|
18054
|
+
skipped.push({ component, reason: "only-filter" });
|
|
18055
|
+
continue;
|
|
18056
|
+
}
|
|
18057
|
+
if (component === "infra") {
|
|
18058
|
+
if (opts.skipInfra) {
|
|
18059
|
+
skipped.push({ component, reason: "up-to-date" });
|
|
18060
|
+
continue;
|
|
18061
|
+
}
|
|
18062
|
+
const to = infraTargetSha(manifest, tree);
|
|
18063
|
+
const from2 = stamp?.components.infra.treeSha ?? null;
|
|
18064
|
+
if (opts.forceInfra) actions.push({ component, reason: "forced", from: from2, to });
|
|
18065
|
+
else if (adopt) actions.push({ component, reason: "adopt", from: from2, to });
|
|
18066
|
+
else if (from2 !== to) skipped.push({ component, reason: "deferred" });
|
|
18067
|
+
else skipped.push({ component, reason: "up-to-date" });
|
|
18068
|
+
continue;
|
|
18069
|
+
}
|
|
18070
|
+
if (component === "personas") {
|
|
18071
|
+
for (const [name, item] of Object.entries(manifest.components.personas.items)) {
|
|
18072
|
+
const from2 = stamp?.components.personas[name]?.treeSha ?? null;
|
|
18073
|
+
if (adopt) actions.push({ component, personaName: name, reason: "adopt", from: from2, to: item.treeSha });
|
|
18074
|
+
else if (from2 !== item.treeSha) actions.push({ component, personaName: name, reason: "changed", from: from2, to: item.treeSha });
|
|
18075
|
+
else skipped.push({ component, personaName: name, reason: "up-to-date" });
|
|
18076
|
+
}
|
|
18077
|
+
continue;
|
|
18078
|
+
}
|
|
18079
|
+
const img = manifest.components[component];
|
|
18080
|
+
const cur = stamp?.components[component];
|
|
18081
|
+
if (cur?.state === "external") {
|
|
18082
|
+
skipped.push({ component, reason: "external" });
|
|
18083
|
+
continue;
|
|
18084
|
+
}
|
|
18085
|
+
const from = cur?.tag ?? null;
|
|
18086
|
+
if (adopt) actions.push({ component, reason: "adopt", from, to: img.tag });
|
|
18087
|
+
else if (from !== img.tag) actions.push({ component, reason: "changed", from, to: img.tag });
|
|
18088
|
+
else skipped.push({ component, reason: "up-to-date" });
|
|
18089
|
+
}
|
|
18090
|
+
return { targetVersion: manifest.platform.tag, previousVersion: manifest.platform.previousVersion, actions, skipped };
|
|
18091
|
+
}
|
|
18092
|
+
function statusRows(manifest, stamp, tree, opts = {}) {
|
|
18093
|
+
const plan = diffPlan(manifest, stamp, tree, opts);
|
|
18094
|
+
const rows = [];
|
|
18095
|
+
for (const a of plan.actions) {
|
|
18096
|
+
rows.push({
|
|
18097
|
+
component: a.component,
|
|
18098
|
+
...a.personaName ? { personaName: a.personaName } : {},
|
|
18099
|
+
installed: a.from ?? "(none)",
|
|
18100
|
+
target: a.to,
|
|
18101
|
+
status: a.reason === "adopt" ? "needs-adopt" : "drift"
|
|
18102
|
+
});
|
|
18103
|
+
}
|
|
18104
|
+
for (const s of plan.skipped) {
|
|
18105
|
+
rows.push({
|
|
18106
|
+
component: s.component,
|
|
18107
|
+
...s.personaName ? { personaName: s.personaName } : {},
|
|
18108
|
+
installed: installedValueOf(stamp, s.component, s.personaName),
|
|
18109
|
+
target: targetValueOf(manifest, tree, s.component, s.personaName),
|
|
18110
|
+
status: s.reason
|
|
18111
|
+
});
|
|
18112
|
+
}
|
|
18113
|
+
return rows;
|
|
18114
|
+
}
|
|
18115
|
+
function installedValueOf(stamp, component, personaName) {
|
|
18116
|
+
if (!stamp) return "-";
|
|
18117
|
+
if (component === "infra") return stamp.components.infra.treeSha || "-";
|
|
18118
|
+
if (component === "personas") {
|
|
18119
|
+
if (!personaName || !Object.hasOwn(stamp.components.personas, personaName)) return "-";
|
|
18120
|
+
return stamp.components.personas[personaName].treeSha;
|
|
18121
|
+
}
|
|
18122
|
+
return stamp.components[component].tag;
|
|
18123
|
+
}
|
|
18124
|
+
function targetValueOf(manifest, tree, component, personaName) {
|
|
18125
|
+
if (component === "infra") return infraTargetSha(manifest, tree) || "-";
|
|
18126
|
+
if (component === "personas") {
|
|
18127
|
+
if (!personaName || !Object.hasOwn(manifest.components.personas.items, personaName)) return "-";
|
|
18128
|
+
return manifest.components.personas.items[personaName].treeSha;
|
|
18129
|
+
}
|
|
18130
|
+
return manifest.components[component].tag;
|
|
18131
|
+
}
|
|
18132
|
+
async function applyPlan(plan, deps, ctx) {
|
|
18133
|
+
const prior = await deps.readStamp();
|
|
18134
|
+
const stamp = seedStamp(prior, ctx.manifest, plan);
|
|
18135
|
+
delete stamp.lastError;
|
|
18136
|
+
const applied = [];
|
|
18137
|
+
try {
|
|
18138
|
+
for (const a of plan.actions) {
|
|
18139
|
+
if (a.component === "infra") {
|
|
18140
|
+
stamp.components.infra = await deps.applyInfra(a, ctx);
|
|
18141
|
+
} else if (a.component === "gateway") {
|
|
18142
|
+
stamp.components.gateway = await deps.applyGateway(a, ctx);
|
|
18143
|
+
} else if (a.component === "codingAgent" || a.component === "azureExecutor") {
|
|
18144
|
+
const r = await deps.applyAgent(a, ctx);
|
|
18145
|
+
if (r !== "absent") {
|
|
18146
|
+
stamp.components[a.component] = { ...r, state: "managed" };
|
|
18147
|
+
} else {
|
|
18148
|
+
ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 stamp left unchanged.`);
|
|
18149
|
+
}
|
|
18150
|
+
} else {
|
|
18151
|
+
if (!a.personaName) {
|
|
18152
|
+
throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
|
|
18153
|
+
}
|
|
18154
|
+
const r = await deps.applyPersona(a, ctx);
|
|
18155
|
+
if (r === "absent") {
|
|
18156
|
+
ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 stamp left unchanged.`);
|
|
18157
|
+
} else {
|
|
18158
|
+
const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
|
|
18159
|
+
stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
|
|
18160
|
+
}
|
|
18161
|
+
}
|
|
18162
|
+
applied.push(a);
|
|
18163
|
+
stamp.lastResult = "partial";
|
|
18164
|
+
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18165
|
+
await deps.writeStamp(stamp);
|
|
18166
|
+
}
|
|
18167
|
+
await deps.healthGate(ctx, applied);
|
|
18168
|
+
stamp.lastResult = "success";
|
|
18169
|
+
delete stamp.lastError;
|
|
18170
|
+
const isTargeted = plan.skipped.some((s) => s.reason === "only-filter");
|
|
18171
|
+
if (!isTargeted && plan.targetVersion !== stamp.platformVersion) {
|
|
18172
|
+
stamp.previousPlatformVersion = stamp.platformVersion;
|
|
18173
|
+
stamp.platformVersion = plan.targetVersion;
|
|
18174
|
+
}
|
|
18175
|
+
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18176
|
+
await deps.writeStamp(stamp);
|
|
18177
|
+
return { stamp, applied };
|
|
18178
|
+
} catch (e) {
|
|
18179
|
+
stamp.lastResult = "failed";
|
|
18180
|
+
stamp.lastError = {
|
|
18181
|
+
component: applied.length < plan.actions.length ? plan.actions[applied.length].component : "healthGate",
|
|
18182
|
+
message: e.message
|
|
18183
|
+
};
|
|
18184
|
+
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18185
|
+
await deps.writeStamp(stamp).catch(() => {
|
|
18186
|
+
});
|
|
18187
|
+
throw e;
|
|
18188
|
+
}
|
|
18189
|
+
}
|
|
18190
|
+
function seedStamp(prior, manifest, plan) {
|
|
18191
|
+
const blankImg = () => ({ tag: "", digest: "", state: "managed" });
|
|
18192
|
+
return prior ? { ...prior } : {
|
|
18193
|
+
schemaVersion: 1,
|
|
18194
|
+
platformVersion: plan.targetVersion,
|
|
18195
|
+
previousPlatformVersion: plan.previousVersion,
|
|
18196
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18197
|
+
lastResult: "partial",
|
|
18198
|
+
cli: "",
|
|
18199
|
+
components: { gateway: blankImg(), codingAgent: blankImg(), azureExecutor: blankImg(), personas: {}, infra: { treeSha: "" } }
|
|
18200
|
+
};
|
|
18201
|
+
}
|
|
18202
|
+
async function applyGatewayImage(a, ctx, gatewayResourceId) {
|
|
18203
|
+
const { resourceGroup, name } = parseContainerAppResourceId(gatewayResourceId);
|
|
18204
|
+
const current = (await runAz(["containerapp", "show", "-g", resourceGroup, "-n", name, "--query", "properties.template.containers[0].image", "-o", "tsv"])).trim();
|
|
18205
|
+
const tags = await fetchPublicTags(DEFAULT_IMAGE_REPO);
|
|
18206
|
+
const plan = planUpdate({ currentImage: current, availableTags: tags, imageRepo: DEFAULT_IMAGE_REPO, to: a.to });
|
|
18207
|
+
const digest = ctx.manifest.components.gateway.digest;
|
|
18208
|
+
switch (plan.kind) {
|
|
18209
|
+
case "refuse-byoc":
|
|
18210
|
+
ctx.onProgress?.(`gateway is BYOC (${plan.currentRepo}) \u2014 marking externally managed.`);
|
|
18211
|
+
return { tag: a.to, digest, state: "external" };
|
|
18212
|
+
case "roll":
|
|
18213
|
+
await runAz(["containerapp", "update", "-n", name, "-g", resourceGroup, "--image", `${plan.repo}:${plan.target}`]);
|
|
18214
|
+
return { tag: a.to, digest, state: "managed" };
|
|
18215
|
+
case "noop":
|
|
18216
|
+
return { tag: a.to, digest, state: "managed" };
|
|
18217
|
+
case "tag-not-found":
|
|
18218
|
+
throw new LocalCliError({
|
|
18219
|
+
code: "PLATFORM_GATEWAY_TAG_UNAVAILABLE",
|
|
18220
|
+
message: `Gateway image tag '${a.to}' is not published on the tracked repo \u2014 cannot converge the gateway.`,
|
|
18221
|
+
hint: "The manifest may be ahead of the registry (propagation lag), or the repo is wrong. Retry shortly or check --image-repo."
|
|
18222
|
+
});
|
|
18223
|
+
case "no-versions":
|
|
18224
|
+
throw new LocalCliError({
|
|
18225
|
+
code: "PLATFORM_GATEWAY_NO_VERSIONS",
|
|
18226
|
+
message: "No published gateway image versions found on the tracked repo."
|
|
18227
|
+
});
|
|
18228
|
+
}
|
|
18229
|
+
}
|
|
18230
|
+
async function applyInfraDeploy(a, ctx, target, opts = {}) {
|
|
18231
|
+
if (a.reason !== "forced") {
|
|
18232
|
+
ctx.onProgress?.(`infra baseline recorded at ${target} (not re-provisioned; apply with --force-infra or the update job).`);
|
|
18233
|
+
return { treeSha: target };
|
|
18234
|
+
}
|
|
18235
|
+
ctx.onProgress?.("converging infra (idempotent bicep deploy)\u2026");
|
|
18236
|
+
const params = await resolveBicepParamsForConverge(ctx, opts);
|
|
18237
|
+
await runBicepDeployment({
|
|
18238
|
+
resourceGroup: ctx.resourceGroup,
|
|
18239
|
+
repoRoot: ctx.contentDir,
|
|
18240
|
+
params: buildBicepParams(params),
|
|
18241
|
+
deploymentName: `m8t-converge-${target.slice(0, 12)}`
|
|
18242
|
+
});
|
|
18243
|
+
return { treeSha: target };
|
|
18244
|
+
}
|
|
18245
|
+
async function resolveBicepParamsForConverge(ctx, opts = {}) {
|
|
18246
|
+
if (!opts.suffix) {
|
|
18247
|
+
throw new LocalCliError({
|
|
18248
|
+
code: "PLATFORM_INFRA_SUFFIX_REQUIRED",
|
|
18249
|
+
message: "A --force-infra converge deploy requires an explicit --suffix.",
|
|
18250
|
+
hint: "Pass --suffix <existing-suffix> (from the deployed resource names) to avoid provisioning duplicate resources."
|
|
18251
|
+
});
|
|
18252
|
+
}
|
|
18253
|
+
const location = (await runAz(["resource", "list", "-g", ctx.resourceGroup, "--query", "[0].location", "-o", "tsv"])).trim();
|
|
18254
|
+
const foundryResourceId = await resolveFoundryResourceId(opts.foundryEndpoint ?? "", opts.foundryResourceId);
|
|
18255
|
+
const acrPullIdentityResourceId = resolveAcrPullIdentity({ explicit: opts.acrPullIdentity });
|
|
18256
|
+
const imageRef = `${ctx.manifest.components.gateway.ref}:${ctx.manifest.components.gateway.tag}`;
|
|
18257
|
+
const acrResourceId = await resolveAcrResourceId({ imageRef, explicit: opts.acrResourceId });
|
|
18258
|
+
return {
|
|
18259
|
+
location,
|
|
18260
|
+
suffix: opts.suffix,
|
|
18261
|
+
imageRef,
|
|
18262
|
+
tenantId: opts.tenantId ?? "",
|
|
18263
|
+
clientId: opts.clientId ?? "",
|
|
18264
|
+
foundryResourceId,
|
|
18265
|
+
foundryProjectEndpoint: opts.foundryEndpoint ?? "",
|
|
18266
|
+
acrPullIdentityResourceId,
|
|
18267
|
+
acrResourceId,
|
|
18268
|
+
foundryTracingMode: opts.foundryTracingMode
|
|
18269
|
+
};
|
|
18270
|
+
}
|
|
18271
|
+
function swapHostedImage(def, newImage) {
|
|
18272
|
+
return { ...def, image: newImage };
|
|
18273
|
+
}
|
|
18274
|
+
async function applyAgentImage(a, ctx, opts) {
|
|
18275
|
+
const current = await getAgentVersion({ credential: ctx.credential, projectEndpoint: opts.project.endpoint, agentName: opts.agentName });
|
|
18276
|
+
const staged = await stagePublicImageIfNeeded({ image: opts.ghcrImage, project: opts.project, onProgress: ctx.onProgress });
|
|
18277
|
+
const definition = swapHostedImage(current.definition, staged.image);
|
|
18278
|
+
await createAgentVersion({
|
|
18279
|
+
credential: ctx.credential,
|
|
18280
|
+
projectEndpoint: opts.project.endpoint,
|
|
18281
|
+
agentName: opts.agentName,
|
|
18282
|
+
definition,
|
|
18283
|
+
metadata: current.metadata ?? {},
|
|
18284
|
+
extraHeaders: { "Foundry-Features": "HostedAgents=V1Preview" }
|
|
18285
|
+
});
|
|
18286
|
+
const digest = a.component === "codingAgent" ? ctx.manifest.components.codingAgent.digest : ctx.manifest.components.azureExecutor.digest;
|
|
18287
|
+
return { tag: a.to, digest };
|
|
18288
|
+
}
|
|
18289
|
+
function declaredFillableFields(personaPath) {
|
|
18290
|
+
let raw;
|
|
18291
|
+
try {
|
|
18292
|
+
raw = fs21.readFileSync(personaPath, "utf8");
|
|
18293
|
+
} catch {
|
|
18294
|
+
return [];
|
|
18295
|
+
}
|
|
18296
|
+
const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
18297
|
+
if (!fmMatch) return [];
|
|
18298
|
+
try {
|
|
18299
|
+
const fm = parseYaml10(fmMatch[1]);
|
|
18300
|
+
return fm["fillable-fields"] ?? [];
|
|
18301
|
+
} catch {
|
|
18302
|
+
return [];
|
|
18303
|
+
}
|
|
18304
|
+
}
|
|
18305
|
+
function defaultsOf(declared) {
|
|
18306
|
+
const out = {};
|
|
18307
|
+
for (const f of declared) {
|
|
18308
|
+
if (f.default !== void 0) out[f.name] = f.default;
|
|
18309
|
+
}
|
|
18310
|
+
return out;
|
|
18311
|
+
}
|
|
18312
|
+
function injectAndRender(personaPath, values, agentName) {
|
|
18313
|
+
return renderPersonaBody(personaPath, values, agentName);
|
|
18314
|
+
}
|
|
18315
|
+
function loaderText(loaderPath) {
|
|
18316
|
+
if (!loaderPath) return null;
|
|
18317
|
+
try {
|
|
18318
|
+
return fs21.readFileSync(loaderPath, "utf8");
|
|
18319
|
+
} catch {
|
|
18320
|
+
return null;
|
|
18321
|
+
}
|
|
18322
|
+
}
|
|
18323
|
+
function brainRepoOf(current) {
|
|
18324
|
+
const raw = current.metadata?.brain;
|
|
18325
|
+
if (typeof raw !== "string") return null;
|
|
18326
|
+
try {
|
|
18327
|
+
const parsed = JSON.parse(raw);
|
|
18328
|
+
return parsed.repo || null;
|
|
18329
|
+
} catch {
|
|
18330
|
+
return null;
|
|
18331
|
+
}
|
|
18332
|
+
}
|
|
18333
|
+
async function applyPersona(a, ctx, opts) {
|
|
18334
|
+
const current = await getAgentVersion({ credential: ctx.credential, projectEndpoint: opts.project.endpoint, agentName: opts.agentName });
|
|
18335
|
+
const personaPath = path20.join(opts.personaDir, "persona.md");
|
|
18336
|
+
const yamlValues = readAgentYaml(opts.agentName)?.fillableFieldValues ?? null;
|
|
18337
|
+
const values = resolveFillableValues(current, yamlValues);
|
|
18338
|
+
const declared = declaredFillableFields(personaPath);
|
|
18339
|
+
if (declared.length > 0 && values === null) {
|
|
18340
|
+
const defaultBody = injectAndRender(personaPath, defaultsOf(declared), opts.agentName);
|
|
18341
|
+
const candidateInstructions = composeInstructions({
|
|
18342
|
+
baseBody: defaultBody,
|
|
18343
|
+
current,
|
|
18344
|
+
loaderText: loaderText(opts.loaderPath),
|
|
18345
|
+
brainRepo: brainRepoOf(current)
|
|
18346
|
+
});
|
|
18347
|
+
if (candidateInstructions !== (current.definition.instructions ?? "")) {
|
|
18348
|
+
throw new LocalCliError({
|
|
18349
|
+
code: "PLATFORM_PERSONA_UNRECOVERABLE",
|
|
18350
|
+
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.`
|
|
18351
|
+
});
|
|
18352
|
+
}
|
|
18353
|
+
}
|
|
18354
|
+
const missing = declared.filter((f) => !values?.[f.name] && f.default === void 0);
|
|
18355
|
+
if (missing.length > 0) {
|
|
18356
|
+
throw new LocalCliError({
|
|
18357
|
+
code: "PLATFORM_PERSONA_FIELD_MISSING",
|
|
18358
|
+
message: `Persona '${a.personaName ?? opts.agentName}' needs values for: ${missing.map((f) => f.name).join(", ")}.`
|
|
18359
|
+
});
|
|
18360
|
+
}
|
|
18361
|
+
const baseBody = injectAndRender(personaPath, values ?? defaultsOf(declared), opts.agentName);
|
|
18362
|
+
const instructions = composeInstructions({
|
|
18363
|
+
baseBody,
|
|
18364
|
+
current,
|
|
18365
|
+
loaderText: loaderText(opts.loaderPath),
|
|
18366
|
+
brainRepo: brainRepoOf(current)
|
|
18367
|
+
});
|
|
18368
|
+
const metadata = {
|
|
18369
|
+
...current.metadata ?? {},
|
|
18370
|
+
persona: a.personaName ?? opts.agentName,
|
|
18371
|
+
...values ? { fillableFieldValues: JSON.stringify(values) } : {}
|
|
18372
|
+
};
|
|
18373
|
+
const candidate2 = { definition: { ...current.definition, instructions }, metadata };
|
|
18374
|
+
if (isNoop(candidate2, current)) return "noop";
|
|
18375
|
+
const foundryVersion = await createAgentVersion({
|
|
18376
|
+
credential: ctx.credential,
|
|
18377
|
+
projectEndpoint: opts.project.endpoint,
|
|
18378
|
+
agentName: opts.agentName,
|
|
18379
|
+
definition: candidate2.definition,
|
|
18380
|
+
metadata
|
|
18381
|
+
});
|
|
18382
|
+
return { treeSha: a.to, foundryVersion };
|
|
18383
|
+
}
|
|
18384
|
+
|
|
18385
|
+
// src/commands/platform/status.ts
|
|
18386
|
+
var PlatformStatusCommand = class extends M8tCommand {
|
|
18387
|
+
static paths = [["platform", "status"]];
|
|
18388
|
+
static usage = Command31.Usage({
|
|
18389
|
+
description: "Show per-component platform drift against a release manifest.",
|
|
18390
|
+
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."
|
|
18391
|
+
});
|
|
18392
|
+
subscription = Option29.String("--subscription");
|
|
18393
|
+
resourceGroup = Option29.String("--resource-group", {
|
|
18394
|
+
description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
18395
|
+
});
|
|
18396
|
+
to = Option29.String("--to", {
|
|
18397
|
+
description: "Check status against a pinned platform version (defaults to the current release channel)."
|
|
18398
|
+
});
|
|
18399
|
+
contentDir = Option29.String("--content-dir", {
|
|
18400
|
+
description: "Path to a local m8t-stack repo checkout (skips the tarball tree-SHA fetch)."
|
|
18401
|
+
});
|
|
18402
|
+
manifestUrl = Option29.String("--manifest-url", {
|
|
18403
|
+
description: "Fetch the release manifest from this URL instead of the release channel."
|
|
18404
|
+
});
|
|
18405
|
+
manifestFile = Option29.String("--manifest-file", {
|
|
18406
|
+
description: "Read the release manifest from this local file instead of the release channel."
|
|
18407
|
+
});
|
|
18408
|
+
verify = Option29.Boolean("--verify", false, {
|
|
18409
|
+
description: "Additionally probe the live gateway (image tag + /api/version) and report stamp-vs-live."
|
|
18410
|
+
});
|
|
18411
|
+
output = Option29.String("--output");
|
|
18412
|
+
async executeCommand() {
|
|
18413
|
+
const mode = resolveOutputMode(
|
|
18414
|
+
this.output,
|
|
18415
|
+
this.context.stdout
|
|
18416
|
+
);
|
|
18417
|
+
const log = (msg) => {
|
|
18418
|
+
if (mode !== "json") this.context.stdout.write(msg + "\n");
|
|
18419
|
+
};
|
|
18420
|
+
const to = typeof this.to === "string" ? this.to : void 0;
|
|
18421
|
+
const contentDirFlag = typeof this.contentDir === "string" ? this.contentDir : void 0;
|
|
18422
|
+
const manifestUrl = typeof this.manifestUrl === "string" ? this.manifestUrl : void 0;
|
|
18423
|
+
const manifestFile = typeof this.manifestFile === "string" ? this.manifestFile : void 0;
|
|
18424
|
+
const gw = await resolveGatewayContext({
|
|
18425
|
+
interactive: mode !== "json",
|
|
18426
|
+
subscriptionId: this.subscription,
|
|
18427
|
+
resourceGroup: this.resourceGroup
|
|
18428
|
+
});
|
|
18429
|
+
const { resourceGroup } = parseContainerAppResourceId(gw.containerAppResourceId);
|
|
18430
|
+
const credential2 = new DefaultAzureCredential16();
|
|
18431
|
+
const account = await getAzAccount();
|
|
18432
|
+
const subscriptionId = this.subscription ?? account.subscriptionId;
|
|
18433
|
+
const source = manifestFile ? { file: manifestFile } : manifestUrl ? { url: manifestUrl } : { channel: true, version: to };
|
|
18434
|
+
const manifest = await fetchManifest(source);
|
|
18435
|
+
const stamp = await readStamp({ credential: credential2, subscriptionId, resourceGroup });
|
|
18436
|
+
const tree = contentDirFlag ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
|
|
18437
|
+
const rows = statusRows(manifest, stamp, tree);
|
|
18438
|
+
let verify;
|
|
18439
|
+
if (this.verify) {
|
|
18440
|
+
verify = await this.probeLive(gw.containerAppResourceId, gw.gatewayUrl, stamp?.components.gateway.tag);
|
|
18441
|
+
}
|
|
18442
|
+
if (mode === "json") {
|
|
18443
|
+
this.context.stdout.write(
|
|
18444
|
+
renderJson({
|
|
18445
|
+
kind: "platform-status",
|
|
18446
|
+
gateway: resourceGroup,
|
|
18447
|
+
targetVersion: manifest.platform.tag,
|
|
18448
|
+
managed: stamp !== null,
|
|
18449
|
+
rows,
|
|
18450
|
+
...verify ? { verify } : {}
|
|
18451
|
+
}) + "\n"
|
|
18452
|
+
);
|
|
18453
|
+
return 0;
|
|
18454
|
+
}
|
|
18455
|
+
log(colors.field(`target: ${manifest.platform.tag}${stamp === null ? " (unmanaged \u2014 no stamp found)" : ""}`));
|
|
18456
|
+
this.context.stdout.write(this.renderRows(rows) + "\n");
|
|
18457
|
+
if (verify) {
|
|
18458
|
+
log("");
|
|
18459
|
+
log(colors.field("live verify:"));
|
|
18460
|
+
log(` stamp gateway tag: ${verify.stampGatewayTag}`);
|
|
18461
|
+
log(` live gateway tag: ${verify.liveGatewayTag}`);
|
|
18462
|
+
log(
|
|
18463
|
+
` match: ${verify.gatewayTagMatch === "unknown" ? colors.warn("unknown") : verify.gatewayTagMatch ? colors.success("yes") : colors.warn("no")}`
|
|
18464
|
+
);
|
|
18465
|
+
log(` /api/version: ${verify.apiVersion}`);
|
|
18466
|
+
}
|
|
18467
|
+
return 0;
|
|
18468
|
+
}
|
|
18469
|
+
renderRows(rows) {
|
|
18470
|
+
const tableRows = rows.map((r) => ({
|
|
18471
|
+
component: r.personaName ? `personas:${r.personaName}` : r.component,
|
|
18472
|
+
installed: r.installed,
|
|
18473
|
+
target: r.target,
|
|
18474
|
+
status: r.status
|
|
18475
|
+
}));
|
|
18476
|
+
return renderTable(tableRows, [
|
|
18477
|
+
{ key: "component", label: "COMPONENT" },
|
|
18478
|
+
{ key: "installed", label: "INSTALLED" },
|
|
18479
|
+
{ key: "target", label: "TARGET" },
|
|
18480
|
+
{ key: "status", label: "STATUS" }
|
|
18481
|
+
]);
|
|
18482
|
+
}
|
|
18483
|
+
/**
|
|
18484
|
+
* Best-effort live probe for --verify: the live gateway image tag (az) and
|
|
18485
|
+
* GET /api/version. Any failure (auth-gated endpoint, transient network
|
|
18486
|
+
* error, etc.) degrades the affected field to "unknown" rather than
|
|
18487
|
+
* throwing — a verify probe must never crash the read-only status command.
|
|
18488
|
+
*/
|
|
18489
|
+
async probeLive(gatewayResourceId, gatewayUrl, stampGatewayTag) {
|
|
18490
|
+
let liveGatewayTag = "unknown";
|
|
18491
|
+
try {
|
|
18492
|
+
const { resourceGroup, name } = parseContainerAppResourceId(gatewayResourceId);
|
|
18493
|
+
const image = (await runAz([
|
|
18494
|
+
"containerapp",
|
|
18495
|
+
"show",
|
|
18496
|
+
"-g",
|
|
18497
|
+
resourceGroup,
|
|
18498
|
+
"-n",
|
|
18499
|
+
name,
|
|
18500
|
+
"--query",
|
|
18501
|
+
"properties.template.containers[0].image",
|
|
18502
|
+
"-o",
|
|
18503
|
+
"tsv"
|
|
18504
|
+
])).trim();
|
|
18505
|
+
const tag = image.split(":").pop();
|
|
18506
|
+
if (tag) liveGatewayTag = tag;
|
|
18507
|
+
} catch {
|
|
18508
|
+
liveGatewayTag = "unknown";
|
|
18509
|
+
}
|
|
18510
|
+
let apiVersion = "unknown";
|
|
18511
|
+
try {
|
|
18512
|
+
const res = await fetch(`${gatewayUrl}/api/version`, { method: "GET" });
|
|
18513
|
+
if (res.ok) {
|
|
18514
|
+
const body = await res.json().catch(() => ({}));
|
|
18515
|
+
apiVersion = body.version ?? "unknown";
|
|
18516
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
18517
|
+
apiVersion = "unknown (auth-gated)";
|
|
18518
|
+
}
|
|
18519
|
+
} catch {
|
|
18520
|
+
apiVersion = "unknown";
|
|
18521
|
+
}
|
|
18522
|
+
const stampTag = stampGatewayTag ?? "unknown";
|
|
18523
|
+
const gatewayTagMatch = liveGatewayTag === "unknown" || stampTag === "unknown" ? "unknown" : liveGatewayTag === stampTag;
|
|
18524
|
+
return { liveGatewayTag, stampGatewayTag: stampTag, gatewayTagMatch, apiVersion };
|
|
18525
|
+
}
|
|
18526
|
+
};
|
|
18527
|
+
|
|
18528
|
+
// src/commands/platform/update.ts
|
|
18529
|
+
import { Command as Command32, Option as Option30 } from "clipanion";
|
|
18530
|
+
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
18531
|
+
import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/identity";
|
|
18532
|
+
|
|
18533
|
+
// src/lib/platform-converge-cli.ts
|
|
18534
|
+
import * as path21 from "path";
|
|
18535
|
+
init_errors();
|
|
18536
|
+
var HOSTED_AGENT_PERSONA_DIR = {
|
|
18537
|
+
codingAgent: "coding-agent",
|
|
18538
|
+
azureExecutor: "azure-executor"
|
|
18539
|
+
};
|
|
18540
|
+
async function buildConvergeDeps(args) {
|
|
18541
|
+
const warn = args.onWarn ?? (() => {
|
|
18542
|
+
});
|
|
18543
|
+
const discoveredAgents = await listM8tAgents({ credential: args.credential, projectEndpoint: args.project.endpoint });
|
|
18544
|
+
const discovered = {};
|
|
18545
|
+
for (const a of discoveredAgents) {
|
|
18546
|
+
const persona = a.metadata.persona;
|
|
18547
|
+
if (persona) discovered[persona] = a.name;
|
|
18548
|
+
}
|
|
18549
|
+
const loaderPath = path21.join(args.contentDir, "targets/foundry/brain-loader.md");
|
|
18550
|
+
return {
|
|
18551
|
+
async applyPersona(a, ctx) {
|
|
18552
|
+
const personaName = a.personaName;
|
|
18553
|
+
if (!personaName) {
|
|
18554
|
+
throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
|
|
18555
|
+
}
|
|
18556
|
+
const agentName = discovered[personaName];
|
|
18557
|
+
if (!agentName) {
|
|
18558
|
+
warn(`persona '${personaName}' is not deployed on this install \u2014 skipping.`);
|
|
18559
|
+
return "absent";
|
|
18560
|
+
}
|
|
18561
|
+
const personaItem = ctx.manifest.components.personas.items[personaName];
|
|
18562
|
+
return applyPersona(a, ctx, {
|
|
18563
|
+
agentName,
|
|
18564
|
+
project: args.project,
|
|
18565
|
+
personaDir: path21.join(ctx.contentDir, personaItem.path),
|
|
18566
|
+
loaderPath
|
|
18567
|
+
});
|
|
18568
|
+
},
|
|
18569
|
+
async applyAgent(a, ctx) {
|
|
18570
|
+
if (a.component !== "codingAgent" && a.component !== "azureExecutor") {
|
|
18571
|
+
throw new LocalCliError({
|
|
18572
|
+
code: "PLATFORM_AGENT_BAD_COMPONENT",
|
|
18573
|
+
message: `applyAgent called with non-hosted component '${a.component}'`
|
|
18574
|
+
});
|
|
18575
|
+
}
|
|
18576
|
+
const dir = HOSTED_AGENT_PERSONA_DIR[a.component];
|
|
18577
|
+
const agentName = dir ? discovered[dir] : void 0;
|
|
18578
|
+
if (!agentName) {
|
|
18579
|
+
warn(`hosted agent for component '${a.component}' is not deployed on this install \u2014 skipping.`);
|
|
18580
|
+
return "absent";
|
|
18581
|
+
}
|
|
18582
|
+
const componentRef = ctx.manifest.components[a.component];
|
|
18583
|
+
return applyAgentImage(a, ctx, {
|
|
18584
|
+
agentName,
|
|
18585
|
+
project: args.project,
|
|
18586
|
+
ghcrImage: `${componentRef.ref}:${a.to}`
|
|
18587
|
+
});
|
|
18588
|
+
},
|
|
18589
|
+
async applyGateway(a, ctx) {
|
|
18590
|
+
return applyGatewayImage(a, ctx, args.gatewayResourceId);
|
|
18591
|
+
},
|
|
18592
|
+
async applyInfra(a, ctx) {
|
|
18593
|
+
const target = infraTargetSha(ctx.manifest, args.tree);
|
|
18594
|
+
return applyInfraDeploy(a, ctx, target, { suffix: args.suffix });
|
|
18595
|
+
},
|
|
18596
|
+
async healthGate(_ctx, applied) {
|
|
18597
|
+
for (const a of applied) {
|
|
18598
|
+
let agentName;
|
|
18599
|
+
if (a.component === "personas" && a.personaName) {
|
|
18600
|
+
agentName = discovered[a.personaName];
|
|
18601
|
+
} else if (a.component === "codingAgent" || a.component === "azureExecutor") {
|
|
18602
|
+
const dir = HOSTED_AGENT_PERSONA_DIR[a.component];
|
|
18603
|
+
agentName = dir ? discovered[dir] : void 0;
|
|
18604
|
+
}
|
|
18605
|
+
if (!agentName) continue;
|
|
18606
|
+
await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.project.endpoint, agentName, onProgress: args.onProgress });
|
|
18607
|
+
}
|
|
18608
|
+
if (args.gatewayUrl) {
|
|
18609
|
+
try {
|
|
18610
|
+
const res = await fetch(`${args.gatewayUrl}/api/version`, { method: "GET" });
|
|
18611
|
+
if (res.ok) {
|
|
18612
|
+
const body = await res.json().catch(() => ({}));
|
|
18613
|
+
args.onProgress?.(`gateway reachable (version: ${body.version ?? "unknown"}).`);
|
|
18614
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
18615
|
+
args.onProgress?.(`gateway reachable (auth-gated, HTTP ${res.status.toString()}).`);
|
|
18616
|
+
} else {
|
|
18617
|
+
warn(`gateway health probe returned HTTP ${res.status.toString()} \u2014 not treated as fatal.`);
|
|
18618
|
+
}
|
|
18619
|
+
} catch (e) {
|
|
18620
|
+
warn(`gateway health probe failed: ${e.message} \u2014 not treated as fatal.`);
|
|
18621
|
+
}
|
|
18622
|
+
}
|
|
18623
|
+
},
|
|
18624
|
+
async readStamp() {
|
|
18625
|
+
return readStamp({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
|
|
18626
|
+
},
|
|
18627
|
+
async writeStamp(s) {
|
|
18628
|
+
await writeStamp({
|
|
18629
|
+
credential: args.credential,
|
|
18630
|
+
subscriptionId: args.subscriptionId,
|
|
18631
|
+
resourceGroup: args.resourceGroup,
|
|
18632
|
+
stamp: { ...s, cli: CLI_VERSION },
|
|
18633
|
+
onProgress: args.onProgress
|
|
18634
|
+
});
|
|
18635
|
+
}
|
|
18636
|
+
};
|
|
18637
|
+
}
|
|
18638
|
+
function toVTag(v) {
|
|
18639
|
+
return v.startsWith("v") ? v : `v${v}`;
|
|
18640
|
+
}
|
|
18641
|
+
function assertCliVersionOk(manifest, runningVersion, onWarn = () => {
|
|
18642
|
+
}) {
|
|
18643
|
+
const cli2 = manifest.components.cli;
|
|
18644
|
+
const running = toVTag(runningVersion);
|
|
18645
|
+
const min = toVTag(cli2.min);
|
|
18646
|
+
const recommended = toVTag(cli2.recommended);
|
|
18647
|
+
if (compareSemver(running, min) < 0) {
|
|
18648
|
+
throw new LocalCliError({
|
|
18649
|
+
code: "PLATFORM_CLI_TOO_OLD",
|
|
18650
|
+
message: `This CLI (${runningVersion}) is older than the minimum supported version (${cli2.min}) for platform ${manifest.platform.tag}.`,
|
|
18651
|
+
hint: "Upgrade: 'npm install -g @m8t-stack/cli@latest' (or your package manager's equivalent)."
|
|
18652
|
+
});
|
|
18653
|
+
}
|
|
18654
|
+
if (compareSemver(running, recommended) < 0) {
|
|
18655
|
+
onWarn(`This CLI (${runningVersion}) is older than the recommended version (${cli2.recommended}) for platform ${manifest.platform.tag}. Consider upgrading.`);
|
|
18656
|
+
}
|
|
18657
|
+
}
|
|
18658
|
+
|
|
18659
|
+
// src/commands/platform/update.ts
|
|
18660
|
+
var PlatformUpdateCommand = class extends M8tCommand {
|
|
18661
|
+
static paths = [["platform", "update"]];
|
|
18662
|
+
static usage = Command32.Usage({
|
|
18663
|
+
description: "Converge the whole platform (gateway, hosted agents, personas, infra) to a release manifest.",
|
|
18664
|
+
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."
|
|
18665
|
+
});
|
|
18666
|
+
subscription = Option30.String("--subscription");
|
|
18667
|
+
resourceGroup = Option30.String("--resource-group", {
|
|
18668
|
+
description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
18669
|
+
});
|
|
18670
|
+
to = Option30.String("--to", {
|
|
18671
|
+
description: "Pin the platform version to converge to (defaults to the current release channel)."
|
|
18672
|
+
});
|
|
18673
|
+
only = Option30.String("--only", {
|
|
18674
|
+
description: "Converge only one component: infra | gateway | codingAgent | azureExecutor | personas."
|
|
18675
|
+
});
|
|
18676
|
+
check = Option30.Boolean("--check", false);
|
|
18677
|
+
yes = Option30.Boolean("--yes", false);
|
|
18678
|
+
skipInfra = Option30.Boolean("--skip-infra", false);
|
|
18679
|
+
forceInfra = Option30.Boolean("--force-infra", false);
|
|
18680
|
+
suffix = Option30.String("--suffix", {
|
|
18681
|
+
description: "Required with --force-infra \u2014 the existing deployment's resource-name suffix (avoids provisioning duplicates)."
|
|
18682
|
+
});
|
|
18683
|
+
contentDir = Option30.String("--content-dir", {
|
|
18684
|
+
description: "Path to a local m8t-stack repo checkout (skips the tarball fetch + tree-drift check)."
|
|
18685
|
+
});
|
|
18686
|
+
manifestUrl = Option30.String("--manifest-url", {
|
|
18687
|
+
description: "Fetch the release manifest from this URL instead of the release channel."
|
|
18688
|
+
});
|
|
18689
|
+
manifestFile = Option30.String("--manifest-file", {
|
|
18690
|
+
description: "Read the release manifest from this local file instead of the release channel."
|
|
18691
|
+
});
|
|
18692
|
+
endpoint = Option30.String("--endpoint", {
|
|
18693
|
+
description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
|
|
18694
|
+
});
|
|
18695
|
+
output = Option30.String("--output");
|
|
18696
|
+
async executeCommand() {
|
|
18697
|
+
const mode = resolveOutputMode(
|
|
18698
|
+
this.output,
|
|
18699
|
+
this.context.stdout
|
|
18700
|
+
);
|
|
18701
|
+
const interactive = this.context.stdout.isTTY === true;
|
|
18702
|
+
const log = (msg) => {
|
|
18703
|
+
if (mode !== "json") this.context.stdout.write(msg + "\n");
|
|
18704
|
+
};
|
|
18705
|
+
const to = typeof this.to === "string" ? this.to : void 0;
|
|
18706
|
+
const only = typeof this.only === "string" ? this.only : void 0;
|
|
18707
|
+
const suffix = typeof this.suffix === "string" ? this.suffix : void 0;
|
|
18708
|
+
const contentDirFlag = typeof this.contentDir === "string" ? this.contentDir : void 0;
|
|
18709
|
+
const manifestUrl = typeof this.manifestUrl === "string" ? this.manifestUrl : void 0;
|
|
18710
|
+
const manifestFile = typeof this.manifestFile === "string" ? this.manifestFile : void 0;
|
|
18711
|
+
const gw = await resolveGatewayContext({
|
|
18712
|
+
interactive: mode !== "json",
|
|
18713
|
+
subscriptionId: this.subscription,
|
|
18714
|
+
resourceGroup: this.resourceGroup
|
|
18715
|
+
});
|
|
18716
|
+
const { resourceGroup } = parseContainerAppResourceId(gw.containerAppResourceId);
|
|
18717
|
+
const credential2 = new DefaultAzureCredential17();
|
|
18718
|
+
const account = await getAzAccount();
|
|
18719
|
+
const subscriptionId = this.subscription ?? account.subscriptionId;
|
|
18720
|
+
const onProgress = (m) => {
|
|
18721
|
+
log(colors.dim(m));
|
|
18722
|
+
};
|
|
18723
|
+
const onWarn = (w) => {
|
|
18724
|
+
log(colors.warn(w));
|
|
18725
|
+
};
|
|
18726
|
+
onProgress("resolving Foundry project\u2026");
|
|
18727
|
+
const project = await resolveFoundryProject({
|
|
18728
|
+
credential: credential2,
|
|
18729
|
+
subscriptionId,
|
|
18730
|
+
interactive,
|
|
18731
|
+
endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
|
|
18732
|
+
});
|
|
18733
|
+
const source = manifestFile ? { file: manifestFile } : manifestUrl ? { url: manifestUrl } : { channel: true, version: to };
|
|
18734
|
+
onProgress("fetching release manifest\u2026");
|
|
18735
|
+
const manifest = await fetchManifest(source);
|
|
18736
|
+
assertCliVersionOk(manifest, CLI_VERSION, onWarn);
|
|
18737
|
+
const stamp = await readStamp({ credential: credential2, subscriptionId, resourceGroup });
|
|
18738
|
+
onProgress("resolving release content\u2026");
|
|
18739
|
+
const contentDir = await resolveReleaseContent(manifest, { contentDir: contentDirFlag });
|
|
18740
|
+
const tree = contentDirFlag ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
|
|
18741
|
+
if (!contentDirFlag) {
|
|
18742
|
+
const mismatches = verifyPersonaTrees(manifest, tree);
|
|
18743
|
+
if (mismatches.length > 0) {
|
|
18744
|
+
log(colors.warn(`warning: ${mismatches.length.toString()} persona tree mismatch(es) vs the manifest.`));
|
|
18745
|
+
}
|
|
18746
|
+
}
|
|
18747
|
+
const plan = diffPlan(manifest, stamp, tree, { only, forceInfra: this.forceInfra, skipInfra: this.skipInfra });
|
|
18748
|
+
this.renderPlan(plan, mode);
|
|
18749
|
+
const deferredInfra = plan.skipped.find((s) => s.component === "infra" && s.reason === "deferred");
|
|
18750
|
+
if (deferredInfra) {
|
|
18751
|
+
log(
|
|
18752
|
+
colors.warn(
|
|
18753
|
+
`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.`
|
|
18754
|
+
)
|
|
18755
|
+
);
|
|
18756
|
+
}
|
|
18757
|
+
if (this.check) {
|
|
18758
|
+
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, checked: true, applied: false }) + "\n");
|
|
18759
|
+
return 0;
|
|
18760
|
+
}
|
|
18761
|
+
if (plan.actions.length === 0) {
|
|
18762
|
+
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: false }) + "\n");
|
|
18763
|
+
else log(colors.success("already up to date \u2713"));
|
|
18764
|
+
return 0;
|
|
18765
|
+
}
|
|
18766
|
+
if (!this.yes && interactive) {
|
|
18767
|
+
const ok = await confirm5({
|
|
18768
|
+
message: `Converge ${plan.actions.length.toString()} component(s) to ${plan.targetVersion}?`,
|
|
18769
|
+
default: true
|
|
18770
|
+
});
|
|
18771
|
+
if (!ok) {
|
|
18772
|
+
log(colors.dim("aborted \u2014 no change."));
|
|
18773
|
+
return 0;
|
|
18774
|
+
}
|
|
18775
|
+
}
|
|
18776
|
+
const deps = await buildConvergeDeps({
|
|
18777
|
+
credential: credential2,
|
|
18778
|
+
subscriptionId,
|
|
18779
|
+
resourceGroup,
|
|
18780
|
+
contentDir,
|
|
18781
|
+
manifest,
|
|
18782
|
+
tree,
|
|
18783
|
+
project,
|
|
18784
|
+
gatewayResourceId: gw.containerAppResourceId,
|
|
18785
|
+
gatewayUrl: gw.gatewayUrl,
|
|
18786
|
+
suffix,
|
|
18787
|
+
onProgress,
|
|
18788
|
+
onWarn
|
|
18789
|
+
});
|
|
18790
|
+
await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress });
|
|
18791
|
+
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: true }) + "\n");
|
|
18792
|
+
else log(colors.success(`converged \u2192 ${plan.targetVersion} \u2713`));
|
|
18793
|
+
return 0;
|
|
18794
|
+
}
|
|
18795
|
+
renderPlan(plan, mode) {
|
|
18796
|
+
if (mode === "json") return;
|
|
18797
|
+
const rows = [
|
|
18798
|
+
...plan.actions.map((a) => ({
|
|
18799
|
+
component: a.personaName ? `personas:${a.personaName}` : a.component,
|
|
18800
|
+
from: a.from ?? "(none)",
|
|
18801
|
+
to: a.to,
|
|
18802
|
+
reason: a.reason
|
|
18803
|
+
})),
|
|
18804
|
+
...plan.skipped.map((s) => ({
|
|
18805
|
+
component: s.personaName ? `personas:${s.personaName}` : s.component,
|
|
18806
|
+
from: "-",
|
|
18807
|
+
to: "-",
|
|
18808
|
+
reason: s.reason
|
|
18809
|
+
}))
|
|
18810
|
+
];
|
|
18811
|
+
this.context.stdout.write(
|
|
18812
|
+
renderTable(rows, [
|
|
18813
|
+
{ key: "component", label: "COMPONENT" },
|
|
18814
|
+
{ key: "from", label: "FROM" },
|
|
18815
|
+
{ key: "to", label: "TO" },
|
|
18816
|
+
{ key: "reason", label: "REASON" }
|
|
18817
|
+
]) + "\n"
|
|
18818
|
+
);
|
|
18819
|
+
}
|
|
18820
|
+
};
|
|
18821
|
+
|
|
18822
|
+
// src/commands/platform/enable-cost-report.ts
|
|
18823
|
+
import { Command as Command33, Option as Option31 } from "clipanion";
|
|
18824
|
+
|
|
18825
|
+
// src/lib/wire-gateway-acs.ts
|
|
18826
|
+
init_rbac();
|
|
18827
|
+
async function wireGatewayForAcs(args) {
|
|
18828
|
+
try {
|
|
18829
|
+
await args.az([
|
|
18830
|
+
"role",
|
|
18831
|
+
"assignment",
|
|
18832
|
+
"create",
|
|
18833
|
+
"--assignee-object-id",
|
|
18834
|
+
args.gatewayPrincipalId,
|
|
18835
|
+
"--assignee-principal-type",
|
|
18836
|
+
"ServicePrincipal",
|
|
18837
|
+
"--role",
|
|
18838
|
+
CONTRIBUTOR_ROLE_ID,
|
|
18839
|
+
"--scope",
|
|
18840
|
+
args.acsResourceId
|
|
18841
|
+
]);
|
|
18842
|
+
} catch (e) {
|
|
18843
|
+
if (!(e instanceof Error && /RoleAssignmentExists/i.test(e.message))) throw e;
|
|
17657
18844
|
}
|
|
17658
18845
|
await args.az([
|
|
17659
18846
|
"containerapp",
|
|
@@ -18015,156 +19202,11 @@ async function patchRedirectUris(appObjectId, fqdn) {
|
|
|
18015
19202
|
]);
|
|
18016
19203
|
}
|
|
18017
19204
|
|
|
18018
|
-
// src/lib/deploy.ts
|
|
18019
|
-
import * as fs18 from "fs/promises";
|
|
18020
|
-
import * as os8 from "os";
|
|
18021
|
-
import * as path18 from "path";
|
|
18022
|
-
init_errors();
|
|
18023
|
-
var FOUNDRY_TRACING_MODES = ["project", "account", "skip"];
|
|
18024
|
-
function parseFoundryTracingMode(v) {
|
|
18025
|
-
if (v === void 0) return void 0;
|
|
18026
|
-
if (!FOUNDRY_TRACING_MODES.includes(v)) {
|
|
18027
|
-
throw new LocalCliError({
|
|
18028
|
-
code: "DEPLOY_INVALID_TRACING_MODE",
|
|
18029
|
-
message: `Invalid --foundry-tracing value: '${v}'.`,
|
|
18030
|
-
hint: `Use one of: ${FOUNDRY_TRACING_MODES.join(", ")}.`
|
|
18031
|
-
});
|
|
18032
|
-
}
|
|
18033
|
-
return v;
|
|
18034
|
-
}
|
|
18035
|
-
function buildBicepParams(p) {
|
|
18036
|
-
const params = [
|
|
18037
|
-
`location=${p.location}`,
|
|
18038
|
-
`suffix=${p.suffix}`,
|
|
18039
|
-
`imageRef=${p.imageRef}`,
|
|
18040
|
-
`tenantId=${p.tenantId}`,
|
|
18041
|
-
`clientId=${p.clientId}`,
|
|
18042
|
-
`foundryResourceId=${p.foundryResourceId}`,
|
|
18043
|
-
`foundryProjectEndpoint=${p.foundryProjectEndpoint}`,
|
|
18044
|
-
`acrPullIdentityResourceId=${p.acrPullIdentityResourceId}`,
|
|
18045
|
-
`acrResourceId=${p.acrResourceId}`
|
|
18046
|
-
];
|
|
18047
|
-
if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
|
|
18048
|
-
return params;
|
|
18049
|
-
}
|
|
18050
|
-
async function resolveRepoRoot2(home = os8.homedir()) {
|
|
18051
|
-
const marker = path18.join(home, ".m8t-stack", "repo-root");
|
|
18052
|
-
try {
|
|
18053
|
-
const root = (await fs18.readFile(marker, "utf8")).trim();
|
|
18054
|
-
if (!root) throw new Error("empty");
|
|
18055
|
-
return root;
|
|
18056
|
-
} catch {
|
|
18057
|
-
throw new LocalCliError({
|
|
18058
|
-
code: "DEPLOY_NO_REPO_ROOT",
|
|
18059
|
-
message: "~/.m8t-stack/repo-root marker is missing or empty.",
|
|
18060
|
-
hint: "Run 'm8t install' from an m8t-stack checkout (it writes the marker), or run 'm8t deploy' from the repo."
|
|
18061
|
-
});
|
|
18062
|
-
}
|
|
18063
|
-
}
|
|
18064
|
-
async function resolveFoundryResourceId(endpoint, explicit) {
|
|
18065
|
-
if (explicit) return explicit;
|
|
18066
|
-
const m = /^https:\/\/([^.]+)\.services\.ai\.azure\.com/.exec(endpoint);
|
|
18067
|
-
if (!m) {
|
|
18068
|
-
throw new LocalCliError({
|
|
18069
|
-
code: "DEPLOY_FOUNDRY_ENDPOINT_BAD",
|
|
18070
|
-
message: `Cannot parse the Foundry account name from endpoint: ${endpoint}`,
|
|
18071
|
-
hint: "Pass --foundry-resource-id <ARM-id> explicitly."
|
|
18072
|
-
});
|
|
18073
|
-
}
|
|
18074
|
-
const accounts = JSON.parse(
|
|
18075
|
-
await runAz([
|
|
18076
|
-
"cognitiveservices",
|
|
18077
|
-
"account",
|
|
18078
|
-
"list",
|
|
18079
|
-
"--query",
|
|
18080
|
-
`[?name=='${m[1]}'].id`,
|
|
18081
|
-
"--output",
|
|
18082
|
-
"json"
|
|
18083
|
-
])
|
|
18084
|
-
);
|
|
18085
|
-
if (accounts.length === 0) {
|
|
18086
|
-
throw new LocalCliError({
|
|
18087
|
-
code: "DEPLOY_FOUNDRY_NOT_FOUND",
|
|
18088
|
-
message: `No Foundry account named '${m[1]}' in the active subscription.`,
|
|
18089
|
-
hint: "Check the endpoint, or pass --foundry-resource-id explicitly."
|
|
18090
|
-
});
|
|
18091
|
-
}
|
|
18092
|
-
if (accounts.length > 1) {
|
|
18093
|
-
throw new LocalCliError({
|
|
18094
|
-
code: "DEPLOY_FOUNDRY_AMBIGUOUS",
|
|
18095
|
-
message: `Multiple Foundry accounts named '${m[1]}' \u2014 ambiguous.`,
|
|
18096
|
-
hint: "Pass --foundry-resource-id <ARM-id> to disambiguate."
|
|
18097
|
-
});
|
|
18098
|
-
}
|
|
18099
|
-
return accounts[0];
|
|
18100
|
-
}
|
|
18101
|
-
async function resolveAcrResourceId(opts) {
|
|
18102
|
-
if (opts.explicit) return opts.explicit;
|
|
18103
|
-
const host = opts.imageRef.split("/")[0];
|
|
18104
|
-
if (!host.endsWith(".azurecr.io")) return "";
|
|
18105
|
-
const acrName = host.split(".")[0];
|
|
18106
|
-
try {
|
|
18107
|
-
const id = (await runAz(["acr", "show", "-n", acrName, "--query", "id", "-o", "tsv"])).trim();
|
|
18108
|
-
if (!id) throw new Error("empty id");
|
|
18109
|
-
return id;
|
|
18110
|
-
} catch (e) {
|
|
18111
|
-
if (e instanceof LocalCliError) throw e;
|
|
18112
|
-
throw new LocalCliError({
|
|
18113
|
-
code: "DEPLOY_ACR_NOT_FOUND",
|
|
18114
|
-
message: `Cannot resolve the ACR '${acrName}' for image ${opts.imageRef}.`,
|
|
18115
|
-
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>."
|
|
18116
|
-
});
|
|
18117
|
-
}
|
|
18118
|
-
}
|
|
18119
|
-
async function ensureResourceGroup(name, location) {
|
|
18120
|
-
const existing = JSON.parse(
|
|
18121
|
-
await runAz(["group", "show", "--name", name, "--output", "json"]).catch(() => "") || "null"
|
|
18122
|
-
);
|
|
18123
|
-
if (existing) return;
|
|
18124
|
-
await runAz(["group", "create", "--name", name, "--location", location, "--tags", "m8t-stack=deployment"]);
|
|
18125
|
-
}
|
|
18126
|
-
function resolveAcrPullIdentity(opts) {
|
|
18127
|
-
return opts.explicit ?? "";
|
|
18128
|
-
}
|
|
18129
|
-
async function runBicepDeployment(opts) {
|
|
18130
|
-
const bicepPath = path18.join(opts.repoRoot, "deploy", "main.bicep");
|
|
18131
|
-
const result = JSON.parse(
|
|
18132
|
-
await runAz([
|
|
18133
|
-
"deployment",
|
|
18134
|
-
"group",
|
|
18135
|
-
"create",
|
|
18136
|
-
"--name",
|
|
18137
|
-
opts.deploymentName,
|
|
18138
|
-
"--resource-group",
|
|
18139
|
-
opts.resourceGroup,
|
|
18140
|
-
"--template-file",
|
|
18141
|
-
bicepPath,
|
|
18142
|
-
"--parameters",
|
|
18143
|
-
...opts.params,
|
|
18144
|
-
"--output",
|
|
18145
|
-
"json"
|
|
18146
|
-
])
|
|
18147
|
-
);
|
|
18148
|
-
const outputs = result.properties?.outputs ?? {};
|
|
18149
|
-
const fqdn = outputs.containerAppFqdn?.value;
|
|
18150
|
-
if (typeof fqdn !== "string" || !fqdn) {
|
|
18151
|
-
throw new LocalCliError({
|
|
18152
|
-
code: "DEPLOY_NO_FQDN",
|
|
18153
|
-
message: "Bicep deployment did not return containerAppFqdn.",
|
|
18154
|
-
hint: "Check deploy/main.bicep outputs and the deployment log."
|
|
18155
|
-
});
|
|
18156
|
-
}
|
|
18157
|
-
return {
|
|
18158
|
-
containerAppFqdn: fqdn,
|
|
18159
|
-
resourceNames: outputs.resourceNames?.value ?? {}
|
|
18160
|
-
};
|
|
18161
|
-
}
|
|
18162
|
-
|
|
18163
19205
|
// src/commands/deploy.ts
|
|
18164
19206
|
init_errors();
|
|
18165
19207
|
|
|
18166
19208
|
// src/lib/whatif.ts
|
|
18167
|
-
import * as
|
|
19209
|
+
import * as path22 from "path";
|
|
18168
19210
|
function deriveResourceType(resourceId) {
|
|
18169
19211
|
if (resourceId.startsWith("[")) return "";
|
|
18170
19212
|
const afterProviders = resourceId.split("/providers/")[1];
|
|
@@ -18175,7 +19217,7 @@ function deriveResourceType(resourceId) {
|
|
|
18175
19217
|
return parts.join("/");
|
|
18176
19218
|
}
|
|
18177
19219
|
async function runWhatIf(opts) {
|
|
18178
|
-
const bicepPath =
|
|
19220
|
+
const bicepPath = path22.join(opts.repoRoot, "deploy", "main.bicep");
|
|
18179
19221
|
const out = await runAz([
|
|
18180
19222
|
"deployment",
|
|
18181
19223
|
"group",
|
|
@@ -18240,9 +19282,9 @@ function flattenDelta(delta, prefix = "") {
|
|
|
18240
19282
|
const out = [];
|
|
18241
19283
|
for (const d of delta) {
|
|
18242
19284
|
const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
|
|
18243
|
-
const
|
|
18244
|
-
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children,
|
|
18245
|
-
else out.push({ ...d, path:
|
|
19285
|
+
const path32 = prefix ? `${prefix}${segment}` : d.path;
|
|
19286
|
+
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path32));
|
|
19287
|
+
else out.push({ ...d, path: path32 });
|
|
18246
19288
|
}
|
|
18247
19289
|
return out;
|
|
18248
19290
|
}
|
|
@@ -18512,8 +19554,8 @@ var EvalSkillCommand = class extends M8tCommand {
|
|
|
18512
19554
|
|
|
18513
19555
|
// src/commands/eval/exam.ts
|
|
18514
19556
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
18515
|
-
import { writeFileSync as
|
|
18516
|
-
import { join as
|
|
19557
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync15, existsSync as existsSync13, readdirSync } from "fs";
|
|
19558
|
+
import { join as join22 } from "path";
|
|
18517
19559
|
import { Command as Command36, Option as Option34 } from "clipanion";
|
|
18518
19560
|
init_errors();
|
|
18519
19561
|
init_esm();
|
|
@@ -18679,10 +19721,10 @@ function resolveRefereeHome(refereeHomeOut, env) {
|
|
|
18679
19721
|
return null;
|
|
18680
19722
|
}
|
|
18681
19723
|
function resolveTaskSetDir(base, worker, version) {
|
|
18682
|
-
if (
|
|
19724
|
+
if (existsSync13(join22(base, version))) return version;
|
|
18683
19725
|
const prefixed = `${worker}-${version}`;
|
|
18684
|
-
if (
|
|
18685
|
-
if (version === "latest" &&
|
|
19726
|
+
if (existsSync13(join22(base, prefixed))) return prefixed;
|
|
19727
|
+
if (version === "latest" && existsSync13(base)) {
|
|
18686
19728
|
const dirs = readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(`${worker}-`)).map((d) => d.name).sort();
|
|
18687
19729
|
if (dirs.length > 0) return dirs[dirs.length - 1];
|
|
18688
19730
|
}
|
|
@@ -18697,11 +19739,11 @@ function readSealedTaskIds(taskSetVersion, refereeHomeOut, env = process.env) {
|
|
|
18697
19739
|
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)"
|
|
18698
19740
|
});
|
|
18699
19741
|
}
|
|
18700
|
-
const versionDir = resolveTaskSetDir(
|
|
18701
|
-
const manifestPath =
|
|
19742
|
+
const versionDir = resolveTaskSetDir(join22(home, "tasksets", worker), worker, version);
|
|
19743
|
+
const manifestPath = join22(home, "tasksets", worker, versionDir, "manifest.yaml");
|
|
18702
19744
|
let text;
|
|
18703
19745
|
try {
|
|
18704
|
-
text =
|
|
19746
|
+
text = readFileSync15(manifestPath, "utf-8");
|
|
18705
19747
|
} catch (e) {
|
|
18706
19748
|
throw new LocalCliError({
|
|
18707
19749
|
code: "EXAM_REDACTION_UNSAFE",
|
|
@@ -18819,10 +19861,10 @@ var EvalExamCommand = class extends M8tCommand {
|
|
|
18819
19861
|
const verdict = parseExamVerdict(res.stdout);
|
|
18820
19862
|
if (out !== void 0) {
|
|
18821
19863
|
const sealedTaskIds = taskSetVersion ? readSealedTaskIds(taskSetVersion, out) : /* @__PURE__ */ new Set();
|
|
18822
|
-
|
|
18823
|
-
|
|
19864
|
+
mkdirSync4(out, { recursive: true });
|
|
19865
|
+
writeFileSync4(join22(out, "verdict.json"), JSON.stringify(verdict, null, 2));
|
|
18824
19866
|
const feed = redactForFeed(verdict, sealedTaskIds);
|
|
18825
|
-
|
|
19867
|
+
writeFileSync4(join22(out, "feed.json"), JSON.stringify(feed, null, 2));
|
|
18826
19868
|
}
|
|
18827
19869
|
if (mode === "json") {
|
|
18828
19870
|
this.context.stdout.write(renderJson(verdict) + "\n");
|
|
@@ -19084,10 +20126,10 @@ var StatusCommand = class extends M8tCommand {
|
|
|
19084
20126
|
|
|
19085
20127
|
// src/commands/doctor.ts
|
|
19086
20128
|
import { Command as Command40, Option as Option38 } from "clipanion";
|
|
19087
|
-
import { DefaultAzureCredential as
|
|
19088
|
-
import * as
|
|
19089
|
-
import * as
|
|
19090
|
-
import * as
|
|
20129
|
+
import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
|
|
20130
|
+
import * as fs22 from "fs";
|
|
20131
|
+
import * as os10 from "os";
|
|
20132
|
+
import * as path23 from "path";
|
|
19091
20133
|
|
|
19092
20134
|
// src/lib/doctor-checks.ts
|
|
19093
20135
|
function checkAzSignedIn(az) {
|
|
@@ -19312,24 +20354,24 @@ async function resolveAccountLocation(accountId) {
|
|
|
19312
20354
|
}
|
|
19313
20355
|
}
|
|
19314
20356
|
function probeRepoRoot() {
|
|
19315
|
-
const marker =
|
|
19316
|
-
if (!
|
|
20357
|
+
const marker = path23.join(os10.homedir(), ".m8t-stack", "repo-root");
|
|
20358
|
+
if (!fs22.existsSync(marker)) {
|
|
19317
20359
|
return { markerExists: false, path: null, isDir: false, isGitCheckout: false };
|
|
19318
20360
|
}
|
|
19319
20361
|
let repoPath;
|
|
19320
20362
|
try {
|
|
19321
|
-
repoPath =
|
|
20363
|
+
repoPath = fs22.readFileSync(marker, "utf8").trim();
|
|
19322
20364
|
} catch {
|
|
19323
20365
|
return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
|
|
19324
20366
|
}
|
|
19325
20367
|
if (!repoPath) return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
|
|
19326
20368
|
let isDir = false;
|
|
19327
20369
|
try {
|
|
19328
|
-
isDir =
|
|
20370
|
+
isDir = fs22.statSync(repoPath).isDirectory();
|
|
19329
20371
|
} catch {
|
|
19330
20372
|
isDir = false;
|
|
19331
20373
|
}
|
|
19332
|
-
const isGitCheckout = isDir &&
|
|
20374
|
+
const isGitCheckout = isDir && fs22.existsSync(path23.join(repoPath, ".git"));
|
|
19333
20375
|
return { markerExists: true, path: repoPath, isDir, isGitCheckout };
|
|
19334
20376
|
}
|
|
19335
20377
|
var DoctorCommand = class extends M8tCommand {
|
|
@@ -19412,7 +20454,7 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
19412
20454
|
if (typeof this.agent === "string" && this.agent) {
|
|
19413
20455
|
checking(`delivery grant for ${this.agent}`);
|
|
19414
20456
|
try {
|
|
19415
|
-
const credential2 = new
|
|
20457
|
+
const credential2 = new DefaultAzureCredential18();
|
|
19416
20458
|
const cur = await getAgentVersion({
|
|
19417
20459
|
credential: credential2,
|
|
19418
20460
|
projectEndpoint: foundry.projectEndpoint,
|
|
@@ -19458,15 +20500,15 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
19458
20500
|
import { Command as Command41, Option as Option39 } from "clipanion";
|
|
19459
20501
|
|
|
19460
20502
|
// src/lib/profiles.ts
|
|
19461
|
-
import * as
|
|
19462
|
-
import * as
|
|
19463
|
-
import { parse as
|
|
20503
|
+
import * as fs23 from "fs/promises";
|
|
20504
|
+
import * as path24 from "path";
|
|
20505
|
+
import { parse as parseYaml11, stringify as stringifyYaml4 } from "yaml";
|
|
19464
20506
|
function profilesDir() {
|
|
19465
20507
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
19466
|
-
return
|
|
20508
|
+
return path24.join(home, ".m8t-stack", "profiles");
|
|
19467
20509
|
}
|
|
19468
20510
|
function getProfilePath(name) {
|
|
19469
|
-
return
|
|
20511
|
+
return path24.join(profilesDir(), `${path24.basename(name)}.yaml`);
|
|
19470
20512
|
}
|
|
19471
20513
|
function slugifyTenant(domainOrId) {
|
|
19472
20514
|
const base = domainOrId.split(".")[0].toLowerCase();
|
|
@@ -19475,7 +20517,7 @@ function slugifyTenant(domainOrId) {
|
|
|
19475
20517
|
}
|
|
19476
20518
|
async function listProfiles() {
|
|
19477
20519
|
try {
|
|
19478
|
-
const entries = await
|
|
20520
|
+
const entries = await fs23.readdir(profilesDir());
|
|
19479
20521
|
return entries.filter((e) => e.endsWith(".yaml")).map((e) => e.replace(/\.yaml$/, "")).sort();
|
|
19480
20522
|
} catch (e) {
|
|
19481
20523
|
if (e.code === "ENOENT") return [];
|
|
@@ -19484,8 +20526,8 @@ async function listProfiles() {
|
|
|
19484
20526
|
}
|
|
19485
20527
|
async function readProfile(name) {
|
|
19486
20528
|
try {
|
|
19487
|
-
const raw = await
|
|
19488
|
-
const parsed =
|
|
20529
|
+
const raw = await fs23.readFile(getProfilePath(name), "utf8");
|
|
20530
|
+
const parsed = parseYaml11(raw);
|
|
19489
20531
|
if (!parsed || typeof parsed !== "object") return null;
|
|
19490
20532
|
return parsed;
|
|
19491
20533
|
} catch (e) {
|
|
@@ -19495,14 +20537,14 @@ async function readProfile(name) {
|
|
|
19495
20537
|
}
|
|
19496
20538
|
async function exists(p) {
|
|
19497
20539
|
try {
|
|
19498
|
-
await
|
|
20540
|
+
await fs23.stat(p);
|
|
19499
20541
|
return true;
|
|
19500
20542
|
} catch {
|
|
19501
20543
|
return false;
|
|
19502
20544
|
}
|
|
19503
20545
|
}
|
|
19504
20546
|
async function saveProfile(p) {
|
|
19505
|
-
await
|
|
20547
|
+
await fs23.mkdir(profilesDir(), { recursive: true });
|
|
19506
20548
|
let name = p.name;
|
|
19507
20549
|
let n = 2;
|
|
19508
20550
|
while (await exists(getProfilePath(name))) {
|
|
@@ -19511,8 +20553,8 @@ async function saveProfile(p) {
|
|
|
19511
20553
|
}
|
|
19512
20554
|
const target = getProfilePath(name);
|
|
19513
20555
|
const tmp = `${target}.tmp`;
|
|
19514
|
-
await
|
|
19515
|
-
await
|
|
20556
|
+
await fs23.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
|
|
20557
|
+
await fs23.rename(tmp, target);
|
|
19516
20558
|
return name;
|
|
19517
20559
|
}
|
|
19518
20560
|
|
|
@@ -19758,7 +20800,7 @@ var OpenCommand = class extends M8tCommand {
|
|
|
19758
20800
|
// src/commands/dream/run.ts
|
|
19759
20801
|
import { Command as Command43, Option as Option41 } from "clipanion";
|
|
19760
20802
|
import { AzureCliCredential } from "@azure/identity";
|
|
19761
|
-
import { TableClient as
|
|
20803
|
+
import { TableClient as TableClient3 } from "@azure/data-tables";
|
|
19762
20804
|
import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
|
|
19763
20805
|
import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
|
|
19764
20806
|
|
|
@@ -20065,7 +21107,7 @@ var ConvFetchError = class extends Error {
|
|
|
20065
21107
|
};
|
|
20066
21108
|
|
|
20067
21109
|
// ../../packages/brain/engine/dist/esm/cursor.js
|
|
20068
|
-
import { TableClient } from "@azure/data-tables";
|
|
21110
|
+
import { TableClient as TableClient2 } from "@azure/data-tables";
|
|
20069
21111
|
|
|
20070
21112
|
// ../../packages/agent-ledger/dist/esm/row-key.js
|
|
20071
21113
|
var MAX_MS = 864e13;
|
|
@@ -20187,7 +21229,7 @@ async function commitCursorAdvance(plan, cursor) {
|
|
|
20187
21229
|
}
|
|
20188
21230
|
var CURSOR_TABLE_NAME = "BrainDreamCursor";
|
|
20189
21231
|
function makeTableCursor(credential2, tableEndpoint) {
|
|
20190
|
-
const client = new
|
|
21232
|
+
const client = new TableClient2(tableEndpoint, CURSOR_TABLE_NAME, credential2);
|
|
20191
21233
|
return new TableCursor(client);
|
|
20192
21234
|
}
|
|
20193
21235
|
function formatCursorPreview(cursor) {
|
|
@@ -20264,7 +21306,7 @@ function mapItems(raw) {
|
|
|
20264
21306
|
return acc.reverse();
|
|
20265
21307
|
}
|
|
20266
21308
|
function makeConversationSource(client, opts = {}) {
|
|
20267
|
-
const
|
|
21309
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
20268
21310
|
return {
|
|
20269
21311
|
async items(conversationId) {
|
|
20270
21312
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
@@ -20282,7 +21324,7 @@ function makeConversationSource(client, opts = {}) {
|
|
|
20282
21324
|
throw new ConvFetchError("auth", `auth failed reading ${conversationId}`);
|
|
20283
21325
|
const retryable = status === void 0 || status === TRANSIENT;
|
|
20284
21326
|
if (retryable && attempt < MAX_ATTEMPTS - 1) {
|
|
20285
|
-
await
|
|
21327
|
+
await sleep3(attempt);
|
|
20286
21328
|
continue;
|
|
20287
21329
|
}
|
|
20288
21330
|
throw new ConvFetchError("terminal", `exhausted ${String(MAX_ATTEMPTS)} retries reading ${conversationId}: ${e?.message ?? String(e)}`);
|
|
@@ -20681,7 +21723,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
|
|
|
20681
21723
|
// ../../packages/brain/engine/dist/esm/dream/writer.js
|
|
20682
21724
|
var API2 = "https://api.github.com";
|
|
20683
21725
|
var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
|
|
20684
|
-
var isAllowedWritePath = (
|
|
21726
|
+
var isAllowedWritePath = (path32) => WRITER_ALLOWED_PREFIX.test(path32) && !path32.split("/").includes("..");
|
|
20685
21727
|
var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
20686
21728
|
function parseFrontmatter(content) {
|
|
20687
21729
|
const m = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
@@ -20720,8 +21762,8 @@ function parseFrontmatter(content) {
|
|
|
20720
21762
|
function makeGitDataWriter(args) {
|
|
20721
21763
|
const doFetch = args.fetchImpl ?? fetch;
|
|
20722
21764
|
const repoPath = args.repository;
|
|
20723
|
-
async function gh(token, method,
|
|
20724
|
-
const res = await doFetch(`${API2}/repos/${repoPath}${
|
|
21765
|
+
async function gh(token, method, path32, body) {
|
|
21766
|
+
const res = await doFetch(`${API2}/repos/${repoPath}${path32}`, {
|
|
20725
21767
|
method,
|
|
20726
21768
|
headers: {
|
|
20727
21769
|
Authorization: `Bearer ${token}`,
|
|
@@ -20734,8 +21776,8 @@ function makeGitDataWriter(args) {
|
|
|
20734
21776
|
const text = await res.text();
|
|
20735
21777
|
return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
|
|
20736
21778
|
}
|
|
20737
|
-
async function readFileWith(token,
|
|
20738
|
-
const encodedPath =
|
|
21779
|
+
async function readFileWith(token, path32) {
|
|
21780
|
+
const encodedPath = path32.split("/").map(encodeURIComponent).join("/");
|
|
20739
21781
|
const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
|
|
20740
21782
|
if (r.status === 404)
|
|
20741
21783
|
return null;
|
|
@@ -20751,9 +21793,9 @@ function makeGitDataWriter(args) {
|
|
|
20751
21793
|
throw new Error(`fetchTip: HTTP ${String(r.status)}`);
|
|
20752
21794
|
return r.json.object.sha;
|
|
20753
21795
|
},
|
|
20754
|
-
async readFile(
|
|
21796
|
+
async readFile(path32) {
|
|
20755
21797
|
const token = await args.mintToken();
|
|
20756
|
-
return readFileWith(token,
|
|
21798
|
+
return readFileWith(token, path32);
|
|
20757
21799
|
},
|
|
20758
21800
|
async listMemory() {
|
|
20759
21801
|
const token = await args.mintToken();
|
|
@@ -20762,9 +21804,9 @@ function makeGitDataWriter(args) {
|
|
|
20762
21804
|
return [];
|
|
20763
21805
|
const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
|
|
20764
21806
|
const out = [];
|
|
20765
|
-
for (const
|
|
20766
|
-
const content = await readFileWith(token,
|
|
20767
|
-
out.push({ path:
|
|
21807
|
+
for (const path32 of paths) {
|
|
21808
|
+
const content = await readFileWith(token, path32);
|
|
21809
|
+
out.push({ path: path32, frontmatter: content ? parseFrontmatter(content) : {} });
|
|
20768
21810
|
}
|
|
20769
21811
|
return out;
|
|
20770
21812
|
},
|
|
@@ -21066,7 +22108,7 @@ function extractJson(text) {
|
|
|
21066
22108
|
return JSON.parse(candidate2.slice(start, end + 1));
|
|
21067
22109
|
}
|
|
21068
22110
|
async function propose(model, system, user, opts = {}) {
|
|
21069
|
-
const
|
|
22111
|
+
const sleep3 = opts.sleep ?? defaultSleep2;
|
|
21070
22112
|
const modelName = opts.model ?? MODEL_NAME_FALLBACK;
|
|
21071
22113
|
let inputTokens = 0;
|
|
21072
22114
|
let outputTokens = 0;
|
|
@@ -21082,7 +22124,7 @@ async function propose(model, system, user, opts = {}) {
|
|
|
21082
22124
|
} catch (e) {
|
|
21083
22125
|
lastErr = `parse: ${e.message}`;
|
|
21084
22126
|
if (attempt < MAX_MODEL_ATTEMPTS - 1) {
|
|
21085
|
-
await
|
|
22127
|
+
await sleep3(attempt);
|
|
21086
22128
|
continue;
|
|
21087
22129
|
}
|
|
21088
22130
|
break;
|
|
@@ -21091,7 +22133,7 @@ async function propose(model, system, user, opts = {}) {
|
|
|
21091
22133
|
if (!Array.isArray(rawDeltas)) {
|
|
21092
22134
|
lastErr = "schema: top-level { deltas: [] } missing";
|
|
21093
22135
|
if (attempt < MAX_MODEL_ATTEMPTS - 1) {
|
|
21094
|
-
await
|
|
22136
|
+
await sleep3(attempt);
|
|
21095
22137
|
continue;
|
|
21096
22138
|
}
|
|
21097
22139
|
break;
|
|
@@ -21201,7 +22243,7 @@ async function loadMemoryContext(brain) {
|
|
|
21201
22243
|
return {
|
|
21202
22244
|
files,
|
|
21203
22245
|
indexEntries,
|
|
21204
|
-
originOf: (
|
|
22246
|
+
originOf: (path32) => originByPath.get(path32) ?? "worker"
|
|
21205
22247
|
};
|
|
21206
22248
|
}
|
|
21207
22249
|
|
|
@@ -21222,14 +22264,14 @@ function checkEvidenceMembership(delta, harvested) {
|
|
|
21222
22264
|
}
|
|
21223
22265
|
var BatchAbort = class extends Error {
|
|
21224
22266
|
path;
|
|
21225
|
-
constructor(
|
|
22267
|
+
constructor(path32, message) {
|
|
21226
22268
|
super(message);
|
|
21227
|
-
this.path =
|
|
22269
|
+
this.path = path32;
|
|
21228
22270
|
this.name = "BatchAbort";
|
|
21229
22271
|
}
|
|
21230
22272
|
};
|
|
21231
|
-
var isIndexFile = (
|
|
21232
|
-
var isMemoryIndex = (
|
|
22273
|
+
var isIndexFile = (path32) => /(^|\/)[^/]*_index\.md$/.test(path32);
|
|
22274
|
+
var isMemoryIndex = (path32) => path32 === "memory/MEMORY.md";
|
|
21233
22275
|
function targetPath(delta) {
|
|
21234
22276
|
switch (delta.verb) {
|
|
21235
22277
|
case "new":
|
|
@@ -21246,29 +22288,29 @@ function targetPath(delta) {
|
|
|
21246
22288
|
}
|
|
21247
22289
|
async function checkOriginTier(delta, lookup) {
|
|
21248
22290
|
const evidence = evidenceOf(delta) ?? [];
|
|
21249
|
-
const
|
|
21250
|
-
if (
|
|
22291
|
+
const path32 = targetPath(delta);
|
|
22292
|
+
if (path32 === null)
|
|
21251
22293
|
return { ok: true };
|
|
21252
|
-
if (isIndexFile(
|
|
21253
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
22294
|
+
if (isIndexFile(path32) && !isMemoryIndex(path32)) {
|
|
22295
|
+
return { ok: false, flagged: { kind: "flagged", path: path32, tension: `refused: ${path32} is an index file (only memory/MEMORY.md is editable)`, evidence } };
|
|
21254
22296
|
}
|
|
21255
|
-
const target = await lookup(
|
|
21256
|
-
const isStructural =
|
|
22297
|
+
const target = await lookup(path32);
|
|
22298
|
+
const isStructural = path32.startsWith("references/");
|
|
21257
22299
|
const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
|
|
21258
22300
|
const editable = origin === "worker" || origin === "dream";
|
|
21259
22301
|
if (editable)
|
|
21260
22302
|
return { ok: true };
|
|
21261
22303
|
const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
|
|
21262
22304
|
if (removesOperatorTarget) {
|
|
21263
|
-
throw new BatchAbort(
|
|
22305
|
+
throw new BatchAbort(path32, `supersede/retract OLD target ${path32} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
|
|
21264
22306
|
}
|
|
21265
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
22307
|
+
return { ok: false, flagged: { kind: "flagged", path: path32, tension: `operator-origin (${origin}); flag-only`, evidence } };
|
|
21266
22308
|
}
|
|
21267
22309
|
var ALLOWED_MEMORY = /^memory\/.+\.md$/;
|
|
21268
22310
|
var ALLOWED_INBOX = /^inbox\//;
|
|
21269
22311
|
var ALLOWED_QUARANTINE = /^quarantine\//;
|
|
21270
22312
|
var ALLOWED_FLAG = /^memory\//;
|
|
21271
|
-
var hasTraversal = (
|
|
22313
|
+
var hasTraversal = (path32) => path32.split("/").includes("..");
|
|
21272
22314
|
function checkPathAllowed(delta) {
|
|
21273
22315
|
const evidence = evidenceOf(delta) ?? [];
|
|
21274
22316
|
const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
|
|
@@ -21375,7 +22417,7 @@ ${inject}
|
|
|
21375
22417
|
---
|
|
21376
22418
|
`);
|
|
21377
22419
|
}
|
|
21378
|
-
var indexLine = (
|
|
22420
|
+
var indexLine = (path32, title, date, tags) => `- \`${path32}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
|
|
21379
22421
|
function splitIndex(index) {
|
|
21380
22422
|
const all = index.split("\n");
|
|
21381
22423
|
const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
|
|
@@ -21548,16 +22590,16 @@ function defaultDreamSeams(opts = {}) {
|
|
|
21548
22590
|
const mem = await memory(deps.brain);
|
|
21549
22591
|
const harvested = harvestedIds(input);
|
|
21550
22592
|
const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
|
|
21551
|
-
const lookup = (
|
|
22593
|
+
const lookup = (path32) => Promise.resolve(mem.files.find((f) => f.path === path32) ?? null);
|
|
21552
22594
|
const digest = [...pendingRejected];
|
|
21553
22595
|
let changes = [];
|
|
21554
22596
|
let indexEdit;
|
|
21555
22597
|
try {
|
|
21556
22598
|
for (const raw of deltas) {
|
|
21557
22599
|
const delta = forcePolicyQuarantine(raw);
|
|
21558
|
-
const
|
|
21559
|
-
if (!
|
|
21560
|
-
digest.push(
|
|
22600
|
+
const path32 = checkPathAllowed(delta);
|
|
22601
|
+
if (!path32.ok) {
|
|
22602
|
+
digest.push(path32.rejected);
|
|
21561
22603
|
continue;
|
|
21562
22604
|
}
|
|
21563
22605
|
const ev = checkEvidenceMembership(delta, harvested);
|
|
@@ -21752,7 +22794,7 @@ async function emitDigest(input, deps, digest) {
|
|
|
21752
22794
|
}
|
|
21753
22795
|
|
|
21754
22796
|
// ../../packages/brain/engine/dist/esm/dream/config.js
|
|
21755
|
-
import { parse as
|
|
22797
|
+
import { parse as parseYaml12 } from "yaml";
|
|
21756
22798
|
var DEFAULT_DREAM_MODEL = "gpt-5.4";
|
|
21757
22799
|
function asCadence(v) {
|
|
21758
22800
|
return v === "nightly" ? "nightly" : "off";
|
|
@@ -21760,7 +22802,7 @@ function asCadence(v) {
|
|
|
21760
22802
|
function parseDreamerConfig(rawYaml, env) {
|
|
21761
22803
|
let parsed;
|
|
21762
22804
|
try {
|
|
21763
|
-
parsed =
|
|
22805
|
+
parsed = parseYaml12(rawYaml);
|
|
21764
22806
|
} catch {
|
|
21765
22807
|
return { enabled: false, cadence: "off", model: env.M8T_DREAM_MODEL ?? DEFAULT_DREAM_MODEL };
|
|
21766
22808
|
}
|
|
@@ -21774,31 +22816,31 @@ function parseDreamerConfig(rawYaml, env) {
|
|
|
21774
22816
|
}
|
|
21775
22817
|
|
|
21776
22818
|
// ../../packages/brain/engine/dist/esm/cost-report/config.js
|
|
21777
|
-
import { parse as
|
|
22819
|
+
import { parse as parseYaml13 } from "yaml";
|
|
21778
22820
|
|
|
21779
22821
|
// ../../packages/brain/engine/dist/esm/librarian/codify/persistence-guard.js
|
|
21780
22822
|
var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
|
|
21781
22823
|
|
|
21782
22824
|
// ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
|
|
21783
|
-
import { parse as
|
|
22825
|
+
import { parse as parseYaml14, stringify as stringifyYaml5 } from "yaml";
|
|
21784
22826
|
|
|
21785
22827
|
// ../../packages/brain/engine/dist/esm/librarian/config.js
|
|
21786
|
-
import { parse as
|
|
22828
|
+
import { parse as parseYaml15 } from "yaml";
|
|
21787
22829
|
|
|
21788
22830
|
// src/lib/dream-discovery.ts
|
|
21789
22831
|
init_http();
|
|
21790
22832
|
init_errors();
|
|
21791
|
-
var
|
|
21792
|
-
var
|
|
21793
|
-
var
|
|
22833
|
+
var ARM4 = "https://management.azure.com";
|
|
22834
|
+
var ARM_SCOPE8 = "https://management.azure.com/.default";
|
|
22835
|
+
var STORAGE_API2 = "2023-05-01";
|
|
21794
22836
|
var LA_API = "2023-09-01";
|
|
21795
22837
|
async function discoverLedgerResources(opts) {
|
|
21796
22838
|
const { credential: credential2, subscriptionId, resourceGroup } = opts;
|
|
21797
22839
|
const storage = await authedJson({
|
|
21798
22840
|
credential: credential2,
|
|
21799
|
-
scope:
|
|
22841
|
+
scope: ARM_SCOPE8,
|
|
21800
22842
|
method: "GET",
|
|
21801
|
-
url: `${
|
|
22843
|
+
url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API2}`
|
|
21802
22844
|
}) ?? {};
|
|
21803
22845
|
const accounts = (storage.value ?? []).filter((a) => a.properties?.primaryEndpoints?.table);
|
|
21804
22846
|
const ledgerAcct = accounts.find((a) => (a.tags?.["m8t-stack:role"] ?? a.properties?.tags?.["m8t-stack:role"]) === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
|
|
@@ -21812,9 +22854,9 @@ async function discoverLedgerResources(opts) {
|
|
|
21812
22854
|
}
|
|
21813
22855
|
const laList = await authedJson({
|
|
21814
22856
|
credential: credential2,
|
|
21815
|
-
scope:
|
|
22857
|
+
scope: ARM_SCOPE8,
|
|
21816
22858
|
method: "GET",
|
|
21817
|
-
url: `${
|
|
22859
|
+
url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.OperationalInsights/workspaces?api-version=${LA_API}`
|
|
21818
22860
|
}) ?? {};
|
|
21819
22861
|
const ws = laList.value ?? [];
|
|
21820
22862
|
const workspaceId = (ws.length === 1 ? ws[0] : ws.find((w) => w.properties?.customerId))?.properties?.customerId;
|
|
@@ -22108,7 +23150,7 @@ AppDependencies
|
|
|
22108
23150
|
};
|
|
22109
23151
|
}
|
|
22110
23152
|
function buildLiveSources(context, credential2) {
|
|
22111
|
-
const ledgerClient = new
|
|
23153
|
+
const ledgerClient = new TableClient3(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
|
|
22112
23154
|
const ledger = makeLedgerSource(
|
|
22113
23155
|
async (pk, sinceTs) => fetchLedgerRows(
|
|
22114
23156
|
{ workers: [pk], source: "all", from: sinceTs, to: "9999-12-31T23:59:59.999Z" },
|
|
@@ -22777,9 +23819,9 @@ ${colors.error(" " + why)}
|
|
|
22777
23819
|
}
|
|
22778
23820
|
|
|
22779
23821
|
// src/commands/bootstrap/launch.ts
|
|
22780
|
-
import * as
|
|
22781
|
-
import * as
|
|
22782
|
-
import * as
|
|
23822
|
+
import * as fs26 from "fs";
|
|
23823
|
+
import * as os13 from "os";
|
|
23824
|
+
import * as path27 from "path";
|
|
22783
23825
|
import { Command as Command47, Option as Option45 } from "clipanion";
|
|
22784
23826
|
init_errors();
|
|
22785
23827
|
|
|
@@ -22925,9 +23967,9 @@ async function kickInstaller(s) {
|
|
|
22925
23967
|
|
|
22926
23968
|
// src/lib/bootstrap-status.ts
|
|
22927
23969
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
22928
|
-
import * as
|
|
22929
|
-
import * as
|
|
22930
|
-
import * as
|
|
23970
|
+
import * as fs24 from "fs/promises";
|
|
23971
|
+
import * as os11 from "os";
|
|
23972
|
+
import * as path25 from "path";
|
|
22931
23973
|
init_errors();
|
|
22932
23974
|
var STATUS_CONTAINER = "status";
|
|
22933
23975
|
var STATUS_BLOB = "status.json";
|
|
@@ -22966,7 +24008,7 @@ async function readStatusBlob(opts) {
|
|
|
22966
24008
|
cause: e
|
|
22967
24009
|
});
|
|
22968
24010
|
}
|
|
22969
|
-
const tmpFile =
|
|
24011
|
+
const tmpFile = path25.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
|
|
22970
24012
|
try {
|
|
22971
24013
|
await runAz([
|
|
22972
24014
|
"storage",
|
|
@@ -22985,7 +24027,7 @@ async function readStatusBlob(opts) {
|
|
|
22985
24027
|
"--no-progress",
|
|
22986
24028
|
"--only-show-errors"
|
|
22987
24029
|
]);
|
|
22988
|
-
const raw = await
|
|
24030
|
+
const raw = await fs24.readFile(tmpFile, "utf8");
|
|
22989
24031
|
return JSON.parse(raw.trim());
|
|
22990
24032
|
} catch (e) {
|
|
22991
24033
|
throw new LocalCliError({
|
|
@@ -22995,34 +24037,34 @@ async function readStatusBlob(opts) {
|
|
|
22995
24037
|
cause: e
|
|
22996
24038
|
});
|
|
22997
24039
|
} finally {
|
|
22998
|
-
await
|
|
24040
|
+
await fs24.rm(tmpFile, { force: true });
|
|
22999
24041
|
}
|
|
23000
24042
|
}
|
|
23001
24043
|
|
|
23002
24044
|
// src/lib/bootstrap-state.ts
|
|
23003
|
-
import * as
|
|
23004
|
-
import * as
|
|
23005
|
-
import * as
|
|
24045
|
+
import * as fs25 from "fs/promises";
|
|
24046
|
+
import * as os12 from "os";
|
|
24047
|
+
import * as path26 from "path";
|
|
23006
24048
|
function stateDir(home) {
|
|
23007
|
-
return
|
|
24049
|
+
return path26.join(home, ".m8t-stack");
|
|
23008
24050
|
}
|
|
23009
24051
|
function statePath(home) {
|
|
23010
|
-
return
|
|
24052
|
+
return path26.join(stateDir(home), "bootstrap.json");
|
|
23011
24053
|
}
|
|
23012
|
-
async function readBootstrapState(home =
|
|
24054
|
+
async function readBootstrapState(home = os12.homedir()) {
|
|
23013
24055
|
try {
|
|
23014
|
-
const raw = await
|
|
24056
|
+
const raw = await fs25.readFile(statePath(home), "utf8");
|
|
23015
24057
|
return JSON.parse(raw);
|
|
23016
24058
|
} catch (e) {
|
|
23017
24059
|
if (e.code === "ENOENT") return null;
|
|
23018
24060
|
throw e;
|
|
23019
24061
|
}
|
|
23020
24062
|
}
|
|
23021
|
-
async function writeBootstrapState(state, home =
|
|
23022
|
-
await
|
|
24063
|
+
async function writeBootstrapState(state, home = os12.homedir()) {
|
|
24064
|
+
await fs25.mkdir(stateDir(home), { recursive: true });
|
|
23023
24065
|
const tmp = `${statePath(home)}.tmp`;
|
|
23024
|
-
await
|
|
23025
|
-
await
|
|
24066
|
+
await fs25.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
24067
|
+
await fs25.rename(tmp, statePath(home));
|
|
23026
24068
|
}
|
|
23027
24069
|
|
|
23028
24070
|
// src/commands/bootstrap/launch.ts
|
|
@@ -23062,12 +24104,12 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
23062
24104
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
23063
24105
|
const out = (m) => this.context.stderr.write(` ${colors.dim(m)}
|
|
23064
24106
|
`);
|
|
23065
|
-
const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds :
|
|
24107
|
+
const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path27.join(os13.homedir(), ".m8t-stack", "github-app.json");
|
|
23066
24108
|
let githubApp;
|
|
23067
|
-
if (
|
|
24109
|
+
if (fs26.existsSync(credsPath)) {
|
|
23068
24110
|
let parsed;
|
|
23069
24111
|
try {
|
|
23070
|
-
parsed = JSON.parse(
|
|
24112
|
+
parsed = JSON.parse(fs26.readFileSync(credsPath, "utf8"));
|
|
23071
24113
|
} catch {
|
|
23072
24114
|
throw new LocalCliError({
|
|
23073
24115
|
code: "GITHUB_APP_CREDS_INVALID",
|
|
@@ -23077,7 +24119,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
23077
24119
|
}
|
|
23078
24120
|
let pem;
|
|
23079
24121
|
try {
|
|
23080
|
-
pem =
|
|
24122
|
+
pem = fs26.readFileSync(parsed.pemPath, "utf8");
|
|
23081
24123
|
} catch {
|
|
23082
24124
|
throw new LocalCliError({
|
|
23083
24125
|
code: "GITHUB_APP_PEM_MISSING",
|
|
@@ -23197,7 +24239,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
23197
24239
|
typeof this.output === "string" ? this.output : void 0,
|
|
23198
24240
|
this.context.stdout
|
|
23199
24241
|
);
|
|
23200
|
-
const
|
|
24242
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23201
24243
|
const watch = this.watch === true;
|
|
23202
24244
|
for (; ; ) {
|
|
23203
24245
|
let doc;
|
|
@@ -23207,7 +24249,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
23207
24249
|
if (watch && e instanceof LocalCliError && e.code === "BOOTSTRAP_STATUS_UNREADABLE") {
|
|
23208
24250
|
if (mode === "pretty") this.context.stderr.write(` ${colors.dim("waiting for the installer to start\u2026")}
|
|
23209
24251
|
`);
|
|
23210
|
-
await
|
|
24252
|
+
await sleep3(1e4);
|
|
23211
24253
|
continue;
|
|
23212
24254
|
}
|
|
23213
24255
|
throw e;
|
|
@@ -23232,7 +24274,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
23232
24274
|
);
|
|
23233
24275
|
return 1;
|
|
23234
24276
|
}
|
|
23235
|
-
await
|
|
24277
|
+
await sleep3(1e4);
|
|
23236
24278
|
}
|
|
23237
24279
|
}
|
|
23238
24280
|
};
|
|
@@ -23402,17 +24444,17 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
|
|
|
23402
24444
|
};
|
|
23403
24445
|
|
|
23404
24446
|
// src/commands/bootstrap/finish.ts
|
|
23405
|
-
import * as
|
|
23406
|
-
import * as
|
|
23407
|
-
import * as
|
|
24447
|
+
import * as fs27 from "fs/promises";
|
|
24448
|
+
import * as os15 from "os";
|
|
24449
|
+
import * as path29 from "path";
|
|
23408
24450
|
import { Command as Command50, Option as Option48 } from "clipanion";
|
|
23409
24451
|
init_errors();
|
|
23410
24452
|
|
|
23411
24453
|
// src/lib/company-profile-seed.ts
|
|
23412
24454
|
import { spawn as spawn6 } from "child_process";
|
|
23413
|
-
import { closeSync, openSync, readFileSync as
|
|
23414
|
-
import * as
|
|
23415
|
-
import * as
|
|
24455
|
+
import { closeSync, openSync, readFileSync as readFileSync18 } from "fs";
|
|
24456
|
+
import * as os14 from "os";
|
|
24457
|
+
import * as path28 from "path";
|
|
23416
24458
|
init_errors();
|
|
23417
24459
|
|
|
23418
24460
|
// src/lib/onboarding-profile.ts
|
|
@@ -23593,9 +24635,9 @@ function composeFounderIdentityNote(id) {
|
|
|
23593
24635
|
|
|
23594
24636
|
// src/lib/company-profile-seed.ts
|
|
23595
24637
|
function readGithubAppCreds(credsPath) {
|
|
23596
|
-
const p = credsPath ??
|
|
24638
|
+
const p = credsPath ?? path28.join(os14.homedir(), ".m8t-stack", "github-app.json");
|
|
23597
24639
|
try {
|
|
23598
|
-
return JSON.parse(
|
|
24640
|
+
return JSON.parse(readFileSync18(p, "utf8"));
|
|
23599
24641
|
} catch {
|
|
23600
24642
|
return null;
|
|
23601
24643
|
}
|
|
@@ -23623,7 +24665,7 @@ async function resolveSeedContext(opts) {
|
|
|
23623
24665
|
async function applyProfileToBrain(args) {
|
|
23624
24666
|
const token = await mintInstallationTokenFromPem({
|
|
23625
24667
|
appId: args.appCreds.appId,
|
|
23626
|
-
privateKeyPem:
|
|
24668
|
+
privateKeyPem: readFileSync18(args.appCreds.pemPath, "utf8"),
|
|
23627
24669
|
installationId: args.appCreds.installationId,
|
|
23628
24670
|
fetchImpl: args.fetchImpl
|
|
23629
24671
|
});
|
|
@@ -23658,7 +24700,7 @@ async function applyProfileToBrain(args) {
|
|
|
23658
24700
|
});
|
|
23659
24701
|
}
|
|
23660
24702
|
function spawnDetachedSeedWatch() {
|
|
23661
|
-
const logPath =
|
|
24703
|
+
const logPath = path28.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
|
|
23662
24704
|
const fd = openSync(logPath, "a");
|
|
23663
24705
|
try {
|
|
23664
24706
|
const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
|
|
@@ -23757,9 +24799,9 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23757
24799
|
});
|
|
23758
24800
|
}
|
|
23759
24801
|
const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
|
|
23760
|
-
const markerDir =
|
|
23761
|
-
await
|
|
23762
|
-
await
|
|
24802
|
+
const markerDir = path29.join(os15.homedir(), ".m8t-stack");
|
|
24803
|
+
await fs27.mkdir(markerDir, { recursive: true });
|
|
24804
|
+
await fs27.writeFile(path29.join(markerDir, "repo-root"), `${repoRoot}
|
|
23763
24805
|
`, "utf8");
|
|
23764
24806
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
|
|
23765
24807
|
const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
|
|
@@ -23795,7 +24837,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23795
24837
|
}
|
|
23796
24838
|
let brainOrg = null;
|
|
23797
24839
|
try {
|
|
23798
|
-
const credsRaw = await
|
|
24840
|
+
const credsRaw = await fs27.readFile(path29.join(markerDir, "github-app.json"), "utf8");
|
|
23799
24841
|
const creds = JSON.parse(credsRaw);
|
|
23800
24842
|
brainOrg = typeof creds.org === "string" ? creds.org : null;
|
|
23801
24843
|
} catch {
|
|
@@ -23826,18 +24868,18 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
|
|
|
23826
24868
|
};
|
|
23827
24869
|
|
|
23828
24870
|
// src/commands/bootstrap/ui.ts
|
|
23829
|
-
import * as
|
|
23830
|
-
import * as
|
|
23831
|
-
import * as
|
|
24871
|
+
import * as fs29 from "fs";
|
|
24872
|
+
import * as os17 from "os";
|
|
24873
|
+
import * as path31 from "path";
|
|
23832
24874
|
import { Command as Command51, Option as Option49 } from "clipanion";
|
|
23833
|
-
import { DefaultAzureCredential as
|
|
24875
|
+
import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
|
|
23834
24876
|
init_errors();
|
|
23835
24877
|
|
|
23836
24878
|
// src/lib/bootstrap-ui.ts
|
|
23837
|
-
import * as
|
|
24879
|
+
import * as fs28 from "fs";
|
|
23838
24880
|
import * as net from "net";
|
|
23839
|
-
import * as
|
|
23840
|
-
import * as
|
|
24881
|
+
import * as os16 from "os";
|
|
24882
|
+
import * as path30 from "path";
|
|
23841
24883
|
import { spawn as spawn7 } from "child_process";
|
|
23842
24884
|
init_errors();
|
|
23843
24885
|
init_rbac();
|
|
@@ -23845,7 +24887,7 @@ async function resolveFoundryEndpointWithWait(args, opts = {}) {
|
|
|
23845
24887
|
const pollMs = opts.pollMs ?? 1e4;
|
|
23846
24888
|
const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
|
|
23847
24889
|
const deadline = Date.now() + timeoutMs;
|
|
23848
|
-
const
|
|
24890
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23849
24891
|
for (; ; ) {
|
|
23850
24892
|
try {
|
|
23851
24893
|
const p = await resolveFoundryProject({
|
|
@@ -23879,7 +24921,7 @@ async function resolveFoundryEndpointWithWait(args, opts = {}) {
|
|
|
23879
24921
|
});
|
|
23880
24922
|
}
|
|
23881
24923
|
opts.onWait?.("waiting for Foundry-create to finish\u2026");
|
|
23882
|
-
await
|
|
24924
|
+
await sleep3(pollMs);
|
|
23883
24925
|
}
|
|
23884
24926
|
}
|
|
23885
24927
|
}
|
|
@@ -23904,9 +24946,9 @@ async function ensureFounderFoundryRole(args) {
|
|
|
23904
24946
|
});
|
|
23905
24947
|
}
|
|
23906
24948
|
function writeWebEnvLocal(args) {
|
|
23907
|
-
const envPath =
|
|
23908
|
-
if (
|
|
23909
|
-
|
|
24949
|
+
const envPath = path30.join(args.repoRoot, "apps", "web", ".env.local");
|
|
24950
|
+
if (fs28.existsSync(envPath)) {
|
|
24951
|
+
fs28.copyFileSync(envPath, envPath + ".bak");
|
|
23910
24952
|
}
|
|
23911
24953
|
const body = [
|
|
23912
24954
|
"# Written by `m8t bootstrap ui` \u2014 onboarding chat-only run (Simple Stacey).",
|
|
@@ -23920,7 +24962,7 @@ function writeWebEnvLocal(args) {
|
|
|
23920
24962
|
"NEXT_PUBLIC_M8T_ONBOARDING=1",
|
|
23921
24963
|
""
|
|
23922
24964
|
].join("\n");
|
|
23923
|
-
|
|
24965
|
+
fs28.writeFileSync(envPath, body, "utf8");
|
|
23924
24966
|
return envPath;
|
|
23925
24967
|
}
|
|
23926
24968
|
function assertNodeVersion(versionString = process.version) {
|
|
@@ -23961,11 +25003,11 @@ async function installWebDeps(repoRoot) {
|
|
|
23961
25003
|
});
|
|
23962
25004
|
await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
|
|
23963
25005
|
}
|
|
23964
|
-
function onboardingUiPaths(home =
|
|
23965
|
-
const dir =
|
|
25006
|
+
function onboardingUiPaths(home = os16.homedir()) {
|
|
25007
|
+
const dir = path30.join(home, ".m8t-stack");
|
|
23966
25008
|
return {
|
|
23967
|
-
logPath:
|
|
23968
|
-
pidPath:
|
|
25009
|
+
logPath: path30.join(dir, "onboarding-ui.log"),
|
|
25010
|
+
pidPath: path30.join(dir, "onboarding-ui.pid")
|
|
23969
25011
|
};
|
|
23970
25012
|
}
|
|
23971
25013
|
async function serveOnboardingUiDetached(args) {
|
|
@@ -23989,8 +25031,8 @@ async function serveOnboardingUiDetached(args) {
|
|
|
23989
25031
|
if (await isPortOpen()) {
|
|
23990
25032
|
return { alreadyRunning: true, logPath };
|
|
23991
25033
|
}
|
|
23992
|
-
|
|
23993
|
-
const fd =
|
|
25034
|
+
fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
|
|
25035
|
+
const fd = fs28.openSync(logPath, "a");
|
|
23994
25036
|
const child = spawn7("pnpm", ["--filter", "web", "dev"], {
|
|
23995
25037
|
cwd: args.repoRoot,
|
|
23996
25038
|
detached: true,
|
|
@@ -23998,7 +25040,7 @@ async function serveOnboardingUiDetached(args) {
|
|
|
23998
25040
|
env: { ...process.env, PORT: args.port },
|
|
23999
25041
|
shell: false
|
|
24000
25042
|
});
|
|
24001
|
-
|
|
25043
|
+
fs28.closeSync(fd);
|
|
24002
25044
|
if (child.pid == null) {
|
|
24003
25045
|
throw new LocalCliError({
|
|
24004
25046
|
code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
|
|
@@ -24007,11 +25049,11 @@ async function serveOnboardingUiDetached(args) {
|
|
|
24007
25049
|
});
|
|
24008
25050
|
}
|
|
24009
25051
|
child.unref();
|
|
24010
|
-
|
|
25052
|
+
fs28.writeFileSync(pidPath, String(child.pid), "utf8");
|
|
24011
25053
|
const deadline = Date.now() + 45e3;
|
|
24012
|
-
const
|
|
25054
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24013
25055
|
while (Date.now() < deadline) {
|
|
24014
|
-
await
|
|
25056
|
+
await sleep3(500);
|
|
24015
25057
|
if (await isPortOpen()) break;
|
|
24016
25058
|
}
|
|
24017
25059
|
return { alreadyRunning: false, logPath };
|
|
@@ -24026,11 +25068,11 @@ function autoOpenOnboardingUi(url, open = tryOpenUrl) {
|
|
|
24026
25068
|
} catch {
|
|
24027
25069
|
}
|
|
24028
25070
|
}
|
|
24029
|
-
function stopOnboardingUi(home =
|
|
25071
|
+
function stopOnboardingUi(home = os16.homedir()) {
|
|
24030
25072
|
const { pidPath } = onboardingUiPaths(home);
|
|
24031
25073
|
let pidStr;
|
|
24032
25074
|
try {
|
|
24033
|
-
pidStr =
|
|
25075
|
+
pidStr = fs28.readFileSync(pidPath, "utf8").trim();
|
|
24034
25076
|
} catch {
|
|
24035
25077
|
return false;
|
|
24036
25078
|
}
|
|
@@ -24043,7 +25085,7 @@ function stopOnboardingUi(home = os15.homedir()) {
|
|
|
24043
25085
|
}
|
|
24044
25086
|
}
|
|
24045
25087
|
try {
|
|
24046
|
-
|
|
25088
|
+
fs28.unlinkSync(pidPath);
|
|
24047
25089
|
} catch {
|
|
24048
25090
|
}
|
|
24049
25091
|
return true;
|
|
@@ -24159,10 +25201,10 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
24159
25201
|
this.context.stderr.write(` ${colors.dim(m)}
|
|
24160
25202
|
`);
|
|
24161
25203
|
};
|
|
24162
|
-
if (
|
|
25204
|
+
if (fs29.existsSync(path31.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
|
|
24163
25205
|
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."));
|
|
24164
25206
|
}
|
|
24165
|
-
const credential2 = new
|
|
25207
|
+
const credential2 = new DefaultAzureCredential19();
|
|
24166
25208
|
const account = await getAzAccount();
|
|
24167
25209
|
assertNodeVersion();
|
|
24168
25210
|
out("waiting for Foundry (the installer's foundry-create phase)\u2026");
|
|
@@ -24265,7 +25307,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
24265
25307
|
const parsedTimeout = Number(rawTimeout);
|
|
24266
25308
|
const timeoutMin = rawTimeout !== "" && Number.isFinite(parsedTimeout) ? parsedTimeout : 20;
|
|
24267
25309
|
const deadline = Date.now() + timeoutMin * 6e4;
|
|
24268
|
-
const
|
|
25310
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24269
25311
|
for (; ; ) {
|
|
24270
25312
|
const token = await getFoundryToken();
|
|
24271
25313
|
const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
|
|
@@ -24288,7 +25330,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
24288
25330
|
}
|
|
24289
25331
|
this.context.stderr.write(` ${colors.dim("waiting for the questionnaire to complete\u2026")}
|
|
24290
25332
|
`);
|
|
24291
|
-
await
|
|
25333
|
+
await sleep3(2e4);
|
|
24292
25334
|
}
|
|
24293
25335
|
}
|
|
24294
25336
|
};
|